diff --git a/src/battle-scene.ts b/src/battle-scene.ts index cbaf07d579c..35709216d58 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2136,6 +2136,7 @@ export default class BattleScene extends SceneBase { if (ignoreLevelCap || Overrides.LEVEL_CAP_OVERRIDE < 0) { return Number.MAX_SAFE_INTEGER; } + const waveIndex = Math.ceil((this.currentBattle?.waveIndex || 1) / 10) * 10; const difficultyWaveIndex = this.gameMode.getWaveForDifficulty(waveIndex); const baseLevel = (1 + difficultyWaveIndex / 2 + Math.pow(difficultyWaveIndex / 25, 2)) * 1.2; @@ -3719,7 +3720,7 @@ export default class BattleScene extends SceneBase { expMultiplier *= 1.5; } if (Overrides.XP_MULTIPLIER_OVERRIDE !== null) { - expMultiplier = Overrides.XP_MULTIPLIER_OVERRIDE; + expMultiplier = Math.max(Overrides.XP_MULTIPLIER_OVERRIDE, 0); } const pokemonExp = new NumberHolder(expValue * expMultiplier); this.applyModifiers(PokemonExpBoosterModifier, true, partyMember, pokemonExp); diff --git a/src/overrides.ts b/src/overrides.ts index 95c758d7c43..14fb38cc9e8 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -48,7 +48,7 @@ const overrides = {} satisfies Partial>; /** * If you need to add Overrides values for local testing do that inside {@linkcode overrides} * --- - * Defaults for Overrides that are used when testing different in game situations + * Defaults for Overrides that are used when testing different in-game situations. * * If an override name starts with "STARTING", it will only apply when a new run begins. */ @@ -56,10 +56,16 @@ class DefaultOverrides { // ----------------- // OVERALL OVERRIDES // ----------------- - /** a specific seed (default: a random string of 24 characters) */ - readonly SEED_OVERRIDE: string = ""; + + /** Overrides run initial RNG seed. If empty or `null`, defaults to a random string of 24 characters. */ + readonly SEED_OVERRIDE: string | null = null; + /** + * Overrides starting RNG seed used for daily run generation. + * If empty or `null`, defaults to a base-64 representation of the current ISO clock date + * (YYYY-MM-DD). + */ readonly DAILY_RUN_SEED_OVERRIDE: string | null = null; - readonly WEATHER_OVERRIDE: WeatherType = WeatherType.NONE; + readonly WEATHER_OVERRIDE: WeatherType | null = null; /** * If `null`, ignore this override. * @@ -75,10 +81,16 @@ class DefaultOverrides { readonly STARTING_WAVE_OVERRIDE: number = 0; readonly STARTING_BIOME_OVERRIDE: Biome | null = null; readonly ARENA_TINT_OVERRIDE: TimeOfDay | null = null; - /** Multiplies XP gained by this value including 0. Set to null to ignore the override. */ + /** + * Overrides the XP multiplier used during experience gain calculations. + * Set to `0` or lower to disable XP gains completely, or `null` to disable the override. + */ readonly XP_MULTIPLIER_OVERRIDE: number | null = null; - /** Sets the level cap to this number during experience gain calculations. Set to `0` to disable override & use normal wave-based level caps, - or any negative number to set it to 9 quadrillion (effectively disabling it). */ + /** + * Overrides the level cap used during experience gain calculations. + * Set to `0` to disable override & use normal wave-based level caps, + * or any negative number to set it to `Number.MAX_SAFE_INTEGER` (effectively disabling it). + */ readonly LEVEL_CAP_OVERRIDE: number = 0; readonly NEVER_CRIT_OVERRIDE: boolean = false; /** default 1000 */ @@ -120,9 +132,9 @@ class DefaultOverrides { // ---------------- /** * Set the form index of any starter in the party whose `speciesId` is inside this override - * @see {@link allSpecies} in `src/data/pokemon-species.ts` for form indexes + * @see {@linkcode allSpecies} in `src/data/pokemon-species.ts` for form indices * @example - * ``` + * ```ts * const STARTER_FORM_OVERRIDES = { * [Species.DARMANITAN]: 1 * } @@ -130,23 +142,26 @@ class DefaultOverrides { */ readonly STARTER_FORM_OVERRIDES: Partial> = {}; - /** default 5 or 20 for Daily */ + /** + * Override player party starting level. + * Defaults to normal starting levels for game mode if `0` or less. + */ readonly STARTING_LEVEL_OVERRIDE: number = 0; /** - * SPECIES OVERRIDE - * will only apply to the first starter in your party or each enemy pokemon - * default is 0 to not override - * @example SPECIES_OVERRIDE = Species.Bulbasaur; + * If defined, this will override the species of the first starter in your party. + * default is `null` to not override + * @example STARTER_SPECIES_OVERRIDE = Species.BULBASAUR; */ - readonly STARTER_SPECIES_OVERRIDE: Species | number = 0; + readonly STARTER_SPECIES_OVERRIDE: Species | null = null; /** - * This will force your starter to be a random fusion + * Set to `true` to force your starter to be a random fusion (similar to Spliced Endless). */ readonly STARTER_FUSION_OVERRIDE: boolean = false; /** - * This will override the species of the fusion + * Overrides player random fusion species (à la Spliced Endless) if starter fusion is enabled. + * Set to `null` to disable and use random fusion species. */ - readonly STARTER_FUSION_SPECIES_OVERRIDE: Species | number = 0; + readonly STARTER_FUSION_SPECIES_OVERRIDE: Species | null = null; readonly ABILITY_OVERRIDE: Abilities = Abilities.NONE; readonly PASSIVE_ABILITY_OVERRIDE: Abilities = Abilities.NONE; readonly HAS_PASSIVE_ABILITY_OVERRIDE: boolean | null = null; @@ -159,15 +174,16 @@ class DefaultOverrides { // -------------------------- // OPPONENT / ENEMY OVERRIDES // -------------------------- - readonly OPP_SPECIES_OVERRIDE: Species | number = 0; + readonly OPP_SPECIES_OVERRIDE: Species | null = null; /** - * This will make all opponents fused Pokemon + * Set to `true` to force all enemy pokemon to be random fusions (similar to Spliced Endless/Fusion Tokens). */ readonly OPP_FUSION_OVERRIDE: boolean = false; /** - * This will override the species of the fusion only when the opponent is already a fusion + * Overrides enemy random fusion species (à la Spliced Endless) for enemies with fusions. + * Set to `null` to disable and use random fusion species. */ - readonly OPP_FUSION_SPECIES_OVERRIDE: Species | number = 0; + readonly OPP_FUSION_SPECIES_OVERRIDE: Species | null = null; readonly OPP_LEVEL_OVERRIDE: number = 0; readonly OPP_ABILITY_OVERRIDE: Abilities = Abilities.NONE; readonly OPP_PASSIVE_ABILITY_OVERRIDE: Abilities = Abilities.NONE; @@ -180,8 +196,7 @@ class DefaultOverrides { readonly OPP_IVS_OVERRIDE: number | number[] = []; readonly OPP_FORM_OVERRIDES: Partial> = {}; /** - * Override to give the enemy Pokemon a given amount of health segments - * + * Overrides enemy Pokemon's health segments * 0 (default): the health segments will be handled normally based on wave, level and species * 1: the Pokemon will have a single health segment and therefore will not be a boss * 2+: the Pokemon will be a boss with the given number of health segments @@ -204,7 +219,9 @@ class DefaultOverrides { // ------------------------- /** - * `1` (almost never) to `256` (always), set to `null` to disable the override + * Override chance of encountering a Mystery Encounter (out of 256). + * Also disables the required 3 wave gap between successive MEs if defined. + * Ranges from `0` (never) to `256` (always); set to `null` to disable the override * * Note: Make sure `STARTING_WAVE_OVERRIDE > 10`, otherwise MEs won't trigger */ @@ -258,8 +275,8 @@ class DefaultOverrides { /** * Override array of {@linkcode ModifierOverride}s used to replace the generated item rolls after a wave. * - * If less entries are listed than rolled, only those entries will be used to replace the corresponding items while the rest randomly generated. - * If more entries are listed than rolled, only the first X entries will be used, where X is the number of items rolled. + * If less entries are listed than rolled, the remaining entries will be randomly generated as normal. + * If more entries are listed than rolled, any excess items are ignored. * * Note that, for all items in the array, `count` is not used. */ @@ -272,7 +289,7 @@ class DefaultOverrides { /** * Set all non-scripted waves to use the selected battle type. - * + * * Ignored if set to {@linkcode BattleType.TRAINER} and `DISABLE_STANDARD_TRAINERS_OVERRIDE` is `true`. */ readonly BATTLE_TYPE_OVERRIDE: Exclude | null = null; @@ -298,4 +315,4 @@ export type RandomTrainerOverride = { } /** The type of the {@linkcode DefaultOverrides} class */ -export type OverridesType = typeof DefaultOverrides; \ No newline at end of file +export type OverridesType = typeof DefaultOverrides; diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index 56057c23372..01a2f07ae8f 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -18,7 +18,7 @@ import { vouchers } from "#app/system/voucher"; import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { SaveSlotUiMode } from "#app/ui/save-slot-select-ui-handler"; import { UiMode } from "#enums/ui-mode"; -import { isLocal, isLocalServerConnected, isNullOrUndefined } from "#app/utils/common"; +import { isLocal, isLocalServerConnected } from "#app/utils/common"; import i18next from "i18next"; import { CheckSwitchPhase } from "./check-switch-phase"; import { EncounterPhase } from "./encounter-phase"; @@ -290,7 +290,7 @@ export class TitlePhase extends Phase { }); } else { let seed: string = btoa(new Date().toISOString().substring(0, 10)); - if (!isNullOrUndefined(Overrides.DAILY_RUN_SEED_OVERRIDE)) { + if (Overrides.DAILY_RUN_SEED_OVERRIDE) { seed = Overrides.DAILY_RUN_SEED_OVERRIDE; } generateDaily(seed); diff --git a/test/abilities/disguise.test.ts b/test/abilities/disguise.test.ts index 9eaa751f53f..cd578911140 100644 --- a/test/abilities/disguise.test.ts +++ b/test/abilities/disguise.test.ts @@ -105,7 +105,7 @@ describe("Abilities - Disguise", () => { }); it("persists form change when switched out", async () => { - game.override.enemyMoveset([Moves.SHADOW_SNEAK]).starterSpecies(0); + game.override.enemyMoveset([Moves.SHADOW_SNEAK]).starterSpecies(null); await game.classicMode.startBattle([Species.MIMIKYU, Species.FURRET]); @@ -129,7 +129,7 @@ describe("Abilities - Disguise", () => { }); it("persists form change when wave changes with no arena reset", async () => { - game.override.starterSpecies(0).starterForms({ + game.override.starterSpecies(null).starterForms({ [Species.MIMIKYU]: bustedForm, }); await game.classicMode.startBattle([Species.FURRET, Species.MIMIKYU]); @@ -168,7 +168,7 @@ describe("Abilities - Disguise", () => { it("reverts to Disguised form on biome change when fainted", async () => { game.override .startingWave(10) - .starterSpecies(0) + .starterSpecies(null) .starterForms({ [Species.MIMIKYU]: bustedForm, }); diff --git a/test/moves/burning_jealousy.test.ts b/test/moves/burning_jealousy.test.ts index 1d9ba974687..09b1c745249 100644 --- a/test/moves/burning_jealousy.test.ts +++ b/test/moves/burning_jealousy.test.ts @@ -50,7 +50,7 @@ describe("Moves - Burning Jealousy", () => { }); it("should still burn the opponent if their stat stages were both raised and lowered in the same turn", async () => { - game.override.starterSpecies(0).battleStyle("double"); + game.override.starterSpecies(null).battleStyle("double"); await game.classicMode.startBattle([Species.FEEBAS, Species.ABRA]); const enemy = game.scene.getEnemyPokemon()!; diff --git a/test/testUtils/helpers/overridesHelper.ts b/test/testUtils/helpers/overridesHelper.ts index acc2e4d5cd0..1a94bb8f7d7 100644 --- a/test/testUtils/helpers/overridesHelper.ts +++ b/test/testUtils/helpers/overridesHelper.ts @@ -51,91 +51,115 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the player pokemon's starting level - * @param level - The level to set + * @param level - The level to set; set to `0` or lower to disable override * @returns `this` */ - public startingLevel(level: Species | number): this { + public startingLevel(level: number): this { vi.spyOn(Overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(level); - this.log(`Player Pokemon starting level set to ${level}!`); + if (level > 0) { + this.log(`Player Pokemon starting level set to ${level}!`); + } else { + this.log("Player Pokemon starting level set to default for game mode!"); + } return this; } /** - * Override the XP Multiplier - * @param value - The XP multiplier to set + * Override the XP Multiplier used during experience gain calculations. + * @param multi - The XP multiplier to set; set to any negative number to disable XP gain + * or `null` to disable override. * @returns `this` */ - public xpMultiplier(value: number): this { - vi.spyOn(Overrides, "XP_MULTIPLIER_OVERRIDE", "get").mockReturnValue(value); - this.log(`XP Multiplier set to ${value}!`); + public xpMultiplier(multi: number | null): this { + vi.spyOn(Overrides, "XP_MULTIPLIER_OVERRIDE", "get").mockReturnValue(multi); + const multiStr = !multi + ? "XP multiplier reset to default value!" + : multi > 0 + ? `XP multiplier set to ${multi?.toPrecision(5)}!` + : "XP gain disabled!"; + + this.log(multiStr); return this; } /** - * Override the wave level cap - * @param cap - The level cap value to set; 0 uses normal level caps and negative values - * disable it completely + * Override the wave level cap used during experience gain calculations. + * @param cap - The level cap value to set. Set to any negative number to disable level caps entirely, + * or `0` to disable the override. * @returns `this` */ public levelCap(cap: number): this { vi.spyOn(Overrides, "LEVEL_CAP_OVERRIDE", "get").mockReturnValue(cap); - let capStr: string; - if (cap > 0) { - capStr = `Level cap set to ${cap}!`; - } else if (cap < 0) { - capStr = "Level cap disabled!"; - } else { - capStr = "Level cap reset to default value for wave."; - } + const capStr = !cap + ? "Level cap reset to default value for wave!" + : cap > 0 + ? `Level cap set to ${cap}!` + : "Level cap disabled!"; + this.log(capStr); return this; } /** - * Override the player pokemon's starting held items - * @param items - The items to hold + * Override the player pokemon's starting held items. + * @param modifiers - Array of {@linkcode ModifierOverride | modifiers} to set + * @remarks Use {@linkcode startingModifier} for non-held item modifiers * @returns `this` */ public startingHeldItems(items: ModifierOverride[]): this { vi.spyOn(Overrides, "STARTING_HELD_ITEMS_OVERRIDE", "get").mockReturnValue(items); - this.log("Player Pokemon starting held items set to:", items); + this.log(`Player Pokemon starting held items set to: ${items}`); return this; } /** - * Override the player pokemon's {@linkcode Species | species} + * Override the player pokemon's {@linkcode Species | species}. * @param species - The {@linkcode Species | species} to set * @returns `this` */ - public starterSpecies(species: Species | number): this { + public starterSpecies(species: Species | null): this { vi.spyOn(Overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(species); - this.log(`Player Pokemon species set to ${Species[species]} (=${species})!`); + if (species) { + this.log(`Player Pokemon species set to ${Species[species]} (=${species})!`); + } else { + this.log("Player Pokemon species reset to default value!"); + } return this; } /** - * Override the player pokemon to be a random fusion + * Override the player pokemon to be (or not be) a random fusion. + * @param enabled - `true` to enable fusion, `false` to disable; default `true` * @returns `this` */ - public enableStarterFusion(): this { - vi.spyOn(Overrides, "STARTER_FUSION_OVERRIDE", "get").mockReturnValue(true); - this.log("Player Pokemon is a random fusion!"); + public enableStarterFusion(enabled = true): this { + vi.spyOn(Overrides, "STARTER_FUSION_OVERRIDE", "get").mockReturnValue(enabled); + if (enabled) { + this.log("Player Pokemon is a random fusion!"); + } else { + this.log("Player Pokemon is no longer a random fusion!"); + } + return this; } /** - * Override the player pokemon's fusion species - * @param species - The fusion species to set + * Override the player pokemon's fusion species for starter fusion. + * @param species - The {@linkcode Species | species} to fuse with, or `null` to disable and use random fusion * @returns `this` */ - public starterFusionSpecies(species: Species | number): this { + public starterFusionSpecies(species: Species | null): this { vi.spyOn(Overrides, "STARTER_FUSION_SPECIES_OVERRIDE", "get").mockReturnValue(species); - this.log(`Player Pokemon fusion species set to ${Species[species]} (=${species})!`); + if (species) { + this.log(`Player Pokemon fusion species set to ${Species[species]} (=${species})!`); + } else { + this.log("Player Pokemon fusion species reset to random species!"); + } return this; } /** - * Override the player pokemon's forms + * Override the player pokemon's starting forms * @param forms - The forms to set * @returns `this` */ @@ -144,60 +168,73 @@ export class OverridesHelper extends GameManagerHelper { const formsStr = Object.entries(forms) .map(([speciesId, formIndex]) => `${Species[speciesId]}=${formIndex}`) .join(", "); - this.log(`Player Pokemon form set to: ${formsStr}!`); + this.log(`Player Pokemon form set to ${formsStr}!`); return this; } /** - * Override the player's starting modifiers - * @param modifiers - The modifiers to set + * Override the player's starting modifiers. + * @param modifiers - Array of {@linkcode ModifierOverride | modifiers} to set + * @remarks Use {@linkcode startingHeldItems} for held item modifiers. + * @see {@linkcode startingHeldItems} * @returns `this` */ public startingModifier(modifiers: ModifierOverride[]): this { vi.spyOn(Overrides, "STARTING_MODIFIER_OVERRIDE", "get").mockReturnValue(modifiers); - this.log(`Player starting modifiers set to: ${modifiers}`); + this.log(`Player starting modifiers set to ${modifiers}`); return this; } /** * Override the player pokemon's {@linkcode Abilities | ability}. - * @param ability - The {@linkcode Abilities | ability} to set + * @param ability - The {@linkcode Abilities | ability} to set, or `Abilities.NONE` to disable override * @returns `this` */ public ability(ability: Abilities): this { vi.spyOn(Overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(ability); - this.log(`Player Pokemon ability set to ${Abilities[ability]} (=${ability})!`); + if (ability) { + this.log(`Player Pokemon ability set to ${Abilities[ability]} (=${ability})!`); + } else { + this.log("Player Pokemon ability reset to default value for species!"); + } return this; } /** * Override the player pokemon's **passive** {@linkcode Abilities | ability} - * @param passiveAbility - The **passive** {@linkcode Abilities | ability} to set + * @param passiveAbility - The **passive** {@linkcode Abilities | ability} to set, or `Abilities.NONE` to disable override * @returns `this` */ public passiveAbility(passiveAbility: Abilities): this { vi.spyOn(Overrides, "PASSIVE_ABILITY_OVERRIDE", "get").mockReturnValue(passiveAbility); - this.log(`Player Pokemon PASSIVE ability set to ${Abilities[passiveAbility]} (=${passiveAbility})!`); + if (passiveAbility) { + this.log(`Player Pokemon PASSIVE ability set to ${Abilities[passiveAbility]} (=${passiveAbility})!`); + } else { + this.log("Player Pokemon PASSIVE ability reset to default value for species!"); + } return this; } /** * Forces the status of the player pokemon **passive** {@linkcode Abilities | ability} - * @param hasPassiveAbility - Forces the passive to be active if `true`, inactive if `false` + * @param hasPassiveAbility - Forces passive to be active if `true` or inactive if `false`; + * set to `null` to disable * @returns `this` */ public hasPassiveAbility(hasPassiveAbility: boolean | null): this { vi.spyOn(Overrides, "HAS_PASSIVE_ABILITY_OVERRIDE", "get").mockReturnValue(hasPassiveAbility); if (hasPassiveAbility === null) { - this.log("Player Pokemon PASSIVE ability no longer force enabled or disabled!"); + this.log("Player Pokemon PASSIVE ability no longer forcibly enabled or disabled!"); } else { - this.log(`Player Pokemon PASSIVE ability is force ${hasPassiveAbility ? "enabled" : "disabled"}!`); + this.log(`Player Pokemon PASSIVE ability forcibly ${hasPassiveAbility ? "enabled" : "disabled"}!`); } return this; } /** - * Override the player pokemon's {@linkcode Moves | moves}set - * @param moveset - The {@linkcode Moves | moves}set to set + * Override the player pokemon's {@linkcode Moves | moveset}. + * @param moveset - The {@linkcode Moves | moveset} to set. + * @warning This also overrides PP count and other values. + * @see {@linkcode changeMoveset} in `moveHelper.ts` for a more manual override * @returns `this` */ public moveset(moveset: Moves | Moves[]): this { @@ -211,9 +248,9 @@ export class OverridesHelper extends GameManagerHelper { } /** - * Override the player pokemon's {@linkcode StatusEffect | status-effect} - * @param statusEffect - The {@linkcode StatusEffect | status-effect} to set - * @returns + * Override the player pokemon's {@linkcode StatusEffect | non-volatile status condition}. + * @param statusEffect - The {@linkcode StatusEffect | status effect} to set + * @returns `this` */ public statusEffect(statusEffect: StatusEffect): this { vi.spyOn(Overrides, "STATUS_OVERRIDE", "get").mockReturnValue(statusEffect); @@ -307,13 +344,17 @@ export class OverridesHelper extends GameManagerHelper { } /** - * Override the {@linkcode Species | species} of enemy pokemon - * @param species - The {@linkcode Species | species} to set + * Override the {@linkcode Species | species} of enemy pokemon. + * @param species - The {@linkcode Species | species} to set, or `null` to disable override * @returns `this` */ - public enemySpecies(species: Species | number): this { + public enemySpecies(species: Species | null): this { vi.spyOn(Overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(species); - this.log(`Enemy Pokemon species set to ${Species[species]} (=${species})!`); + if (species) { + this.log(`Enemy Pokemon species set to ${Species[species]} (=${species})!`); + } else { + this.log("Enemy Pokemon species reset to default!"); + } return this; }