diff --git a/test/abilities/intimidate.test.ts b/test/abilities/intimidate.test.ts index 8db39270dcf..2b35d81283e 100644 --- a/test/abilities/intimidate.test.ts +++ b/test/abilities/intimidate.test.ts @@ -1,12 +1,11 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import Phaser from "phaser"; import GameManager from "#test/testUtils/gameManager"; -import { UiMode } from "#enums/ui-mode"; import { Stat } from "#enums/stat"; -import { getMovePosition } from "#test/testUtils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { BattleType } from "#enums/battle-type"; describe("Abilities - Intimidate", () => { let phaserGame: Phaser.Game; @@ -28,24 +27,13 @@ describe("Abilities - Intimidate", () => { .battleStyle("single") .enemySpecies(Species.RATTATA) .enemyAbility(Abilities.INTIMIDATE) - .enemyPassiveAbility(Abilities.HYDRATION) .ability(Abilities.INTIMIDATE) - .startingWave(3) + .moveset(Moves.SPLASH) .enemyMoveset(Moves.SPLASH); }); it("should lower ATK stat stage by 1 of enemy Pokemon on entry and player switch", async () => { - await game.classicMode.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]); - game.onNextPrompt( - "CheckSwitchPhase", - UiMode.CONFIRM, - () => { - game.setMode(UiMode.MESSAGE); - game.endPhase(); - }, - () => game.isCurrentPhase("CommandPhase") || game.isCurrentPhase("TurnInitPhase"), - ); - await game.phaseInterceptor.to("CommandPhase", false); + await game.classicMode.startBattle([Species.MIGHTYENA, Species.POOCHYENA]); let playerPokemon = game.scene.getPlayerPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!; @@ -55,28 +43,17 @@ describe("Abilities - Intimidate", () => { expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1); game.doSwitchPokemon(1); - await game.phaseInterceptor.run("CommandPhase"); - await game.phaseInterceptor.to("CommandPhase"); + await game.toNextTurn(); playerPokemon = game.scene.getPlayerPokemon()!; expect(playerPokemon.species.speciesId).toBe(Species.POOCHYENA); expect(playerPokemon.getStatStage(Stat.ATK)).toBe(0); expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-2); - }, 20000); + }); it("should lower ATK stat stage by 1 for every enemy Pokemon in a double battle on entry", async () => { - game.override.battleStyle("double").startingWave(3); - await game.classicMode.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]); - game.onNextPrompt( - "CheckSwitchPhase", - UiMode.CONFIRM, - () => { - game.setMode(UiMode.MESSAGE); - game.endPhase(); - }, - () => game.isCurrentPhase("CommandPhase") || game.isCurrentPhase("TurnInitPhase"), - ); - await game.phaseInterceptor.to("CommandPhase", false); + game.override.battleStyle("double"); + await game.classicMode.startBattle([Species.MIGHTYENA, Species.POOCHYENA]); const playerField = game.scene.getPlayerField()!; const enemyField = game.scene.getEnemyField()!; @@ -85,11 +62,9 @@ describe("Abilities - Intimidate", () => { expect(enemyField[1].getStatStage(Stat.ATK)).toBe(-2); expect(playerField[0].getStatStage(Stat.ATK)).toBe(-2); expect(playerField[1].getStatStage(Stat.ATK)).toBe(-2); - }, 20000); + }); it("should not activate again if there is no switch or new entry", async () => { - game.override.startingWave(2); - game.override.moveset([Moves.SPLASH]); await game.classicMode.startBattle([Species.MIGHTYENA, Species.POOCHYENA]); const playerPokemon = game.scene.getPlayerPokemon()!; @@ -103,32 +78,42 @@ describe("Abilities - Intimidate", () => { expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1); expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1); - }, 20000); + }); - it("should lower ATK stat stage by 1 for every switch", async () => { - game.override.moveset([Moves.SPLASH]).enemyMoveset([Moves.VOLT_SWITCH]).startingWave(5); - await game.classicMode.startBattle([Species.MIGHTYENA, Species.POOCHYENA]); + it("should NOT trigger on switching moves used by wild Pokemon", async () => { + game.override.enemyMoveset(Moves.VOLT_SWITCH).battleType(BattleType.WILD); + await game.classicMode.startBattle([Species.MIGHTYENA]); const playerPokemon = game.scene.getPlayerPokemon()!; - let enemyPokemon = game.scene.getEnemyPokemon()!; - - expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1); expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1); - game.move.select(getMovePosition(game.scene, 0, Moves.SPLASH)); - await game.toNextTurn(); - - enemyPokemon = game.scene.getEnemyPokemon()!; - - expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-2); - expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0); - game.move.select(Moves.SPLASH); await game.toNextTurn(); + // doesn't lower attack due to not actually switching out + expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1); + }); - enemyPokemon = game.scene.getEnemyPokemon()!; + it("should trigger on moves that switch user/target out during trainer battles", async () => { + game.override + .moveset([Moves.SPLASH, Moves.DRAGON_TAIL]) + .enemyMoveset([Moves.SPLASH, Moves.TELEPORT]) + .battleType(BattleType.TRAINER) + .startingWave(8) + .passiveAbility(Abilities.NO_GUARD); + await game.classicMode.startBattle([Species.MIGHTYENA]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1); + + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.TELEPORT); + await game.toNextTurn(); + expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-2); + + game.move.select(Moves.DRAGON_TAIL); + await game.forceEnemyMove(Moves.SPLASH); + await game.toNextTurn(); expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-3); - expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0); - }, 200000); + }); }); diff --git a/test/moves/ignore-abilities.test.ts b/test/moves/ignore-abilities.test.ts new file mode 100644 index 00000000000..7c4bcf365fe --- /dev/null +++ b/test/moves/ignore-abilities.test.ts @@ -0,0 +1,105 @@ +import { BattlerIndex } from "#app/battle"; +import { RandomMoveAttr } from "#app/data/moves/move"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("Moves - Ability Ignores", () => { + 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.MOONGEIST_BEAM, Moves.SUNSTEEL_STRIKE, Moves.PHOTON_GEYSER, Moves.METRONOME]) + .ability(Abilities.STURDY) + .startingLevel(200) + .battleStyle("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.STURDY) + .enemyMoveset(Moves.SPLASH); + }); + + it.each<{ name: string; move: Moves }>([ + { name: "Sunsteel Strike", move: Moves.SUNSTEEL_STRIKE }, + { name: "Moongeist Beam", move: Moves.MOONGEIST_BEAM }, + { name: "Photon Geyser", move: Moves.PHOTON_GEYSER }, + ])("$name should ignore enemy abilities during move use", async () => { + await game.classicMode.startBattle([Species.NECROZMA]); + + const player = game.scene.getPlayerPokemon()!; + const enemy = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.MOONGEIST_BEAM); + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(game.scene.arena.ignoreAbilities).toBe(true); + expect(game.scene.arena.ignoringEffectSource).toBe(player.getBattlerIndex()); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(game.scene.arena.ignoreAbilities).toBe(false); + expect(enemy.isFainted()).toBe(true); + }); + + it("should not ignore enemy abilities when called by metronome", async () => { + await game.classicMode.startBattle([Species.MILOTIC]); + vi.spyOn(RandomMoveAttr.prototype, "getMoveOverride").mockReturnValue(Moves.PHOTON_GEYSER); + + const enemy = game.scene.getEnemyPokemon()!; + game.move.select(Moves.METRONOME); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy.isFainted()).toBe(false); + expect(game.scene.getPlayerPokemon()?.getLastXMoves()[0].move).toBe(Moves.PHOTON_GEYSER); + }); + + it("should not ignore enemy abilities when called by Mirror Move", async () => { + game.override.moveset(Moves.MIRROR_MOVE).enemyMoveset(Moves.SUNSTEEL_STRIKE); + + await game.classicMode.startBattle([Species.MILOTIC]); + + const enemy = game.scene.getEnemyPokemon()!; + game.move.select(Moves.MIRROR_MOVE); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy.isFainted()).toBe(false); + expect(game.scene.getPlayerPokemon()?.getLastXMoves()[0].move).toBe(Moves.SUNSTEEL_STRIKE); + }); + + it("should ignore enemy abilities when called by Instruct", async () => { + game.override.moveset([Moves.SUNSTEEL_STRIKE, Moves.INSTRUCT]).battleStyle("double"); + await game.classicMode.startBattle([Species.SOLGALEO, Species.LUNALA]); + + const solgaleo = game.scene.getPlayerPokemon()!; + + game.move.select(Moves.SUNSTEEL_STRIKE, BattlerIndex.PLAYER, BattlerIndex.ENEMY); + game.move.select(Moves.INSTRUCT, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]); + + await game.phaseInterceptor.to("MoveEffectPhase"); // initial attack + await game.phaseInterceptor.to("MoveEffectPhase"); // instruct + + expect(game.scene.arena.ignoreAbilities).toBe(true); + expect(game.scene.arena.ignoringEffectSource).toBe(solgaleo.getBattlerIndex()); + + await game.phaseInterceptor.to("BerryPhase"); + const [enemy1, enemy2] = game.scene.getEnemyField(); + expect(enemy1.isFainted()).toBe(true); + expect(enemy2.isFainted()).toBe(true); + }); +}); diff --git a/test/moves/moongeist_beam.test.ts b/test/moves/moongeist_beam.test.ts deleted file mode 100644 index 82a2567377b..00000000000 --- a/test/moves/moongeist_beam.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { allMoves, RandomMoveAttr } from "#app/data/moves/move"; -import { Abilities } from "#enums/abilities"; -import { Moves } from "#enums/moves"; -import { Species } from "#enums/species"; -import GameManager from "#test/testUtils/gameManager"; -import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; - -describe("Moves - Moongeist Beam", () => { - 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.MOONGEIST_BEAM, Moves.METRONOME]) - .ability(Abilities.BALL_FETCH) - .startingLevel(200) - .battleStyle("single") - .disableCrits() - .enemySpecies(Species.MAGIKARP) - .enemyAbility(Abilities.STURDY) - .enemyMoveset(Moves.SPLASH); - }); - - // Also covers Photon Geyser and Sunsteel Strike - it("should ignore enemy abilities", async () => { - await game.classicMode.startBattle([Species.MILOTIC]); - - const enemy = game.scene.getEnemyPokemon()!; - - game.move.select(Moves.MOONGEIST_BEAM); - await game.phaseInterceptor.to("BerryPhase"); - - expect(enemy.isFainted()).toBe(true); - }); - - // Also covers Photon Geyser and Sunsteel Strike - it("should not ignore enemy abilities when called by another move, such as metronome", async () => { - await game.classicMode.startBattle([Species.MILOTIC]); - vi.spyOn(allMoves[Moves.METRONOME].getAttrs(RandomMoveAttr)[0], "getMoveOverride").mockReturnValue( - Moves.MOONGEIST_BEAM, - ); - - game.move.select(Moves.METRONOME); - await game.phaseInterceptor.to("BerryPhase"); - - expect(game.scene.getEnemyPokemon()!.isFainted()).toBe(false); - expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].move).toBe(Moves.MOONGEIST_BEAM); - }); -});