From ebc6ff2ed3f2dce4c59753f7d0290250de145982 Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Mon, 16 Jun 2025 08:18:44 -0400 Subject: [PATCH 1/4] [Refactor] Clean up shiny/HA reroll methods (#5803) --- src/field/pokemon.ts | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 730d2ab2c2a..24399504eba 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2785,17 +2785,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ public trySetShinySeed(thresholdOverride?: number, applyModifiersToOverride?: boolean): boolean { if (!this.shiny) { - const shinyThreshold = new NumberHolder(BASE_SHINY_CHANCE); - if (thresholdOverride === undefined || applyModifiersToOverride) { - if (thresholdOverride !== undefined && applyModifiersToOverride) { - shinyThreshold.value = thresholdOverride; - } + const shinyThreshold = new NumberHolder(thresholdOverride ?? BASE_SHINY_CHANCE); + if (applyModifiersToOverride) { if (timedEventManager.isEventActive()) { shinyThreshold.value *= timedEventManager.getShinyMultiplier(); } globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold); - } else { - shinyThreshold.value = thresholdOverride; } this.shiny = randSeedInt(65536) < shinyThreshold.value; @@ -2864,16 +2859,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!this.species.abilityHidden) { return false; } - const haThreshold = new NumberHolder(BASE_HIDDEN_ABILITY_CHANCE); - if (thresholdOverride === undefined || applyModifiersToOverride) { - if (thresholdOverride !== undefined && applyModifiersToOverride) { - haThreshold.value = thresholdOverride; - } + const haThreshold = new NumberHolder(thresholdOverride ?? BASE_HIDDEN_ABILITY_CHANCE); + if (applyModifiersToOverride) { if (!this.hasTrainer()) { globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, haThreshold); } - } else { - haThreshold.value = thresholdOverride; } if (randSeedInt(65536) < haThreshold.value) { From ccceaac877495d063a62238aa2c1d43cfe100213 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Mon, 16 Jun 2025 08:36:09 -0400 Subject: [PATCH 2/4] [Test] Reworked crit override to allow for forced crits (#5738) * Crit override stuff * Update ability.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update pokemon.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update game-mode.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- .env.development | 1 + scripts/create-test/create-test.js | 2 +- scripts/create-test/test-boilerplate.ts | 2 +- src/battle-scene.ts | 21 +++-- src/data/abilities/ability.ts | 10 +-- src/field/pokemon.ts | 57 ++++++------ src/game-mode.ts | 5 +- src/overrides.ts | 6 +- src/phases/move-effect-phase.ts | 2 +- .../ability_activation_order.test.ts | 2 +- test/abilities/analytic.test.ts | 2 +- test/abilities/commander.test.ts | 2 +- test/abilities/corrosion.test.ts | 2 +- test/abilities/cud_chew.test.ts | 2 +- test/abilities/dry_skin.test.ts | 2 +- test/abilities/early_bird.test.ts | 2 +- test/abilities/flash_fire.test.ts | 2 +- test/abilities/flower_veil.test.ts | 2 +- test/abilities/good_as_gold.test.ts | 2 +- test/abilities/gulp_missile.test.ts | 2 +- test/abilities/harvest.test.ts | 2 +- test/abilities/healer.test.ts | 2 +- test/abilities/heatproof.test.ts | 2 +- test/abilities/honey_gather.test.ts | 2 +- test/abilities/hustle.test.ts | 2 +- test/abilities/immunity.test.ts | 2 +- test/abilities/infiltrator.test.ts | 2 +- test/abilities/insomnia.test.ts | 2 +- test/abilities/lightningrod.test.ts | 2 +- test/abilities/limber.test.ts | 2 +- test/abilities/magic_bounce.test.ts | 2 +- test/abilities/magic_guard.test.ts | 4 +- test/abilities/magma_armor.test.ts | 2 +- test/abilities/mimicry.test.ts | 2 +- test/abilities/mold_breaker.test.ts | 2 +- test/abilities/mummy.test.ts | 2 +- test/abilities/mycelium_might.test.ts | 2 +- test/abilities/neutralizing_gas.test.ts | 2 +- test/abilities/normalize.test.ts | 2 +- test/abilities/oblivious.test.ts | 2 +- test/abilities/own_tempo.test.ts | 2 +- test/abilities/parental_bond.test.ts | 2 +- test/abilities/perish_body.test.ts | 6 +- test/abilities/protosynthesis.test.ts | 2 +- test/abilities/sand_spit.test.ts | 2 +- test/abilities/sap_sipper.test.ts | 2 +- test/abilities/seed_sower.test.ts | 4 +- test/abilities/serene_grace.test.ts | 2 +- test/abilities/sheer_force.test.ts | 2 +- test/abilities/shell-armor.test.ts | 69 +++++++++++++++ test/abilities/stakeout.test.ts | 2 +- test/abilities/stall.test.ts | 2 +- test/abilities/storm_drain.test.ts | 2 +- test/abilities/thermal_exchange.test.ts | 2 +- test/abilities/trace.test.ts | 2 +- test/abilities/victory_star.test.ts | 2 +- test/abilities/vital_spirit.test.ts | 2 +- test/abilities/volt_absorb.test.ts | 2 +- test/abilities/wandering_spirit.test.ts | 2 +- test/abilities/water_bubble.test.ts | 2 +- test/abilities/water_veil.test.ts | 2 +- test/abilities/wimp_out.test.ts | 2 +- test/abilities/zen_mode.test.ts | 2 +- test/arena/grassy_terrain.test.ts | 2 +- test/battle/ability_swap.test.ts | 2 +- test/battle/damage_calculation.test.ts | 2 +- test/boss-pokemon.test.ts | 2 +- test/data/status_effect.test.ts | 4 +- test/endless_boss.test.ts | 2 +- test/final_boss.test.ts | 2 +- test/items/multi_lens.test.ts | 2 +- test/items/reviver_seed.test.ts | 2 +- test/moves/alluring_voice.test.ts | 2 +- test/moves/assist.test.ts | 2 +- test/moves/aurora_veil.test.ts | 2 +- test/moves/baton_pass.test.ts | 2 +- test/moves/burning_jealousy.test.ts | 2 +- test/moves/camouflage.test.ts | 2 +- test/moves/chloroblast.test.ts | 2 +- test/moves/copycat.test.ts | 2 +- test/moves/defog.test.ts | 2 +- test/moves/doodle.test.ts | 2 +- test/moves/double_team.test.ts | 2 +- test/moves/dragon_rage.test.ts | 10 +-- test/moves/dynamax_cannon.test.ts | 8 +- test/moves/encore.test.ts | 2 +- test/moves/endure.test.ts | 2 +- test/moves/entrainment.test.ts | 2 +- test/moves/fairy_lock.test.ts | 2 +- test/moves/fake_out.test.ts | 2 +- test/moves/false_swipe.test.ts | 2 +- test/moves/fell_stinger.test.ts | 2 +- test/moves/fissure.test.ts | 4 +- test/moves/flame_burst.test.ts | 4 +- test/moves/foresight.test.ts | 2 +- test/moves/forests_curse.test.ts | 2 +- test/moves/fusion_bolt.test.ts | 2 +- test/moves/fusion_flare.test.ts | 2 +- test/moves/fusion_flare_bolt.test.ts | 2 +- test/moves/gigaton_hammer.test.ts | 2 +- test/moves/glaive_rush.test.ts | 2 +- test/moves/grudge.test.ts | 2 +- test/moves/heal_block.test.ts | 2 +- test/moves/instruct.test.ts | 2 +- test/moves/jaw_lock.test.ts | 2 +- test/moves/lash_out.test.ts | 2 +- test/moves/last-resort.test.ts | 2 +- test/moves/last_respects.test.ts | 2 +- test/moves/light_screen.test.ts | 2 +- test/moves/lucky_chant.test.ts | 86 +++++++++++-------- test/moves/magic_coat.test.ts | 2 +- test/moves/magnet_rise.test.ts | 2 +- test/moves/metal_burst.test.ts | 2 +- test/moves/miracle_eye.test.ts | 2 +- test/moves/mirror_move.test.ts | 2 +- test/moves/mist.test.ts | 2 +- test/moves/moongeist_beam.test.ts | 2 +- test/moves/multi_target.test.ts | 2 +- test/moves/order_up.test.ts | 2 +- test/moves/pollen_puff.test.ts | 2 +- test/moves/psycho_shift.test.ts | 2 +- test/moves/reflect.test.ts | 2 +- test/moves/reflect_type.test.ts | 6 +- test/moves/retaliate.test.ts | 2 +- test/moves/revival_blessing.test.ts | 2 +- test/moves/role_play.test.ts | 2 +- test/moves/rollout.test.ts | 2 +- test/moves/round.test.ts | 2 +- test/moves/scale_shot.test.ts | 2 +- test/moves/secret_power.test.ts | 2 +- test/moves/simple_beam.test.ts | 2 +- test/moves/sketch.test.ts | 2 +- test/moves/skill_swap.test.ts | 2 +- test/moves/sleep_talk.test.ts | 2 +- test/moves/spectral_thief.test.ts | 3 +- test/moves/struggle.test.ts | 2 +- test/moves/synchronoise.test.ts | 2 +- test/moves/tackle.test.ts | 2 +- test/moves/tar_shot.test.ts | 2 +- test/moves/tera_blast.test.ts | 2 +- test/moves/trick_or_treat.test.ts | 2 +- test/moves/u_turn.test.ts | 2 +- test/moves/upper_hand.test.ts | 2 +- test/moves/will_o_wisp.test.ts | 2 +- test/phases/form-change-phase.test.ts | 2 +- test/phases/frenzy-move-reset.test.ts | 2 +- test/phases/game-over-phase.test.ts | 2 +- test/testUtils/gameManagerUtils.ts | 2 +- test/testUtils/helpers/dailyModeHelper.ts | 5 +- test/testUtils/helpers/overridesHelper.ts | 11 ++- test/testUtils/phaseInterceptor.ts | 4 +- 151 files changed, 345 insertions(+), 243 deletions(-) create mode 100644 test/abilities/shell-armor.test.ts diff --git a/.env.development b/.env.development index e4e5053016f..91c228d6761 100644 --- a/.env.development +++ b/.env.development @@ -1,6 +1,7 @@ VITE_BYPASS_LOGIN=1 VITE_BYPASS_TUTORIAL=0 VITE_SERVER_URL=http://localhost:8001 +# IDs for discord/google auth go unused due to VITE_BYPASS_LOGIN VITE_DISCORD_CLIENT_ID=1234567890 VITE_GOOGLE_CLIENT_ID=1234567890 VITE_I18N_DEBUG=0 diff --git a/scripts/create-test/create-test.js b/scripts/create-test/create-test.js index e71994472f3..d5cac5cd408 100644 --- a/scripts/create-test/create-test.js +++ b/scripts/create-test/create-test.js @@ -48,7 +48,7 @@ async function promptTestType() { { type: "list", name: "selectedOption", - message: "What type of test would you like to create:", + message: "What type of test would you like to create?", choices: [...choices.map(choice => ({ name: choice.label, value: choice })), "EXIT"], }, ]); diff --git a/scripts/create-test/test-boilerplate.ts b/scripts/create-test/test-boilerplate.ts index 40912be6c5f..1fbd3c8040a 100644 --- a/scripts/create-test/test-boilerplate.ts +++ b/scripts/create-test/test-boilerplate.ts @@ -24,7 +24,7 @@ describe("{{description}}", () => { game.override .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 81c65d85e06..14b0c6a3d19 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2094,12 +2094,15 @@ export default class BattleScene extends SceneBase { } getMaxExpLevel(ignoreLevelCap = false): number { - if (Overrides.LEVEL_CAP_OVERRIDE > 0) { - return Overrides.LEVEL_CAP_OVERRIDE; + const capOverride = Overrides.LEVEL_CAP_OVERRIDE ?? 0; + if (capOverride > 0) { + return capOverride; } - if (ignoreLevelCap || Overrides.LEVEL_CAP_OVERRIDE < 0) { + + if (ignoreLevelCap || capOverride < 0) { return Number.MAX_SAFE_INTEGER; } + const waveIndex = Math.ceil((this.currentBattle?.waveIndex || 1) / 10) * 10; const difficultyWaveIndex = this.gameMode.getWaveForDifficulty(waveIndex); const baseLevel = (1 + difficultyWaveIndex / 2 + Math.pow(difficultyWaveIndex / 25, 2)) * 1.2; @@ -3492,17 +3495,13 @@ export default class BattleScene extends SceneBase { sessionEncounterRate + Math.min(currentRunDiffFromAvg * ANTI_VARIANCE_WEIGHT_MODIFIER, MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT / 2); - const successRate = isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE) - ? favoredEncounterRate - : Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE!; + const successRate = Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE ?? favoredEncounterRate; - // If the most recent ME was 3 or fewer waves ago, can never spawn a ME + // MEs can only spawn 3 or more waves after the previous ME, barring overrides const canSpawn = - encounteredEvents.length === 0 || - waveIndex - encounteredEvents[encounteredEvents.length - 1].waveIndex > 3 || - !isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE); + encounteredEvents.length === 0 || waveIndex - encounteredEvents[encounteredEvents.length - 1].waveIndex > 3; - if (canSpawn) { + if (canSpawn || Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE !== null) { let roll = MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT; // Always rolls the check on the same offset to ensure no RNG changes from reloading session this.executeWithSeedOffset( diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index c9684986c46..3124f782ff5 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -4617,7 +4617,7 @@ export class ConditionalUserFieldProtectStatAbAttr extends PreStatStageChangeAbA * @param stat The stat being affected * @param cancelled Holds whether the stat change was already prevented. * @param args Args[0] is the target pokemon of the stat change. - * @returns + * @returns `true` if the ability can be applied */ override canApplyPreStatStageChange( _pokemon: Pokemon, @@ -4778,17 +4778,17 @@ export class BlockCritAbAttr extends AbAttr { } /** - * Apply the block crit ability by setting the value in the provided boolean holder to false - * @param args - [0] is a boolean holder representing whether the attack can crit + * Apply the block crit ability by setting the value in the provided boolean holder to `true`. + * @param args - `[0]`: A {@linkcode BooleanHolder} containing whether the attack is prevented from critting. */ override apply( _pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: BooleanHolder, - args: [BooleanHolder, ...any], + args: [BooleanHolder], ): void { - args[0].value = false; + args[0].value = true; } } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 24399504eba..fe89142b5c6 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1356,8 +1356,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * Calculate the critical-hit stage of a move used against this pokemon by - * the given source + * Calculate the critical-hit stage of a move used **against** this pokemon by + * the given source. + * * @param source - The {@linkcode Pokemon} using the move * @param move - The {@linkcode Move} being used * @returns The final critical-hit stage value @@ -1370,11 +1371,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyAbAttrs("BonusCritAbAttr", source, null, false, critStage); const critBoostTag = source.getTag(CritBoostTag); if (critBoostTag) { - if (critBoostTag instanceof DragonCheerTag) { - critStage.value += critBoostTag.typesOnAdd.includes(PokemonType.DRAGON) ? 2 : 1; - } else { - critStage.value += 2; - } + // Dragon cheer only gives +1 crit stage to non-dragon types + critStage.value += + critBoostTag instanceof DragonCheerTag && !critBoostTag.typesOnAdd.includes(PokemonType.DRAGON) ? 1 : 2; } console.log(`crit stage: +${critStage.value}`); @@ -3878,33 +3877,39 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { }; } - /** Calculate whether the given move critically hits this pokemon + /** + * Determine whether the given move will score a critical hit **against** this Pokemon. * @param source - The {@linkcode Pokemon} using the move * @param move - The {@linkcode Move} being used - * @param simulated - If `true`, suppresses changes to game state during calculation (defaults to `true`) - * @returns whether the move critically hits the pokemon + * @returns Whether the move will critically hit the defender. */ - getCriticalHitResult(source: Pokemon, move: Move, simulated = true): boolean { - const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - const noCritTag = globalScene.arena.getTagOnSide(NoCritTag, defendingSide); - if (noCritTag || Overrides.NEVER_CRIT_OVERRIDE || move.hasAttr("FixedDamageAttr")) { + getCriticalHitResult(source: Pokemon, move: Move): boolean { + if (move.hasAttr("FixedDamageAttr")) { + // fixed damage moves (Dragon Rage, etc.) will nevet crit return false; } - const isCritical = new BooleanHolder(false); - if (source.getTag(BattlerTagType.ALWAYS_CRIT)) { - isCritical.value = true; - } - applyMoveAttrs("CritOnlyAttr", source, this, move, isCritical); - applyAbAttrs("ConditionalCritAbAttr", source, null, simulated, isCritical, this, move); - if (!isCritical.value) { - const critChance = [24, 8, 2, 1][Math.max(0, Math.min(this.getCritStage(source, move), 3))]; - isCritical.value = critChance === 1 || !globalScene.randBattleSeedInt(critChance); - } + const alwaysCrit = new BooleanHolder(false); + applyMoveAttrs("CritOnlyAttr", source, this, move, alwaysCrit); + applyAbAttrs("ConditionalCritAbAttr", source, null, false, alwaysCrit, this, move); + const alwaysCritTag = !!source.getTag(BattlerTagType.ALWAYS_CRIT); + const critChance = [24, 8, 2, 1][Phaser.Math.Clamp(this.getCritStage(source, move), 0, 3)]; - applyAbAttrs("BlockCritAbAttr", this, null, simulated, isCritical); + let isCritical = alwaysCrit.value || alwaysCritTag || critChance === 1; - return isCritical.value; + // If we aren't already guaranteed to crit, do a random roll & check overrides + isCritical ||= Overrides.CRITICAL_HIT_OVERRIDE ?? globalScene.randBattleSeedInt(critChance) === 0; + + // apply crit block effects from lucky chant & co., overriding previous effects + const blockCrit = new BooleanHolder(false); + applyAbAttrs("BlockCritAbAttr", this, null, false, blockCrit); + const blockCritTag = globalScene.arena.getTagOnSide( + NoCritTag, + this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, + ); + isCritical &&= !blockCritTag && !blockCrit.value; // need to roll a crit and not be blocked by either crit prevention effect + + return isCritical; } /** diff --git a/src/game-mode.ts b/src/game-mode.ts index a6fc8195175..9722d564e09 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -90,13 +90,14 @@ export class GameMode implements GameModeConfig { } /** + * Helper function to get starting level for game mode. * @returns either: - * - override from overrides.ts + * - starting level override from overrides.ts * - 20 for Daily Runs * - 5 for all other modes */ getStartingLevel(): number { - if (Overrides.STARTING_LEVEL_OVERRIDE) { + if (Overrides.STARTING_LEVEL_OVERRIDE > 0) { return Overrides.STARTING_LEVEL_OVERRIDE; } switch (this.modeId) { diff --git a/src/overrides.ts b/src/overrides.ts index 5858be77dac..b390b9fa70f 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -80,7 +80,11 @@ class DefaultOverrides { /** Sets the level cap to this number during experience gain calculations. Set to `0` to disable override & use normal wave-based level caps, or any negative number to set it to 9 quadrillion (effectively disabling it). */ readonly LEVEL_CAP_OVERRIDE: number = 0; - readonly NEVER_CRIT_OVERRIDE: boolean = false; + /** + * If defined, overrides random critical hit rolls to always or never succeed. + * Ignored if the move is guaranteed to always/never crit. + */ + readonly CRITICAL_HIT_OVERRIDE: boolean | null = null; /** default 1000 */ readonly STARTING_MONEY_OVERRIDE: number = 0; /** Sets all shop item prices to 0 */ diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 35c803ee1d7..d7da1ab996c 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -821,7 +821,7 @@ export class MoveEffectPhase extends PokemonPhase { * @param effectiveness - The effectiveness of the move against the target */ protected applyMoveDamage(user: Pokemon, target: Pokemon, effectiveness: TypeDamageMultiplier): HitResult { - const isCritical = target.getCriticalHitResult(user, this.move, false); + const isCritical = target.getCriticalHitResult(user, this.move); /* * Apply stat changes from {@linkcode move} and gives it to {@linkcode source} diff --git a/test/abilities/ability_activation_order.test.ts b/test/abilities/ability_activation_order.test.ts index 04adf40b623..0b8e354cc7d 100644 --- a/test/abilities/ability_activation_order.test.ts +++ b/test/abilities/ability_activation_order.test.ts @@ -27,7 +27,7 @@ describe("Ability Activation Order", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/analytic.test.ts b/test/abilities/analytic.test.ts index 2cfea64d20e..5dbf4365186 100644 --- a/test/abilities/analytic.test.ts +++ b/test/abilities/analytic.test.ts @@ -27,7 +27,7 @@ describe("Abilities - Analytic", () => { .moveset([MoveId.SPLASH, MoveId.TACKLE]) .ability(AbilityId.ANALYTIC) .battleStyle("single") - .disableCrits() + .criticalHits(false) .startingLevel(200) .enemyLevel(200) .enemySpecies(SpeciesId.SNORLAX) diff --git a/test/abilities/commander.test.ts b/test/abilities/commander.test.ts index 39d9d0f4d7f..99512d7a733 100644 --- a/test/abilities/commander.test.ts +++ b/test/abilities/commander.test.ts @@ -35,7 +35,7 @@ describe("Abilities - Commander", () => { .moveset([MoveId.LIQUIDATION, MoveId.MEMENTO, MoveId.SPLASH, MoveId.FLIP_TURN]) .ability(AbilityId.COMMANDER) .battleStyle("double") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.SNORLAX) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.TACKLE); diff --git a/test/abilities/corrosion.test.ts b/test/abilities/corrosion.test.ts index 81b04ef340c..2a0464d1146 100644 --- a/test/abilities/corrosion.test.ts +++ b/test/abilities/corrosion.test.ts @@ -24,7 +24,7 @@ describe("Abilities - Corrosion", () => { game.override .moveset([MoveId.SPLASH]) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.GRIMER) .enemyAbility(AbilityId.CORROSION) .enemyMoveset(MoveId.TOXIC); diff --git a/test/abilities/cud_chew.test.ts b/test/abilities/cud_chew.test.ts index 8a80c6625cc..70c282bf8a8 100644 --- a/test/abilities/cud_chew.test.ts +++ b/test/abilities/cud_chew.test.ts @@ -33,7 +33,7 @@ describe("Abilities - Cud Chew", () => { .startingHeldItems([{ name: "BERRY", type: BerryType.SITRUS, count: 1 }]) .ability(AbilityId.CUD_CHEW) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/dry_skin.test.ts b/test/abilities/dry_skin.test.ts index 549bb45ba9a..31620beb9bd 100644 --- a/test/abilities/dry_skin.test.ts +++ b/test/abilities/dry_skin.test.ts @@ -23,7 +23,7 @@ describe("Abilities - Dry Skin", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemyAbility(AbilityId.DRY_SKIN) .enemyMoveset(MoveId.SPLASH) .enemySpecies(SpeciesId.CHARMANDER) diff --git a/test/abilities/early_bird.test.ts b/test/abilities/early_bird.test.ts index e19f2653b57..dcdfd8e4956 100644 --- a/test/abilities/early_bird.test.ts +++ b/test/abilities/early_bird.test.ts @@ -28,7 +28,7 @@ describe("Abilities - Early Bird", () => { .moveset([MoveId.REST, MoveId.BELLY_DRUM, MoveId.SPLASH]) .ability(AbilityId.EARLY_BIRD) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/flash_fire.test.ts b/test/abilities/flash_fire.test.ts index fe17013f49f..3be156a1578 100644 --- a/test/abilities/flash_fire.test.ts +++ b/test/abilities/flash_fire.test.ts @@ -32,7 +32,7 @@ describe("Abilities - Flash Fire", () => { .enemyAbility(AbilityId.BALL_FETCH) .startingLevel(20) .enemyLevel(20) - .disableCrits(); + .criticalHits(false); }); it("immune to Fire-type moves", async () => { diff --git a/test/abilities/flower_veil.test.ts b/test/abilities/flower_veil.test.ts index d3e99185c2f..7cabfa23e96 100644 --- a/test/abilities/flower_veil.test.ts +++ b/test/abilities/flower_veil.test.ts @@ -32,7 +32,7 @@ describe("Abilities - Flower Veil", () => { .enemySpecies(SpeciesId.BULBASAUR) .ability(AbilityId.FLOWER_VEIL) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/good_as_gold.test.ts b/test/abilities/good_as_gold.test.ts index 3b6600eabd4..89e354b1f34 100644 --- a/test/abilities/good_as_gold.test.ts +++ b/test/abilities/good_as_gold.test.ts @@ -33,7 +33,7 @@ describe("Abilities - Good As Gold", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.GOOD_AS_GOLD) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/gulp_missile.test.ts b/test/abilities/gulp_missile.test.ts index b41eea9ce18..e47b22f0c06 100644 --- a/test/abilities/gulp_missile.test.ts +++ b/test/abilities/gulp_missile.test.ts @@ -41,7 +41,7 @@ describe("Abilities - Gulp Missile", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .disableCrits() + .criticalHits(false) .battleStyle("single") .moveset([MoveId.SURF, MoveId.DIVE, MoveId.SPLASH, MoveId.SUBSTITUTE]) .enemySpecies(SpeciesId.SNORLAX) diff --git a/test/abilities/harvest.test.ts b/test/abilities/harvest.test.ts index 40e6a6abd62..662eeed6dd0 100644 --- a/test/abilities/harvest.test.ts +++ b/test/abilities/harvest.test.ts @@ -47,7 +47,7 @@ describe("Abilities - Harvest", () => { .ability(AbilityId.HARVEST) .startingLevel(100) .battleStyle("single") - .disableCrits() + .criticalHits(false) .statusActivation(false) // Since we're using nuzzle to proc both enigma and sitrus berries .weather(WeatherType.SUNNY) // guaranteed recovery .enemyLevel(1) diff --git a/test/abilities/healer.test.ts b/test/abilities/healer.test.ts index 94272848e16..b37c9effeb0 100644 --- a/test/abilities/healer.test.ts +++ b/test/abilities/healer.test.ts @@ -30,7 +30,7 @@ describe("Abilities - Healer", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("double") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/heatproof.test.ts b/test/abilities/heatproof.test.ts index f705e8bf785..39c9dff8289 100644 --- a/test/abilities/heatproof.test.ts +++ b/test/abilities/heatproof.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Heatproof", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.CHARMANDER) .enemyAbility(AbilityId.HEATPROOF) .enemyMoveset(MoveId.SPLASH) diff --git a/test/abilities/honey_gather.test.ts b/test/abilities/honey_gather.test.ts index 06d351b2a61..f8700f3e6f7 100644 --- a/test/abilities/honey_gather.test.ts +++ b/test/abilities/honey_gather.test.ts @@ -29,7 +29,7 @@ describe("Abilities - Honey Gather", () => { .ability(AbilityId.HONEY_GATHER) .passiveAbility(AbilityId.RUN_AWAY) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/hustle.test.ts b/test/abilities/hustle.test.ts index 7ac636b15b2..57a1a2e1d86 100644 --- a/test/abilities/hustle.test.ts +++ b/test/abilities/hustle.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Hustle", () => { game.override .ability(AbilityId.HUSTLE) .moveset([MoveId.TACKLE, MoveId.GIGA_DRAIN, MoveId.FISSURE]) - .disableCrits() + .criticalHits(false) .battleStyle("single") .enemyMoveset(MoveId.SPLASH) .enemySpecies(SpeciesId.SHUCKLE) diff --git a/test/abilities/immunity.test.ts b/test/abilities/immunity.test.ts index b6ca34bfaa3..063ab57f11a 100644 --- a/test/abilities/immunity.test.ts +++ b/test/abilities/immunity.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Immunity", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/infiltrator.test.ts b/test/abilities/infiltrator.test.ts index 03b55e925cd..a5de035a136 100644 --- a/test/abilities/infiltrator.test.ts +++ b/test/abilities/infiltrator.test.ts @@ -31,7 +31,7 @@ describe("Abilities - Infiltrator", () => { .moveset([MoveId.TACKLE, MoveId.WATER_GUN, MoveId.SPORE, MoveId.BABY_DOLL_EYES]) .ability(AbilityId.INFILTRATOR) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.SNORLAX) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH) diff --git a/test/abilities/insomnia.test.ts b/test/abilities/insomnia.test.ts index 418e0ed1345..800663823c3 100644 --- a/test/abilities/insomnia.test.ts +++ b/test/abilities/insomnia.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Insomnia", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/lightningrod.test.ts b/test/abilities/lightningrod.test.ts index a69f5e94067..2dc29500454 100644 --- a/test/abilities/lightningrod.test.ts +++ b/test/abilities/lightningrod.test.ts @@ -27,7 +27,7 @@ describe("Abilities - Lightningrod", () => { .moveset([MoveId.SPLASH, MoveId.SHOCK_WAVE]) .ability(AbilityId.BALL_FETCH) .battleStyle("double") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/limber.test.ts b/test/abilities/limber.test.ts index 2ca469dcaa1..77acd8b3dab 100644 --- a/test/abilities/limber.test.ts +++ b/test/abilities/limber.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Limber", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/magic_bounce.test.ts b/test/abilities/magic_bounce.test.ts index fb4f273950a..04bfcbbfe8f 100644 --- a/test/abilities/magic_bounce.test.ts +++ b/test/abilities/magic_bounce.test.ts @@ -32,7 +32,7 @@ describe("Abilities - Magic Bounce", () => { .ability(AbilityId.BALL_FETCH) .battleStyle("single") .moveset([MoveId.GROWL, MoveId.SPLASH]) - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.MAGIC_BOUNCE) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/magic_guard.test.ts b/test/abilities/magic_guard.test.ts index 4274e692a2e..f135a761bba 100644 --- a/test/abilities/magic_guard.test.ts +++ b/test/abilities/magic_guard.test.ts @@ -89,9 +89,7 @@ describe("Abilities - Magic Guard", () => { }); it("ability effect should not persist when the ability is replaced", async () => { - game.override - .enemyMoveset([MoveId.WORRY_SEED, MoveId.WORRY_SEED, MoveId.WORRY_SEED, MoveId.WORRY_SEED]) - .statusEffect(StatusEffect.POISON); + game.override.enemyMoveset(MoveId.WORRY_SEED).statusEffect(StatusEffect.POISON); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); diff --git a/test/abilities/magma_armor.test.ts b/test/abilities/magma_armor.test.ts index 74493fac365..0a8f3024291 100644 --- a/test/abilities/magma_armor.test.ts +++ b/test/abilities/magma_armor.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Magma Armor", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/mimicry.test.ts b/test/abilities/mimicry.test.ts index df6facc3755..7ac7aeb599f 100644 --- a/test/abilities/mimicry.test.ts +++ b/test/abilities/mimicry.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Mimicry", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.MIMICRY) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyMoveset(MoveId.SPLASH); }); diff --git a/test/abilities/mold_breaker.test.ts b/test/abilities/mold_breaker.test.ts index e2e8a73da9d..28a077e8908 100644 --- a/test/abilities/mold_breaker.test.ts +++ b/test/abilities/mold_breaker.test.ts @@ -27,7 +27,7 @@ describe("Abilities - Mold Breaker", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.MOLD_BREAKER) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/mummy.test.ts b/test/abilities/mummy.test.ts index 2b35e801677..d6c40541d25 100644 --- a/test/abilities/mummy.test.ts +++ b/test/abilities/mummy.test.ts @@ -25,7 +25,7 @@ describe("Abilities - Mummy", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.MUMMY) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.TACKLE); diff --git a/test/abilities/mycelium_might.test.ts b/test/abilities/mycelium_might.test.ts index 7bfb3fb7b81..2cdda4353b5 100644 --- a/test/abilities/mycelium_might.test.ts +++ b/test/abilities/mycelium_might.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Mycelium Might", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.SHUCKLE) .enemyAbility(AbilityId.CLEAR_BODY) diff --git a/test/abilities/neutralizing_gas.test.ts b/test/abilities/neutralizing_gas.test.ts index 51d2bed3ff0..2408a78f11d 100644 --- a/test/abilities/neutralizing_gas.test.ts +++ b/test/abilities/neutralizing_gas.test.ts @@ -31,7 +31,7 @@ describe("Abilities - Neutralizing Gas", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.NEUTRALIZING_GAS) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/normalize.test.ts b/test/abilities/normalize.test.ts index 2e000e76a27..1a4b93f3f32 100644 --- a/test/abilities/normalize.test.ts +++ b/test/abilities/normalize.test.ts @@ -29,7 +29,7 @@ describe("Abilities - Normalize", () => { .moveset([MoveId.TACKLE]) .ability(AbilityId.NORMALIZE) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/oblivious.test.ts b/test/abilities/oblivious.test.ts index e340d3c867d..dab8394edd0 100644 --- a/test/abilities/oblivious.test.ts +++ b/test/abilities/oblivious.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Oblivious", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/own_tempo.test.ts b/test/abilities/own_tempo.test.ts index 1d6d8aa76e5..869953b4e93 100644 --- a/test/abilities/own_tempo.test.ts +++ b/test/abilities/own_tempo.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Own Tempo", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/parental_bond.test.ts b/test/abilities/parental_bond.test.ts index f9bd703d751..fd914b86100 100644 --- a/test/abilities/parental_bond.test.ts +++ b/test/abilities/parental_bond.test.ts @@ -28,7 +28,7 @@ describe("Abilities - Parental Bond", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .ability(AbilityId.PARENTAL_BOND) .enemySpecies(SpeciesId.SNORLAX) .enemyAbility(AbilityId.FUR_COAT) diff --git a/test/abilities/perish_body.test.ts b/test/abilities/perish_body.test.ts index 5b98aab5f6c..4852533816b 100644 --- a/test/abilities/perish_body.test.ts +++ b/test/abilities/perish_body.test.ts @@ -23,13 +23,13 @@ describe("Abilities - Perish Song", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .starterSpecies(SpeciesId.CURSOLA) .ability(AbilityId.PERISH_BODY) - .moveset([MoveId.SPLASH]) - .enemyMoveset([MoveId.AQUA_JET]); + .moveset(MoveId.SPLASH) + .enemyMoveset(MoveId.AQUA_JET); }); it("should trigger when hit with damaging move", async () => { diff --git a/test/abilities/protosynthesis.test.ts b/test/abilities/protosynthesis.test.ts index ba7bb5d084b..ef860fc0cd5 100644 --- a/test/abilities/protosynthesis.test.ts +++ b/test/abilities/protosynthesis.test.ts @@ -28,7 +28,7 @@ describe("Abilities - Protosynthesis", () => { .moveset([MoveId.SPLASH, MoveId.TACKLE]) .ability(AbilityId.PROTOSYNTHESIS) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/sand_spit.test.ts b/test/abilities/sand_spit.test.ts index 62d5d782fc5..2b519745649 100644 --- a/test/abilities/sand_spit.test.ts +++ b/test/abilities/sand_spit.test.ts @@ -24,7 +24,7 @@ describe("Abilities - Sand Spit", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .starterSpecies(SpeciesId.SILICOBRA) diff --git a/test/abilities/sap_sipper.test.ts b/test/abilities/sap_sipper.test.ts index 16559fb563f..c70730f0711 100644 --- a/test/abilities/sap_sipper.test.ts +++ b/test/abilities/sap_sipper.test.ts @@ -31,7 +31,7 @@ describe("Abilities - Sap Sipper", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .ability(AbilityId.SAP_SIPPER) .enemySpecies(SpeciesId.RATTATA) .enemyAbility(AbilityId.SAP_SIPPER) diff --git a/test/abilities/seed_sower.test.ts b/test/abilities/seed_sower.test.ts index 4b649275d83..7a898f5bcf9 100644 --- a/test/abilities/seed_sower.test.ts +++ b/test/abilities/seed_sower.test.ts @@ -24,12 +24,12 @@ describe("Abilities - Seed Sower", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .starterSpecies(SpeciesId.ARBOLIVA) .ability(AbilityId.SEED_SOWER) - .moveset([MoveId.SPLASH]); + .moveset(MoveId.SPLASH); }); it("should trigger when hit with damaging move", async () => { diff --git a/test/abilities/serene_grace.test.ts b/test/abilities/serene_grace.test.ts index c5c12fe217e..2acf2981537 100644 --- a/test/abilities/serene_grace.test.ts +++ b/test/abilities/serene_grace.test.ts @@ -24,7 +24,7 @@ describe("Abilities - Serene Grace", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .disableCrits() + .criticalHits(false) .battleStyle("single") .ability(AbilityId.SERENE_GRACE) .moveset([MoveId.AIR_SLASH]) diff --git a/test/abilities/sheer_force.test.ts b/test/abilities/sheer_force.test.ts index b597afd8293..e90b8d08611 100644 --- a/test/abilities/sheer_force.test.ts +++ b/test/abilities/sheer_force.test.ts @@ -31,7 +31,7 @@ describe("Abilities - Sheer Force", () => { .enemySpecies(SpeciesId.ONIX) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset([MoveId.SPLASH]) - .disableCrits(); + .criticalHits(false); }); const SHEER_FORCE_MULT = 1.3; diff --git a/test/abilities/shell-armor.test.ts b/test/abilities/shell-armor.test.ts new file mode 100644 index 00000000000..ea0ed4a95dd --- /dev/null +++ b/test/abilities/shell-armor.test.ts @@ -0,0 +1,69 @@ +import type Move from "#app/data/moves/move"; +import Pokemon from "#app/field/pokemon"; +import { AbilityId } from "#enums/ability-id"; +import { MoveId } from "#enums/move-id"; +import { SpeciesId } from "#enums/species-id"; +import { StatusEffect } from "#enums/status-effect"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi, type MockInstance } from "vitest"; + +describe("Abilities - Shell Armor", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + let critSpy: MockInstance<(source: Pokemon, move: Move, simulated?: boolean) => boolean>; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .ability(AbilityId.SHELL_ARMOR) + .battleStyle("single") + .enemySpecies(SpeciesId.MAGIKARP) + .enemyAbility(AbilityId.BALL_FETCH) + .statusEffect(StatusEffect.POISON); + + critSpy = vi.spyOn(Pokemon.prototype, "getCriticalHitResult"); + }); + + it("should prevent natural crit rolls from suceeding", async () => { + game.override.criticalHits(true); // force random crit rolls to always succeed + await game.classicMode.startBattle([SpeciesId.ABOMASNOW]); + + game.move.use(MoveId.SPLASH); + await game.move.forceEnemyMove(MoveId.TACKLE); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(critSpy).toHaveReturnedWith(false); + }); + + it("should prevent guaranteed-crit moves from critting", async () => { + await game.classicMode.startBattle([SpeciesId.ABOMASNOW]); + + game.move.use(MoveId.SPLASH); + await game.move.forceEnemyMove(MoveId.FLOWER_TRICK); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(critSpy).toHaveReturnedWith(false); + }); + + it("should block Merciless guaranteed crits", async () => { + game.override.enemyAbility(AbilityId.MERCILESS); + await game.classicMode.startBattle([SpeciesId.ABOMASNOW]); + + game.move.use(MoveId.SPLASH); + await game.move.forceEnemyMove(MoveId.TACKLE); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(critSpy).toHaveReturnedWith(false); + }); +}); diff --git a/test/abilities/stakeout.test.ts b/test/abilities/stakeout.test.ts index ba4325e0295..b3844bf17ca 100644 --- a/test/abilities/stakeout.test.ts +++ b/test/abilities/stakeout.test.ts @@ -27,7 +27,7 @@ describe("Abilities - Stakeout", () => { .moveset([MoveId.SPLASH, MoveId.SURF]) .ability(AbilityId.STAKEOUT) .battleStyle("single") - .disableCrits() + .criticalHits(false) .startingLevel(100) .enemyLevel(100) .enemySpecies(SpeciesId.SNORLAX) diff --git a/test/abilities/stall.test.ts b/test/abilities/stall.test.ts index 74fd2f67f83..e8ee23fb972 100644 --- a/test/abilities/stall.test.ts +++ b/test/abilities/stall.test.ts @@ -24,7 +24,7 @@ describe("Abilities - Stall", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.REGIELEKI) .enemyAbility(AbilityId.STALL) .enemyMoveset(MoveId.QUICK_ATTACK) diff --git a/test/abilities/storm_drain.test.ts b/test/abilities/storm_drain.test.ts index 3a6c0aba100..8eedf0c7ce8 100644 --- a/test/abilities/storm_drain.test.ts +++ b/test/abilities/storm_drain.test.ts @@ -27,7 +27,7 @@ describe("Abilities - Storm Drain", () => { .moveset([MoveId.SPLASH, MoveId.WATER_GUN]) .ability(AbilityId.BALL_FETCH) .battleStyle("double") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/thermal_exchange.test.ts b/test/abilities/thermal_exchange.test.ts index f27e6da1d3b..70af864d413 100644 --- a/test/abilities/thermal_exchange.test.ts +++ b/test/abilities/thermal_exchange.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Thermal Exchange", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/trace.test.ts b/test/abilities/trace.test.ts index 9bfd2f021c8..f17bec159ab 100644 --- a/test/abilities/trace.test.ts +++ b/test/abilities/trace.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Trace", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.TRACE) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/victory_star.test.ts b/test/abilities/victory_star.test.ts index a15beac8b2c..4742dd96aa6 100644 --- a/test/abilities/victory_star.test.ts +++ b/test/abilities/victory_star.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Victory Star", () => { game.override .moveset([MoveId.TACKLE, MoveId.SPLASH]) .battleStyle("double") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/vital_spirit.test.ts b/test/abilities/vital_spirit.test.ts index c32454e9d31..18764f94a6b 100644 --- a/test/abilities/vital_spirit.test.ts +++ b/test/abilities/vital_spirit.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Vital Spirit", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/volt_absorb.test.ts b/test/abilities/volt_absorb.test.ts index 707a889f951..6bea70ee2a4 100644 --- a/test/abilities/volt_absorb.test.ts +++ b/test/abilities/volt_absorb.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Volt Absorb", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single").disableCrits(); + game.override.battleStyle("single").criticalHits(false); }); it("does not activate when CHARGE is used", async () => { diff --git a/test/abilities/wandering_spirit.test.ts b/test/abilities/wandering_spirit.test.ts index 360eedda4c9..950cec6d27c 100644 --- a/test/abilities/wandering_spirit.test.ts +++ b/test/abilities/wandering_spirit.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Wandering Spirit", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.WANDERING_SPIRIT) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.TACKLE); diff --git a/test/abilities/water_bubble.test.ts b/test/abilities/water_bubble.test.ts index 412c4a25035..455a2e368c4 100644 --- a/test/abilities/water_bubble.test.ts +++ b/test/abilities/water_bubble.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Water Bubble", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/water_veil.test.ts b/test/abilities/water_veil.test.ts index e67287d250f..2df06ec1a21 100644 --- a/test/abilities/water_veil.test.ts +++ b/test/abilities/water_veil.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Water Veil", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/wimp_out.test.ts b/test/abilities/wimp_out.test.ts index 05c848a75c0..1db0b80fcd0 100644 --- a/test/abilities/wimp_out.test.ts +++ b/test/abilities/wimp_out.test.ts @@ -39,7 +39,7 @@ describe("Abilities - Wimp Out", () => { .enemyLevel(70) .moveset([MoveId.SPLASH, MoveId.FALSE_SWIPE, MoveId.ENDURE]) .enemyMoveset(MoveId.FALSE_SWIPE) - .disableCrits(); + .criticalHits(false); }); function confirmSwitch(): void { diff --git a/test/abilities/zen_mode.test.ts b/test/abilities/zen_mode.test.ts index 70d2f564218..5df2b3a6bc7 100644 --- a/test/abilities/zen_mode.test.ts +++ b/test/abilities/zen_mode.test.ts @@ -27,7 +27,7 @@ describe("Abilities - ZEN MODE", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyLevel(5) diff --git a/test/arena/grassy_terrain.test.ts b/test/arena/grassy_terrain.test.ts index 832f4400cf4..5f78d8f801d 100644 --- a/test/arena/grassy_terrain.test.ts +++ b/test/arena/grassy_terrain.test.ts @@ -23,7 +23,7 @@ describe("Arena - Grassy Terrain", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemyLevel(1) .enemySpecies(SpeciesId.SHUCKLE) .enemyAbility(AbilityId.STURDY) diff --git a/test/battle/ability_swap.test.ts b/test/battle/ability_swap.test.ts index 64446d703ac..3dd92576e3b 100644 --- a/test/battle/ability_swap.test.ts +++ b/test/battle/ability_swap.test.ts @@ -27,7 +27,7 @@ describe("Test Ability Swapping", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/battle/damage_calculation.test.ts b/test/battle/damage_calculation.test.ts index 555b3a9c554..19cdf6b9237 100644 --- a/test/battle/damage_calculation.test.ts +++ b/test/battle/damage_calculation.test.ts @@ -32,7 +32,7 @@ describe("Battle Mechanics - Damage Calculation", () => { .enemyMoveset(MoveId.SPLASH) .startingLevel(100) .enemyLevel(100) - .disableCrits() + .criticalHits(false) .moveset([MoveId.TACKLE, MoveId.DRAGON_RAGE, MoveId.FISSURE, MoveId.JUMP_KICK]); }); diff --git a/test/boss-pokemon.test.ts b/test/boss-pokemon.test.ts index fdfca9249f7..afcf5e8fa77 100644 --- a/test/boss-pokemon.test.ts +++ b/test/boss-pokemon.test.ts @@ -28,7 +28,7 @@ describe("Boss Pokemon / Shields", () => { game.override .battleStyle("single") .disableTrainerWaves() - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.RATTATA) .enemyMoveset(MoveId.SPLASH) .enemyHeldItems([]) diff --git a/test/data/status_effect.test.ts b/test/data/status_effect.test.ts index a5bfb33c023..e697ee3f1c7 100644 --- a/test/data/status_effect.test.ts +++ b/test/data/status_effect.test.ts @@ -356,7 +356,7 @@ describe("Status Effects", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); @@ -412,7 +412,7 @@ describe("Status Effects", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.NUZZLE) diff --git a/test/endless_boss.test.ts b/test/endless_boss.test.ts index 6814cf5e12a..d219cacf317 100644 --- a/test/endless_boss.test.ts +++ b/test/endless_boss.test.ts @@ -21,7 +21,7 @@ describe("Endless Boss", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.startingBiome(BiomeId.END).disableCrits(); + game.override.startingBiome(BiomeId.END).criticalHits(false); }); afterEach(() => { diff --git a/test/final_boss.test.ts b/test/final_boss.test.ts index d3fcd3214bb..071f83285e7 100644 --- a/test/final_boss.test.ts +++ b/test/final_boss.test.ts @@ -27,7 +27,7 @@ describe("Final Boss", () => { game.override .startingWave(FinalWave.Classic) .startingBiome(BiomeId.END) - .disableCrits() + .criticalHits(false) .enemyMoveset(MoveId.SPLASH) .moveset([MoveId.SPLASH, MoveId.WILL_O_WISP, MoveId.DRAGON_PULSE]) .startingLevel(10000); diff --git a/test/items/multi_lens.test.ts b/test/items/multi_lens.test.ts index 11e4c04ec21..8a3161970c0 100644 --- a/test/items/multi_lens.test.ts +++ b/test/items/multi_lens.test.ts @@ -28,7 +28,7 @@ describe("Items - Multi Lens", () => { .ability(AbilityId.BALL_FETCH) .startingHeldItems([{ name: "MULTI_LENS" }]) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.SNORLAX) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH) diff --git a/test/items/reviver_seed.test.ts b/test/items/reviver_seed.test.ts index 54927130869..f444a6eac66 100644 --- a/test/items/reviver_seed.test.ts +++ b/test/items/reviver_seed.test.ts @@ -29,7 +29,7 @@ describe("Items - Reviver Seed", () => { .moveset([MoveId.SPLASH, MoveId.TACKLE, MoveId.ENDURE]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .startingHeldItems([{ name: "REVIVER_SEED" }]) diff --git a/test/moves/alluring_voice.test.ts b/test/moves/alluring_voice.test.ts index 132f83cd4c1..ba096391f1b 100644 --- a/test/moves/alluring_voice.test.ts +++ b/test/moves/alluring_voice.test.ts @@ -26,7 +26,7 @@ describe("Moves - Alluring Voice", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.ICE_SCALES) .enemyMoveset(MoveId.HOWL) diff --git a/test/moves/assist.test.ts b/test/moves/assist.test.ts index bc51f8bd06a..27aa5528f17 100644 --- a/test/moves/assist.test.ts +++ b/test/moves/assist.test.ts @@ -30,7 +30,7 @@ describe("Moves - Assist", () => { game.override .ability(AbilityId.BALL_FETCH) .battleStyle("double") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyLevel(100) .enemyAbility(AbilityId.BALL_FETCH) diff --git a/test/moves/aurora_veil.test.ts b/test/moves/aurora_veil.test.ts index 98b490b0e32..b9ae79e4155 100644 --- a/test/moves/aurora_veil.test.ts +++ b/test/moves/aurora_veil.test.ts @@ -42,7 +42,7 @@ describe("Moves - Aurora Veil", () => { .enemyLevel(100) .enemySpecies(SpeciesId.MAGIKARP) .enemyMoveset(MoveId.AURORA_VEIL) - .disableCrits() + .criticalHits(false) .weather(WeatherType.HAIL); }); diff --git a/test/moves/baton_pass.test.ts b/test/moves/baton_pass.test.ts index a010636034a..1b1b0620133 100644 --- a/test/moves/baton_pass.test.ts +++ b/test/moves/baton_pass.test.ts @@ -31,7 +31,7 @@ describe("Moves - Baton Pass", () => { .moveset([MoveId.BATON_PASS, MoveId.NASTY_PLOT, MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH) - .disableCrits(); + .criticalHits(false); }); it("transfers all stat stages when player uses it", async () => { diff --git a/test/moves/burning_jealousy.test.ts b/test/moves/burning_jealousy.test.ts index 9e1898d9cd1..ce0a1ce57c9 100644 --- a/test/moves/burning_jealousy.test.ts +++ b/test/moves/burning_jealousy.test.ts @@ -26,7 +26,7 @@ describe("Moves - Burning Jealousy", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.ICE_SCALES) .enemyMoveset([MoveId.HOWL]) diff --git a/test/moves/camouflage.test.ts b/test/moves/camouflage.test.ts index 03364265179..6f28493b1e5 100644 --- a/test/moves/camouflage.test.ts +++ b/test/moves/camouflage.test.ts @@ -28,7 +28,7 @@ describe("Moves - Camouflage", () => { .moveset([MoveId.CAMOUFLAGE]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.REGIELEKI) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.PSYCHIC_TERRAIN); diff --git a/test/moves/chloroblast.test.ts b/test/moves/chloroblast.test.ts index cf5e791d0b9..6c790bac150 100644 --- a/test/moves/chloroblast.test.ts +++ b/test/moves/chloroblast.test.ts @@ -25,7 +25,7 @@ describe("Moves - Chloroblast", () => { game.override .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH); }); diff --git a/test/moves/copycat.test.ts b/test/moves/copycat.test.ts index e11a2262a43..8d135d5e2c7 100644 --- a/test/moves/copycat.test.ts +++ b/test/moves/copycat.test.ts @@ -30,7 +30,7 @@ describe("Moves - Copycat", () => { .moveset([MoveId.COPYCAT, MoveId.SPIKY_SHIELD, MoveId.SWORDS_DANCE, MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/defog.test.ts b/test/moves/defog.test.ts index d42682c7f3f..17dc0613e9b 100644 --- a/test/moves/defog.test.ts +++ b/test/moves/defog.test.ts @@ -26,7 +26,7 @@ describe("Moves - Defog", () => { .moveset([MoveId.MIST, MoveId.SAFEGUARD, MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.SHUCKLE) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset([MoveId.DEFOG, MoveId.GROWL]); diff --git a/test/moves/doodle.test.ts b/test/moves/doodle.test.ts index d4d09b3a195..2dbd5c08c8f 100644 --- a/test/moves/doodle.test.ts +++ b/test/moves/doodle.test.ts @@ -27,7 +27,7 @@ describe("Moves - Doodle", () => { .moveset([MoveId.SPLASH, MoveId.DOODLE]) .ability(AbilityId.ADAPTABILITY) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/double_team.test.ts b/test/moves/double_team.test.ts index 9a17a542f47..d1f6c5be3e4 100644 --- a/test/moves/double_team.test.ts +++ b/test/moves/double_team.test.ts @@ -26,7 +26,7 @@ describe("Moves - Double Team", () => { game.override .battleStyle("single") .moveset([MoveId.DOUBLE_TEAM]) - .disableCrits() + .criticalHits(false) .ability(AbilityId.BALL_FETCH) .enemySpecies(SpeciesId.SHUCKLE) .enemyAbility(AbilityId.BALL_FETCH) diff --git a/test/moves/dragon_rage.test.ts b/test/moves/dragon_rage.test.ts index f0be6748c7f..c5bed3377fa 100644 --- a/test/moves/dragon_rage.test.ts +++ b/test/moves/dragon_rage.test.ts @@ -51,7 +51,7 @@ describe("Moves - Dragon Rage", () => { }); it("ignores weaknesses", async () => { - game.override.disableCrits(); + game.override.criticalHits(false); vi.spyOn(enemyPokemon, "getTypes").mockReturnValue([PokemonType.DRAGON]); game.move.select(MoveId.DRAGON_RAGE); @@ -61,7 +61,7 @@ describe("Moves - Dragon Rage", () => { }); it("ignores resistances", async () => { - game.override.disableCrits(); + game.override.criticalHits(false); vi.spyOn(enemyPokemon, "getTypes").mockReturnValue([PokemonType.STEEL]); game.move.select(MoveId.DRAGON_RAGE); @@ -71,7 +71,7 @@ describe("Moves - Dragon Rage", () => { }); it("ignores SPATK stat stages", async () => { - game.override.disableCrits(); + game.override.criticalHits(false); partyPokemon.setStatStage(Stat.SPATK, 2); game.move.select(MoveId.DRAGON_RAGE); @@ -81,7 +81,7 @@ describe("Moves - Dragon Rage", () => { }); it("ignores stab", async () => { - game.override.disableCrits(); + game.override.criticalHits(false); vi.spyOn(partyPokemon, "getTypes").mockReturnValue([PokemonType.DRAGON]); game.move.select(MoveId.DRAGON_RAGE); @@ -100,7 +100,7 @@ describe("Moves - Dragon Rage", () => { }); it("ignores damage modification from abilities, for example ICE_SCALES", async () => { - game.override.disableCrits().enemyAbility(AbilityId.ICE_SCALES); + game.override.criticalHits(false).enemyAbility(AbilityId.ICE_SCALES); game.move.select(MoveId.DRAGON_RAGE); await game.phaseInterceptor.to(TurnEndPhase); diff --git a/test/moves/dynamax_cannon.test.ts b/test/moves/dynamax_cannon.test.ts index bf8a63f044c..3febf918de8 100644 --- a/test/moves/dynamax_cannon.test.ts +++ b/test/moves/dynamax_cannon.test.ts @@ -32,13 +32,13 @@ describe("Moves - Dynamax Cannon", () => { game.override .moveset(MoveId.DYNAMAX_CANNON) .startingLevel(200) - .levelCap(10) + .levelCap(100) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyMoveset(MoveId.SPLASH); - vi.spyOn(allMoves[MoveId.DYNAMAX_CANNON], "calculateBattlePower"); + vi.spyOn(dynamaxCannon, "calculateBattlePower"); }); it("should return 100 power against an enemy below level cap", async () => { @@ -54,7 +54,7 @@ describe("Moves - Dynamax Cannon", () => { }); it("should return 100 power against an enemy at level cap", async () => { - game.override.enemyLevel(10); + game.override.enemyLevel(100); await game.classicMode.startBattle([SpeciesId.ETERNATUS]); game.move.select(dynamaxCannon.id); diff --git a/test/moves/encore.test.ts b/test/moves/encore.test.ts index 120d065d528..772b7c96060 100644 --- a/test/moves/encore.test.ts +++ b/test/moves/encore.test.ts @@ -28,7 +28,7 @@ describe("Moves - Encore", () => { .moveset([MoveId.SPLASH, MoveId.ENCORE]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset([MoveId.SPLASH, MoveId.TACKLE]) diff --git a/test/moves/endure.test.ts b/test/moves/endure.test.ts index 799be22ba45..7738f7426fe 100644 --- a/test/moves/endure.test.ts +++ b/test/moves/endure.test.ts @@ -26,7 +26,7 @@ describe("Moves - Endure", () => { .ability(AbilityId.SKILL_LINK) .startingLevel(100) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.NO_GUARD) .enemyMoveset(MoveId.ENDURE); diff --git a/test/moves/entrainment.test.ts b/test/moves/entrainment.test.ts index 0a0cbd3b5f9..65586149492 100644 --- a/test/moves/entrainment.test.ts +++ b/test/moves/entrainment.test.ts @@ -26,7 +26,7 @@ describe("Moves - Entrainment", () => { .moveset([MoveId.SPLASH, MoveId.ENTRAINMENT]) .ability(AbilityId.ADAPTABILITY) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/fairy_lock.test.ts b/test/moves/fairy_lock.test.ts index 74524d67b38..b9f854cdc93 100644 --- a/test/moves/fairy_lock.test.ts +++ b/test/moves/fairy_lock.test.ts @@ -27,7 +27,7 @@ describe("Moves - Fairy Lock", () => { .moveset([MoveId.FAIRY_LOCK, MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("double") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset([MoveId.SPLASH, MoveId.U_TURN]); diff --git a/test/moves/fake_out.test.ts b/test/moves/fake_out.test.ts index 95f69fe792e..4fc83e5aa07 100644 --- a/test/moves/fake_out.test.ts +++ b/test/moves/fake_out.test.ts @@ -27,7 +27,7 @@ describe("Moves - Fake Out", () => { .enemyMoveset(MoveId.SPLASH) .enemyLevel(10) .startingLevel(1) // prevent LevelUpPhase from happening - .disableCrits(); + .criticalHits(false); }); it("should only work the first turn a pokemon is sent out in a battle", async () => { diff --git a/test/moves/false_swipe.test.ts b/test/moves/false_swipe.test.ts index c98b76f1ef1..bf9c8307c22 100644 --- a/test/moves/false_swipe.test.ts +++ b/test/moves/false_swipe.test.ts @@ -27,7 +27,7 @@ describe("Moves - False Swipe", () => { .ability(AbilityId.BALL_FETCH) .startingLevel(1000) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/fell_stinger.test.ts b/test/moves/fell_stinger.test.ts index d5081a3ba30..0737db9105f 100644 --- a/test/moves/fell_stinger.test.ts +++ b/test/moves/fell_stinger.test.ts @@ -30,7 +30,7 @@ describe("Moves - Fell Stinger", () => { .battleStyle("single") .moveset([MoveId.FELL_STINGER, MoveId.SALT_CURE, MoveId.BIND, MoveId.LEECH_SEED]) .startingLevel(50) - .disableCrits() + .criticalHits(false) .enemyAbility(AbilityId.STURDY) .enemySpecies(SpeciesId.HYPNO) .enemyMoveset(MoveId.SPLASH) diff --git a/test/moves/fissure.test.ts b/test/moves/fissure.test.ts index 231eab427e1..27031a7736d 100644 --- a/test/moves/fissure.test.ts +++ b/test/moves/fissure.test.ts @@ -30,9 +30,9 @@ describe("Moves - Fissure", () => { game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .starterSpecies(SpeciesId.SNORLAX) - .moveset([MoveId.FISSURE]) + .moveset(MoveId.FISSURE) .passiveAbility(AbilityId.BALL_FETCH) .startingLevel(100) .enemySpecies(SpeciesId.SNORLAX) diff --git a/test/moves/flame_burst.test.ts b/test/moves/flame_burst.test.ts index e994dc3e568..0a378df1077 100644 --- a/test/moves/flame_burst.test.ts +++ b/test/moves/flame_burst.test.ts @@ -38,12 +38,12 @@ describe("Moves - Flame Burst", () => { game.override .battleStyle("double") .moveset([MoveId.FLAME_BURST, MoveId.SPLASH]) - .disableCrits() + .criticalHits(false) .ability(AbilityId.UNNERVE) .startingWave(4) .enemySpecies(SpeciesId.SHUCKLE) .enemyAbility(AbilityId.BALL_FETCH) - .enemyMoveset([MoveId.SPLASH]); + .enemyMoveset(MoveId.SPLASH); }); it("inflicts damage to the target's ally equal to 1/16 of its max HP", async () => { diff --git a/test/moves/foresight.test.ts b/test/moves/foresight.test.ts index a30b4d6a444..96a341582f9 100644 --- a/test/moves/foresight.test.ts +++ b/test/moves/foresight.test.ts @@ -22,7 +22,7 @@ describe("Moves - Foresight", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.GASTLY) .enemyMoveset(MoveId.SPLASH) .enemyLevel(5) diff --git a/test/moves/forests_curse.test.ts b/test/moves/forests_curse.test.ts index 77fec5d277d..81ced674a33 100644 --- a/test/moves/forests_curse.test.ts +++ b/test/moves/forests_curse.test.ts @@ -26,7 +26,7 @@ describe("Moves - Forest's Curse", () => { .moveset([MoveId.FORESTS_CURSE, MoveId.TRICK_OR_TREAT]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/fusion_bolt.test.ts b/test/moves/fusion_bolt.test.ts index cb3d75f56eb..c92a483a497 100644 --- a/test/moves/fusion_bolt.test.ts +++ b/test/moves/fusion_bolt.test.ts @@ -31,7 +31,7 @@ describe("Moves - Fusion Bolt", () => { .enemyMoveset(MoveId.SPLASH) .battleStyle("single") .startingWave(97) - .disableCrits(); + .criticalHits(false); }); it("should not make contact", async () => { diff --git a/test/moves/fusion_flare.test.ts b/test/moves/fusion_flare.test.ts index df6d390686f..c0df347fcce 100644 --- a/test/moves/fusion_flare.test.ts +++ b/test/moves/fusion_flare.test.ts @@ -31,7 +31,7 @@ describe("Moves - Fusion Flare", () => { .enemyMoveset(MoveId.REST) .battleStyle("single") .startingWave(97) - .disableCrits(); + .criticalHits(false); }); it("should thaw freeze status condition", async () => { diff --git a/test/moves/fusion_flare_bolt.test.ts b/test/moves/fusion_flare_bolt.test.ts index f9e55b72f6c..1967e9f12d1 100644 --- a/test/moves/fusion_flare_bolt.test.ts +++ b/test/moves/fusion_flare_bolt.test.ts @@ -40,7 +40,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { .enemyMoveset(MoveId.REST) .battleStyle("double") .startingWave(97) - .disableCrits(); + .criticalHits(false); vi.spyOn(fusionFlare, "calculateBattlePower"); vi.spyOn(fusionBolt, "calculateBattlePower"); diff --git a/test/moves/gigaton_hammer.test.ts b/test/moves/gigaton_hammer.test.ts index e8c9d316372..d80743f4ed0 100644 --- a/test/moves/gigaton_hammer.test.ts +++ b/test/moves/gigaton_hammer.test.ts @@ -29,7 +29,7 @@ describe("Moves - Gigaton Hammer", () => { .startingLevel(10) .enemyLevel(100) .enemyMoveset(MoveId.SPLASH) - .disableCrits(); + .criticalHits(false); }); it("can't be used two turns in a row", async () => { diff --git a/test/moves/glaive_rush.test.ts b/test/moves/glaive_rush.test.ts index 2aead405fb6..0b6f30da71a 100644 --- a/test/moves/glaive_rush.test.ts +++ b/test/moves/glaive_rush.test.ts @@ -24,7 +24,7 @@ describe("Moves - Glaive Rush", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset([MoveId.GLAIVE_RUSH]) diff --git a/test/moves/grudge.test.ts b/test/moves/grudge.test.ts index 63018a419e3..36030c88bad 100644 --- a/test/moves/grudge.test.ts +++ b/test/moves/grudge.test.ts @@ -26,7 +26,7 @@ describe("Moves - Grudge", () => { .moveset([MoveId.EMBER, MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.SHEDINJA) .enemyAbility(AbilityId.WONDER_GUARD) .enemyMoveset([MoveId.GRUDGE, MoveId.SPLASH]); diff --git a/test/moves/heal_block.test.ts b/test/moves/heal_block.test.ts index 39e8efea827..77a10927930 100644 --- a/test/moves/heal_block.test.ts +++ b/test/moves/heal_block.test.ts @@ -33,7 +33,7 @@ describe("Moves - Heal Block", () => { .ability(AbilityId.NO_GUARD) .enemyAbility(AbilityId.BALL_FETCH) .enemySpecies(SpeciesId.BLISSEY) - .disableCrits(); + .criticalHits(false); }); it("shouldn't stop damage from HP-drain attacks, just HP restoration", async () => { diff --git a/test/moves/instruct.test.ts b/test/moves/instruct.test.ts index 6167b6fc4dd..d12859301b6 100644 --- a/test/moves/instruct.test.ts +++ b/test/moves/instruct.test.ts @@ -42,7 +42,7 @@ describe("Moves - Instruct", () => { .passiveAbility(AbilityId.NO_GUARD) .enemyLevel(100) .startingLevel(100) - .disableCrits(); + .criticalHits(false); }); it("should repeat target's last used move", async () => { diff --git a/test/moves/jaw_lock.test.ts b/test/moves/jaw_lock.test.ts index e0815a4c3c9..4e103c14f98 100644 --- a/test/moves/jaw_lock.test.ts +++ b/test/moves/jaw_lock.test.ts @@ -36,7 +36,7 @@ describe("Moves - Jaw Lock", () => { .moveset([MoveId.JAW_LOCK, MoveId.SPLASH]) .startingLevel(100) .enemyLevel(100) - .disableCrits(); + .criticalHits(false); }); it("should trap the move's user and target", async () => { diff --git a/test/moves/lash_out.test.ts b/test/moves/lash_out.test.ts index c0c0881b340..33a58914978 100644 --- a/test/moves/lash_out.test.ts +++ b/test/moves/lash_out.test.ts @@ -25,7 +25,7 @@ describe("Moves - Lash Out", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.FUR_COAT) .enemyMoveset([MoveId.GROWL]) diff --git a/test/moves/last-resort.test.ts b/test/moves/last-resort.test.ts index 3b57bc5cf61..3cb98a0cd92 100644 --- a/test/moves/last-resort.test.ts +++ b/test/moves/last-resort.test.ts @@ -35,7 +35,7 @@ describe("Moves - Last Resort", () => { game.override .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/last_respects.test.ts b/test/moves/last_respects.test.ts index 08256d3ebfb..14aa5ad3309 100644 --- a/test/moves/last_respects.test.ts +++ b/test/moves/last_respects.test.ts @@ -32,7 +32,7 @@ describe("Moves - Last Respects", () => { basePower = move.power; game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .moveset([MoveId.LAST_RESPECTS, MoveId.EXPLOSION, MoveId.LUNAR_DANCE]) .ability(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH) diff --git a/test/moves/light_screen.test.ts b/test/moves/light_screen.test.ts index 404d30920c7..8c436aacf6a 100644 --- a/test/moves/light_screen.test.ts +++ b/test/moves/light_screen.test.ts @@ -41,7 +41,7 @@ describe("Moves - Light Screen", () => { .enemyLevel(100) .enemySpecies(SpeciesId.MAGIKARP) .enemyMoveset(MoveId.LIGHT_SCREEN) - .disableCrits(); + .criticalHits(false); }); it("reduces damage of special attacks by half in a single battle", async () => { diff --git a/test/moves/lucky_chant.test.ts b/test/moves/lucky_chant.test.ts index 58944027398..a57d231134b 100644 --- a/test/moves/lucky_chant.test.ts +++ b/test/moves/lucky_chant.test.ts @@ -1,11 +1,10 @@ import { AbilityId } from "#enums/ability-id"; -import { BattlerTagType } from "#app/enums/battler-tag-type"; +import { BattlerTagType } from "#enums/battler-tag-type"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; -import { BerryPhase } from "#app/phases/berry-phase"; -import { TurnEndPhase } from "#app/phases/turn-end-phase"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import GameManager from "#test/testUtils/gameManager"; +import { BattlerIndex } from "#enums/battler-index"; describe("Moves - Lucky Chant", () => { let phaserGame: Phaser.Game; @@ -29,74 +28,91 @@ describe("Moves - Lucky Chant", () => { .moveset([MoveId.LUCKY_CHANT, MoveId.SPLASH, MoveId.FOLLOW_ME]) .enemySpecies(SpeciesId.SNORLAX) .enemyAbility(AbilityId.INSOMNIA) - .enemyMoveset([MoveId.FLOWER_TRICK]) + .enemyMoveset(MoveId.TACKLE) .startingLevel(100) .enemyLevel(100); }); - it("should prevent critical hits from moves", async () => { + it("should prevent random critical hits from moves", async () => { + game.override.criticalHits(true); await game.classicMode.startBattle([SpeciesId.CHARIZARD]); - const playerPokemon = game.scene.getPlayerPokemon()!; + const charizard = game.scene.getPlayerPokemon()!; + expect(charizard).toBeDefined(); + const critSpy = vi.spyOn(charizard, "getCriticalHitResult"); // called on the defender (ie player) game.move.select(MoveId.SPLASH); + await game.phaseInterceptor.to("TurnEndPhase"); - await game.phaseInterceptor.to(TurnEndPhase); - - const firstTurnDamage = playerPokemon.getMaxHp() - playerPokemon.hp; + expect(critSpy).toHaveLastReturnedWith(true); + const firstTurnDamage = charizard.getInverseHp(); game.move.select(MoveId.LUCKY_CHANT); + await game.phaseInterceptor.to("TurnEndPhase"); - await game.phaseInterceptor.to(BerryPhase, false); + expect(critSpy).toHaveLastReturnedWith(false); + const secondTurnDamage = charizard.getInverseHp() - firstTurnDamage; + expect(secondTurnDamage).toBeLessThan(firstTurnDamage); + }); - const secondTurnDamage = playerPokemon.getMaxHp() - playerPokemon.hp - firstTurnDamage; + it("should prevent guaranteed critical hits from moves", async () => { + game.override.enemyMoveset(MoveId.FLOWER_TRICK); + await game.classicMode.startBattle([SpeciesId.CHARIZARD]); + + const charizard = game.scene.getPlayerPokemon()!; + expect(charizard).toBeDefined(); + + game.move.select(MoveId.SPLASH); + await game.phaseInterceptor.to("TurnEndPhase"); + + const firstTurnDamage = charizard.getInverseHp(); + + game.move.select(MoveId.LUCKY_CHANT); + await game.phaseInterceptor.to("TurnEndPhase"); + + const secondTurnDamage = charizard.getInverseHp() - firstTurnDamage; expect(secondTurnDamage).toBeLessThan(firstTurnDamage); }); it("should prevent critical hits against the user's ally", async () => { - game.override.battleStyle("double"); + game.override.battleStyle("double").criticalHits(true); await game.classicMode.startBattle([SpeciesId.CHARIZARD, SpeciesId.BLASTOISE]); - const playerPokemon = game.scene.getPlayerField(); + const charizard = game.scene.getPlayerPokemon()!; + expect(charizard).toBeDefined(); - game.move.select(MoveId.FOLLOW_ME); - game.move.select(MoveId.SPLASH, 1); + game.move.select(MoveId.FOLLOW_ME, BattlerIndex.PLAYER); + game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2); + await game.phaseInterceptor.to("TurnEndPhase"); - await game.phaseInterceptor.to(TurnEndPhase); + const firstTurnDamage = charizard.getInverseHp(); - const firstTurnDamage = playerPokemon[0].getMaxHp() - playerPokemon[0].hp; + game.move.select(MoveId.FOLLOW_ME, BattlerIndex.PLAYER); + game.move.select(MoveId.LUCKY_CHANT, BattlerIndex.PLAYER_2); - game.move.select(MoveId.FOLLOW_ME); - game.move.select(MoveId.LUCKY_CHANT, 1); + await game.phaseInterceptor.to("TurnEndPhase"); - await game.phaseInterceptor.to(BerryPhase, false); - - const secondTurnDamage = playerPokemon[0].getMaxHp() - playerPokemon[0].hp - firstTurnDamage; + const secondTurnDamage = charizard.getInverseHp() - firstTurnDamage; expect(secondTurnDamage).toBeLessThan(firstTurnDamage); }); it("should prevent critical hits from field effects", async () => { - game.override.enemyMoveset([MoveId.TACKLE]); - await game.classicMode.startBattle([SpeciesId.CHARIZARD]); - const playerPokemon = game.scene.getPlayerPokemon()!; - const enemyPokemon = game.scene.getEnemyPokemon()!; - - enemyPokemon.addTag(BattlerTagType.ALWAYS_CRIT, 2, MoveId.NONE, 0); + const charizard = game.field.getPlayerPokemon(); + const snorlax = game.field.getEnemyPokemon(); + snorlax.addTag(BattlerTagType.ALWAYS_CRIT, 2, MoveId.NONE, 0); game.move.select(MoveId.SPLASH); + await game.phaseInterceptor.to("TurnEndPhase"); - await game.phaseInterceptor.to(TurnEndPhase); - - const firstTurnDamage = playerPokemon.getMaxHp() - playerPokemon.hp; + const firstTurnDamage = charizard.getInverseHp(); game.move.select(MoveId.LUCKY_CHANT); + await game.phaseInterceptor.to("TurnEndPhase"); - await game.phaseInterceptor.to(BerryPhase, false); - - const secondTurnDamage = playerPokemon.getMaxHp() - playerPokemon.hp - firstTurnDamage; + const secondTurnDamage = charizard.getInverseHp() - firstTurnDamage; expect(secondTurnDamage).toBeLessThan(firstTurnDamage); }); }); diff --git a/test/moves/magic_coat.test.ts b/test/moves/magic_coat.test.ts index 7bbd4779da6..566c206f1ba 100644 --- a/test/moves/magic_coat.test.ts +++ b/test/moves/magic_coat.test.ts @@ -31,7 +31,7 @@ describe("Moves - Magic Coat", () => { game.override .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.MAGIC_COAT); diff --git a/test/moves/magnet_rise.test.ts b/test/moves/magnet_rise.test.ts index 496c0609411..d8fc6a74225 100644 --- a/test/moves/magnet_rise.test.ts +++ b/test/moves/magnet_rise.test.ts @@ -28,7 +28,7 @@ describe("Moves - Magnet Rise", () => { .starterSpecies(SpeciesId.MAGNEZONE) .enemySpecies(SpeciesId.RATTATA) .enemyMoveset(MoveId.DRILL_RUN) - .disableCrits() + .criticalHits(false) .enemyLevel(1) .moveset([moveToUse, MoveId.SPLASH, MoveId.GRAVITY, MoveId.BATON_PASS]); }); diff --git a/test/moves/metal_burst.test.ts b/test/moves/metal_burst.test.ts index bcb9d938985..4d07d59c16d 100644 --- a/test/moves/metal_burst.test.ts +++ b/test/moves/metal_burst.test.ts @@ -28,7 +28,7 @@ describe("Moves - Metal Burst", () => { .ability(AbilityId.PURE_POWER) .startingLevel(10) .battleStyle("double") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.PICHU) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.TACKLE); diff --git a/test/moves/miracle_eye.test.ts b/test/moves/miracle_eye.test.ts index 1238ae6a650..9281931621f 100644 --- a/test/moves/miracle_eye.test.ts +++ b/test/moves/miracle_eye.test.ts @@ -23,7 +23,7 @@ describe("Moves - Miracle Eye", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.UMBREON) .enemyMoveset(MoveId.SPLASH) .enemyLevel(5) diff --git a/test/moves/mirror_move.test.ts b/test/moves/mirror_move.test.ts index fe40402e5a2..03de93c56dc 100644 --- a/test/moves/mirror_move.test.ts +++ b/test/moves/mirror_move.test.ts @@ -28,7 +28,7 @@ describe("Moves - Mirror Move", () => { .moveset([MoveId.MIRROR_MOVE, MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/mist.test.ts b/test/moves/mist.test.ts index ea4eb95a1eb..4191aae0a46 100644 --- a/test/moves/mist.test.ts +++ b/test/moves/mist.test.ts @@ -26,7 +26,7 @@ describe("Moves - Mist", () => { .moveset([MoveId.MIST, MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("double") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.SNORLAX) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.GROWL); diff --git a/test/moves/moongeist_beam.test.ts b/test/moves/moongeist_beam.test.ts index 28bd3f70daa..367fe67cff5 100644 --- a/test/moves/moongeist_beam.test.ts +++ b/test/moves/moongeist_beam.test.ts @@ -27,7 +27,7 @@ describe("Moves - Moongeist Beam", () => { .ability(AbilityId.BALL_FETCH) .startingLevel(200) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.STURDY) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/multi_target.test.ts b/test/moves/multi_target.test.ts index 139b669da7b..bc6b24c474d 100644 --- a/test/moves/multi_target.test.ts +++ b/test/moves/multi_target.test.ts @@ -24,7 +24,7 @@ describe("Multi-target damage reduction", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .disableCrits() + .criticalHits(false) .battleStyle("double") .enemyLevel(100) .startingLevel(100) diff --git a/test/moves/order_up.test.ts b/test/moves/order_up.test.ts index adf37c0719e..fc505809fe6 100644 --- a/test/moves/order_up.test.ts +++ b/test/moves/order_up.test.ts @@ -30,7 +30,7 @@ describe("Moves - Order Up", () => { .moveset(MoveId.ORDER_UP) .ability(AbilityId.COMMANDER) .battleStyle("double") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.SNORLAX) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH) diff --git a/test/moves/pollen_puff.test.ts b/test/moves/pollen_puff.test.ts index e6dcd2c41d0..cd0a138e96c 100644 --- a/test/moves/pollen_puff.test.ts +++ b/test/moves/pollen_puff.test.ts @@ -26,7 +26,7 @@ describe("Moves - Pollen Puff", () => { .moveset([MoveId.POLLEN_PUFF]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/psycho_shift.test.ts b/test/moves/psycho_shift.test.ts index f92eea5fd38..e4eb2bd8ef6 100644 --- a/test/moves/psycho_shift.test.ts +++ b/test/moves/psycho_shift.test.ts @@ -27,7 +27,7 @@ describe("Moves - Psycho Shift", () => { .ability(AbilityId.BALL_FETCH) .statusEffect(StatusEffect.POISON) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyLevel(20) .enemyAbility(AbilityId.SYNCHRONIZE) diff --git a/test/moves/reflect.test.ts b/test/moves/reflect.test.ts index 5e10de42a3c..ef8c80070bf 100644 --- a/test/moves/reflect.test.ts +++ b/test/moves/reflect.test.ts @@ -41,7 +41,7 @@ describe("Moves - Reflect", () => { .enemyLevel(100) .enemySpecies(SpeciesId.MAGIKARP) .enemyMoveset(MoveId.REFLECT) - .disableCrits(); + .criticalHits(false); }); it("reduces damage of physical attacks by half in a single battle", async () => { diff --git a/test/moves/reflect_type.test.ts b/test/moves/reflect_type.test.ts index 86c70ed62f1..0915069764c 100644 --- a/test/moves/reflect_type.test.ts +++ b/test/moves/reflect_type.test.ts @@ -22,7 +22,11 @@ describe("Moves - Reflect Type", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.ability(AbilityId.BALL_FETCH).battleStyle("single").disableCrits().enemyAbility(AbilityId.BALL_FETCH); + game.override + .ability(AbilityId.BALL_FETCH) + .battleStyle("single") + .criticalHits(false) + .enemyAbility(AbilityId.BALL_FETCH); }); it("will make the user Normal/Grass if targetting a typeless Pokemon affected by Forest's Curse", async () => { diff --git a/test/moves/retaliate.test.ts b/test/moves/retaliate.test.ts index 16261c71dc3..dd5029a7c83 100644 --- a/test/moves/retaliate.test.ts +++ b/test/moves/retaliate.test.ts @@ -32,7 +32,7 @@ describe("Moves - Retaliate", () => { .enemyLevel(100) .moveset([MoveId.RETALIATE, MoveId.SPLASH]) .startingLevel(80) - .disableCrits(); + .criticalHits(false); }); it("increases power if ally died previous turn", async () => { diff --git a/test/moves/revival_blessing.test.ts b/test/moves/revival_blessing.test.ts index f22c0467378..1f2ba869e4f 100644 --- a/test/moves/revival_blessing.test.ts +++ b/test/moves/revival_blessing.test.ts @@ -28,7 +28,7 @@ describe("Moves - Revival Blessing", () => { .moveset([MoveId.SPLASH, MoveId.REVIVAL_BLESSING, MoveId.MEMENTO]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/role_play.test.ts b/test/moves/role_play.test.ts index 3c3f0afb1a3..cfa38ed38b7 100644 --- a/test/moves/role_play.test.ts +++ b/test/moves/role_play.test.ts @@ -26,7 +26,7 @@ describe("Moves - Role Play", () => { .moveset([MoveId.SPLASH, MoveId.ROLE_PLAY]) .ability(AbilityId.ADAPTABILITY) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/rollout.test.ts b/test/moves/rollout.test.ts index b9faf438ae5..9639a2e5408 100644 --- a/test/moves/rollout.test.ts +++ b/test/moves/rollout.test.ts @@ -24,7 +24,7 @@ describe("Moves - Rollout", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .disableCrits() + .criticalHits(false) .battleStyle("single") .starterSpecies(SpeciesId.RATTATA) .ability(AbilityId.BALL_FETCH) diff --git a/test/moves/round.test.ts b/test/moves/round.test.ts index 630137b7dce..8e07a819131 100644 --- a/test/moves/round.test.ts +++ b/test/moves/round.test.ts @@ -28,7 +28,7 @@ describe("Moves - Round", () => { .moveset([MoveId.SPLASH, MoveId.ROUND]) .ability(AbilityId.BALL_FETCH) .battleStyle("double") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset([MoveId.SPLASH, MoveId.ROUND]) diff --git a/test/moves/scale_shot.test.ts b/test/moves/scale_shot.test.ts index a5872579003..e2c86091378 100644 --- a/test/moves/scale_shot.test.ts +++ b/test/moves/scale_shot.test.ts @@ -31,7 +31,7 @@ describe("Moves - Scale Shot", () => { game.override .moveset([MoveId.SCALE_SHOT]) .battleStyle("single") - .disableCrits() + .criticalHits(false) .ability(AbilityId.NO_GUARD) .passiveAbility(AbilityId.SKILL_LINK) .enemyMoveset(MoveId.SPLASH) diff --git a/test/moves/secret_power.test.ts b/test/moves/secret_power.test.ts index 39498782b58..adb91162ae8 100644 --- a/test/moves/secret_power.test.ts +++ b/test/moves/secret_power.test.ts @@ -33,7 +33,7 @@ describe("Moves - Secret Power", () => { .moveset([MoveId.SECRET_POWER]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyLevel(60) .enemyAbility(AbilityId.BALL_FETCH); diff --git a/test/moves/simple_beam.test.ts b/test/moves/simple_beam.test.ts index 94609c3c4ac..f8549944ed9 100644 --- a/test/moves/simple_beam.test.ts +++ b/test/moves/simple_beam.test.ts @@ -25,7 +25,7 @@ describe("Moves - Simple Beam", () => { .moveset([MoveId.SPLASH, MoveId.SIMPLE_BEAM]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/sketch.test.ts b/test/moves/sketch.test.ts index c6fb7b4a32a..5e9ce9a9e84 100644 --- a/test/moves/sketch.test.ts +++ b/test/moves/sketch.test.ts @@ -30,7 +30,7 @@ describe("Moves - Sketch", () => { game.override .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.SHUCKLE) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/skill_swap.test.ts b/test/moves/skill_swap.test.ts index c4fb6005e28..7c2e6f5fd0a 100644 --- a/test/moves/skill_swap.test.ts +++ b/test/moves/skill_swap.test.ts @@ -26,7 +26,7 @@ describe("Moves - Skill Swap", () => { .moveset([MoveId.SPLASH, MoveId.SKILL_SWAP]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/sleep_talk.test.ts b/test/moves/sleep_talk.test.ts index 1d9aec77ea2..84e9d509a03 100644 --- a/test/moves/sleep_talk.test.ts +++ b/test/moves/sleep_talk.test.ts @@ -29,7 +29,7 @@ describe("Moves - Sleep Talk", () => { .statusEffect(StatusEffect.SLEEP) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH) diff --git a/test/moves/spectral_thief.test.ts b/test/moves/spectral_thief.test.ts index 808e9174caf..e7b19eda506 100644 --- a/test/moves/spectral_thief.test.ts +++ b/test/moves/spectral_thief.test.ts @@ -29,7 +29,8 @@ describe("Moves - Spectral Thief", () => { .enemyMoveset(MoveId.SPLASH) .enemyAbility(AbilityId.BALL_FETCH) .moveset([MoveId.SPECTRAL_THIEF, MoveId.SPLASH]) - .ability(AbilityId.BALL_FETCH).disableCrits; + .ability(AbilityId.BALL_FETCH) + .criticalHits(false); }); it("should steal max possible positive stat changes and ignore negative ones.", async () => { diff --git a/test/moves/struggle.test.ts b/test/moves/struggle.test.ts index 2175dc557b3..e1373b29d33 100644 --- a/test/moves/struggle.test.ts +++ b/test/moves/struggle.test.ts @@ -25,7 +25,7 @@ describe("Moves - Struggle", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/synchronoise.test.ts b/test/moves/synchronoise.test.ts index 176137c8180..9068a40693b 100644 --- a/test/moves/synchronoise.test.ts +++ b/test/moves/synchronoise.test.ts @@ -26,7 +26,7 @@ describe("Moves - Synchronoise", () => { .moveset([MoveId.SYNCHRONOISE]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/tackle.test.ts b/test/moves/tackle.test.ts index 96faa27ad38..f3c0db85a39 100644 --- a/test/moves/tackle.test.ts +++ b/test/moves/tackle.test.ts @@ -31,7 +31,7 @@ describe("Moves - Tackle", () => { .startingWave(97) .moveset([moveToUse]) .enemyMoveset(MoveId.GROWTH) - .disableCrits(); + .criticalHits(false); }); it("TACKLE against ghost", async () => { diff --git a/test/moves/tar_shot.test.ts b/test/moves/tar_shot.test.ts index fefd1a30e1e..8d5ea4b3582 100644 --- a/test/moves/tar_shot.test.ts +++ b/test/moves/tar_shot.test.ts @@ -30,7 +30,7 @@ describe("Moves - Tar Shot", () => { .enemySpecies(SpeciesId.TANGELA) .enemyLevel(1000) .moveset([MoveId.TAR_SHOT, MoveId.FIRE_PUNCH]) - .disableCrits(); + .criticalHits(false); }); it("lowers the target's Speed stat by one stage and doubles the effectiveness of Fire-type moves used on the target", async () => { diff --git a/test/moves/tera_blast.test.ts b/test/moves/tera_blast.test.ts index 3372fd21539..fbebd428cfd 100644 --- a/test/moves/tera_blast.test.ts +++ b/test/moves/tera_blast.test.ts @@ -35,7 +35,7 @@ describe("Moves - Tera Blast", () => { game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .starterSpecies(SpeciesId.FEEBAS) .moveset([MoveId.TERA_BLAST]) .ability(AbilityId.BALL_FETCH) diff --git a/test/moves/trick_or_treat.test.ts b/test/moves/trick_or_treat.test.ts index 366c6ee60fe..861115e2503 100644 --- a/test/moves/trick_or_treat.test.ts +++ b/test/moves/trick_or_treat.test.ts @@ -26,7 +26,7 @@ describe("Moves - Trick Or Treat", () => { .moveset([MoveId.FORESTS_CURSE, MoveId.TRICK_OR_TREAT]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/u_turn.test.ts b/test/moves/u_turn.test.ts index 5d4cad3a5ad..a2e29899ad8 100644 --- a/test/moves/u_turn.test.ts +++ b/test/moves/u_turn.test.ts @@ -29,7 +29,7 @@ describe("Moves - U-turn", () => { .startingWave(97) .moveset([MoveId.U_TURN]) .enemyMoveset(MoveId.SPLASH) - .disableCrits(); + .criticalHits(false); }); it("triggers regenerator a single time when a regenerator user switches out with u-turn", async () => { diff --git a/test/moves/upper_hand.test.ts b/test/moves/upper_hand.test.ts index e3d490ba6fa..fe38e75cf9a 100644 --- a/test/moves/upper_hand.test.ts +++ b/test/moves/upper_hand.test.ts @@ -27,7 +27,7 @@ describe("Moves - Upper Hand", () => { .moveset(MoveId.UPPER_HAND) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.QUICK_ATTACK) diff --git a/test/moves/will_o_wisp.test.ts b/test/moves/will_o_wisp.test.ts index 40495009d2a..336e84129c5 100644 --- a/test/moves/will_o_wisp.test.ts +++ b/test/moves/will_o_wisp.test.ts @@ -27,7 +27,7 @@ describe("Moves - Will-O-Wisp", () => { .moveset([MoveId.WILL_O_WISP, MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/phases/form-change-phase.test.ts b/test/phases/form-change-phase.test.ts index 99c072bdafe..b025f72f5e4 100644 --- a/test/phases/form-change-phase.test.ts +++ b/test/phases/form-change-phase.test.ts @@ -28,7 +28,7 @@ describe("Form Change Phase", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/phases/frenzy-move-reset.test.ts b/test/phases/frenzy-move-reset.test.ts index 878c58d8666..9c59e1da968 100644 --- a/test/phases/frenzy-move-reset.test.ts +++ b/test/phases/frenzy-move-reset.test.ts @@ -26,7 +26,7 @@ describe("Frenzy Move Reset", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .starterSpecies(SpeciesId.MAGIKARP) .moveset(MoveId.THRASH) .statusEffect(StatusEffect.PARALYSIS) diff --git a/test/phases/game-over-phase.test.ts b/test/phases/game-over-phase.test.ts index d45330697fc..008f9fb68e8 100644 --- a/test/phases/game-over-phase.test.ts +++ b/test/phases/game-over-phase.test.ts @@ -28,7 +28,7 @@ describe("Game Over Phase", () => { .moveset([MoveId.MEMENTO, MoveId.ICE_BEAM, MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH) .startingWave(200) diff --git a/test/testUtils/gameManagerUtils.ts b/test/testUtils/gameManagerUtils.ts index d582f8c04e3..eda3921e9e0 100644 --- a/test/testUtils/gameManagerUtils.ts +++ b/test/testUtils/gameManagerUtils.ts @@ -97,7 +97,7 @@ export function waitUntil(truth): Promise { }); } -/** Get the index of `move` from the moveset of the pokemon on the player's field at location `pokemonIndex` */ +/** Get the index of `move` from the moveset of the pokemon on the player's field at location `pokemonIndex`. */ export function getMovePosition(scene: BattleScene, pokemonIndex: 0 | 1, move: MoveId): number { const playerPokemon = scene.getPlayerField()[pokemonIndex]; const moveSet = playerPokemon.getMoveset(); diff --git a/test/testUtils/helpers/dailyModeHelper.ts b/test/testUtils/helpers/dailyModeHelper.ts index 8ee03ce5f89..4672d4dc787 100644 --- a/test/testUtils/helpers/dailyModeHelper.ts +++ b/test/testUtils/helpers/dailyModeHelper.ts @@ -16,8 +16,9 @@ export class DailyModeHelper extends GameManagerHelper { /** * Runs the daily game to the summon phase. * @returns A promise that resolves when the summon phase is reached. + * @remarks Please do not use for starting normal battles - use {@linkcode startBattle} instead */ - async runToSummon() { + async runToSummon(): Promise { await this.game.runToTitle(); if (this.game.override.disableShinies) { @@ -45,7 +46,7 @@ export class DailyModeHelper extends GameManagerHelper { * Transitions to the start of a battle. * @returns A promise that resolves when the battle is started. */ - async startBattle() { + async startBattle(): Promise { await this.runToSummon(); if (this.game.scene.battleStyle === BattleStyle.SWITCH) { diff --git a/test/testUtils/helpers/overridesHelper.ts b/test/testUtils/helpers/overridesHelper.ts index 9869c7450e2..3bf0fbbda47 100644 --- a/test/testUtils/helpers/overridesHelper.ts +++ b/test/testUtils/helpers/overridesHelper.ts @@ -243,12 +243,15 @@ export class OverridesHelper extends GameManagerHelper { } /** - * Override each wave to not have critical hits + * Force random critical hit rolls to always or never suceed. + * @param crits - `true` to guarantee crits on eligible moves, `false` to force rolls to fail, `null` to disable override + * @remarks This does not bypass effects that guarantee or block critical hits; it merely mocks the chance-based rolls. * @returns `this` */ - public disableCrits(): this { - vi.spyOn(Overrides, "NEVER_CRIT_OVERRIDE", "get").mockReturnValue(true); - this.log("Critical hits are disabled!"); + public criticalHits(crits: boolean | null): this { + vi.spyOn(Overrides, "CRITICAL_HIT_OVERRIDE", "get").mockReturnValue(crits); + const freq = crits === true ? "always" : crits === false ? "never" : "randomly"; + this.log(`Critical hit rolls set to ${freq} succeed!`); return this; } diff --git a/test/testUtils/phaseInterceptor.ts b/test/testUtils/phaseInterceptor.ts index 34fba2c145d..9d046fc85ba 100644 --- a/test/testUtils/phaseInterceptor.ts +++ b/test/testUtils/phaseInterceptor.ts @@ -210,7 +210,7 @@ export default class PhaseInterceptor { /** * Method to transition to a target phase. * @param phaseTo - The phase to transition to. - * @param runTarget - Whether or not to run the target phase. + * @param runTarget - Whether or not to run the target phase; default `true`. * @returns A promise that resolves when the transition is complete. */ async to(phaseTo: PhaseInterceptorPhase, runTarget = true): Promise { @@ -439,7 +439,7 @@ export default class PhaseInterceptor { * @param mode - The mode of the UI. * @param callback - The callback function to execute. * @param expireFn - The function to determine if the prompt has expired. - * @param awaitingActionInput + * @param awaitingActionInput - ???; default `false` */ addToNextPrompt( phaseTarget: string, From e3108603e36f6f8c10100c8adb6619c3f623f755 Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Mon, 16 Jun 2025 08:57:51 -0400 Subject: [PATCH 3/4] [Refactor] Rework evolution conditions and descriptions (#5679) * Refactor evo conditions and descriptions * Fix test * Fix Shedinja * Simplify Gimmighoul evolution * Primeape and Stantler evolve by using their move 10 times * Basculin white stripe evolves by taking 294 recoil damage * Primeape and Stantler use modifiers for tracking * Basculin uses modifier too * Remove evo count from pokemon data * No more evo counter data, Gallade/Froslass * Fix allmoves import * Clamperl * Struggle shouldn't count for Basc recoil * Change to nicer type * Apply Benjie's suggestions Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Address formatting * Undo new evolution changes * Remove unused imports * Fix speciesid * Fixed up descriptions a little * Change a key name * Fix Gimmighoul * Apply Biome * Apply Biome unsafe fixes * Review suggestions - Convert `EvoCondKey` enum to `const` object - Use early returns in `SpeciesEvolutionCondition#description` and `SpeciesFormEvolution#description` - Replace `!!x.find` with `x.some` and `y.indexOf() > -1` with `y.includes()` - Implement `coerceArray` - Fix Shelmet evolution condition checking for Shelmet and not Karrablast - Remove unnecessary type casting in `battle-scene.ts` * Remove leftover enforce func loop * Fix circular imports issue - `getPokemonSpecies` moved to `src/utils/pokemon-utils.ts` - `allSpecies` moved to `src/data/data-lists.ts` --------- Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/battle-scene.ts | 10 +- src/data/balance/pokemon-evolutions.ts | 665 +++++++++--------- src/data/challenge.ts | 3 +- src/data/daily-run.ts | 3 +- src/data/data-lists.ts | 2 + src/data/egg.ts | 2 +- .../encounters/absolute-avarice-encounter.ts | 2 +- .../an-offer-you-cant-refuse-encounter.ts | 2 +- .../encounters/clowning-around-encounter.ts | 2 +- .../encounters/dancing-lessons-encounter.ts | 2 +- .../encounters/dark-deal-encounter.ts | 2 +- .../encounters/delibirdy-encounter.ts | 2 +- .../encounters/fiery-fallout-encounter.ts | 2 +- .../encounters/fun-and-games-encounter.ts | 2 +- .../global-trade-system-encounter.ts | 3 +- .../encounters/lost-at-sea-encounter.ts | 2 +- .../encounters/mysterious-chest-encounter.ts | 2 +- .../encounters/safari-zone-encounter.ts | 2 +- .../slumbering-snorlax-encounter.ts | 2 +- .../the-expert-pokemon-breeder-encounter.ts | 2 +- .../the-pokemon-salesman-encounter.ts | 2 +- .../encounters/the-strong-stuff-encounter.ts | 2 +- .../the-winstrate-challenge-encounter.ts | 2 +- .../encounters/trash-to-treasure-encounter.ts | 2 +- .../encounters/weird-dream-encounter.ts | 3 +- .../mystery-encounter-requirements.ts | 66 -- .../utils/encounter-phase-utils.ts | 2 +- .../utils/encounter-pokemon-utils.ts | 2 +- src/data/pokemon-species.ts | 22 +- src/data/trainers/trainer-config.ts | 2 +- src/field/arena.ts | 2 +- src/field/pokemon.ts | 55 +- src/field/trainer.ts | 2 +- src/game-mode.ts | 2 +- src/modifier/modifier-type.ts | 35 +- src/modifier/modifier.ts | 57 +- src/phases/game-over-phase.ts | 2 +- src/phases/select-starter-phase.ts | 2 +- src/system/game-data.ts | 3 +- src/system/pokemon-data.ts | 5 +- .../version_migration/versions/v1_0_4.ts | 2 +- .../version_migration/versions/v1_7_0.ts | 3 +- .../version_migration/versions/v1_8_3.ts | 2 +- src/ui/egg-gacha-ui-handler.ts | 2 +- src/ui/pokedex-page-ui-handler.ts | 4 +- src/ui/pokedex-scan-ui-handler.ts | 2 +- src/ui/pokedex-ui-handler.ts | 3 +- src/ui/starter-select-ui-handler.ts | 3 +- src/ui/title-ui-handler.ts | 2 +- src/utils/pokemon-utils.ts | 21 + test/battle/battle.test.ts | 2 +- test/boss-pokemon.test.ts | 2 +- test/eggs/egg.test.ts | 2 +- test/moves/effectiveness.test.ts | 2 +- ...an-offer-you-cant-refuse-encounter.test.ts | 2 +- .../clowning-around-encounter.test.ts | 2 +- .../fiery-fallout-encounter.test.ts | 2 +- .../encounters/lost-at-sea-encounter.test.ts | 2 +- .../the-strong-stuff-encounter.test.ts | 2 +- .../the-winstrate-challenge-encounter.test.ts | 2 +- .../trash-to-treasure-encounter.test.ts | 2 +- .../uncommon-breed-encounter.test.ts | 2 +- .../mystery-encounter-utils.test.ts | 2 +- test/phases/select-modifier-phase.test.ts | 2 +- test/testUtils/gameManagerUtils.ts | 3 +- test/ui/pokedex.test.ts | 4 +- test/ui/starter-select.test.ts | 2 +- 67 files changed, 465 insertions(+), 600 deletions(-) create mode 100644 src/utils/pokemon-utils.ts diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 14b0c6a3d19..b802466ee19 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -4,7 +4,8 @@ import type Pokemon from "#app/field/pokemon"; import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import type { PokemonSpeciesFilter } from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; +import { allSpecies } from "#app/data/data-lists"; import { fixedInt, getIvsFromId, @@ -2966,6 +2967,13 @@ export default class BattleScene extends SceneBase { ) { modifiers.splice(m--, 1); } + if ( + modifier instanceof PokemonHeldItemModifier && + !isNullOrUndefined(modifier.getSpecies()) && + !this.getPokemonById(modifier.pokemonId)?.hasSpecies(modifier.getSpecies()!) + ) { + modifiers.splice(m--, 1); + } } for (const modifier of modifiers) { if (modifier instanceof PersistentModifier) { diff --git a/src/data/balance/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts index d307f5cfbf6..e97a51fed29 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -1,9 +1,9 @@ import { globalScene } from "#app/global-scene"; -import { Gender } from "#app/data/gender"; +import { Gender, getGenderSymbol } from "#app/data/gender"; import { PokeballType } from "#enums/pokeball"; import type Pokemon from "#app/field/pokemon"; import { PokemonType } from "#enums/pokemon-type"; -import { randSeedInt } from "#app/utils/common"; +import { coerceArray, isNullOrUndefined, randSeedInt } from "#app/utils/common"; import { WeatherType } from "#enums/weather-type"; import { Nature } from "#enums/nature"; import { BiomeId } from "#enums/biome-id"; @@ -11,10 +11,11 @@ import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { SpeciesFormKey } from "#enums/species-form-key"; import { TimeOfDay } from "#enums/time-of-day"; -import type { SpeciesStatBoosterModifierType } from "#app/modifier/modifier-type"; +import type { SpeciesStatBoosterItem, SpeciesStatBoosterModifierType } from "#app/modifier/modifier-type"; import { speciesStarterCosts } from "./starters"; import i18next from "i18next"; -import { initI18n } from "#app/plugins/i18n"; +import { allMoves } from "#app/data/data-lists"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; export enum SpeciesWildEvolutionDelay { NONE, @@ -74,6 +75,8 @@ export enum EvolutionItem { LEADERS_CREST } +type TyrogueMove = MoveId.LOW_SWEEP | MoveId.MACH_PUNCH | MoveId.RAPID_SPIN; + /** * Pokemon Evolution tuple type consisting of: * @property 0 {@linkcode SpeciesId} The species of the Pokemon. @@ -81,8 +84,132 @@ export enum EvolutionItem { */ export type EvolutionLevel = [species: SpeciesId, level: number]; -export type EvolutionConditionPredicate = (p: Pokemon) => boolean; -export type EvolutionConditionEnforceFunc = (p: Pokemon) => void; +const EvoCondKey = { + FRIENDSHIP: 1, + TIME: 2, + MOVE: 3, + MOVE_TYPE: 4, + PARTY_TYPE: 5, + WEATHER: 6, + BIOME: 7, + TYROGUE: 8, + SHEDINJA: 9, + EVO_TREASURE_TRACKER: 10, + RANDOM_FORM: 11, + SPECIES_CAUGHT: 12, + GENDER: 13, + NATURE: 14, + HELD_ITEM: 15, // Currently checks only for species stat booster items +} as const; + +type EvolutionConditionData = + {key: typeof EvoCondKey.FRIENDSHIP | typeof EvoCondKey.RANDOM_FORM | typeof EvoCondKey.EVO_TREASURE_TRACKER, value: number} | + {key: typeof EvoCondKey.MOVE, move: MoveId} | + {key: typeof EvoCondKey.TIME, time: TimeOfDay[]} | + {key: typeof EvoCondKey.BIOME, biome: BiomeId[]} | + {key: typeof EvoCondKey.GENDER, gender: Gender} | + {key: typeof EvoCondKey.MOVE_TYPE | typeof EvoCondKey.PARTY_TYPE, pkmnType: PokemonType} | + {key: typeof EvoCondKey.SPECIES_CAUGHT, speciesCaught: SpeciesId} | + {key: typeof EvoCondKey.HELD_ITEM, itemKey: SpeciesStatBoosterItem} | + {key: typeof EvoCondKey.NATURE, nature: Nature[]} | + {key: typeof EvoCondKey.WEATHER, weather: WeatherType[]} | + {key: typeof EvoCondKey.TYROGUE, move: TyrogueMove} | + {key: typeof EvoCondKey.SHEDINJA}; + +export class SpeciesEvolutionCondition { + public data: EvolutionConditionData[]; + private desc: string[]; + + constructor(...data: EvolutionConditionData[]) { + this.data = data; + } + + public get description(): string[] { + if (!isNullOrUndefined(this.desc)) { + return this.desc; + } + this.desc = this.data.map(cond => { + switch(cond.key) { + case EvoCondKey.FRIENDSHIP: + return i18next.t("pokemonEvolutions:friendship"); + case EvoCondKey.TIME: + return i18next.t(`pokemonEvolutions:timeOfDay.${TimeOfDay[cond.time[cond.time.length - 1]]}`); // For Day and Night evos, the key we want goes last + case EvoCondKey.MOVE_TYPE: + return i18next.t("pokemonEvolutions:moveType", {type: i18next.t(`pokemonInfo:Type.${PokemonType[cond.pkmnType]}`)}); + case EvoCondKey.PARTY_TYPE: + return i18next.t("pokemonEvolutions:partyType", {type: i18next.t(`pokemonInfo:Type.${PokemonType[cond.pkmnType]}`)}); + case EvoCondKey.GENDER: + return i18next.t("pokemonEvolutions:gender", {gender: getGenderSymbol(cond.gender)}); + case EvoCondKey.MOVE: + case EvoCondKey.TYROGUE: + return i18next.t("pokemonEvolutions:move", {move: allMoves[cond.move].name}); + case EvoCondKey.BIOME: + return i18next.t("pokemonEvolutions:biome"); + case EvoCondKey.NATURE: + return i18next.t("pokemonEvolutions:nature"); + case EvoCondKey.WEATHER: + return i18next.t("pokemonEvolutions:weather"); + case EvoCondKey.SHEDINJA: + return i18next.t("pokemonEvolutions:shedinja"); + case EvoCondKey.EVO_TREASURE_TRACKER: + return i18next.t("pokemonEvolutions:treasure"); + case EvoCondKey.SPECIES_CAUGHT: + return i18next.t("pokemonEvolutions:caught", {species: getPokemonSpecies(cond.speciesCaught).name}); + case EvoCondKey.HELD_ITEM: + return i18next.t(`pokemonEvolutions:heldItem.${cond.itemKey}`); + } + }).filter(s => !isNullOrUndefined(s)); // Filter out stringless conditions + return this.desc; + } + + public conditionsFulfilled(pokemon: Pokemon): boolean { + console.log(this.data); + return this.data.every(cond => { + switch (cond.key) { + case EvoCondKey.FRIENDSHIP: + return pokemon.friendship >= cond.value; + case EvoCondKey.TIME: + return cond.time.includes(globalScene.arena.getTimeOfDay()); + case EvoCondKey.MOVE: + return pokemon.moveset.some(m => m.moveId === cond.move); + case EvoCondKey.MOVE_TYPE: + return pokemon.moveset.some(m => m.getMove().type === cond.pkmnType); + case EvoCondKey.PARTY_TYPE: + return globalScene.getPlayerParty().some(p => p.getTypes(false, false, true).includes(cond.pkmnType)) + case EvoCondKey.EVO_TREASURE_TRACKER: + return pokemon.getHeldItems().some(m => + m.is("EvoTrackerModifier") && + m.getStackCount() + pokemon.getPersistentTreasureCount() >= cond.value + ); + case EvoCondKey.GENDER: + return pokemon.gender === cond.gender; + case EvoCondKey.SHEDINJA: // Shedinja cannot be evolved into directly + return false; + case EvoCondKey.BIOME: + return cond.biome.includes(globalScene.arena.biomeType); + case EvoCondKey.WEATHER: + return cond.weather.includes(globalScene.arena.getWeatherType()); + case EvoCondKey.TYROGUE: + return pokemon.getMoveset(true).find(m => m.moveId as TyrogueMove)?.moveId === cond.move; + case EvoCondKey.NATURE: + return cond.nature.includes(pokemon.getNature()); + case EvoCondKey.RANDOM_FORM: { + let ret = false; + globalScene.executeWithSeedOffset(() => ret = !randSeedInt(cond.value), pokemon.id); + return ret; + } + case EvoCondKey.SPECIES_CAUGHT: + return !!globalScene.gameData.dexData[cond.speciesCaught].caughtAttr; + case EvoCondKey.HELD_ITEM: + return pokemon.getHeldItems().some(m => m.is("SpeciesStatBoosterModifier") && (m.type as SpeciesStatBoosterModifierType).key === cond.itemKey) + } + }); + } +} + +export function validateShedinjaEvo(): boolean { + return globalScene.getPlayerParty().length < 6 && globalScene.pokeballCounts[PokeballType.POKEBALL] > 0; +} export class SpeciesFormEvolution { public speciesId: SpeciesId; @@ -92,41 +219,96 @@ export class SpeciesFormEvolution { public item: EvolutionItem | null; public condition: SpeciesEvolutionCondition | null; public wildDelay: SpeciesWildEvolutionDelay; - public description = ""; + public desc = ""; - constructor(speciesId: SpeciesId, preFormKey: string | null, evoFormKey: string | null, level: number, item: EvolutionItem | null, condition: SpeciesEvolutionCondition | null, wildDelay?: SpeciesWildEvolutionDelay) { - if (!i18next.isInitialized) { - initI18n(); - } + constructor(speciesId: SpeciesId, preFormKey: string | null, evoFormKey: string | null, level: number, item: EvolutionItem | null, condition: EvolutionConditionData | EvolutionConditionData[] | null, wildDelay?: SpeciesWildEvolutionDelay) { this.speciesId = speciesId; this.preFormKey = preFormKey; this.evoFormKey = evoFormKey; this.level = level; this.item = item || EvolutionItem.NONE; - this.condition = condition; + if (!isNullOrUndefined(condition)) { + this.condition = new SpeciesEvolutionCondition(...coerceArray(condition)); + } this.wildDelay = wildDelay ?? SpeciesWildEvolutionDelay.NONE; + } + + get description(): string { + if (this.desc.length > 0) { + return this.desc; + } const strings: string[] = []; + let len = 0; if (this.level > 1) { - strings.push(i18next.t("pokemonEvolutions:level") + ` ${this.level}`); + strings.push(i18next.t("pokemonEvolutions:atLevel", {lv: this.level})); } if (this.item) { const itemDescription = i18next.t(`modifierType:EvolutionItem.${EvolutionItem[this.item].toUpperCase()}`); const rarity = this.item > 50 ? i18next.t("pokemonEvolutions:ULTRA") : i18next.t("pokemonEvolutions:GREAT"); - strings.push(i18next.t("pokemonEvolutions:using") + itemDescription + ` (${rarity})`); + strings.push(i18next.t("pokemonEvolutions:using", {item: itemDescription, tier: rarity})); } if (this.condition) { - strings.push(this.condition.description); + if (strings.length === 0) { + strings.push(i18next.t("pokemonEvolutions:levelUp")); + } + strings.push(...this.condition.description); } - this.description = strings + this.desc = strings .filter(str => str !== "") - .map((str, index) => index > 0 ? str[0].toLowerCase() + str.slice(1) : str) - .join(i18next.t("pokemonEvolutions:connector")); + .map((str, index) => { + if (index === 0) { + len = str.length; + return str; + } + if (len + str.length > 60) { + len = str.length; + return "\n" + str[0].toLowerCase() + str.slice(1); + } + len += str.length; + return str[0].toLowerCase() + str.slice(1); + }) + .join(" ") + .replace(" \n", i18next.t("pokemonEvolutions:connector") + "\n"); + + return this.desc; + } + + /** + * Checks if a Pokemon fulfills the requirements of this evolution. + * @param pokemon {@linkcode Pokemon} who wants to evolve + * @param forFusion defaults to False. Whether this evolution is meant for the secondary fused mon. In that case, use their form key. + * @param item {@linkcode EvolutionItem} optional, check if the evolution uses a certain item + * @returns whether this evolution can apply to the Pokemon + */ + public validate(pokemon: Pokemon, forFusion = false, item?: EvolutionItem): boolean { + return ( + pokemon.level >= this.level && + // Check form key, using the fusion's form key if we're checking the fusion + (isNullOrUndefined(this.preFormKey) || (forFusion ? pokemon.getFusionFormKey() : pokemon.getFormKey()) === this.preFormKey) && + (isNullOrUndefined(this.condition) || this.condition.conditionsFulfilled(pokemon)) && + ((item ?? EvolutionItem.NONE) === (this.item ?? EvolutionItem.NONE)) + ); + } + + public isValidItemEvolution(pokemon: Pokemon, forFusion = false): boolean { + return ( + // If an item is given, check if it's the right one + !isNullOrUndefined(this.item) && + pokemon.level >= this.level && + // Check form key, using the fusion's form key if we're checking the fusion + (isNullOrUndefined(this.preFormKey) || (forFusion ? pokemon.getFormKey() : pokemon.getFusionFormKey()) === this.preFormKey) && + (isNullOrUndefined(this.condition) || this.condition.conditionsFulfilled(pokemon)) + ); + } + + public get evoItem(): EvolutionItem { + return this.item ?? EvolutionItem.NONE; } } export class SpeciesEvolution extends SpeciesFormEvolution { - constructor(speciesId: SpeciesId, level: number, item: EvolutionItem | null, condition: SpeciesEvolutionCondition | null, wildDelay?: SpeciesWildEvolutionDelay) { + constructor(speciesId: SpeciesId, level: number, item: EvolutionItem | null, condition: EvolutionConditionData | EvolutionConditionData[] | null, wildDelay?: SpeciesWildEvolutionDelay) { super(speciesId, null, null, level, item, condition, wildDelay); } } @@ -135,226 +317,12 @@ export class FusionSpeciesFormEvolution extends SpeciesFormEvolution { public primarySpeciesId: SpeciesId; constructor(primarySpeciesId: SpeciesId, evolution: SpeciesFormEvolution) { - super(evolution.speciesId, evolution.preFormKey, evolution.evoFormKey, evolution.level, evolution.item, evolution.condition, evolution.wildDelay); + super(evolution.speciesId, evolution.preFormKey, evolution.evoFormKey, evolution.level, evolution.item, evolution.condition?.data ?? null, evolution.wildDelay); this.primarySpeciesId = primarySpeciesId; } } -export class SpeciesEvolutionCondition { - public predicate: EvolutionConditionPredicate; - public enforceFunc?: EvolutionConditionEnforceFunc; - public description: string; - - constructor(predicate: EvolutionConditionPredicate, enforceFunc?: EvolutionConditionEnforceFunc) { - this.predicate = predicate; - this.enforceFunc = enforceFunc; - this.description = ""; - } -} - -class GenderEvolutionCondition extends SpeciesEvolutionCondition { - public gender: Gender; - constructor(gender: Gender) { - super(p => p.gender === gender, p => p.gender = gender); - this.gender = gender; - this.description = i18next.t("pokemonEvolutions:gender", { gender: i18next.t(`pokemonEvolutions:${Gender[gender]}`) }); - } -} - -class TimeOfDayEvolutionCondition extends SpeciesEvolutionCondition { - public timesOfDay: TimeOfDay[]; - constructor(tod: "day" | "night") { - if (tod === "day") { - super(() => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY); - this.timesOfDay = [ TimeOfDay.DAWN, TimeOfDay.DAY ]; - } else if (tod === "night") { - super(() => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT); - this.timesOfDay = [ TimeOfDay.DUSK, TimeOfDay.NIGHT ]; - } else { - super(() => false); - this.timesOfDay = []; - } - this.description = i18next.t("pokemonEvolutions:timeOfDay", { tod: i18next.t(`pokemonEvolutions:${tod}`) }); - } -} - -class MoveEvolutionCondition extends SpeciesEvolutionCondition { - public move: MoveId; - constructor(move: MoveId) { - super(p => p.moveset.filter(m => m.moveId === move).length > 0); - this.move = move; - const moveKey = MoveId[this.move].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join(""); - this.description = i18next.t("pokemonEvolutions:move", { move: i18next.t(`move:${moveKey}.name`) }); - } -} - -class FriendshipEvolutionCondition extends SpeciesEvolutionCondition { - public amount: number; - constructor(amount: number) { - super(p => p.friendship >= amount); - this.amount = amount; - this.description = i18next.t("pokemonEvolutions:friendship"); - } -} - -class FriendshipTimeOfDayEvolutionCondition extends SpeciesEvolutionCondition { - public amount: number; - public timesOfDay: TimeOfDay[]; - constructor(amount: number, tod: "day" | "night") { - if (tod === "day") { - super(p => p.friendship >= amount && (globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)); - this.timesOfDay = [ TimeOfDay.DAWN, TimeOfDay.DAY ]; - } else if (tod === "night") { - super(p => p.friendship >= amount && (globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)); - this.timesOfDay = [ TimeOfDay.DUSK, TimeOfDay.NIGHT ]; - } else { - super(_p => false); - this.timesOfDay = []; - } - this.amount = amount; - this.description = i18next.t("pokemonEvolutions:friendshipTimeOfDay", { tod: i18next.t(`pokemonEvolutions:${tod}`) }); - } -} - -class FriendshipMoveTypeEvolutionCondition extends SpeciesEvolutionCondition { - public amount: number; - public type: PokemonType; - constructor(amount: number, type: PokemonType) { - super(p => p.friendship >= amount && !!p.getMoveset().find(m => m?.getMove().type === type)); - this.amount = amount; - this.type = type; - this.description = i18next.t("pokemonEvolutions:friendshipMoveType", { type: i18next.t(`pokemonInfo:Type.${PokemonType[this.type]}`) }); - } -} - -class ShedinjaEvolutionCondition extends SpeciesEvolutionCondition { - constructor() { - super(() => globalScene.getPlayerParty().length < 6 && globalScene.pokeballCounts[PokeballType.POKEBALL] > 0); - this.description = i18next.t("pokemonEvolutions:shedinja"); - } -} - -class PartyTypeEvolutionCondition extends SpeciesEvolutionCondition { - public type: PokemonType; - constructor(type: PokemonType) { - super(() => !!globalScene.getPlayerParty().find(p => p.getTypes(false, false, true).indexOf(type) > -1)); - this.type = type; - this.description = i18next.t("pokemonEvolutions:partyType", { type: i18next.t(`pokemonInfo:Type.${PokemonType[this.type]}`) }); - } -} - -class CaughtEvolutionCondition extends SpeciesEvolutionCondition { - public species: SpeciesId; - constructor(species: SpeciesId) { - super(() => !!globalScene.gameData.dexData[species].caughtAttr); - this.species = species; - this.description = i18next.t("pokemonEvolutions:caught", { species: i18next.t(`pokemon:${SpeciesId[this.species].toLowerCase()}`) }); - } -} - -class WeatherEvolutionCondition extends SpeciesEvolutionCondition { - public weatherTypes: WeatherType[]; - constructor(weatherTypes: WeatherType[]) { - super(() => weatherTypes.indexOf(globalScene.arena.weather?.weatherType || WeatherType.NONE) > -1); - this.weatherTypes = weatherTypes; - this.description = i18next.t("pokemonEvolutions:weather"); - } -} - -class MoveTypeEvolutionCondition extends SpeciesEvolutionCondition { - public type: PokemonType; - constructor(type: PokemonType) { - super(p => p.moveset.filter(m => m?.getMove().type === type).length > 0); - this.type = type; - this.description = i18next.t("pokemonEvolutions:moveType", { type: i18next.t(`pokemonInfo:Type.${PokemonType[this.type]}`) }); - } -} - -class TreasureEvolutionCondition extends SpeciesEvolutionCondition { - constructor() { - super(p => p.evoCounter - + p.getHeldItems().filter(m => m.is("DamageMoneyRewardModifier")).length - + globalScene.findModifiers(m => m.is("MoneyMultiplierModifier") - || m.is("ExtraModifierModifier") || m.is("TempExtraModifierModifier")).length > 9); - this.description = i18next.t("pokemonEvolutions:treasure"); - } -} - -class TyrogueEvolutionCondition extends SpeciesEvolutionCondition { - public move: MoveId; - constructor(move: MoveId) { - super(p => - p.getMoveset(true).find(m => m && [ MoveId.LOW_SWEEP, MoveId.MACH_PUNCH, MoveId.RAPID_SPIN ].includes(m.moveId))?.moveId === move); - this.move = move; - const moveKey = MoveId[this.move].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join(""); - this.description = i18next.t("pokemonEvolutions:move", { move: i18next.t(`move:${moveKey}.name`) }); - } -} - -class NatureEvolutionCondition extends SpeciesEvolutionCondition { - public natures: Nature[]; - constructor(natures: Nature[]) { - super(p => natures.indexOf(p.getNature()) > -1); - this.natures = natures; - this.description = i18next.t("pokemonEvolutions:nature"); - } -} - -class MoveTimeOfDayEvolutionCondition extends SpeciesEvolutionCondition { - public move: MoveId; - public timesOfDay: TimeOfDay[]; - constructor(move: MoveId, tod: "day" | "night") { - if (tod === "day") { - super(p => p.moveset.filter(m => m.moveId === move).length > 0 && (globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)); - this.move = move; - this.timesOfDay = [ TimeOfDay.DAWN, TimeOfDay.DAY ]; - } else if (tod === "night") { - super(p => p.moveset.filter(m => m.moveId === move).length > 0 && (globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)); - this.move = move; - this.timesOfDay = [ TimeOfDay.DUSK, TimeOfDay.NIGHT ]; - } else { - super(() => false); - this.timesOfDay = []; - } - const moveKey = MoveId[this.move].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join(""); - this.description = i18next.t("pokemonEvolutions:moveTimeOfDay", { move: i18next.t(`move:${moveKey}.name`), tod: i18next.t(`pokemonEvolutions:${tod}`) }); - } -} - -class BiomeEvolutionCondition extends SpeciesEvolutionCondition { - public biomes: BiomeId[]; - constructor(biomes: BiomeId[]) { - super(() => biomes.filter(b => b === globalScene.arena.biomeType).length > 0); - this.biomes = biomes; - this.description = i18next.t("pokemonEvolutions:biome"); - } -} - -class DunsparceEvolutionCondition extends SpeciesEvolutionCondition { - constructor() { - super(p => { - let ret = false; - if (p.moveset.filter(m => m.moveId === MoveId.HYPER_DRILL).length > 0) { - globalScene.executeWithSeedOffset(() => ret = !randSeedInt(4), p.id); - } - return ret; - }); - const moveKey = MoveId[MoveId.HYPER_DRILL].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join(""); - this.description = i18next.t("pokemonEvolutions:move", { move: i18next.t(`move:${moveKey}.name`) }); - } -} - -class TandemausEvolutionCondition extends SpeciesEvolutionCondition { - constructor() { - super(p => { - let ret = false; - globalScene.executeWithSeedOffset(() => ret = !randSeedInt(4), p.id); - return ret; - }); - } -} - interface PokemonEvolutions { [key: string]: SpeciesFormEvolution[] } @@ -488,8 +456,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.ELECTRODE, 30, null, null) ], [SpeciesId.CUBONE]: [ - new SpeciesEvolution(SpeciesId.ALOLA_MAROWAK, 28, null, new TimeOfDayEvolutionCondition("night")), - new SpeciesEvolution(SpeciesId.MAROWAK, 28, null, new TimeOfDayEvolutionCondition("day")) + new SpeciesEvolution(SpeciesId.ALOLA_MAROWAK, 28, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}), + new SpeciesEvolution(SpeciesId.MAROWAK, 28, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}) ], [SpeciesId.TYROGUE]: [ /** @@ -498,13 +466,13 @@ export const pokemonEvolutions: PokemonEvolutions = { * If Tyrogue knows multiple of these moves, its evolution is based on * the first qualifying move in its moveset. */ - new SpeciesEvolution(SpeciesId.HITMONLEE, 20, null, new TyrogueEvolutionCondition(MoveId.LOW_SWEEP)), - new SpeciesEvolution(SpeciesId.HITMONCHAN, 20, null, new TyrogueEvolutionCondition(MoveId.MACH_PUNCH)), - new SpeciesEvolution(SpeciesId.HITMONTOP, 20, null, new TyrogueEvolutionCondition(MoveId.RAPID_SPIN)), + new SpeciesEvolution(SpeciesId.HITMONLEE, 20, null, {key: EvoCondKey.TYROGUE, move: MoveId.LOW_SWEEP}), + new SpeciesEvolution(SpeciesId.HITMONCHAN, 20, null, {key: EvoCondKey.TYROGUE, move: MoveId.MACH_PUNCH}), + new SpeciesEvolution(SpeciesId.HITMONTOP, 20, null, {key: EvoCondKey.TYROGUE, move: MoveId.RAPID_SPIN}), ], [SpeciesId.KOFFING]: [ - new SpeciesEvolution(SpeciesId.GALAR_WEEZING, 35, null, new TimeOfDayEvolutionCondition("night")), - new SpeciesEvolution(SpeciesId.WEEZING, 35, null, new TimeOfDayEvolutionCondition("day")) + new SpeciesEvolution(SpeciesId.GALAR_WEEZING, 35, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}), + new SpeciesEvolution(SpeciesId.WEEZING, 35, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}) ], [SpeciesId.RHYHORN]: [ new SpeciesEvolution(SpeciesId.RHYDON, 42, null, null) @@ -549,8 +517,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.QUILAVA, 14, null, null) ], [SpeciesId.QUILAVA]: [ - new SpeciesEvolution(SpeciesId.HISUI_TYPHLOSION, 36, null, new TimeOfDayEvolutionCondition("night")), - new SpeciesEvolution(SpeciesId.TYPHLOSION, 36, null, new TimeOfDayEvolutionCondition("day")) + new SpeciesEvolution(SpeciesId.HISUI_TYPHLOSION, 36, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}), + new SpeciesEvolution(SpeciesId.TYPHLOSION, 36, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}) ], [SpeciesId.TOTODILE]: [ new SpeciesEvolution(SpeciesId.CROCONAW, 18, null, null) @@ -652,8 +620,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.LINOONE, 20, null, null) ], [SpeciesId.WURMPLE]: [ - new SpeciesEvolution(SpeciesId.SILCOON, 7, null, new TimeOfDayEvolutionCondition("day")), - new SpeciesEvolution(SpeciesId.CASCOON, 7, null, new TimeOfDayEvolutionCondition("night")) + new SpeciesEvolution(SpeciesId.SILCOON, 7, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}), + new SpeciesEvolution(SpeciesId.CASCOON, 7, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}) ], [SpeciesId.SILCOON]: [ new SpeciesEvolution(SpeciesId.BEAUTIFLY, 10, null, null) @@ -677,8 +645,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.KIRLIA, 20, null, null) ], [SpeciesId.KIRLIA]: [ - new SpeciesEvolution(SpeciesId.GARDEVOIR, 30, null, new GenderEvolutionCondition(Gender.FEMALE)), - new SpeciesEvolution(SpeciesId.GALLADE, 30, null, new GenderEvolutionCondition(Gender.MALE)) + new SpeciesEvolution(SpeciesId.GARDEVOIR, 30, null, {key: EvoCondKey.GENDER, gender: Gender.FEMALE}), + new SpeciesEvolution(SpeciesId.GALLADE, 30, null, {key: EvoCondKey.GENDER, gender: Gender.MALE}) ], [SpeciesId.SURSKIT]: [ new SpeciesEvolution(SpeciesId.MASQUERAIN, 22, null, null) @@ -694,7 +662,7 @@ export const pokemonEvolutions: PokemonEvolutions = { ], [SpeciesId.NINCADA]: [ new SpeciesEvolution(SpeciesId.NINJASK, 20, null, null), - new SpeciesEvolution(SpeciesId.SHEDINJA, 20, null, new ShedinjaEvolutionCondition()) + new SpeciesEvolution(SpeciesId.SHEDINJA, 20, null, {key: EvoCondKey.SHEDINJA}) ], [SpeciesId.WHISMUR]: [ new SpeciesEvolution(SpeciesId.LOUDRED, 20, null, null) @@ -766,8 +734,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.DUSCLOPS, 37, null, null) ], [SpeciesId.SNORUNT]: [ - new SpeciesEvolution(SpeciesId.GLALIE, 42, null, new GenderEvolutionCondition(Gender.MALE)), - new SpeciesEvolution(SpeciesId.FROSLASS, 42, null, new GenderEvolutionCondition(Gender.FEMALE)) + new SpeciesEvolution(SpeciesId.GLALIE, 42, null, {key: EvoCondKey.GENDER, gender: Gender.MALE}), + new SpeciesEvolution(SpeciesId.FROSLASS, 42, null, {key: EvoCondKey.GENDER, gender: Gender.FEMALE}) ], [SpeciesId.SPHEAL]: [ new SpeciesEvolution(SpeciesId.SEALEO, 32, null, null) @@ -830,11 +798,11 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.BASTIODON, 30, null, null) ], [SpeciesId.BURMY]: [ - new SpeciesEvolution(SpeciesId.MOTHIM, 20, null, new GenderEvolutionCondition(Gender.MALE)), - new SpeciesEvolution(SpeciesId.WORMADAM, 20, null, new GenderEvolutionCondition(Gender.FEMALE)) + new SpeciesEvolution(SpeciesId.MOTHIM, 20, null, {key: EvoCondKey.GENDER, gender: Gender.MALE}), + new SpeciesEvolution(SpeciesId.WORMADAM, 20, null, {key: EvoCondKey.GENDER, gender: Gender.FEMALE}) ], [SpeciesId.COMBEE]: [ - new SpeciesEvolution(SpeciesId.VESPIQUEN, 21, null, new GenderEvolutionCondition(Gender.FEMALE)) + new SpeciesEvolution(SpeciesId.VESPIQUEN, 21, null, {key: EvoCondKey.GENDER, gender: Gender.FEMALE}) ], [SpeciesId.BUIZEL]: [ new SpeciesEvolution(SpeciesId.FLOATZEL, 26, null, null) @@ -876,7 +844,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.LUMINEON, 31, null, null) ], [SpeciesId.MANTYKE]: [ - new SpeciesEvolution(SpeciesId.MANTINE, 32, null, new CaughtEvolutionCondition(SpeciesId.REMORAID), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(SpeciesId.MANTINE, 32, null, {key: EvoCondKey.SPECIES_CAUGHT, speciesCaught: SpeciesId.REMORAID}, SpeciesWildEvolutionDelay.MEDIUM) ], [SpeciesId.SNOVER]: [ new SpeciesEvolution(SpeciesId.ABOMASNOW, 40, null, null) @@ -897,8 +865,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.DEWOTT, 17, null, null) ], [SpeciesId.DEWOTT]: [ - new SpeciesEvolution(SpeciesId.HISUI_SAMUROTT, 36, null, new TimeOfDayEvolutionCondition("night")), - new SpeciesEvolution(SpeciesId.SAMUROTT, 36, null, new TimeOfDayEvolutionCondition("day")) + new SpeciesEvolution(SpeciesId.HISUI_SAMUROTT, 36, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}), + new SpeciesEvolution(SpeciesId.SAMUROTT, 36, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}) ], [SpeciesId.PATRAT]: [ new SpeciesEvolution(SpeciesId.WATCHOG, 20, null, null) @@ -1048,8 +1016,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.KINGAMBIT, 1, EvolutionItem.LEADERS_CREST, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.RUFFLET]: [ - new SpeciesEvolution(SpeciesId.HISUI_BRAVIARY, 54, null, new TimeOfDayEvolutionCondition("night")), - new SpeciesEvolution(SpeciesId.BRAVIARY, 54, null, new TimeOfDayEvolutionCondition("day")) + new SpeciesEvolution(SpeciesId.HISUI_BRAVIARY, 54, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}), + new SpeciesEvolution(SpeciesId.BRAVIARY, 54, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}) ], [SpeciesId.VULLABY]: [ new SpeciesEvolution(SpeciesId.MANDIBUZZ, 54, null, null) @@ -1106,11 +1074,11 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.GOGOAT, 32, null, null) ], [SpeciesId.PANCHAM]: [ - new SpeciesEvolution(SpeciesId.PANGORO, 32, null, new PartyTypeEvolutionCondition(PokemonType.DARK), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(SpeciesId.PANGORO, 32, null, {key: EvoCondKey.PARTY_TYPE, pkmnType: PokemonType.DARK}, SpeciesWildEvolutionDelay.MEDIUM) ], [SpeciesId.ESPURR]: [ - new SpeciesFormEvolution(SpeciesId.MEOWSTIC, "", "female", 25, null, new GenderEvolutionCondition(Gender.FEMALE)), - new SpeciesFormEvolution(SpeciesId.MEOWSTIC, "", "", 25, null, new GenderEvolutionCondition(Gender.MALE)) + new SpeciesFormEvolution(SpeciesId.MEOWSTIC, "", "female", 25, null, {key: EvoCondKey.GENDER, gender: Gender.FEMALE}), + new SpeciesFormEvolution(SpeciesId.MEOWSTIC, "", "", 25, null, {key: EvoCondKey.GENDER, gender: Gender.MALE}) ], [SpeciesId.HONEDGE]: [ new SpeciesEvolution(SpeciesId.DOUBLADE, 35, null, null) @@ -1128,21 +1096,21 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.CLAWITZER, 37, null, null) ], [SpeciesId.TYRUNT]: [ - new SpeciesEvolution(SpeciesId.TYRANTRUM, 39, null, new TimeOfDayEvolutionCondition("day")) + new SpeciesEvolution(SpeciesId.TYRANTRUM, 39, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}) ], [SpeciesId.AMAURA]: [ - new SpeciesEvolution(SpeciesId.AURORUS, 39, null, new TimeOfDayEvolutionCondition("night")) + new SpeciesEvolution(SpeciesId.AURORUS, 39, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}) ], [SpeciesId.GOOMY]: [ - new SpeciesEvolution(SpeciesId.HISUI_SLIGGOO, 40, null, new TimeOfDayEvolutionCondition("night")), - new SpeciesEvolution(SpeciesId.SLIGGOO, 40, null, new TimeOfDayEvolutionCondition("day")) + new SpeciesEvolution(SpeciesId.HISUI_SLIGGOO, 40, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}), + new SpeciesEvolution(SpeciesId.SLIGGOO, 40, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}) ], [SpeciesId.SLIGGOO]: [ - new SpeciesEvolution(SpeciesId.GOODRA, 50, null, new WeatherEvolutionCondition([ WeatherType.RAIN, WeatherType.FOG, WeatherType.HEAVY_RAIN ]), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.GOODRA, 50, null, {key: EvoCondKey.WEATHER, weather: [ WeatherType.RAIN, WeatherType.FOG, WeatherType.HEAVY_RAIN ]}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.BERGMITE]: [ - new SpeciesEvolution(SpeciesId.HISUI_AVALUGG, 37, null, new TimeOfDayEvolutionCondition("night")), - new SpeciesEvolution(SpeciesId.AVALUGG, 37, null, new TimeOfDayEvolutionCondition("day")) + new SpeciesEvolution(SpeciesId.HISUI_AVALUGG, 37, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}), + new SpeciesEvolution(SpeciesId.AVALUGG, 37, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}) ], [SpeciesId.NOIBAT]: [ new SpeciesEvolution(SpeciesId.NOIVERN, 48, null, null) @@ -1151,8 +1119,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.DARTRIX, 17, null, null) ], [SpeciesId.DARTRIX]: [ - new SpeciesEvolution(SpeciesId.HISUI_DECIDUEYE, 36, null, new TimeOfDayEvolutionCondition("night")), - new SpeciesEvolution(SpeciesId.DECIDUEYE, 34, null, new TimeOfDayEvolutionCondition("day")) + new SpeciesEvolution(SpeciesId.HISUI_DECIDUEYE, 36, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}), + new SpeciesEvolution(SpeciesId.DECIDUEYE, 34, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}) ], [SpeciesId.LITTEN]: [ new SpeciesEvolution(SpeciesId.TORRACAT, 17, null, null) @@ -1173,7 +1141,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.TOUCANNON, 28, null, null) ], [SpeciesId.YUNGOOS]: [ - new SpeciesEvolution(SpeciesId.GUMSHOOS, 20, null, new TimeOfDayEvolutionCondition("day")) + new SpeciesEvolution(SpeciesId.GUMSHOOS, 20, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}) ], [SpeciesId.GRUBBIN]: [ new SpeciesEvolution(SpeciesId.CHARJABUG, 20, null, null) @@ -1191,13 +1159,13 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.ARAQUANID, 22, null, null) ], [SpeciesId.FOMANTIS]: [ - new SpeciesEvolution(SpeciesId.LURANTIS, 34, null, new TimeOfDayEvolutionCondition("day")) + new SpeciesEvolution(SpeciesId.LURANTIS, 34, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}) ], [SpeciesId.MORELULL]: [ new SpeciesEvolution(SpeciesId.SHIINOTIC, 24, null, null) ], [SpeciesId.SALANDIT]: [ - new SpeciesEvolution(SpeciesId.SALAZZLE, 33, null, new GenderEvolutionCondition(Gender.FEMALE)) + new SpeciesEvolution(SpeciesId.SALAZZLE, 33, null, {key: EvoCondKey.GENDER, gender: Gender.FEMALE}) ], [SpeciesId.STUFFUL]: [ new SpeciesEvolution(SpeciesId.BEWEAR, 27, null, null) @@ -1228,7 +1196,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.MELMETAL, 48, null, null) ], [SpeciesId.ALOLA_RATTATA]: [ - new SpeciesEvolution(SpeciesId.ALOLA_RATICATE, 20, null, new TimeOfDayEvolutionCondition("night")) + new SpeciesEvolution(SpeciesId.ALOLA_RATICATE, 20, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}) ], [SpeciesId.ALOLA_DIGLETT]: [ new SpeciesEvolution(SpeciesId.ALOLA_DUGTRIO, 26, null, null) @@ -1301,7 +1269,7 @@ export const pokemonEvolutions: PokemonEvolutions = { ], [SpeciesId.TOXEL]: [ new SpeciesFormEvolution(SpeciesId.TOXTRICITY, "", "lowkey", 30, null, - new NatureEvolutionCondition([ Nature.LONELY, Nature.BOLD, Nature.RELAXED, Nature.TIMID, Nature.SERIOUS, Nature.MODEST, Nature.MILD, Nature.QUIET, Nature.BASHFUL, Nature.CALM, Nature.GENTLE, Nature.CAREFUL ]) + {key: EvoCondKey.NATURE, nature: [ Nature.LONELY, Nature.BOLD, Nature.RELAXED, Nature.TIMID, Nature.SERIOUS, Nature.MODEST, Nature.MILD, Nature.QUIET, Nature.BASHFUL, Nature.CALM, Nature.GENTLE, Nature.CAREFUL ]} ), new SpeciesFormEvolution(SpeciesId.TOXTRICITY, "", "amped", 30, null, null) ], @@ -1352,7 +1320,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.GALAR_LINOONE, 20, null, null) ], [SpeciesId.GALAR_LINOONE]: [ - new SpeciesEvolution(SpeciesId.OBSTAGOON, 35, null, new TimeOfDayEvolutionCondition("night")) + new SpeciesEvolution(SpeciesId.OBSTAGOON, 35, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}) ], [SpeciesId.GALAR_YAMASK]: [ new SpeciesEvolution(SpeciesId.RUNERIGUS, 34, null, null) @@ -1361,7 +1329,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.HISUI_ZOROARK, 30, null, null) ], [SpeciesId.HISUI_SLIGGOO]: [ - new SpeciesEvolution(SpeciesId.HISUI_GOODRA, 50, null, new WeatherEvolutionCondition([ WeatherType.RAIN, WeatherType.FOG, WeatherType.HEAVY_RAIN ]), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.HISUI_GOODRA, 50, null, {key: EvoCondKey.WEATHER, weather: [ WeatherType.RAIN, WeatherType.FOG, WeatherType.HEAVY_RAIN ]}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.SPRIGATITO]: [ new SpeciesEvolution(SpeciesId.FLORAGATO, 16, null, null) @@ -1382,8 +1350,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.QUAQUAVAL, 36, null, null) ], [SpeciesId.LECHONK]: [ - new SpeciesFormEvolution(SpeciesId.OINKOLOGNE, "", "female", 18, null, new GenderEvolutionCondition(Gender.FEMALE)), - new SpeciesFormEvolution(SpeciesId.OINKOLOGNE, "", "", 18, null, new GenderEvolutionCondition(Gender.MALE)) + new SpeciesFormEvolution(SpeciesId.OINKOLOGNE, "", "female", 18, null, {key: EvoCondKey.GENDER, gender: Gender.FEMALE}), + new SpeciesFormEvolution(SpeciesId.OINKOLOGNE, "", "", 18, null, {key: EvoCondKey.GENDER, gender: Gender.MALE}) ], [SpeciesId.TAROUNTULA]: [ new SpeciesEvolution(SpeciesId.SPIDOPS, 15, null, null) @@ -1398,7 +1366,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.PAWMOT, 32, null, null) ], [SpeciesId.TANDEMAUS]: [ - new SpeciesFormEvolution(SpeciesId.MAUSHOLD, "", "three", 25, null, new TandemausEvolutionCondition()), + new SpeciesFormEvolution(SpeciesId.MAUSHOLD, "", "three", 25, null, {key: EvoCondKey.RANDOM_FORM, value: 4}), new SpeciesFormEvolution(SpeciesId.MAUSHOLD, "", "four", 25, null, null) ], [SpeciesId.FIDOUGH]: [ @@ -1456,7 +1424,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.GLIMMORA, 35, null, null) ], [SpeciesId.GREAVARD]: [ - new SpeciesEvolution(SpeciesId.HOUNDSTONE, 30, null, new TimeOfDayEvolutionCondition("night")) + new SpeciesEvolution(SpeciesId.HOUNDSTONE, 30, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}) ], [SpeciesId.FRIGIBAX]: [ new SpeciesEvolution(SpeciesId.ARCTIBAX, 35, null, null) @@ -1513,21 +1481,21 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.EXEGGUTOR, 1, EvolutionItem.LEAF_STONE, null, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.TANGELA]: [ - new SpeciesEvolution(SpeciesId.TANGROWTH, 34, null, new MoveEvolutionCondition(MoveId.ANCIENT_POWER), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.TANGROWTH, 34, null, {key: EvoCondKey.MOVE, move: MoveId.ANCIENT_POWER}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.LICKITUNG]: [ - new SpeciesEvolution(SpeciesId.LICKILICKY, 32, null, new MoveEvolutionCondition(MoveId.ROLLOUT), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.LICKILICKY, 32, null, {key: EvoCondKey.MOVE, move: MoveId.ROLLOUT}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.STARYU]: [ new SpeciesEvolution(SpeciesId.STARMIE, 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.EEVEE]: [ - new SpeciesFormEvolution(SpeciesId.SYLVEON, "", "", 1, null, new FriendshipMoveTypeEvolutionCondition(120, PokemonType.FAIRY), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(SpeciesId.SYLVEON, "partner", "", 1, null, new FriendshipMoveTypeEvolutionCondition(120, PokemonType.FAIRY), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(SpeciesId.ESPEON, "", "", 1, null, new FriendshipTimeOfDayEvolutionCondition(120, "day"), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(SpeciesId.ESPEON, "partner", "", 1, null, new FriendshipTimeOfDayEvolutionCondition(120, "day"), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(SpeciesId.UMBREON, "", "", 1, null, new FriendshipTimeOfDayEvolutionCondition(120, "night"), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(SpeciesId.UMBREON, "partner", "", 1, null, new FriendshipTimeOfDayEvolutionCondition(120, "night"), SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(SpeciesId.SYLVEON, "", "", 1, null, [{key: EvoCondKey.FRIENDSHIP, value: 120}, {key: EvoCondKey.MOVE_TYPE, pkmnType: PokemonType.FAIRY}], SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(SpeciesId.SYLVEON, "partner", "", 1, null, [{key: EvoCondKey.FRIENDSHIP, value: 120}, {key: EvoCondKey.MOVE_TYPE, pkmnType: PokemonType.FAIRY}], SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(SpeciesId.ESPEON, "", "", 1, null, [{key: EvoCondKey.FRIENDSHIP, value: 120}, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}], SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(SpeciesId.ESPEON, "partner", "", 1, null, [{key: EvoCondKey.FRIENDSHIP, value: 120}, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}], SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(SpeciesId.UMBREON, "", "", 1, null, [{key: EvoCondKey.FRIENDSHIP, value: 120}, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}], SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(SpeciesId.UMBREON, "partner", "", 1, null, [{key: EvoCondKey.FRIENDSHIP, value: 120}, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}], SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(SpeciesId.VAPOREON, "", "", 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(SpeciesId.VAPOREON, "partner", "", 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(SpeciesId.JOLTEON, "", "", 1, EvolutionItem.THUNDER_STONE, null, SpeciesWildEvolutionDelay.LONG), @@ -1543,13 +1511,13 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.TOGEKISS, 1, EvolutionItem.SHINY_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.AIPOM]: [ - new SpeciesEvolution(SpeciesId.AMBIPOM, 32, null, new MoveEvolutionCondition(MoveId.DOUBLE_HIT), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.AMBIPOM, 32, null, {key: EvoCondKey.MOVE, move: MoveId.DOUBLE_HIT}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.SUNKERN]: [ new SpeciesEvolution(SpeciesId.SUNFLORA, 1, EvolutionItem.SUN_STONE, null, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.YANMA]: [ - new SpeciesEvolution(SpeciesId.YANMEGA, 33, null, new MoveEvolutionCondition(MoveId.ANCIENT_POWER), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.YANMEGA, 33, null, {key: EvoCondKey.MOVE, move: MoveId.ANCIENT_POWER}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.MURKROW]: [ new SpeciesEvolution(SpeciesId.HONCHKROW, 1, EvolutionItem.DUSK_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG) @@ -1558,26 +1526,26 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.MISMAGIUS, 1, EvolutionItem.DUSK_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.GIRAFARIG]: [ - new SpeciesEvolution(SpeciesId.FARIGIRAF, 32, null, new MoveEvolutionCondition(MoveId.TWIN_BEAM), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.FARIGIRAF, 32, null, {key: EvoCondKey.MOVE, move: MoveId.TWIN_BEAM}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.DUNSPARCE]: [ - new SpeciesFormEvolution(SpeciesId.DUDUNSPARCE, "", "three-segment", 32, null, new DunsparceEvolutionCondition(), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(SpeciesId.DUDUNSPARCE, "", "two-segment", 32, null, new MoveEvolutionCondition(MoveId.HYPER_DRILL), SpeciesWildEvolutionDelay.LONG) + new SpeciesFormEvolution(SpeciesId.DUDUNSPARCE, "", "three-segment", 32, null, [{key: EvoCondKey.RANDOM_FORM, value: 4}, {key: EvoCondKey.MOVE, move: MoveId.HYPER_DRILL}], SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(SpeciesId.DUDUNSPARCE, "", "two-segment", 32, null, {key: EvoCondKey.MOVE, move: MoveId.HYPER_DRILL}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.GLIGAR]: [ - new SpeciesEvolution(SpeciesId.GLISCOR, 1, EvolutionItem.RAZOR_FANG, new TimeOfDayEvolutionCondition("night") /* Razor fang at night*/, SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.GLISCOR, 1, EvolutionItem.RAZOR_FANG, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]} /* Razor fang at night*/, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.SNEASEL]: [ - new SpeciesEvolution(SpeciesId.WEAVILE, 1, EvolutionItem.RAZOR_CLAW, new TimeOfDayEvolutionCondition("night") /* Razor claw at night*/, SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.WEAVILE, 1, EvolutionItem.RAZOR_CLAW, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]} /* Razor claw at night*/, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.URSARING]: [ new SpeciesEvolution(SpeciesId.URSALUNA, 1, EvolutionItem.PEAT_BLOCK, null, SpeciesWildEvolutionDelay.VERY_LONG) //Ursaring does not evolve into Bloodmoon Ursaluna ], [SpeciesId.PILOSWINE]: [ - new SpeciesEvolution(SpeciesId.MAMOSWINE, 1, null, new MoveEvolutionCondition(MoveId.ANCIENT_POWER), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.MAMOSWINE, 1, null, {key: EvoCondKey.MOVE, move: MoveId.ANCIENT_POWER}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.STANTLER]: [ - new SpeciesEvolution(SpeciesId.WYRDEER, 25, null, new MoveEvolutionCondition(MoveId.PSYSHIELD_BASH), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.WYRDEER, 25, null, {key: EvoCondKey.MOVE, move: MoveId.PSYSHIELD_BASH}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.LOMBRE]: [ new SpeciesEvolution(SpeciesId.LUDICOLO, 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG) @@ -1595,11 +1563,11 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.ROSERADE, 1, EvolutionItem.SHINY_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.BONSLY]: [ - new SpeciesEvolution(SpeciesId.SUDOWOODO, 1, null, new MoveEvolutionCondition(MoveId.MIMIC), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(SpeciesId.SUDOWOODO, 1, null, {key: EvoCondKey.MOVE, move: MoveId.MIMIC}, SpeciesWildEvolutionDelay.MEDIUM) ], [SpeciesId.MIME_JR]: [ - new SpeciesEvolution(SpeciesId.GALAR_MR_MIME, 1, null, new MoveTimeOfDayEvolutionCondition(MoveId.MIMIC, "night"), SpeciesWildEvolutionDelay.MEDIUM), - new SpeciesEvolution(SpeciesId.MR_MIME, 1, null, new MoveTimeOfDayEvolutionCondition(MoveId.MIMIC, "day"), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(SpeciesId.GALAR_MR_MIME, 1, null, [{key: EvoCondKey.MOVE, move: MoveId.MIMIC}, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}], SpeciesWildEvolutionDelay.MEDIUM), + new SpeciesEvolution(SpeciesId.MR_MIME, 1, null, [{key: EvoCondKey.MOVE, move: MoveId.MIMIC}, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}], SpeciesWildEvolutionDelay.MEDIUM) ], [SpeciesId.PANSAGE]: [ new SpeciesEvolution(SpeciesId.SIMISAGE, 1, EvolutionItem.LEAF_STONE, null, SpeciesWildEvolutionDelay.LONG) @@ -1621,8 +1589,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.LILLIGANT, 1, EvolutionItem.SUN_STONE, null, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.BASCULIN]: [ - new SpeciesFormEvolution(SpeciesId.BASCULEGION, "white-striped", "female", 40, null, new GenderEvolutionCondition(Gender.FEMALE), SpeciesWildEvolutionDelay.VERY_LONG), - new SpeciesFormEvolution(SpeciesId.BASCULEGION, "white-striped", "male", 40, null, new GenderEvolutionCondition(Gender.MALE), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesFormEvolution(SpeciesId.BASCULEGION, "white-striped", "female", 40, null, [{key: EvoCondKey.GENDER, gender: Gender.FEMALE}], SpeciesWildEvolutionDelay.VERY_LONG), + new SpeciesFormEvolution(SpeciesId.BASCULEGION, "white-striped", "male", 40, null, [{key: EvoCondKey.GENDER, gender: Gender.MALE}], SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.MINCCINO]: [ new SpeciesEvolution(SpeciesId.CINCCINO, 1, EvolutionItem.SHINY_STONE, null, SpeciesWildEvolutionDelay.LONG) @@ -1650,14 +1618,14 @@ export const pokemonEvolutions: PokemonEvolutions = { ], [SpeciesId.ROCKRUFF]: [ new SpeciesFormEvolution(SpeciesId.LYCANROC, "own-tempo", "dusk", 25, null, null), - new SpeciesFormEvolution(SpeciesId.LYCANROC, "", "midday", 25, null, new TimeOfDayEvolutionCondition("day")), - new SpeciesFormEvolution(SpeciesId.LYCANROC, "", "midnight", 25, null, new TimeOfDayEvolutionCondition("night")) + new SpeciesFormEvolution(SpeciesId.LYCANROC, "", "midday", 25, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}), + new SpeciesFormEvolution(SpeciesId.LYCANROC, "", "midnight", 25, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}) ], [SpeciesId.STEENEE]: [ - new SpeciesEvolution(SpeciesId.TSAREENA, 28, null, new MoveEvolutionCondition(MoveId.STOMP), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.TSAREENA, 28, null, {key: EvoCondKey.MOVE, move: MoveId.STOMP}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.POIPOLE]: [ - new SpeciesEvolution(SpeciesId.NAGANADEL, 1, null, new MoveEvolutionCondition(MoveId.DRAGON_PULSE), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.NAGANADEL, 1, null, {key: EvoCondKey.MOVE, move: MoveId.DRAGON_PULSE}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.ALOLA_SANDSHREW]: [ new SpeciesEvolution(SpeciesId.ALOLA_SANDSLASH, 1, EvolutionItem.ICE_STONE, null, SpeciesWildEvolutionDelay.LONG) @@ -1671,7 +1639,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.APPLETUN, 1, EvolutionItem.SWEET_APPLE, null, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.CLOBBOPUS]: [ - new SpeciesEvolution(SpeciesId.GRAPPLOCT, 35, null, new MoveEvolutionCondition(MoveId.TAUNT)/*Once Taunt is implemented, change evo level to 1 and delay to LONG*/) + new SpeciesEvolution(SpeciesId.GRAPPLOCT, 35, null, {key: EvoCondKey.MOVE, move: MoveId.TAUNT}/*Once Taunt is implemented, change evo level to 1 and delay to LONG*/) ], [SpeciesId.SINISTEA]: [ new SpeciesFormEvolution(SpeciesId.POLTEAGEIST, "phony", "phony", 1, EvolutionItem.CRACKED_POT, null, SpeciesWildEvolutionDelay.LONG), @@ -1679,31 +1647,31 @@ export const pokemonEvolutions: PokemonEvolutions = { ], [SpeciesId.MILCERY]: [ new SpeciesFormEvolution(SpeciesId.ALCREMIE, "", "vanilla-cream", 1, EvolutionItem.STRAWBERRY_SWEET, - new BiomeEvolutionCondition([ BiomeId.TOWN, BiomeId.PLAINS, BiomeId.GRASS, BiomeId.TALL_GRASS, BiomeId.METROPOLIS ]), + {key: EvoCondKey.BIOME, biome: [ BiomeId.TOWN, BiomeId.PLAINS, BiomeId.GRASS, BiomeId.TALL_GRASS, BiomeId.METROPOLIS ]}, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(SpeciesId.ALCREMIE, "", "ruby-cream", 1, EvolutionItem.STRAWBERRY_SWEET, - new BiomeEvolutionCondition([ BiomeId.BADLANDS, BiomeId.VOLCANO, BiomeId.GRAVEYARD, BiomeId.FACTORY, BiomeId.SLUM ]), + {key: EvoCondKey.BIOME, biome: [ BiomeId.BADLANDS, BiomeId.VOLCANO, BiomeId.GRAVEYARD, BiomeId.FACTORY, BiomeId.SLUM ]}, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(SpeciesId.ALCREMIE, "", "matcha-cream", 1, EvolutionItem.STRAWBERRY_SWEET, - new BiomeEvolutionCondition([ BiomeId.FOREST, BiomeId.SWAMP, BiomeId.MEADOW, BiomeId.JUNGLE ]), + {key: EvoCondKey.BIOME, biome: [ BiomeId.FOREST, BiomeId.SWAMP, BiomeId.MEADOW, BiomeId.JUNGLE ]}, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(SpeciesId.ALCREMIE, "", "mint-cream", 1, EvolutionItem.STRAWBERRY_SWEET, - new BiomeEvolutionCondition([ BiomeId.SEA, BiomeId.BEACH, BiomeId.LAKE, BiomeId.SEABED ]), + {key: EvoCondKey.BIOME, biome: [ BiomeId.SEA, BiomeId.BEACH, BiomeId.LAKE, BiomeId.SEABED ]}, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(SpeciesId.ALCREMIE, "", "lemon-cream", 1, EvolutionItem.STRAWBERRY_SWEET, - new BiomeEvolutionCondition([ BiomeId.DESERT, BiomeId.POWER_PLANT, BiomeId.DOJO, BiomeId.RUINS, BiomeId.CONSTRUCTION_SITE ]), + {key: EvoCondKey.BIOME, biome: [ BiomeId.DESERT, BiomeId.POWER_PLANT, BiomeId.DOJO, BiomeId.RUINS, BiomeId.CONSTRUCTION_SITE ]}, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(SpeciesId.ALCREMIE, "", "salted-cream", 1, EvolutionItem.STRAWBERRY_SWEET, - new BiomeEvolutionCondition([ BiomeId.MOUNTAIN, BiomeId.CAVE, BiomeId.ICE_CAVE, BiomeId.FAIRY_CAVE, BiomeId.SNOWY_FOREST ]), + {key: EvoCondKey.BIOME, biome: [ BiomeId.MOUNTAIN, BiomeId.CAVE, BiomeId.ICE_CAVE, BiomeId.FAIRY_CAVE, BiomeId.SNOWY_FOREST ]}, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(SpeciesId.ALCREMIE, "", "ruby-swirl", 1, EvolutionItem.STRAWBERRY_SWEET, - new BiomeEvolutionCondition([ BiomeId.WASTELAND, BiomeId.LABORATORY ]), + {key: EvoCondKey.BIOME, biome: [ BiomeId.WASTELAND, BiomeId.LABORATORY ]}, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(SpeciesId.ALCREMIE, "", "caramel-swirl", 1, EvolutionItem.STRAWBERRY_SWEET, - new BiomeEvolutionCondition([ BiomeId.TEMPLE, BiomeId.ISLAND ]), + {key: EvoCondKey.BIOME, biome: [ BiomeId.TEMPLE, BiomeId.ISLAND ]}, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(SpeciesId.ALCREMIE, "", "rainbow-swirl", 1, EvolutionItem.STRAWBERRY_SWEET, - new BiomeEvolutionCondition([ BiomeId.ABYSS, BiomeId.SPACE, BiomeId.END ]), + {key: EvoCondKey.BIOME, biome: [ BiomeId.ABYSS, BiomeId.SPACE, BiomeId.END ]}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.DURALUDON]: [ @@ -1723,10 +1691,10 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.HISUI_ELECTRODE, 1, EvolutionItem.LEAF_STONE, null, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.HISUI_QWILFISH]: [ - new SpeciesEvolution(SpeciesId.OVERQWIL, 28, null, new MoveEvolutionCondition(MoveId.BARB_BARRAGE), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.OVERQWIL, 28, null, {key: EvoCondKey.MOVE, move: MoveId.BARB_BARRAGE}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.HISUI_SNEASEL]: [ - new SpeciesEvolution(SpeciesId.SNEASLER, 1, EvolutionItem.RAZOR_CLAW, new TimeOfDayEvolutionCondition("day") /* Razor claw at day*/, SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.SNEASLER, 1, EvolutionItem.RAZOR_CLAW, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]} /* Razor claw at day*/, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.CHARCADET]: [ new SpeciesEvolution(SpeciesId.ARMAROUGE, 1, EvolutionItem.AUSPICIOUS_ARMOR, null, SpeciesWildEvolutionDelay.LONG), @@ -1746,7 +1714,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesFormEvolution(SpeciesId.SINISTCHA, "artisan", "masterpiece", 1, EvolutionItem.MASTERPIECE_TEACUP, null, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.DIPPLIN]: [ - new SpeciesEvolution(SpeciesId.HYDRAPPLE, 1, null, new MoveEvolutionCondition(MoveId.DRAGON_CHEER), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.HYDRAPPLE, 1, null, {key: EvoCondKey.MOVE, move: MoveId.DRAGON_CHEER}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.KADABRA]: [ new SpeciesEvolution(SpeciesId.ALAKAZAM, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) @@ -1761,7 +1729,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.GENGAR, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.ONIX]: [ - new SpeciesEvolution(SpeciesId.STEELIX, 1, EvolutionItem.LINKING_CORD, new MoveTypeEvolutionCondition(PokemonType.STEEL), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.STEELIX, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.MOVE_TYPE, pkmnType: PokemonType.STEEL}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.RHYDON]: [ new SpeciesEvolution(SpeciesId.RHYPERIOR, 1, EvolutionItem.PROTECTOR, null, SpeciesWildEvolutionDelay.VERY_LONG) @@ -1770,7 +1738,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.KINGDRA, 1, EvolutionItem.DRAGON_SCALE, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.SCYTHER]: [ - new SpeciesEvolution(SpeciesId.SCIZOR, 1, EvolutionItem.LINKING_CORD, new MoveTypeEvolutionCondition(PokemonType.STEEL), SpeciesWildEvolutionDelay.VERY_LONG), + new SpeciesEvolution(SpeciesId.SCIZOR, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.MOVE_TYPE, pkmnType: PokemonType.STEEL}, SpeciesWildEvolutionDelay.VERY_LONG), new SpeciesEvolution(SpeciesId.KLEAVOR, 1, EvolutionItem.BLACK_AUGURITE, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.ELECTABUZZ]: [ @@ -1792,9 +1760,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.DUSKNOIR, 1, EvolutionItem.REAPER_CLOTH, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.CLAMPERL]: [ - // TODO: Change the SpeciesEvolutionConditions here to use a bespoke HeldItemEvolutionCondition after the modifier rework - new SpeciesEvolution(SpeciesId.HUNTAIL, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => p.getHeldItems().some(m => m.is("SpeciesStatBoosterModifier") && (m.type as SpeciesStatBoosterModifierType).key === "DEEP_SEA_TOOTH")), SpeciesWildEvolutionDelay.VERY_LONG), - new SpeciesEvolution(SpeciesId.GOREBYSS, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => p.getHeldItems().some(m => m.is("SpeciesStatBoosterModifier") && (m.type as SpeciesStatBoosterModifierType).key === "DEEP_SEA_SCALE")), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.HUNTAIL, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.HELD_ITEM, itemKey: "DEEP_SEA_TOOTH"}, SpeciesWildEvolutionDelay.VERY_LONG), + new SpeciesEvolution(SpeciesId.GOREBYSS, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.HELD_ITEM, itemKey: "DEEP_SEA_SCALE"}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.BOLDORE]: [ new SpeciesEvolution(SpeciesId.GIGALITH, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) @@ -1803,10 +1770,10 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.CONKELDURR, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.KARRABLAST]: [ - new SpeciesEvolution(SpeciesId.ESCAVALIER, 1, EvolutionItem.LINKING_CORD, new CaughtEvolutionCondition(SpeciesId.SHELMET), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.ESCAVALIER, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.SPECIES_CAUGHT, speciesCaught: SpeciesId.SHELMET}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.SHELMET]: [ - new SpeciesEvolution(SpeciesId.ACCELGOR, 1, EvolutionItem.LINKING_CORD, new CaughtEvolutionCondition(SpeciesId.KARRABLAST), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.ACCELGOR, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.SPECIES_CAUGHT, speciesCaught: SpeciesId.KARRABLAST}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.SPRITZEE]: [ new SpeciesEvolution(SpeciesId.AROMATISSE, 1, EvolutionItem.SACHET, null, SpeciesWildEvolutionDelay.VERY_LONG) @@ -1824,66 +1791,66 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.ALOLA_GOLEM, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.PRIMEAPE]: [ - new SpeciesEvolution(SpeciesId.ANNIHILAPE, 35, null, new MoveEvolutionCondition(MoveId.RAGE_FIST), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.ANNIHILAPE, 35, null, {key: EvoCondKey.MOVE, move: MoveId.RAGE_FIST}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.GOLBAT]: [ - new SpeciesEvolution(SpeciesId.CROBAT, 1, null, new FriendshipEvolutionCondition(120), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.CROBAT, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 120}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.CHANSEY]: [ - new SpeciesEvolution(SpeciesId.BLISSEY, 1, null, new FriendshipEvolutionCondition(200), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.BLISSEY, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 200}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.PICHU]: [ - new SpeciesFormEvolution(SpeciesId.PIKACHU, "spiky", "partner", 1, null, new FriendshipEvolutionCondition(90), SpeciesWildEvolutionDelay.SHORT), - new SpeciesFormEvolution(SpeciesId.PIKACHU, "", "", 1, null, new FriendshipEvolutionCondition(90), SpeciesWildEvolutionDelay.SHORT), + new SpeciesFormEvolution(SpeciesId.PIKACHU, "spiky", "partner", 1, null, {key: EvoCondKey.FRIENDSHIP, value: 90}, SpeciesWildEvolutionDelay.SHORT), + new SpeciesFormEvolution(SpeciesId.PIKACHU, "", "", 1, null, {key: EvoCondKey.FRIENDSHIP, value: 90}, SpeciesWildEvolutionDelay.SHORT), ], [SpeciesId.CLEFFA]: [ - new SpeciesEvolution(SpeciesId.CLEFAIRY, 1, null, new FriendshipEvolutionCondition(160), SpeciesWildEvolutionDelay.SHORT) + new SpeciesEvolution(SpeciesId.CLEFAIRY, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 160}, SpeciesWildEvolutionDelay.SHORT) ], [SpeciesId.IGGLYBUFF]: [ - new SpeciesEvolution(SpeciesId.JIGGLYPUFF, 1, null, new FriendshipEvolutionCondition(70), SpeciesWildEvolutionDelay.SHORT) + new SpeciesEvolution(SpeciesId.JIGGLYPUFF, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 70}, SpeciesWildEvolutionDelay.SHORT) ], [SpeciesId.TOGEPI]: [ - new SpeciesEvolution(SpeciesId.TOGETIC, 1, null, new FriendshipEvolutionCondition(70), SpeciesWildEvolutionDelay.SHORT) + new SpeciesEvolution(SpeciesId.TOGETIC, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 70}, SpeciesWildEvolutionDelay.SHORT) ], [SpeciesId.AZURILL]: [ - new SpeciesEvolution(SpeciesId.MARILL, 1, null, new FriendshipEvolutionCondition(70), SpeciesWildEvolutionDelay.SHORT) + new SpeciesEvolution(SpeciesId.MARILL, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 70}, SpeciesWildEvolutionDelay.SHORT) ], [SpeciesId.BUDEW]: [ - new SpeciesEvolution(SpeciesId.ROSELIA, 1, null, new FriendshipTimeOfDayEvolutionCondition(70, "day"), SpeciesWildEvolutionDelay.SHORT) + new SpeciesEvolution(SpeciesId.ROSELIA, 1, null, [{key: EvoCondKey.FRIENDSHIP, value: 70}, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}], SpeciesWildEvolutionDelay.SHORT) ], [SpeciesId.BUNEARY]: [ - new SpeciesEvolution(SpeciesId.LOPUNNY, 1, null, new FriendshipEvolutionCondition(70), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(SpeciesId.LOPUNNY, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 70}, SpeciesWildEvolutionDelay.MEDIUM) ], [SpeciesId.CHINGLING]: [ - new SpeciesEvolution(SpeciesId.CHIMECHO, 1, null, new FriendshipTimeOfDayEvolutionCondition(90, "night"), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(SpeciesId.CHIMECHO, 1, null, [{key: EvoCondKey.FRIENDSHIP, value: 90}, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}], SpeciesWildEvolutionDelay.MEDIUM) ], [SpeciesId.HAPPINY]: [ - new SpeciesEvolution(SpeciesId.CHANSEY, 1, null, new FriendshipEvolutionCondition(160), SpeciesWildEvolutionDelay.SHORT) + new SpeciesEvolution(SpeciesId.CHANSEY, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 160}, SpeciesWildEvolutionDelay.SHORT) ], [SpeciesId.MUNCHLAX]: [ - new SpeciesEvolution(SpeciesId.SNORLAX, 1, null, new FriendshipEvolutionCondition(120), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.SNORLAX, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 120}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.RIOLU]: [ - new SpeciesEvolution(SpeciesId.LUCARIO, 1, null, new FriendshipTimeOfDayEvolutionCondition(120, "day"), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.LUCARIO, 1, null, [{key: EvoCondKey.FRIENDSHIP, value: 120}, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}], SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.WOOBAT]: [ - new SpeciesEvolution(SpeciesId.SWOOBAT, 1, null, new FriendshipEvolutionCondition(90), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(SpeciesId.SWOOBAT, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 90}, SpeciesWildEvolutionDelay.MEDIUM) ], [SpeciesId.SWADLOON]: [ - new SpeciesEvolution(SpeciesId.LEAVANNY, 1, null, new FriendshipEvolutionCondition(120), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.LEAVANNY, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 120}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.TYPE_NULL]: [ - new SpeciesEvolution(SpeciesId.SILVALLY, 1, null, new FriendshipEvolutionCondition(100), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.SILVALLY, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 100}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.ALOLA_MEOWTH]: [ - new SpeciesEvolution(SpeciesId.ALOLA_PERSIAN, 1, null, new FriendshipEvolutionCondition(120), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.ALOLA_PERSIAN, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 120}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.SNOM]: [ - new SpeciesEvolution(SpeciesId.FROSMOTH, 1, null, new FriendshipTimeOfDayEvolutionCondition(90, "night"), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(SpeciesId.FROSMOTH, 1, null, [{key: EvoCondKey.FRIENDSHIP, value: 90}, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}], SpeciesWildEvolutionDelay.MEDIUM) ], [SpeciesId.GIMMIGHOUL]: [ - new SpeciesFormEvolution(SpeciesId.GHOLDENGO, "chest", "", 1, null, new TreasureEvolutionCondition(), SpeciesWildEvolutionDelay.VERY_LONG), - new SpeciesFormEvolution(SpeciesId.GHOLDENGO, "roaming", "", 1, null, new TreasureEvolutionCondition(), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesFormEvolution(SpeciesId.GHOLDENGO, "chest", "", 1, null, {key: EvoCondKey.EVO_TREASURE_TRACKER, value: 10}, SpeciesWildEvolutionDelay.VERY_LONG), + new SpeciesFormEvolution(SpeciesId.GHOLDENGO, "roaming", "", 1, null, {key: EvoCondKey.EVO_TREASURE_TRACKER, value: 10}, SpeciesWildEvolutionDelay.VERY_LONG) ] }; diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 683fb48a9ba..3fdd83c185d 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -4,7 +4,8 @@ import i18next from "i18next"; import type { DexAttrProps, GameData } from "#app/system/game-data"; import { defaultStarterSpecies } from "#app/constants"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import { getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { speciesStarterCosts } from "#app/data/balance/starters"; import type Pokemon from "#app/field/pokemon"; import { PokemonMove } from "./moves/pokemon-move"; diff --git a/src/data/daily-run.ts b/src/data/daily-run.ts index fc60e5795dc..e869ba7f28f 100644 --- a/src/data/daily-run.ts +++ b/src/data/daily-run.ts @@ -5,7 +5,8 @@ import { PlayerPokemon } from "#app/field/pokemon"; import type { Starter } from "#app/ui/starter-select-ui-handler"; import { randSeedGauss, randSeedInt, randSeedItem, getEnumValues } from "#app/utils/common"; import type { PokemonSpeciesForm } from "#app/data/pokemon-species"; -import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import PokemonSpecies, { getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import { BiomeId } from "#enums/biome-id"; diff --git a/src/data/data-lists.ts b/src/data/data-lists.ts index 4d482dc2d7d..ed172846fe1 100644 --- a/src/data/data-lists.ts +++ b/src/data/data-lists.ts @@ -1,9 +1,11 @@ +import type PokemonSpecies from "#app/data/pokemon-species"; import type { ModifierTypes } from "#app/modifier/modifier-type"; import type { Ability } from "./abilities/ability"; import type Move from "./moves/move"; export const allAbilities: Ability[] = []; export const allMoves: Move[] = []; +export const allSpecies: PokemonSpecies[] = []; // TODO: Figure out what this is used for and provide an appropriate tsdoc comment export const modifierTypes = {} as ModifierTypes; diff --git a/src/data/egg.ts b/src/data/egg.ts index 67cdb7b1344..a6e2e04a5fe 100644 --- a/src/data/egg.ts +++ b/src/data/egg.ts @@ -1,7 +1,7 @@ import type BattleScene from "#app/battle-scene"; import { globalScene } from "#app/global-scene"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { VariantTier } from "#enums/variant-tier"; import { randInt, randomString, randSeedInt, getIvsFromId } from "#app/utils/common"; diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index e3741226335..bec75288837 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -22,7 +22,7 @@ import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encoun import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { BerryModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { MoveId } from "#enums/move-id"; import { BattlerTagType } from "#enums/battler-tag-type"; import { randInt } from "#app/utils/common"; diff --git a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts index 271346616d1..f8a904cdb45 100644 --- a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts +++ b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts @@ -18,7 +18,7 @@ import { } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index b873e30fe0c..9bdaa603540 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -22,7 +22,7 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { SpeciesId } from "#enums/species-id"; import { TrainerType } from "#enums/trainer-type"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { AbilityId } from "#enums/ability-id"; import { applyAbilityOverrideToPokemon, diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index 42af5339a80..c68e395b379 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -19,7 +19,7 @@ import { getEncounterPokemonLevelForWave, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER, } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { TrainerSlot } from "#enums/trainer-slot"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index 002b38f445d..4056ba3532e 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -4,7 +4,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import { globalScene } from "#app/global-scene"; import { modifierTypes } from "#app/data/data-lists"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index 692ffe6e80e..842fc9d73bd 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -15,7 +15,7 @@ import { updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index 94dc4ab6153..e8900f8def4 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -20,7 +20,7 @@ import { TypeRequirement, } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { SpeciesId } from "#enums/species-id"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { Gender } from "#app/data/gender"; import { PokemonType } from "#enums/pokemon-type"; import { BattlerIndex } from "#enums/battler-index"; diff --git a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts index 48557541512..c57232402dd 100644 --- a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts +++ b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts @@ -14,7 +14,7 @@ import { TrainerSlot } from "#enums/trainer-slot"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { FieldPosition } from "#enums/field-position"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; 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 0393bcdaa62..6bbc1a68772 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -16,7 +16,8 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { SpeciesId } from "#enums/species-id"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; +import { allSpecies } from "#app/data/data-lists"; import { getTypeRgb } from "#app/data/type"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; diff --git a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts index 8eea0623cd4..2b54b0a42f5 100644 --- a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts +++ b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts @@ -1,4 +1,4 @@ -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; diff --git a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts index 022b0125fde..321e65d7008 100644 --- a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts @@ -14,7 +14,7 @@ import { getHighestLevelPlayerPokemon, koPlayerPokemon, } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { ModifierTier } from "#enums/modifier-tier"; import { randSeedInt } from "#app/utils/common"; diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts index d324e9f9b6c..207e6ca400d 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -17,7 +17,7 @@ import { PokeballType } from "#enums/pokeball"; import { PlayerGender } from "#enums/player-gender"; import { NumberHolder, randSeedInt } from "#app/utils/common"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { doPlayerFlee, diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index 6b6cd71af1f..4169fd6d7c5 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -24,7 +24,7 @@ import { MoveId } from "#enums/move-id"; import { BattlerIndex } from "#enums/battler-index"; import { PokemonMove } from "#app/data/moves/pokemon-move"; import { AiType } from "#enums/ai-type"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { BerryType } from "#enums/berry-type"; diff --git a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts index 0676b40c548..03c09f6918e 100644 --- a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts @@ -15,7 +15,7 @@ import { BiomeId } from "#enums/biome-id"; import { TrainerType } from "#enums/trainer-type"; import i18next from "i18next"; import { SpeciesId } from "#enums/species-id"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { Nature } from "#enums/nature"; import { MoveId } from "#enums/move-id"; diff --git a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts index 0ad209ae4c6..c3bcf9ceb24 100644 --- a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts @@ -15,7 +15,7 @@ import { getSpriteKeysFromPokemon, } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { SpeciesId } from "#enums/species-id"; import { PokeballType } from "#enums/pokeball"; diff --git a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts index adf4f9dde8f..37d16075543 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -13,7 +13,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { SpeciesId } from "#enums/species-id"; import { Nature } from "#enums/nature"; import type Pokemon from "#app/field/pokemon"; 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 e2c87d8c0ae..eba8a6ba00e 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -17,7 +17,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { TrainerType } from "#enums/trainer-type"; import { SpeciesId } from "#enums/species-id"; import { AbilityId } from "#enums/ability-id"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { MoveId } from "#enums/move-id"; import { Nature } from "#enums/nature"; import { PokemonType } from "#enums/pokemon-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 62413b96523..e2a740c4900 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -22,7 +22,7 @@ import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/u import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import i18next from "#app/plugins/i18n"; import { ModifierTier } from "#enums/modifier-tier"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { MoveId } from "#enums/move-id"; import { BattlerIndex } from "#enums/battler-index"; import { PokemonMove } from "#app/data/moves/pokemon-move"; diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 83e876d1aa8..1ba756c7f5d 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -19,7 +19,8 @@ import type Pokemon from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move"; import { NumberHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils/common"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; +import { allSpecies } from "#app/data/data-lists"; import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier } from "#app/modifier/modifier"; import { achvs } from "#app/system/achv"; diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index a6e6e84846f..07fd155b2b2 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -1,6 +1,5 @@ import { globalScene } from "#app/global-scene"; import { allAbilities } from "../data-lists"; -import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { Nature } from "#enums/nature"; import { pokemonFormChanges } from "#app/data/pokemon-forms"; import { SpeciesFormChangeItemTrigger } from "../pokemon-forms/form-change-triggers"; @@ -16,7 +15,6 @@ import type { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; -import { SpeciesFormKey } from "#enums/species-form-key"; import { TimeOfDay } from "#enums/time-of-day"; export interface EncounterRequirement { @@ -834,70 +832,6 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen } } -export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement { - requiredEvolutionItem: EvolutionItem[]; - minNumberOfPokemon: number; - invertQuery: boolean; - - constructor(evolutionItems: EvolutionItem | EvolutionItem[], minNumberOfPokemon = 1, invertQuery = false) { - super(); - this.minNumberOfPokemon = minNumberOfPokemon; - this.invertQuery = invertQuery; - this.requiredEvolutionItem = coerceArray(evolutionItems); - } - - override meetsRequirement(): boolean { - const partyPokemon = globalScene.getPlayerParty(); - if (isNullOrUndefined(partyPokemon) || this.requiredEvolutionItem?.length < 0) { - return false; - } - return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon; - } - - filterByEvo(pokemon, evolutionItem) { - if ( - pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) && - pokemonEvolutions[pokemon.species.speciesId].filter( - e => e.item === evolutionItem && (!e.condition || e.condition.predicate(pokemon)), - ).length && - pokemon.getFormKey() !== SpeciesFormKey.GIGANTAMAX - ) { - return true; - } - - return ( - pokemon.isFusion() && - pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId) && - pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter( - e => e.item === evolutionItem && (!e.condition || e.condition.predicate(pokemon)), - ).length && - pokemon.getFusionFormKey() !== SpeciesFormKey.GIGANTAMAX - ); - } - - override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { - if (!this.invertQuery) { - return partyPokemon.filter( - pokemon => - this.requiredEvolutionItem.filter(evolutionItem => this.filterByEvo(pokemon, evolutionItem)).length > 0, - ); - } - // for an inverted query, we only want to get the pokemon that don't have ANY of the listed evolutionItemss - return partyPokemon.filter( - pokemon => - this.requiredEvolutionItem.filter(evolutionItems => this.filterByEvo(pokemon, evolutionItems)).length === 0, - ); - } - - override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { - const requiredItems = this.requiredEvolutionItem.filter(evoItem => this.filterByEvo(pokemon, evoItem)); - if (requiredItems.length > 0) { - return ["evolutionItem", EvolutionItem[requiredItems[0]]]; - } - return ["evolutionItem", ""]; - } -} - export class HeldItemRequirement extends EncounterPokemonRequirement { requiredHeldItemModifiers: string[]; minNumberOfPokemon: number; diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index eaa4f08ef70..bb74f11ce60 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -48,7 +48,7 @@ import type HeldModifierConfig from "#app/@types/held-modifier-config"; import type { Variant } from "#app/sprites/variant"; import { StatusEffect } from "#enums/status-effect"; import { globalScene } from "#app/global-scene"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { PokemonType } from "#enums/pokemon-type"; import { getNatureName } from "#app/data/nature"; import { getPokemonNameWithAffix } from "#app/messages"; diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index 4671869a2ba..93abd432ef5 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -20,7 +20,7 @@ import { PartyUiMode } from "#app/ui/party-ui-handler"; import { SpeciesId } from "#enums/species-id"; import type { PokemonType } from "#enums/pokemon-type"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { getEncounterText, diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index bb6ede7731d..8e7e029fd56 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -42,6 +42,8 @@ import { starterPassiveAbilities } from "#app/data/balance/passives"; import { loadPokemonVariantAssets } from "#app/sprites/pokemon-sprite"; import { hasExpSprite } from "#app/sprites/sprite-utils"; import { Gender } from "./gender"; +import { allSpecies } from "#app/data/data-lists"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; export enum Region { NORMAL, @@ -82,24 +84,6 @@ export const normalForm: SpeciesId[] = [ SpeciesId.CALYREX, ]; -/** - * Gets the {@linkcode PokemonSpecies} object associated with the {@linkcode SpeciesId} enum given - * @param species - The {@linkcode SpeciesId} to fetch. - * If an array of `SpeciesId`s is passed (such as for named trainer spawn pools), - * one will be selected at random. - * @returns The associated {@linkcode PokemonSpecies} object - */ -export function getPokemonSpecies(species: SpeciesId | SpeciesId[]): PokemonSpecies { - if (Array.isArray(species)) { - // TODO: this RNG roll should not be handled by this function - species = species[Math.floor(Math.random() * species.length)]; - } - if (species >= 2000) { - return allSpecies.find(s => s.speciesId === species)!; // TODO: is this bang correct? - } - return allSpecies[species - 1]; -} - export function getPokemonSpeciesForm(species: SpeciesId, formIndex: number): PokemonSpeciesForm { const retSpecies: PokemonSpecies = species >= 2000 @@ -1449,8 +1433,6 @@ export function getPokerusStarters(): PokemonSpecies[] { return pokerusStarters; } -export const allSpecies: PokemonSpecies[] = []; - // biome-ignore format: manually formatted export function initSpecies() { allSpecies.push( diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 063dddafee8..6786aa00ef7 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -10,7 +10,7 @@ import { randSeedIntRange, } from "#app/utils/common"; import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { tmSpecies } from "#app/data/balance/tms"; import { doubleBattleDialogue } from "../double-battle-dialogue"; import { TrainerVariant } from "#enums/trainer-variant"; diff --git a/src/field/arena.ts b/src/field/arena.ts index aece908d653..8d7e5037852 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -3,7 +3,7 @@ import type { BiomeTierTrainerPools, PokemonPools } from "#app/data/balance/biom import { biomePokemonPools, BiomePoolTier, biomeTrainerPools } from "#app/data/balance/biomes"; import { randSeedInt, NumberHolder, isNullOrUndefined, type Constructor } from "#app/utils/common"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { getTerrainClearMessage, getTerrainStartMessage, diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index fe89142b5c6..e9cc4f70d70 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -15,12 +15,8 @@ import { allMoves } from "#app/data/data-lists"; import { MoveTarget } from "#enums/MoveTarget"; import { MoveCategory } from "#enums/MoveCategory"; import type { PokemonSpeciesForm } from "#app/data/pokemon-species"; -import { - default as PokemonSpecies, - getFusedSpeciesName, - getPokemonSpecies, - getPokemonSpeciesForm, -} from "#app/data/pokemon-species"; +import { default as PokemonSpecies, getFusedSpeciesName, getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { NumberHolder, @@ -79,11 +75,12 @@ import { import { PokeballType } from "#enums/pokeball"; import { Gender } from "#app/data/gender"; import { Status, getRandomStatus } from "#app/data/status-effect"; -import type { SpeciesFormEvolution, SpeciesEvolutionCondition } from "#app/data/balance/pokemon-evolutions"; +import type { SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; import { pokemonEvolutions, pokemonPrevolutions, FusionSpeciesFormEvolution, + validateShedinjaEvo, } from "#app/data/balance/pokemon-evolutions"; import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms"; import { @@ -370,7 +367,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.metWave = dataSource.metWave ?? (this.metBiome === -1 ? -1 : 0); this.pauseEvolutions = dataSource.pauseEvolutions; this.pokerus = !!dataSource.pokerus; - this.evoCounter = dataSource.evoCounter ?? 0; this.fusionSpecies = dataSource.fusionSpecies instanceof PokemonSpecies ? dataSource.fusionSpecies @@ -2518,14 +2514,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (pokemonEvolutions.hasOwnProperty(this.species.speciesId)) { const evolutions = pokemonEvolutions[this.species.speciesId]; for (const e of evolutions) { - if ( - !e.item && - this.level >= e.level && - (isNullOrUndefined(e.preFormKey) || this.getFormKey() === e.preFormKey) - ) { - if (e.condition === null || (e.condition as SpeciesEvolutionCondition).predicate(this)) { - return e; - } + if (e.validate(this)) { + return e; } } } @@ -2535,14 +2525,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { e => new FusionSpeciesFormEvolution(this.species.speciesId, e), ); for (const fe of fusionEvolutions) { - if ( - !fe.item && - this.level >= fe.level && - (isNullOrUndefined(fe.preFormKey) || this.getFusionFormKey() === fe.preFormKey) - ) { - if (fe.condition === null || (fe.condition as SpeciesEvolutionCondition).predicate(this)) { - return fe; - } + if (fe.validate(this)) { + return fe; } } } @@ -5487,6 +5471,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } this.turnData.berriesEaten.push(berryType); } + + getPersistentTreasureCount(): number { + return ( + this.getHeldItems().filter(m => m.is("DamageMoneyRewardModifier")).length + + globalScene.findModifiers(m => m.is("MoneyMultiplierModifier") || m.is("ExtraModifierModifier")).length + ); + } } export class PlayerPokemon extends Pokemon { @@ -5825,7 +5816,7 @@ export class PlayerPokemon extends Pokemon { if (evoSpecies?.speciesId === SpeciesId.NINCADA && evolution.speciesId === SpeciesId.NINJASK) { const newEvolution = pokemonEvolutions[evoSpecies.speciesId][1]; - if (newEvolution.condition?.predicate(this)) { + if (validateShedinjaEvo()) { const newPokemon = globalScene.addPlayerPokemon( this.species, this.level, @@ -5855,7 +5846,6 @@ export class PlayerPokemon extends Pokemon { newPokemon.fusionLuck = this.fusionLuck; newPokemon.fusionTeraType = this.fusionTeraType; newPokemon.usedTMs = this.usedTMs; - newPokemon.evoCounter = this.evoCounter; globalScene.getPlayerParty().push(newPokemon); newPokemon.evolve(!isFusion ? newEvolution : new FusionSpeciesFormEvolution(this.id, newEvolution), evoSpecies); @@ -5944,7 +5934,6 @@ export class PlayerPokemon extends Pokemon { this.fusionGender = pokemon.gender; this.fusionLuck = pokemon.luck; this.fusionCustomPokemonData = pokemon.customPokemonData; - this.evoCounter = Math.max(pokemon.evoCounter, this.evoCounter); if (pokemon.pauseEvolutions || this.pauseEvolutions) { this.pauseEvolutions = true; } @@ -6100,18 +6089,6 @@ export class EnemyPokemon extends Pokemon { this.luck = (this.shiny ? this.variant + 1 : 0) + (this.fusionShiny ? this.fusionVariant + 1 : 0); - let prevolution: SpeciesId; - let speciesId = species.speciesId; - while ((prevolution = pokemonPrevolutions[speciesId])) { - const evolution = pokemonEvolutions[prevolution].find( - pe => pe.speciesId === speciesId && (!pe.evoFormKey || pe.evoFormKey === this.getFormKey()), - ); - if (evolution?.condition?.enforceFunc) { - evolution.condition.enforceFunc(this); - } - speciesId = prevolution; - } - if (this.hasTrainer() && globalScene.currentBattle) { const { waveIndex } = globalScene.currentBattle; const ivs: number[] = []; diff --git a/src/field/trainer.ts b/src/field/trainer.ts index 8d950b08507..b64821d259a 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -1,7 +1,7 @@ import { globalScene } from "#app/global-scene"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import type { TrainerConfig } from "#app/data/trainers/trainer-config"; import type { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { trainerConfigs } from "#app/data/trainers/trainer-config"; diff --git a/src/game-mode.ts b/src/game-mode.ts index 9722d564e09..da6ef62e33c 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -5,7 +5,7 @@ import type { Challenge } from "./data/challenge"; import { allChallenges, applyChallenges, copyChallenge } from "./data/challenge"; import { ChallengeType } from "#enums/challenge-type"; import type PokemonSpecies from "./data/pokemon-species"; -import { allSpecies } from "./data/pokemon-species"; +import { allSpecies } from "#app/data/data-lists"; import type { Arena } from "./field/arena"; import Overrides from "#app/overrides"; import { isNullOrUndefined, randSeedInt, randSeedItem } from "#app/utils/common"; diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index cf373e6441a..a22486210b0 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1218,12 +1218,8 @@ export class EvolutionItemModifierType extends PokemonModifierType implements Ge (pokemon: PlayerPokemon) => { if ( pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) && - pokemonEvolutions[pokemon.species.speciesId].filter( - e => - e.item === this.evolutionItem && - (!e.condition || e.condition.predicate(pokemon)) && - (e.preFormKey === null || e.preFormKey === pokemon.getFormKey()), - ).length && + pokemonEvolutions[pokemon.species.speciesId].filter(e => e.validate(pokemon, false, this.evolutionItem)) + .length && pokemon.getFormKey() !== SpeciesFormKey.GIGANTAMAX ) { return null; @@ -1232,12 +1228,8 @@ export class EvolutionItemModifierType extends PokemonModifierType implements Ge pokemon.isFusion() && pokemon.fusionSpecies && pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId) && - pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter( - e => - e.item === this.evolutionItem && - (!e.condition || e.condition.predicate(pokemon)) && - (e.preFormKey === null || e.preFormKey === pokemon.getFusionFormKey()), - ).length && + pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter(e => e.validate(pokemon, true, this.evolutionItem)) + .length && pokemon.getFusionFormKey() !== SpeciesFormKey.GIGANTAMAX ) { return null; @@ -1597,12 +1589,7 @@ class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator { ) .flatMap(p => { const evolutions = pokemonEvolutions[p.species.speciesId]; - return evolutions.filter( - e => - e.item !== EvolutionItem.NONE && - (e.evoFormKey === null || (e.preFormKey || "") === p.getFormKey()) && - (!e.condition || e.condition.predicate(p)), - ); + return evolutions.filter(e => e.isValidItemEvolution(p)); }), party .filter( @@ -1616,16 +1603,11 @@ class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator { ) .flatMap(p => { const evolutions = pokemonEvolutions[p.fusionSpecies!.speciesId]; - return evolutions.filter( - e => - e.item !== EvolutionItem.NONE && - (e.evoFormKey === null || (e.preFormKey || "") === p.getFusionFormKey()) && - (!e.condition || e.condition.predicate(p)), - ); + return evolutions.filter(e => e.validate(p, true)); }), ] .flat() - .flatMap(e => e.item) + .flatMap(e => e.evoItem) .filter(i => (!!i && i > 50) === rare); if (!evolutionItemPool.length) { @@ -1892,7 +1874,8 @@ const modifierTypeInitObj = Object.freeze({ new PokemonHeldItemModifierType( "modifierType:ModifierType.EVOLUTION_TRACKER_GIMMIGHOUL", "relic_gold", - (type, args) => new EvoTrackerModifier(type, (args[0] as Pokemon).id, SpeciesId.GIMMIGHOUL, 10), + (type, args) => + new EvoTrackerModifier(type, (args[0] as Pokemon).id, SpeciesId.GIMMIGHOUL, 10, (args[1] as number) ?? 1), ), MEGA_BRACELET: () => diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index e11f2c07ce8..54b7323569a 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -772,6 +772,10 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier { return this.getMaxHeldItemCount(pokemon); } + getSpecies(): SpeciesId | null { + return null; + } + abstract getMaxHeldItemCount(pokemon?: Pokemon): number; } @@ -918,27 +922,14 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier { return true; } - getIconStackText(virtual?: boolean): Phaser.GameObjects.BitmapText | null { - if (this.getMaxStackCount() === 1 || (virtual && !this.virtualStackCount)) { - return null; - } + getIconStackText(_virtual?: boolean): Phaser.GameObjects.BitmapText | null { + const pokemon = this.getPokemon(); - const pokemon = globalScene.getPokemonById(this.pokemonId); + const count = (pokemon?.getPersistentTreasureCount() || 0) + this.getStackCount(); - this.stackCount = pokemon - ? pokemon.evoCounter + - pokemon.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length + - globalScene.findModifiers( - m => - m instanceof MoneyMultiplierModifier || - m instanceof ExtraModifierModifier || - m instanceof TempExtraModifierModifier, - ).length - : this.stackCount; - - const text = globalScene.add.bitmapText(10, 15, "item-count", this.stackCount.toString(), 11); + const text = globalScene.add.bitmapText(10, 15, "item-count", count.toString(), 11); text.letterSpacing = -0.5; - if (this.getStackCount() >= this.required) { + if (count >= this.required) { text.setTint(0xf89890); } text.setOrigin(0, 0); @@ -946,18 +937,13 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier { return text; } - getMaxHeldItemCount(pokemon: Pokemon): number { - this.stackCount = - pokemon.evoCounter + - pokemon.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length + - globalScene.findModifiers( - m => - m instanceof MoneyMultiplierModifier || - m instanceof ExtraModifierModifier || - m instanceof TempExtraModifierModifier, - ).length; + getMaxHeldItemCount(_pokemon: Pokemon): number { return 999; } + + override getSpecies(): SpeciesId { + return this.species; + } } /** @@ -2402,19 +2388,13 @@ export class EvolutionItemModifier extends ConsumablePokemonModifier { override apply(playerPokemon: PlayerPokemon): boolean { let matchingEvolution = pokemonEvolutions.hasOwnProperty(playerPokemon.species.speciesId) ? pokemonEvolutions[playerPokemon.species.speciesId].find( - e => - e.item === this.type.evolutionItem && - (e.evoFormKey === null || (e.preFormKey || "") === playerPokemon.getFormKey()) && - (!e.condition || e.condition.predicate(playerPokemon)), + e => e.evoItem === this.type.evolutionItem && e.validate(playerPokemon, false, e.item!), ) : null; if (!matchingEvolution && playerPokemon.isFusion()) { matchingEvolution = pokemonEvolutions[playerPokemon.fusionSpecies!.speciesId].find( - e => - e.item === this.type.evolutionItem && // TODO: is the bang correct? - (e.evoFormKey === null || (e.preFormKey || "") === playerPokemon.getFusionFormKey()) && - (!e.condition || e.condition.predicate(playerPokemon)), + e => e.evoItem === this.type.evolutionItem && e.validate(playerPokemon, true, e.item!), ); if (matchingEvolution) { matchingEvolution = new FusionSpeciesFormEvolution(playerPokemon.species.speciesId, matchingEvolution); @@ -2934,11 +2914,10 @@ export class MoneyRewardModifier extends ConsumableModifier { globalScene.getPlayerParty().map(p => { if (p.species?.speciesId === SpeciesId.GIMMIGHOUL || p.fusionSpecies?.speciesId === SpeciesId.GIMMIGHOUL) { - p.evoCounter - ? (p.evoCounter += Math.min(Math.floor(this.moneyMultiplier), 3)) - : (p.evoCounter = Math.min(Math.floor(this.moneyMultiplier), 3)); + const factor = Math.min(Math.floor(this.moneyMultiplier), 3); const modifier = getModifierType(modifierTypes.EVOLUTION_TRACKER_GIMMIGHOUL).newModifier( p, + factor, ) as EvoTrackerModifier; globalScene.addModifier(modifier); } diff --git a/src/phases/game-over-phase.ts b/src/phases/game-over-phase.ts index 2cd8bf120b5..eaf1e4f58d7 100644 --- a/src/phases/game-over-phase.ts +++ b/src/phases/game-over-phase.ts @@ -4,7 +4,7 @@ import { globalScene } from "#app/global-scene"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { getCharVariantFromDialogue } from "#app/data/dialogue"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { trainerConfigs } from "#app/data/trainers/trainer-config"; import type Pokemon from "#app/field/pokemon"; import { modifierTypes } from "#app/data/data-lists"; diff --git a/src/phases/select-starter-phase.ts b/src/phases/select-starter-phase.ts index e7e87f5a25f..76247c14ce0 100644 --- a/src/phases/select-starter-phase.ts +++ b/src/phases/select-starter-phase.ts @@ -3,7 +3,7 @@ import { applyChallenges } from "#app/data/challenge"; import { ChallengeType } from "#enums/challenge-type"; import { Gender } from "#app/data/gender"; import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms/form-change-triggers"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { overrideHeldItems, overrideModifiers } from "#app/modifier/modifier"; import Overrides from "#app/overrides"; import { Phase } from "#app/phase"; diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 00b46b9e5f4..e933c5704f9 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -6,7 +6,8 @@ import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; +import { allSpecies } from "#app/data/data-lists"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { randInt, getEnumKeys, isLocal, executeIf, fixedInt, randSeedItem, NumberHolder } from "#app/utils/common"; import Overrides from "#app/overrides"; diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 7571f0cc82f..050da57e0be 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -3,7 +3,8 @@ import { globalScene } from "#app/global-scene"; import type { Gender } from "../data/gender"; import { Nature } from "#enums/nature"; import { PokeballType } from "#enums/pokeball"; -import { getPokemonSpecies, getPokemonSpeciesForm } from "../data/pokemon-species"; +import { getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { Status } from "../data/status-effect"; import Pokemon, { EnemyPokemon, PokemonBattleData, PokemonSummonData } from "../field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move"; @@ -45,7 +46,6 @@ export default class PokemonData { public pauseEvolutions: boolean; public pokerus: boolean; public usedTMs: MoveId[]; - public evoCounter: number; public teraType: PokemonType; public isTerastallized: boolean; public stellarTypesBoosted: PokemonType[]; @@ -118,7 +118,6 @@ export default class PokemonData { this.pauseEvolutions = !!source.pauseEvolutions; this.pokerus = !!source.pokerus; this.usedTMs = source.usedTMs ?? []; - this.evoCounter = source.evoCounter ?? 0; this.teraType = source.teraType as PokemonType; this.isTerastallized = !!source.isTerastallized; this.stellarTypesBoosted = source.stellarTypesBoosted ?? []; diff --git a/src/system/version_migration/versions/v1_0_4.ts b/src/system/version_migration/versions/v1_0_4.ts index fbbde49bc08..a08327a3b70 100644 --- a/src/system/version_migration/versions/v1_0_4.ts +++ b/src/system/version_migration/versions/v1_0_4.ts @@ -3,7 +3,7 @@ import type { SystemSaveData, SessionSaveData } from "#app/system/game-data"; import { defaultStarterSpecies } from "#app/constants"; import { AbilityAttr } from "#enums/ability-attr"; import { DexAttr } from "#enums/dex-attr"; -import { allSpecies } from "#app/data/pokemon-species"; +import { allSpecies } from "#app/data/data-lists"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { isNullOrUndefined } from "#app/utils/common"; import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator"; diff --git a/src/system/version_migration/versions/v1_7_0.ts b/src/system/version_migration/versions/v1_7_0.ts index e309959317e..e3c599bf77b 100644 --- a/src/system/version_migration/versions/v1_7_0.ts +++ b/src/system/version_migration/versions/v1_7_0.ts @@ -1,6 +1,7 @@ import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator"; import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator"; -import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import { getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { globalScene } from "#app/global-scene"; import type { SessionSaveData, SystemSaveData } from "#app/system/game-data"; import { DexAttr } from "#enums/dex-attr"; diff --git a/src/system/version_migration/versions/v1_8_3.ts b/src/system/version_migration/versions/v1_8_3.ts index bd963290800..81430659e0b 100644 --- a/src/system/version_migration/versions/v1_8_3.ts +++ b/src/system/version_migration/versions/v1_8_3.ts @@ -1,5 +1,5 @@ import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import type { SystemSaveData } from "#app/system/game-data"; import { DexAttr } from "#enums/dex-attr"; import { SpeciesId } from "#enums/species-id"; diff --git a/src/ui/egg-gacha-ui-handler.ts b/src/ui/egg-gacha-ui-handler.ts index 1b1aed91203..0c8d90fa138 100644 --- a/src/ui/egg-gacha-ui-handler.ts +++ b/src/ui/egg-gacha-ui-handler.ts @@ -5,7 +5,7 @@ import { getEnumValues, getEnumKeys, fixedInt, randSeedShuffle } from "#app/util import type { IEggOptions } from "../data/egg"; import { Egg, getLegendaryGachaSpeciesForTimestamp } from "../data/egg"; import { VoucherType, getVoucherTypeIcon } from "../system/voucher"; -import { getPokemonSpecies } from "../data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { addWindow } from "./ui-theme"; import { Tutorial, handleTutorial } from "../tutorial"; import { Button } from "#enums/buttons"; diff --git a/src/ui/pokedex-page-ui-handler.ts b/src/ui/pokedex-page-ui-handler.ts index 7ef4f8f920b..50c15336e36 100644 --- a/src/ui/pokedex-page-ui-handler.ts +++ b/src/ui/pokedex-page-ui-handler.ts @@ -16,7 +16,9 @@ import { pokemonFormChanges } from "#app/data/pokemon-forms"; import type { LevelMoves } from "#app/data/balance/pokemon-level-moves"; import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { allSpecies, getPokemonSpecies, getPokemonSpeciesForm, normalForm } from "#app/data/pokemon-species"; +import { getPokemonSpeciesForm, normalForm } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; +import { allSpecies } from "#app/data/data-lists"; import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { starterPassiveAbilities } from "#app/data/balance/passives"; import { PokemonType } from "#enums/pokemon-type"; diff --git a/src/ui/pokedex-scan-ui-handler.ts b/src/ui/pokedex-scan-ui-handler.ts index df3e7cbc8c4..2bffc464793 100644 --- a/src/ui/pokedex-scan-ui-handler.ts +++ b/src/ui/pokedex-scan-ui-handler.ts @@ -8,7 +8,7 @@ import { UiMode } from "#enums/ui-mode"; import { FilterTextRow } from "./filter-text"; import { allAbilities } from "#app/data/data-lists"; import { allMoves } from "#app/data/data-lists"; -import { allSpecies } from "#app/data/pokemon-species"; +import { allSpecies } from "#app/data/data-lists"; import i18next from "i18next"; export default class PokedexScanUiHandler extends FormModalUiHandler { diff --git a/src/ui/pokedex-ui-handler.ts b/src/ui/pokedex-ui-handler.ts index 5b292e7232f..1732bb005d3 100644 --- a/src/ui/pokedex-ui-handler.ts +++ b/src/ui/pokedex-ui-handler.ts @@ -7,7 +7,8 @@ import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves"; import type { PokemonForm } from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { allSpecies, getPokemonSpeciesForm, getPokerusStarters, normalForm } from "#app/data/pokemon-species"; +import { getPokemonSpeciesForm, getPokerusStarters, normalForm } from "#app/data/pokemon-species"; +import { allSpecies } from "#app/data/data-lists"; import { getStarterValueFriendshipCap, speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters"; import { catchableSpecies } from "#app/data/balance/biomes"; import { PokemonType } from "#enums/pokemon-type"; diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 7c8b05b6f76..20f613fb694 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -19,7 +19,8 @@ import { pokemonFormChanges } from "#app/data/pokemon-forms"; import type { LevelMoves } from "#app/data/balance/pokemon-level-moves"; import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { allSpecies, getPokemonSpeciesForm, getPokerusStarters } from "#app/data/pokemon-species"; +import { getPokemonSpeciesForm, getPokerusStarters } from "#app/data/pokemon-species"; +import { allSpecies } from "#app/data/data-lists"; import { getStarterValueFriendshipCap, speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters"; import { PokemonType } from "#enums/pokemon-type"; import { GameModes } from "#enums/game-modes"; diff --git a/src/ui/title-ui-handler.ts b/src/ui/title-ui-handler.ts index 29a354dbe01..50e77bbdd14 100644 --- a/src/ui/title-ui-handler.ts +++ b/src/ui/title-ui-handler.ts @@ -9,7 +9,7 @@ import { version } from "../../package.json"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import { globalScene } from "#app/global-scene"; import type { SpeciesId } from "#enums/species-id"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { PlayerGender } from "#enums/player-gender"; import { timedEventManager } from "#app/global-event-manager"; diff --git a/src/utils/pokemon-utils.ts b/src/utils/pokemon-utils.ts new file mode 100644 index 00000000000..9f87c36b050 --- /dev/null +++ b/src/utils/pokemon-utils.ts @@ -0,0 +1,21 @@ +import { allSpecies } from "#app/data/data-lists"; +import type PokemonSpecies from "#app/data/pokemon-species"; +import type { SpeciesId } from "#enums/species-id"; + +/** + * Gets the {@linkcode PokemonSpecies} object associated with the {@linkcode SpeciesId} enum given + * @param species - The {@linkcode SpeciesId} to fetch. + * If an array of `SpeciesId`s is passed (such as for named trainer spawn pools), + * one will be selected at random. + * @returns The associated {@linkcode PokemonSpecies} object + */ +export function getPokemonSpecies(species: SpeciesId | SpeciesId[]): PokemonSpecies { + if (Array.isArray(species)) { + // TODO: this RNG roll should not be handled by this function + species = species[Math.floor(Math.random() * species.length)]; + } + if (species >= 2000) { + return allSpecies.find(s => s.speciesId === species)!; // TODO: is this bang correct? + } + return allSpecies[species - 1]; +} diff --git a/test/battle/battle.test.ts b/test/battle/battle.test.ts index a71dca111e3..bf2c3968aa6 100644 --- a/test/battle/battle.test.ts +++ b/test/battle/battle.test.ts @@ -1,4 +1,4 @@ -import { allSpecies } from "#app/data/pokemon-species"; +import { allSpecies } from "#app/data/data-lists"; import { Stat } from "#enums/stat"; import { getGameMode } from "#app/game-mode"; import { GameModes } from "#enums/game-modes"; diff --git a/test/boss-pokemon.test.ts b/test/boss-pokemon.test.ts index afcf5e8fa77..6eb6d262c56 100644 --- a/test/boss-pokemon.test.ts +++ b/test/boss-pokemon.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import GameManager from "#test/testUtils/gameManager"; import { SpeciesId } from "#enums/species-id"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { EFFECTIVE_STATS } from "#app/enums/stat"; diff --git a/test/eggs/egg.test.ts b/test/eggs/egg.test.ts index 7792756a8a3..0d5d09c5179 100644 --- a/test/eggs/egg.test.ts +++ b/test/eggs/egg.test.ts @@ -1,6 +1,6 @@ import { speciesEggTiers } from "#app/data/balance/species-egg-tiers"; import { Egg, getLegendaryGachaSpeciesForTimestamp, getValidLegendaryGachaSpecies } from "#app/data/egg"; -import { allSpecies } from "#app/data/pokemon-species"; +import { allSpecies } from "#app/data/data-lists"; import { EggSourceType } from "#app/enums/egg-source-types"; import { EggTier } from "#app/enums/egg-type"; import { VariantTier } from "#app/enums/variant-tier"; diff --git a/test/moves/effectiveness.test.ts b/test/moves/effectiveness.test.ts index b906e00e1a0..58b2d07b1b6 100644 --- a/test/moves/effectiveness.test.ts +++ b/test/moves/effectiveness.test.ts @@ -1,5 +1,5 @@ import { allMoves } from "#app/data/data-lists"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { TrainerSlot } from "#enums/trainer-slot"; import { PokemonType } from "#enums/pokemon-type"; import { AbilityId } from "#enums/ability-id"; diff --git a/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts b/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts index 4b0f1d50b74..4f986f58b88 100644 --- a/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts +++ b/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts @@ -13,7 +13,7 @@ import { AnOfferYouCantRefuseEncounter } from "#app/data/mystery-encounters/enco import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { MoveId } from "#enums/move-id"; import { ShinyRateBoosterModifier } from "#app/modifier/modifier"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; diff --git a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts index 738b3a54067..85193d1ec72 100644 --- a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -4,7 +4,7 @@ import { MysteryEncounterType } from "#app/enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import * as BattleAnims from "#app/data/battle-anims"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; diff --git a/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts b/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts index 5e6b8507a91..16adc47ff11 100644 --- a/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts +++ b/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts @@ -6,7 +6,7 @@ import GameManager from "#test/testUtils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { FieryFalloutEncounter } from "#app/data/mystery-encounters/encounters/fiery-fallout-encounter"; import { Gender } from "#app/data/gender"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import * as BattleAnims from "#app/data/battle-anims"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { diff --git a/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts b/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts index aa367277ac6..a55806e5f48 100644 --- a/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts +++ b/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts @@ -1,7 +1,7 @@ import { LostAtSeaEncounter } from "#app/data/mystery-encounters/encounters/lost-at-sea-encounter"; import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { BiomeId } from "#enums/biome-id"; import { MysteryEncounterType } from "#app/enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts index d8626a7bf8a..96060e4114c 100644 --- a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts @@ -4,7 +4,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import * as BattleAnims from "#app/data/battle-anims"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { diff --git a/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts b/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts index 87caa6ccd40..012b88bcd73 100644 --- a/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts @@ -17,7 +17,7 @@ import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { TrainerType } from "#enums/trainer-type"; import { Nature } from "#enums/nature"; import { MoveId } from "#enums/move-id"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { TheWinstrateChallengeEncounter } from "#app/data/mystery-encounters/encounters/the-winstrate-challenge-encounter"; import { Status } from "#app/data/status-effect"; import { MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases"; 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 2bffa26ff4a..9ab5f16d1b9 100644 --- a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts +++ b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts @@ -8,7 +8,7 @@ import { type EnemyPokemonConfig, generateModifierType, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { BiomeId } from "#enums/biome-id"; import { MysteryEncounterType } from "#app/enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts index 65c266a5a6c..ec64a17d291 100644 --- a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts +++ b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts @@ -21,7 +21,7 @@ import { CommandPhase } from "#app/phases/command-phase"; import { UncommonBreedEncounter } from "#app/data/mystery-encounters/encounters/uncommon-breed-encounter"; import { MovePhase } from "#app/phases/move-phase"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { BerryType } from "#enums/berry-type"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { Stat } from "#enums/stat"; diff --git a/test/mystery-encounter/mystery-encounter-utils.test.ts b/test/mystery-encounter/mystery-encounter-utils.test.ts index f327d8f9e9c..b775ce8df60 100644 --- a/test/mystery-encounter/mystery-encounter-utils.test.ts +++ b/test/mystery-encounter/mystery-encounter-utils.test.ts @@ -14,7 +14,7 @@ import { getRandomSpeciesByStarterCost, koPlayerPokemon, } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { PokemonType } from "#enums/pokemon-type"; import { MessagePhase } from "#app/phases/message-phase"; import GameManager from "#test/testUtils/gameManager"; diff --git a/test/phases/select-modifier-phase.test.ts b/test/phases/select-modifier-phase.test.ts index 6e92861260e..b6c3089e236 100644 --- a/test/phases/select-modifier-phase.test.ts +++ b/test/phases/select-modifier-phase.test.ts @@ -1,5 +1,5 @@ import type BattleScene from "#app/battle-scene"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { PlayerPokemon } from "#app/field/pokemon"; import { ModifierTier } from "#enums/modifier-tier"; import type { CustomModifierSettings } from "#app/modifier/modifier-type"; diff --git a/test/testUtils/gameManagerUtils.ts b/test/testUtils/gameManagerUtils.ts index eda3921e9e0..57fd9b91d26 100644 --- a/test/testUtils/gameManagerUtils.ts +++ b/test/testUtils/gameManagerUtils.ts @@ -3,7 +3,8 @@ import { BattleType } from "#enums/battle-type"; import type BattleScene from "#app/battle-scene"; import { getDailyRunStarters } from "#app/data/daily-run"; import { Gender } from "#app/data/gender"; -import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import { getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { PlayerPokemon } from "#app/field/pokemon"; import { getGameMode } from "#app/game-mode"; import { GameModes } from "#enums/game-modes"; diff --git a/test/ui/pokedex.test.ts b/test/ui/pokedex.test.ts index d3fc4b11968..13f595e0c60 100644 --- a/test/ui/pokedex.test.ts +++ b/test/ui/pokedex.test.ts @@ -6,7 +6,9 @@ import { FilterTextRow } from "#app/ui/filter-text"; import { allAbilities } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { SpeciesId } from "#enums/species-id"; -import { allSpecies, getPokemonSpecies, type PokemonForm } from "#app/data/pokemon-species"; +import type { PokemonForm } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; +import { allSpecies } from "#app/data/data-lists"; import { Button } from "#enums/buttons"; import { DropDownColumn } from "#enums/drop-down-column"; import type PokemonSpecies from "#app/data/pokemon-species"; diff --git a/test/ui/starter-select.test.ts b/test/ui/starter-select.test.ts index 3e540c4e2c5..8167ab17957 100644 --- a/test/ui/starter-select.test.ts +++ b/test/ui/starter-select.test.ts @@ -1,6 +1,6 @@ import { Gender } from "#app/data/gender"; import { Nature } from "#enums/nature"; -import { allSpecies } from "#app/data/pokemon-species"; +import { allSpecies } from "#app/data/data-lists"; import { GameModes } from "#enums/game-modes"; import { EncounterPhase } from "#app/phases/encounter-phase"; import { SelectStarterPhase } from "#app/phases/select-starter-phase"; From 6ff258fb378590d0ac65576232520120105c368c Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Mon, 16 Jun 2025 08:02:09 -0500 Subject: [PATCH 4/4] [Bug] Fix weather form changes not changing with weather abilities https://github.com/pagefaultgames/pokerogue/pull/5857 * Add test for forecast * Fix PostSummonFormChangeByWeatherAbAttr * Fix overrides in forecast test * Remove a test whose trigger conditions can no longer happen * Update src/data/abilities/ability.ts Co-authored-by: Dean <69436131+emdeann@users.noreply.github.com> * Fix missing tsdoc param * Apply kev's suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Simplify PostSummonFormChangeByWeather's canApplyPostSummon * Fix unused params that messed up after rebase * Fix form change import Messed up due to improper rebase --------- Co-authored-by: Dean <69436131+emdeann@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/abilities/ability.ts | 44 +++++++++++++++++++-------------- test/abilities/forecast.test.ts | 40 ++++++++++++++---------------- 2 files changed, 43 insertions(+), 41 deletions(-) diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 3124f782ff5..70195d6a152 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -24,11 +24,11 @@ import { allMoves } from "../data-lists"; import { ArenaTagSide } from "#enums/arena-tag-side"; import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { TerrainType } from "#app/data/terrain"; +import { pokemonFormChanges } from "../pokemon-forms"; import { - SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger, + SpeciesFormChangeAbilityTrigger, } from "../pokemon-forms/form-change-triggers"; -import { SpeciesFormChangeAbilityTrigger } from "../pokemon-forms/form-change-triggers"; import i18next from "i18next"; import { Command } from "#enums/command"; import { BerryModifierType } from "#app/modifier/modifier-type"; @@ -3971,27 +3971,32 @@ export class PostSummonFormChangeByWeatherAbAttr extends PostSummonAbAttr { this.ability = ability; } + /** + * Determine if the pokemon has a forme change that is triggered by the weather + * + * @param pokemon - The pokemon with the forme change ability + * @param _passive - unused + * @param _simulated - unused + * @param _args - unused + */ override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { - const isCastformWithForecast = - pokemon.species.speciesId === SpeciesId.CASTFORM && this.ability === AbilityId.FORECAST; - const isCherrimWithFlowerGift = - pokemon.species.speciesId === SpeciesId.CHERRIM && this.ability === AbilityId.FLOWER_GIFT; - return isCastformWithForecast || isCherrimWithFlowerGift; + return !!pokemonFormChanges[pokemon.species.speciesId]?.some( + fc => fc.findTrigger(SpeciesFormChangeWeatherTrigger) && fc.canChange(pokemon), + ); } /** - * Calls the {@linkcode BattleScene.triggerPokemonFormChange | triggerPokemonFormChange} for both - * {@linkcode SpeciesFormChange.SpeciesFormChangeWeatherTrigger | SpeciesFormChangeWeatherTrigger} and - * {@linkcode SpeciesFormChange.SpeciesFormChangeWeatherTrigger | SpeciesFormChangeRevertWeatherFormTrigger} if it - * is the specific Pokemon and ability - * @param {Pokemon} pokemon the Pokemon with this ability - * @param _passive n/a - * @param _args n/a + * Trigger the pokemon's forme change by invoking + * {@linkcode BattleScene.triggerPokemonFormChange | triggerPokemonFormChange} + * + * @param pokemon - The Pokemon with this ability + * @param _passive - unused + * @param simulated - unused + * @param _args - unused */ override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated) { globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeWeatherTrigger); - globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeRevertWeatherFormTrigger); } } } @@ -5301,10 +5306,11 @@ export class PostWeatherChangeFormChangeAbAttr extends PostWeatherChangeAbAttr { /** * Calls {@linkcode Arena.triggerWeatherBasedFormChangesToNormal | triggerWeatherBasedFormChangesToNormal} when the * weather changed to form-reverting weather, otherwise calls {@linkcode Arena.triggerWeatherBasedFormChanges | triggerWeatherBasedFormChanges} - * @param {Pokemon} _pokemon the Pokemon with this ability - * @param _passive n/a - * @param _weather n/a - * @param _args n/a + * @param _pokemon - The Pokemon with this ability + * @param _passive - unused + * @param simulated - unused + * @param _weather - unused + * @param _args - unused */ override applyPostWeatherChange( _pokemon: Pokemon, diff --git a/test/abilities/forecast.test.ts b/test/abilities/forecast.test.ts index 8d3a679c917..9111766ebdf 100644 --- a/test/abilities/forecast.test.ts +++ b/test/abilities/forecast.test.ts @@ -21,26 +21,10 @@ describe("Abilities - Forecast", () => { const RAINY_FORM = 2; const SNOWY_FORM = 3; - /** - * Tests form changes based on weather changes - * @param {GameManager} game The game manager instance - * @param {WeatherType} weather The active weather to set - * @param form The expected form based on the active weather - * @param initialForm The initial form pre form change - */ - const testWeatherFormChange = async (game: GameManager, weather: WeatherType, form: number, initialForm?: number) => { - game.override.weather(weather).starterForms({ [SpeciesId.CASTFORM]: initialForm }); - await game.classicMode.startBattle([SpeciesId.CASTFORM]); - - game.move.select(MoveId.SPLASH); - - expect(game.scene.getPlayerPokemon()?.formIndex).toBe(form); - }; - /** * Tests reverting to normal form when Cloud Nine/Air Lock is active on the field - * @param {GameManager} game The game manager instance - * @param {AbilityId} ability The ability that is active on the field + * @param game - The game manager instance + * @param ability - The ability that is active on the field */ const testRevertFormAgainstAbility = async (game: GameManager, ability: AbilityId) => { game.override.starterForms({ [SpeciesId.CASTFORM]: SUNNY_FORM }).enemyAbility(ability); @@ -191,10 +175,6 @@ describe("Abilities - Forecast", () => { 30 * 1000, ); - it("reverts to Normal Form during Clear weather", async () => { - await testWeatherFormChange(game, WeatherType.NONE, NORMAL_FORM, SUNNY_FORM); - }); - it("reverts to Normal Form if a Pokémon on the field has Air Lock", async () => { await testRevertFormAgainstAbility(game, AbilityId.AIR_LOCK); }); @@ -277,4 +257,20 @@ describe("Abilities - Forecast", () => { expect(castform.formIndex).toBe(NORMAL_FORM); }); + + // NOTE: The following pairs of tests are intentionally testing the same scenario, switching the player and enemy pokemon + // as this is a regression test where the order of player and enemy mattered. + it("should trigger player's form change when summoned at the same time as an enemy with a weather changing ability", async () => { + game.override.enemyAbility(AbilityId.DROUGHT); + await game.classicMode.startBattle([SpeciesId.CASTFORM, SpeciesId.MAGIKARP]); + const castform = game.scene.getPlayerPokemon()!; + expect(castform.formIndex).toBe(SUNNY_FORM); + }); + + it("should trigger enemy's form change when summoned at the same time as a player with a weather changing ability", async () => { + game.override.ability(AbilityId.DROUGHT).enemySpecies(SpeciesId.CASTFORM).enemyAbility(AbilityId.FORECAST); + await game.classicMode.startBattle([SpeciesId.MAGIKARP]); + const castform = game.scene.getEnemyPokemon()!; + expect(castform.formIndex).toBe(SUNNY_FORM); + }); });