This commit is contained in:
1 parent
6a74f59e46
commit
3e92936cb9
20 files changed
+1417
-32
No files matched your search
@@ -0,0 +1,120 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { TrainerData } from "../src/game/rom/trainers";
|
||||
import { RomReader } from "../src/game/rom/reader";
|
||||
import { CrystalAssets } from "../src/game/rom/assets";
|
||||
import { NUM_TRAINER_CLASSES } from "../src/game/rom/profile";
|
||||
|
||||
const romPath =
|
||||
process.env.CRYSTAL_ROM_PATH ??
|
||||
"Pokemon - Crystal Version (UE) (V1.1) [C][!].gbc";
|
||||
describe.skipIf(!existsSync(romPath))("ROM trainer decoder", () => {
|
||||
const bytes = existsSync(romPath)
|
||||
? new Uint8Array(readFileSync(romPath))
|
||||
: new Uint8Array();
|
||||
const data = new TrainerData(new RomReader(bytes));
|
||||
it("reads Joey and Mikey, including class DVs and actual payout", () => {
|
||||
const joey = data.trainer(22, 1),
|
||||
mikey = data.trainer(22, 2);
|
||||
expect(joey).toMatchObject({
|
||||
name: "JOEY",
|
||||
className: "YOUNGSTER",
|
||||
dvs: 0x9888,
|
||||
reward: 64,
|
||||
partyType: 0,
|
||||
});
|
||||
expect(joey.party).toEqual([{ species: 19, level: 4, item: 0 }]);
|
||||
expect(mikey.party.map((mon) => [mon.species, mon.level])).toEqual([
|
||||
[16, 2],
|
||||
[19, 4],
|
||||
]);
|
||||
expect(data.createParty(joey)[0]).toMatchObject({
|
||||
moves: [33, 39, 0, 0],
|
||||
dvs: 0x9888,
|
||||
statExp: [0, 0, 0, 0, 0],
|
||||
});
|
||||
});
|
||||
it("reads Falkner's explicit moves and the rival starter variants", () => {
|
||||
const falkner = data.trainer(1, 1);
|
||||
expect(falkner).toMatchObject({
|
||||
name: "FALKNER",
|
||||
partyType: 1,
|
||||
dvs: 0x9a77,
|
||||
reward: 900,
|
||||
});
|
||||
const party = data.createParty(falkner, { ot: "CHRIS", otId: 123 });
|
||||
expect(party.map((mon) => mon.moves)).toEqual([
|
||||
[33, 189, 0, 0],
|
||||
[33, 189, 16, 0],
|
||||
]);
|
||||
expect(party[0].pp.slice(0, 2)).toEqual([35, 10]);
|
||||
expect(
|
||||
party.every((mon) => mon.hp === mon.stats[0] && mon.otId === 123),
|
||||
).toBe(true);
|
||||
expect([1, 2, 3].map((id) => data.trainer(9, id).party[0].species)).toEqual(
|
||||
[152, 155, 158],
|
||||
);
|
||||
expect(data.trainer(9, 1).dvs).toBe(0xdddd);
|
||||
});
|
||||
it("reads held items with and without explicit moves", () => {
|
||||
expect(data.trainer(59, 1).partyType).toBe(2);
|
||||
const gaven = data.trainer(27, 7);
|
||||
expect(gaven.partyType).toBe(3);
|
||||
expect(gaven.party[1].item).toBe(82);
|
||||
expect(data.createParty(gaven)[1].moves).toEqual(gaven.party[1].moves);
|
||||
});
|
||||
it.skipIf(!existsSync(".reference/pokecrystal/data/trainers/parties.asm"))(
|
||||
"sweeps every party listed in the pinned source",
|
||||
() => {
|
||||
const source = readFileSync(
|
||||
".reference/pokecrystal/data/trainers/parties.asm",
|
||||
"utf8",
|
||||
);
|
||||
const groups = source.split(/^\w+Group:\s*$/m).slice(1);
|
||||
expect(groups).toHaveLength(NUM_TRAINER_CLASSES);
|
||||
const formats = new Set<number>();
|
||||
let total = 0;
|
||||
groups.forEach((group, index) => {
|
||||
const count = [...group.matchAll(/db -1 ; end/g)].length;
|
||||
for (let id = 1; id <= count; id++) {
|
||||
const trainer = data.trainer(index + 1, id);
|
||||
formats.add(trainer.partyType);
|
||||
total++;
|
||||
if (index + 1 === 12 && id === 2) {
|
||||
expect(() => data.createParty(trainer)).toThrow(/Mystery Gift/);
|
||||
continue;
|
||||
}
|
||||
const party = data.createParty(trainer);
|
||||
expect(party.length).toBeGreaterThan(0);
|
||||
expect(
|
||||
party.every((mon) => mon.hp > 0 && mon.hp === mon.stats[0]),
|
||||
).toBe(true);
|
||||
}
|
||||
expect(() => data.trainer(index + 1, count + 1)).toThrow();
|
||||
});
|
||||
expect(formats).toEqual(new Set([0, 1, 2, 3]));
|
||||
expect(total).toBeGreaterThan(500);
|
||||
},
|
||||
);
|
||||
it("reads map trainer headers and vision range", () => {
|
||||
const assets = new CrystalAssets(bytes);
|
||||
const route = assets.map("26:1");
|
||||
const trainers = route.objects.filter((object) => object.kind === 2);
|
||||
expect(trainers.length).toBeGreaterThan(0);
|
||||
for (const object of trainers) {
|
||||
expect(object.sightRange).toBeGreaterThan(0);
|
||||
const header = data.header(object.script);
|
||||
expect(header.seenText).toBeGreaterThan(0);
|
||||
expect(header.beatenText).toBeGreaterThan(0);
|
||||
expect(
|
||||
data.trainer(header.classId, header.id).party.length,
|
||||
).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
it("rejects invalid class and trainer IDs", () => {
|
||||
for (const classId of [0, -1, 68, 1.5])
|
||||
expect(() => data.trainer(classId, 1)).toThrow();
|
||||
for (const id of [0, -1, 256, 1.5, 2])
|
||||
expect(() => data.trainer(1, id)).toThrow();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user