Refactors and doc comment updates; moved types to new file

This commit is contained in:
Bertie690 2025-09-15 11:11:03 -04:00
parent 75d811c904
commit f8f4552d7e
5 changed files with 85 additions and 67 deletions

View File

@ -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<NewBattleBaseProps, "trainerConfig" | "trainerData" | "mysteryEncounterType">;
/**
* Interface representing the return type of {@linkcode BattleScene.getNewBattleProps}, used
* while creating a new battle.
* @interface
*/
export type NewBattleProps = Omit<NewBattleBaseProps, "trainer">;

View File

@ -114,12 +114,12 @@ import { GameData } from "#system/game-data";
import { initGameSpeed } from "#system/game-speed"; import { initGameSpeed } from "#system/game-speed";
import type { PokemonData } from "#system/pokemon-data"; import type { PokemonData } from "#system/pokemon-data";
import { MusicPreference } from "#system/settings"; import { MusicPreference } from "#system/settings";
import type { TrainerData } from "#system/trainer-data";
import type { Voucher } from "#system/voucher"; import type { Voucher } from "#system/voucher";
import { vouchers } from "#system/voucher"; import { vouchers } from "#system/voucher";
import { trainerConfigs } from "#trainers/trainer-config"; import { trainerConfigs } from "#trainers/trainer-config";
import type { HeldModifierConfig } from "#types/held-modifier-config"; import type { HeldModifierConfig } from "#types/held-modifier-config";
import type { Localizable } from "#types/locales"; import type { Localizable } from "#types/locales";
import type { NewBattleProps, NewBattleResolvedProps } from "#types/new-battle-props";
import type { SessionSaveData } from "#types/save-data"; import type { SessionSaveData } from "#types/save-data";
import { AbilityBar } from "#ui/ability-bar"; import { AbilityBar } from "#ui/ability-bar";
import { ArenaFlyout } from "#ui/arena-flyout"; import { ArenaFlyout } from "#ui/arena-flyout";
@ -165,29 +165,6 @@ export interface InfoToggle {
isActive(): boolean; 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<NewBattleBaseProps, "trainerConfig" | "trainerData" | "mysteryEncounterType">;
/**
* Interface representing the type of {@linkcode BattleScene.getNewBattleProps}, used for DRY
* @interface
*/
export type NewBattleProps = Omit<NewBattleBaseProps, "trainer">;
/** /**
* The `BattleScene` is the primary scene for the game. * The `BattleScene` is the primary scene for the game.
* Despite its name, it handles _everything_ other than initial asset loading, * 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. * @param fromSession - The {@linkcode SessionSaveData} being used to seed the battle.
* Should be omitted if not loading a new save file. * 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 props = this.getNewBattleProps(fromSession);
const foo: Partial<NewBattleResolvedProps> = {}; const resolved: Partial<NewBattleResolvedProps> = {};
const { waveIndex } = props; const { waveIndex } = props;
this.resetSeed(waveIndex); this.resetSeed(waveIndex);
// First, check if it's a fixed wave and do stuff accordingly // First, check if it's a fixed wave and do stuff accordingly
if (this.gameMode.isFixedBattle(waveIndex)) { if (this.gameMode.isFixedBattle(waveIndex)) {
this.handleFixedBattle(foo, waveIndex); this.handleFixedBattle(resolved, waveIndex);
} else if (props.trainerData) { } else if (props.trainerData) {
this.handleSavedBattle(foo, props); this.handleSavedBattle(resolved, props);
} else { } 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 lastBattle = this.currentBattle;
const maxExpLevel = this.getMaxExpLevel(); const maxExpLevel = this.getMaxExpLevel();
@ -1344,7 +1322,7 @@ export class BattleScene extends SceneBase {
this.lastMysteryEncounter = lastBattle?.mysteryEncounter; this.lastMysteryEncounter = lastBattle?.mysteryEncounter;
// TODO: Is this even needed? // TODO: Is this even needed?
if (lastBattle?.double && !foo.double) { if (lastBattle?.double && !resolved.double) {
this.phaseManager.tryRemovePhase((p: Phase) => p.is("SwitchPhase")); this.phaseManager.tryRemovePhase((p: Phase) => p.is("SwitchPhase"));
// TODO: We already do this later in the function // TODO: We already do this later in the function
for (const p of this.getPlayerField()) { 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 // NB: Type assertion is fine as foo should always be defined
this.executeWithSeedOffset( 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? waveIndex << 3, // TODO: Why use this specific index?
this.waveSeed, this.waveSeed,
@ -1370,12 +1348,16 @@ export class BattleScene extends SceneBase {
return this.currentBattle; return this.currentBattle;
} }
// TODO: Document these and stop /**
private handleFixedBattle(foo: Partial<NewBattleResolvedProps>, waveIndex: number): Trainer { * Sub-method of `launchBattle` that handles a
let t: Trainer; */
private handleFixedBattle(resolved: Partial<NewBattleResolvedProps>, waveIndex: number): Trainer {
// Bang is justified as this code is only called when `isFixedBattle` is true
const battleConfig = this.gameMode.getFixedBattle(waveIndex)!; const battleConfig = this.gameMode.getFixedBattle(waveIndex)!;
foo.double = battleConfig.double; resolved.double = battleConfig.double;
foo.battleType = battleConfig.battleType; resolved.battleType = battleConfig.battleType;
let t: Trainer;
this.executeWithSeedOffset( this.executeWithSeedOffset(
() => { () => {
t = battleConfig.getTrainer(); t = battleConfig.getTrainer();
@ -1416,21 +1398,19 @@ export class BattleScene extends SceneBase {
// Determine the trainer's attributes // Determine the trainer's attributes
const trainerType = Overrides.RANDOM_TRAINER_OVERRIDE?.trainerType ?? this.arena.randomTrainerType(waveIndex); const trainerType = Overrides.RANDOM_TRAINER_OVERRIDE?.trainerType ?? this.arena.randomTrainerType(waveIndex);
const config = trainerConfigs[trainerType];
let doubleTrainer: boolean; let doubleTrainer: boolean;
if (trainerConfigs[trainerType].doubleOnly) { if (config.doubleOnly) {
doubleTrainer = true; 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; doubleTrainer = false;
} else { } else {
doubleTrainer = doubleTrainer =
Overrides.RANDOM_TRAINER_OVERRIDE?.alwaysDouble || !randSeedInt(this.getDoubleBattleChance(waveIndex)); 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 const variant = doubleTrainer
@ -1438,11 +1418,17 @@ export class BattleScene extends SceneBase {
: randSeedInt(2) : randSeedInt(2)
? TrainerVariant.FEMALE ? TrainerVariant.FEMALE
: TrainerVariant.DEFAULT; : TrainerVariant.DEFAULT;
const trainer = new Trainer(trainerType, variant); const trainer = new Trainer(trainerType, variant);
this.field.add(trainer); this.field.add(trainer);
foo.trainer = 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 { private getNewBattleProps(fromSession?: SessionSaveData): NewBattleProps {
const battleType = fromSession?.battleType ?? BattleType.WILD; const battleType = fromSession?.battleType ?? BattleType.WILD;
const mysteryEncounterType = const mysteryEncounterType =
@ -1459,8 +1445,8 @@ export class BattleScene extends SceneBase {
fromSession == null fromSession == null
? undefined ? undefined
: battleType === BattleType.TRAINER : battleType === BattleType.TRAINER
? trainerConfigs[fromSession?.trainer.trainerType]?.doubleOnly ? trainerConfigs[fromSession.trainer.trainerType]?.doubleOnly
|| fromSession.trainer?.variant === TrainerVariant.DOUBLE || fromSession.trainer.variant === TrainerVariant.DOUBLE
: battleType !== BattleType.MYSTERY_ENCOUNTER && fromSession.enemyParty.length > 1; : battleType !== BattleType.MYSTERY_ENCOUNTER && fromSession.enemyParty.length > 1;
return { battleType, mysteryEncounterType, waveIndex: newWaveIndex, trainerData, double: fixedDouble }; 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( * Sub-method of `newBattle` that returns whether the new battle is a double battle.
{ trainer }: Partial<NewBattleResolvedProps>, * @param __namedParameters
{ double, battleType, waveIndex }: NewBattleProps, */
): boolean { private checkIsDouble(trainer: Trainer | undefined, { double, battleType, waveIndex }: NewBattleProps): boolean {
// Edge cases // Edge cases
if ( if (
waveIndex === 1 // Wave 1 doubles cause crashes // Wave 1 doubles cause crashes
waveIndex === 1
|| this.gameMode.isWaveFinal(waveIndex) || this.gameMode.isWaveFinal(waveIndex)
|| this.gameMode.isEndlessBoss(waveIndex) || this.gameMode.isEndlessBoss(waveIndex)
|| battleType === BattleType.MYSTERY_ENCOUNTER || battleType === BattleType.MYSTERY_ENCOUNTER // MEs are never double battles
) { ) {
return false; 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 { resetSeed(waveIndex?: number): void {
const wave = waveIndex ?? this.currentBattle?.waveIndex ?? 0; const wave = waveIndex ?? this.currentBattle?.waveIndex ?? 0;
this.waveSeed = shiftCharCodes(this.seed, wave); this.waveSeed = shiftCharCodes(this.seed, wave);

View File

@ -1,4 +1,3 @@
import type { NewBattleResolvedProps } from "#app/battle-scene";
import type { GameMode } from "#app/game-mode"; import type { GameMode } from "#app/game-mode";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { ArenaTagType } from "#enums/arena-tag-type"; 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 i18next from "#plugins/i18n";
import { MusicPreference } from "#system/settings"; import { MusicPreference } from "#system/settings";
import { trainerConfigs } from "#trainers/trainer-config"; import { trainerConfigs } from "#trainers/trainer-config";
import type { NewBattleResolvedProps } from "#types/new-battle-props";
import type { TurnMove } from "#types/turn-move"; import type { TurnMove } from "#types/turn-move";
import { import {
NumberHolder, NumberHolder,

View File

@ -10,11 +10,11 @@ export class TrainerPartyTemplate {
public sameSpecies: boolean; public sameSpecies: boolean;
public balanced: 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.size = size;
this.strength = strength; this.strength = strength;
this.sameSpecies = !!sameSpecies; this.sameSpecies = sameSpecies;
this.balanced = !!balanced; this.balanced = balanced;
} }
getStrength(_index: number): PartyMemberStrength { getStrength(_index: number): PartyMemberStrength {

View File

@ -287,20 +287,21 @@ export class GameMode implements GameModeConfig {
} }
/** /**
* Every 50 waves of an Endless mode is a boss * Check whether the current wave is an Endless boss of any kind.
* At this time it is paradox pokemon * @param waveIndex - The current wave number.
* @returns true if waveIndex is a multiple of 50 in Endless * @returns Whether `waveIndex` corresponds to an Endless boss.
*/ */
isEndlessBoss(waveIndex: number): boolean { isEndlessBoss(waveIndex: number): boolean {
return waveIndex % 50 === 0 && (this.modeId === GameModes.ENDLESS || this.modeId === GameModes.SPLICED_ENDLESS); return waveIndex % 50 === 0 && (this.modeId === GameModes.ENDLESS || this.modeId === GameModes.SPLICED_ENDLESS);
} }
/** /**
* Every 250 waves of an Endless mode is a minor boss * Check whether the current wave is an Endless minor boss.
* At this time it is Eternatus * Currently is normal Eternatus.
* @returns true if waveIndex is a multiple of 250 in Endless * @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); return waveIndex % 250 === 0 && (this.modeId === GameModes.ENDLESS || this.modeId === GameModes.SPLICED_ENDLESS);
} }