108 lines
4.4 KiB
TypeScript
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);
|
|
});
|
|
});
|