Open Route 31, Violet City and Sprout Tower (#31)
Enable Route 31 and its gate, Dark Cave's Violet entrance, Violet City with its buildings and Sprout Tower. Port the script commands they use: gettrainername, askforphonenumber (with AddPhoneNumber's room for Mom and Elm; addcellnum now reports failure as the original does), checkpokemail, loadmenu/_2dmenu/closewindow, giveegg and trade (NPCTrade, with wTradeFlags saved). Eggs keep their species with an egg flag, as wPartySpecies does. The pack's TM/HM counts receive, check and toss TMs and HMs, which are named TM01-HM07. The playthrough continues through Route 31 (with Mom's call), Violet City and Sprout Tower to Elder Li's Flash. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ec3d1cd5c4
commit
7909302db1
16 files changed
+585
-63
No files matched your search
+143
-1
@@ -14,7 +14,23 @@ const NEW_BARK = "24:4",
|
||||
ROUTE_29 = "24:3",
|
||||
CHERRYGROVE = "26:3",
|
||||
ROUTE_30 = "26:1",
|
||||
MR_POKEMONS_HOUSE = "26:10";
|
||||
MR_POKEMONS_HOUSE = "26:10",
|
||||
CHERRYGROVE_CENTER = "26:5",
|
||||
ROUTE_31 = "26:2",
|
||||
ROUTE_31_GATE = "26:11",
|
||||
VIOLET = "10:5",
|
||||
VIOLET_CENTER = "10:10",
|
||||
VIOLET_GYM = "10:7",
|
||||
SPROUT_1F = "3:1",
|
||||
SPROUT_2F = "3:2",
|
||||
SPROUT_3F = "3:3";
|
||||
const EVENT_TALKED_TO_MOM_AFTER_MYSTERY_EGG_QUEST = 0x40,
|
||||
EVENT_GOT_HM05_FLASH = 0x14,
|
||||
EVENT_BEAT_FALKNER = 0x2bd,
|
||||
EVENT_GOT_TM31_MUD_SLAP = 0x8,
|
||||
EVENT_GOT_TOGEPI_EGG_FROM_ELMS_AIDE = 0x2d,
|
||||
EVENT_ELMS_AIDE_IN_VIOLET_POKEMON_CENTER = 0x48c,
|
||||
ENGINE_ZEPHYRBADGE = 0x1b;
|
||||
|
||||
/** The acceptance run for PLAN section 2: a new game played from the
|
||||
* introduction to the catching tutorial through the world's own inputs,
|
||||
@@ -196,6 +212,89 @@ describe.skipIf(!existsSync(romPath))(
|
||||
expect(world.save()).toEqual(saved);
|
||||
};
|
||||
|
||||
const lead = () => world.state.party[0];
|
||||
/** PokecenterNurseScript from in front of the counter, then back out. */
|
||||
const heal = (center: string, city: string) => {
|
||||
enter(center, "up");
|
||||
walk(3, 3);
|
||||
world.move("up"); // the counter: this only turns the player
|
||||
world.interact();
|
||||
play();
|
||||
expect(world.state.party.every((mon) => mon.egg || mon.hp === mon.stats[0])).toBe(true);
|
||||
exit(city);
|
||||
};
|
||||
/** Pace between two neighbouring grass tiles (the nearest pair) until
|
||||
* the lead reaches `level`. `rest` heals and comes back here. */
|
||||
const train = (level: number, rest: () => void) => {
|
||||
const map = world.mapId,
|
||||
grass = world.assets.grassCollisions();
|
||||
const isGrass = (x: number, y: number) =>
|
||||
x >= 0 && y >= 0 && x < world.map.width && y < world.map.height &&
|
||||
grass.includes(world.assets.collision(world.map, x, y));
|
||||
for (let round = 0; round < 2000 && lead().level < level; round++) {
|
||||
if (lead().hp * 2 < lead().stats[0]) {
|
||||
rest();
|
||||
expect(world.mapId).toBe(map);
|
||||
}
|
||||
const pairs: { x: number; y: number; length: number }[] = [];
|
||||
for (let y = 0; y < world.map.height; y++)
|
||||
for (let x = 0; x < world.map.width; x++)
|
||||
if (isGrass(x, y) && isGrass(x + 1, y))
|
||||
try {
|
||||
pairs.push({ x, y, length: findPath(world, x, y).length });
|
||||
} catch {
|
||||
/* unreachable */
|
||||
}
|
||||
pairs.sort((a, b) => a.length - b.length);
|
||||
const { x, y } = pairs[0];
|
||||
walk(x, y);
|
||||
walk(x + 1, y);
|
||||
}
|
||||
expect(lead().level).toBeGreaterThanOrEqual(level);
|
||||
};
|
||||
|
||||
/** Take the stairs (or another warp) to `target`, trying each. */
|
||||
const stairs = (target: string) => {
|
||||
for (const warp of world.map.warps.filter((warp) => warp.targetMap === target)) {
|
||||
try {
|
||||
findPath(world, warp.x, warp.y);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
walk(warp.x, warp.y);
|
||||
if (world.mapId === target) return;
|
||||
}
|
||||
throw new Error(`No way from ${world.mapId} to ${target}`);
|
||||
};
|
||||
/** Find a way through warps among `maps` to `target`, as a player
|
||||
* trying staircases would: depth first, backing out of dead ends. */
|
||||
const route = (target: string, maps: string[], depth = 6): boolean => {
|
||||
if (world.mapId === target) return true;
|
||||
if (!depth) return false;
|
||||
const here = world.save();
|
||||
for (const warp of world.map.warps) {
|
||||
if (!maps.includes(warp.targetMap) && warp.targetMap !== target) continue;
|
||||
try {
|
||||
findPath(world, warp.x, warp.y);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
const from = world.mapId;
|
||||
walk(warp.x, warp.y);
|
||||
if (world.mapId !== from && route(target, maps, depth - 1)) return true;
|
||||
world.restore(here);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
/** From Route 31: through the gate to Violet's Pokémon Center and back. */
|
||||
const restInViolet = () => {
|
||||
exit(ROUTE_31_GATE, "left");
|
||||
exit(VIOLET, "left");
|
||||
heal(VIOLET_CENTER, VIOLET);
|
||||
exit(ROUTE_31_GATE, "right");
|
||||
exit(ROUTE_31, "right");
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
log.length = 0;
|
||||
});
|
||||
@@ -317,5 +416,48 @@ describe.skipIf(!existsSync(romPath))(
|
||||
expect(save.rivalName).toBe("SILVER");
|
||||
expect(save.intro).toBe(false);
|
||||
});
|
||||
|
||||
it("trains and walks Route 30 and Route 31 to Violet City", () => {
|
||||
traceFailures();
|
||||
const restInCherrygrove = () => {
|
||||
cross("left", CHERRYGROVE);
|
||||
heal(CHERRYGROVE_CENTER, CHERRYGROVE);
|
||||
cross("right", ROUTE_29);
|
||||
};
|
||||
heal(CHERRYGROVE_CENTER, CHERRYGROVE);
|
||||
cross("right", ROUTE_29);
|
||||
train(10, restInCherrygrove);
|
||||
cross("left", CHERRYGROVE);
|
||||
heal(CHERRYGROVE_CENTER, CHERRYGROVE);
|
||||
cross("up", ROUTE_30);
|
||||
cross("up", ROUTE_31);
|
||||
exit(ROUTE_31_GATE, "left");
|
||||
// Mom worries when the player has not come home since the Egg; the
|
||||
// call rings on a step after Route 31's callback queued it.
|
||||
expect(world.state.events.has(EVENT_TALKED_TO_MOM_AFTER_MYSTERY_EGG_QUEST), log.join("|")).toBe(true);
|
||||
exit(VIOLET, "left");
|
||||
expect(world.state.flags.size).toBeGreaterThan(0);
|
||||
reload();
|
||||
});
|
||||
|
||||
it("trains on Route 31 and earns Flash from Elder Li in Sprout Tower", () => {
|
||||
traceFailures();
|
||||
exit(ROUTE_31_GATE, "right");
|
||||
exit(ROUTE_31, "right");
|
||||
train(14, restInViolet);
|
||||
restInViolet();
|
||||
exit(ROUTE_31_GATE, "left");
|
||||
exit(VIOLET, "left");
|
||||
enter(SPROUT_1F, "up");
|
||||
expect(route(SPROUT_3F, [SPROUT_1F, SPROUT_2F])).toBe(true);
|
||||
walk(10, 3);
|
||||
world.move("up"); // Elder Li: this only turns the player
|
||||
world.interact();
|
||||
play();
|
||||
const pages = log.join("\n").replace(/\n/g, " ");
|
||||
expect(pages).toContain("FLASH");
|
||||
expect(world.state.events.has(EVENT_GOT_HM05_FLASH)).toBe(true);
|
||||
reload();
|
||||
});
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user