diff --git a/src/@types/arena-tags.ts b/src/@types/arena-tags.ts index afcc8a0f924..bac9e815c31 100644 --- a/src/@types/arena-tags.ts +++ b/src/@types/arena-tags.ts @@ -2,7 +2,7 @@ import type { ArenaTagTypeMap } from "#data/arena-tag"; import type { ArenaTagType } from "#enums/arena-tag-type"; /** Subset of {@linkcode ArenaTagType}s that apply some negative effect to pokemon that switch in ({@link https://bulbapedia.bulbagarden.net/wiki/List_of_moves_that_cause_entry_hazards#List_of_traps | entry hazards} and Imprison. */ -export type ArenaTrapTagType = +export type EntryHazardTagType = | ArenaTagType.STICKY_WEB | ArenaTagType.SPIKES | ArenaTagType.TOXIC_SPIKES diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 03670835dbd..b0089c414d2 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -6,7 +6,7 @@ import type { SpeciesFormChangeRevertWeatherFormTrigger } from "#data/form-chang import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; -import type { ArenaTrapTag, SuppressAbilitiesTag } from "#data/arena-tag"; +import type { EntryHazardTag, SuppressAbilitiesTag } from "#data/arena-tag"; import type { BattlerTag } from "#data/battler-tags"; import { GroundedTag } from "#data/battler-tags"; import { getBerryEffectFunc } from "#data/berry"; @@ -1113,7 +1113,7 @@ export class PostDefendApplyArenaTrapTagAbAttr extends PostDefendAbAttr { } override canApply({ pokemon, opponent: attacker, move }: PostMoveInteractionAbAttrParams): boolean { - const tag = globalScene.arena.getTag(this.arenaTagType) as ArenaTrapTag; + const tag = globalScene.arena.getTag(this.arenaTagType) as EntryHazardTag; return ( this.condition(pokemon, attacker, move) && (!globalScene.arena.getTag(this.arenaTagType) || tag.layers < tag.maxLayers) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index a2efc1ba067..79af73da82c 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -24,7 +24,7 @@ import type { Pokemon } from "#field/pokemon"; import type { ArenaScreenTagType, ArenaTagTypeData, - ArenaTrapTagType, + EntryHazardTagType, SerializableArenaTagType, } from "#types/arena-tags"; import type { Mutable } from "#types/type-helpers"; @@ -725,12 +725,12 @@ export class IonDelugeTag extends ArenaTag { } /** - * Abstract class to implement [arena traps (AKA entry hazards)](https://bulbapedia.bulbagarden.net/wiki/List_of_moves_that_cause_entry_hazards). + * Abstract class to implement [entry hazards](https://bulbapedia.bulbagarden.net/wiki/List_of_moves_that_cause_entry_hazards). * These persistent tags remain on-field across turns and apply effects to any {@linkcode Pokemon} switching in. \ - * Uniquely, adding a tag multiple times will stack multiple "layers" of the effect, increasing its severity. + * Uniquely, adding a tag multiple times may stack multiple "layers" of the effect, increasing its severity. */ -export abstract class ArenaTrapTag extends SerializableArenaTag { - abstract readonly tagType: ArenaTrapTagType; +export abstract class EntryHazardTag extends SerializableArenaTag { + public declare abstract readonly tagType: EntryHazardTagType; /** * The current number of layers this tag has. * Starts at 1 and increases each time the trap is laid. @@ -837,7 +837,7 @@ export abstract class ArenaTrapTag extends SerializableArenaTag { * Abstract class to implement damaging entry hazards. * Currently used for {@linkcode SpikesTag} and {@linkcode StealthRockTag}. */ -abstract class DamagingTrapTag extends ArenaTrapTag { +abstract class DamagingTrapTag extends EntryHazardTag { override activateTrap(pokemon: Pokemon, simulated: boolean): boolean { // Check for magic guard immunity const cancelled = new BooleanHolder(false); @@ -956,7 +956,7 @@ class StealthRockTag extends DamagingTrapTag { * based on the current layer count. \ * Poison-type Pokémon will remove it entirely upon switch-in. */ -class ToxicSpikesTag extends ArenaTrapTag { +class ToxicSpikesTag extends EntryHazardTag { /** * Whether the tag is currently in the process of being neutralized by a Poison-type. * @defaultValue `false` @@ -1024,7 +1024,7 @@ class ToxicSpikesTag extends ArenaTrapTag { * Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Sticky_Web_(move) | Sticky Web}. * Applies a single-layer trap that lowers the Speed of all grounded Pokémon switching in. */ -class StickyWebTag extends ArenaTrapTag { +class StickyWebTag extends EntryHazardTag { public readonly tagType = ArenaTagType.STICKY_WEB; public override get maxLayers() { return 1 as const; @@ -1087,7 +1087,7 @@ class StickyWebTag extends ArenaTrapTag { * Imprison remains in effect as long as the source Pokemon is active and present on the field. * Imprison will apply to any opposing Pokemon that switch onto the field as well. */ -class ImprisonTag extends ArenaTrapTag { +class ImprisonTag extends EntryHazardTag { public readonly tagType = ArenaTagType.IMPRISON; public override get maxLayers() { return 1 as const; diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 5c4061ec388..794214cbca2 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -6,7 +6,7 @@ import { loggedInUser } from "#app/account"; import type { GameMode } from "#app/game-mode"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; -import type { ArenaTrapTag } from "#data/arena-tag"; +import type { EntryHazardTag } from "#data/arena-tag"; import { WeakenMoveTypeTag } from "#data/arena-tag"; import { MoveChargeAnim } from "#data/battle-anims"; import { @@ -6083,7 +6083,7 @@ export class AddArenaTrapTagAttr extends AddArenaTagAttr { getCondition(): MoveConditionFunc { return (user, target, move) => { const side = (this.selfSideTarget !== user.isPlayer()) ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER; - const tag = globalScene.arena.getTagOnSide(this.tagType, side) as ArenaTrapTag; + const tag = globalScene.arena.getTagOnSide(this.tagType, side) as EntryHazardTag; if (!tag) { return true; } @@ -6107,7 +6107,7 @@ export class AddArenaTrapTagHitAttr extends AddArenaTagAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true); const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - const tag = globalScene.arena.getTagOnSide(this.tagType, side) as ArenaTrapTag; + const tag = globalScene.arena.getTagOnSide(this.tagType, side) as EntryHazardTag; if ((moveChance < 0 || moveChance === 100 || user.randBattleSeedInt(100) < moveChance) && user.getLastXMoves(1)[0]?.result === MoveResult.SUCCESS) { globalScene.arena.addTag(this.tagType, 0, move.id, user.id, side); if (!tag) { diff --git a/src/field/arena.ts b/src/field/arena.ts index 2ce347b5337..802d41317e8 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -8,7 +8,7 @@ import Overrides from "#app/overrides"; import type { BiomeTierTrainerPools, PokemonPools } from "#balance/biomes"; import { BiomePoolTier, biomePokemonPools, biomeTrainerPools } from "#balance/biomes"; import type { ArenaTag } from "#data/arena-tag"; -import { ArenaTrapTag, getArenaTag } from "#data/arena-tag"; +import { EntryHazardTag, getArenaTag } from "#data/arena-tag"; import { SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "#data/form-change-triggers"; import type { PokemonSpecies } from "#data/pokemon-species"; import { PositionalTagManager } from "#data/positional-tags/positional-tag-manager"; @@ -709,8 +709,8 @@ export class Arena { if (existingTag) { existingTag.onOverlap(this, globalScene.getPokemonById(sourceId)); - if (existingTag instanceof ArenaTrapTag) { - const { tagType, side, turnCount, layers, maxLayers } = existingTag as ArenaTrapTag; + if (existingTag instanceof EntryHazardTag) { + const { tagType, side, turnCount, layers, maxLayers } = existingTag as EntryHazardTag; this.eventTarget.dispatchEvent(new TagAddedEvent(tagType, side, turnCount, layers, maxLayers)); } @@ -723,7 +723,7 @@ export class Arena { newTag.onAdd(this, quiet); this.tags.push(newTag); - const { layers = 0, maxLayers = 0 } = newTag instanceof ArenaTrapTag ? newTag : {}; + const { layers = 0, maxLayers = 0 } = newTag instanceof EntryHazardTag ? newTag : {}; this.eventTarget.dispatchEvent( new TagAddedEvent(newTag.tagType, newTag.side, newTag.turnCount, layers, maxLayers), diff --git a/src/field/trainer.ts b/src/field/trainer.ts index 584c9310932..56b5c0660fa 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -1,7 +1,7 @@ import { globalScene } from "#app/global-scene"; import { pokemonPrevolutions } from "#balance/pokemon-evolutions"; import { signatureSpecies } from "#balance/signature-species"; -import { ArenaTrapTag } from "#data/arena-tag"; +import { EntryHazardTag } from "#data/arena-tag"; import type { PokemonSpecies } from "#data/pokemon-species"; import { ArenaTagSide } from "#enums/arena-tag-side"; import { PartyMemberStrength } from "#enums/party-member-strength"; @@ -584,8 +584,8 @@ export class Trainer extends Phaser.GameObjects.Container { score /= playerField.length; if (forSwitch && !p.isOnField()) { globalScene.arena - .findTagsOnSide(t => t instanceof ArenaTrapTag, ArenaTagSide.ENEMY) - .map(t => (score *= (t as ArenaTrapTag).getMatchupScoreMultiplier(p))); + .findTagsOnSide(t => t instanceof EntryHazardTag, ArenaTagSide.ENEMY) + .map(t => (score *= (t as EntryHazardTag).getMatchupScoreMultiplier(p))); } } diff --git a/src/phases/post-summon-phase.ts b/src/phases/post-summon-phase.ts index e0811d0ab93..913a29cded8 100644 --- a/src/phases/post-summon-phase.ts +++ b/src/phases/post-summon-phase.ts @@ -1,6 +1,6 @@ import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; -import { ArenaTrapTag } from "#data/arena-tag"; +import { EntryHazardTag } from "#data/arena-tag"; import { MysteryEncounterPostSummonTag } from "#data/battler-tags"; import { BattlerTagType } from "#enums/battler-tag-type"; import { StatusEffect } from "#enums/status-effect"; @@ -16,7 +16,7 @@ export class PostSummonPhase extends PokemonPhase { if (pokemon.status?.effect === StatusEffect.TOXIC) { pokemon.status.toxicTurnCount = 0; } - globalScene.arena.applyTags(ArenaTrapTag, false, pokemon); + globalScene.arena.applyTags(EntryHazardTag, false, pokemon); // If this is mystery encounter and has post summon phase tag, apply post summon effects if ( diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 90cbf6e18cc..14224751262 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -10,7 +10,7 @@ import { Tutorial } from "#app/tutorial"; import { speciesEggMoves } from "#balance/egg-moves"; import { pokemonPrevolutions } from "#balance/pokemon-evolutions"; import { speciesStarterCosts } from "#balance/starters"; -import { ArenaTrapTag } from "#data/arena-tag"; +import { EntryHazardTag } from "#data/arena-tag"; import { allMoves, allSpecies } from "#data/data-lists"; import type { Egg } from "#data/egg"; import { pokemonFormChanges } from "#data/pokemon-forms"; @@ -1135,8 +1135,8 @@ export class GameData { globalScene.arena.tags = sessionData.arena.tags; if (globalScene.arena.tags) { for (const tag of globalScene.arena.tags) { - if (tag instanceof ArenaTrapTag) { - const { tagType, side, turnCount, layers, maxLayers } = tag as ArenaTrapTag; + if (tag instanceof EntryHazardTag) { + const { tagType, side, turnCount, layers, maxLayers } = tag as EntryHazardTag; globalScene.arena.eventTarget.dispatchEvent( new TagAddedEvent(tagType, side, turnCount, layers, maxLayers), ); diff --git a/src/ui/arena-flyout.ts b/src/ui/arena-flyout.ts index e243bef342e..da062f5c96f 100644 --- a/src/ui/arena-flyout.ts +++ b/src/ui/arena-flyout.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import { ArenaTrapTag } from "#data/arena-tag"; +import { EntryHazardTag } from "#data/arena-tag"; import { TerrainType } from "#data/terrain"; import { ArenaTagSide } from "#enums/arena-tag-side"; import { ArenaTagType } from "#enums/arena-tag-type"; @@ -287,7 +287,7 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { switch (arenaEffectChangedEvent.constructor) { case TagAddedEvent: { const tagAddedEvent = arenaEffectChangedEvent as TagAddedEvent; - const isArenaTrapTag = globalScene.arena.getTag(tagAddedEvent.arenaTagType) instanceof ArenaTrapTag; + const isArenaTrapTag = globalScene.arena.getTag(tagAddedEvent.arenaTagType) instanceof EntryHazardTag; let arenaEffectType: ArenaEffectType; if (tagAddedEvent.arenaTagSide === ArenaTagSide.BOTH) { diff --git a/test/moves/ceaseless-edge.test.ts b/test/moves/ceaseless-edge.test.ts index 64f4cf15511..b06ea84308c 100644 --- a/test/moves/ceaseless-edge.test.ts +++ b/test/moves/ceaseless-edge.test.ts @@ -1,4 +1,4 @@ -import { ArenaTrapTag } from "#data/arena-tag"; +import { EntryHazardTag } from "#data/arena-tag"; import { allMoves } from "#data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagSide } from "#enums/arena-tag-side"; @@ -50,12 +50,12 @@ describe("Moves - Ceaseless Edge", () => { await game.phaseInterceptor.to(MoveEffectPhase, false); // Spikes should not have any layers before move effect is applied - const tagBefore = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag; - expect(tagBefore instanceof ArenaTrapTag).toBeFalsy(); + const tagBefore = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as EntryHazardTag; + expect(tagBefore instanceof EntryHazardTag).toBeFalsy(); await game.phaseInterceptor.to(TurnEndPhase); - const tagAfter = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag; - expect(tagAfter instanceof ArenaTrapTag).toBeTruthy(); + const tagAfter = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as EntryHazardTag; + expect(tagAfter instanceof EntryHazardTag).toBeTruthy(); expect(tagAfter.layers).toBe(1); expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp); }); @@ -72,12 +72,12 @@ describe("Moves - Ceaseless Edge", () => { await game.phaseInterceptor.to(MoveEffectPhase, false); // Spikes should not have any layers before move effect is applied - const tagBefore = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag; - expect(tagBefore instanceof ArenaTrapTag).toBeFalsy(); + const tagBefore = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as EntryHazardTag; + expect(tagBefore instanceof EntryHazardTag).toBeFalsy(); await game.phaseInterceptor.to(TurnEndPhase); - const tagAfter = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag; - expect(tagAfter instanceof ArenaTrapTag).toBeTruthy(); + const tagAfter = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as EntryHazardTag; + expect(tagAfter instanceof EntryHazardTag).toBeTruthy(); expect(tagAfter.layers).toBe(2); expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp); }); @@ -90,12 +90,12 @@ describe("Moves - Ceaseless Edge", () => { game.move.select(MoveId.CEASELESS_EDGE); await game.phaseInterceptor.to(MoveEffectPhase, false); // Spikes should not have any layers before move effect is applied - const tagBefore = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag; - expect(tagBefore instanceof ArenaTrapTag).toBeFalsy(); + const tagBefore = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as EntryHazardTag; + expect(tagBefore instanceof EntryHazardTag).toBeFalsy(); await game.toNextTurn(); - const tagAfter = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag; - expect(tagAfter instanceof ArenaTrapTag).toBeTruthy(); + const tagAfter = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as EntryHazardTag; + expect(tagAfter instanceof EntryHazardTag).toBeTruthy(); expect(tagAfter.layers).toBe(2); const hpBeforeSpikes = game.scene.currentBattle.enemyParty[1].hp; diff --git a/test/moves/destiny-bond.test.ts b/test/moves/destiny-bond.test.ts index 9c397717335..118a45e7682 100644 --- a/test/moves/destiny-bond.test.ts +++ b/test/moves/destiny-bond.test.ts @@ -1,4 +1,4 @@ -import type { ArenaTrapTag } from "#data/arena-tag"; +import type { EntryHazardTag } from "#data/arena-tag"; import { allMoves } from "#data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagSide } from "#enums/arena-tag-side"; @@ -195,7 +195,7 @@ describe("Moves - Destiny Bond", () => { expect(playerPokemon.isFainted()).toBe(true); // Ceaseless Edge spikes effect should still activate - const tagAfter = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag; + const tagAfter = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as EntryHazardTag; expect(tagAfter.tagType).toBe(ArenaTagType.SPIKES); expect(tagAfter.layers).toBe(1); }); @@ -220,7 +220,10 @@ describe("Moves - Destiny Bond", () => { expect(playerPokemon1?.isFainted()).toBe(true); // Pledge secondary effect should still activate - const tagAfter = game.scene.arena.getTagOnSide(ArenaTagType.GRASS_WATER_PLEDGE, ArenaTagSide.ENEMY) as ArenaTrapTag; + const tagAfter = game.scene.arena.getTagOnSide( + ArenaTagType.GRASS_WATER_PLEDGE, + ArenaTagSide.ENEMY, + ) as EntryHazardTag; expect(tagAfter.tagType).toBe(ArenaTagType.GRASS_WATER_PLEDGE); }); diff --git a/test/moves/entry-hazards.test.ts b/test/moves/entry-hazards.test.ts index 2431c9512d5..c4dead1bb67 100644 --- a/test/moves/entry-hazards.test.ts +++ b/test/moves/entry-hazards.test.ts @@ -1,5 +1,4 @@ import { getPokemonNameWithAffix } from "#app/messages"; -import { ArenaTrapTag } from "#data/arena-tag"; import { allMoves } from "#data/data-lists"; import type { TypeDamageMultiplier } from "#data/type"; import { AbilityId } from "#enums/ability-id"; @@ -14,7 +13,7 @@ import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import { GameManager } from "#test/test-utils/game-manager"; -import type { ArenaTrapTagType } from "#types/arena-tags"; +import type { EntryHazardTagType } from "#types/arena-tags"; import i18next from "i18next"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -46,7 +45,7 @@ describe("Moves - Entry Hazards", () => { .battleType(BattleType.TRAINER); }); - describe.each<{ name: string; move: MoveId; tagType: ArenaTrapTagType }>([ + describe.each<{ name: string; move: MoveId; tagType: EntryHazardTagType }>([ { name: "Spikes", move: MoveId.SPIKES, tagType: ArenaTagType.SPIKES }, { name: "Toxic Spikes", @@ -79,18 +78,19 @@ describe("Moves - Entry Hazards", () => { // TODO: re-enable after re-fixing hazards moves it.todo("should work when all targets fainted", async () => { - game.override.enemySpecies(SpeciesId.DIGLETT).battleStyle("double").startingLevel(1000); + game.override.battleStyle("double"); await game.classicMode.startBattle([SpeciesId.RAYQUAZA, SpeciesId.SHUCKLE]); const [enemy1, enemy2] = game.scene.getEnemyField(); - game.move.use(MoveId.HYPER_VOICE, BattlerIndex.PLAYER); - game.move.use(MoveId.SPIKES, BattlerIndex.PLAYER_2); + game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER); + game.move.use(move, BattlerIndex.PLAYER_2); + await game.doKillOpponents(); await game.toEndOfTurn(); expect(enemy1.isFainted()).toBe(true); expect(enemy2.isFainted()).toBe(true); - expect(game.scene.arena.getTagOnSide(ArenaTrapTag, ArenaTagSide.ENEMY)).toBeDefined(); + expect(game).toHaveArenaTag(tagType, ArenaTagSide.ENEMY); }); const maxLayers = tagType === ArenaTagType.SPIKES ? 3 : tagType === ArenaTagType.TOXIC_SPIKES ? 2 : 1;