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), ), ); }