Port field moves from the menu, SEL with Select, and moving Pokémon in Bill's PC
Checks / web (push) Canceled after 0s
Checks / web (push) Canceled after 0s
Field moves chosen in the party menu run their original checks and the ROM's from-menu scripts (Cut, Surf, Strength, Flash, Whirlpool, Waterfall, Sweet Scent), and Softboiled and Milk Drink share HP. SEL registers a key item that Select then uses, replacing the Key Items shortcut, and Bill's PC moves Pokémon between the party and every box. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
45275ba83b
commit
3456aa4e11
25 files changed
+1129
-186
No files matched your search
@@ -419,6 +419,169 @@ describe.skipIf(!existsSync(romPath))("field moves with the local ROM", () => {
|
||||
expect(used).toContain("used\nWHIRLPOOL!");
|
||||
expect(world.map.blocks).not.toEqual(before);
|
||||
});
|
||||
|
||||
/** Start → POKéMON → the first Pokémon → `move`; returns the pages
|
||||
* shown until the menu closed or a choice (the party menu) returned. */
|
||||
const useFromMenu = (world: GameWorld, move: string) => {
|
||||
expect(world.openStartMenu()).toBe(true);
|
||||
const choose = (label: string) => {
|
||||
const choice = world.dialogue?.choice;
|
||||
if (choice?.kind !== "menu") throw new Error(`No menu for ${label}`);
|
||||
const index = choice.items.findIndex((item) => item.startsWith(label));
|
||||
expect(index, `${label} in ${choice.items}`).toBeGreaterThanOrEqual(0);
|
||||
world.choose(index);
|
||||
};
|
||||
choose("POKéMON");
|
||||
choose("SWIMMY");
|
||||
choose(move);
|
||||
const pages: string[] = [];
|
||||
for (let i = 0; i < 50 && world.dialogue && !world.dialogue.choice; i++) {
|
||||
pages.push(world.dialogue.pages[world.dialogue.index]);
|
||||
world.advanceDialogue();
|
||||
}
|
||||
return pages;
|
||||
};
|
||||
/** B out of every menu, then let any queued script finish. */
|
||||
const closeMenus = (world: GameWorld) => {
|
||||
for (let i = 0; i < 10 && world.dialogue?.choice; i++) world.advanceDialogue();
|
||||
return settle(world).join("\n");
|
||||
};
|
||||
|
||||
it("surfs from the party menu, after the badge and facing checks", () => {
|
||||
const { assets, world } = fixture([]);
|
||||
world.state.party = partyWith(assets, [0x0f, 0x39]); // CUT, SURF
|
||||
world.state.scenes["24:4"] = 1;
|
||||
world.restore({ ...world.save(), map: "24:4", x: 17, y: 7, direction: "right", objects: [] });
|
||||
expect(world.dialogue).toBeUndefined();
|
||||
// CUT and SURF come first in the submenu.
|
||||
expect(useFromMenu(world, "SURF")).toEqual(["Sorry! A new BADGE\nis required."]);
|
||||
closeMenus(world);
|
||||
world.state.flags.add(30); // ENGINE_FOGBADGE
|
||||
world.restore({ ...world.save(), direction: "up" });
|
||||
expect(useFromMenu(world, "SURF")).toEqual(["You can't SURF\nhere."]);
|
||||
closeMenus(world);
|
||||
world.restore({ ...world.save(), direction: "right" });
|
||||
const shown = [...useFromMenu(world, "SURF"), closeMenus(world)].join("\n");
|
||||
expect(world.notice, world.diagnostic).toBe("");
|
||||
// No "Want to SURF?" question from the menu.
|
||||
expect(shown).not.toContain("?");
|
||||
expect(shown).toContain("SWIMMY used\nSURF!");
|
||||
expect([world.x, world.y, world.state.playerState]).toEqual([18, 7, 4]);
|
||||
expect(useFromMenu(world, "SURF")).toEqual(["You're already\nSURFING."]);
|
||||
closeMenus(world);
|
||||
});
|
||||
|
||||
it("cuts from the party menu, or finds nothing to cut", () => {
|
||||
const { assets, world } = fixture([]);
|
||||
const spot = find(assets, (map, x, y) =>
|
||||
[1, 2, 3, 12, 25].includes(map.tileset) &&
|
||||
assets.collision(map, x, y) === 0x12 &&
|
||||
assets.permission(assets.collision(map, x, y + 1)) === 0 &&
|
||||
!map.objects.some((o) => o.x === x && o.y === y + 1) &&
|
||||
map.callbacks.length === 0,
|
||||
);
|
||||
const ported = assets.isPorted.bind(assets);
|
||||
assets.isPorted = (id) => ported(id) || id === spot.map;
|
||||
world.state.party = partyWith(assets, [0x0f]);
|
||||
world.state.flags.add(28); // ENGINE_HIVEBADGE
|
||||
world.warpTo(spot.map, spot.x, spot.y + 1);
|
||||
world.restore({ ...world.save(), direction: "down" });
|
||||
expect(useFromMenu(world, "CUT")).toEqual(["There's nothing to\nCUT here."]);
|
||||
closeMenus(world);
|
||||
world.restore({ ...world.save(), direction: "up" });
|
||||
const before = world.map.blocks.slice();
|
||||
const shown = [...useFromMenu(world, "CUT"), closeMenus(world)].join("\n");
|
||||
expect(world.notice, world.diagnostic).toBe("");
|
||||
expect(shown).toContain("SWIMMY used\nCUT!");
|
||||
expect(world.map.blocks).not.toEqual(before);
|
||||
});
|
||||
|
||||
it("uses Strength from the party menu", () => {
|
||||
const { assets, world } = fixture([]);
|
||||
world.state.party = partyWith(assets, [0x46]);
|
||||
world.state.flags.add(29); // ENGINE_PLAINBADGE
|
||||
world.state.scenes["24:4"] = 1;
|
||||
world.restore({ ...world.save(), map: "24:4", x: 13, y: 7, direction: "down", objects: [] });
|
||||
const shown = [...useFromMenu(world, "STRENGTH"), closeMenus(world)].join("\n");
|
||||
expect(world.notice, world.diagnostic).toBe("");
|
||||
expect(shown).toContain("SWIMMY used\nSTRENGTH!");
|
||||
expect(world.state.flags.has(24)).toBe(true); // ENGINE_STRENGTH_ACTIVE
|
||||
});
|
||||
|
||||
it("lights a dark cave with Flash, which fails elsewhere", () => {
|
||||
// Dark Cave's Violet entrance (PALETTE_DARK).
|
||||
const DARK_CAVE = "3:78";
|
||||
const { assets, world } = fixture([DARK_CAVE]);
|
||||
const map = assets.map(DARK_CAVE);
|
||||
expect(map.paletteMode).toBe(4);
|
||||
let spot = { map: DARK_CAVE, x: 0, y: 0 };
|
||||
search: for (let y = 1; y < map.height - 1; y++)
|
||||
for (let x = 1; x < map.width - 1; x++)
|
||||
if (
|
||||
assets.permission(assets.collision(map, x, y)) === 0 &&
|
||||
!map.objects.some((o) => o.x === x && o.y === y)
|
||||
) {
|
||||
spot = { map: DARK_CAVE, x, y };
|
||||
break search;
|
||||
}
|
||||
world.state.party = partyWith(assets, [0x94]); // FLASH
|
||||
world.state.flags.add(27); // ENGINE_ZEPHYRBADGE
|
||||
world.warpTo(spot.map, spot.x, spot.y);
|
||||
expect(world.timeOfDay).toBe(3);
|
||||
const shown = [...useFromMenu(world, "FLASH"), closeMenus(world)].join("\n");
|
||||
expect(world.notice, world.diagnostic).toBe("");
|
||||
expect(shown).toContain("A blinding FLASH\nlights the area!");
|
||||
expect(world.state.flash).toBe(true);
|
||||
expect(world.timeOfDay).toBe(2);
|
||||
expect(useFromMenu(world, "FLASH")).toEqual(["Can't use that\nhere."]);
|
||||
closeMenus(world);
|
||||
});
|
||||
|
||||
it("recognises a smashable rock for Rock Smash, which is not ported further", () => {
|
||||
// A rock (SPRITEMOVEDATA_SMASHABLE_ROCK) in Dark Cave at 27, 6.
|
||||
const spot = { map: "3:78", x: 27, y: 6 };
|
||||
const { assets, world } = fixture([spot.map]);
|
||||
expect(
|
||||
assets.map(spot.map).objects.some(
|
||||
(o) => o.x === spot.x && o.y === spot.y && o.movement === 0x18,
|
||||
),
|
||||
).toBe(true);
|
||||
world.state.party = partyWith(assets, [0xf9]); // ROCK_SMASH
|
||||
world.warpTo(spot.map, spot.x, spot.y + 1);
|
||||
world.restore({ ...world.save(), direction: "down" });
|
||||
expect(useFromMenu(world, "ROCK SMASH")).toEqual(["Can't use that\nhere."]);
|
||||
closeMenus(world);
|
||||
world.restore({ ...world.save(), direction: "up" });
|
||||
useFromMenu(world, "ROCK SMASH");
|
||||
expect(world.notice).toBe("Smashing rocks is not ported yet.");
|
||||
closeMenus(world);
|
||||
expect(world.diagnostic).toBe("");
|
||||
});
|
||||
|
||||
it("draws a wild Pokémon with Sweet Scent in grass, and nothing elsewhere", () => {
|
||||
const { assets, world } = fixture([]);
|
||||
world.state.party = partyWith(assets, [0xe6]); // SWEET_SCENT
|
||||
world.state.scenes["24:4"] = 1;
|
||||
world.restore({ ...world.save(), map: "24:4", x: 13, y: 7, direction: "down", objects: [] });
|
||||
const nothing = [...useFromMenu(world, "SWEET SCENT"), closeMenus(world)].join("\n");
|
||||
expect(world.notice, world.diagnostic).toBe("");
|
||||
expect(nothing).toContain("SWIMMY used\nSWEET SCENT!");
|
||||
expect(nothing).toContain("nothing");
|
||||
const grass = find(assets, (map, x, y) =>
|
||||
map.id === "24:3" &&
|
||||
assets.grassCollisions().includes(assets.collision(map, x, y)) &&
|
||||
!map.objects.some((o) => o.x === x && o.y === y),
|
||||
);
|
||||
world.warpTo(grass.map, grass.x, grass.y);
|
||||
useFromMenu(world, "SWEET SCENT");
|
||||
for (let i = 0; i < 10 && world.dialogue?.choice; i++) world.advanceDialogue();
|
||||
for (let tick = 0; tick < 200 && !world.battle; tick++) {
|
||||
if (world.dialogue) world.advanceDialogue();
|
||||
world.tick();
|
||||
}
|
||||
expect(world.notice, world.diagnostic).toBe("");
|
||||
expect(world.battle).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe.skipIf(!existsSync(romPath))("Strength boulders and stone tables with the local ROM", () => {
|
||||
|
||||
Reference in new issue
Block a user