Files
Crystal/src/game/party/hall-of-fame.ts
T
kawaandClaude Opus 5.5 e49bc6de9a 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>
2026-10-17 08:52:30 +02:00

109 lines
3.4 KiB
TypeScript

import { NUM_POKEMON } from "../rom/profile";
import { NICKNAME_PATTERN, PARTY_LENGTH, type PartyMon } from "./pokemon";
/** NUM_HOF_TEAMS: the teams sHallOfFame keeps; HOF_MASTER_COUNT: where
* wHallOfFameCount stops (constants/misc_constants.asm). */
export const NUM_HOF_TEAMS = 30;
export const HOF_MASTER_COUNT = 200;
/** HOF_MON_LENGTH: only the fields GetHallOfFameParty copies. */
export interface HallOfFameMon {
species: number;
otId: number;
dvs: number;
level: number;
nickname: string;
}
/** One sHallOfFame entry (HOF_LENGTH): the win it records and the team
* that won, without its Eggs. */
export interface HallOfFameTeam {
/** wHallOfFameCount when the team entered, capped at HOF_MASTER_COUNT. */
win: number;
mons: HallOfFameMon[];
}
export interface HallOfFameRecords {
/** wHallOfFameCount: the League wins, up to HOF_MASTER_COUNT. */
hallOfFameCount: number;
/** sHallOfFame, newest first. Separate from STATUSFLAGS_HALL_OF_FAME_F. */
hallOfFameTeams: HallOfFameTeam[];
}
/** GetHallOfFameParty: skip Eggs, copy the six recorded fields, and keep
* party order. An all-Egg party produces an empty team. */
export function getHallOfFameParty(
party: readonly PartyMon[],
win: number,
): HallOfFameTeam {
return {
win,
mons: party
.filter((mon) => !mon.egg)
.map(({ species, otId, dvs, level, nickname }) => ({
species,
otId,
dvs,
level,
nickname,
})),
};
}
/** HallOfFame's capped count increment, then AddHallOfFameEntry: the
* newest team goes first and the oldest of a full sHallOfFame drops off.
* The League's ceremony is not ported, so nothing calls this yet; it
* leaves STATUSFLAGS_HALL_OF_FAME_F (`hallOfFame`) to its caller. */
export function addHallOfFameEntry(
records: HallOfFameRecords,
party: readonly PartyMon[],
) {
records.hallOfFameCount = Math.min(
HOF_MASTER_COUNT,
records.hallOfFameCount + 1,
);
records.hallOfFameTeams.unshift(
getHallOfFameParty(party, records.hallOfFameCount),
);
records.hallOfFameTeams.length = Math.min(
NUM_HOF_TEAMS,
records.hallOfFameTeams.length,
);
}
/** Structural records validation; no stats, party ownership or current
* species are required of a historical entry. */
export function validHallOfFame(
count: unknown,
value: unknown,
): value is HallOfFameTeam[] {
const integer = (n: unknown, min: number, max: number): n is number =>
Number.isInteger(n) && Number(n) >= min && Number(n) <= max;
if (
!integer(count, 0, HOF_MASTER_COUNT) ||
!Array.isArray(value) ||
value.length > NUM_HOF_TEAMS
)
return false;
if ((count === 0) !== (value.length === 0) || value.length > count)
return false;
return value.every(
(team, index) =>
team !== null &&
typeof team === "object" &&
integer(team.win, 1, count) &&
(index === 0 ? team.win === count : team.win <= value[index - 1].win) &&
Array.isArray(team.mons) &&
team.mons.length <= PARTY_LENGTH &&
team.mons.every(
(mon: HallOfFameMon) =>
mon !== null &&
typeof mon === "object" &&
integer(mon.species, 1, NUM_POKEMON) &&
integer(mon.otId, 0, 65535) &&
integer(mon.dvs, 0, 65535) &&
integer(mon.level, 1, 100) &&
typeof mon.nickname === "string" &&
NICKNAME_PATTERN.test(mon.nickname),
),
);
}