Files
Crystal/tests/trainer-fixture.ts
T
Sébastien QUEROLandClaude Opus 5.5 e00dfab42e Open Route 30 with map trainers and multi-Pokémon trainer battles
Map trainers run the ROM's TalkToTrainerScript and SeenByTrainerScript.
_CheckTrainerBattle runs before tile events at the end of each step and
on idle ticks; TrainerWalkToPlayer builds wMovementBuffer natively, and
writeobjectxy keeps the trainer where it stopped. The interpreter gains
the trainer header commands, wRunningTrainerBattleScript and hidden
items (HiddenItemScript).

Trainers with several Pokémon send out the next one through
EnemyPartyMonEntrance and FindMonInOTPartyToSwitchIntoBattle, including
its BattleRandom fallback. AI_SwitchOrTryItem, CheckAbleToSwitch and
AI_Switch are translated with their original bugs, and wPlayerUsedMoves
is recorded. The Shift-mode switch offer, trainer items, Pursuit on a
switch, Perish Song and Spikes remain refused.

Route 30 and its Berry house are enabled; Mr. Pokémon's house and
Route 31 stay closed. A refused trainer sighting does not restart until
the player moves, so a rollback cannot trap the player in view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 14:36:54 +02:00

53 lines
2.2 KiB
TypeScript

import { CrystalAssets } from "../src/game/rom/assets";
import { generatePartyMon } from "../src/game/party/pokemon";
import { GameWorld } from "../src/game/world/world";
import { martFixtureWorld } from "./mart-fixture";
// Test-only script on an already accessible clerk. The actual trainer party,
// class attributes, moves and battle text are read from the local ROM.
export function trainerFixtureWorld(assets: CrystalAssets, tail = [0x60, 0x33, 0x00, 0x01, 0x91], lose = false, winText = false) {
const clerk = assets.map("26:4").objects[0];
// Place the synthetic script in Joey's bank to test winlosstext's near pointer.
if (winText) clerk.script = 0x68 * 0x4000 + 0x3f00;
assets.reader.bytes.set([
0x64, ...(winText ? [0xa2, 0x58] : [0, 0]), 0, 0, // Joey beaten text or default
0x5e, 22, 1, // loadtrainer YOUNGSTER, JOEY1
...(lose ? [0x1e, 3, 1] : []), // loadvar VAR_BATTLETYPE, BATTLETYPE_CANLOSE
0x5f, ...tail,
], clerk.script);
const world = martFixtureWorld(assets);
const mon = generatePartyMon(assets.pokemon, {
species: 155, level: lose ? 2 : 50, item: 0, otId: world.playerId,
ot: "CHRIS", dvs: 0x823b, caughtData: 0,
});
mon.moves = [33, 0, 0, 0];
mon.pp = [35, 0, 0, 0];
if (lose) mon.hp = 1;
world.state.party = [mon];
world.state.pokedex.seen.add(155);
world.state.pokedex.caught.add(155);
return world;
}
/** Route 30 is enabled in production; only the start (below Bug Catcher
* Don's sight line, with a level 20 Totodile) is a test fixture. */
export function route30FixtureWorld(assets: CrystalAssets) {
let seed = 7;
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: 158, level: 20, item: 0, otId: world.playerId,
ot: "CHRIS", dvs: 0xffff, caughtData: 0,
});
mon.moves = [10, 0, 0, 0]; // Scratch
mon.pp = [35, 0, 0, 0];
world.state.party = [mon];
world.state.pokedex.seen.add(158);
world.state.pokedex.caught.add(158);
world.restore({ ...world.save(), map: "26:1", x: 1, y: 11, direction: "up" });
return world;
}