diff --git a/src/data/move.ts b/src/data/move.ts index 1283713ef30..27f7829a920 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -8666,7 +8666,7 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1) .ballBombMove(), new AttackMove(Moves.FUTURE_SIGHT, Type.PSYCHIC, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 2) - .partial() // cannot be used on multiple Pokemon on the same side in a double battle, hits immediately when called by Metronome/etc + .partial() // cannot be used on multiple Pokemon on the same side in a double battle, hits immediately when called by Metronome/etc, should not apply abilities or held items if user is off the field .ignoresProtect() .attr(DelayedAttackAttr, ArenaTagType.FUTURE_SIGHT, ChargeAnim.FUTURE_SIGHT_CHARGING, i18next.t("moveTriggers:foresawAnAttack", { pokemonName: "{USER}" })), new AttackMove(Moves.ROCK_SMASH, Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 15, 50, 0, 2) @@ -8976,7 +8976,7 @@ export function initMoves() { .attr(ConfuseAttr) .pulseMove(), new AttackMove(Moves.DOOM_DESIRE, Type.STEEL, MoveCategory.SPECIAL, 140, 100, 5, -1, 0, 3) - .partial() // cannot be used on multiple Pokemon on the same side in a double battle, hits immediately when called by Metronome/etc + .partial() // cannot be used on multiple Pokemon on the same side in a double battle, hits immediately when called by Metronome/etc, should not apply abilities or held items if user is off the field .ignoresProtect() .attr(DelayedAttackAttr, ArenaTagType.DOOM_DESIRE, ChargeAnim.DOOM_DESIRE_CHARGING, i18next.t("moveTriggers:choseDoomDesireAsDestiny", { pokemonName: "{USER}" })), new AttackMove(Moves.PSYCHO_BOOST, Type.PSYCHIC, MoveCategory.SPECIAL, 140, 90, 5, -1, 0, 3) diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 96ded602b30..d08fc46e563 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -56,7 +56,7 @@ import { PokemonMultiHitModifier, } from "#app/modifier/modifier"; import { PokemonPhase } from "#app/phases/pokemon-phase"; -import { BooleanHolder, executeIf, NumberHolder } from "#app/utils"; +import { BooleanHolder, executeIf, isNullOrUndefined, NumberHolder } from "#app/utils"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Moves } from "#enums/moves"; import i18next from "i18next"; @@ -106,7 +106,9 @@ export class MoveEffectPhase extends PokemonPhase { */ return super.end(); } - user.resetTurnData(); + if (isNullOrUndefined(user.turnData)) { + user.resetTurnData(); + } } } diff --git a/src/test/abilities/parental_bond.test.ts b/src/test/abilities/parental_bond.test.ts index 6790e7e632c..c2f54fa4cfc 100644 --- a/src/test/abilities/parental_bond.test.ts +++ b/src/test/abilities/parental_bond.test.ts @@ -1,5 +1,5 @@ import { Type } from "#enums/type"; -import { BattlerTagType } from "#app/enums/battler-tag-type"; +import { BattlerTagType } from "#enums/battler-tag-type"; import { toDmgValue } from "#app/utils"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; @@ -8,7 +8,7 @@ import { Stat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; describe("Abilities - Parental Bond", () => { @@ -470,4 +470,25 @@ describe("Abilities - Parental Bond", () => { expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(1); } ); + + it("should not allow Future Sight to hit infinitely many times if the user switches out", async () => { + game.override.enemyLevel(1000) + .moveset(Moves.FUTURE_SIGHT); + await game.classicMode.startBattle([ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + vi.spyOn(enemyPokemon, "damageAndUpdate"); + + game.move.select(Moves.FUTURE_SIGHT); + await game.toNextTurn(); + + game.doSwitchPokemon(1); + await game.toNextTurn(); + + game.doSwitchPokemon(2); + await game.toNextTurn(); + + // TODO: Update hit count to 1 once Future Sight is fixed to not activate abilities if user is off the field + expect(enemyPokemon.damageAndUpdate).toHaveBeenCalledTimes(2); + }); }); diff --git a/src/test/items/multi_lens.test.ts b/src/test/items/multi_lens.test.ts index f4b4c5712ee..bd586878fce 100644 --- a/src/test/items/multi_lens.test.ts +++ b/src/test/items/multi_lens.test.ts @@ -24,7 +24,7 @@ describe("Items - Multi Lens", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .moveset([ Moves.TACKLE, Moves.TRAILBLAZE, Moves.TACHYON_CUTTER ]) + .moveset([ Moves.TACKLE, Moves.TRAILBLAZE, Moves.TACHYON_CUTTER, Moves.FUTURE_SIGHT ]) .ability(Abilities.BALL_FETCH) .startingHeldItems([{ name: "MULTI_LENS" }]) .battleType("single") @@ -170,6 +170,7 @@ describe("Items - Multi Lens", () => { await game.phaseInterceptor.to("MoveEndPhase"); expect(enemyPokemon.getHpRatio()).toBeCloseTo(0.5, 5); }); + it("should result in correct damage for hp% attacks with 2 lenses + Parental Bond", async () => { game.override.startingHeldItems([{ name: "MULTI_LENS", count: 2 }]) .moveset(Moves.SUPER_FANG) @@ -188,4 +189,24 @@ describe("Items - Multi Lens", () => { await game.phaseInterceptor.to("MoveEndPhase"); expect(enemyPokemon.getHpRatio()).toBeCloseTo(0.25, 5); }); + + it("should not allow Future Sight to hit infinitely many times if the user switches out", async () => { + game.override.enemyLevel(1000); + await game.classicMode.startBattle([ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + vi.spyOn(enemyPokemon, "damageAndUpdate"); + + game.move.select(Moves.FUTURE_SIGHT); + await game.toNextTurn(); + + game.doSwitchPokemon(1); + await game.toNextTurn(); + + game.doSwitchPokemon(2); + await game.toNextTurn(); + + // TODO: Update hit count to 1 once Future Sight is fixed to not activate held items if user is off the field + expect(enemyPokemon.damageAndUpdate).toHaveBeenCalledTimes(2); + }); });