mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-09-23 15:03:24 +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 { 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 NewBattleBaseProps {
|
||||
/** The type of battle to create. */
|
||||
battleType: BattleType;
|
||||
/**
|
||||
* The `Trainer` to spawn.
|
||||
* Only present in populated data and will be `undefined` for non-trainer battles.
|
||||
*/
|
||||
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;
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* The wave number of the NEW wave to spawn.
|
||||
* Will always be >=1 (barring save data corruption).
|
||||
*/
|
||||
waveIndex: number;
|
||||
/**
|
||||
* Whether the battle is a double battle.
|
||||
* Always `false` when an ME is spawned.
|
||||
*/
|
||||
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.
|
||||
* when converting session data into a new battle.
|
||||
* @interface
|
||||
*/
|
||||
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 type { HeldModifierConfig } from "#types/held-modifier-config";
|
||||
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 { AbilityBar } from "#ui/ability-bar";
|
||||
import { ArenaFlyout } from "#ui/arena-flyout";
|
||||
@ -273,7 +278,7 @@ export class BattleScene extends SceneBase {
|
||||
public score: number;
|
||||
public lockModifierTiers: boolean;
|
||||
public trainer: Phaser.GameObjects.Sprite;
|
||||
public lastEnemyTrainer: Trainer | null;
|
||||
public lastEnemyTrainer: Trainer | undefined;
|
||||
public currentBattle: Battle;
|
||||
public pokeballCounts: PokeballCounts;
|
||||
public money: number;
|
||||
@ -1299,26 +1304,31 @@ export class BattleScene extends SceneBase {
|
||||
*/
|
||||
public newBattle(fromSession?: SessionSaveData): Battle {
|
||||
const props = this.getNewBattleProps(fromSession);
|
||||
const resolved: Partial<NewBattleResolvedProps> = {};
|
||||
const { waveIndex } = props;
|
||||
const resolved: NewBattleInitialProps = { waveIndex, mysteryEncounterType: props.mysteryEncounterType };
|
||||
|
||||
// TODO: Address this during an RNG overhaul
|
||||
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)) {
|
||||
this.handleFixedBattle(resolved, waveIndex);
|
||||
this.handleFixedBattle(resolved);
|
||||
} else if (props.trainerData) {
|
||||
this.handleSavedBattle(resolved, props);
|
||||
} 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();
|
||||
|
||||
this.lastEnemyTrainer = lastBattle?.trainer ?? null;
|
||||
this.lastEnemyTrainer = lastBattle?.trainer;
|
||||
this.lastMysteryEncounter = lastBattle?.mysteryEncounter;
|
||||
|
||||
// TODO: Is this even needed?
|
||||
@ -1335,7 +1345,7 @@ export class BattleScene extends SceneBase {
|
||||
() => {
|
||||
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.currentBattle.incrementTurn();
|
||||
@ -1348,81 +1358,6 @@ export class BattleScene extends SceneBase {
|
||||
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.
|
||||
* @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 };
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
const isNewBiome = this.isNewBiome(lastBattle);
|
||||
/** 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.
|
||||
* @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
|
||||
if (
|
||||
// Wave 1 doubles cause crashes
|
||||
@ -1522,10 +1545,13 @@ export class BattleScene extends SceneBase {
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Previously defined double battle status (from save data)
|
||||
if (double != null) {
|
||||
return double;
|
||||
}
|
||||
|
||||
// Overrides
|
||||
switch (Overrides.BATTLE_STYLE_OVERRIDE) {
|
||||
case "double":
|
||||
return true;
|
||||
@ -1537,6 +1563,7 @@ export class BattleScene extends SceneBase {
|
||||
return waveIndex % 2 === 1;
|
||||
}
|
||||
|
||||
// Standard wild battle chance
|
||||
if (battleType === BattleType.WILD) {
|
||||
return !randSeedInt(this.getDoubleBattleChance(waveIndex));
|
||||
}
|
||||
@ -3538,8 +3565,10 @@ export class BattleScene extends SceneBase {
|
||||
|
||||
/**
|
||||
* Returns if a wave COULD spawn a {@linkcode MysteryEncounter}.
|
||||
* Even if returns `true`, does not guarantee that a wave will actually be a ME.
|
||||
* That check is made in {@linkcode BattleScene.isWaveMysteryEncounter} instead.
|
||||
* @param battleType - The {@linkcode BattleType} of the newly created battle
|
||||
* @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 {
|
||||
const [lowestMysteryEncounterWave, highestMysteryEncounterWave] = this.gameMode.getMysteryEncounterLegalWaves();
|
||||
@ -3548,55 +3577,56 @@ export class BattleScene extends SceneBase {
|
||||
&& battleType === BattleType.WILD
|
||||
&& !this.gameMode.isBoss(waveIndex)
|
||||
&& waveIndex % 10 !== 1
|
||||
&& waveIndex < highestMysteryEncounterWave
|
||||
&& waveIndex > lowestMysteryEncounterWave
|
||||
&& isBetween(waveIndex, lowestMysteryEncounterWave, highestMysteryEncounterWave, true)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Additionally, MEs cannot spawn outside of waves 10-180 in those modes
|
||||
* @param newBattleType
|
||||
* @param waveIndex
|
||||
* @param battleType - The {@linkcode BattleType} of the newly created battle
|
||||
* @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 {
|
||||
const [lowestMysteryEncounterWave, highestMysteryEncounterWave] = this.gameMode.getMysteryEncounterLegalWaves();
|
||||
if (this.isMysteryEncounterValidForWave(newBattleType, waveIndex)) {
|
||||
// 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
|
||||
const sessionEncounterRate = this.mysteryEncounterSaveData.encounterSpawnChance;
|
||||
const encounteredEvents = this.mysteryEncounterSaveData.encounteredEvents;
|
||||
|
||||
// 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
|
||||
// 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
|
||||
const expectedEncountersByFloor =
|
||||
(AVERAGE_ENCOUNTERS_PER_RUN_TARGET / (highestMysteryEncounterWave - lowestMysteryEncounterWave))
|
||||
* (waveIndex - lowestMysteryEncounterWave);
|
||||
const currentRunDiffFromAvg = expectedEncountersByFloor - encounteredEvents.length;
|
||||
const favoredEncounterRate =
|
||||
sessionEncounterRate
|
||||
+ Math.min(currentRunDiffFromAvg * ANTI_VARIANCE_WEIGHT_MODIFIER, MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT / 2);
|
||||
|
||||
const successRate = Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE ?? favoredEncounterRate;
|
||||
|
||||
// MEs can only spawn 3 or more waves after the previous ME, barring overrides
|
||||
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
|
||||
this.executeWithSeedOffset(
|
||||
() => {
|
||||
roll = randSeedInt(MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT);
|
||||
},
|
||||
waveIndex * 3 * 1000,
|
||||
);
|
||||
return roll < successRate;
|
||||
}
|
||||
private isWaveMysteryEncounter(battleType: BattleType, waveIndex: number): boolean {
|
||||
if (!this.isMysteryEncounterValidForWave(battleType, waveIndex)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
const [lowestMysteryEncounterWave, highestMysteryEncounterWave] = this.gameMode.getMysteryEncounterLegalWaves();
|
||||
// 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
|
||||
const sessionEncounterRate = this.mysteryEncounterSaveData.encounterSpawnChance;
|
||||
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)
|
||||
// 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
|
||||
const expectedEncountersByFloor =
|
||||
(AVERAGE_ENCOUNTERS_PER_RUN_TARGET / (highestMysteryEncounterWave - lowestMysteryEncounterWave))
|
||||
* (waveIndex - lowestMysteryEncounterWave);
|
||||
const currentRunDiffFromAvg = expectedEncountersByFloor - encounteredEvents.length;
|
||||
const favoredEncounterRate =
|
||||
sessionEncounterRate
|
||||
+ Math.min(currentRunDiffFromAvg * ANTI_VARIANCE_WEIGHT_MODIFIER, MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT / 2);
|
||||
|
||||
const successRate = Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE ?? favoredEncounterRate;
|
||||
|
||||
let roll = 0;
|
||||
// Always rolls the check on the same offset to ensure no RNG changes from reloading session
|
||||
this.executeWithSeedOffset(() => {
|
||||
roll = randSeedInt(MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT);
|
||||
}, waveIndex * 3000);
|
||||
return roll < successRate;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -100,17 +100,22 @@ export class Battle {
|
||||
|
||||
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.waveIndex = waveIndex;
|
||||
this.battleType = battleType;
|
||||
this.trainer = trainer;
|
||||
this.mysteryEncounterType = mysteryEncounterType;
|
||||
this.double = double;
|
||||
|
||||
this.initBattleSpec();
|
||||
this.enemyLevels =
|
||||
battleType !== BattleType.TRAINER
|
||||
? new Array(double ? 2 : 1).fill(null).map(() => this.getLevelForWave())
|
||||
: trainer?.getPartyLevels(this.waveIndex);
|
||||
this.double = double;
|
||||
}
|
||||
|
||||
private initBattleSpec(): void {
|
||||
|
@ -228,7 +228,7 @@ function endTrainerBattleAndShowDialogue(): Promise<void> {
|
||||
// Hide the trainer and init next battle
|
||||
const trainer = globalScene.currentBattle.trainer;
|
||||
// Unassign previous trainer from battle so it isn't destroyed before animation completes
|
||||
globalScene.currentBattle.trainer = null;
|
||||
globalScene.currentBattle.trainer = undefined;
|
||||
await spawnNextTrainerOrEndEncounter();
|
||||
if (trainer) {
|
||||
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]
|
||||
*/
|
||||
getMysteryEncounterLegalWaves(): [number, number] {
|
||||
getMysteryEncounterLegalWaves(): [minWave: number, maxWave: number] {
|
||||
switch (this.modeId) {
|
||||
case GameModes.CLASSIC:
|
||||
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.
|
||||
globalScene.lastEnemyTrainer = globalScene.currentBattle?.trainer ?? null;
|
||||
globalScene.lastEnemyTrainer = globalScene.currentBattle?.trainer;
|
||||
globalScene.lastMysteryEncounter = globalScene.currentBattle?.mysteryEncounter;
|
||||
|
||||
globalScene.tweens.add({
|
||||
|
@ -482,13 +482,17 @@ export function getLocalizedSpriteKey(baseKey: string) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a number is **inclusively** between two numbers
|
||||
* @param num - the number to check
|
||||
* @param min - the minimum value (inclusive)
|
||||
* @param max - the maximum value (inclusive)
|
||||
* @returns Whether num is no less than min and no greater than max
|
||||
* Check if a number is between two numbers (either inclusively or exclusively).
|
||||
* @param num - The number to check
|
||||
* @param min - The minimum value
|
||||
* @param max - The maximum value
|
||||
* @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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user