From f8f4552d7e8a699136a8971b92f9ebeb2ff89026 Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Mon, 15 Sep 2025 11:11:03 -0400 Subject: [PATCH] Refactors and doc comment updates; moved types to new file --- src/@types/new-battle-props.ts | 29 ++++++ src/battle-scene.ts | 100 +++++++++----------- src/battle.ts | 2 +- src/data/trainers/trainer-party-template.ts | 6 +- src/game-mode.ts | 15 +-- 5 files changed, 85 insertions(+), 67 deletions(-) create mode 100644 src/@types/new-battle-props.ts diff --git a/src/@types/new-battle-props.ts b/src/@types/new-battle-props.ts new file mode 100644 index 00000000000..aff429ea9eb --- /dev/null +++ b/src/@types/new-battle-props.ts @@ -0,0 +1,29 @@ +// biome-ignore lint/correctness/noUnusedImports: TSDoc +import type { BattleScene } from "#app/battle-scene"; +import type { BattleType } from "#enums/battle-type"; +import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import type { Trainer } from "#field/trainer"; +import type { TrainerData } from "#system/trainer-data"; + +/** Interface representing the base type of a new battle config, used for DRY. */ +interface NewBattleBaseProps { + battleType: BattleType; + trainer?: Trainer; + trainerData?: TrainerData; + mysteryEncounterType?: MysteryEncounterType; + waveIndex: number; + double?: boolean; +} + +/** + * Interface representing the resolved type of a new battle config, used after creating a new Battle. + * @interface + */ +export type NewBattleResolvedProps = Omit; + +/** + * Interface representing the return type of {@linkcode BattleScene.getNewBattleProps}, used + * while creating a new battle. + * @interface + */ +export type NewBattleProps = Omit; diff --git a/src/battle-scene.ts b/src/battle-scene.ts index a106c94a195..1f586873bb7 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -114,12 +114,12 @@ import { GameData } from "#system/game-data"; import { initGameSpeed } from "#system/game-speed"; import type { PokemonData } from "#system/pokemon-data"; import { MusicPreference } from "#system/settings"; -import type { TrainerData } from "#system/trainer-data"; import type { Voucher } from "#system/voucher"; import { vouchers } from "#system/voucher"; import { trainerConfigs } from "#trainers/trainer-config"; import type { HeldModifierConfig } from "#types/held-modifier-config"; import type { Localizable } from "#types/locales"; +import type { NewBattleProps, NewBattleResolvedProps } from "#types/new-battle-props"; import type { SessionSaveData } from "#types/save-data"; import { AbilityBar } from "#ui/ability-bar"; import { ArenaFlyout } from "#ui/arena-flyout"; @@ -165,29 +165,6 @@ export interface InfoToggle { isActive(): boolean; } -// todo move this to the file -/** Interface representing the base type of a new battle config. */ -interface NewBattleBaseProps { - battleType: BattleType; - trainer?: Trainer; - trainerData?: TrainerData; - mysteryEncounterType?: MysteryEncounterType; - waveIndex: number; - double?: boolean; -} - -/** - * Interface representing the resolved type of a new battle config. - * @interface - */ -export type NewBattleResolvedProps = Omit; - -/** - * Interface representing the type of {@linkcode BattleScene.getNewBattleProps}, used for DRY - * @interface - */ -export type NewBattleProps = Omit; - /** * The `BattleScene` is the primary scene for the game. * Despite its name, it handles _everything_ other than initial asset loading, @@ -1315,27 +1292,28 @@ export class BattleScene extends SceneBase { } /** + * Create and initialize a new battle. * @param fromSession - The {@linkcode SessionSaveData} being used to seed the battle. * Should be omitted if not loading a new save file. - * @returns The newly created Battle instance + * @returns The newly created `Battle` instance. */ - newBattle(fromSession?: SessionSaveData): Battle { + public newBattle(fromSession?: SessionSaveData): Battle { const props = this.getNewBattleProps(fromSession); - const foo: Partial = {}; + const resolved: Partial = {}; const { waveIndex } = props; this.resetSeed(waveIndex); // First, check if it's a fixed wave and do stuff accordingly if (this.gameMode.isFixedBattle(waveIndex)) { - this.handleFixedBattle(foo, waveIndex); + this.handleFixedBattle(resolved, waveIndex); } else if (props.trainerData) { - this.handleSavedBattle(foo, props); + this.handleSavedBattle(resolved, props); } else { - this.handleNonFixedBattle(foo, props); + this.handleNonFixedBattle(resolved, props); } - foo.double = this.checkIsDouble(foo, props); + resolved.double = this.checkIsDouble(resolved.trainer, props); const lastBattle = this.currentBattle; const maxExpLevel = this.getMaxExpLevel(); @@ -1344,7 +1322,7 @@ export class BattleScene extends SceneBase { this.lastMysteryEncounter = lastBattle?.mysteryEncounter; // TODO: Is this even needed? - if (lastBattle?.double && !foo.double) { + if (lastBattle?.double && !resolved.double) { this.phaseManager.tryRemovePhase((p: Phase) => p.is("SwitchPhase")); // TODO: We already do this later in the function for (const p of this.getPlayerField()) { @@ -1355,7 +1333,7 @@ export class BattleScene extends SceneBase { // NB: Type assertion is fine as foo should always be defined this.executeWithSeedOffset( () => { - this.currentBattle = new Battle(this.gameMode, foo as NewBattleResolvedProps); + this.currentBattle = new Battle(this.gameMode, resolved as NewBattleResolvedProps); }, waveIndex << 3, // TODO: Why use this specific index? this.waveSeed, @@ -1370,12 +1348,16 @@ export class BattleScene extends SceneBase { return this.currentBattle; } - // TODO: Document these and stop - private handleFixedBattle(foo: Partial, waveIndex: number): Trainer { - let t: Trainer; + /** + * Sub-method of `launchBattle` that handles a + */ + private handleFixedBattle(resolved: Partial, waveIndex: number): Trainer { + // Bang is justified as this code is only called when `isFixedBattle` is true const battleConfig = this.gameMode.getFixedBattle(waveIndex)!; - foo.double = battleConfig.double; - foo.battleType = battleConfig.battleType; + resolved.double = battleConfig.double; + resolved.battleType = battleConfig.battleType; + + let t: Trainer; this.executeWithSeedOffset( () => { t = battleConfig.getTrainer(); @@ -1416,21 +1398,19 @@ export class BattleScene extends SceneBase { // Determine the trainer's attributes const trainerType = Overrides.RANDOM_TRAINER_OVERRIDE?.trainerType ?? this.arena.randomTrainerType(waveIndex); + const config = trainerConfigs[trainerType]; let doubleTrainer: boolean; - if (trainerConfigs[trainerType].doubleOnly) { + if (config.doubleOnly) { doubleTrainer = true; - } else if (!trainerConfigs[trainerType].hasDouble) { + } else if ( + !config.hasDouble // Add a check that special trainers can't be double except for tate and liza - they should use the normal double chance + || // TODO: Review this + (config.trainerTypeDouble && ![TrainerType.TATE, TrainerType.LIZA].includes(trainerType)) + ) { doubleTrainer = false; } else { doubleTrainer = Overrides.RANDOM_TRAINER_OVERRIDE?.alwaysDouble || !randSeedInt(this.getDoubleBattleChance(waveIndex)); - // Add a check that special trainers can't be double except for tate and liza - they should use the normal double chance - if ( - trainerConfigs[trainerType].trainerTypeDouble - && ![TrainerType.TATE, TrainerType.LIZA].includes(trainerType) - ) { - doubleTrainer = false; - } } const variant = doubleTrainer @@ -1438,11 +1418,17 @@ export class BattleScene extends SceneBase { : randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT; + const trainer = new Trainer(trainerType, variant); this.field.add(trainer); foo.trainer = trainer; } + /** + * Helper function to {@linkcode newBattle} to initialize a {@linkcode NewBattleProps} from session data, using defaults if no session data is provided. + * @param fromSession - The session data being used to initialize the battle, or `undefined` if a new battle is being created mid run + * @returns The new battle props + */ private getNewBattleProps(fromSession?: SessionSaveData): NewBattleProps { const battleType = fromSession?.battleType ?? BattleType.WILD; const mysteryEncounterType = @@ -1459,8 +1445,8 @@ export class BattleScene extends SceneBase { fromSession == null ? undefined : battleType === BattleType.TRAINER - ? trainerConfigs[fromSession?.trainer.trainerType]?.doubleOnly - || fromSession.trainer?.variant === TrainerVariant.DOUBLE + ? trainerConfigs[fromSession.trainer.trainerType]?.doubleOnly + || fromSession.trainer.variant === TrainerVariant.DOUBLE : battleType !== BattleType.MYSTERY_ENCOUNTER && fromSession.enemyParty.length > 1; return { battleType, mysteryEncounterType, waveIndex: newWaveIndex, trainerData, double: fixedDouble }; @@ -1522,17 +1508,18 @@ export class BattleScene extends SceneBase { } } - /** Sub-method of `newBattle` that returns whether the new battle is a double battle. */ - private checkIsDouble( - { trainer }: Partial, - { double, battleType, waveIndex }: NewBattleProps, - ): boolean { + /** + * Sub-method of `newBattle` that returns whether the new battle is a double battle. + * @param __namedParameters + */ + private checkIsDouble(trainer: Trainer | undefined, { double, battleType, waveIndex }: NewBattleProps): boolean { // Edge cases if ( - waveIndex === 1 // Wave 1 doubles cause crashes + // Wave 1 doubles cause crashes + waveIndex === 1 || this.gameMode.isWaveFinal(waveIndex) || this.gameMode.isEndlessBoss(waveIndex) - || battleType === BattleType.MYSTERY_ENCOUNTER + || battleType === BattleType.MYSTERY_ENCOUNTER // MEs are never double battles ) { return false; } @@ -1877,6 +1864,7 @@ export class BattleScene extends SceneBase { }); } + // TODO: Refactor this and other RNG functions - these dearly need help resetSeed(waveIndex?: number): void { const wave = waveIndex ?? this.currentBattle?.waveIndex ?? 0; this.waveSeed = shiftCharCodes(this.seed, wave); diff --git a/src/battle.ts b/src/battle.ts index 33264a0e296..18193bf7f9c 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -1,4 +1,3 @@ -import type { NewBattleResolvedProps } from "#app/battle-scene"; import type { GameMode } from "#app/game-mode"; import { globalScene } from "#app/global-scene"; import { ArenaTagType } from "#enums/arena-tag-type"; @@ -22,6 +21,7 @@ import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter"; import i18next from "#plugins/i18n"; import { MusicPreference } from "#system/settings"; import { trainerConfigs } from "#trainers/trainer-config"; +import type { NewBattleResolvedProps } from "#types/new-battle-props"; import type { TurnMove } from "#types/turn-move"; import { NumberHolder, diff --git a/src/data/trainers/trainer-party-template.ts b/src/data/trainers/trainer-party-template.ts index ba710726d6e..b43d9c10144 100644 --- a/src/data/trainers/trainer-party-template.ts +++ b/src/data/trainers/trainer-party-template.ts @@ -10,11 +10,11 @@ export class TrainerPartyTemplate { public sameSpecies: boolean; public balanced: boolean; - constructor(size: number, strength: PartyMemberStrength, sameSpecies?: boolean, balanced?: boolean) { + constructor(size: number, strength: PartyMemberStrength, sameSpecies = false, balanced = false) { this.size = size; this.strength = strength; - this.sameSpecies = !!sameSpecies; - this.balanced = !!balanced; + this.sameSpecies = sameSpecies; + this.balanced = balanced; } getStrength(_index: number): PartyMemberStrength { diff --git a/src/game-mode.ts b/src/game-mode.ts index 23fe946b4ec..c71cfda1d2b 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -287,20 +287,21 @@ export class GameMode implements GameModeConfig { } /** - * Every 50 waves of an Endless mode is a boss - * At this time it is paradox pokemon - * @returns true if waveIndex is a multiple of 50 in Endless + * Check whether the current wave is an Endless boss of any kind. + * @param waveIndex - The current wave number. + * @returns Whether `waveIndex` corresponds to an Endless boss. */ isEndlessBoss(waveIndex: number): boolean { return waveIndex % 50 === 0 && (this.modeId === GameModes.ENDLESS || this.modeId === GameModes.SPLICED_ENDLESS); } /** - * Every 250 waves of an Endless mode is a minor boss - * At this time it is Eternatus - * @returns true if waveIndex is a multiple of 250 in Endless + * Check whether the current wave is an Endless minor boss. + * Currently is normal Eternatus. + * @param waveIndex - The current wave number. + * @returns Whether `waveIndex` is a multiple of 250 during endless mode. */ - isEndlessMinorBoss(waveIndex: number): boolean { + public isEndlessMinorBoss(waveIndex: number): boolean { return waveIndex % 250 === 0 && (this.modeId === GameModes.ENDLESS || this.modeId === GameModes.SPLICED_ENDLESS); }