Run the forced-shiny, trap, forced-item, Celebi, Suicune, roaming and Bug-Catching Contest battle types. Roaming battles keep their struct's DVs and HP byte, and every other battle makes CleanUpBattleRAM's roaming roll. The contest shows PARKBALL×, counts down Park Balls, ends when they run out, keeps its catch outside the party and asks before switching. Scripts load the special types with loadvar, and while a contest runs, encounters use BugCatchingContestBattleScript and ContestMons. Validated with 596 unit tests, 155 browser tests (including the contest menu and switch screen), native/reference builds and reference/vendor/release checks. National Park's contest event, the roaming release and the Ilex Forest Celebi event come with their maps. Closes #28 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b547ca29ff
commit
8fae1f0c0f
15 files changed
+787
-42
No files matched your search
@@ -37,6 +37,49 @@ function finish(world: GameWorld, action: "fight" | "run" = "fight") {
|
||||
}
|
||||
|
||||
describe.skipIf(!existsSync(romPath))("world battle integration with the local ROM", () => {
|
||||
it("runs a Bug-Catching Contest encounter through its battle script", () => {
|
||||
const world = fixture("contest");
|
||||
const party = structuredClone(world.state.party),
|
||||
bag = structuredClone(world.state.bag);
|
||||
// Pace on and off the test grass until the contest's 20% roll hits.
|
||||
for (const direction of ENCOUNTER_WALK) if (!world.battle) world.move(direction);
|
||||
for (let step = 0; step < 200 && !world.battle; step++) world.move(step % 2 ? "right" : "left");
|
||||
expect(world.battle, world.diagnostic).toBeDefined();
|
||||
const contest = world.state.bugContest!;
|
||||
let asked = false;
|
||||
const messages = new Set<string>();
|
||||
for (let tick = 0; tick < 10000 && (world.battle || world.busy || world.dialogue); tick++) {
|
||||
const view = world.battle;
|
||||
if (!view) {
|
||||
if (world.dialogue) world.advanceDialogue();
|
||||
world.tick();
|
||||
continue;
|
||||
}
|
||||
if (view.message) messages.add(view.message.text.replace(/\n/g, " "));
|
||||
if (view.prompt?.kind === "action") {
|
||||
expect(view.prompt.parkBalls).toBeGreaterThan(0);
|
||||
world.battleAction({ action: "item", item: 0xb1, slot: -1 });
|
||||
} else if (view.prompt?.kind === "contestswitch") {
|
||||
asked = true;
|
||||
expect(view.prompt.stock).toMatchObject({ name: "SCYTHER", level: 14 });
|
||||
world.battleChoose(1);
|
||||
} else if (view.message?.prompt) world.interact();
|
||||
else world.tick();
|
||||
}
|
||||
expect(world.notice, world.diagnostic).toBe("");
|
||||
// Committed once the script ended: one Park Ball per throw, the new
|
||||
// catch in the contest, and the party and pack untouched.
|
||||
const thrown = [...messages].filter((text) => text.includes("used the PARK BALL")).length;
|
||||
expect(thrown, [...messages].join(" | ")).toBeGreaterThan(0);
|
||||
expect(world.state.bugContest!.parkBalls).toBe(20 - thrown);
|
||||
expect(world.state.bugContest!.parkBalls).toBeLessThan(20);
|
||||
expect(asked).toBe(true);
|
||||
expect(world.state.bugContest!.mon!.species).not.toBe(123);
|
||||
expect(world.state.party).toEqual(party);
|
||||
expect(world.state.bag).toEqual(bag);
|
||||
expect(contest).not.toBe(world.state.bugContest);
|
||||
});
|
||||
|
||||
it("commits a victory, level-up, move learning and Pokédex sighting", () => {
|
||||
const world = fixture();
|
||||
encounter(world);
|
||||
|
||||
Reference in new issue
Block a user