mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-09-24 15:33:29 +02:00
Misc docs and code cleanup
This commit is contained in:
parent
f9892387f5
commit
ad61eafc84
@ -5,25 +5,74 @@ import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
|||||||
import type { Trainer } from "#field/trainer";
|
import type { Trainer } from "#field/trainer";
|
||||||
import type { TrainerData } from "#system/trainer-data";
|
import type { TrainerData } from "#system/trainer-data";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @module
|
||||||
|
* A collection of types/interfaces used for {@linkcode BattleScene.newBattle} and associated
|
||||||
|
* sub-methods.
|
||||||
|
*
|
||||||
|
* Types are listed in order of appearance in the function.
|
||||||
|
*/
|
||||||
|
|
||||||
/** Interface representing the base type of a new battle config, used for DRY. */
|
/** Interface representing the base type of a new battle config, used for DRY. */
|
||||||
interface NewBattleBaseProps {
|
interface NewBattleBaseProps {
|
||||||
|
/** The type of battle to create. */
|
||||||
battleType: BattleType;
|
battleType: BattleType;
|
||||||
|
/**
|
||||||
|
* The `Trainer` to spawn.
|
||||||
|
* Only present in populated data and will be `undefined` for non-trainer battles.
|
||||||
|
*/
|
||||||
trainer?: Trainer;
|
trainer?: Trainer;
|
||||||
|
/**
|
||||||
|
* Saved data used to initialize the trainer.
|
||||||
|
* Only present in data initialized from a saved session,
|
||||||
|
* and will be `undefined` for non-trainer battles.
|
||||||
|
*/
|
||||||
trainerData?: TrainerData;
|
trainerData?: TrainerData;
|
||||||
|
/**
|
||||||
|
* The type of Mystery Encounter to spawn.
|
||||||
|
* Only present in data initialized from a saved session,
|
||||||
|
* and will be `undefined` for non-ME battles.
|
||||||
|
*/
|
||||||
mysteryEncounterType?: MysteryEncounterType;
|
mysteryEncounterType?: MysteryEncounterType;
|
||||||
|
/**
|
||||||
|
* The wave number of the NEW wave to spawn.
|
||||||
|
* Will always be >=1 (barring save data corruption).
|
||||||
|
*/
|
||||||
waveIndex: number;
|
waveIndex: number;
|
||||||
|
/**
|
||||||
|
* Whether the battle is a double battle.
|
||||||
|
* Always `false` when an ME is spawned.
|
||||||
|
*/
|
||||||
double?: boolean;
|
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
|
* Interface representing the return type of {@linkcode BattleScene.getNewBattleProps}, used
|
||||||
* while creating a new battle.
|
* when converting session data into a new battle.
|
||||||
* @interface
|
* @interface
|
||||||
*/
|
*/
|
||||||
export type NewBattleProps = Omit<NewBattleBaseProps, "trainer">;
|
export type NewBattleProps = Omit<NewBattleBaseProps, "trainer">;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface representing the type of a new battle.
|
||||||
|
*
|
||||||
|
* @interface
|
||||||
|
* @privateRemarks
|
||||||
|
* The reason all "missing" properties are marked as `Partial` rather than simply being `undefined`
|
||||||
|
* is to allow assignment during function calls.
|
||||||
|
*/
|
||||||
|
export type NewBattleInitialProps = Partial<NewBattleResolvedProps> & Pick<NewBattleResolvedProps, "waveIndex">;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface representing the type of a partially resolved new battle config, used when passing stuff around midway through.
|
||||||
|
* Only contains properties known to be present after all 3 sub-methods finish execution.
|
||||||
|
* @interface
|
||||||
|
*/
|
||||||
|
export type NewBattleConstructedProps = Partial<NewBattleResolvedProps> &
|
||||||
|
Pick<NewBattleResolvedProps, "waveIndex" | "battleType">;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface representing the fully resolved type of a new battle config, used to create a new Battle instance.
|
||||||
|
* @interface
|
||||||
|
*/
|
||||||
|
export type NewBattleResolvedProps = Omit<NewBattleBaseProps, "trainerConfig" | "trainerData">;
|
||||||
|
@ -119,7 +119,12 @@ 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 {
|
||||||
|
NewBattleConstructedProps,
|
||||||
|
NewBattleInitialProps,
|
||||||
|
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";
|
||||||
@ -273,7 +278,7 @@ export class BattleScene extends SceneBase {
|
|||||||
public score: number;
|
public score: number;
|
||||||
public lockModifierTiers: boolean;
|
public lockModifierTiers: boolean;
|
||||||
public trainer: Phaser.GameObjects.Sprite;
|
public trainer: Phaser.GameObjects.Sprite;
|
||||||
public lastEnemyTrainer: Trainer | null;
|
public lastEnemyTrainer: Trainer | undefined;
|
||||||
public currentBattle: Battle;
|
public currentBattle: Battle;
|
||||||
public pokeballCounts: PokeballCounts;
|
public pokeballCounts: PokeballCounts;
|
||||||
public money: number;
|
public money: number;
|
||||||
@ -1299,26 +1304,31 @@ export class BattleScene extends SceneBase {
|
|||||||
*/
|
*/
|
||||||
public newBattle(fromSession?: SessionSaveData): Battle {
|
public newBattle(fromSession?: SessionSaveData): Battle {
|
||||||
const props = this.getNewBattleProps(fromSession);
|
const props = this.getNewBattleProps(fromSession);
|
||||||
const resolved: Partial<NewBattleResolvedProps> = {};
|
|
||||||
const { waveIndex } = props;
|
const { waveIndex } = props;
|
||||||
|
const resolved: NewBattleInitialProps = { waveIndex, mysteryEncounterType: props.mysteryEncounterType };
|
||||||
|
|
||||||
|
// TODO: Address this during an RNG overhaul
|
||||||
this.resetSeed(waveIndex);
|
this.resetSeed(waveIndex);
|
||||||
|
|
||||||
// First, check if it's a fixed wave and do stuff accordingly
|
// Set attributes of the `resolved` object based on the type of battle being created.
|
||||||
if (this.gameMode.isFixedBattle(waveIndex)) {
|
if (this.gameMode.isFixedBattle(waveIndex)) {
|
||||||
this.handleFixedBattle(resolved, waveIndex);
|
this.handleFixedBattle(resolved);
|
||||||
} else if (props.trainerData) {
|
} else if (props.trainerData) {
|
||||||
this.handleSavedBattle(resolved, props);
|
this.handleSavedBattle(resolved, props);
|
||||||
} else {
|
} else {
|
||||||
this.handleNonFixedBattle(resolved, props);
|
this.handleNonFixedBattle(resolved);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolved.double = this.checkIsDouble(resolved.trainer, props);
|
if (!resolved.battleType) {
|
||||||
|
throw new Error("Whoopsie! I guess my type checks were wrong");
|
||||||
|
}
|
||||||
|
resolved.double = this.checkIsDouble(resolved as NewBattleConstructedProps);
|
||||||
|
|
||||||
const lastBattle = this.currentBattle;
|
//
|
||||||
|
const lastBattle: Battle | undefined = this.currentBattle;
|
||||||
const maxExpLevel = this.getMaxExpLevel();
|
const maxExpLevel = this.getMaxExpLevel();
|
||||||
|
|
||||||
this.lastEnemyTrainer = lastBattle?.trainer ?? null;
|
this.lastEnemyTrainer = lastBattle?.trainer;
|
||||||
this.lastMysteryEncounter = lastBattle?.mysteryEncounter;
|
this.lastMysteryEncounter = lastBattle?.mysteryEncounter;
|
||||||
|
|
||||||
// TODO: Is this even needed?
|
// TODO: Is this even needed?
|
||||||
@ -1335,7 +1345,7 @@ export class BattleScene extends SceneBase {
|
|||||||
() => {
|
() => {
|
||||||
this.currentBattle = new Battle(this.gameMode, resolved 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 bitshift?
|
||||||
this.waveSeed,
|
this.waveSeed,
|
||||||
);
|
);
|
||||||
this.currentBattle.incrementTurn();
|
this.currentBattle.incrementTurn();
|
||||||
@ -1348,81 +1358,6 @@ export class BattleScene extends SceneBase {
|
|||||||
return this.currentBattle;
|
return this.currentBattle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sub-method of `launchBattle` that handles a
|
|
||||||
*/
|
|
||||||
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)!;
|
|
||||||
resolved.double = battleConfig.double;
|
|
||||||
resolved.battleType = battleConfig.battleType;
|
|
||||||
|
|
||||||
let t: Trainer;
|
|
||||||
this.executeWithSeedOffset(
|
|
||||||
() => {
|
|
||||||
t = battleConfig.getTrainer();
|
|
||||||
},
|
|
||||||
(battleConfig.seedOffsetWaveIndex || waveIndex) << 8,
|
|
||||||
);
|
|
||||||
// Tell TS this is defined
|
|
||||||
this.field.add(t!);
|
|
||||||
return t!;
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleSavedBattle(foo: Partial<NewBattleResolvedProps>, props: NewBattleProps): void {
|
|
||||||
foo.battleType = props.battleType;
|
|
||||||
foo.double = props.double;
|
|
||||||
foo.trainer = props.trainerData?.toTrainer();
|
|
||||||
foo.waveIndex = props.waveIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleNonFixedBattle(foo: Partial<NewBattleResolvedProps>, { waveIndex, battleType }: NewBattleProps): void {
|
|
||||||
battleType =
|
|
||||||
!this.gameMode.hasTrainers || Overrides.DISABLE_STANDARD_TRAINERS_OVERRIDE
|
|
||||||
? BattleType.WILD
|
|
||||||
: (Overrides.BATTLE_TYPE_OVERRIDE
|
|
||||||
?? (this.gameMode.isWaveTrainer(waveIndex, this.arena) ? BattleType.TRAINER : BattleType.WILD));
|
|
||||||
|
|
||||||
// Check for mystery encounter
|
|
||||||
// Can only occur in place of a standard (non-boss) wild battle, waves 10-180
|
|
||||||
if (this.isWaveMysteryEncounter(battleType, waveIndex)) {
|
|
||||||
foo.battleType = BattleType.MYSTERY_ENCOUNTER;
|
|
||||||
// Reset to base spawn weight
|
|
||||||
this.mysteryEncounterSaveData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (battleType !== BattleType.TRAINER) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine the trainer's attributes
|
|
||||||
const trainerType = Overrides.RANDOM_TRAINER_OVERRIDE?.trainerType ?? this.arena.randomTrainerType(waveIndex);
|
|
||||||
const config = trainerConfigs[trainerType];
|
|
||||||
let doubleTrainer: boolean;
|
|
||||||
if (config.doubleOnly) {
|
|
||||||
doubleTrainer = true;
|
|
||||||
} 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
const variant = doubleTrainer
|
|
||||||
? TrainerVariant.DOUBLE
|
|
||||||
: 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.
|
* 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
|
* @param fromSession - The session data being used to initialize the battle, or `undefined` if a new battle is being created mid run
|
||||||
@ -1451,6 +1386,93 @@ export class BattleScene extends SceneBase {
|
|||||||
return { battleType, mysteryEncounterType, waveIndex: newWaveIndex, trainerData, double: fixedDouble };
|
return { battleType, mysteryEncounterType, waveIndex: newWaveIndex, trainerData, double: fixedDouble };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sub-method of `launchBattle` that handles fixed trainer battles.
|
||||||
|
* @param resolved - The object to modify
|
||||||
|
*/
|
||||||
|
private handleFixedBattle(resolved: NewBattleInitialProps): void {
|
||||||
|
const { waveIndex } = resolved;
|
||||||
|
// Bang is justified as this code is only called when `isFixedBattle` is true
|
||||||
|
const battleConfig = this.gameMode.getFixedBattle(waveIndex)!;
|
||||||
|
resolved.double = battleConfig.double;
|
||||||
|
resolved.battleType = battleConfig.battleType;
|
||||||
|
|
||||||
|
let t: Trainer;
|
||||||
|
this.executeWithSeedOffset(
|
||||||
|
() => {
|
||||||
|
t = battleConfig.getTrainer();
|
||||||
|
},
|
||||||
|
(battleConfig.seedOffsetWaveIndex || waveIndex) << 8,
|
||||||
|
);
|
||||||
|
// Tell TS this is defined
|
||||||
|
this.field.add(t!);
|
||||||
|
resolved.trainer = t!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sub-method of `launchBattle` that handles loading existing saved battles.
|
||||||
|
* @param resolved - The object to modify
|
||||||
|
* @param props - The {@linkcode NewBattleProps} created from the save data
|
||||||
|
*/
|
||||||
|
private handleSavedBattle(resolved: NewBattleInitialProps, props: NewBattleProps): void {
|
||||||
|
resolved.battleType = props.battleType;
|
||||||
|
resolved.double = props.double;
|
||||||
|
resolved.trainer = props.trainerData?.toTrainer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sub-method of `launchBattle` that handles creating a new battle from scratch.
|
||||||
|
* @param resolved - The object to modify properties of
|
||||||
|
*/
|
||||||
|
private handleNonFixedBattle(resolved: NewBattleInitialProps): void {
|
||||||
|
const { waveIndex } = resolved;
|
||||||
|
resolved.battleType =
|
||||||
|
!this.gameMode.hasTrainers || Overrides.DISABLE_STANDARD_TRAINERS_OVERRIDE
|
||||||
|
? BattleType.WILD
|
||||||
|
: (Overrides.BATTLE_TYPE_OVERRIDE
|
||||||
|
?? (this.gameMode.isWaveTrainer(waveIndex, this.arena) ? BattleType.TRAINER : BattleType.WILD));
|
||||||
|
|
||||||
|
// Check for mystery encounter
|
||||||
|
// Can only occur in place of a standard (non-boss) wild battle, waves 10-180
|
||||||
|
if (this.isWaveMysteryEncounter(resolved.battleType, waveIndex)) {
|
||||||
|
resolved.battleType = BattleType.MYSTERY_ENCOUNTER;
|
||||||
|
// Reset to base spawn weight
|
||||||
|
this.mysteryEncounterSaveData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolved.battleType !== BattleType.TRAINER) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the trainer's attributes
|
||||||
|
const trainerType = Overrides.RANDOM_TRAINER_OVERRIDE?.trainerType ?? this.arena.randomTrainerType(waveIndex);
|
||||||
|
const config = trainerConfigs[trainerType];
|
||||||
|
let doubleTrainer: boolean;
|
||||||
|
if (config.doubleOnly) {
|
||||||
|
doubleTrainer = true;
|
||||||
|
} 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
// NB: The original code leaves `double` unset here so I suppose we do too
|
||||||
|
const variant = doubleTrainer
|
||||||
|
? TrainerVariant.DOUBLE
|
||||||
|
: randSeedInt(2)
|
||||||
|
? TrainerVariant.FEMALE
|
||||||
|
: TrainerVariant.DEFAULT;
|
||||||
|
|
||||||
|
const trainer = new Trainer(trainerType, variant);
|
||||||
|
this.field.add(trainer);
|
||||||
|
resolved.trainer = trainer;
|
||||||
|
}
|
||||||
|
|
||||||
private doPostBattleCleanup(lastBattle: Battle, maxExpLevel: number): void {
|
private doPostBattleCleanup(lastBattle: Battle, maxExpLevel: number): void {
|
||||||
const isNewBiome = this.isNewBiome(lastBattle);
|
const isNewBiome = this.isNewBiome(lastBattle);
|
||||||
/** Whether to reset and recall pokemon */
|
/** Whether to reset and recall pokemon */
|
||||||
@ -1509,9 +1531,10 @@ export class BattleScene extends SceneBase {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sub-method of `newBattle` that returns whether the new battle is a double battle.
|
* Sub-method of `newBattle` that returns whether the new battle is a double battle.
|
||||||
* @param __namedParameters
|
* @param __namedParameters - filler text for typedoc to shut up
|
||||||
|
* @returns Whether the battle should be a duouble battle.
|
||||||
*/
|
*/
|
||||||
private checkIsDouble(trainer: Trainer | undefined, { double, battleType, waveIndex }: NewBattleProps): boolean {
|
private checkIsDouble({ double, battleType, waveIndex, trainer }: NewBattleConstructedProps): boolean {
|
||||||
// Edge cases
|
// Edge cases
|
||||||
if (
|
if (
|
||||||
// Wave 1 doubles cause crashes
|
// Wave 1 doubles cause crashes
|
||||||
@ -1522,10 +1545,13 @@ export class BattleScene extends SceneBase {
|
|||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Previously defined double battle status (from save data)
|
||||||
if (double != null) {
|
if (double != null) {
|
||||||
return double;
|
return double;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Overrides
|
||||||
switch (Overrides.BATTLE_STYLE_OVERRIDE) {
|
switch (Overrides.BATTLE_STYLE_OVERRIDE) {
|
||||||
case "double":
|
case "double":
|
||||||
return true;
|
return true;
|
||||||
@ -1537,6 +1563,7 @@ export class BattleScene extends SceneBase {
|
|||||||
return waveIndex % 2 === 1;
|
return waveIndex % 2 === 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Standard wild battle chance
|
||||||
if (battleType === BattleType.WILD) {
|
if (battleType === BattleType.WILD) {
|
||||||
return !randSeedInt(this.getDoubleBattleChance(waveIndex));
|
return !randSeedInt(this.getDoubleBattleChance(waveIndex));
|
||||||
}
|
}
|
||||||
@ -3538,8 +3565,10 @@ export class BattleScene extends SceneBase {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns if a wave COULD spawn a {@linkcode MysteryEncounter}.
|
* Returns if a wave COULD spawn a {@linkcode MysteryEncounter}.
|
||||||
* Even if returns `true`, does not guarantee that a wave will actually be a ME.
|
* @param battleType - The {@linkcode BattleType} of the newly created battle
|
||||||
* That check is made in {@linkcode BattleScene.isWaveMysteryEncounter} instead.
|
* @param waveIndex - The wave number of the newly spawned wave
|
||||||
|
* @returns Whether an ME could legally spawn on the given wave.
|
||||||
|
* @see {@linkcode BattleScene.isWaveMysteryEncounter} - Function that rolls for ME creation on new wave start
|
||||||
*/
|
*/
|
||||||
isMysteryEncounterValidForWave(battleType: BattleType, waveIndex: number): boolean {
|
isMysteryEncounterValidForWave(battleType: BattleType, waveIndex: number): boolean {
|
||||||
const [lowestMysteryEncounterWave, highestMysteryEncounterWave] = this.gameMode.getMysteryEncounterLegalWaves();
|
const [lowestMysteryEncounterWave, highestMysteryEncounterWave] = this.gameMode.getMysteryEncounterLegalWaves();
|
||||||
@ -3548,25 +3577,37 @@ export class BattleScene extends SceneBase {
|
|||||||
&& battleType === BattleType.WILD
|
&& battleType === BattleType.WILD
|
||||||
&& !this.gameMode.isBoss(waveIndex)
|
&& !this.gameMode.isBoss(waveIndex)
|
||||||
&& waveIndex % 10 !== 1
|
&& waveIndex % 10 !== 1
|
||||||
&& waveIndex < highestMysteryEncounterWave
|
&& isBetween(waveIndex, lowestMysteryEncounterWave, highestMysteryEncounterWave, true)
|
||||||
&& waveIndex > lowestMysteryEncounterWave
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether a wave should randomly generate a {@linkcode MysteryEncounter}.
|
* Determine whether a wave should randomly generate a {@linkcode MysteryEncounter}.
|
||||||
* Currently, the only modes that MEs are allowed in are Classic and Challenge.
|
* Currently, the only modes that MEs are allowed in are Classic and Challenge.
|
||||||
* Additionally, MEs cannot spawn outside of waves 10-180 in those modes
|
* Additionally, MEs cannot spawn outside of waves 10-180 in those modes
|
||||||
* @param newBattleType
|
* @param battleType - The {@linkcode BattleType} of the newly created battle
|
||||||
* @param waveIndex
|
* @param waveIndex - The wave number of the newly spawned wave
|
||||||
|
* @returns Whether a Mystery Encounter should be generated.
|
||||||
*/
|
*/
|
||||||
private isWaveMysteryEncounter(newBattleType: BattleType, waveIndex: number): boolean {
|
private isWaveMysteryEncounter(battleType: BattleType, waveIndex: number): boolean {
|
||||||
|
if (!this.isMysteryEncounterValidForWave(battleType, waveIndex)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const [lowestMysteryEncounterWave, highestMysteryEncounterWave] = this.gameMode.getMysteryEncounterLegalWaves();
|
const [lowestMysteryEncounterWave, highestMysteryEncounterWave] = this.gameMode.getMysteryEncounterLegalWaves();
|
||||||
if (this.isMysteryEncounterValidForWave(newBattleType, waveIndex)) {
|
// Base spawn weight is BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT/256, and increases
|
||||||
// Base spawn weight is BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT/256, and increases by WEIGHT_INCREMENT_ON_SPAWN_MISS/256 for each missed attempt at spawning an encounter on a valid floor
|
// by WEIGHT_INCREMENT_ON_SPAWN_MISS/256 for each missed attempt at spawning an encounter on a valid floor
|
||||||
const sessionEncounterRate = this.mysteryEncounterSaveData.encounterSpawnChance;
|
const sessionEncounterRate = this.mysteryEncounterSaveData.encounterSpawnChance;
|
||||||
const encounteredEvents = this.mysteryEncounterSaveData.encounteredEvents;
|
const encounteredEvents = this.mysteryEncounterSaveData.encounteredEvents;
|
||||||
|
|
||||||
|
// MEs can only spawn 3 or more waves after the previous ME, barring overrides
|
||||||
|
const canSpawn =
|
||||||
|
Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE !== null // Bang on `at()` is justified due to the check for length === 0
|
||||||
|
&& (encounteredEvents.length === 0 || waveIndex > 3 + encounteredEvents.at(-1)!.waveIndex);
|
||||||
|
if (!canSpawn) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// If total number of encounters is lower than expected for the run, slightly favor a new encounter spawn (reverse as well)
|
// If total number of encounters is lower than expected for the run, slightly favor a new encounter spawn (reverse as well)
|
||||||
// Reduces occurrence of runs with total encounters significantly different from AVERAGE_ENCOUNTERS_PER_RUN_TARGET
|
// Reduces occurrence of runs with total encounters significantly different from AVERAGE_ENCOUNTERS_PER_RUN_TARGET
|
||||||
// Favored rate changes can never exceed 50%. So if base rate is 15/256 and favored rate would add 200/256, result will be (15 + 128)/256
|
// Favored rate changes can never exceed 50%. So if base rate is 15/256 and favored rate would add 200/256, result will be (15 + 128)/256
|
||||||
@ -3580,24 +3621,13 @@ export class BattleScene extends SceneBase {
|
|||||||
|
|
||||||
const successRate = Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE ?? favoredEncounterRate;
|
const successRate = Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE ?? favoredEncounterRate;
|
||||||
|
|
||||||
// MEs can only spawn 3 or more waves after the previous ME, barring overrides
|
let roll = 0;
|
||||||
const canSpawn = encounteredEvents.length === 0 || waveIndex - encounteredEvents.at(-1)!.waveIndex > 3;
|
|
||||||
|
|
||||||
if (canSpawn || Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE !== null) {
|
|
||||||
let roll = MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT;
|
|
||||||
// Always rolls the check on the same offset to ensure no RNG changes from reloading session
|
// Always rolls the check on the same offset to ensure no RNG changes from reloading session
|
||||||
this.executeWithSeedOffset(
|
this.executeWithSeedOffset(() => {
|
||||||
() => {
|
|
||||||
roll = randSeedInt(MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT);
|
roll = randSeedInt(MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT);
|
||||||
},
|
}, waveIndex * 3000);
|
||||||
waveIndex * 3 * 1000,
|
|
||||||
);
|
|
||||||
return roll < successRate;
|
return roll < successRate;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads or generates a mystery encounter
|
* Loads or generates a mystery encounter
|
||||||
|
@ -100,17 +100,22 @@ export class Battle {
|
|||||||
|
|
||||||
private rngCounter = 0;
|
private rngCounter = 0;
|
||||||
|
|
||||||
constructor(gameMode: GameMode, { waveIndex, battleType, trainer, double = false }: NewBattleResolvedProps) {
|
constructor(
|
||||||
|
gameMode: GameMode,
|
||||||
|
{ waveIndex, battleType, trainer, mysteryEncounterType, double = false }: NewBattleResolvedProps,
|
||||||
|
) {
|
||||||
this.gameMode = gameMode;
|
this.gameMode = gameMode;
|
||||||
this.waveIndex = waveIndex;
|
this.waveIndex = waveIndex;
|
||||||
this.battleType = battleType;
|
this.battleType = battleType;
|
||||||
this.trainer = trainer;
|
this.trainer = trainer;
|
||||||
|
this.mysteryEncounterType = mysteryEncounterType;
|
||||||
|
this.double = double;
|
||||||
|
|
||||||
this.initBattleSpec();
|
this.initBattleSpec();
|
||||||
this.enemyLevels =
|
this.enemyLevels =
|
||||||
battleType !== BattleType.TRAINER
|
battleType !== BattleType.TRAINER
|
||||||
? new Array(double ? 2 : 1).fill(null).map(() => this.getLevelForWave())
|
? new Array(double ? 2 : 1).fill(null).map(() => this.getLevelForWave())
|
||||||
: trainer?.getPartyLevels(this.waveIndex);
|
: trainer?.getPartyLevels(this.waveIndex);
|
||||||
this.double = double;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private initBattleSpec(): void {
|
private initBattleSpec(): void {
|
||||||
|
@ -228,7 +228,7 @@ function endTrainerBattleAndShowDialogue(): Promise<void> {
|
|||||||
// Hide the trainer and init next battle
|
// Hide the trainer and init next battle
|
||||||
const trainer = globalScene.currentBattle.trainer;
|
const trainer = globalScene.currentBattle.trainer;
|
||||||
// Unassign previous trainer from battle so it isn't destroyed before animation completes
|
// Unassign previous trainer from battle so it isn't destroyed before animation completes
|
||||||
globalScene.currentBattle.trainer = null;
|
globalScene.currentBattle.trainer = undefined;
|
||||||
await spawnNextTrainerOrEndEncounter();
|
await spawnNextTrainerOrEndEncounter();
|
||||||
if (trainer) {
|
if (trainer) {
|
||||||
globalScene.tweens.add({
|
globalScene.tweens.add({
|
||||||
|
@ -392,7 +392,7 @@ export class GameMode implements GameModeConfig {
|
|||||||
/**
|
/**
|
||||||
* Returns the wave range where MEs can spawn for the game mode [min, max]
|
* Returns the wave range where MEs can spawn for the game mode [min, max]
|
||||||
*/
|
*/
|
||||||
getMysteryEncounterLegalWaves(): [number, number] {
|
getMysteryEncounterLegalWaves(): [minWave: number, maxWave: number] {
|
||||||
switch (this.modeId) {
|
switch (this.modeId) {
|
||||||
case GameModes.CLASSIC:
|
case GameModes.CLASSIC:
|
||||||
return CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES;
|
return CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES;
|
||||||
|
@ -21,7 +21,7 @@ export class SwitchBiomePhase extends BattlePhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Before switching biomes, make sure to set the last encounter for other phases that need it too.
|
// Before switching biomes, make sure to set the last encounter for other phases that need it too.
|
||||||
globalScene.lastEnemyTrainer = globalScene.currentBattle?.trainer ?? null;
|
globalScene.lastEnemyTrainer = globalScene.currentBattle?.trainer;
|
||||||
globalScene.lastMysteryEncounter = globalScene.currentBattle?.mysteryEncounter;
|
globalScene.lastMysteryEncounter = globalScene.currentBattle?.mysteryEncounter;
|
||||||
|
|
||||||
globalScene.tweens.add({
|
globalScene.tweens.add({
|
||||||
|
@ -482,13 +482,17 @@ export function getLocalizedSpriteKey(baseKey: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a number is **inclusively** between two numbers
|
* Check if a number is between two numbers (either inclusively or exclusively).
|
||||||
* @param num - the number to check
|
* @param num - The number to check
|
||||||
* @param min - the minimum value (inclusive)
|
* @param min - The minimum value
|
||||||
* @param max - the maximum value (inclusive)
|
* @param max - The maximum value
|
||||||
* @returns Whether num is no less than min and no greater than max
|
* @param exclusive - Whether to exclusively check boundedness; default `false` (inclusive)
|
||||||
|
* @returns Whether `num` is no less than `min` and no greater than `max`
|
||||||
*/
|
*/
|
||||||
export function isBetween(num: number, min: number, max: number): boolean {
|
export function isBetween(num: number, min: number, max: number, exclusive = true): boolean {
|
||||||
|
if (exclusive) {
|
||||||
|
return min < num && num < max;
|
||||||
|
}
|
||||||
return min <= num && num <= max;
|
return min <= num && num <= max;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user