The Old, Good and Super Rods run FishFunction facing water: the map's fish group and Fish pick no fish, no bite, or a Pokémon from FishGroups and the time of day's TimeFishGroups. The player casts and jiggles with the fishing frames drawn from LoadFishingGFX's tiles, shows "!" on a bite, and the hooked Pokémon attacks as BATTLETYPE_FISH. Facing pieces now use signed offsets, which the rod above and left of the player needs. Wild Magikarp no longer stop a battle: LoadEnemyMon redraws their DVs through CalcMagikarpLength and its filters, as the original does. The length routine matches the original in the reference emulator. Part of #27 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
171 lines
5.5 KiB
TypeScript
171 lines
5.5 KiB
TypeScript
import { CrystalAssets } from "../src/game/rom/assets";
|
|
import { GameWorld } from "../src/game/world/world";
|
|
import { OW } from "../src/game/world/constants";
|
|
import { generatePartyMon } from "../src/game/party/pokemon";
|
|
|
|
/** `room` places the player at the bedroom PC or poster, "center" at
|
|
* Cherrygrove's Pokémon Center PC with two Pokémon ("candy": the first at
|
|
* level 13, with a Rare Candy); otherwise outdoors ("pokegear": with the
|
|
* Pokégear, its map and phone cards, and Mom's and Elm's numbers). "mom"
|
|
* faces Mom in the living room after the Mystery Egg, with her bank open
|
|
* and ¥2300 saved. "fly" stands outdoors with a Pidgeot that knows Fly,
|
|
* the Storm Badge, and New Bark Town and Cherrygrove City visited.
|
|
* "headbutt" faces Route 29's first reachable Headbutt tree from below
|
|
* with a Pokémon that knows Headbutt; Random always returning 0 drops a
|
|
* sleeping Hoothoot. "fishing" faces the sea on New Bark Town's shore
|
|
* with the Old Rod, which then hooks a Magikarp. */
|
|
export function foundationFixture(
|
|
assets: CrystalAssets,
|
|
room:
|
|
| boolean
|
|
| "poster"
|
|
| "center"
|
|
| "candy"
|
|
| "pokegear"
|
|
| "mom"
|
|
| "fly"
|
|
| "headbutt"
|
|
| "fishing",
|
|
) {
|
|
const world = new GameWorld(
|
|
assets,
|
|
() => new Date("2026-09-22T12:00:00"),
|
|
() => 0,
|
|
);
|
|
world.state.scenes["24:4"] = 1;
|
|
world.state.bag.keyItems.push(OW.BICYCLE);
|
|
if (room === "center" || room === "candy") {
|
|
world.state.party = [155, 16].map((species, i) =>
|
|
generatePartyMon(assets.pokemon, {
|
|
species,
|
|
level: room === "candy" && !i ? 13 : 8 - i * 4,
|
|
item: 0,
|
|
otId: world.playerId,
|
|
ot: "CHRIS",
|
|
dvs: 0x9abc,
|
|
caughtData: 0,
|
|
}),
|
|
);
|
|
world.state.bag.items = [{ item: 18, quantity: 5 }]; // POTION
|
|
if (room === "candy")
|
|
world.state.bag.items.push({ item: 0x20, quantity: 1 });
|
|
}
|
|
if (room === "pokegear" || room === "mom") {
|
|
world.state.party = [
|
|
generatePartyMon(assets.pokemon, {
|
|
species: 155,
|
|
level: 8,
|
|
item: 0,
|
|
otId: world.playerId,
|
|
ot: "CHRIS",
|
|
dvs: 0x9abc,
|
|
caughtData: 0,
|
|
}),
|
|
];
|
|
for (const flag of [OW.ENGINE_MAP_CARD, OW.ENGINE_PHONE_CARD, 4])
|
|
world.state.flags.add(flag); // 4: ENGINE_POKEGEAR
|
|
world.state.phone = [OW.PHONECONTACT_MOM, OW.PHONECONTACT_ELM];
|
|
world.state.events.add(26); // EVENT_GOT_A_POKEMON_FROM_ELM
|
|
// Walking outside clears the bedroom's temporary events.
|
|
for (let event = 0; event < 8; event++) world.state.events.delete(event);
|
|
}
|
|
if (room === "fly") {
|
|
const mon = generatePartyMon(assets.pokemon, {
|
|
species: 18, // PIDGEOT
|
|
level: 40,
|
|
item: 0,
|
|
otId: world.playerId,
|
|
ot: "CHRIS",
|
|
dvs: 0x9abc,
|
|
caughtData: 0,
|
|
});
|
|
mon.moves = [OW.FLY, 0, 0, 0];
|
|
mon.pp = [15, 0, 0, 0];
|
|
world.state.party = [mon];
|
|
world.state.flags.add(OW.ENGINE_STORMBADGE);
|
|
for (const spawn of [14, 15])
|
|
// SPAWN_NEW_BARK, SPAWN_CHERRYGROVE
|
|
world.state.flags.add(assets.spawnEngineFlag(spawn)!);
|
|
for (let event = 0; event < 8; event++) world.state.events.delete(event);
|
|
}
|
|
if (room === "fishing") {
|
|
world.state.party = [
|
|
generatePartyMon(assets.pokemon, {
|
|
species: 155,
|
|
level: 8,
|
|
item: 0,
|
|
otId: world.playerId,
|
|
ot: "CHRIS",
|
|
dvs: 0x9abc,
|
|
caughtData: 0,
|
|
}),
|
|
];
|
|
world.state.bag.keyItems.push(0x3a); // OLD_ROD
|
|
for (let event = 0; event < 8; event++) world.state.events.delete(event);
|
|
}
|
|
if (room === "headbutt") {
|
|
const mon = generatePartyMon(assets.pokemon, {
|
|
species: 158, // TOTODILE
|
|
level: 30,
|
|
item: 0,
|
|
otId: world.playerId,
|
|
ot: "CHRIS",
|
|
dvs: 0x9abc,
|
|
caughtData: 0,
|
|
});
|
|
mon.moves = [OW.HEADBUTT, 33, 0, 0]; // TACKLE
|
|
mon.pp = [15, 35, 0, 0];
|
|
world.state.party = [mon];
|
|
for (let event = 0; event < 8; event++) world.state.events.delete(event);
|
|
}
|
|
if (room === "mom") {
|
|
// EVENT_GOT_MYSTERY_EGG_FROM_MR_POKEMON, EVENT_GAVE_MYSTERY_EGG_TO_ELM
|
|
// and EVENT_TALKED_TO_MOM_AFTER_MYSTERY_EGG_QUEST.
|
|
for (const event of [30, 31, 64]) world.state.events.add(event);
|
|
world.state.flags.add(OW.ENGINE_MOM_ACTIVE);
|
|
world.state.scenes["24:6"] = 1;
|
|
world.state.momsMoney = 2300;
|
|
}
|
|
const save = world.save();
|
|
const [map, x, y] =
|
|
room === "headbutt"
|
|
? headbuttSpot(assets)
|
|
: room === "fishing"
|
|
? ["24:4", 17, 7]
|
|
: room === "center" || room === "candy"
|
|
? ["26:5", 9, 2]
|
|
: room === "poster"
|
|
? ["24:7", 6, 1]
|
|
: room === "mom"
|
|
? ["24:6", 7, 3]
|
|
: room &&
|
|
room !== "pokegear" &&
|
|
room !== "fly" &&
|
|
room !== "fishing"
|
|
? ["24:7", 2, 2]
|
|
: ["24:4", 9, 6];
|
|
world.restore({
|
|
...save,
|
|
map,
|
|
x,
|
|
y,
|
|
direction: room === "mom" ? "down" : room === "fishing" ? "right" : "up",
|
|
objects: [],
|
|
});
|
|
return world;
|
|
}
|
|
|
|
/** Below Route 29's first Headbutt tree with walkable ground under it. */
|
|
function headbuttSpot(assets: CrystalAssets): [string, number, number] {
|
|
const map = assets.map("24:3");
|
|
for (let y = 1; y < map.height - 1; y++)
|
|
for (let x = 0; x < map.width; x++)
|
|
if (
|
|
assets.collision(map, x, y) === OW.COLL_HEADBUTT_TREE &&
|
|
assets.permission(assets.collision(map, x, y + 1)) === 0 &&
|
|
!map.objects.some((o) => o.x === x && o.y <= y + 1 && o.y >= y)
|
|
)
|
|
return ["24:3", x, y + 1];
|
|
throw new Error("No reachable Headbutt tree.");
|
|
}
|