diff --git a/src/@types/dex-data.ts b/src/@types/dex-data.ts index 88cc16886bd..741b9a872b3 100644 --- a/src/@types/dex-data.ts +++ b/src/@types/dex-data.ts @@ -1,3 +1,5 @@ +import type { IVTuple } from "#app/@types/stat-types"; + export interface DexData { [key: number]: DexEntry; } @@ -9,5 +11,5 @@ export interface DexEntry { seenCount: number; caughtCount: number; hatchedCount: number; - ivs: number[]; + ivs: IVTuple; } diff --git a/src/@types/stat-types.ts b/src/@types/stat-types.ts new file mode 100644 index 00000000000..bcba054383e --- /dev/null +++ b/src/@types/stat-types.ts @@ -0,0 +1,59 @@ +import type { Head, Tail } from "#app/@types/utility-types/tuple"; + +// biome-ignore lint/correctness/noUnusedImports: Type Imports +import type { PermanentStat, BattleStat } from "#enums/stat"; + +type StatTuple = [ + hp: number, + atk: number, + def: number, + spAtk: number, + spDef: number, + spd: number, + acc: number, + eva: number, +]; + +/** Tuple containing all {@linkcode PermanentStat}s of a Pokemon. */ +export type PermanentStatTuple = Head>; +/** Tuple containing all {@linkcode BattleStat}s of a Pokemon. */ +export type BattleStatTuple = Tail; + +// Since typescript lacks integer range unions, we have to use THIS abomination to strongly type an IV +export type IVType = + | 0 + | 1 + | 2 + | 3 + | 4 + | 5 + | 6 + | 7 + | 8 + | 9 + | 10 + | 11 + | 12 + | 13 + | 14 + | 15 + | 16 + | 17 + | 18 + | 19 + | 20 + | 21 + | 22 + | 23 + | 24 + | 25 + | 26 + | 27 + | 28 + | 29 + | 30 + | 31; + +type toIvTuple = { [k in keyof T]: IVType }; + +export type IVTuple = toIvTuple; diff --git a/src/@types/utility-types/tuple.ts b/src/@types/utility-types/tuple.ts new file mode 100644 index 00000000000..4c01c330f98 --- /dev/null +++ b/src/@types/utility-types/tuple.ts @@ -0,0 +1,5 @@ +/** Extract the elements of a tuple type, excluding the first element. */ +export type Tail = Required extends [any, ...infer Head] ? Head : never; + +/** Extract the elements of a tuple type, excluding the last element. */ +export type Head = Required extends [...infer Head, any] ? Head : never; diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 81c65d85e06..0adce63134f 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -160,12 +160,16 @@ import { timedEventManager } from "./global-event-manager"; import { starterColors } from "./global-vars/starter-colors"; import { startingWave } from "./starting-wave"; import { PhaseManager } from "./phase-manager"; +import type { IVTuple } from "#app/@types/stat-types"; const DEBUG_RNG = false; -const OPP_IVS_OVERRIDE_VALIDATED: number[] = ( - Array.isArray(Overrides.OPP_IVS_OVERRIDE) ? Overrides.OPP_IVS_OVERRIDE : new Array(6).fill(Overrides.OPP_IVS_OVERRIDE) -).map(iv => (Number.isNaN(iv) || iv === null || iv > 31 ? -1 : iv)); +const OPP_IVS_OVERRIDE_VALIDATED: IVTuple | null = + Overrides.OPP_IVS_OVERRIDE === null + ? null + : Array.isArray(Overrides.OPP_IVS_OVERRIDE) + ? Overrides.OPP_IVS_OVERRIDE + : new Array(6).fill(Overrides.OPP_IVS_OVERRIDE); export interface PokeballCounts { [pb: string]: number; @@ -970,12 +974,9 @@ export default class BattleScene extends SceneBase { postProcess(pokemon); } - for (let i = 0; i < pokemon.ivs.length; i++) { - if (OPP_IVS_OVERRIDE_VALIDATED[i] > -1) { - pokemon.ivs[i] = OPP_IVS_OVERRIDE_VALIDATED[i]; - } + if (OPP_IVS_OVERRIDE_VALIDATED) { + pokemon.ivs = OPP_IVS_OVERRIDE_VALIDATED; } - pokemon.init(); return pokemon; } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 0205b5bf4b2..ddd469281fc 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -193,6 +193,7 @@ import { AiType } from "#enums/ai-type"; import type { MoveResult } from "#enums/move-result"; import { PokemonMove } from "#app/data/moves/pokemon-move"; import type { AbAttrMap, AbAttrString } from "#app/@types/ability-types"; +import type { IVTuple, PermanentStatTuple } from "#app/@types/stat-types"; /** Base typeclass for damage parameter methods, used for DRY */ type damageParams = { @@ -247,8 +248,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public levelExp: number; public gender: Gender; public hp: number; - public stats: number[]; - public ivs: number[]; + public stats: PermanentStatTuple; + public ivs: IVTuple; public nature: Nature; public moveset: PokemonMove[]; public status: Status | null; @@ -317,7 +318,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { gender?: Gender, shiny?: boolean, variant?: Variant, - ivs?: number[], + ivs?: IVTuple, nature?: Nature, dataSource?: Pokemon | PokemonData, ) { @@ -5475,7 +5476,7 @@ export class PlayerPokemon extends Pokemon { gender?: Gender, shiny?: boolean, variant?: Variant, - ivs?: number[], + ivs?: IVTuple, nature?: Nature, dataSource?: Pokemon | PokemonData, ) { @@ -6088,10 +6089,12 @@ export class EnemyPokemon extends Pokemon { if (this.hasTrainer() && globalScene.currentBattle) { const { waveIndex } = globalScene.currentBattle; - const ivs: number[] = []; - while (ivs.length < 6) { - ivs.push(randSeedIntRange(Math.floor(waveIndex / 10), 31)); - } + const ivs: IVTuple = Array.from( + { + length: 6, + }, + () => randSeedIntRange(Math.floor(waveIndex / 10), 31), + ); this.ivs = ivs; } } diff --git a/src/overrides.ts b/src/overrides.ts index 6086f75a58e..ed33ee01410 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -22,6 +22,7 @@ import { TimeOfDay } from "#enums/time-of-day"; import { TrainerType } from "#enums/trainer-type"; import { VariantTier } from "#enums/variant-tier"; import { WeatherType } from "#enums/weather-type"; +import type { IVTuple, IVType } from "#app/@types/stat-types"; /** * This comment block exists to prevent IDEs from automatically removing unused imports @@ -177,7 +178,11 @@ class DefaultOverrides { readonly OPP_MOVESET_OVERRIDE: MoveId | Array = []; readonly OPP_SHINY_OVERRIDE: boolean | null = null; readonly OPP_VARIANT_OVERRIDE: Variant | null = null; - readonly OPP_IVS_OVERRIDE: number | number[] = []; + /** + * An array of IVs to give the opposing pokemon. + * Specifying a single number will use it for all 6 IVs, while `null` will disable the override. + */ + readonly OPP_IVS_OVERRIDE: IVType | IVTuple | null = null; readonly OPP_FORM_OVERRIDES: Partial> = {}; /** * Override to give the enemy Pokemon a given amount of health segments diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index 0056c3e2f11..d6e35544bf3 100644 --- a/src/ui/pokemon-info-container.ts +++ b/src/ui/pokemon-info-container.ts @@ -14,6 +14,7 @@ import ConfirmUiHandler from "./confirm-ui-handler"; import { StatsContainer } from "./stats-container"; import { TextStyle, addBBCodeTextObject, addTextObject, getTextColor } from "./text"; import { addWindow } from "./ui-theme"; +import type { PermanentStatTuple } from "#app/@types/stat-types"; interface LanguageSetting { infoContainerTextSize: string; @@ -383,7 +384,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { } const starterSpeciesId = pokemon.species.getRootSpeciesId(); - const originalIvs: number[] | null = eggInfo + const originalIvs: PermanentStatTuple | null = eggInfo ? dexEntry.caughtAttr ? dexEntry.ivs : null diff --git a/src/utils/common.ts b/src/utils/common.ts index c8b37c4e3fd..f6036ed981d 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -3,6 +3,7 @@ import { MoveId } from "#enums/move-id"; import i18next from "i18next"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import type { Variant } from "#app/sprites/variant"; +import type { IVTuple } from "#app/@types/stat-types"; export type nil = null | undefined; @@ -182,7 +183,7 @@ export function getPlayTimeString(totalSeconds: number): string { * @param id 32-bit number * @returns An array of six numbers corresponding to 5-bit chunks from {@linkcode id} */ -export function getIvsFromId(id: number): number[] { +export function getIvsFromId(id: number): IVTuple { return [ (id & 0x3e000000) >>> 25, (id & 0x01f00000) >>> 20,