Read TileCollisionStdScripts from the ROM so the Pokémon Center PC runs PokemonCenterPC: Bill's PC (deposit, withdraw, release, change and name boxes), the player's item storage (withdraw, deposit, toss, mail box) and Oak's Pokédex rating. The house PC now opens the full player's PC menu. A catch with a full party goes to the current box through SendMonIntoBox; a full box keeps the ball, and filling it queues Bill's call as a memory script after the battle. Saves gain boxes, box names and PC items, migrated by absence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
451464f7c6
commit
3bdca0099e
21 files changed
+1999
-131
No files matched your search
@@ -163,23 +163,108 @@ describe.skipIf(!existsSync(romPath))("the catching tutorial and capture in the
|
||||
expect(validPartyMon(world.assets.pokemon, world.state.party[1])).toBe(true);
|
||||
});
|
||||
|
||||
it("rolls back the whole battle when a full party would need the PC", () => {
|
||||
/** Throw balls until the battle ends, answering prompts; returns the
|
||||
* battle messages and the pages shown afterwards. */
|
||||
const catchAll = (world: GameWorld, nickname?: string) => {
|
||||
const messages: string[] = [], pages: string[] = [];
|
||||
for (let tick = 0; tick < 20_000 && world.busy; tick++) {
|
||||
const view = world.battle;
|
||||
if (!view) {
|
||||
if (world.dialogue) {
|
||||
const page = world.dialogue.pages[world.dialogue.index];
|
||||
if (pages.at(-1) !== page) pages.push(page);
|
||||
world.advanceDialogue();
|
||||
} else world.tick();
|
||||
continue;
|
||||
}
|
||||
const text = view.message?.text;
|
||||
if (text && messages.at(-1) !== text) messages.push(text);
|
||||
const prompt = view.prompt;
|
||||
if (prompt?.kind === "action")
|
||||
world.battleAction({ action: "item", item: C.POKE_BALL, slot: 0 });
|
||||
else if (prompt?.kind === "yesno") world.battleChoose(nickname ? 1 : 0);
|
||||
else if (prompt?.kind === "name") world.battleName(nickname!);
|
||||
else if (view.message?.prompt) world.interact();
|
||||
else world.tick();
|
||||
}
|
||||
return {
|
||||
messages: messages.map((text) => text.replace(/\n/g, " ")),
|
||||
pages: pages.map((text) => text.replace(/\n/g, " ")),
|
||||
};
|
||||
};
|
||||
const fullParty = (boxed = 0) => {
|
||||
const world = battleFixtureWorld(assets());
|
||||
const mon = world.state.party[0];
|
||||
world.state.party = Array.from({ length: 6 }, () => structuredClone(mon));
|
||||
world.state.bag.balls = [{ item: C.POKE_BALL, quantity: 10 }];
|
||||
const { status: _s, hp: _h, stats: _t, ...box } = mon;
|
||||
world.state.storage.boxes[0] = Array.from({ length: boxed }, () => structuredClone(box));
|
||||
return world;
|
||||
};
|
||||
|
||||
it("sends a catch with a full party to the current box", () => {
|
||||
const world = fullParty();
|
||||
for (const direction of ENCOUNTER_WALK) expect(world.move(direction)).toBe(true);
|
||||
const { messages } = catchAll(world, "PIDGE");
|
||||
expect(world.notice, world.diagnostic).toBe("");
|
||||
expect(messages).toContain("PIDGE was sent to BILL's PC.");
|
||||
expect(world.state.party).toHaveLength(6);
|
||||
// SendMonIntoBox: first in the box, with full PP and base happiness.
|
||||
const [boxed] = world.state.storage.boxes[0];
|
||||
expect(boxed).toMatchObject({ species: PIDGEY, nickname: "PIDGE", level: 3, ot: world.name, happiness: 70, statExp: [0, 0, 0, 0, 0] });
|
||||
expect(boxed).not.toHaveProperty("hp");
|
||||
const data = world.assets.pokemon;
|
||||
expect(boxed.pp).toEqual(boxed.moves.map((move) => (move ? data.move(move).pp : 0)));
|
||||
expect(boxed.caughtData >> 8).toBe((2 << 6) | 3);
|
||||
expect(world.state.pokedex.caught.has(PIDGEY)).toBe(true);
|
||||
const saved = world.save();
|
||||
world.restore(saved);
|
||||
expect(world.save()).toEqual(saved);
|
||||
});
|
||||
|
||||
it("keeps the ball when the party and the box are full", () => {
|
||||
const world = fullParty(20);
|
||||
const before = world.save();
|
||||
for (const direction of ENCOUNTER_WALK) expect(world.move(direction)).toBe(true);
|
||||
for (let tick = 0; tick < 200 && world.battle?.prompt?.kind !== "action"; tick++)
|
||||
if (world.battle?.message?.prompt) world.interact();
|
||||
else world.tick();
|
||||
world.battleAction({ action: "item", item: C.POKE_BALL, slot: 0 });
|
||||
for (let tick = 0; tick < 200 && world.battle; tick++) {
|
||||
if (world.battle.prompt?.kind === "action")
|
||||
world.battleAction({ action: "item", item: C.POKE_BALL, slot: 0 });
|
||||
const messages: string[] = [];
|
||||
for (let tick = 0; tick < 200 && world.battle?.prompt?.kind !== "action"; tick++) {
|
||||
const text = world.battle?.message?.text;
|
||||
if (text && messages.at(-1) !== text) messages.push(text.replace(/\n/g, " "));
|
||||
if (world.battle?.message?.prompt) world.interact();
|
||||
else world.tick();
|
||||
}
|
||||
// Ball_BoxIsFullMessage: the item is not used and the turn is not taken.
|
||||
expect(messages).toEqual(["The POKéMON BOX is full. That can't be used now."]);
|
||||
expect(world.battle?.prompt?.kind).toBe("action");
|
||||
expect(world.battle?.engine.enemy.mon.hp).toBe(world.battle?.engine.enemy.mon.maxHp);
|
||||
for (let tick = 0; tick < 2000 && world.battle; tick++) {
|
||||
if (world.battle.prompt?.kind === "action") world.battleAction({ action: "run" });
|
||||
else if (world.battle.message?.prompt) world.interact();
|
||||
else world.tick();
|
||||
}
|
||||
expect(world.battle).toBeUndefined();
|
||||
expect(world.notice).toMatch(/not been ported/);
|
||||
expect(world.state.bag).toEqual({ ...before.bag });
|
||||
expect(world.state.party).toHaveLength(6);
|
||||
settle(world);
|
||||
expect(world.notice, world.diagnostic).toBe("");
|
||||
expect(world.state.bag).toEqual(before.bag);
|
||||
expect(world.state.storage).toEqual(before.storage);
|
||||
});
|
||||
|
||||
it("has Bill call after a catch fills the box", () => {
|
||||
const world = fullParty(19);
|
||||
for (const direction of ENCOUNTER_WALK) expect(world.move(direction)).toBe(true);
|
||||
const { messages } = catchAll(world);
|
||||
expect(world.notice, world.diagnostic).toBe("");
|
||||
expect(messages).toContain("PIDGEY was sent to BILL's PC.");
|
||||
expect(world.state.storage.boxes[0]).toHaveLength(20);
|
||||
// Script_SpecialBillCall runs at the next PlayerEvents.
|
||||
expect(world.busy).toBe(false);
|
||||
world.tick();
|
||||
const call = settle(world).join(" ").replace(/\n/g, " ");
|
||||
expect(call).toContain("It's me, BILL!");
|
||||
expect(call).toContain("Click!");
|
||||
expect(world.busy).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user