Files
Crystal/tests/trainer-world.test.ts
T
2026-09-24 09:33:33 +02:00

108 lines
4.4 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { existsSync, readFileSync } from "node:fs";
import { CrystalAssets } from "../src/game/rom/assets";
import { C } from "../src/game/battle/constants";
import { trainerFixtureWorld } from "./trainer-fixture";
import type { GameWorld } from "../src/game/world/world";
const romPath = process.env.CRYSTAL_ROM_PATH ?? "Pokemon - Crystal Version (UE) (V1.1) [C][!].gbc";
const fixture = (tail?: number[], lose = false, winText = false) =>
trainerFixtureWorld(new CrystalAssets(new Uint8Array(readFileSync(romPath))), tail, lose, winText);
function finish(world: GameWorld) {
const messages: string[] = [];
for (let tick = 0; tick < 10000; tick++) {
const view = world.battle;
if (view) {
if (view.message) messages.push(view.message.text);
if (view.prompt?.kind === "action") world.battleAction({ action: "fight", move: 0 });
else if (view.prompt?.kind === "yesno") world.battleChoose(1);
else if (view.prompt?.kind === "forget") world.battleChoose(3);
else if (view.prompt?.kind === "stats" || view.message?.prompt) world.interact();
else world.tick();
} else if (world.dialogue) world.advanceDialogue();
else if (world.busy) world.tick();
else return messages.join("\n");
}
throw new Error(`Trainer battle did not settle: ${world.diagnostic}`);
}
describe.skipIf(!existsSync(romPath))("scripted trainer world transactions", () => {
it("commits Joey's ROM reward, party experience, sighting and postbattle event together", () => {
const world = fixture();
const before = world.save();
world.interact();
expect(world.battle).toBeDefined();
expect(world.save()).toEqual(before);
const messages = finish(world);
expect(world.notice, world.diagnostic).toBe("");
expect(messages).toContain("JOEY");
expect(world.state.money).toBe(before.money + 64);
expect(world.state.party[0].exp).toBeGreaterThan(before.party[0].exp);
expect(world.state.pokedex.seen.has(19)).toBe(true);
expect(world.state.events.has(256)).toBe(true);
const saved = world.save();
world.restore(saved);
expect(world.save()).toEqual(saved);
});
it("reads scripted win text from the script bank's ROM pointer", () => {
const world = fixture(undefined, false, true);
world.interact();
const messages = finish(world);
expect(world.notice, world.diagnostic).toBe("");
expect(messages).toContain("Ack! I lost again!");
expect(messages.indexOf("Ack! I lost again!")).toBeLessThan(messages.lastIndexOf("64"));
});
it("caps trainer rewards at the money limit", () => {
const world = fixture();
world.state.money = C.MAX_MONEY - 1;
world.interact();
finish(world);
expect(world.notice, world.diagnostic).toBe("");
expect(world.state.money).toBe(C.MAX_MONEY);
});
it("rolls back reward, experience and sightings if the following script is unsupported", () => {
const world = fixture([0xff]);
const before = world.save();
world.interact();
finish(world);
expect(world.notice).toContain("not been ported");
expect(world.save()).toEqual(before);
});
it("restores a midbattle save without partially paying the reward", () => {
const world = fixture();
const before = world.save();
world.interact();
for (let tick = 0; tick < 1000 && !world.battle?.prompt; tick++) {
if (world.battle?.message?.prompt) world.interact();
else world.tick();
}
expect(world.save()).toEqual(before);
world.restore(before);
expect(world.battle).toBeUndefined();
expect(world.save()).toEqual(before);
});
it("CANLOSE scripts receive LOSE in wScriptVar without forcing a whiteout", () => {
// ifequal LOSE, .lost; invalid; .lost: setevent 256; end
const world = fixture([0x91], true);
const clerk = world.assets.map("26:4").objects[0];
const start = clerk.script + 12; // after winlosstext/loadtrainer/loadvar/startbattle
const target = ((start + 5) % 0x4000) + 0x4000;
world.assets.reader.bytes.set([0x06, 1, target & 255, target >> 8, 0xff, 0x33, 0, 1, 0x91], start);
const before = world.save();
world.interact();
const messages = finish(world);
expect(world.notice, world.diagnostic).toBe("");
expect(world.state.events.has(256)).toBe(true);
expect(world.state.money).toBe(before.money);
expect(world.save().map).toBe(before.map);
expect(messages).not.toMatch(/whited\s+out/i);
});
});