diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 942ef4e1bfc..16a74757598 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -115,6 +115,26 @@ export class ModifierType { return this; } + /** + * Populates the tier field by performing a reverse lookup on the modifier pool specified by {@linkcode poolType} using the + * {@linkcode ModifierType}'s id. + * @param poolType the {@linkcode ModifierPoolType} to look into to derive the item's tier; defaults to {@linkcode ModifierPoolType.PLAYER} + */ + withTierFromPool(poolType: ModifierPoolType = ModifierPoolType.PLAYER): ModifierType { + for (const tier of Object.values(getModifierPoolForType(poolType))) { + for (const modifier of tier) { + if (this.id === modifier.modifierType.id) { + this.tier = modifier.modifierType.tier; + break; + } + } + if (this.tier) { + break; + } + } + return this; + } + newModifier(...args: any[]): Modifier | null { return this.newModifierFunc && this.newModifierFunc(this, args); } @@ -855,7 +875,7 @@ export class FusePokemonModifierType extends PokemonModifierType { class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator { constructor() { super((party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs) { + if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in Type)) { return new AttackTypeBoosterModifierType(pregenArgs[0] as Type, 20); } @@ -919,12 +939,13 @@ class SpeciesStatBoosterModifierTypeGenerator extends ModifierTypeGenerator { constructor() { super((party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs) { + const items = SpeciesStatBoosterModifierTypeGenerator.items; + if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in items)) { return new SpeciesStatBoosterModifierType(pregenArgs[0] as SpeciesStatBoosterItem); } - const values = Object.values(SpeciesStatBoosterModifierTypeGenerator.items); - const keys = Object.keys(SpeciesStatBoosterModifierTypeGenerator.items); + const values = Object.values(items); + const keys = Object.keys(items); const weights = keys.map(() => 0); for (const p of party) { @@ -979,7 +1000,10 @@ class SpeciesStatBoosterModifierTypeGenerator extends ModifierTypeGenerator { class TmModifierTypeGenerator extends ModifierTypeGenerator { constructor(tier: ModifierTier) { - super((party: Pokemon[]) => { + super((party: Pokemon[], pregenArgs?: any[]) => { + if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in Moves)) { + return new TmModifierType(pregenArgs[0] as Moves); + } const partyMemberCompatibleTms = party.map(p => (p as PlayerPokemon).compatibleTms.filter(tm => !p.moveset.find(m => m?.moveId === tm))); const tierUniqueCompatibleTms = partyMemberCompatibleTms.flat().filter(tm => tmPoolTiers[tm] === tier).filter(tm => !allMoves[tm].name.endsWith(" (N)")).filter((tm, i, array) => array.indexOf(tm) === i); if (!tierUniqueCompatibleTms.length) { @@ -994,7 +1018,7 @@ class TmModifierTypeGenerator extends ModifierTypeGenerator { class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator { constructor(rare: boolean) { super((party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs) { + if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in EvolutionItem)) { return new EvolutionItemModifierType(pregenArgs[0] as EvolutionItem); } @@ -1021,7 +1045,7 @@ class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator { class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { constructor() { super((party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs) { + if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in FormChangeItem)) { return new FormChangeItemModifierType(pregenArgs[0] as FormChangeItem); } @@ -1274,7 +1298,7 @@ export const modifierTypes = { SPECIES_STAT_BOOSTER: () => new SpeciesStatBoosterModifierTypeGenerator(), TEMP_STAT_BOOSTER: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs) { + if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in TempBattleStat)) { return new TempBattleStatBoosterModifierType(pregenArgs[0] as TempBattleStat); } const randTempBattleStat = Utils.randSeedInt(6) as TempBattleStat; @@ -1283,7 +1307,7 @@ export const modifierTypes = { DIRE_HIT: () => new TempBattleStatBoosterModifierType(TempBattleStat.CRIT), BASE_STAT_BOOSTER: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs) { + if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in Stat)) { const stat = pregenArgs[0] as Stat; return new PokemonBaseStatBoosterModifierType(getBaseStatBoosterItemName(stat), stat); } @@ -1294,14 +1318,14 @@ export const modifierTypes = { ATTACK_TYPE_BOOSTER: () => new AttackTypeBoosterModifierTypeGenerator(), MINT: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs) { + if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in Nature)) { return new PokemonNatureChangeModifierType(pregenArgs[0] as Nature); } return new PokemonNatureChangeModifierType(Utils.randSeedInt(Utils.getEnumValues(Nature).length) as Nature); }), TERA_SHARD: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs) { + if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in Type)) { return new TerastallizeModifierType(pregenArgs[0] as Type); } if (!party[0].scene.getModifiers(Modifiers.TerastallizeAccessModifier).length) { @@ -1318,7 +1342,7 @@ export const modifierTypes = { }), BERRY: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs) { + if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in BerryType)) { return new BerryModifierType(pregenArgs[0] as BerryType); } const berryTypes = Utils.getEnumValues(BerryType); @@ -1908,15 +1932,36 @@ export function getPlayerModifierTypeOptions(count: integer, party: PlayerPokemo } }); - // OVERRIDE IF NECESSARY - Overrides.ITEM_REWARD_OVERRIDE.forEach((item, i) => { - const override = modifierTypes[item](); - options[i].type = override instanceof ModifierTypeGenerator ? override.generateType(party) : override; - }); + overridePlayerModifierTypeOptions(options, party); return options; } +/** + * Replaces the {@linkcode ModifierType} of the entries within {@linkcode options} with any + * {@linkcode ModifierOverride} entries listed in {@linkcode Overrides.ITEM_REWARD_OVERRIDE} + * up to the smallest amount of entries between {@linkcode options} and the override array. + * @param options Array of naturally rolled {@linkcode ModifierTypeOption}s + * @param party Array of the player's current party + */ +export function overridePlayerModifierTypeOptions(options: ModifierTypeOption[], party: PlayerPokemon[]) { + const minLength = Math.min(options.length, Overrides.ITEM_REWARD_OVERRIDE.length); + for (let i = 0; i < minLength; i++) { + const override: ModifierOverride = Overrides.ITEM_REWARD_OVERRIDE[i]; + const modifierFunc = modifierTypes[override.name]; + let modifierType: ModifierType | null = modifierFunc(); + + if (modifierType instanceof ModifierTypeGenerator) { + const pregenArgs = ("type" in override) && (override.type !== null) ? [override.type] : undefined; + modifierType = modifierType.generateType(party, pregenArgs); + } + + if (modifierType) { + options[i].type = modifierType.withIdFromFunc(modifierFunc).withTierFromPool(); + } + } +} + export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, baseCost: integer): ModifierTypeOption[] { if (!(waveIndex % 10)) { return []; @@ -1955,7 +2000,19 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, base } export function getEnemyBuffModifierForWave(tier: ModifierTier, enemyModifiers: Modifiers.PersistentModifier[], scene: BattleScene): Modifiers.EnemyPersistentModifier { - const tierStackCount = tier === ModifierTier.ULTRA ? 5 : tier === ModifierTier.GREAT ? 3 : 1; + let tierStackCount: number; + switch (tier) { + case ModifierTier.ULTRA: + tierStackCount = 5; + break; + case ModifierTier.GREAT: + tierStackCount = 3; + break; + default: + tierStackCount = 1; + break; + } + const retryCount = 50; let candidate = getNewModifierTypeOption([], ModifierPoolType.ENEMY_BUFF, tier); let r = 0; @@ -1983,7 +2040,20 @@ export function getDailyRunStarterModifiers(party: PlayerPokemon[]): Modifiers.P for (const p of party) { for (let m = 0; m < 3; m++) { const tierValue = Utils.randSeedInt(64); - const tier = tierValue > 25 ? ModifierTier.COMMON : tierValue > 12 ? ModifierTier.GREAT : tierValue > 4 ? ModifierTier.ULTRA : tierValue ? ModifierTier.ROGUE : ModifierTier.MASTER; + + let tier: ModifierTier; + if (tierValue > 25) { + tier = ModifierTier.COMMON; + } else if (tierValue > 12) { + tier = ModifierTier.GREAT; + } else if (tierValue > 4) { + tier = ModifierTier.ULTRA; + } else if (tierValue) { + tier = ModifierTier.ROGUE; + } else { + tier = ModifierTier.MASTER; + } + const modifier = getNewModifierTypeOption(party, ModifierPoolType.DAILY_STARTER, tier)?.type?.newModifier(p) as Modifiers.PokemonHeldItemModifier; ret.push(modifier); } @@ -2029,7 +2099,19 @@ function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, } } while (upgraded); } - tier = tierValue > 255 ? ModifierTier.COMMON : tierValue > 60 ? ModifierTier.GREAT : tierValue > 12 ? ModifierTier.ULTRA : tierValue ? ModifierTier.ROGUE : ModifierTier.MASTER; + + if (tierValue > 255) { + tier = ModifierTier.COMMON; + } else if (tierValue > 60) { + tier = ModifierTier.GREAT; + } else if (tierValue > 12) { + tier = ModifierTier.ULTRA; + } else if (tierValue) { + tier = ModifierTier.ROGUE; + } else { + tier = ModifierTier.MASTER; + } + tier += upgradeCount; while (tier && (!modifierPool.hasOwnProperty(tier) || !modifierPool[tier].length)) { tier--; @@ -2122,6 +2204,19 @@ export function getLuckString(luckValue: integer): string { } export function getLuckTextTint(luckValue: integer): integer { - const modifierTier = luckValue ? luckValue > 2 ? luckValue > 5 ? luckValue > 9 ? luckValue > 11 ? ModifierTier.LUXURY : ModifierTier.MASTER : ModifierTier.ROGUE : ModifierTier.ULTRA : ModifierTier.GREAT : ModifierTier.COMMON; + let modifierTier: ModifierTier; + if (luckValue > 11) { + modifierTier = ModifierTier.LUXURY; + } else if (luckValue > 9) { + modifierTier = ModifierTier.MASTER; + } else if (luckValue > 5) { + modifierTier = ModifierTier.ROGUE; + } else if (luckValue > 2) { + modifierTier = ModifierTier.ULTRA; + } else if (luckValue) { + modifierTier = ModifierTier.GREAT; + } else { + modifierTier = ModifierTier.COMMON; + } return getModifierTierTextTint(modifierTier); } diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index c6871353a7d..93ea067e424 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -2734,30 +2734,29 @@ export class EnemyFusionChanceModifier extends EnemyPersistentModifier { } /** - * Uses override from overrides.ts to set PersistentModifiers for starting a new game - * @param scene current BattleScene - * @param player is this for player for enemy + * Uses either `MODIFIER_OVERRIDE` in overrides.ts to set {@linkcode PersistentModifier}s for either: + * - The player + * - The enemy + * @param scene current {@linkcode BattleScene} + * @param isPlayer {@linkcode boolean} for whether the the player (`true`) or enemy (`false`) is being overridden */ -export function overrideModifiers(scene: BattleScene, player: boolean = true): void { - const modifierOverride = player ? Overrides.STARTING_MODIFIER_OVERRIDE : Overrides.OPP_MODIFIER_OVERRIDE; - if (!modifierOverride || modifierOverride.length === 0 || !scene) { +export function overrideModifiers(scene: BattleScene, isPlayer: boolean = true): void { + const modifiersOverride: ModifierTypes.ModifierOverride[] = isPlayer ? Overrides.STARTING_MODIFIER_OVERRIDE : Overrides.OPP_MODIFIER_OVERRIDE; + if (!modifiersOverride || modifiersOverride.length === 0 || !scene) { return; - } // if no override, do nothing - // if it's the opponent, we clear all his current modifiers to avoid stacking - if (!player) { + } + + // If it's the opponent, clear all of their current modifiers to avoid stacking + if (!isPlayer) { scene.clearEnemyModifiers(); } - // we loop through all the modifier name given in the override file - modifierOverride.forEach(item => { - const modifierName = item.name; - const qty = item.count || 1; - if (!modifierTypes.hasOwnProperty(modifierName)) { - return; - } // if the modifier does not exist, we skip it - const modifierType: ModifierType = modifierTypes[modifierName](); - const modifier: PersistentModifier = modifierType.withIdFromFunc(modifierTypes[modifierName]).newModifier() as PersistentModifier; - modifier.stackCount = qty; - if (player) { + + modifiersOverride.forEach(item => { + const modifierFunc = modifierTypes[item.name]; + const modifier = modifierFunc().withIdFromFunc(modifierFunc).newModifier() as PersistentModifier; + modifier.stackCount = item.count || 1; + + if (isPlayer) { scene.addModifier(modifier, true, false, false, true); } else { scene.addEnemyModifier(modifier, true, true); @@ -2766,37 +2765,38 @@ export function overrideModifiers(scene: BattleScene, player: boolean = true): v } /** - * Uses override from overrides.ts to set PokemonHeldItemModifiers for starting a new game - * @param scene current BattleScene - * @param player is this for player for enemy + * Uses either `HELD_ITEMS_OVERRIDE` in overrides.ts to set {@linkcode PokemonHeldItemModifier}s for either: + * - The first member of the player's team when starting a new game + * - An enemy {@linkcode Pokemon} being spawned in + * @param scene current {@linkcode BattleScene} + * @param pokemon {@linkcode Pokemon} whose held items are being overridden + * @param isPlayer {@linkcode boolean} for whether the {@linkcode pokemon} is the player's (`true`) or an enemy (`false`) */ -export function overrideHeldItems(scene: BattleScene, pokemon: Pokemon, player: boolean = true): void { - const heldItemsOverride = player ? Overrides.STARTING_HELD_ITEMS_OVERRIDE : Overrides.OPP_HELD_ITEMS_OVERRIDE; +export function overrideHeldItems(scene: BattleScene, pokemon: Pokemon, isPlayer: boolean = true): void { + const heldItemsOverride: ModifierTypes.ModifierOverride[] = isPlayer ? Overrides.STARTING_HELD_ITEMS_OVERRIDE : Overrides.OPP_HELD_ITEMS_OVERRIDE; if (!heldItemsOverride || heldItemsOverride.length === 0 || !scene) { return; - } // if no override, do nothing - // we loop through all the itemName given in the override file + } + heldItemsOverride.forEach(item => { - const itemName = item.name; + const modifierFunc = modifierTypes[item.name]; + let modifierType: ModifierType | null = modifierFunc(); const qty = item.count || 1; - if (!modifierTypes.hasOwnProperty(itemName)) { - return; - } // if the item does not exist, we skip it - const modifierType: ModifierType = modifierTypes[itemName](); // we retrieve the item in the list - let itemModifier: PokemonHeldItemModifier; + if (modifierType instanceof ModifierTypes.ModifierTypeGenerator) { - const pregenArgs = "type" in item ? [item.type] : undefined; - itemModifier = modifierType.generateType([], pregenArgs)?.withIdFromFunc(modifierTypes[itemName]).newModifier(pokemon) as PokemonHeldItemModifier; - } else { - itemModifier = modifierType.withIdFromFunc(modifierTypes[itemName]).newModifier(pokemon) as PokemonHeldItemModifier; + const pregenArgs = ("type" in item) && (item.type !== null) ? [item.type] : undefined; + modifierType = modifierType.generateType([], pregenArgs); } - // we create the item - itemModifier.pokemonId = pokemon.id; // we assign the created item to the pokemon - itemModifier.stackCount = qty; // we say how many items we want - if (player) { - scene.addModifier(itemModifier, true, false, false, true); - } else { - scene.addEnemyModifier(itemModifier, true, true); + + const heldItemModifier = modifierType && modifierType.withIdFromFunc(modifierFunc).newModifier(pokemon) as PokemonHeldItemModifier; + if (heldItemModifier) { + heldItemModifier.pokemonId = pokemon.id; + heldItemModifier.stackCount = qty; + if (isPlayer) { + scene.addModifier(heldItemModifier, true, false, false, true); + } else { + scene.addEnemyModifier(heldItemModifier, true, true); + } } }); } diff --git a/src/overrides.ts b/src/overrides.ts index 88db105475c..8b3d628e05e 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -12,7 +12,7 @@ import { type PokeballCounts } from "./battle-scene"; import { Gender } from "./data/gender"; import { allSpecies } from "./data/pokemon-species"; // eslint-disable-line @typescript-eslint/no-unused-vars import { Variant } from "./data/variant"; -import { type ModifierOverride, type ModifierTypeKeys } from "./modifier/modifier-type"; +import { type ModifierOverride } from "./modifier/modifier-type"; /** * Overrides that are using when testing different in game situations @@ -51,6 +51,7 @@ class DefaultOverrides { readonly ARENA_TINT_OVERRIDE: TimeOfDay | null = null; /** Multiplies XP gained by this value including 0. Set to null to ignore the override */ readonly XP_MULTIPLIER_OVERRIDE: number | null = null; + readonly NEVER_CRIT_OVERRIDE: boolean = false; /** default 1000 */ readonly STARTING_MONEY_OVERRIDE: integer = 0; /** Sets all shop item prices to 0 */ @@ -155,20 +156,28 @@ class DefaultOverrides { * STARTING_HELD_ITEM_OVERRIDE = [{name: "BERRY"}] * ``` */ - readonly STARTING_MODIFIER_OVERRIDE: Array = []; - readonly OPP_MODIFIER_OVERRIDE: Array = []; + readonly STARTING_MODIFIER_OVERRIDE: ModifierOverride[] = []; + /** + * Override array of {@linkcode ModifierOverride}s used to provide modifiers to enemies. + * + * Note that any previous modifiers are cleared. + */ + readonly OPP_MODIFIER_OVERRIDE: ModifierOverride[] = []; - readonly STARTING_HELD_ITEMS_OVERRIDE: Array = []; - readonly OPP_HELD_ITEMS_OVERRIDE: Array = []; - readonly NEVER_CRIT_OVERRIDE: boolean = false; + /** Override array of {@linkcode ModifierOverride}s used to provide held items to first party member when starting a new game. */ + readonly STARTING_HELD_ITEMS_OVERRIDE: ModifierOverride[] = []; + /** Override array of {@linkcode ModifierOverride}s used to provide held items to enemies on spawn. */ + readonly OPP_HELD_ITEMS_OVERRIDE: ModifierOverride[] = []; /** - * An array of items by keys as defined in the "modifierTypes" object in the "modifier/modifier-type.ts" file. - * Items listed will replace the normal rolls. - * If less items are listed than rolled, only some items will be replaced - * If more items are listed than rolled, only the first X items will be shown, where X is the number of items rolled. + * 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. + * + * Note that, for all items in the array, `count` is not used. */ - readonly ITEM_REWARD_OVERRIDE: Array = []; + readonly ITEM_REWARD_OVERRIDE: ModifierOverride[] = []; } export const defaultOverrides = new DefaultOverrides();