diff --git a/src/data/ability.ts b/src/data/ability.ts index 8de0c68a8e7..5f19af8cea4 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -12,7 +12,7 @@ import { getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffect import { Gender } from "./gender"; import type Move from "./move"; import { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, NeutralDamageAgainstFlyingTypeMultiplierAttr, FixedDamageAttr } from "./move"; -import type { ArenaTrapTag } from "./arena-tag"; +import type { ArenaTrapTag, SuppressAbilitiesTag } from "./arena-tag"; import { ArenaTagSide } from "./arena-tag"; import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "../modifier/modifier"; import { TerrainType } from "./terrain"; @@ -2197,6 +2197,34 @@ export class PostSummonRemoveArenaTagAbAttr extends PostSummonAbAttr { } } +/** + * Generic class to add an arena tag upon switching in + */ +export class PostSummonAddArenaTagAbAttr extends PostSummonAbAttr { + private readonly tagType: ArenaTagType; + private readonly turnCount: number; + private readonly side?: ArenaTagSide; + private readonly quiet?: boolean; + private sourceId: number; + + + constructor(tagType: ArenaTagType, turnCount: number, side?: ArenaTagSide, quiet?: boolean) { + super(false); + this.tagType = tagType; + this.turnCount = turnCount; + this.side = side; + this.quiet = quiet; + } + + public override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + this.sourceId = pokemon.id; + if (!simulated) { + globalScene.arena.addTag(this.tagType, this.turnCount, undefined, this.sourceId, this.side, this.quiet); + } + return true; + } +} + export class PostSummonMessageAbAttr extends PostSummonAbAttr { private messageFunc: (pokemon: Pokemon) => string; @@ -2941,6 +2969,26 @@ export class PreLeaveFieldClearWeatherAbAttr extends PreLeaveFieldAbAttr { } } +/** + * Updates the active {@linkcode SuppressAbilitiesTag} when a pokemon with {@linkcode Abilities.NEUTRALIZING_GAS} leaves the field + */ +export class PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr extends PreLeaveFieldAbAttr { + constructor() { + super(false); + } + + public override applyPreLeaveField(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + if (!simulated) { + const suppressTag = globalScene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS) as SuppressAbilitiesTag; + if (suppressTag) { + suppressTag.onSourceLeave(globalScene.arena); + return true; + } + } + return simulated; + } +} + export class PreStatStageChangeAbAttr extends AbAttr { applyPreStatStageChange( pokemon: Pokemon | null, @@ -4692,21 +4740,6 @@ export class MoveAbilityBypassAbAttr extends AbAttr { } } -export class SuppressFieldAbilitiesAbAttr extends AbAttr { - constructor() { - super(false); - } - - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - const ability = (args[0] as Ability); - if (!ability.hasAttr(UnsuppressableAbilityAbAttr) && !ability.hasAttr(SuppressFieldAbilitiesAbAttr)) { - cancelled.value = true; - return true; - } - return false; - } -} - export class AlwaysHitAbAttr extends AbAttr { } /** Attribute for abilities that allow moves that make contact to ignore protection (i.e. Unseen Fist) */ @@ -5946,10 +5979,10 @@ export function applyOnGainAbAttrs(pokemon: Pokemon, passive: boolean = false, s } /** - * Clears primal weather during the turn if {@linkcode pokemon}'s ability corresponds to one + * Clears primal weather/neutralizing gas during the turn if {@linkcode pokemon}'s ability corresponds to one */ -export function applyOnLoseClearWeatherAbAttrs(pokemon: Pokemon, passive: boolean = false, simulated: boolean = false, ...args: any[]): void { - applySingleAbAttrs(pokemon, passive, PreLeaveFieldClearWeatherAbAttr, (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [ ...args, true ]), args, true, simulated); +export function applyOnLoseAbAttrs(pokemon: Pokemon, passive: boolean = false, simulated: boolean = false, ...args: any[]): void { + applySingleAbAttrs(pokemon, passive, PreLeaveFieldAbAttr, (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [ ...args, true ]), args, true, simulated); } function queueShowAbility(pokemon: Pokemon, passive: boolean): void { globalScene.unshiftPhase(new ShowAbilityPhase(pokemon.id, passive)); @@ -6838,12 +6871,11 @@ export function initAbilities() { new Ability(Abilities.GORILLA_TACTICS, 8) .attr(GorillaTacticsAbAttr), new Ability(Abilities.NEUTRALIZING_GAS, 8) - .attr(SuppressFieldAbilitiesAbAttr) + .attr(PostSummonAddArenaTagAbAttr, ArenaTagType.NEUTRALIZING_GAS, 0) + .attr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) - .attr(NoTransformAbilityAbAttr) - .attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonNeutralizingGas", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })) - .partial(), // A bunch of weird interactions with other abilities being suppressed then unsuppressed + .attr(NoTransformAbilityAbAttr), new Ability(Abilities.PASTEL_VEIL, 8) .attr(PostSummonUserFieldRemoveStatusEffectAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) .attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 2fa4593fd6c..d034ccf83b8 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -8,7 +8,7 @@ import type Pokemon from "#app/field/pokemon"; import { HitResult, PokemonMove } from "#app/field/pokemon"; import { StatusEffect } from "#enums/status-effect"; import type { BattlerIndex } from "#app/battle"; -import { BlockNonDirectDamageAbAttr, InfiltratorAbAttr, ProtectStatAbAttr, applyAbAttrs } from "#app/data/ability"; +import { BlockNonDirectDamageAbAttr, InfiltratorAbAttr, PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, ProtectStatAbAttr, applyAbAttrs, applyOnGainAbAttrs, applyOnLoseAbAttrs } from "#app/data/ability"; import { Stat } from "#enums/stat"; import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims"; import i18next from "i18next"; @@ -1221,6 +1221,69 @@ export class FairyLockTag extends ArenaTag { } +/** + * Arena tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Neutralizing_Gas_(Ability) Neutralizing Gas} + * + * Keeps track of the number of pokemon on the field with Neutralizing Gas - If it drops to zero, the effect is ended and abilities are reactivated + * + * Additionally ends onLose abilities when it is activated + */ +export class SuppressAbilitiesTag extends ArenaTag { + private sourceCount: number; + + constructor(sourceId: number) { + super(ArenaTagType.NEUTRALIZING_GAS, 0, undefined, sourceId); + this.sourceCount = 1; + } + + public override onAdd(arena: Arena): void { + const pokemon = this.getSourcePokemon(); + if (pokemon) { + globalScene.queueMessage(i18next.t("arenaTag:neutralizingGasOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + + for (const fieldPokemon of globalScene.getField()) { + if (fieldPokemon && fieldPokemon.id !== pokemon.id) { + [ true, false ].forEach((passive) => applyOnLoseAbAttrs(fieldPokemon, passive)); + } + } + } + } + + public override onOverlap(arena: Arena): void { + this.sourceCount++; + } + + public onSourceLeave(arena: Arena): void { + this.sourceCount--; + if (this.sourceCount <= 0) { + arena.removeTag(ArenaTagType.NEUTRALIZING_GAS); + } else if (this.sourceCount === 1) { + // With 1 source left, that pokemon's other abilities should reactivate + // This may be confusing for players but would be the most accurate gameplay-wise + // Could have a custom message that plays when a specific pokemon's NG ends? This entire thing exists due to passives after all + const setter = globalScene.getField().filter((p) => p && p.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false))[0]; + applyOnGainAbAttrs(setter, setter.getAbility().hasAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr)); + } + } + + public override onRemove(arena: Arena, quiet: boolean = false) { + if (!quiet) { + globalScene.queueMessage(i18next.t("arenaTag:neutralizingGasOnRemove")); + } + + for (const pokemon of globalScene.getField()) { + // There is only one pokemon with this attr on the field on removal, so its abilities are already active + if (pokemon && !pokemon.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false)) { + [ true, false ].forEach((passive) => applyOnGainAbAttrs(pokemon, passive)); + } + } + } + + public shouldApplyToSelf(): boolean { + return this.sourceCount > 1; + } +} + // TODO: swap `sourceMove` and `sourceId` and make `sourceMove` an optional parameter export function getArenaTag(tagType: ArenaTagType, turnCount: number, sourceMove: Moves | undefined, sourceId: number, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null { switch (tagType) { @@ -1281,6 +1344,8 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: number, sourceMove return new GrassWaterPledgeTag(sourceId, side); case ArenaTagType.FAIRY_LOCK: return new FairyLockTag(turnCount, sourceId); + case ArenaTagType.NEUTRALIZING_GAS: + return new SuppressAbilitiesTag(sourceId); default: return null; } diff --git a/src/data/move.ts b/src/data/move.ts index d9ceb0f3dce..26b182ec5db 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -7811,11 +7811,12 @@ export class SuppressAbilitiesAttr extends MoveEffectAttr { return false; } - target.summonData.abilitySuppressed = true; - globalScene.arena.triggerWeatherBasedFormChangesToNormal(); - globalScene.queueMessage(i18next.t("moveTriggers:suppressAbilities", { pokemonName: getPokemonNameWithAffix(target) })); + target.suppressAbility(); + + globalScene.arena.triggerWeatherBasedFormChangesToNormal(); + return true; } diff --git a/src/enums/arena-tag-type.ts b/src/enums/arena-tag-type.ts index 1c62ccb14a6..4180aa00ef5 100644 --- a/src/enums/arena-tag-type.ts +++ b/src/enums/arena-tag-type.ts @@ -29,4 +29,5 @@ export enum ArenaTagType { WATER_FIRE_PLEDGE = "WATER_FIRE_PLEDGE", GRASS_WATER_PLEDGE = "GRASS_WATER_PLEDGE", FAIRY_LOCK = "FAIRY_LOCK", + NEUTRALIZING_GAS = "NEUTRALIZING_GAS" } diff --git a/src/field/arena.ts b/src/field/arena.ts index 60ee4b5b03c..752eef81596 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -588,8 +588,8 @@ export class Arena { // creates a new tag object const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, targetIndex, side); if (newTag) { - this.tags.push(newTag); newTag.onAdd(this, quiet); + this.tags.push(newTag); const { layers = 0, maxLayers = 0 } = newTag instanceof ArenaTrapTag ? newTag : {}; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 17917ad9b40..bc3b9b1403f 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -63,8 +63,9 @@ import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/ import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag, PowerTrickTag } from "../data/battler-tags"; import { WeatherType } from "#enums/weather-type"; import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag"; +import type { SuppressAbilitiesTag } from "#app/data/arena-tag"; import type { Ability, AbAttr } from "#app/data/ability"; -import { StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs, InfiltratorAbAttr, AlliedFieldDamageReductionAbAttr, PostDamageAbAttr, applyPostDamageAbAttrs, CommanderAbAttr, applyPostItemLostAbAttrs, PostItemLostAbAttr, applyOnGainAbAttrs, PreLeaveFieldAbAttr, applyPreLeaveFieldAbAttrs, applyOnLoseClearWeatherAbAttrs } from "#app/data/ability"; +import { StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs, InfiltratorAbAttr, AlliedFieldDamageReductionAbAttr, PostDamageAbAttr, applyPostDamageAbAttrs, CommanderAbAttr, applyPostItemLostAbAttrs, PostItemLostAbAttr, applyOnGainAbAttrs, PreLeaveFieldAbAttr, applyPreLeaveFieldAbAttrs, applyOnLoseAbAttrs, PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr } from "#app/data/ability"; import type PokemonData from "#app/system/pokemon-data"; import { BattlerIndex } from "#app/battle"; import { Mode } from "#app/ui/ui"; @@ -1487,7 +1488,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param ability New Ability */ public setTempAbility(ability: Ability, passive: boolean = false): void { - applyOnLoseClearWeatherAbAttrs(this, passive); + applyOnLoseAbAttrs(this, passive); if (passive) { this.summonData.passiveAbility = ability.id; } else { @@ -1496,6 +1497,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyOnGainAbAttrs(this, passive); } + /** + * Suppresses an ability and calls its onlose attributes + */ + public suppressAbility() { + this.summonData.abilitySuppressed = true; + [ true, false ].forEach((passive) => applyOnLoseAbAttrs(this, passive)); + } + /** * Checks if a pokemon has a passive either from: * - bought with starter candy @@ -1553,17 +1562,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.summonData?.abilitySuppressed && !ability.hasAttr(UnsuppressableAbilityAbAttr)) { return false; } - if (this.isOnField() && !ability.hasAttr(SuppressFieldAbilitiesAbAttr)) { - const suppressed = new Utils.BooleanHolder(false); - globalScene.getField(true).filter(p => p !== this).map(p => { - if (p.getAbility().hasAttr(SuppressFieldAbilitiesAbAttr) && p.canApplyAbility()) { - p.getAbility().getAttrs(SuppressFieldAbilitiesAbAttr).map(a => a.apply(this, false, false, suppressed, [ ability ])); - } - if (p.getPassiveAbility().hasAttr(SuppressFieldAbilitiesAbAttr) && p.canApplyAbility(true)) { - p.getPassiveAbility().getAttrs(SuppressFieldAbilitiesAbAttr).map(a => a.apply(this, true, false, suppressed, [ ability ])); - } - }); - if (suppressed.value) { + const suppressAbilitiesTag = arena.getTag(ArenaTagType.NEUTRALIZING_GAS) as SuppressAbilitiesTag; + if (this.isOnField() && suppressAbilitiesTag) { + const thisAbilitySuppressing = ability.hasAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr); + const hasSuppressingAbility = this.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false); + // Neutralizing gas is up - suppress abilities unless they are unsuppressable or this pokemon is responsible for the gas + // (Balance decided that the other ability of a neutralizing gas pokemon should not be neutralized) + // If the ability itself is neutralizing gas, don't suppress it (handled through arena tag) + const unsuppressable = ability.hasAttr(UnsuppressableAbilityAbAttr) || thisAbilitySuppressing || (hasSuppressingAbility && !suppressAbilitiesTag.shouldApplyToSelf()); + if (!unsuppressable) { return false; } } diff --git a/test/abilities/neutralizing_gas.test.ts b/test/abilities/neutralizing_gas.test.ts new file mode 100644 index 00000000000..8b9c374f1cc --- /dev/null +++ b/test/abilities/neutralizing_gas.test.ts @@ -0,0 +1,138 @@ +import { BattlerIndex } from "#app/battle"; +import { Abilities } from "#enums/abilities"; +import { ArenaTagType } from "#enums/arena-tag-type"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { Stat } from "#enums/stat"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Neutralizing Gas", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH ]) + .ability(Abilities.NEUTRALIZING_GAS) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should prevent other abilities from activating", async () => { + game.override.enemyAbility(Abilities.INTIMIDATE); + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to("TurnEndPhase"); + + // Intimidate is suppressed, so the attack stat should not be lowered + expect(game.scene.getPlayerPokemon()?.getStatStage(Stat.ATK)).toBe(0); + }); + + it("should allow the user's passive to activate", async () => { + game.override.passiveAbility(Abilities.INTREPID_SWORD); + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(game.scene.getPlayerPokemon()?.getStatStage(Stat.ATK)).toBe(1); + }); + + it.todo("should activate before other abilities", async () => { + game.override.enemySpecies(Species.ACCELGOR) + .enemyLevel(100) + .enemyAbility(Abilities.INTIMIDATE); + + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to("TurnEndPhase"); + + // Intimidate is suppressed even when the user's speed is lower + expect(game.scene.getPlayerPokemon()?.getStatStage(Stat.ATK)).toBe(0); + }); + + it("should activate other abilities when removed", async () => { + game.override.enemyAbility(Abilities.INTREPID_SWORD) + .enemyPassiveAbility(Abilities.DAUNTLESS_SHIELD) + .enemyMoveset(Moves.ENTRAINMENT); + + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const enemyPokemon = game.scene.getEnemyPokemon(); + expect(enemyPokemon?.getStatStage(Stat.ATK)).toBe(0); + expect(enemyPokemon?.getStatStage(Stat.DEF)).toBe(0); + + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to("BerryPhase"); + // Enemy removes user's ability, so both abilities are activated + expect(enemyPokemon?.getStatStage(Stat.ATK)).toBe(1); + expect(enemyPokemon?.getStatStage(Stat.DEF)).toBe(1); + }); + + it("should not activate the user's other ability when removed", async () => { + game.override.passiveAbility(Abilities.INTIMIDATE) + .enemyMoveset(Moves.ENTRAINMENT); + + await game.classicMode.startBattle([ Species.FEEBAS ]); + // Neutralising gas user's passive is still active + const enemyPokemon = game.scene.getEnemyPokemon(); + expect(enemyPokemon?.getStatStage(Stat.ATK)).toBe(-1); + + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to("BerryPhase"); + // Intimidate did not reactivate after neutralizing gas was removed + expect(enemyPokemon?.getStatStage(Stat.ATK)).toBe(-1); + }); + + it("should only deactivate when all setters are off the field", async () => { + game.override.enemyMoveset([ Moves.ENTRAINMENT, Moves.SPLASH ]) + .battleType("double"); + + await game.classicMode.startBattle([ Species.ACCELGOR, Species.ACCELGOR ]); + game.move.select(Moves.SPLASH, 0); + game.move.select(Moves.SPLASH, 1); + await game.forceEnemyMove(Moves.ENTRAINMENT, BattlerIndex.PLAYER); + await game.forceEnemyMove(Moves.SPLASH); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ]); + await game.phaseInterceptor.to("BerryPhase"); + expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeDefined(); // Now one neut gas user is left + + game.move.select(Moves.SPLASH, 0); + game.move.select(Moves.SPLASH, 1); + await game.forceEnemyMove(Moves.ENTRAINMENT, BattlerIndex.PLAYER_2); + await game.forceEnemyMove(Moves.SPLASH); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ]); + await game.phaseInterceptor.to("BerryPhase"); + expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeUndefined(); // No neut gas users are left + }); + + it("should deactivate when suppressed by gastro acid", async () => { + game.override.enemyMoveset(Moves.GASTRO_ACID); + + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to("BerryPhase"); + + expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeUndefined(); + }); + +});