Finish the opening milestone's switching, items and acceptance run
- Decode evolution lists from the ROM; all 251 match the pinned PRET source. - Compare CalcMonStats and CalcExpAtLevel against the original routines in the reference emulator. - Add a continuous opening playthrough from the intro to the catching tutorial, reloading a JSON save after every stage. - Port player switching in battle: the PKMN menu, forced choices after a faint, Shift mode's offer and double faints, with the original texts. - Port HP and status healing items in battle through the ROM's ItemEffects, HealingHPAmounts and StatusHealingActions tables. - Add the battle party menu and the Items pocket to the UI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
eb03a20d8f
commit
f8e0f9c65a
20 files changed
+1902
-96
No files matched your search
+86
-12
@@ -379,8 +379,11 @@ function drawPic(
|
||||
const battlePics = { foe: "", player: "" };
|
||||
let battleMenuKey = "";
|
||||
let showMoves = false;
|
||||
/** BattlePack's Balls pocket, the only pocket usable in battle so far. */
|
||||
/** BattlePack, opened on the last pocket used (wLastPocket). */
|
||||
let showPack = false;
|
||||
let packPocket: "item" | "ball" = "item";
|
||||
/** The party slot whose BattleMonMenu (SWITCH/STATS/CANCEL) is open. */
|
||||
let partyChoice: number | undefined;
|
||||
let battleCursor = 0;
|
||||
let battleSelectionKey = "";
|
||||
|
||||
@@ -460,6 +463,11 @@ function handleBattleInput(button: Button): boolean {
|
||||
showMoves = showPack = false;
|
||||
return true;
|
||||
}
|
||||
if (button === "b" && partyChoice !== undefined) {
|
||||
partyChoice = undefined;
|
||||
showWorld(world);
|
||||
return true;
|
||||
}
|
||||
const buttons = battleButtons();
|
||||
if (!buttons.length) return false;
|
||||
if (button === "a") {
|
||||
@@ -530,22 +538,38 @@ function showBattle(world: GameWorld) {
|
||||
}
|
||||
const prompt = view.prompt;
|
||||
get("battle-text").textContent =
|
||||
prompt?.kind === "name" ? prompt.title : (view.message?.text ?? "");
|
||||
prompt?.kind === "name"
|
||||
? prompt.title
|
||||
: prompt?.kind === "party"
|
||||
? prompt.text
|
||||
: (view.message?.text ?? "");
|
||||
if (prompt?.kind !== "action") showMoves = showPack = false;
|
||||
const selectionKey = `${prompt?.kind ?? "text"}:${showMoves}:${showPack}`;
|
||||
if (prompt?.kind !== "party") partyChoice = undefined;
|
||||
const selectionKey = `${prompt?.kind ?? "text"}:${showMoves}:${showPack}:${partyChoice}`;
|
||||
if (selectionKey !== battleSelectionKey) {
|
||||
battleCursor = 0;
|
||||
battleSelectionKey = selectionKey;
|
||||
}
|
||||
if (prompt?.kind === "yesno") battleCursor = view.selected === 1 ? 0 : 1;
|
||||
const balls = world.liveState.bag.balls;
|
||||
const bag = world.liveState.bag;
|
||||
// The Pokémon out has its live battle HP; the others their party values.
|
||||
const party = world.liveState.party.map((mon, index) =>
|
||||
index === view.engine.curBattleMon && own
|
||||
? { ...mon, hp: own.hp, status: own.status }
|
||||
: mon,
|
||||
);
|
||||
const key = JSON.stringify([
|
||||
prompt,
|
||||
view.selected,
|
||||
showMoves,
|
||||
showPack,
|
||||
partyChoice,
|
||||
packPocket,
|
||||
own?.pp,
|
||||
balls,
|
||||
bag.items,
|
||||
bag.balls,
|
||||
prompt?.kind === "party" &&
|
||||
party.map(({ hp, status, level }) => [hp, status, level]),
|
||||
view.message?.prompt,
|
||||
]);
|
||||
if (key === battleMenuKey) return;
|
||||
@@ -610,7 +634,19 @@ function showBattle(world: GameWorld) {
|
||||
),
|
||||
);
|
||||
} else if (showPack) {
|
||||
balls.forEach(({ item, quantity }, slot) =>
|
||||
for (const pocket of ["item", "ball"] as const)
|
||||
buttons.push(
|
||||
button(
|
||||
data.pocketName(pocket),
|
||||
() => {
|
||||
packPocket = pocket;
|
||||
showWorld(world);
|
||||
},
|
||||
{ pressed: packPocket === pocket },
|
||||
),
|
||||
);
|
||||
const slots = packPocket === "item" ? bag.items : bag.balls;
|
||||
slots.forEach(({ item, quantity }, slot) =>
|
||||
buttons.push(
|
||||
button(
|
||||
data.itemName(item),
|
||||
@@ -622,10 +658,11 @@ function showBattle(world: GameWorld) {
|
||||
),
|
||||
),
|
||||
);
|
||||
if (!balls.length)
|
||||
if (!slots.length)
|
||||
buttons.push(
|
||||
button("No BALLS", () => {}, {
|
||||
disabled: "The BALL pocket is empty.",
|
||||
button("Empty", () => {}, {
|
||||
disabled: `The ${data.pocketName(packPocket)} is empty.`,
|
||||
wide: true,
|
||||
}),
|
||||
);
|
||||
buttons.push(
|
||||
@@ -644,9 +681,7 @@ function showBattle(world: GameWorld) {
|
||||
showMoves = true;
|
||||
showWorld(world);
|
||||
}),
|
||||
button("PKMN", () => {}, {
|
||||
disabled: "Switching Pokémon is not ported yet.",
|
||||
}),
|
||||
button("PKMN", () => runtime.battleAction({ action: "pkmn" })),
|
||||
button("PACK", () => {
|
||||
showPack = true;
|
||||
showWorld(world);
|
||||
@@ -663,6 +698,45 @@ function showBattle(world: GameWorld) {
|
||||
pressed: view.selected === 0,
|
||||
}),
|
||||
);
|
||||
} else if (prompt?.kind === "party" && partyChoice !== undefined) {
|
||||
// BattleMonMenu. STATS opens the stats screen, which is not ported.
|
||||
const slot = partyChoice;
|
||||
buttons.push(
|
||||
button("SWITCH", () => {
|
||||
partyChoice = undefined;
|
||||
runtime.battleChoose(slot);
|
||||
}),
|
||||
button("STATS", () => {}, {
|
||||
disabled: "The stats screen is not ported yet.",
|
||||
}),
|
||||
button("CANCEL", () => {
|
||||
partyChoice = undefined;
|
||||
showWorld(world);
|
||||
}),
|
||||
);
|
||||
} else if (prompt?.kind === "party") {
|
||||
party.forEach((mon, slot) =>
|
||||
buttons.push(
|
||||
button(
|
||||
mon.nickname,
|
||||
() => {
|
||||
if (!prompt.menu) return runtime.battleChoose(slot);
|
||||
partyChoice = slot;
|
||||
showWorld(world);
|
||||
},
|
||||
{
|
||||
detail: `:L${mon.level} HP ${mon.hp}/${mon.stats[0]}${
|
||||
mon.status ? ` ${statusName(mon.status)}` : ""
|
||||
}`,
|
||||
wide: true,
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
if (prompt.cancel)
|
||||
buttons.push(
|
||||
button("CANCEL", () => runtime.battleChoose(-1), { wide: true }),
|
||||
);
|
||||
} else if (prompt?.kind === "forget") {
|
||||
prompt.moves.forEach((move, index) =>
|
||||
buttons.push(
|
||||
|
||||
Reference in new issue
Block a user