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:
Sébastien QUEROLandClaude Opus 5.5 committed 2026-10-03 16:24:29 +02:00
1 parent ec3d1cd5c4
commit 7909302db1
16 files changed
+585 -63

No files matched your search

+11 -22
View File
@@ -8,7 +8,7 @@ import {
restoredPP,
} from "../battle/battle";
import { receiveItem, removeFromSlot, type ItemSlot } from "../party/bag";
import { calcStats, maxPP, type PartyMon } from "../party/pokemon";
import { calcStats, maxPP, partyMonDetails, type PartyMon } from "../party/pokemon";
import type { GameTime } from "../world/game-time";
import { PartyEvolution, evoStoneCompatible } from "./evolution";
import {
@@ -60,7 +60,6 @@ const ITEM_MENUS: Record<string, ItemAction[]> = {
"MenuHeader_HoldableItem.MenuData": ["give", "toss", "quit"],
};
const {
EGG,
ITEMMENU_CURRENT,
ITEMMENU_PARTY,
ITEMMENU_CLOSE,
@@ -435,14 +434,14 @@ export class StartMenuRun {
party.map((mon) =>
// PlacePartyMonEvoStoneCompatibility replaces the HP bar.
action === C.PARTYMENUACTION_EVO_STONE
? mon.species === EGG
? mon.egg
? ""
: this.string(
evoStoneCompatible(r, mon.species, item)
? MENU["PlacePartyMonEvoStoneCompatibility.string_able"]
: MENU["PlacePartyMonEvoStoneCompatibility.string_not_able"],
)
: `:L${mon.level} HP ${mon.hp}/${mon.stats[0]}${mon.hp ? statusName(mon.status) : " FNT"}`,
: partyMonDetails(mon),
),
// InitPartyMenuWithCancel: the last row chosen, if still listed.
this.cursors.party >= 1 && this.cursors.party <= party.length
@@ -467,7 +466,7 @@ export class StartMenuRun {
/** GetMonSubmenuItems: field moves the Pokémon knows, then STATS,
* SWITCH, MOVE, ITEM or MAIL, and CANCEL if there is room. */
private monSubmenuItems(mon: PartyMon) {
if (mon.species === EGG)
if (mon.egg)
return [MONMENUITEM_STATS, MONMENUITEM_SWITCH, MONMENUITEM_CANCEL];
const r = this.reader,
items: number[] = [];
@@ -621,7 +620,7 @@ export class StartMenuRun {
/** GiveTakePartyMonItem: GIVE opens the pack, TAKE returns the item. */
private *giveTakePartyMonItem(slot: number): Gen {
const mon = this.state.party[slot];
if (mon.species === EGG) return;
if (mon.egg) return;
const choice = yield* this.menu(
[mon.nickname],
this.stringList(MENU["GiveTakeItemMenuData.Items"] + 2, 2),
@@ -751,7 +750,7 @@ export class StartMenuRun {
* or next party member's moves, skipping Eggs, unless a move is being
* moved. */
private *managePokemonMoves(slot: number): Gen {
if (this.state.party[slot].species === EGG) return;
if (this.state.party[slot].egg) return;
const where = this.string(MENU.String_MoveWhere);
let cursor = 0;
for (;;) {
@@ -807,7 +806,7 @@ export class StartMenuRun {
private cycleMoveScreen(slot: number, direction: "left" | "right") {
const party = this.state.party;
let current = slot;
const egg = (index: number) => party[index].species === EGG;
const egg = (index: number) => party[index].egg;
let step: "left" | "right" | "done" = direction;
for (let guard = 0; step !== "done" && guard < 64; guard++) {
if (step === "right") {
@@ -1021,7 +1020,7 @@ export class StartMenuRun {
for (;;) {
const slot = yield* this.partyMenu(PARTYMENUACTION_GIVE_ITEM);
if (slot < 0) return;
if (this.state.party[slot].species === EGG) {
if (this.state.party[slot].egg) {
yield* this.print(MENU["GiveItem.AnEggCantHoldAnItemText"]);
continue;
}
@@ -1152,7 +1151,7 @@ export class StartMenuRun {
case MENU.SacredAshEffect:
// _SacredAsh: SacredAshScript heals the party once the menu closes,
// if a Pokémon that is not an Egg has fainted (CheckAnyFaintedMon).
if (!this.state.party.some((mon) => mon.species !== EGG && !mon.hp))
if (!this.state.party.some((mon) => !mon.egg && !mon.hp))
return false;
this.useDisposableItem(use);
this.deferred = MENU.SacredAshScript;
@@ -1275,7 +1274,7 @@ export class StartMenuRun {
): Gen<number> {
const slot = yield* this.partyMenu(action, undefined, item);
if (slot < 0) return -1;
if (this.state.party[slot].species === EGG) {
if (this.state.party[slot].egg) {
yield* this.print(BATTLE.ItemCantUseOnEggText);
return -1;
}
@@ -1305,7 +1304,7 @@ export class StartMenuRun {
}
private changeHappiness(slot: number, action: number) {
const mon = this.state.party[slot];
if (mon.species === EGG) return;
if (mon.egg) return;
const tier = mon.happiness < 100 ? 0 : mon.happiness < 200 ? 1 : 2;
mon.happiness = changedHappiness(
mon.happiness,
@@ -1563,13 +1562,3 @@ interface FieldItemUse {
index: number;
}
const STATUS_NAMES: [number, string][] = [
[0x08, " PSN"],
[0x10, " BRN"],
[0x20, " FRZ"],
[0x40, " PAR"],
];
function statusName(status: number) {
if (status & 7) return " SLP";
return STATUS_NAMES.find(([bit]) => status & bit)?.[1] ?? "";
}