From 5c9697e36a10942a3187876e25270dc409a1f18a Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Sun, 8 Jun 2025 19:03:41 -0500 Subject: [PATCH] [WIP] move modifier stuff around --- src/@types/modifier-types.ts | 32 + src/battle-scene.ts | 4 +- src/battle.ts | 2 +- src/data/balance/tms.ts | 2 +- src/data/challenge.ts | 2 +- src/data/moves/move.ts | 2 +- .../encounters/a-trainers-test-encounter.ts | 2 +- .../encounters/berries-abound-encounter.ts | 3 +- .../encounters/bug-type-superfan-encounter.ts | 2 +- .../encounters/clowning-around-encounter.ts | 5 +- .../department-store-sale-encounter.ts | 2 +- .../encounters/fight-or-flight-encounter.ts | 4 +- .../global-trade-system-encounter.ts | 4 +- .../mysterious-challengers-encounter.ts | 2 +- .../encounters/mysterious-chest-encounter.ts | 2 +- .../the-winstrate-challenge-encounter.ts | 2 +- .../encounters/trash-to-treasure-encounter.ts | 2 +- .../encounters/weird-dream-encounter.ts | 2 +- .../utils/encounter-phase-utils.ts | 2 +- src/data/pokeball.ts | 3 +- src/data/trainers/trainer-config.ts | 2 +- src/enums/modifier-pool-type.ts | 7 + src/{modifier => enums}/modifier-tier.ts | 0 src/field/pokemon.ts | 2 +- src/loading-scene.ts | 3 + src/modifier/init-modifier-pools.ts | 853 ++++++++++++++++++ src/modifier/init-modifier-types.ts | 0 src/modifier/modifier-pools.ts | 16 + src/modifier/modifier-type.ts | 819 +---------------- src/modifier/modifier-types.ts | 22 + src/modifier/modifier.ts | 109 +++ src/phases/add-enemy-buff-modifier-phase.ts | 4 +- src/phases/encounter-phase.ts | 3 +- src/phases/modifier-reward-phase.ts | 3 +- src/phases/ribbon-modifier-reward-phase.ts | 2 +- src/phases/select-modifier-phase.ts | 4 +- src/phases/title-phase.ts | 2 +- src/ui/text.ts | 2 +- src/utils/modifier-pool-utils.ts | 24 + test/items/lock_capsule.test.ts | 2 +- .../clowning-around-encounter.test.ts | 2 +- .../global-trade-system-encounter.test.ts | 2 +- .../mysterious-challengers-encounter.test.ts | 2 +- .../trash-to-treasure-encounter.test.ts | 2 +- .../encounters/weird-dream-encounter.test.ts | 2 +- test/phases/select-modifier-phase.test.ts | 2 +- 46 files changed, 1123 insertions(+), 849 deletions(-) create mode 100644 src/@types/modifier-types.ts create mode 100644 src/enums/modifier-pool-type.ts rename src/{modifier => enums}/modifier-tier.ts (100%) create mode 100644 src/modifier/init-modifier-pools.ts create mode 100644 src/modifier/init-modifier-types.ts create mode 100644 src/modifier/modifier-pools.ts create mode 100644 src/modifier/modifier-types.ts create mode 100644 src/utils/modifier-pool-utils.ts diff --git a/src/@types/modifier-types.ts b/src/@types/modifier-types.ts new file mode 100644 index 00000000000..6c0136e655e --- /dev/null +++ b/src/@types/modifier-types.ts @@ -0,0 +1,32 @@ +/** + * Re-exports of all the types defined in the modifier module. + */ + +import type Pokemon from "#app/field/pokemon"; +import type { ModifierConstructorMap } from "#app/modifier/modifier"; +import type { ModifierType, WeightedModifierType } from "#app/modifier/modifier-type"; +export type ModifierTypeFunc = () => ModifierType; +export type WeightedModifierTypeWeightFunc = (party: Pokemon[], rerollCount?: number) => number; + +export type { ModifierConstructorMap } from "#app/modifier/modifier"; + +/** + * Map of modifier names to their respective instance types + */ +export type ModifierInstanceMap = { + [K in keyof ModifierConstructorMap]: InstanceType; +}; + +/** + * Union type of all modifier constructors. + */ +export type ModifierClass = ModifierConstructorMap[keyof ModifierConstructorMap]; + +/** + * Union type of all modifier names as strings. + */ +export type ModifierString = keyof ModifierConstructorMap; + +export type ModifierPool = { + [tier: string]: WeightedModifierType[]; +} \ No newline at end of file diff --git a/src/battle-scene.ts b/src/battle-scene.ts index e4669a6ec3a..2a1d680e0ad 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -58,13 +58,13 @@ import { getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, - getModifierPoolForType, getModifierType, getPartyLuckValue, - ModifierPoolType, modifierTypes, PokemonHeldItemModifierType, } from "#app/modifier/modifier-type"; +import { getModifierPoolForType } from "./utils/modifier-pool-utils"; +import { ModifierPoolType } from "#enums/modifier-pool-type"; import AbilityBar from "#app/ui/ability-bar"; import { applyAbAttrs, applyPostBattleInitAbAttrs, applyPostItemLostAbAttrs } from "./data/abilities/apply-ab-attrs"; import { allAbilities } from "./data/data-lists"; diff --git a/src/battle.ts b/src/battle.ts index 0cf01a0873d..229e769c622 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -30,7 +30,7 @@ import i18next from "#app/plugins/i18n"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import type { CustomModifierSettings } from "#app/modifier/modifier-type"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { BattleType } from "#enums/battle-type"; import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves"; diff --git a/src/data/balance/tms.ts b/src/data/balance/tms.ts index 2b0e8f5142d..e194dc4040c 100644 --- a/src/data/balance/tms.ts +++ b/src/data/balance/tms.ts @@ -1,4 +1,4 @@ -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 8bdccb6d5fd..683fb48a9ba 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -21,7 +21,7 @@ import { TrainerType } from "#enums/trainer-type"; import { Nature } from "#enums/nature"; import type { MoveId } from "#enums/move-id"; import { TypeColor, TypeShadow } from "#enums/color"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { globalScene } from "#app/global-scene"; import { pokemonFormChanges } from "./pokemon-forms"; import { pokemonEvolutions } from "./balance/pokemon-evolutions"; diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 374c18ed4de..e713020cf9c 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -51,7 +51,7 @@ import { import type { BattlerIndex } from "#enums/battler-index"; import { BattleType } from "#enums/battle-type"; import { TerrainType } from "../terrain"; -import { ModifierPoolType } from "#app/modifier/modifier-type"; +import { ModifierPoolType } from "#enums/modifier-pool-type"; import { Command } from "#enums/command"; import i18next from "i18next"; import type { Localizable } from "#app/@types/locales"; diff --git a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts index 11081892205..2f2b5686ae1 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -19,7 +19,7 @@ import i18next from "i18next"; import type { IEggOptions } from "#app/data/egg"; import { EggSourceType } from "#enums/egg-source-types"; import { EggTier } from "#enums/egg-type"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { modifierTypes } from "#app/modifier/modifier-type"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; diff --git a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts index 65ae3ea6c4f..eff055a5767 100644 --- a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts +++ b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts @@ -12,7 +12,8 @@ import { import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import type { BerryModifierType, ModifierTypeOption } from "#app/modifier/modifier-type"; -import { ModifierPoolType, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; +import { modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; +import { ModifierPoolType } from "#enums/modifier-pool-type"; import { randSeedInt } from "#app/utils/common"; import { BattlerTagType } from "#enums/battler-tag-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index c318efc0cb3..1ebd72d9f03 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -50,7 +50,7 @@ import { import i18next from "i18next"; import MoveInfoOverlay from "#app/ui/move-info-overlay"; import { allMoves } from "#app/data/data-lists"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index ac92e94520d..7ae18ad0dd4 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -11,9 +11,10 @@ import { import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; -import { ModifierPoolType, modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/modifier/modifier-type"; +import { ModifierPoolType } from "#enums/modifier-pool-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; import { globalScene } from "#app/global-scene"; diff --git a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts index 2b6ac9b7cf3..fb874691aa0 100644 --- a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts +++ b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts @@ -2,7 +2,7 @@ import { leaveEncounterWithoutBattle, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import type { ModifierTypeFunc } from "#app/modifier/modifier-type"; +import type { ModifierTypeFunc } from "#app/@types/modifier-types"; import { modifierTypes } from "#app/modifier/modifier-type"; import { randSeedInt } from "#app/utils/common"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; diff --git a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts index 83538e9e0e9..f925452e143 100644 --- a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts +++ b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts @@ -9,13 +9,13 @@ import { } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import type Pokemon from "#app/field/pokemon"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import type { ModifierTypeOption } from "#app/modifier/modifier-type"; import { getPlayerModifierTypeOptions, - ModifierPoolType, regenerateModifierPoolThresholds, } from "#app/modifier/modifier-type"; +import { ModifierPoolType } from "#enums/modifier-pool-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index a84b0af30ed..1b188915de7 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -4,14 +4,14 @@ import { setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { TrainerSlot } from "#enums/trainer-slot"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { MusicPreference } from "#app/system/settings/settings"; import type { ModifierTypeOption } from "#app/modifier/modifier-type"; import { getPlayerModifierTypeOptions, - ModifierPoolType, regenerateModifierPoolThresholds, } from "#app/modifier/modifier-type"; +import { ModifierPoolType } from "#enums/modifier-pool-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; diff --git a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts index 6907e18cfdc..d19998cc051 100644 --- a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts @@ -7,7 +7,7 @@ import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { trainerPartyTemplates } from "#app/data/trainers/TrainerPartyTemplate"; import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { modifierTypes } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; diff --git a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts index 62029eb1847..022b0125fde 100644 --- a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts @@ -16,7 +16,7 @@ import { } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { randSeedInt } from "#app/utils/common"; import { MoveId } from "#enums/move-id"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; diff --git a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts index 7afc6c1a784..336e4941ef7 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -28,7 +28,7 @@ import { applyPostBattleInitAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import i18next from "i18next"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { BattlerTagType } from "#enums/battler-tag-type"; diff --git a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts index 8bcd024de5c..18a84bd6bd2 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -21,7 +21,7 @@ import { HitHealModifier, PokemonHeldItemModifier, TurnHealModifier } from "#app import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import i18next from "#app/plugins/i18n"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { MoveId } from "#enums/move-id"; import { BattlerIndex } from "#enums/battler-index"; diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index fe1911c9007..3f28a9f2f3f 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -34,7 +34,7 @@ import { import { getLevelTotalExp } from "#app/data/exp"; import { Stat } from "#enums/stat"; import { Challenges } from "#enums/challenges"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { PlayerGender } from "#enums/player-gender"; import { TrainerType } from "#enums/trainer-type"; import PokemonData from "#app/system/pokemon-data"; diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 69984229681..85d389254d5 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -17,12 +17,12 @@ import { FieldPosition } from "#enums/field-position"; import type { CustomModifierSettings, ModifierType } from "#app/modifier/modifier-type"; import { getPartyLuckValue, - ModifierPoolType, ModifierTypeGenerator, ModifierTypeOption, modifierTypes, regenerateModifierPoolThresholds, } from "#app/modifier/modifier-type"; +import { ModifierPoolType } from "#enums/modifier-pool-type"; import type PokemonData from "#app/system/pokemon-data"; import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler"; diff --git a/src/data/pokeball.ts b/src/data/pokeball.ts index 7a44ebdda7c..a479fd8068a 100644 --- a/src/data/pokeball.ts +++ b/src/data/pokeball.ts @@ -1,5 +1,4 @@ import { globalScene } from "#app/global-scene"; -import { CriticalCatchChanceBoosterModifier } from "#app/modifier/modifier"; import { NumberHolder } from "#app/utils/common"; import { PokeballType } from "#enums/pokeball"; import i18next from "i18next"; @@ -94,7 +93,7 @@ export function getCriticalCaptureChance(modifiedCatchRate: number): number { } const dexCount = globalScene.gameData.getSpeciesCount(d => !!d.caughtAttr); const catchingCharmMultiplier = new NumberHolder(1); - globalScene.findModifier(m => m instanceof CriticalCatchChanceBoosterModifier)?.apply(catchingCharmMultiplier); + globalScene.findModifier(m => m.is("CriticalCatchChanceBoosterModifier"))?.apply(catchingCharmMultiplier); const dexMultiplier = globalScene.gameMode.isDaily || dexCount > 800 ? 2.5 diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 2f1f7ed07a8..f551015240a 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -37,7 +37,7 @@ import { timedEventManager } from "#app/global-event-manager"; // Type imports import type { PokemonSpeciesFilter } from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species"; -import type { ModifierTypeFunc } from "#app/modifier/modifier-type"; +import type { ModifierTypeFunc } from "#app/@types/modifier-types"; import type { EnemyPokemon } from "#app/field/pokemon"; import type { EvilTeam } from "./evil-admin-trainer-pools"; import type { diff --git a/src/enums/modifier-pool-type.ts b/src/enums/modifier-pool-type.ts new file mode 100644 index 00000000000..0d2b92ba80d --- /dev/null +++ b/src/enums/modifier-pool-type.ts @@ -0,0 +1,7 @@ +export enum ModifierPoolType { + PLAYER, + WILD, + TRAINER, + ENEMY_BUFF, + DAILY_STARTER +} diff --git a/src/modifier/modifier-tier.ts b/src/enums/modifier-tier.ts similarity index 100% rename from src/modifier/modifier-tier.ts rename to src/enums/modifier-tier.ts diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 77f34409dfe..b7f27be31b2 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -154,7 +154,7 @@ import type { TrainerSlot } from "#enums/trainer-slot"; import Overrides from "#app/overrides"; import i18next from "i18next"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { applyChallenges } from "#app/data/challenge"; import { ChallengeType } from "#enums/challenge-type"; import { AbilityId } from "#enums/ability-id"; diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 67ca9a28bc5..17cd5c277f1 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -21,6 +21,7 @@ import { initVouchers } from "#app/system/voucher"; import { BiomeId } from "#enums/biome-id"; import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters"; import { timedEventManager } from "./global-event-manager"; +import { initModifierPools } from "./modifier/init-modifier-pools"; export class LoadingScene extends SceneBase { public static readonly KEY = "loading"; @@ -363,6 +364,8 @@ export class LoadingScene extends SceneBase { this.loadLoadingScreen(); + initModifierPools(); + initAchievements(); initVouchers(); initStatsKeys(); diff --git a/src/modifier/init-modifier-pools.ts b/src/modifier/init-modifier-pools.ts new file mode 100644 index 00000000000..11836f522d0 --- /dev/null +++ b/src/modifier/init-modifier-pools.ts @@ -0,0 +1,853 @@ +import type Pokemon from "#app/field/pokemon"; +import { + dailyStarterModifierPool, + enemyBuffModifierPool, + modifierPool, + trainerModifierPool, + wildModifierPool, +} from "#app/modifier/modifier-pools"; +import { globalScene } from "#app/global-scene"; +import { + DoubleBattleChanceBoosterModifier, + ResetNegativeStatStageModifier, + SpeciesCritBoosterModifier, + TurnStatusEffectModifier, +} from "./modifier"; +import { WeightedModifierType } from "./modifier-type"; +import { ModifierTier } from "../enums/modifier-tier"; +import type { WeightedModifierTypeWeightFunc } from "#app/@types/modifier-types"; +import { modifierTypes } from "./modifier-type"; +import { PokeballType } from "#enums/pokeball"; +import { BerryModifier } from "./modifier"; +import { BerryType } from "#enums/berry-type"; +import { SpeciesId } from "#enums/species-id"; +import { timedEventManager } from "#app/global-event-manager"; +import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; +import { Unlockables } from "#app/system/unlockables"; +import { isNullOrUndefined } from "#app/utils/common"; +import { MoveId } from "#enums/move-id"; +import { StatusEffect } from "#enums/status-effect"; +import { AbilityId } from "#enums/ability-id"; +import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; + +/** + * Initialize the wild modifier pool + */ +function initWildModifierPool() { + wildModifierPool[ModifierTier.COMMON] = [new WeightedModifierType(modifierTypes.BERRY, 1)].map(m => { + m.setTier(ModifierTier.COMMON); + return m; + }); + wildModifierPool[ModifierTier.GREAT] = [new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 1)].map(m => { + m.setTier(ModifierTier.GREAT); + return m; + }); + wildModifierPool[ModifierTier.ULTRA] = [ + new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10), + new WeightedModifierType(modifierTypes.WHITE_HERB, 0), + ].map(m => { + m.setTier(ModifierTier.ULTRA); + return m; + }); + wildModifierPool[ModifierTier.ROGUE] = [new WeightedModifierType(modifierTypes.LUCKY_EGG, 4)].map(m => { + m.setTier(ModifierTier.ROGUE); + return m; + }); + wildModifierPool[ModifierTier.MASTER] = [new WeightedModifierType(modifierTypes.GOLDEN_EGG, 1)].map(m => { + m.setTier(ModifierTier.MASTER); + return m; + }); +} + +/** + * Initialize the common modifier pool + */ +function initCommonModifierPool() { + modifierPool[ModifierTier.COMMON] = [ + new WeightedModifierType(modifierTypes.POKEBALL, () => (hasMaximumBalls(PokeballType.POKEBALL) ? 0 : 6), 6), + new WeightedModifierType(modifierTypes.RARE_CANDY, 2), + new WeightedModifierType( + modifierTypes.POTION, + (party: Pokemon[]) => { + const thresholdPartyMemberCount = Math.min( + party.filter(p => p.getInverseHp() >= 10 && p.getHpRatio() <= 0.875 && !p.isFainted()).length, + 3, + ); + return thresholdPartyMemberCount * 3; + }, + 9, + ), + new WeightedModifierType( + modifierTypes.SUPER_POTION, + (party: Pokemon[]) => { + const thresholdPartyMemberCount = Math.min( + party.filter(p => p.getInverseHp() >= 25 && p.getHpRatio() <= 0.75 && !p.isFainted()).length, + 3, + ); + return thresholdPartyMemberCount; + }, + 3, + ), + new WeightedModifierType( + modifierTypes.ETHER, + (party: Pokemon[]) => { + const thresholdPartyMemberCount = Math.min( + party.filter( + p => + p.hp && + !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && + p + .getMoveset() + .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) + .length, + ).length, + 3, + ); + return thresholdPartyMemberCount * 3; + }, + 9, + ), + new WeightedModifierType( + modifierTypes.MAX_ETHER, + (party: Pokemon[]) => { + const thresholdPartyMemberCount = Math.min( + party.filter( + p => + p.hp && + !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && + p + .getMoveset() + .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) + .length, + ).length, + 3, + ); + return thresholdPartyMemberCount; + }, + 3, + ), + new WeightedModifierType(modifierTypes.LURE, lureWeightFunc(10, 2)), + new WeightedModifierType(modifierTypes.TEMP_STAT_STAGE_BOOSTER, 4), + new WeightedModifierType(modifierTypes.BERRY, 2), + new WeightedModifierType(modifierTypes.TM_COMMON, 2), + ].map(m => { + m.setTier(ModifierTier.COMMON); + return m; + }); +} + +/** + * Initialize the Great modifier pool + */ +function initGreatModifierPool() { + modifierPool[ModifierTier.GREAT] = [ + new WeightedModifierType(modifierTypes.GREAT_BALL, () => (hasMaximumBalls(PokeballType.GREAT_BALL) ? 0 : 6), 6), + new WeightedModifierType(modifierTypes.PP_UP, 2), + new WeightedModifierType( + modifierTypes.FULL_HEAL, + (party: Pokemon[]) => { + const statusEffectPartyMemberCount = Math.min( + party.filter( + p => + p.hp && + !!p.status && + !p.getHeldItems().some(i => { + if (i instanceof TurnStatusEffectModifier) { + return (i as TurnStatusEffectModifier).getStatusEffect() === p.status?.effect; + } + return false; + }), + ).length, + 3, + ); + return statusEffectPartyMemberCount * 6; + }, + 18, + ), + new WeightedModifierType( + modifierTypes.REVIVE, + (party: Pokemon[]) => { + const faintedPartyMemberCount = Math.min(party.filter(p => p.isFainted()).length, 3); + return faintedPartyMemberCount * 9; + }, + 27, + ), + new WeightedModifierType( + modifierTypes.MAX_REVIVE, + (party: Pokemon[]) => { + const faintedPartyMemberCount = Math.min(party.filter(p => p.isFainted()).length, 3); + return faintedPartyMemberCount * 3; + }, + 9, + ), + new WeightedModifierType( + modifierTypes.SACRED_ASH, + (party: Pokemon[]) => { + return party.filter(p => p.isFainted()).length >= Math.ceil(party.length / 2) ? 1 : 0; + }, + 1, + ), + new WeightedModifierType( + modifierTypes.HYPER_POTION, + (party: Pokemon[]) => { + const thresholdPartyMemberCount = Math.min( + party.filter(p => p.getInverseHp() >= 100 && p.getHpRatio() <= 0.625 && !p.isFainted()).length, + 3, + ); + return thresholdPartyMemberCount * 3; + }, + 9, + ), + new WeightedModifierType( + modifierTypes.MAX_POTION, + (party: Pokemon[]) => { + const thresholdPartyMemberCount = Math.min( + party.filter(p => p.getInverseHp() >= 100 && p.getHpRatio() <= 0.5 && !p.isFainted()).length, + 3, + ); + return thresholdPartyMemberCount; + }, + 3, + ), + new WeightedModifierType( + modifierTypes.FULL_RESTORE, + (party: Pokemon[]) => { + const statusEffectPartyMemberCount = Math.min( + party.filter( + p => + p.hp && + !!p.status && + !p.getHeldItems().some(i => { + if (i instanceof TurnStatusEffectModifier) { + return (i as TurnStatusEffectModifier).getStatusEffect() === p.status?.effect; + } + return false; + }), + ).length, + 3, + ); + const thresholdPartyMemberCount = Math.floor( + (Math.min(party.filter(p => p.getInverseHp() >= 100 && p.getHpRatio() <= 0.5 && !p.isFainted()).length, 3) + + statusEffectPartyMemberCount) / + 2, + ); + return thresholdPartyMemberCount; + }, + 3, + ), + new WeightedModifierType( + modifierTypes.ELIXIR, + (party: Pokemon[]) => { + const thresholdPartyMemberCount = Math.min( + party.filter( + p => + p.hp && + !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && + p + .getMoveset() + .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) + .length, + ).length, + 3, + ); + return thresholdPartyMemberCount * 3; + }, + 9, + ), + new WeightedModifierType( + modifierTypes.MAX_ELIXIR, + (party: Pokemon[]) => { + const thresholdPartyMemberCount = Math.min( + party.filter( + p => + p.hp && + !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && + p + .getMoveset() + .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) + .length, + ).length, + 3, + ); + return thresholdPartyMemberCount; + }, + 3, + ), + new WeightedModifierType(modifierTypes.DIRE_HIT, 4), + new WeightedModifierType(modifierTypes.SUPER_LURE, lureWeightFunc(15, 4)), + new WeightedModifierType(modifierTypes.NUGGET, skipInLastClassicWaveOrDefault(5)), + new WeightedModifierType(modifierTypes.SPECIES_STAT_BOOSTER, 4), + new WeightedModifierType( + modifierTypes.EVOLUTION_ITEM, + () => { + return Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 15), 8); + }, + 8, + ), + new WeightedModifierType( + modifierTypes.MAP, + () => (globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex < 180 ? 2 : 0), + 2, + ), + new WeightedModifierType(modifierTypes.SOOTHE_BELL, 2), + new WeightedModifierType(modifierTypes.TM_GREAT, 3), + new WeightedModifierType( + modifierTypes.MEMORY_MUSHROOM, + (party: Pokemon[]) => { + if (!party.find(p => p.getLearnableLevelMoves().length)) { + return 0; + } + const highestPartyLevel = party + .map(p => p.level) + .reduce((highestLevel: number, level: number) => Math.max(highestLevel, level), 1); + return Math.min(Math.ceil(highestPartyLevel / 20), 4); + }, + 4, + ), + new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3), + new WeightedModifierType(modifierTypes.TERA_SHARD, (party: Pokemon[]) => + party.filter( + p => + !(p.hasSpecies(SpeciesId.TERAPAGOS) || p.hasSpecies(SpeciesId.OGERPON) || p.hasSpecies(SpeciesId.SHEDINJA)), + ).length > 0 + ? 1 + : 0, + ), + new WeightedModifierType( + modifierTypes.DNA_SPLICERS, + (party: Pokemon[]) => { + if (party.filter(p => !p.fusionSpecies).length > 1) { + if (globalScene.gameMode.isSplicedOnly) { + return 4; + } + if (globalScene.gameMode.isClassic && timedEventManager.areFusionsBoosted()) { + return 2; + } + } + return 0; + }, + 4, + ), + new WeightedModifierType( + modifierTypes.VOUCHER, + (_party: Pokemon[], rerollCount: number) => (!globalScene.gameMode.isDaily ? Math.max(1 - rerollCount, 0) : 0), + 1, + ), + ].map(m => { + m.setTier(ModifierTier.GREAT); + return m; + }); +} + +/** + * Initialize the Ultra modifier pool + */ +function initUltraModifierPool() { + modifierPool[ModifierTier.ULTRA] = [ + new WeightedModifierType(modifierTypes.ULTRA_BALL, () => (hasMaximumBalls(PokeballType.ULTRA_BALL) ? 0 : 15), 15), + new WeightedModifierType(modifierTypes.MAX_LURE, lureWeightFunc(30, 4)), + new WeightedModifierType(modifierTypes.BIG_NUGGET, skipInLastClassicWaveOrDefault(12)), + new WeightedModifierType(modifierTypes.PP_MAX, 3), + new WeightedModifierType(modifierTypes.MINT, 4), + new WeightedModifierType( + modifierTypes.RARE_EVOLUTION_ITEM, + () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 15) * 4, 32), + 32, + ), + new WeightedModifierType( + modifierTypes.FORM_CHANGE_ITEM, + () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 6, + 24, + ), + new WeightedModifierType(modifierTypes.AMULET_COIN, skipInLastClassicWaveOrDefault(3)), + new WeightedModifierType(modifierTypes.EVIOLITE, (party: Pokemon[]) => { + const { gameMode, gameData } = globalScene; + if (gameMode.isDaily || (!gameMode.isFreshStartChallenge() && gameData.isUnlocked(Unlockables.EVIOLITE))) { + return party.some(p => { + // Check if Pokemon's species (or fusion species, if applicable) can evolve or if they're G-Max'd + if ( + !p.isMax() && + (p.getSpeciesForm(true).speciesId in pokemonEvolutions || + (p.isFusion() && p.getFusionSpeciesForm(true).speciesId in pokemonEvolutions)) + ) { + // Check if Pokemon is already holding an Eviolite + return !p.getHeldItems().some(i => i.type.id === "EVIOLITE"); + } + return false; + }) + ? 10 + : 0; + } + return 0; + }), + new WeightedModifierType(modifierTypes.RARE_SPECIES_STAT_BOOSTER, 12), + new WeightedModifierType( + modifierTypes.LEEK, + (party: Pokemon[]) => { + const checkedSpecies = [SpeciesId.FARFETCHD, SpeciesId.GALAR_FARFETCHD, SpeciesId.SIRFETCHD]; + // If a party member doesn't already have a Leek and is one of the relevant species, Leek can appear + return party.some( + p => + !p.getHeldItems().some(i => i instanceof SpeciesCritBoosterModifier) && + (checkedSpecies.includes(p.getSpeciesForm(true).speciesId) || + (p.isFusion() && checkedSpecies.includes(p.getFusionSpeciesForm(true).speciesId))), + ) + ? 12 + : 0; + }, + 12, + ), + new WeightedModifierType( + modifierTypes.TOXIC_ORB, + (party: Pokemon[]) => { + return party.some(p => { + const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB"); + + if (!isHoldingOrb) { + const moveset = p + .getMoveset(true) + .filter(m => !isNullOrUndefined(m)) + .map(m => m.moveId); + const canSetStatus = p.canSetStatus(StatusEffect.TOXIC, true, true, null, true); + + // Moves that take advantage of obtaining the actual status effect + const hasStatusMoves = [MoveId.FACADE, MoveId.PSYCHO_SHIFT].some(m => moveset.includes(m)); + // Moves that take advantage of being able to give the target a status orb + // TODO: Take moves (Trick, Fling, Switcheroo) from comment when they are implemented + const hasItemMoves = [ + /* MoveId.TRICK, MoveId.FLING, MoveId.SWITCHEROO */ + ].some(m => moveset.includes(m)); + + if (canSetStatus) { + // Abilities that take advantage of obtaining the actual status effect, separated based on specificity to the orb + const hasGeneralAbility = [ + AbilityId.QUICK_FEET, + AbilityId.GUTS, + AbilityId.MARVEL_SCALE, + AbilityId.MAGIC_GUARD, + ].some(a => p.hasAbility(a, false, true)); + const hasSpecificAbility = [AbilityId.TOXIC_BOOST, AbilityId.POISON_HEAL].some(a => + p.hasAbility(a, false, true), + ); + const hasOppositeAbility = [AbilityId.FLARE_BOOST].some(a => p.hasAbility(a, false, true)); + + return hasSpecificAbility || (hasGeneralAbility && !hasOppositeAbility) || hasStatusMoves; + } + return hasItemMoves; + } + + return false; + }) + ? 10 + : 0; + }, + 10, + ), + new WeightedModifierType( + modifierTypes.FLAME_ORB, + (party: Pokemon[]) => { + return party.some(p => { + const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB"); + + if (!isHoldingOrb) { + const moveset = p + .getMoveset(true) + .filter(m => !isNullOrUndefined(m)) + .map(m => m.moveId); + const canSetStatus = p.canSetStatus(StatusEffect.BURN, true, true, null, true); + + // Moves that take advantage of obtaining the actual status effect + const hasStatusMoves = [MoveId.FACADE, MoveId.PSYCHO_SHIFT].some(m => moveset.includes(m)); + // Moves that take advantage of being able to give the target a status orb + // TODO: Take moves (Trick, Fling, Switcheroo) from comment when they are implemented + const hasItemMoves = [ + /* MoveId.TRICK, MoveId.FLING, MoveId.SWITCHEROO */ + ].some(m => moveset.includes(m)); + + if (canSetStatus) { + // Abilities that take advantage of obtaining the actual status effect, separated based on specificity to the orb + const hasGeneralAbility = [ + AbilityId.QUICK_FEET, + AbilityId.GUTS, + AbilityId.MARVEL_SCALE, + AbilityId.MAGIC_GUARD, + ].some(a => p.hasAbility(a, false, true)); + const hasSpecificAbility = [AbilityId.FLARE_BOOST].some(a => p.hasAbility(a, false, true)); + const hasOppositeAbility = [AbilityId.TOXIC_BOOST, AbilityId.POISON_HEAL].some(a => + p.hasAbility(a, false, true), + ); + + return hasSpecificAbility || (hasGeneralAbility && !hasOppositeAbility) || hasStatusMoves; + } + return hasItemMoves; + } + + return false; + }) + ? 10 + : 0; + }, + 10, + ), + new WeightedModifierType( + modifierTypes.MYSTICAL_ROCK, + (party: Pokemon[]) => { + return party.some(p => { + let isHoldingMax = false; + for (const i of p.getHeldItems()) { + if (i.type.id === "MYSTICAL_ROCK") { + isHoldingMax = i.getStackCount() === i.getMaxStackCount(); + break; + } + } + + if (!isHoldingMax) { + const moveset = p.getMoveset(true).map(m => m.moveId); + + const hasAbility = [ + AbilityId.DROUGHT, + AbilityId.ORICHALCUM_PULSE, + AbilityId.DRIZZLE, + AbilityId.SAND_STREAM, + AbilityId.SAND_SPIT, + AbilityId.SNOW_WARNING, + AbilityId.ELECTRIC_SURGE, + AbilityId.HADRON_ENGINE, + AbilityId.PSYCHIC_SURGE, + AbilityId.GRASSY_SURGE, + AbilityId.SEED_SOWER, + AbilityId.MISTY_SURGE, + ].some(a => p.hasAbility(a, false, true)); + + const hasMoves = [ + MoveId.SUNNY_DAY, + MoveId.RAIN_DANCE, + MoveId.SANDSTORM, + MoveId.SNOWSCAPE, + MoveId.HAIL, + MoveId.CHILLY_RECEPTION, + MoveId.ELECTRIC_TERRAIN, + MoveId.PSYCHIC_TERRAIN, + MoveId.GRASSY_TERRAIN, + MoveId.MISTY_TERRAIN, + ].some(m => moveset.includes(m)); + + return hasAbility || hasMoves; + } + return false; + }) + ? 10 + : 0; + }, + 10, + ), + new WeightedModifierType(modifierTypes.REVIVER_SEED, 4), + new WeightedModifierType(modifierTypes.CANDY_JAR, skipInLastClassicWaveOrDefault(5)), + new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 9), + new WeightedModifierType(modifierTypes.TM_ULTRA, 11), + new WeightedModifierType(modifierTypes.RARER_CANDY, 4), + new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, skipInLastClassicWaveOrDefault(2)), + new WeightedModifierType(modifierTypes.IV_SCANNER, skipInLastClassicWaveOrDefault(4)), + new WeightedModifierType(modifierTypes.EXP_CHARM, skipInLastClassicWaveOrDefault(8)), + new WeightedModifierType(modifierTypes.EXP_SHARE, skipInLastClassicWaveOrDefault(10)), + new WeightedModifierType( + modifierTypes.TERA_ORB, + () => + !globalScene.gameMode.isClassic + ? Math.min(Math.max(Math.floor(globalScene.currentBattle.waveIndex / 50) * 2, 1), 4) + : 0, + 4, + ), + new WeightedModifierType(modifierTypes.QUICK_CLAW, 3), + new WeightedModifierType(modifierTypes.WIDE_LENS, 7), + ].map(m => { + m.setTier(ModifierTier.ULTRA); + return m; + }); +} + +function initRogueModifierPool() { + modifierPool[ModifierTier.ROGUE] = [ + new WeightedModifierType(modifierTypes.ROGUE_BALL, () => (hasMaximumBalls(PokeballType.ROGUE_BALL) ? 0 : 16), 16), + new WeightedModifierType(modifierTypes.RELIC_GOLD, skipInLastClassicWaveOrDefault(2)), + new WeightedModifierType(modifierTypes.LEFTOVERS, 3), + new WeightedModifierType(modifierTypes.SHELL_BELL, 3), + new WeightedModifierType(modifierTypes.BERRY_POUCH, 4), + new WeightedModifierType(modifierTypes.GRIP_CLAW, 5), + new WeightedModifierType(modifierTypes.SCOPE_LENS, 4), + new WeightedModifierType(modifierTypes.BATON, 2), + new WeightedModifierType(modifierTypes.SOUL_DEW, 7), + new WeightedModifierType(modifierTypes.CATCHING_CHARM, () => (!globalScene.gameMode.isClassic ? 4 : 0), 4), + new WeightedModifierType(modifierTypes.ABILITY_CHARM, skipInClassicAfterWave(189, 6)), + new WeightedModifierType(modifierTypes.FOCUS_BAND, 5), + new WeightedModifierType(modifierTypes.KINGS_ROCK, 3), + new WeightedModifierType(modifierTypes.LOCK_CAPSULE, () => (globalScene.gameMode.isClassic ? 0 : 3)), + new WeightedModifierType(modifierTypes.SUPER_EXP_CHARM, skipInLastClassicWaveOrDefault(8)), + new WeightedModifierType( + modifierTypes.RARE_FORM_CHANGE_ITEM, + () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 6, + 24, + ), + new WeightedModifierType( + modifierTypes.MEGA_BRACELET, + () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 9, + 36, + ), + new WeightedModifierType( + modifierTypes.DYNAMAX_BAND, + () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 9, + 36, + ), + new WeightedModifierType( + modifierTypes.VOUCHER_PLUS, + (_party: Pokemon[], rerollCount: number) => + !globalScene.gameMode.isDaily ? Math.max(3 - rerollCount * 1, 0) : 0, + 3, + ), + ].map(m => { + m.setTier(ModifierTier.ROGUE); + return m; + }); +} + +/** + * Initialize the Master modifier pool + */ +function initMasterModifierPool() { + modifierPool[ModifierTier.MASTER] = [ + new WeightedModifierType(modifierTypes.MASTER_BALL, () => (hasMaximumBalls(PokeballType.MASTER_BALL) ? 0 : 24), 24), + new WeightedModifierType(modifierTypes.SHINY_CHARM, 14), + new WeightedModifierType(modifierTypes.HEALING_CHARM, 18), + new WeightedModifierType(modifierTypes.MULTI_LENS, 18), + new WeightedModifierType( + modifierTypes.VOUCHER_PREMIUM, + (_party: Pokemon[], rerollCount: number) => + !globalScene.gameMode.isDaily && !globalScene.gameMode.isEndless && !globalScene.gameMode.isSplicedOnly + ? Math.max(5 - rerollCount * 2, 0) + : 0, + 5, + ), + new WeightedModifierType( + modifierTypes.DNA_SPLICERS, + (party: Pokemon[]) => + !(globalScene.gameMode.isClassic && timedEventManager.areFusionsBoosted()) && + !globalScene.gameMode.isSplicedOnly && + party.filter(p => !p.fusionSpecies).length > 1 + ? 24 + : 0, + 24, + ), + new WeightedModifierType( + modifierTypes.MINI_BLACK_HOLE, + () => + globalScene.gameMode.isDaily || + (!globalScene.gameMode.isFreshStartChallenge() && globalScene.gameData.isUnlocked(Unlockables.MINI_BLACK_HOLE)) + ? 1 + : 0, + 1, + ), + ].map(m => { + m.setTier(ModifierTier.MASTER); + return m; + }); +} + +function initTrainerModifierPool() { + trainerModifierPool[ModifierTier.COMMON] = [ + new WeightedModifierType(modifierTypes.BERRY, 8), + new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3), + ].map(m => { + m.setTier(ModifierTier.COMMON); + return m; + }); + trainerModifierPool[ModifierTier.GREAT] = [new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3)].map(m => { + m.setTier(ModifierTier.GREAT); + return m; + }); + trainerModifierPool[ModifierTier.ULTRA] = [ + new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10), + new WeightedModifierType(modifierTypes.WHITE_HERB, 0), + ].map(m => { + m.setTier(ModifierTier.ULTRA); + return m; + }); + trainerModifierPool[ModifierTier.ROGUE] = [ + new WeightedModifierType(modifierTypes.FOCUS_BAND, 2), + new WeightedModifierType(modifierTypes.LUCKY_EGG, 4), + new WeightedModifierType(modifierTypes.QUICK_CLAW, 1), + new WeightedModifierType(modifierTypes.GRIP_CLAW, 1), + new WeightedModifierType(modifierTypes.WIDE_LENS, 1), + ].map(m => { + m.setTier(ModifierTier.ROGUE); + return m; + }); + trainerModifierPool[ModifierTier.MASTER] = [ + new WeightedModifierType(modifierTypes.KINGS_ROCK, 1), + new WeightedModifierType(modifierTypes.LEFTOVERS, 1), + new WeightedModifierType(modifierTypes.SHELL_BELL, 1), + new WeightedModifierType(modifierTypes.SCOPE_LENS, 1), + ].map(m => { + m.setTier(ModifierTier.MASTER); + return m; + }); +} + +/** + * Initialize the enemy buff modifier pool + */ +function initEnemyBuffModifierPool() { + enemyBuffModifierPool[ModifierTier.COMMON] = [ + new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 9), + new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 9), + new WeightedModifierType(modifierTypes.ENEMY_ATTACK_POISON_CHANCE, 3), + new WeightedModifierType(modifierTypes.ENEMY_ATTACK_PARALYZE_CHANCE, 3), + new WeightedModifierType(modifierTypes.ENEMY_ATTACK_BURN_CHANCE, 3), + new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 9), + new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 4), + new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 1), + ].map(m => { + m.setTier(ModifierTier.COMMON); + return m; + }); + enemyBuffModifierPool[ModifierTier.GREAT] = [ + new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 5), + new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 5), + new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 5), + new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 5), + new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 1), + ].map(m => { + m.setTier(ModifierTier.GREAT); + return m; + }); + enemyBuffModifierPool[ModifierTier.ULTRA] = [ + new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 10), + new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 10), + new WeightedModifierType(modifierTypes.ENEMY_HEAL, 10), + new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 10), + new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 10), + new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 5), + ].map(m => { + m.setTier(ModifierTier.ULTRA); + return m; + }); + enemyBuffModifierPool[ModifierTier.ROGUE] = [].map((m: WeightedModifierType) => { + m.setTier(ModifierTier.ROGUE); + return m; + }); + enemyBuffModifierPool[ModifierTier.MASTER] = [].map((m: WeightedModifierType) => { + m.setTier(ModifierTier.MASTER); + return m; + }); +} + +/** + * Initialize the daily starter modifier pool + */ +function initDailyStarterModifierPool() { + dailyStarterModifierPool[ModifierTier.COMMON] = [ + new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 1), + new WeightedModifierType(modifierTypes.BERRY, 3), + ].map(m => { + m.setTier(ModifierTier.COMMON); + return m; + }); + dailyStarterModifierPool[ModifierTier.GREAT] = [new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 5)].map( + m => { + m.setTier(ModifierTier.GREAT); + return m; + }, + ); + dailyStarterModifierPool[ModifierTier.ULTRA] = [ + new WeightedModifierType(modifierTypes.REVIVER_SEED, 4), + new WeightedModifierType(modifierTypes.SOOTHE_BELL, 1), + new WeightedModifierType(modifierTypes.SOUL_DEW, 1), + new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, 1), + ].map(m => { + m.setTier(ModifierTier.ULTRA); + return m; + }); + dailyStarterModifierPool[ModifierTier.ROGUE] = [ + new WeightedModifierType(modifierTypes.GRIP_CLAW, 5), + new WeightedModifierType(modifierTypes.BATON, 2), + new WeightedModifierType(modifierTypes.FOCUS_BAND, 5), + new WeightedModifierType(modifierTypes.QUICK_CLAW, 3), + new WeightedModifierType(modifierTypes.KINGS_ROCK, 3), + ].map(m => { + m.setTier(ModifierTier.ROGUE); + return m; + }); + dailyStarterModifierPool[ModifierTier.MASTER] = [ + new WeightedModifierType(modifierTypes.LEFTOVERS, 1), + new WeightedModifierType(modifierTypes.SHELL_BELL, 1), + ].map(m => { + m.setTier(ModifierTier.MASTER); + return m; + }); +} + +export function initModifierPools() { + // The modifier pools the player chooses from during modifier selection + initCommonModifierPool(); + initGreatModifierPool(); + initUltraModifierPool(); + initRogueModifierPool(); + initMasterModifierPool(); + + // Modifier pools for specific scenarios + initWildModifierPool(); + initTrainerModifierPool(); + initEnemyBuffModifierPool(); + initDailyStarterModifierPool(); +} + +/** + * High order function that returns a WeightedModifierTypeWeightFunc that will only be applied on + * classic and skip an ModifierType if current wave is greater or equal to the one passed down + * @param wave - Wave where we should stop showing the modifier + * @param defaultWeight - ModifierType default weight + * @returns A WeightedModifierTypeWeightFunc + */ +function skipInClassicAfterWave(wave: number, defaultWeight: number): WeightedModifierTypeWeightFunc { + return () => { + const gameMode = globalScene.gameMode; + const currentWave = globalScene.currentBattle.waveIndex; + return gameMode.isClassic && currentWave >= wave ? 0 : defaultWeight; + }; +} + +/** + * High order function that returns a WeightedModifierTypeWeightFunc that will only be applied on + * classic and it will skip a ModifierType if it is the last wave pull. + * @param defaultWeight ModifierType default weight + * @returns A WeightedModifierTypeWeightFunc + */ +function skipInLastClassicWaveOrDefault(defaultWeight: number): WeightedModifierTypeWeightFunc { + return skipInClassicAfterWave(199, defaultWeight); +} + +/** + * High order function that returns a WeightedModifierTypeWeightFunc to ensure Lures don't spawn on Classic 199 + * or if the lure still has over 60% of its duration left + * @param maxBattles The max battles the lure type in question lasts. 10 for green, 15 for Super, 30 for Max + * @param weight The desired weight for the lure when it does spawn + * @returns A WeightedModifierTypeWeightFunc + */ +function lureWeightFunc(maxBattles: number, weight: number): WeightedModifierTypeWeightFunc { + return () => { + const lures = globalScene.getModifiers(DoubleBattleChanceBoosterModifier); + return !(globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex === 199) && + (lures.length === 0 || + lures.filter(m => m.getMaxBattles() === maxBattles && m.getBattleCount() >= maxBattles * 0.6).length === 0) + ? weight + : 0; + }; +} + +/** + * Used to check if the player has max of a given ball type in Classic + * @param ballType The {@linkcode PokeballType} being checked + * @returns boolean: true if the player has the maximum of a given ball type + */ +function hasMaximumBalls(ballType: PokeballType): boolean { + return globalScene.gameMode.isClassic && globalScene.pokeballCounts[ballType] >= MAX_PER_TYPE_POKEBALLS; +} diff --git a/src/modifier/init-modifier-types.ts b/src/modifier/init-modifier-types.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/modifier/modifier-pools.ts b/src/modifier/modifier-pools.ts new file mode 100644 index 00000000000..3396dca1f93 --- /dev/null +++ b/src/modifier/modifier-pools.ts @@ -0,0 +1,16 @@ +/** + * Contains modifier pools for different contexts in the game. + * Can be safely imported without worrying about circular dependencies. + */ + +import type { ModifierPool } from "#app/@types/modifier-types"; + +export const modifierPool: ModifierPool = {}; + +export const wildModifierPool: ModifierPool = {}; + +export const trainerModifierPool: ModifierPool = {}; + +export const enemyBuffModifierPool: ModifierPool = {}; + +export const dailyStarterModifierPool: ModifierPool = {}; diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index b37ed7dea3b..5c21ae6f65e 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -4,7 +4,7 @@ import { tmPoolTiers, tmSpecies } from "#app/data/balance/tms"; import { getBerryEffectDescription, getBerryName } from "#app/data/berry"; import { allMoves } from "#app/data/data-lists"; import { getNatureName, getNatureStatMultiplier } from "#app/data/nature"; -import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; +import { getPokeballCatchMultiplier, getPokeballName } from "#app/data/pokeball"; import { pokemonFormChanges, SpeciesFormChangeCondition } from "#app/data/pokemon-forms"; import { SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { FormChangeItem } from "#enums/form-change-item"; @@ -97,9 +97,8 @@ import { CriticalCatchChanceBoosterModifier, FieldEffectModifier, } from "#app/modifier/modifier"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import Overrides from "#app/overrides"; -import { Unlockables } from "#app/system/unlockables"; import { getVoucherTypeIcon, getVoucherTypeName, VoucherType } from "#app/system/voucher"; import type { PokemonMoveSelectFilter, PokemonSelectFilter } from "#app/ui/party-ui-handler"; import PartyUiHandler from "#app/ui/party-ui-handler"; @@ -113,7 +112,6 @@ import { padInt, randSeedInt, } from "#app/utils/common"; -import { AbilityId } from "#enums/ability-id"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; import { MoveId } from "#enums/move-id"; @@ -127,18 +125,15 @@ import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; import { timedEventManager } from "#app/global-event-manager"; import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants"; +import { ModifierPoolType } from "#enums/modifier-pool-type"; + + +import { getModifierPoolForType } from "#app/utils/modifier-pool-utils"; +import type { ModifierTypeFunc, WeightedModifierTypeWeightFunc } from "#app/@types/modifier-types"; const outputModifierData = false; const useMaxWeightForOutput = false; -export enum ModifierPoolType { - PLAYER, - WILD, - TRAINER, - ENEMY_BUFF, - DAILY_STARTER, -} - type NewModifierFunc = (type: ModifierType, args: any[]) => Modifier; export class ModifierType { @@ -299,7 +294,7 @@ export interface GeneratedPersistentModifierType { getPregenArgs(): any[]; } -class AddPokeballModifierType extends ModifierType { +export class AddPokeballModifierType extends ModifierType { private pokeballType: PokeballType; private count: number; @@ -329,7 +324,7 @@ class AddPokeballModifierType extends ModifierType { } } -class AddVoucherModifierType extends ModifierType { +export class AddVoucherModifierType extends ModifierType { private voucherType: VoucherType; private count: number; @@ -1794,52 +1789,8 @@ export class EnemyEndureChanceModifierType extends ModifierType { } } -export type ModifierTypeFunc = () => ModifierType; -type WeightedModifierTypeWeightFunc = (party: Pokemon[], rerollCount?: number) => number; -/** - * High order function that returns a WeightedModifierTypeWeightFunc that will only be applied on - * classic and skip an ModifierType if current wave is greater or equal to the one passed down - * @param wave - Wave where we should stop showing the modifier - * @param defaultWeight - ModifierType default weight - * @returns A WeightedModifierTypeWeightFunc - */ -function skipInClassicAfterWave(wave: number, defaultWeight: number): WeightedModifierTypeWeightFunc { - return () => { - const gameMode = globalScene.gameMode; - const currentWave = globalScene.currentBattle.waveIndex; - return gameMode.isClassic && currentWave >= wave ? 0 : defaultWeight; - }; -} - -/** - * High order function that returns a WeightedModifierTypeWeightFunc that will only be applied on - * classic and it will skip a ModifierType if it is the last wave pull. - * @param defaultWeight ModifierType default weight - * @returns A WeightedModifierTypeWeightFunc - */ -function skipInLastClassicWaveOrDefault(defaultWeight: number): WeightedModifierTypeWeightFunc { - return skipInClassicAfterWave(199, defaultWeight); -} - -/** - * High order function that returns a WeightedModifierTypeWeightFunc to ensure Lures don't spawn on Classic 199 - * or if the lure still has over 60% of its duration left - * @param maxBattles The max battles the lure type in question lasts. 10 for green, 15 for Super, 30 for Max - * @param weight The desired weight for the lure when it does spawn - * @returns A WeightedModifierTypeWeightFunc - */ -function lureWeightFunc(maxBattles: number, weight: number): WeightedModifierTypeWeightFunc { - return () => { - const lures = globalScene.getModifiers(DoubleBattleChanceBoosterModifier); - return !(globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex === 199) && - (lures.length === 0 || - lures.filter(m => m.getMaxBattles() === maxBattles && m.getBattleCount() >= maxBattles * 0.6).length === 0) - ? weight - : 0; - }; -} -class WeightedModifierType { +export class WeightedModifierType { public modifierType: ModifierType; public weight: number | WeightedModifierTypeWeightFunc; public maxWeight: number | WeightedModifierTypeWeightFunc; @@ -1860,6 +1811,7 @@ class WeightedModifierType { } } + type BaseModifierOverride = { name: Exclude; count?: number; @@ -2423,737 +2375,12 @@ export const modifierTypes = { ), }; -interface ModifierPool { +export interface ModifierPool { [tier: string]: WeightedModifierType[]; } -/** - * Used to check if the player has max of a given ball type in Classic - * @param ballType The {@linkcode PokeballType} being checked - * @returns boolean: true if the player has the maximum of a given ball type - */ -function hasMaximumBalls(ballType: PokeballType): boolean { - return globalScene.gameMode.isClassic && globalScene.pokeballCounts[ballType] >= MAX_PER_TYPE_POKEBALLS; -} const modifierPool: ModifierPool = { - [ModifierTier.COMMON]: [ - new WeightedModifierType(modifierTypes.POKEBALL, () => (hasMaximumBalls(PokeballType.POKEBALL) ? 0 : 6), 6), - new WeightedModifierType(modifierTypes.RARE_CANDY, 2), - new WeightedModifierType( - modifierTypes.POTION, - (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min( - party.filter(p => p.getInverseHp() >= 10 && p.getHpRatio() <= 0.875 && !p.isFainted()).length, - 3, - ); - return thresholdPartyMemberCount * 3; - }, - 9, - ), - new WeightedModifierType( - modifierTypes.SUPER_POTION, - (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min( - party.filter(p => p.getInverseHp() >= 25 && p.getHpRatio() <= 0.75 && !p.isFainted()).length, - 3, - ); - return thresholdPartyMemberCount; - }, - 3, - ), - new WeightedModifierType( - modifierTypes.ETHER, - (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min( - party.filter( - p => - p.hp && - !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && - p - .getMoveset() - .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) - .length, - ).length, - 3, - ); - return thresholdPartyMemberCount * 3; - }, - 9, - ), - new WeightedModifierType( - modifierTypes.MAX_ETHER, - (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min( - party.filter( - p => - p.hp && - !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && - p - .getMoveset() - .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) - .length, - ).length, - 3, - ); - return thresholdPartyMemberCount; - }, - 3, - ), - new WeightedModifierType(modifierTypes.LURE, lureWeightFunc(10, 2)), - new WeightedModifierType(modifierTypes.TEMP_STAT_STAGE_BOOSTER, 4), - new WeightedModifierType(modifierTypes.BERRY, 2), - new WeightedModifierType(modifierTypes.TM_COMMON, 2), - ].map(m => { - m.setTier(ModifierTier.COMMON); - return m; - }), - [ModifierTier.GREAT]: [ - new WeightedModifierType(modifierTypes.GREAT_BALL, () => (hasMaximumBalls(PokeballType.GREAT_BALL) ? 0 : 6), 6), - new WeightedModifierType(modifierTypes.PP_UP, 2), - new WeightedModifierType( - modifierTypes.FULL_HEAL, - (party: Pokemon[]) => { - const statusEffectPartyMemberCount = Math.min( - party.filter( - p => - p.hp && - !!p.status && - !p.getHeldItems().some(i => { - if (i instanceof TurnStatusEffectModifier) { - return (i as TurnStatusEffectModifier).getStatusEffect() === p.status?.effect; - } - return false; - }), - ).length, - 3, - ); - return statusEffectPartyMemberCount * 6; - }, - 18, - ), - new WeightedModifierType( - modifierTypes.REVIVE, - (party: Pokemon[]) => { - const faintedPartyMemberCount = Math.min(party.filter(p => p.isFainted()).length, 3); - return faintedPartyMemberCount * 9; - }, - 27, - ), - new WeightedModifierType( - modifierTypes.MAX_REVIVE, - (party: Pokemon[]) => { - const faintedPartyMemberCount = Math.min(party.filter(p => p.isFainted()).length, 3); - return faintedPartyMemberCount * 3; - }, - 9, - ), - new WeightedModifierType( - modifierTypes.SACRED_ASH, - (party: Pokemon[]) => { - return party.filter(p => p.isFainted()).length >= Math.ceil(party.length / 2) ? 1 : 0; - }, - 1, - ), - new WeightedModifierType( - modifierTypes.HYPER_POTION, - (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min( - party.filter(p => p.getInverseHp() >= 100 && p.getHpRatio() <= 0.625 && !p.isFainted()).length, - 3, - ); - return thresholdPartyMemberCount * 3; - }, - 9, - ), - new WeightedModifierType( - modifierTypes.MAX_POTION, - (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min( - party.filter(p => p.getInverseHp() >= 100 && p.getHpRatio() <= 0.5 && !p.isFainted()).length, - 3, - ); - return thresholdPartyMemberCount; - }, - 3, - ), - new WeightedModifierType( - modifierTypes.FULL_RESTORE, - (party: Pokemon[]) => { - const statusEffectPartyMemberCount = Math.min( - party.filter( - p => - p.hp && - !!p.status && - !p.getHeldItems().some(i => { - if (i instanceof TurnStatusEffectModifier) { - return (i as TurnStatusEffectModifier).getStatusEffect() === p.status?.effect; - } - return false; - }), - ).length, - 3, - ); - const thresholdPartyMemberCount = Math.floor( - (Math.min(party.filter(p => p.getInverseHp() >= 100 && p.getHpRatio() <= 0.5 && !p.isFainted()).length, 3) + - statusEffectPartyMemberCount) / - 2, - ); - return thresholdPartyMemberCount; - }, - 3, - ), - new WeightedModifierType( - modifierTypes.ELIXIR, - (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min( - party.filter( - p => - p.hp && - !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && - p - .getMoveset() - .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) - .length, - ).length, - 3, - ); - return thresholdPartyMemberCount * 3; - }, - 9, - ), - new WeightedModifierType( - modifierTypes.MAX_ELIXIR, - (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min( - party.filter( - p => - p.hp && - !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && - p - .getMoveset() - .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) - .length, - ).length, - 3, - ); - return thresholdPartyMemberCount; - }, - 3, - ), - new WeightedModifierType(modifierTypes.DIRE_HIT, 4), - new WeightedModifierType(modifierTypes.SUPER_LURE, lureWeightFunc(15, 4)), - new WeightedModifierType(modifierTypes.NUGGET, skipInLastClassicWaveOrDefault(5)), - new WeightedModifierType(modifierTypes.SPECIES_STAT_BOOSTER, 4), - new WeightedModifierType( - modifierTypes.EVOLUTION_ITEM, - () => { - return Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 15), 8); - }, - 8, - ), - new WeightedModifierType( - modifierTypes.MAP, - () => (globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex < 180 ? 2 : 0), - 2, - ), - new WeightedModifierType(modifierTypes.SOOTHE_BELL, 2), - new WeightedModifierType(modifierTypes.TM_GREAT, 3), - new WeightedModifierType( - modifierTypes.MEMORY_MUSHROOM, - (party: Pokemon[]) => { - if (!party.find(p => p.getLearnableLevelMoves().length)) { - return 0; - } - const highestPartyLevel = party - .map(p => p.level) - .reduce((highestLevel: number, level: number) => Math.max(highestLevel, level), 1); - return Math.min(Math.ceil(highestPartyLevel / 20), 4); - }, - 4, - ), - new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3), - new WeightedModifierType(modifierTypes.TERA_SHARD, (party: Pokemon[]) => - party.filter( - p => - !(p.hasSpecies(SpeciesId.TERAPAGOS) || p.hasSpecies(SpeciesId.OGERPON) || p.hasSpecies(SpeciesId.SHEDINJA)), - ).length > 0 - ? 1 - : 0, - ), - new WeightedModifierType( - modifierTypes.DNA_SPLICERS, - (party: Pokemon[]) => { - if (party.filter(p => !p.fusionSpecies).length > 1) { - if (globalScene.gameMode.isSplicedOnly) { - return 4; - } - if (globalScene.gameMode.isClassic && timedEventManager.areFusionsBoosted()) { - return 2; - } - } - return 0; - }, - 4, - ), - new WeightedModifierType( - modifierTypes.VOUCHER, - (_party: Pokemon[], rerollCount: number) => (!globalScene.gameMode.isDaily ? Math.max(1 - rerollCount, 0) : 0), - 1, - ), - ].map(m => { - m.setTier(ModifierTier.GREAT); - return m; - }), - [ModifierTier.ULTRA]: [ - new WeightedModifierType(modifierTypes.ULTRA_BALL, () => (hasMaximumBalls(PokeballType.ULTRA_BALL) ? 0 : 15), 15), - new WeightedModifierType(modifierTypes.MAX_LURE, lureWeightFunc(30, 4)), - new WeightedModifierType(modifierTypes.BIG_NUGGET, skipInLastClassicWaveOrDefault(12)), - new WeightedModifierType(modifierTypes.PP_MAX, 3), - new WeightedModifierType(modifierTypes.MINT, 4), - new WeightedModifierType( - modifierTypes.RARE_EVOLUTION_ITEM, - () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 15) * 4, 32), - 32, - ), - new WeightedModifierType( - modifierTypes.FORM_CHANGE_ITEM, - () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 6, - 24, - ), - new WeightedModifierType(modifierTypes.AMULET_COIN, skipInLastClassicWaveOrDefault(3)), - new WeightedModifierType(modifierTypes.EVIOLITE, (party: Pokemon[]) => { - const { gameMode, gameData } = globalScene; - if (gameMode.isDaily || (!gameMode.isFreshStartChallenge() && gameData.isUnlocked(Unlockables.EVIOLITE))) { - return party.some(p => { - // Check if Pokemon's species (or fusion species, if applicable) can evolve or if they're G-Max'd - if ( - !p.isMax() && - (p.getSpeciesForm(true).speciesId in pokemonEvolutions || - (p.isFusion() && p.getFusionSpeciesForm(true).speciesId in pokemonEvolutions)) - ) { - // Check if Pokemon is already holding an Eviolite - return !p.getHeldItems().some(i => i.type.id === "EVIOLITE"); - } - return false; - }) - ? 10 - : 0; - } - return 0; - }), - new WeightedModifierType(modifierTypes.RARE_SPECIES_STAT_BOOSTER, 12), - new WeightedModifierType( - modifierTypes.LEEK, - (party: Pokemon[]) => { - const checkedSpecies = [SpeciesId.FARFETCHD, SpeciesId.GALAR_FARFETCHD, SpeciesId.SIRFETCHD]; - // If a party member doesn't already have a Leek and is one of the relevant species, Leek can appear - return party.some( - p => - !p.getHeldItems().some(i => i instanceof SpeciesCritBoosterModifier) && - (checkedSpecies.includes(p.getSpeciesForm(true).speciesId) || - (p.isFusion() && checkedSpecies.includes(p.getFusionSpeciesForm(true).speciesId))), - ) - ? 12 - : 0; - }, - 12, - ), - new WeightedModifierType( - modifierTypes.TOXIC_ORB, - (party: Pokemon[]) => { - return party.some(p => { - const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB"); - - if (!isHoldingOrb) { - const moveset = p - .getMoveset(true) - .filter(m => !isNullOrUndefined(m)) - .map(m => m.moveId); - const canSetStatus = p.canSetStatus(StatusEffect.TOXIC, true, true, null, true); - - // Moves that take advantage of obtaining the actual status effect - const hasStatusMoves = [MoveId.FACADE, MoveId.PSYCHO_SHIFT].some(m => moveset.includes(m)); - // Moves that take advantage of being able to give the target a status orb - // TODO: Take moves (Trick, Fling, Switcheroo) from comment when they are implemented - const hasItemMoves = [ - /* MoveId.TRICK, MoveId.FLING, MoveId.SWITCHEROO */ - ].some(m => moveset.includes(m)); - - if (canSetStatus) { - // Abilities that take advantage of obtaining the actual status effect, separated based on specificity to the orb - const hasGeneralAbility = [ - AbilityId.QUICK_FEET, - AbilityId.GUTS, - AbilityId.MARVEL_SCALE, - AbilityId.MAGIC_GUARD, - ].some(a => p.hasAbility(a, false, true)); - const hasSpecificAbility = [AbilityId.TOXIC_BOOST, AbilityId.POISON_HEAL].some(a => - p.hasAbility(a, false, true), - ); - const hasOppositeAbility = [AbilityId.FLARE_BOOST].some(a => p.hasAbility(a, false, true)); - - return hasSpecificAbility || (hasGeneralAbility && !hasOppositeAbility) || hasStatusMoves; - } - return hasItemMoves; - } - - return false; - }) - ? 10 - : 0; - }, - 10, - ), - new WeightedModifierType( - modifierTypes.FLAME_ORB, - (party: Pokemon[]) => { - return party.some(p => { - const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB"); - - if (!isHoldingOrb) { - const moveset = p - .getMoveset(true) - .filter(m => !isNullOrUndefined(m)) - .map(m => m.moveId); - const canSetStatus = p.canSetStatus(StatusEffect.BURN, true, true, null, true); - - // Moves that take advantage of obtaining the actual status effect - const hasStatusMoves = [MoveId.FACADE, MoveId.PSYCHO_SHIFT].some(m => moveset.includes(m)); - // Moves that take advantage of being able to give the target a status orb - // TODO: Take moves (Trick, Fling, Switcheroo) from comment when they are implemented - const hasItemMoves = [ - /* MoveId.TRICK, MoveId.FLING, MoveId.SWITCHEROO */ - ].some(m => moveset.includes(m)); - - if (canSetStatus) { - // Abilities that take advantage of obtaining the actual status effect, separated based on specificity to the orb - const hasGeneralAbility = [ - AbilityId.QUICK_FEET, - AbilityId.GUTS, - AbilityId.MARVEL_SCALE, - AbilityId.MAGIC_GUARD, - ].some(a => p.hasAbility(a, false, true)); - const hasSpecificAbility = [AbilityId.FLARE_BOOST].some(a => p.hasAbility(a, false, true)); - const hasOppositeAbility = [AbilityId.TOXIC_BOOST, AbilityId.POISON_HEAL].some(a => - p.hasAbility(a, false, true), - ); - - return hasSpecificAbility || (hasGeneralAbility && !hasOppositeAbility) || hasStatusMoves; - } - return hasItemMoves; - } - - return false; - }) - ? 10 - : 0; - }, - 10, - ), - new WeightedModifierType( - modifierTypes.MYSTICAL_ROCK, - (party: Pokemon[]) => { - return party.some(p => { - let isHoldingMax = false; - for (const i of p.getHeldItems()) { - if (i.type.id === "MYSTICAL_ROCK") { - isHoldingMax = i.getStackCount() === i.getMaxStackCount(); - break; - } - } - - if (!isHoldingMax) { - const moveset = p.getMoveset(true).map(m => m.moveId); - - const hasAbility = [ - AbilityId.DROUGHT, - AbilityId.ORICHALCUM_PULSE, - AbilityId.DRIZZLE, - AbilityId.SAND_STREAM, - AbilityId.SAND_SPIT, - AbilityId.SNOW_WARNING, - AbilityId.ELECTRIC_SURGE, - AbilityId.HADRON_ENGINE, - AbilityId.PSYCHIC_SURGE, - AbilityId.GRASSY_SURGE, - AbilityId.SEED_SOWER, - AbilityId.MISTY_SURGE, - ].some(a => p.hasAbility(a, false, true)); - - const hasMoves = [ - MoveId.SUNNY_DAY, - MoveId.RAIN_DANCE, - MoveId.SANDSTORM, - MoveId.SNOWSCAPE, - MoveId.HAIL, - MoveId.CHILLY_RECEPTION, - MoveId.ELECTRIC_TERRAIN, - MoveId.PSYCHIC_TERRAIN, - MoveId.GRASSY_TERRAIN, - MoveId.MISTY_TERRAIN, - ].some(m => moveset.includes(m)); - - return hasAbility || hasMoves; - } - return false; - }) - ? 10 - : 0; - }, - 10, - ), - new WeightedModifierType(modifierTypes.REVIVER_SEED, 4), - new WeightedModifierType(modifierTypes.CANDY_JAR, skipInLastClassicWaveOrDefault(5)), - new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 9), - new WeightedModifierType(modifierTypes.TM_ULTRA, 11), - new WeightedModifierType(modifierTypes.RARER_CANDY, 4), - new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, skipInLastClassicWaveOrDefault(2)), - new WeightedModifierType(modifierTypes.IV_SCANNER, skipInLastClassicWaveOrDefault(4)), - new WeightedModifierType(modifierTypes.EXP_CHARM, skipInLastClassicWaveOrDefault(8)), - new WeightedModifierType(modifierTypes.EXP_SHARE, skipInLastClassicWaveOrDefault(10)), - new WeightedModifierType( - modifierTypes.TERA_ORB, - () => - !globalScene.gameMode.isClassic - ? Math.min(Math.max(Math.floor(globalScene.currentBattle.waveIndex / 50) * 2, 1), 4) - : 0, - 4, - ), - new WeightedModifierType(modifierTypes.QUICK_CLAW, 3), - new WeightedModifierType(modifierTypes.WIDE_LENS, 7), - ].map(m => { - m.setTier(ModifierTier.ULTRA); - return m; - }), - [ModifierTier.ROGUE]: [ - new WeightedModifierType(modifierTypes.ROGUE_BALL, () => (hasMaximumBalls(PokeballType.ROGUE_BALL) ? 0 : 16), 16), - new WeightedModifierType(modifierTypes.RELIC_GOLD, skipInLastClassicWaveOrDefault(2)), - new WeightedModifierType(modifierTypes.LEFTOVERS, 3), - new WeightedModifierType(modifierTypes.SHELL_BELL, 3), - new WeightedModifierType(modifierTypes.BERRY_POUCH, 4), - new WeightedModifierType(modifierTypes.GRIP_CLAW, 5), - new WeightedModifierType(modifierTypes.SCOPE_LENS, 4), - new WeightedModifierType(modifierTypes.BATON, 2), - new WeightedModifierType(modifierTypes.SOUL_DEW, 7), - new WeightedModifierType(modifierTypes.CATCHING_CHARM, () => (!globalScene.gameMode.isClassic ? 4 : 0), 4), - new WeightedModifierType(modifierTypes.ABILITY_CHARM, skipInClassicAfterWave(189, 6)), - new WeightedModifierType(modifierTypes.FOCUS_BAND, 5), - new WeightedModifierType(modifierTypes.KINGS_ROCK, 3), - new WeightedModifierType(modifierTypes.LOCK_CAPSULE, () => (globalScene.gameMode.isClassic ? 0 : 3)), - new WeightedModifierType(modifierTypes.SUPER_EXP_CHARM, skipInLastClassicWaveOrDefault(8)), - new WeightedModifierType( - modifierTypes.RARE_FORM_CHANGE_ITEM, - () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 6, - 24, - ), - new WeightedModifierType( - modifierTypes.MEGA_BRACELET, - () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 9, - 36, - ), - new WeightedModifierType( - modifierTypes.DYNAMAX_BAND, - () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 9, - 36, - ), - new WeightedModifierType( - modifierTypes.VOUCHER_PLUS, - (_party: Pokemon[], rerollCount: number) => - !globalScene.gameMode.isDaily ? Math.max(3 - rerollCount * 1, 0) : 0, - 3, - ), - ].map(m => { - m.setTier(ModifierTier.ROGUE); - return m; - }), - [ModifierTier.MASTER]: [ - new WeightedModifierType(modifierTypes.MASTER_BALL, () => (hasMaximumBalls(PokeballType.MASTER_BALL) ? 0 : 24), 24), - new WeightedModifierType(modifierTypes.SHINY_CHARM, 14), - new WeightedModifierType(modifierTypes.HEALING_CHARM, 18), - new WeightedModifierType(modifierTypes.MULTI_LENS, 18), - new WeightedModifierType( - modifierTypes.VOUCHER_PREMIUM, - (_party: Pokemon[], rerollCount: number) => - !globalScene.gameMode.isDaily && !globalScene.gameMode.isEndless && !globalScene.gameMode.isSplicedOnly - ? Math.max(5 - rerollCount * 2, 0) - : 0, - 5, - ), - new WeightedModifierType( - modifierTypes.DNA_SPLICERS, - (party: Pokemon[]) => - !(globalScene.gameMode.isClassic && timedEventManager.areFusionsBoosted()) && - !globalScene.gameMode.isSplicedOnly && - party.filter(p => !p.fusionSpecies).length > 1 - ? 24 - : 0, - 24, - ), - new WeightedModifierType( - modifierTypes.MINI_BLACK_HOLE, - () => - globalScene.gameMode.isDaily || - (!globalScene.gameMode.isFreshStartChallenge() && globalScene.gameData.isUnlocked(Unlockables.MINI_BLACK_HOLE)) - ? 1 - : 0, - 1, - ), - ].map(m => { - m.setTier(ModifierTier.MASTER); - return m; - }), -}; - -const wildModifierPool: ModifierPool = { - [ModifierTier.COMMON]: [new WeightedModifierType(modifierTypes.BERRY, 1)].map(m => { - m.setTier(ModifierTier.COMMON); - return m; - }), - [ModifierTier.GREAT]: [new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 1)].map(m => { - m.setTier(ModifierTier.GREAT); - return m; - }), - [ModifierTier.ULTRA]: [ - new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10), - new WeightedModifierType(modifierTypes.WHITE_HERB, 0), - ].map(m => { - m.setTier(ModifierTier.ULTRA); - return m; - }), - [ModifierTier.ROGUE]: [new WeightedModifierType(modifierTypes.LUCKY_EGG, 4)].map(m => { - m.setTier(ModifierTier.ROGUE); - return m; - }), - [ModifierTier.MASTER]: [new WeightedModifierType(modifierTypes.GOLDEN_EGG, 1)].map(m => { - m.setTier(ModifierTier.MASTER); - return m; - }), -}; - -const trainerModifierPool: ModifierPool = { - [ModifierTier.COMMON]: [ - new WeightedModifierType(modifierTypes.BERRY, 8), - new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3), - ].map(m => { - m.setTier(ModifierTier.COMMON); - return m; - }), - [ModifierTier.GREAT]: [new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3)].map(m => { - m.setTier(ModifierTier.GREAT); - return m; - }), - [ModifierTier.ULTRA]: [ - new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10), - new WeightedModifierType(modifierTypes.WHITE_HERB, 0), - ].map(m => { - m.setTier(ModifierTier.ULTRA); - return m; - }), - [ModifierTier.ROGUE]: [ - new WeightedModifierType(modifierTypes.FOCUS_BAND, 2), - new WeightedModifierType(modifierTypes.LUCKY_EGG, 4), - new WeightedModifierType(modifierTypes.QUICK_CLAW, 1), - new WeightedModifierType(modifierTypes.GRIP_CLAW, 1), - new WeightedModifierType(modifierTypes.WIDE_LENS, 1), - ].map(m => { - m.setTier(ModifierTier.ROGUE); - return m; - }), - [ModifierTier.MASTER]: [ - new WeightedModifierType(modifierTypes.KINGS_ROCK, 1), - new WeightedModifierType(modifierTypes.LEFTOVERS, 1), - new WeightedModifierType(modifierTypes.SHELL_BELL, 1), - new WeightedModifierType(modifierTypes.SCOPE_LENS, 1), - ].map(m => { - m.setTier(ModifierTier.MASTER); - return m; - }), -}; - -const enemyBuffModifierPool: ModifierPool = { - [ModifierTier.COMMON]: [ - new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 9), - new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 9), - new WeightedModifierType(modifierTypes.ENEMY_ATTACK_POISON_CHANCE, 3), - new WeightedModifierType(modifierTypes.ENEMY_ATTACK_PARALYZE_CHANCE, 3), - new WeightedModifierType(modifierTypes.ENEMY_ATTACK_BURN_CHANCE, 3), - new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 9), - new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 4), - new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 1), - ].map(m => { - m.setTier(ModifierTier.COMMON); - return m; - }), - [ModifierTier.GREAT]: [ - new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 5), - new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 5), - new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 5), - new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 5), - new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 1), - ].map(m => { - m.setTier(ModifierTier.GREAT); - return m; - }), - [ModifierTier.ULTRA]: [ - new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 10), - new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 10), - new WeightedModifierType(modifierTypes.ENEMY_HEAL, 10), - new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 10), - new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 10), - new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 5), - ].map(m => { - m.setTier(ModifierTier.ULTRA); - return m; - }), - [ModifierTier.ROGUE]: [].map((m: WeightedModifierType) => { - m.setTier(ModifierTier.ROGUE); - return m; - }), - [ModifierTier.MASTER]: [].map((m: WeightedModifierType) => { - m.setTier(ModifierTier.MASTER); - return m; - }), -}; - -const dailyStarterModifierPool: ModifierPool = { - [ModifierTier.COMMON]: [ - new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 1), - new WeightedModifierType(modifierTypes.BERRY, 3), - ].map(m => { - m.setTier(ModifierTier.COMMON); - return m; - }), - [ModifierTier.GREAT]: [new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 5)].map(m => { - m.setTier(ModifierTier.GREAT); - return m; - }), - [ModifierTier.ULTRA]: [ - new WeightedModifierType(modifierTypes.REVIVER_SEED, 4), - new WeightedModifierType(modifierTypes.SOOTHE_BELL, 1), - new WeightedModifierType(modifierTypes.SOUL_DEW, 1), - new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, 1), - ].map(m => { - m.setTier(ModifierTier.ULTRA); - return m; - }), - [ModifierTier.ROGUE]: [ - new WeightedModifierType(modifierTypes.GRIP_CLAW, 5), - new WeightedModifierType(modifierTypes.BATON, 2), - new WeightedModifierType(modifierTypes.FOCUS_BAND, 5), - new WeightedModifierType(modifierTypes.QUICK_CLAW, 3), - new WeightedModifierType(modifierTypes.KINGS_ROCK, 3), - ].map(m => { - m.setTier(ModifierTier.ROGUE); - return m; - }), - [ModifierTier.MASTER]: [ - new WeightedModifierType(modifierTypes.LEFTOVERS, 1), - new WeightedModifierType(modifierTypes.SHELL_BELL, 1), - ].map(m => { - m.setTier(ModifierTier.MASTER); - return m; - }), }; export function getModifierType(modifierTypeFunc: ModifierTypeFunc): ModifierType { @@ -3179,28 +2406,6 @@ let enemyBuffModifierPoolThresholds = {}; // biome-ignore lint/correctness/noUnusedVariables: TODO explain why this is marked as OK let enemyBuffIgnoredPoolIndexes = {}; -export function getModifierPoolForType(poolType: ModifierPoolType): ModifierPool { - let pool: ModifierPool; - switch (poolType) { - case ModifierPoolType.PLAYER: - pool = modifierPool; - break; - case ModifierPoolType.WILD: - pool = wildModifierPool; - break; - case ModifierPoolType.TRAINER: - pool = trainerModifierPool; - break; - case ModifierPoolType.ENEMY_BUFF: - pool = enemyBuffModifierPool; - break; - case ModifierPoolType.DAILY_STARTER: - pool = dailyStarterModifierPool; - break; - } - return pool; -} - const tierWeights = [768 / 1024, 195 / 1024, 48 / 1024, 12 / 1024, 1 / 1024]; /** * Allows a unit test to check if an item exists in the Modifier Pool. Checks the pool directly, rather than attempting to reroll for the item. diff --git a/src/modifier/modifier-types.ts b/src/modifier/modifier-types.ts new file mode 100644 index 00000000000..3b0043c1442 --- /dev/null +++ b/src/modifier/modifier-types.ts @@ -0,0 +1,22 @@ +/** + * + */ + +import type { AddPokeballModifierType, AllPokemonLevelIncrementModifierType, EvolutionItemModifierTypeGenerator, ModifierType, ModifierTypeGenerator, PokemonLevelIncrementModifierType } from "./modifier-type"; + +interface ModifierTypes { + POKEBALL: () => AddPokeballModifierType; + GREAT_BALL: () => AddPokeballModifierType; + ULTRA_BALL: () => AddPokeballModifierType; + ROGUE_BALL: () => AddPokeballModifierType; + MASTER_BALL: () => AddPokeballModifierType; + + RARE_CANDY: () => PokemonLevelIncrementModifierType; + RARER_CANDY: () => AllPokemonLevelIncrementModifierType; + + EVOLUTION_ITEM: () => EvolutionItemModifierTypeGenerator; + RARE_EVOLUTION_ITEM: () => EvolutionItemModifierTypeGenerator; + + FORM_CHANGE_ITEM: () => + +} \ No newline at end of file diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index c99f9d3c6f3..3460c536f12 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -46,6 +46,7 @@ import { Color, ShadowColor } from "#enums/color"; import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters"; import { applyAbAttrs, applyPostItemLostAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; +import type { ModifierString } from "#app/@types/modifier-types"; export type ModifierPredicate = (modifier: Modifier) => boolean; @@ -159,6 +160,15 @@ export abstract class Modifier { this.type = type; } + /** */ + public is(modifier: ModifierString): this is ModifierConstructorMap[T] { + const targetModifier = ModifierClassMap[modifier]; + if (!targetModifier) { + return false; + } + return this instanceof targetModifier; + } + match(_modifier: Modifier): boolean { return false; } @@ -3842,3 +3852,102 @@ export function overrideHeldItems(pokemon: Pokemon, isPlayer = true): void { } } } + + +/** + * Private map from modifier strings to their constructors. + * + * @remarks + * Used for {@linkcode Modifier.is} to check if a modifier is of a certain type without + * requiring modifier types to be imported in every file. + */ +const ModifierClassMap = Object.freeze({ + PersistentModifier, + ConsumableModifier, + AddPokeballModifier, + AddVoucherModifier, + LapsingPersistentModifier, + DoubleBattleChanceBoosterModifier, + TempStatStageBoosterModifier, + TempCritBoosterModifier, + MapModifier, + MegaEvolutionAccessModifier, + GigantamaxAccessModifier, + TerastallizeAccessModifier, + PokemonHeldItemModifier, + LapsingPokemonHeldItemModifier, + BaseStatModifier, + EvoTrackerModifier, + PokemonBaseStatTotalModifier, + PokemonBaseStatFlatModifier, + PokemonIncrementingStatModifier, + StatBoosterModifier, + SpeciesStatBoosterModifier, + CritBoosterModifier, + SpeciesCritBoosterModifier, + AttackTypeBoosterModifier, + SurviveDamageModifier, + BypassSpeedChanceModifier, + FlinchChanceModifier, + TurnHealModifier, + TurnStatusEffectModifier, + HitHealModifier, + LevelIncrementBoosterModifier, + BerryModifier, + PreserveBerryModifier, + PokemonInstantReviveModifier, + ResetNegativeStatStageModifier, + FieldEffectModifier, + ConsumablePokemonModifier, + TerrastalizeModifier, + PokemonHpRestoreModifier, + PokemonStatusHealModifier, + ConsumablePokemonMoveModifier, + PokemonPpRestoreModifier, + PokemonAllMovePpRestoreModifier, + PokemonPpUpModifier, + PokemonNatureChangeModifier, + PokemonLevelIncrementModifier, + TmModifier, + RememberMoveModifier, + EvolutionItemModifier, + FusePokemonModifier, + MultipleParticipantExpBonusModifier, + HealingBoosterModifier, + ExpBoosterModifier, + PokemonExpBoosterModifier, + ExpShareModifier, + ExpBalanceModifier, + PokemonFriendshipBoosterModifier, + PokemonNatureWeightModifier, + PokemonMoveAccuracyBoosterModifier, + PokemonMultiHitModifier, + PokemonFormChangeItemModifier, + MoneyRewardModifier, + DamageMoneyRewardModifier, + MoneyInterestModifier, + HiddenAbilityRateBoosterModifier, + ShinyRateBoosterModifier, + CriticalCatchChanceBoosterModifier, + LockModifierTiersModifier, + HealShopCostModifier, + BoostBugSpawnModifier, + SwitchEffectTransferModifier, + HeldItemTransferModifier, + TurnHeldItemTransferModifier, + ContactHeldItemTransferChanceModifier, + IvScannerModifier, + ExtraModifierModifier, + TempExtraModifierModifier, + EnemyPersistentModifier, + EnemyDamageMultiplierModifier, + EnemyDamageBoosterModifier, + EnemyDamageReducerModifier, + EnemyTurnHealModifier, + EnemyAttackStatusEffectChanceModifier, + EnemyStatusEffectHealChanceModifier, + EnemyEndureChanceModifier, + EnemyFusionChanceModifier, +}); + +export type ModifierConstructorMap = typeof ModifierClassMap; \ No newline at end of file diff --git a/src/phases/add-enemy-buff-modifier-phase.ts b/src/phases/add-enemy-buff-modifier-phase.ts index 28eaf0dc4df..185464670d3 100644 --- a/src/phases/add-enemy-buff-modifier-phase.ts +++ b/src/phases/add-enemy-buff-modifier-phase.ts @@ -1,9 +1,9 @@ -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { regenerateModifierPoolThresholds, - ModifierPoolType, getEnemyBuffModifierForWave, } from "#app/modifier/modifier-type"; +import { ModifierPoolType } from "#enums/modifier-pool-type"; import { EnemyPersistentModifier } from "#app/modifier/modifier"; import { Phase } from "#app/phase"; import { globalScene } from "#app/global-scene"; diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index 08cf5a3e3b7..701527bd4a2 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -15,7 +15,8 @@ import type Pokemon from "#app/field/pokemon"; import { FieldPosition } from "#enums/field-position"; import { getPokemonNameWithAffix } from "#app/messages"; import { BoostBugSpawnModifier, IvScannerModifier, TurnHeldItemTransferModifier } from "#app/modifier/modifier"; -import { ModifierPoolType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; +import { regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; +import { ModifierPoolType } from "#enums/modifier-pool-type"; import Overrides from "#app/overrides"; import { BattlePhase } from "#app/phases/battle-phase"; import { achvs } from "#app/system/achv"; diff --git a/src/phases/modifier-reward-phase.ts b/src/phases/modifier-reward-phase.ts index 83bd8704f59..4c427f2284e 100644 --- a/src/phases/modifier-reward-phase.ts +++ b/src/phases/modifier-reward-phase.ts @@ -1,5 +1,6 @@ import { globalScene } from "#app/global-scene"; -import type { ModifierType, ModifierTypeFunc } from "#app/modifier/modifier-type"; +import type { ModifierType } from "#app/modifier/modifier-type"; +import type { ModifierTypeFunc } from "#app/@types/modifier-types"; import { getModifierType } from "#app/modifier/modifier-type"; import i18next from "i18next"; import { BattlePhase } from "./battle-phase"; diff --git a/src/phases/ribbon-modifier-reward-phase.ts b/src/phases/ribbon-modifier-reward-phase.ts index 949f7af0302..10d63ba707f 100644 --- a/src/phases/ribbon-modifier-reward-phase.ts +++ b/src/phases/ribbon-modifier-reward-phase.ts @@ -1,6 +1,6 @@ import { globalScene } from "#app/global-scene"; import type PokemonSpecies from "#app/data/pokemon-species"; -import type { ModifierTypeFunc } from "#app/modifier/modifier-type"; +import type { ModifierTypeFunc } from "#app/@types/modifier-types"; import { UiMode } from "#enums/ui-mode"; import i18next from "i18next"; import { ModifierRewardPhase } from "./modifier-reward-phase"; diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index f99c921412f..34a905f64c6 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import type { ModifierTier } from "#app/modifier/modifier-tier"; +import type { ModifierTier } from "#enums/modifier-tier"; import type { ModifierTypeOption, ModifierType } from "#app/modifier/modifier-type"; import { regenerateModifierPoolThresholds, @@ -11,9 +11,9 @@ import { RememberMoveModifierType, PokemonPpRestoreModifierType, PokemonPpUpModifierType, - ModifierPoolType, getPlayerModifierTypeOptions, } from "#app/modifier/modifier-type"; +import { ModifierPoolType } from "#enums/modifier-pool-type"; import type { Modifier } from "#app/modifier/modifier"; import { ExtraModifierModifier, diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index 26311d52ab8..5665f7148bd 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -8,10 +8,10 @@ import { GameModes } from "#enums/game-modes"; import type { Modifier } from "#app/modifier/modifier"; import { getDailyRunStarterModifiers, - ModifierPoolType, modifierTypes, regenerateModifierPoolThresholds, } from "#app/modifier/modifier-type"; +import { ModifierPoolType } from "#enums/modifier-pool-type"; import { Phase } from "#app/phase"; import type { SessionSaveData } from "#app/system/game-data"; import { Unlockables } from "#app/system/unlockables"; diff --git a/src/ui/text.ts b/src/ui/text.ts index d3afdef666f..8812d8ee4a8 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -4,7 +4,7 @@ import type Phaser from "phaser"; import BBCodeText from "phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText"; import InputText from "phaser3-rex-plugins/plugins/inputtext"; import { globalScene } from "#app/global-scene"; -import { ModifierTier } from "../modifier/modifier-tier"; +import { ModifierTier } from "../enums/modifier-tier"; import i18next from "#app/plugins/i18n"; export enum TextStyle { diff --git a/src/utils/modifier-pool-utils.ts b/src/utils/modifier-pool-utils.ts new file mode 100644 index 00000000000..46d13ce6f96 --- /dev/null +++ b/src/utils/modifier-pool-utils.ts @@ -0,0 +1,24 @@ +import { ModifierPoolType } from "#enums/modifier-pool-type"; +import { + dailyStarterModifierPool, + enemyBuffModifierPool, + modifierPool, + trainerModifierPool, + wildModifierPool, +} from "#app/modifier/modifier-pools"; +import type { ModifierPool } from "#app/@types/modifier-types"; + +export function getModifierPoolForType(poolType: ModifierPoolType): ModifierPool { + switch (poolType) { + case ModifierPoolType.PLAYER: + return modifierPool; + case ModifierPoolType.WILD: + return wildModifierPool; + case ModifierPoolType.TRAINER: + return trainerModifierPool; + case ModifierPoolType.ENEMY_BUFF: + return enemyBuffModifierPool; + case ModifierPoolType.DAILY_STARTER: + return dailyStarterModifierPool; + } +} diff --git a/test/items/lock_capsule.test.ts b/test/items/lock_capsule.test.ts index 292031a2bf0..640da4a299e 100644 --- a/test/items/lock_capsule.test.ts +++ b/test/items/lock_capsule.test.ts @@ -1,6 +1,6 @@ import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { UiMode } from "#enums/ui-mode"; import GameManager from "#test/testUtils/gameManager"; diff --git a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts index 778f8397417..dceab2f20cb 100644 --- a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -20,7 +20,7 @@ import { UiMode } from "#enums/ui-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { ClowningAroundEncounter } from "#app/data/mystery-encounters/encounters/clowning-around-encounter"; import { TrainerType } from "#enums/trainer-type"; import { AbilityId } from "#enums/ability-id"; diff --git a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts index 96c4adf67c2..53c1ee811e3 100644 --- a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts +++ b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts @@ -17,7 +17,7 @@ import { CIVILIZATION_ENCOUNTER_BIOMES } from "#app/data/mystery-encounters/myst import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { UiMode } from "#enums/ui-mode"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import * as Utils from "#app/utils/common"; const namespace = "mysteryEncounters/globalTradeSystem"; diff --git a/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts b/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts index 948e42547de..9c5660cb25c 100644 --- a/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts +++ b/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts @@ -14,7 +14,7 @@ import { UiMode } from "#enums/ui-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { MysteriousChallengersEncounter } from "#app/data/mystery-encounters/encounters/mysterious-challengers-encounter"; import { TrainerConfig } from "#app/data/trainers/trainer-config"; import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate"; diff --git a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts index 47c75eb19fc..3316bf76280 100644 --- a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts +++ b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts @@ -14,7 +14,7 @@ import { MysteryEncounterType } from "#app/enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import { PokemonMove } from "#app/data/moves/pokemon-move"; import { HealShopCostModifier, HitHealModifier, TurnHealModifier } from "#app/modifier/modifier"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { modifierTypes, type PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; diff --git a/test/mystery-encounter/encounters/weird-dream-encounter.test.ts b/test/mystery-encounter/encounters/weird-dream-encounter.test.ts index 2ad74b48540..dc5c53a75e7 100644 --- a/test/mystery-encounter/encounters/weird-dream-encounter.test.ts +++ b/test/mystery-encounter/encounters/weird-dream-encounter.test.ts @@ -19,7 +19,7 @@ import { WeirdDreamEncounter } from "#app/data/mystery-encounters/encounters/wei import * as EncounterTransformationSequence from "#app/data/mystery-encounters/utils/encounter-transformation-sequence"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { CommandPhase } from "#app/phases/command-phase"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; const namespace = "mysteryEncounters/weirdDream"; const defaultParty = [SpeciesId.MAGBY, SpeciesId.HAUNTER, SpeciesId.ABRA]; diff --git a/test/phases/select-modifier-phase.test.ts b/test/phases/select-modifier-phase.test.ts index 083b7d16f10..0605fdf7809 100644 --- a/test/phases/select-modifier-phase.test.ts +++ b/test/phases/select-modifier-phase.test.ts @@ -1,7 +1,7 @@ import type BattleScene from "#app/battle-scene"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { PlayerPokemon } from "#app/field/pokemon"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import type { CustomModifierSettings } from "#app/modifier/modifier-type"; import { ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase";