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
@@ -58,7 +58,7 @@ async function finish(page: Page, action: "fight" | "run" = "fight") {
|
||||
if (action === "run") await menu.getByRole("button", { name: "RUN", exact: true }).click();
|
||||
else {
|
||||
await fight.click();
|
||||
await menu.getByRole("button", { name: /TACKLE|TRANSFORM/ }).click();
|
||||
await menu.getByRole("button", { name: /TACKLE|TRANSFORM|SCRATCH/ }).click();
|
||||
}
|
||||
} else if (await menu.getByRole("button", { name: "YES", exact: true }).count()) {
|
||||
await menu.getByRole("button", { name: "YES", exact: true }).click();
|
||||
@@ -115,6 +115,8 @@ test("keyboard and touch D-pad navigate battle moves, cancel and run", async ({
|
||||
await expect(menu.getByRole("button", { name: "FIGHT", exact: true })).toBeVisible();
|
||||
// A real pointer press on the on-screen D-pad (synthetic events cannot be captured).
|
||||
await page.locator('[data-game-button="down"]').click();
|
||||
await expect(menu.getByRole("button", { name: "PKMN", exact: true })).toHaveClass(/battle-selected/);
|
||||
await page.keyboard.press("ArrowDown");
|
||||
await expect(menu.getByRole("button", { name: "PACK", exact: true })).toHaveClass(/battle-selected/);
|
||||
await page.keyboard.press("ArrowDown");
|
||||
await expect(menu.getByRole("button", { name: "RUN", exact: true })).toHaveClass(/battle-selected/);
|
||||
@@ -178,6 +180,7 @@ test("battle PACK throws Poké Balls, names the catch and saves it", async ({ pa
|
||||
named = true;
|
||||
} else if (await pack.count()) {
|
||||
await pack.click();
|
||||
await menu.getByRole("button", { name: "BALL POCKET", exact: true }).click();
|
||||
const ball = menu.getByRole("button", { name: /POKé BALL/ });
|
||||
await expect(ball).toContainText("×");
|
||||
await ball.click();
|
||||
@@ -206,7 +209,67 @@ test("battle PACK closes with B and returns to the command menu", async ({ page
|
||||
await page.clock.runFor(1200);
|
||||
}
|
||||
await menu.getByRole("button", { name: "PACK", exact: true }).click();
|
||||
// BattlePack opens on the Items pocket first.
|
||||
await expect(menu.getByRole("button", { name: "ITEM POCKET", exact: true })).toHaveAttribute("aria-pressed", "true");
|
||||
await menu.getByRole("button", { name: "BALL POCKET", exact: true }).click();
|
||||
await expect(menu.getByRole("button", { name: /POKé BALL/ })).toBeVisible();
|
||||
await page.keyboard.press("KeyZ");
|
||||
await expect(menu.getByRole("button", { name: "FIGHT", exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
test("battle PKMN switches through the party menu and its submenu, then shares the experience", async ({ page }) => {
|
||||
await start(page, "switch");
|
||||
const menu = page.locator("#battle-menu");
|
||||
for (let tick = 0; tick < 10 && !(await menu.getByRole("button", { name: "PKMN", exact: true }).count()); tick++) {
|
||||
if (await menu.getByRole("button", { name: /Continue/ }).count()) await page.keyboard.press("KeyX");
|
||||
await page.clock.runFor(1200);
|
||||
}
|
||||
await menu.getByRole("button", { name: "PKMN", exact: true }).click();
|
||||
await expect(page.locator("#battle-text")).toHaveText("Choose a POKéMON.");
|
||||
await expect(menu.getByRole("button", { name: /^CYNDAQUIL/ })).toBeVisible();
|
||||
await menu.getByRole("button", { name: /^TOTODILE/ }).click();
|
||||
// BattleMonMenu: B returns to the party list.
|
||||
await expect(menu.getByRole("button", { name: "STATS", exact: true })).toBeDisabled();
|
||||
await page.keyboard.press("KeyZ");
|
||||
await menu.getByRole("button", { name: /^TOTODILE/ }).click();
|
||||
await menu.getByRole("button", { name: "SWITCH", exact: true }).click();
|
||||
// The withdrawal and send-out texts continue on their own.
|
||||
const shown: string[] = [];
|
||||
for (let tick = 0; tick < 20 && (await page.locator("#battle-player-name").innerText()) !== "TOTODILE"; tick++) {
|
||||
shown.push(await page.locator("#battle-text").innerText());
|
||||
await page.clock.runFor(300);
|
||||
}
|
||||
await expect(page.locator("#battle-player-name")).toHaveText("TOTODILE");
|
||||
expect(shown.join("\n").replace(/\n/g, " ")).toContain("CYNDAQUIL, that's enough! Come back!");
|
||||
const messages = await finish(page);
|
||||
// Pidgey's 23 experience points are halved for the two participants.
|
||||
expect(messages.replace(/\n/g, " ")).toContain("CYNDAQUIL gained 11 EXP. Points!");
|
||||
expect(messages.replace(/\n/g, " ")).toContain("TOTODILE gained 11 EXP. Points!");
|
||||
const { state } = await exportState(page);
|
||||
expect(state.party.map((mon: { species: number }) => mon.species)).toEqual([155, 158]);
|
||||
expect(state.party[1].exp).toBe(135 + 11);
|
||||
});
|
||||
|
||||
test("battle PACK uses a Potion from the Items pocket on the Pokémon out", async ({ page }) => {
|
||||
await start(page, "potion");
|
||||
const menu = page.locator("#battle-menu");
|
||||
for (let tick = 0; tick < 10 && !(await menu.getByRole("button", { name: "PACK", exact: true }).count()); tick++) {
|
||||
if (await menu.getByRole("button", { name: /Continue/ }).count()) await page.keyboard.press("KeyX");
|
||||
await page.clock.runFor(1200);
|
||||
}
|
||||
await menu.getByRole("button", { name: "PACK", exact: true }).click();
|
||||
await menu.getByRole("button", { name: /^POTION/ }).click();
|
||||
await expect(page.locator("#battle-text")).toHaveText("Use on which POKéMON?");
|
||||
await menu.getByRole("button", { name: /^CYNDAQUIL/ }).click();
|
||||
const shown: string[] = [];
|
||||
for (let tick = 0; tick < 20 && !shown.some((text) => text.includes("recovered")); tick++) {
|
||||
shown.push((await page.locator("#battle-text").innerText()).replace(/\n/g, " "));
|
||||
if (await menu.getByRole("button", { name: /Continue/ }).count()) await page.keyboard.press("KeyX");
|
||||
else await page.clock.runFor(300);
|
||||
}
|
||||
// From 3 HP, a Potion's 20 stops at the level 5 maximum of 19.
|
||||
expect(shown).toContain("CYNDAQUIL recovered 16HP!");
|
||||
await finish(page, "run");
|
||||
const { state } = await exportState(page);
|
||||
expect(state.bag.items).toEqual([]);
|
||||
});
|
||||
Reference in new issue
Block a user