Breed at the Day-Care, hatch Eggs, draw MAIL stationery and browse the Hall of Fame (Closes #29)
- Day-Care and breeding: deposits, withdrawal pricing and growth, compatibility, egg species, DVs and moves, DayCareStep, egg collection and the Odd Egg, compared with the cartridge's routines. - CountStep: walking happiness, DoEggStep, DayCareStep and DoBikeStep in the original's order; HatchEggs with the ROM's hatch animation, compared frame by frame with the original. - MAIL: all ten stationery types and the Portrait MAIL picture, compared with the cartridge's tilemaps, graphics and palettes. - Printing: Bill's PC boxes, Pokedex PRNT, MAIL and the Photo Studio take the original's absent-printer path. - Hall of Fame records (saved apart from the League flag, ready for the League to call) and the Hall of Fame PC, compared with the cartridge's _HallOfFamePC on 255 pages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7d03f5c5ca
commit
e49bc6de9a
49 files changed
+6028
-88
No files matched your search
@@ -0,0 +1,113 @@
|
||||
import type { HallOfFameTeam } from "../party/hall-of-fame";
|
||||
import { PokemonData } from "../rom/pokemon";
|
||||
import { JOHTO } from "../rom/profile";
|
||||
import { fontTiles, FRAME_TILE, SPACE_TILE, textTiles } from "../rom/font";
|
||||
import type { ScriptEffect } from "./interpreter";
|
||||
import { blankScreen, SCREEN_BUTTONS, type TileScreen } from "./screen";
|
||||
import { defaultTextContext, readString } from "./text";
|
||||
import type { GameSound } from "../audio/sound";
|
||||
|
||||
/** _HallOfFamePC.DisplayMonAndStrings: DisplayHOFMon's page for one
|
||||
* Pokémon of a team, over _LoadFontsBattleExtra's tiles, in
|
||||
* SCGB_PLAYER_OR_MON_FRONTPIC_PALS's single palette. The picture is shown
|
||||
* at rest: HOF_AnimateFrontpic's ANIM_MON_HOF animation is not ported. */
|
||||
export function hallOfFameScreen(
|
||||
data: PokemonData,
|
||||
team: HallOfFameTeam,
|
||||
slot: number,
|
||||
frame: number,
|
||||
): TileScreen {
|
||||
const mon = team.mons[slot],
|
||||
r = data.reader;
|
||||
const tiles = fontTiles(r, frame);
|
||||
tiles.set(r.slice(JOHTO.FontBattleExtra, 25 * 16), 0x60 * 16);
|
||||
tiles.set(data.frontPic(mon.species, PokemonData.unownLetter(mon.dvs)));
|
||||
const title = readString(
|
||||
r,
|
||||
JOHTO["_HallOfFamePC.TimeFamer"],
|
||||
defaultTextContext(""),
|
||||
);
|
||||
const screen = blankScreen(
|
||||
tiles,
|
||||
`hof-${mon.species}-${mon.dvs}-${frame}`,
|
||||
[data.palette(mon.species, PokemonData.shiny(mon.dvs))],
|
||||
`${team.win}${title.trimStart()}: ${mon.nickname}, ${data.speciesName(mon.species)}, level ${mon.level}, ID ${String(mon.otId).padStart(5, "0")}`,
|
||||
);
|
||||
screen.map.fill(SPACE_TILE);
|
||||
const put = (x: number, y: number, values: readonly number[]) =>
|
||||
screen.map.splice(y * 20 + x, values.length, ...values);
|
||||
const text = (x: number, y: number, value: string) =>
|
||||
put(
|
||||
x,
|
||||
y,
|
||||
[...value].flatMap((character) =>
|
||||
character === '"' ? [0xe1] : textTiles(character),
|
||||
),
|
||||
);
|
||||
// Textbox with 18 interior columns and three/four interior rows.
|
||||
const box = (y: number, height: number) => {
|
||||
put(0, y, [FRAME_TILE, ...Array(18).fill(FRAME_TILE + 1), FRAME_TILE + 2]);
|
||||
for (let row = y + 1; row <= y + height; row++) {
|
||||
put(0, row, [FRAME_TILE + 3]);
|
||||
put(19, row, [FRAME_TILE + 3]);
|
||||
}
|
||||
put(0, y + height + 1, [
|
||||
FRAME_TILE + 4,
|
||||
...Array(18).fill(FRAME_TILE + 1),
|
||||
FRAME_TILE + 5,
|
||||
]);
|
||||
};
|
||||
box(0, 3);
|
||||
box(12, 4);
|
||||
// "HOF Master!" needs a count of HOF_MASTER_COUNT + 1, which the capped
|
||||
// count never reaches: the original's bug (docs/bugs_and_glitches.md)
|
||||
// leaves every team "-Time Famer".
|
||||
text(1, 2, title);
|
||||
text(2, 2, String(team.win).padStart(3, " "));
|
||||
for (let x = 0; x < 7; x++)
|
||||
for (let y = 0; y < 7; y++) put(x + 6, y + 5, [x * 7 + y]);
|
||||
put(1, 13, [0x74, 0xf2]); // № and <DOT>.
|
||||
text(3, 13, String(mon.species).padStart(3, "0"));
|
||||
// GetBasePokemonName drops the Nidorans' gender sign.
|
||||
text(7, 13, data.speciesName(mon.species).split(/[♂♀]/)[0]);
|
||||
const gender = data.gender(mon.species, mon.dvs);
|
||||
text(18, 13, gender === "male" ? "♂" : gender === "female" ? "♀" : " ");
|
||||
text(8, 14, `/${mon.nickname}`);
|
||||
// PrintLevel: <LV> and two digits, or three digits over the <LV>.
|
||||
if (mon.level < 100) {
|
||||
put(1, 16, [0x6e]);
|
||||
text(2, 16, String(mon.level));
|
||||
} else text(1, 16, String(mon.level));
|
||||
put(7, 16, [0x73, 0x74, 0xf3]); // <ID>, № and /.
|
||||
text(10, 16, String(mon.otId).padStart(5, "0"));
|
||||
return screen;
|
||||
}
|
||||
|
||||
/** _HallOfFamePC: LoadHOFTeam takes the teams newest first until an absent
|
||||
* one (or NUM_HOF_TEAMS); A shows the team's next Pokémon, and after the
|
||||
* last one the next team, Start skips to the next team and B leaves. Each
|
||||
* page cries as ANIM_MON_HOF's CryNoWait does. */
|
||||
export function* hallOfFamePC(
|
||||
data: PokemonData,
|
||||
teams: readonly HallOfFameTeam[],
|
||||
frame: number,
|
||||
sound?: GameSound,
|
||||
): Generator<ScriptEffect, void, number | string | undefined> {
|
||||
for (const team of teams) {
|
||||
for (let slot = 0; slot < team.mons.length; slot++) {
|
||||
sound?.playMonCry2(team.mons[slot].species);
|
||||
const screen = hallOfFameScreen(data, team, slot, frame);
|
||||
for (;;) {
|
||||
const button = yield { kind: "screen", screen };
|
||||
if (typeof button !== "string" || !SCREEN_BUTTONS.includes(button))
|
||||
throw new Error("Invalid Hall of Fame button.");
|
||||
if (button === "b") return;
|
||||
if (button === "start") {
|
||||
slot = team.mons.length;
|
||||
break;
|
||||
}
|
||||
if (button === "a") break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user