Port the remaining battle items, the stats screen and hBattleTurn at the menu

- Run every non-ball item effect usable in battle from the ROM's
  ItemEffects: Full Restore, revives, bitter medicine, Bitter Berry, Ethers
  and Elixers, X items, X Accuracy, Guard Spec, Dire Hit and Poké Doll.
- Read happiness changes from HappinessChanges, including the larger
  level-up gain where a Pokémon was caught.
- Model hBattleTurn at the battle menu, including the trainer AI's writes,
  and drop the status-heal refusal.
- Port the stats screen for battles and Bill's PC.
- Compare the send-out and withdraw texts, RestoreHealth, RestorePP,
  ChangeHappiness and CalcPlayerStats with the original routines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Sébastien QUEROLandClaude Opus 5.5 committed 2026-09-30 11:24:55 +02:00
1 parent cfd083c16d
commit b13e09ec20
22 files changed
+1547 -171

No files matched your search

+39
View File
@@ -1,10 +1,12 @@
import { RomReader } from "./reader";
import { decompress } from "./lz";
import {
BATTLE,
INTRO,
NUM_ITEMS,
NUM_POKEMON,
NUM_TRAINER_CLASSES,
STATS,
SYMBOLS,
} from "./profile";
import { defaultTextContext, readString } from "../scripts/text";
@@ -178,6 +180,43 @@ export class PokemonData {
);
}
/** PrintType: the name of a type from TypeNames. */
typeName(type: number): string {
if (!Number.isInteger(type) || type < 0 || type > 27)
throw new Error("Unknown type.");
const r = this.reader;
return readString(
r,
r.near(STATS.TypeNames + type * 2, r.bank(STATS.TypeNames)),
defaultTextContext(""),
);
}
/** GetGender: the Attack and Speed DVs against the species' ratio;
* undefined for genderless species. */
gender(species: number, dvs: number): "male" | "female" | undefined {
const ratio = this.baseData(species).genderRatio;
if (ratio === 0xff) return undefined; // GENDER_UNKNOWN
if (!ratio) return "male"; // GENDER_F0
if (ratio === 0xfe) return "female"; // GENDER_F100
const value = ((dvs >> 8) & 0xf0) | ((dvs >> 4) & 0x0f);
return ratio < value ? "male" : "female";
}
/** PrintTempMonStats' labels: Attack, Defense, Sp. Atk, Sp. Def, Speed. */
statNames(): string[] {
return readString(
this.reader,
BATTLE["PrintTempMonStats.StatNames"],
defaultTextContext(""),
64,
)
.split("\n")
.filter(Boolean);
}
/** A string of the Pokémon stats screen. */
statsString(label: keyof typeof STATS): string {
return readString(this.reader, STATS[label], defaultTextContext(""));
}
move(move: number): MoveData {
if (!Number.isInteger(move) || move < 1 || move > 251)
throw new Error("Unknown move.");
+35
View File
@@ -320,6 +320,23 @@ export const INTRO = {
"GetTimeOfDayString.day_string": 0x90b7b,
} as const;
/** The Pokémon stats screen's strings and the type names, keyed by their
* PRET labels so that \`npm run check:reference\` can verify each address. */
export const STATS = {
"LoadPinkPage.Status_Type": 0x4e119,
"LoadPinkPage.OK_str": 0x4e127,
"LoadPinkPage.ExpPointStr": 0x4e12b,
"LoadPinkPage.LevelUpStr": 0x4e136,
"LoadPinkPage.ToStr": 0x4e13f,
"LoadPinkPage.PkrsStr": 0x4e142,
"LoadGreenPage.Item": 0x4e1a0,
"LoadGreenPage.ThreeDashes": 0x4e1a5,
"LoadGreenPage.Move": 0x4e1a9,
IDNoString: 0x4e21e,
OTString: 0x4e222,
TypeNames: 0x5097b,
} as const;
/** Pokémon Center and house PCs, Bill's PC and Oak's PC: routines, menu
* data, strings and texts, keyed by their PRET labels so that
* \`npm run check:reference\` can verify each address by name. */
@@ -495,6 +512,24 @@ export const BATTLE = {
ItemCantUseOnEggText: 0xf810,
OakThisIsntTheTimeText: 0x10af3,
UseOnWhichPKMNString: 0x504f3,
FullRestoreEffect: 0xf128,
ReviveEffect: 0xf0c8,
RevivalHerbEffect: 0xf0a9,
EnergypowderEffect: 0xf18c,
EnergyRootEffect: 0xf190,
HealPowderEffect: 0xefad,
BitterBerryEffect: 0xf16a,
RestorePPEffect: 0xf5bf,
XItemEffect: 0xf4c5,
XAccuracyEffect: 0xf482,
GuardSpecEffect: 0xf4ab,
DireHitEffect: 0xf4b8,
PokeDollEffect: 0xf48f,
XItemStats: 0xf504,
HappinessChanges: 0x7221,
ItemLooksBitterText: 0xf80b,
RestoreThePPOfWhichMoveText: 0xf72a,
PPRestoredText: 0xf739,
HungOnText: 0x80e99,
HurtByBurnText: 0x807f8,
HurtByPoisonText: 0x807e2,