A new game now runs NewGame's InitGender and OakSpeech natively before the bedroom: Boy or Girl, InitClock's hour and minute steppers with their YES/NO confirmations, Oak's speech with Wooper, and NamePlayer's preset names or naming field (a blank name takes CHRIS or KRIS). Texts, menus and time strings come from a new INTRO profile table verified by check:reference; Oak's and the player's pictures decode from TrainerPicPointers, ChrisPic/KrisPic and TrainerPalettes. The chosen time is kept as a minute offset from the device clock, which time of day, the weekday, NPC hours and the daily reset now read. The intro runs only when nothing is saved; a save made during it starts it again. Saves gain intro and clock.minuteOffset, migrated by absence. The shell's name and character fields are gone. Intentional change: the steppers start at the device time rather than 10:00. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5a0fe1e313
commit
b13fd20be4
28 files changed
+975
-101
No files matched your search
+44
-2
@@ -1,6 +1,12 @@
|
||||
import { RomReader } from "./reader";
|
||||
import { decompress } from "./lz";
|
||||
import { NUM_ITEMS, NUM_POKEMON, SYMBOLS } from "./profile";
|
||||
import {
|
||||
INTRO,
|
||||
NUM_ITEMS,
|
||||
NUM_POKEMON,
|
||||
NUM_TRAINER_CLASSES,
|
||||
SYMBOLS,
|
||||
} from "./profile";
|
||||
import { defaultTextContext, readString } from "../scripts/text";
|
||||
import type { Palette } from "../world/types";
|
||||
|
||||
@@ -254,8 +260,44 @@ export class PokemonData {
|
||||
/** Normal colors from PokemonPalettes, between white and black. */
|
||||
palette(species: number, shiny = false): Palette {
|
||||
this.checkSpecies(species);
|
||||
return this.middleColors(
|
||||
SYMBOLS.pokemonPalettes + species * 8 + (shiny ? 4 : 0),
|
||||
);
|
||||
}
|
||||
/** A trainer class's front picture (GetTrainerPic): 7x7 tiles, column
|
||||
* by column. */
|
||||
trainerPic(classId: number): Uint8Array {
|
||||
if (
|
||||
!Number.isInteger(classId) ||
|
||||
classId < 1 ||
|
||||
classId > NUM_TRAINER_CLASSES
|
||||
)
|
||||
throw new Error("Unknown trainer class.");
|
||||
const r = this.reader,
|
||||
p = SYMBOLS.pokemonPalettes + species * 8 + (shiny ? 4 : 0);
|
||||
entry = INTRO.TrainerPicPointers + (classId - 1) * 3;
|
||||
const pics1 = r.byte(SYMBOLS.picsBanks);
|
||||
const bank = r.byte(SYMBOLS.picsBanks + r.byte(entry) - (pics1 - 0x36));
|
||||
const tiles = decompress(r, r.address(bank, r.word(entry + 1)), 0x1000);
|
||||
if (tiles.length < 49 * 16) throw new Error("Picture data is truncated.");
|
||||
return tiles.slice(0, 49 * 16);
|
||||
}
|
||||
/** DrawIntroPlayerPic's uncompressed ChrisPic or KrisPic. */
|
||||
playerPic(female: boolean): Uint8Array {
|
||||
return this.reader.slice(female ? INTRO.KrisPic : INTRO.ChrisPic, 49 * 16);
|
||||
}
|
||||
/** TrainerPalettes, by class: CHRIS is 0 and KRIS shares class 1. */
|
||||
trainerPalette(classId: number): Palette {
|
||||
if (
|
||||
!Number.isInteger(classId) ||
|
||||
classId < 0 ||
|
||||
classId > NUM_TRAINER_CLASSES
|
||||
)
|
||||
throw new Error("Unknown trainer class.");
|
||||
return this.middleColors(INTRO.TrainerPalettes + classId * 4);
|
||||
}
|
||||
/** Two stored colors between white and black. */
|
||||
private middleColors(p: number): Palette {
|
||||
const r = this.reader;
|
||||
const color = (value: number) =>
|
||||
[
|
||||
Math.round(((value & 31) * 255) / 31),
|
||||
|
||||
@@ -281,6 +281,44 @@ export const OVERWORLD = {
|
||||
MansionPalette2: 0x496fe,
|
||||
} as const;
|
||||
|
||||
/** A new game's introduction (engine/menus/intro_menu.asm NewGame):
|
||||
* InitGender, OakSpeech with InitClock and NamePlayer, and the pictures
|
||||
* they show, keyed by their PRET labels for \`npm run check:reference\`. */
|
||||
export const INTRO = {
|
||||
TrainerPicPointers: 0x128000,
|
||||
TrainerPalettes: 0xb0ce,
|
||||
ChrisPic: 0x888a9,
|
||||
KrisPic: 0x88bb9,
|
||||
_AreYouABoyOrAreYouAGirlText: 0x1c0ca3,
|
||||
"InitGender.MenuData": 0x48e04,
|
||||
_OakText1: 0x1c1d35,
|
||||
_OakText2: 0x1c1da4,
|
||||
_OakText4: 0x1c1de5,
|
||||
_OakText5: 0x1c1e51,
|
||||
_OakText6: 0x1c4000,
|
||||
_OakText7: 0x1c4026,
|
||||
_OakTimeWokeUpText: 0x1bc29c,
|
||||
_OakTimeWhatTimeIsItText: 0x1bc2eb,
|
||||
_OakTimeWhatHoursText: 0x1bc2fd,
|
||||
_OakTimeHoursQuestionMarkText: 0x1bc305,
|
||||
_OakTimeHowManyMinutesText: 0x1bc308,
|
||||
_OakTimeWhoaMinutesText: 0x1bc31b,
|
||||
_OakTimeMinutesQuestionMarkText: 0x1bc323,
|
||||
_OakTimeOversleptText: 0x1bc326,
|
||||
_OakTimeYikesText: 0x1bc336,
|
||||
_OakTimeSoDarkText: 0x1bc34f,
|
||||
"ChrisNameMenuHeader.MaleNames": 0x882be,
|
||||
"KrisNameMenuHeader.FemaleNames": 0x882ee,
|
||||
"NamingScreenJumptable.PlayerNameString": 0x117a3,
|
||||
"NamePlayer.Chris": 0x60d3,
|
||||
"NamePlayer.Kris": 0x60de,
|
||||
String_oclock: 0x9087e,
|
||||
String_min: 0x9089f,
|
||||
"GetTimeOfDayString.nite_string": 0x90b71,
|
||||
"GetTimeOfDayString.morn_string": 0x90b76,
|
||||
"GetTimeOfDayString.day_string": 0x90b7b,
|
||||
} 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. */
|
||||
|
||||
Reference in new issue
Block a user