Open Goldenrod City, Whitney's gym and the Radio Tower quiz (#32)

Enable Goldenrod City and its buildings, the department store with its
elevator and rooftop, the Underground and its rooms, the Radio Tower
floors and the Route 35 gate.

Port what their scripts need: the herb shop, bargain shop, pharmacy and
rooftop sale marts with ROM stock, prices and text; the Name Rater; the
haircut brothers' happiness tables; the Lucky Number show over the party
and boxes with its saved number and Friday countdown (RTC day timers with
the 140-day wrap); coin and money balance boxes; Game Corner prize
registration through NewPokedexEntry; gift Pokémon to the box when the
party is full, with OT, nickname and MAIL (Kenya's 15-letter first line
now stores). Saves gain hallOfFame, timers and luckyNumber, with
defaults for older saves, and wUndergroundSwitchPositions joins the
saved script bytes.

The playthrough continues to the Bicycle, Whitney, the PLAINBADGE and the
radio card. SlotMachine and CardFlip are still refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
kawaandClaude Opus 5.5 committed 2026-10-07 09:26:07 +02:00
1 parent f4ef244fc7
commit 0930b780fc
19 files changed
+1596 -126

No files matched your search

+22 -9
View File
@@ -51,11 +51,16 @@ export class Playthrough {
this.world.restore(dump.save);
}
/** The usable move with the most power, or the first with PP. */
strongest = (mon: { moves: readonly number[]; pp: readonly number[] }) => {
/** The usable move with the most power, or the first with PP; a move
* Disable blocks (its slot in the side's disable count) is skipped. */
strongest = (
mon: { moves: readonly number[]; pp: readonly number[] },
disableCount = 0,
) => {
const disabled = disableCount ? ((disableCount >> 4) & 0xf) - 1 : -1;
let best = -1;
mon.moves.forEach((move, i) => {
if (!move || !(mon.pp[i] & 63)) return;
if (!move || !(mon.pp[i] & 63) || i === disabled) return;
if (
best < 0 ||
this.assets.pokemon.move(move).power >
@@ -82,19 +87,23 @@ export class Playthrough {
if (prompt?.kind === "action") {
const { engine } = battle,
mon = engine.player.mon;
const ball = world.state.bag.balls.findIndex(
const ball = world.liveState.bag.balls.findIndex(
(slot) => slot.item === C.POKE_BALL,
);
if (
engine.battleMode === C.WILD_BATTLE &&
engine.enemy.mon.species === this.wanted &&
!world.state.party.some((member) => member.species === this.wanted) &&
!world.liveState.party.some((member) => member.species === this.wanted) &&
ball >= 0
)
world.battleAction({ action: "item", item: C.POKE_BALL, slot: ball });
else if (engine.battleMode === C.WILD_BATTLE && mon.hp * 2 < mon.maxHp)
world.battleAction({ action: "run" });
else world.battleAction({ action: "fight", move: this.strongest(mon) });
else
world.battleAction({
action: "fight",
move: this.strongest(mon, engine.player.disableCount),
});
} else if (prompt?.kind === "yesno") world.battleChoose(1);
// SHIFT's party menu: keep the Pokémon out (or send the first
// that can fight when one must go).
@@ -102,7 +111,7 @@ export class Playthrough {
world.battleChoose(
prompt.cancel
? -1
: world.state.party.findIndex(
: world.liveState.party.findIndex(
(mon, i) =>
i !== battle.engine.curBattleMon && !mon.egg && mon.hp > 0,
),
@@ -409,6 +418,7 @@ export class Playthrough {
* on the way battles and moves, so the approach is planned again. */
talkTo = (id: number) => {
const world = this.world;
const COUNTER = 0x90;
for (let attempt = 0; attempt < 4; attempt++)
for (const [dx, dy, face] of [
[-1, 0, "right"],
@@ -417,8 +427,11 @@ export class Playthrough {
[0, -1, "down"],
] as const) {
const target = world.objects().find((object) => object.id === id)!;
const x = target.x + dx,
y = target.y + dy;
// Across a counter the player stands one tile further away.
const across =
world.assets.collision(world.map, target.x + dx, target.y + dy) === COUNTER;
const x = target.x + dx * (across ? 2 : 1),
y = target.y + dy * (across ? 2 : 1);
try {
if (!(world.x === x && world.y === y)) this.walk(x, y);
} catch {