Open Route 29 and Cherrygrove City
Checks / web (push) Canceled after 0s

- Enable Route 29, its Route 46 gate, Cherrygrove City and its buildings.
- Run item balls through FindItemInBallScript and fruit trees through
  FruitTreeScript, with a native daily reset of the daily engine flags.
- Port the Pokémon Center nurse (HealParty with the PP Up clamp,
  CheckPokerus, phone-call checks) and record the whiteout spawn on
  entering a Pokémon Center, as EnterMapWarp does.
- Port standard Poké Marts with keyboard, D-pad and pointer menus.
- Add the recoil battle command (Struggle) and VAR_BOXSPACE.
- Save fruit trees, the daily reset day and the last spawn map; older
  version-3 backups migrate by absence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Sébastien QUEROLandClaude Opus 5.5 committed 2026-09-23 17:25:02 +02:00
1 parent 11d1153802
commit 796fe8f6d5
24 files changed
+1549 -59

No files matched your search

+14
View File
@@ -4,6 +4,8 @@ import {
LAST_MAP_GROUP_SIZE,
MAP_GROUP_COUNT,
NATIVE_MAPS,
NUM_ENGINE_FLAGS,
RAM,
SYMBOLS,
} from "./profile";
import { defaultTextContext, readString } from "../scripts/text";
@@ -360,6 +362,18 @@ export class CrystalAssets {
}
throw new Error("Wild encounter data is unterminated.");
}
/** Engine flags kept in the four bytes CheckDailyResetTimer clears
* (wDailyFlags1 through wUnusedDailyFlag), from the ROM's EngineFlags. */
dailyEngineFlags(): number[] {
const r = this.reader,
flags: number[] = [];
for (let index = 0; index < NUM_ENGINE_FLAGS; index++) {
const address = r.word(SYMBOLS.engineFlags + index * 3);
if (address >= RAM.dailyFlags1 && address <= RAM.unusedDailyFlag)
flags.push(index);
}
return flags;
}
/** Emote bubble graphics (data/sprites/emotes.asm). */
emote(index: number): Uint8Array {
const r = this.reader,
+14
View File
@@ -129,6 +129,20 @@ export class PokemonData {
if (!pocket) throw new Error("Item has no pocket.");
return pocket;
}
/** ItemAttributes: price, in Poké Dollars. */
itemPrice(item: number): number {
this.checkItem(item);
return this.reader.word(SYMBOLS.itemAttributes + (item - 1) * 7);
}
/** ItemAttributes permissions, bit 7 (CANT_TOSS_F): mostly key items and
* TMs/HMs, checked by _CheckTossableItem before a mart buys one back. */
itemUnsellable(item: number): boolean {
this.checkItem(item);
const permissions = this.reader.byte(
SYMBOLS.itemAttributes + (item - 1) * 7 + 4,
);
return Boolean(permissions & 0x80);
}
pocketName(pocket: Pocket): string {
const r = this.reader,
bank = r.bank(SYMBOLS.itemPocketNames);
+47
View File
@@ -40,6 +40,7 @@ export const SYMBOLS = {
giveItemScript: 0x96f77,
putItemInPocketText: 0x9705b,
pocketIsFullText: 0x97060,
findItemInBallScript: 0x122ce,
caughtAskNicknameText: 0x4db44,
nicknameStrings: 0x11780,
wildBattleScript: 0x97cf9,
@@ -48,6 +49,22 @@ export const SYMBOLS = {
johtoGrassWildMons: 0x2a5e9,
grassMonProbTable: 0x2a1cb,
grassCollisions: 0x149ea,
fruitTreeScript: 0x44000,
fruitTreeItems: 0x44097,
engineFlags: 0x80462,
marts: 0x160a9,
martWelcomeText: 0x15f83,
martAskMoreText: 0x15fb9,
martComeAgainText: 0x15fb4,
martHowManyText: 0x15e0e,
martFinalPriceText: 0x15e13,
martNoMoneyText: 0x15fa5,
martPackFullText: 0x15faa,
martThanksText: 0x15fa0,
martCantBuyText: 0x15faf,
martSellHowManyText: 0x15f73,
martSellPriceText: 0x15f78,
martBoughtText: 0x15fbe,
} as const;
/** Assembly routines that scripts `callasm` but that do nothing. */
@@ -60,6 +77,11 @@ export const EMPTY_ROUTINES = {
export const NATIVE_ROUTINES = {
halveMoney: 0x12513,
getWhiteoutSpawn: 0x12527,
tryReceiveItem: 0x122f8,
getCurTreeFruit: 0x44041,
tryResetFruitTrees: 0x4404c,
checkFruitTree: 0x44055,
pickedFruitTree: 0x4405f,
} as const;
/** Native handlers are selected by the routine a special index points to,
@@ -73,6 +95,9 @@ export const SPECIAL_ROUTINES = {
toggleMaptileDecorations: 0x26feb,
fadeOutToWhite: 0x8c084,
healParty: 0xc658,
healMachineAnim: 0x12324,
checkPokerus: 0xc419,
stubbedTrainerRankingsHealings: 0x1060a2,
warpToSpawnPoint: 0x97c28,
} as const;
@@ -90,6 +115,12 @@ export const RAM = {
enemyMonNickname: 0xc616,
curPartyLevel: 0xd143,
monOrItemNameBuffer: 0xd050,
curFruit: 0xd03f,
dailyFlags1: 0xdc1e,
unusedDailyFlag: 0xdc21,
itemQuantityChange: 0xd10c,
/** hMoneyTemp: HRAM, not WRAM like the other entries above. */
moneyTemp: 0xffc3,
} as const;
/** Event constants named directly by translated special routines. */
@@ -101,6 +132,11 @@ export const EVENTS = {
playersHouseBigDoll: 1860,
} as const;
/** Engine flag indices named directly by translated routines. */
export const ENGINE_FLAGS = {
allFruitTrees: 84,
} as const;
/** Battle engine tables and texts, keyed by their PRET labels so that
* \`npm run check:reference\` can verify each address by name. */
export const BATTLE = {
@@ -187,6 +223,7 @@ export const BATTLE = {
ProtectedByText: 0x815a9,
ProtectingItselfText: 0x8167a,
RageBuildingText: 0x810f3,
RecoilText: 0x8134d,
RecoveredUsingText: 0x80bc2,
RegainedHealthText: 0x8100c,
SafeguardProtectText: 0x81733,
@@ -217,6 +254,8 @@ export const BATTLE = {
export const MAP_GROUP_COUNT = 26;
export const NUM_POKEMON = 251;
export const NUM_ITEMS = 190;
export const NUM_ENGINE_FLAGS = 162;
export const NUM_FRUIT_TREES = 30;
/** The last group has no following pointer to bound it. */
export const LAST_MAP_GROUP_SIZE = 11;
@@ -224,11 +263,19 @@ export const LAST_MAP_GROUP_SIZE = 11;
* maps remain unavailable; the label only distinguishes interiors that share
* a ROM landmark name. */
export const NATIVE_MAPS: Record<string, string> = {
"24:3": "Route 29",
"24:13": "Route 29 gate",
"24:4": "New Bark Town",
"24:5": "Elm’s lab",
"24:6": "Your home",
"24:7": "Your room",
"24:8": "Neighbor’s house",
"24:9": "Elm’s house",
"26:3": "Cherrygrove City",
"26:4": "Poké Mart",
"26:5": "Pokémon Center",
"26:6": "Gym speech house",
"26:7": "Guide Gent’s house",
"26:8": "Evolution speech house",
};
export const INITIAL_POSITION = { map: "24:7", x: 3, y: 3 } as const;