diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index b122769967d..4c8d3e82eae 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -6781,7 +6781,7 @@ export abstract class CallMoveAttr extends OverrideMoveEffectAttr { const targets = moveTargets.multiple || moveTargets.targets.length === 1 ? moveTargets.targets : [ this.selfTarget ? target.getBattlerIndex() : moveTargets.targets[user.randBattleSeedInt(moveTargets.targets.length)] ]; // account for Mirror Move having a target already - user.getMoveQueue().push({ move: copiedMove.id, targets: targets, virtual: true, ignorePP: true }); + globalScene.phaseManager.unshiftNew("LoadMoveAnimPhase", copiedMove.id); globalScene.phaseManager.unshiftNew("MovePhase", user, targets, new PokemonMove(copiedMove.id, 0, 0, true), true, true); return true; @@ -6883,7 +6883,6 @@ export class NaturePowerAttr extends CallMoveAttr { override getMove(user: Pokemon): MoveId { const moveId = this.getMoveIdForTerrain(globalScene.arena.getTerrainType(), globalScene.arena.biomeType) // Unshift a phase to load the move's animation (in case it isn't already), then use the move. - globalScene.phaseManager.unshiftNew("LoadMoveAnimPhase", moveId); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:naturePowerUse", { pokemonName: getPokemonNameWithAffix(user), moveName: allMoves[moveId].name, @@ -7007,8 +7006,8 @@ export class CopyMoveAttr extends CallMoveAttr { override getMove(_user: Pokemon, target: Pokemon): MoveId { return this.selfTarget - ? target.getLastXMoves()[0]?.move ?? MoveId.NONE - : globalScene.currentBattle.lastMove; + ? globalScene.currentBattle.lastMove + : target.getLastXMoves()[0]?.move ?? MoveId.NONE } getCondition(): MoveConditionFunc { diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index d72c7396f1f..654992a103f 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -370,12 +370,10 @@ export class MovePhase extends BattlePhase { success = passesConditions && !failedDueToWeather && !failedDueToTerrain; } - // Update the battle's "last move" pointer, unless we're currently mimicking a move. - if (!allMoves[this.move.moveId].hasAttr("CopyMoveAttr")) { - // The last move used is unaffected by moves that fail - if (success) { - globalScene.currentBattle.lastMove = this.move.moveId; - } + // Update the battle's "last move" pointer, unless we're currently mimicking a move + // or the move failed. + if (!allMoves[this.move.moveId].hasAttr("CallMoveAttr") && success) { + globalScene.currentBattle.lastMove = this.move.moveId; } /** diff --git a/test/moves/copycat.test.ts b/test/moves/copycat.test.ts index f55c072db35..dae7587b89e 100644 --- a/test/moves/copycat.test.ts +++ b/test/moves/copycat.test.ts @@ -34,58 +34,67 @@ describe("Moves - Copycat", () => { .ability(AbilityId.BALL_FETCH) .battleStyle("single") .disableCrits() - .starterSpecies(SpeciesId.FEEBAS) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); }); it("should copy the last move successfully executed", async () => { - game.override.enemyMoveset(MoveId.SUCKER_PUNCH); - await game.classicMode.startBattle(); + await game.classicMode.startBattle([SpeciesId.FEEBAS]); game.move.select(MoveId.SWORDS_DANCE); await game.toNextTurn(); game.move.select(MoveId.COPYCAT); // Last successful move should be Swords Dance + await game.move.forceEnemyMove(MoveId.SUCKER_PUNCH); await game.toNextTurn(); - expect(game.scene.getPlayerPokemon()!.getStatStage(Stat.ATK)).toBe(4); + const player = game.field.getPlayerPokemon(); + expect(player.getStatStage(Stat.ATK)).toBe(4); + expect(player.getLastXMoves()[0].move).toBe(MoveId.SWORDS_DANCE); }); - it("should fail when the last move used is not a valid Copycat move", async () => { - game.override.enemyMoveset(MoveId.PROTECT); // Protect is not a valid move for Copycat to copy - await game.classicMode.startBattle(); - - game.move.select(MoveId.SPIKY_SHIELD); // Spiky Shield is not a valid move for Copycat to copy - await game.toNextTurn(); + it("should fail if no prior moves have been made", async () => { + await game.classicMode.startBattle([SpeciesId.FEEBAS]); game.move.select(MoveId.COPYCAT); + await game.move.forceEnemyMove(MoveId.SPLASH); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.toNextTurn(); - expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + expect(game.field.getPlayerPokemon().getLastXMoves()[0].result).toBe(MoveResult.FAIL); + }); + + it("should fail if the last move used is not a valid Copycat move", async () => { + await game.classicMode.startBattle([SpeciesId.FEEBAS]); + + game.move.use(MoveId.COPYCAT); + await game.move.forceEnemyMove(MoveId.PROTECT); + await game.toNextTurn(); + + expect(game.field.getPlayerPokemon().getLastXMoves()[0].result).toBe(MoveResult.FAIL); }); it("should copy the called move when the last move successfully calls another", async () => { - game.override.moveset([MoveId.SPLASH, MoveId.METRONOME]).enemyMoveset(MoveId.COPYCAT); - await game.classicMode.startBattle(); vi.spyOn(randomMoveAttr, "getMove").mockReturnValue(MoveId.SWORDS_DANCE); + await game.classicMode.startBattle([SpeciesId.FEEBAS]); - game.move.select(MoveId.METRONOME); + game.move.use(MoveId.METRONOME); + await game.move.forceEnemyMove(MoveId.COPYCAT); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); // Player moves first, so enemy can copy Swords Dance await game.toNextTurn(); - expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.ATK)).toBe(2); + expect(game.field.getEnemyPokemon().getStatStage(Stat.ATK)).toBe(2); }); it("should apply secondary effects of a move", async () => { game.override.enemyMoveset(MoveId.ACID_SPRAY); // Secondary effect lowers SpDef by 2 stages - await game.classicMode.startBattle(); + await game.classicMode.startBattle([SpeciesId.FEEBAS]); - game.move.select(MoveId.COPYCAT); + game.move.use(MoveId.COPYCAT); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextTurn(); - expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.SPDEF)).toBe(-2); + expect(game.field.getEnemyPokemon().getStatStage(Stat.SPDEF)).toBe(-2); }); });