From 8fc0d9a4292a6eadc676adc62e317e91390b9529 Mon Sep 17 00:00:00 2001 From: Lneacx Date: Thu, 3 Oct 2024 07:35:03 -0700 Subject: [PATCH] [P2] Fix Toxic to bypass semi-invulnerability when used by a Poison-type. (#4445) * Fix Toxic to bypass semi-invulnerability when used by a Poison-type. * Apply suggestion to refactor BerryPhase in Toxic move tests. Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Corrected missing quotes on a BerryPhase in Toxic.test. --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/phases/move-effect-phase.ts | 8 +++- src/test/moves/toxic.test.ts | 76 +++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 src/test/moves/toxic.test.ts diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index ca1fb87654f..33f2394cd1e 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -4,7 +4,7 @@ import { applyPreAttackAbAttrs, AddSecondStrikeAbAttr, IgnoreMoveEffectsAbAttr, import { ArenaTagSide, ConditionalProtectTag } from "#app/data/arena-tag"; import { MoveAnim } from "#app/data/battle-anims"; import { BattlerTagLapseType, DamageProtectedTag, ProtectedTag, SemiInvulnerableTag, SubstituteTag } from "#app/data/battler-tags"; -import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, HitsTagAttr } from "#app/data/move"; +import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, HitsTagAttr, ToxicAccuracyAttr } from "#app/data/move"; import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Moves } from "#app/enums/moves"; @@ -14,6 +14,7 @@ import { PokemonMultiHitModifier, FlinchChanceModifier, EnemyAttackStatusEffectC import i18next from "i18next"; import * as Utils from "#app/utils"; import { PokemonPhase } from "./pokemon-phase"; +import { Type } from "#app/data/type"; export class MoveEffectPhase extends PokemonPhase { public move: PokemonMove; @@ -404,7 +405,10 @@ export class MoveEffectPhase extends PokemonPhase { } const semiInvulnerableTag = target.getTag(SemiInvulnerableTag); - if (semiInvulnerableTag && !this.move.getMove().getAttrs(HitsTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType)) { + if (semiInvulnerableTag + && !this.move.getMove().getAttrs(HitsTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType) + && !(this.move.getMove().getAttrs(ToxicAccuracyAttr) && user.isOfType(Type.POISON)) + ) { return false; } diff --git a/src/test/moves/toxic.test.ts b/src/test/moves/toxic.test.ts new file mode 100644 index 00000000000..2d023c201c1 --- /dev/null +++ b/src/test/moves/toxic.test.ts @@ -0,0 +1,76 @@ +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { StatusEffect } from "#enums/status-effect"; +import { BattlerIndex } from "#app/battle"; +import { allMoves } from "#app/data/move"; + +describe("Moves - Toxic", () => { + 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 + .battleType("single") + .moveset(Moves.TOXIC) + .enemySpecies(Species.MAGIKARP) + .enemyMoveset(Moves.SPLASH); + }); + + it("should be guaranteed to hit if user is Poison-type", async () => { + vi.spyOn(allMoves[Moves.TOXIC], "accuracy", "get").mockReturnValue(0); + await game.classicMode.startBattle([Species.TOXAPEX]); + + game.move.select(Moves.TOXIC); + await game.phaseInterceptor.to("BerryPhase", false); + + expect(game.scene.getEnemyPokemon()!.status?.effect).toBe(StatusEffect.TOXIC); + }); + + it("may miss if user is not Poison-type", async () => { + vi.spyOn(allMoves[Moves.TOXIC], "accuracy", "get").mockReturnValue(0); + await game.classicMode.startBattle([Species.UMBREON]); + + game.move.select(Moves.TOXIC); + await game.phaseInterceptor.to("BerryPhase", false); + + expect(game.scene.getEnemyPokemon()!.status).toBeUndefined(); + }); + + it("should hit semi-invulnerable targets if user is Poison-type", async () => { + vi.spyOn(allMoves[Moves.TOXIC], "accuracy", "get").mockReturnValue(0); + game.override.enemyMoveset(Moves.FLY); + await game.classicMode.startBattle([Species.TOXAPEX]); + + game.move.select(Moves.TOXIC); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); + await game.phaseInterceptor.to("BerryPhase", false); + + expect(game.scene.getEnemyPokemon()!.status?.effect).toBe(StatusEffect.TOXIC); + }); + + it("should miss semi-invulnerable targets if user is not Poison-type", async () => { + vi.spyOn(allMoves[Moves.TOXIC], "accuracy", "get").mockReturnValue(-1); + game.override.enemyMoveset(Moves.FLY); + await game.classicMode.startBattle([Species.UMBREON]); + + game.move.select(Moves.TOXIC); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); + await game.phaseInterceptor.to("BerryPhase", false); + + expect(game.scene.getEnemyPokemon()!.status).toBeUndefined(); + }); +});