From c01fff49c4e653de11bde37a52a9fb61f393e29d Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sun, 6 Oct 2024 14:31:11 -0700 Subject: [PATCH] [Beta P1] Fix regression in Metal Burst caused by #3974 (#4589) Also adds a regression test for the scenario --- src/phases/move-phase.ts | 6 +-- src/test/moves/metal_burst.test.ts | 80 ++++++++++++++++++++++++++++ src/test/utils/helpers/moveHelper.ts | 8 +-- 3 files changed, 85 insertions(+), 9 deletions(-) create mode 100644 src/test/moves/metal_burst.test.ts diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 6272358aa85..10cc062ea3b 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -375,11 +375,7 @@ export class MovePhase extends BattlePhase { protected resolveCounterAttackTarget() { if (this.targets.length === 1 && this.targets[0] === BattlerIndex.ATTACKER) { if (this.pokemon.turnData.attacksReceived.length) { - const attacker = this.pokemon.scene.getPokemonById(this.pokemon.turnData.attacksReceived[0].sourceId); - - if (attacker?.isActive(true)) { - this.targets[0] = attacker.getBattlerIndex(); - } + this.targets[0] = this.pokemon.turnData.attacksReceived[0].sourceBattlerIndex; // account for metal burst and comeuppance hitting remaining targets in double battles // counterattack will redirect to remaining ally if original attacker faints diff --git a/src/test/moves/metal_burst.test.ts b/src/test/moves/metal_burst.test.ts new file mode 100644 index 00000000000..3b32dd322a3 --- /dev/null +++ b/src/test/moves/metal_burst.test.ts @@ -0,0 +1,80 @@ +import { BattlerIndex } from "#app/battle"; +import { MoveResult } from "#app/field/pokemon"; +import { Abilities } from "#enums/abilities"; +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 } from "vitest"; + +describe("Moves - Metal Burst", () => { + 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.METAL_BURST, Moves.FISSURE, Moves.PRECIPICE_BLADES ]) + .ability(Abilities.PURE_POWER) + .startingLevel(10) + .battleType("double") + .disableCrits() + .enemySpecies(Species.PICHU) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.TACKLE); + }); + + it("should redirect target if intended target faints", async () => { + await game.classicMode.startBattle([ Species.FEEBAS, Species.FEEBAS ]); + + const [ , enemy2 ] = game.scene.getEnemyField(); + + game.move.select(Moves.METAL_BURST); + game.move.select(Moves.FISSURE, 1, BattlerIndex.ENEMY); + + await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); + await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2); + + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER, BattlerIndex.ENEMY_2 ]); + + await game.phaseInterceptor.to("MoveEndPhase"); + await game.move.forceHit(); + await game.phaseInterceptor.to("MoveEndPhase"); + await game.phaseInterceptor.to("MoveEndPhase"); + + expect(enemy2.isFullHp()).toBe(false); + }); + + it("should not crash if both opponents faint before the move is used", async () => { + await game.classicMode.startBattle([ Species.FEEBAS, Species.ARCEUS ]); + + const [ enemy1, enemy2 ] = game.scene.getEnemyField(); + + game.move.select(Moves.METAL_BURST); + game.move.select(Moves.PRECIPICE_BLADES, 1); + + await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); + await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2); + + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER, BattlerIndex.ENEMY_2 ]); + + await game.phaseInterceptor.to("MoveEndPhase"); + await game.move.forceHit(); + await game.phaseInterceptor.to("MoveEndPhase"); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy1.isFainted()).toBe(true); + expect(enemy2.isFainted()).toBe(true); + expect(game.scene.getPlayerField()[0].getLastXMoves(1)[0].result).toBe(MoveResult.FAIL); + }); +}); diff --git a/src/test/utils/helpers/moveHelper.ts b/src/test/utils/helpers/moveHelper.ts index a53fa521785..a0667d91f4c 100644 --- a/src/test/utils/helpers/moveHelper.ts +++ b/src/test/utils/helpers/moveHelper.ts @@ -13,8 +13,8 @@ import { GameManagerHelper } from "./gameManagerHelper"; */ export class MoveHelper extends GameManagerHelper { /** - * Intercepts `MoveEffectPhase` and mocks the hitCheck's - * return value to `true` {@linkcode MoveEffectPhase.hitCheck}. + * Intercepts {@linkcode MoveEffectPhase} and mocks the + * {@linkcode MoveEffectPhase.hitCheck | hitCheck}'s return value to `true`. * Used to force a move to hit. */ async forceHit(): Promise { @@ -23,8 +23,8 @@ export class MoveHelper extends GameManagerHelper { } /** - * Intercepts `MoveEffectPhase` and mocks the hitCheck's - * return value to `false` {@linkcode MoveEffectPhase.hitCheck}. + * Intercepts {@linkcode MoveEffectPhase} and mocks the + * {@linkcode MoveEffectPhase.hitCheck | hitCheck}'s return value to `false`. * Used to force a move to miss. * @param firstTargetOnly Whether the move should force miss on the first target only, in the case of multi-target moves. */