Integrate battles with the overworld UI and validate browser flows
Checks / web (push) Canceled after 0s
Checks / web (push) Canceled after 0s
- Drive wild battles through the real UI in Chromium and WebKit using a test-only encounter fixture: victory with level-up, escape, whiteout, mid-battle backup restore and unsupported-effect rollback. - Add keyboard and on-screen D-pad navigation for battle menus. - Accept pending stat experience in saves (awarded without CalcMonStats). - Commit whiteout healing, money loss and destination callbacks together; aborted scripts restore state and map. - Give long notices their own status-bar row on narrow screens. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c08d250cf6
commit
11d1153802
11 files changed
+462
-13
No files matched your search
+47
@@ -7,6 +7,7 @@ import type { Choice, GameWorld } from "./game/world/world";
|
||||
import { drawTile } from "./game/rendering/pixels";
|
||||
import { PokemonData } from "./game/rom/pokemon";
|
||||
import type { Palette } from "./game/world/types";
|
||||
import type { Button } from "./input/types";
|
||||
import { NICKNAME_PATTERN } from "./game/party/pokemon";
|
||||
import { GameSession } from "./app/session";
|
||||
import { InputController } from "./input/controller";
|
||||
@@ -179,6 +180,41 @@ function drawPic(target: HTMLCanvasElement, tiles: Uint8Array, size: number, pal
|
||||
const battlePics = { foe: "", player: "" };
|
||||
let battleMenuKey = "";
|
||||
let showMoves = false;
|
||||
let battleCursor = 0;
|
||||
let battleSelectionKey = "";
|
||||
|
||||
function battleButtons() {
|
||||
return [...get("battle-menu").querySelectorAll<HTMLButtonElement>("button:not(:disabled)")];
|
||||
}
|
||||
function highlightBattleChoice() {
|
||||
battleButtons().forEach((button, index) => {
|
||||
button.classList.toggle("battle-selected", index === battleCursor);
|
||||
});
|
||||
}
|
||||
/** Keyboard and on-screen D-pad share the same menu buttons as pointer input. */
|
||||
function handleBattleInput(button: Button): boolean {
|
||||
const world = runtime.world;
|
||||
if (!world?.battle) return false;
|
||||
if (button === "b" && showMoves) {
|
||||
showMoves = false;
|
||||
return true;
|
||||
}
|
||||
const buttons = battleButtons();
|
||||
if (!buttons.length) return false;
|
||||
if (button === "a") {
|
||||
buttons[battleCursor]?.click();
|
||||
return true;
|
||||
}
|
||||
if (["up", "down", "left", "right"].includes(button)) {
|
||||
const delta = button === "up" || button === "left" ? -1 : 1;
|
||||
battleCursor = (battleCursor + delta + buttons.length) % buttons.length;
|
||||
if (world.battle.prompt?.kind === "yesno")
|
||||
world.battle.selected = battleCursor === 0 ? 1 : 0;
|
||||
highlightBattleChoice();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/** The battle screen: pictures, HUDs, messages and the current choice. */
|
||||
function showBattle(world: GameWorld) {
|
||||
const view = world.battle;
|
||||
@@ -187,6 +223,8 @@ function showBattle(world: GameWorld) {
|
||||
if (!view) {
|
||||
battlePics.foe = battlePics.player = battleMenuKey = "";
|
||||
showMoves = false;
|
||||
battleCursor = 0;
|
||||
battleSelectionKey = "";
|
||||
return;
|
||||
}
|
||||
const data = world.assets.pokemon;
|
||||
@@ -221,6 +259,12 @@ function showBattle(world: GameWorld) {
|
||||
}
|
||||
get("battle-text").textContent = view.message?.text ?? "";
|
||||
const prompt = view.prompt;
|
||||
const selectionKey = `${prompt?.kind ?? "text"}:${showMoves}`;
|
||||
if (selectionKey !== battleSelectionKey) {
|
||||
battleCursor = 0;
|
||||
battleSelectionKey = selectionKey;
|
||||
}
|
||||
if (prompt?.kind === "yesno") battleCursor = view.selected === 1 ? 0 : 1;
|
||||
const key = JSON.stringify([prompt, view.selected, showMoves, own?.pp, view.message?.prompt]);
|
||||
if (key === battleMenuKey) return;
|
||||
battleMenuKey = key;
|
||||
@@ -293,6 +337,8 @@ function showBattle(world: GameWorld) {
|
||||
buttons.push(button("Continue ▶", () => runtime.advanceDialogue(), { wide: true }));
|
||||
}
|
||||
menu.replaceChildren(...buttons);
|
||||
battleCursor = Math.min(battleCursor, Math.max(0, battleButtons().length - 1));
|
||||
highlightBattleChoice();
|
||||
}
|
||||
function setHp(bar: HTMLElement, hp: number, max: number) {
|
||||
const fraction = max ? hp / max : 0;
|
||||
@@ -348,6 +394,7 @@ const runtime = new NativeRuntime(
|
||||
? "kris"
|
||||
: "chris",
|
||||
}),
|
||||
handleBattleInput,
|
||||
);
|
||||
const session = new GameSession(
|
||||
runtime,
|
||||
|
||||
Reference in new issue
Block a user