Checks / web (push) Canceled after 0s
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>
60 lines
2.4 KiB
TypeScript
60 lines
2.4 KiB
TypeScript
import { CrystalAssets } from "../src/game/rom/assets";
|
|
import { generatePartyMon } from "../src/game/party/pokemon";
|
|
import { GameWorld } from "../src/game/world/world";
|
|
|
|
export type BattleScenario = "win" | "loss" | "unsupported" | "catch" | "switch" | "potion" | "contest";
|
|
|
|
/** Test-only encounters on an existing floor: production maps stay gated.
|
|
* Everything after selection runs through the ROM's real wild-battle script. */
|
|
export function battleFixtureWorld(assets: CrystalAssets, scenario: BattleScenario = "win") {
|
|
assets.grassCollisions = () => [assets.collision(assets.map("24:7"), 3, 2)];
|
|
assets.grassWildMons = (id) => id === "24:7" ? {
|
|
rates: [256, 256, 256],
|
|
slots: [0, 1, 2].map(() => Array.from({ length: 7 }, () => ({ species: 16, level: 3 }))),
|
|
} : undefined;
|
|
let seed = 1;
|
|
const world = new GameWorld(assets, () => new Date("2026-09-22T12:00:00"), () => {
|
|
seed = (seed * 1103515245 + 12345) >>> 0;
|
|
return (seed >>> 16) & 255;
|
|
});
|
|
const mon = generatePartyMon(assets.pokemon, {
|
|
species: 155, level: 5, item: 0, otId: world.playerId,
|
|
ot: "CHRIS", dvs: 0x823b, caughtData: 0,
|
|
});
|
|
mon.exp = 178; // One battle away from level 6 and Smokescreen.
|
|
if (scenario === "loss") mon.hp = 1;
|
|
if (scenario === "unsupported") {
|
|
// Psywave at level 1 loops forever in the original, so it stays refused.
|
|
mon.level = 1;
|
|
mon.moves = [149, 0, 0, 0];
|
|
mon.pp = [10, 0, 0, 0];
|
|
}
|
|
world.state.party.push(mon);
|
|
if (scenario === "switch")
|
|
world.state.party.push(generatePartyMon(assets.pokemon, {
|
|
species: 158, level: 5, item: 0, otId: world.playerId,
|
|
ot: "CHRIS", dvs: 0x823b, caughtData: 0,
|
|
}));
|
|
if (scenario === "catch") world.state.bag.balls = [{ item: 5, quantity: 10 }]; // POKé BALL
|
|
if (scenario === "potion") {
|
|
mon.hp = 3;
|
|
world.state.bag.items = [{ item: 18, quantity: 1 }]; // POTION
|
|
}
|
|
// A Bug-Catching Contest in progress, already holding a Scyther: the
|
|
// state National Park's event would leave (GiveParkBalls and a catch).
|
|
if (scenario === "contest")
|
|
world.state.bugContest = {
|
|
parkBalls: 20,
|
|
mon: generatePartyMon(assets.pokemon, {
|
|
species: 123, level: 14, item: 0, otId: world.playerId,
|
|
ot: "CHRIS", dvs: 0x8888, caughtData: 0,
|
|
}),
|
|
};
|
|
world.state.pokedex.seen.add(155);
|
|
world.state.pokedex.caught.add(155);
|
|
world.state.money = 3001;
|
|
return world;
|
|
}
|
|
|
|
export const ENCOUNTER_WALK = ["up", "right", "right", "right", "right"] as const;
|