[P1 Bug] Fix multi lens granting infinite Future Sight hits (#4961)

* [P1 Bug] Fix multi lens granting infinite Future Sight hits

* Updated `.partial()` tags

* Added corresponding TODO comments to tests
This commit is contained in:
PigeonBar 2024-12-02 12:40:59 -05:00 committed by GitHub
parent e930536efe
commit 2f377f26b7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 51 additions and 7 deletions

View File

@ -8666,7 +8666,7 @@ export function initMoves() {
.attr(StatStageChangeAttr, [ Stat.SPDEF ], -1) .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1)
.ballBombMove(), .ballBombMove(),
new AttackMove(Moves.FUTURE_SIGHT, Type.PSYCHIC, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 2) 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() .ignoresProtect()
.attr(DelayedAttackAttr, ArenaTagType.FUTURE_SIGHT, ChargeAnim.FUTURE_SIGHT_CHARGING, i18next.t("moveTriggers:foresawAnAttack", { pokemonName: "{USER}" })), .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) new AttackMove(Moves.ROCK_SMASH, Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 15, 50, 0, 2)
@ -8976,7 +8976,7 @@ export function initMoves() {
.attr(ConfuseAttr) .attr(ConfuseAttr)
.pulseMove(), .pulseMove(),
new AttackMove(Moves.DOOM_DESIRE, Type.STEEL, MoveCategory.SPECIAL, 140, 100, 5, -1, 0, 3) 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() .ignoresProtect()
.attr(DelayedAttackAttr, ArenaTagType.DOOM_DESIRE, ChargeAnim.DOOM_DESIRE_CHARGING, i18next.t("moveTriggers:choseDoomDesireAsDestiny", { pokemonName: "{USER}" })), .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) new AttackMove(Moves.PSYCHO_BOOST, Type.PSYCHIC, MoveCategory.SPECIAL, 140, 90, 5, -1, 0, 3)

View File

@ -56,7 +56,7 @@ import {
PokemonMultiHitModifier, PokemonMultiHitModifier,
} from "#app/modifier/modifier"; } from "#app/modifier/modifier";
import { PokemonPhase } from "#app/phases/pokemon-phase"; 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 { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import i18next from "i18next"; import i18next from "i18next";
@ -106,9 +106,11 @@ export class MoveEffectPhase extends PokemonPhase {
*/ */
return super.end(); return super.end();
} }
if (isNullOrUndefined(user.turnData)) {
user.resetTurnData(); user.resetTurnData();
} }
} }
}
/** /**
* Does an effect from this move override other effects on this turn? * Does an effect from this move override other effects on this turn?

View File

@ -1,5 +1,5 @@
import { Type } from "#enums/type"; 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 { toDmgValue } from "#app/utils";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
@ -8,7 +8,7 @@ import { Stat } from "#enums/stat";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import Phaser from "phaser"; 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", () => { describe("Abilities - Parental Bond", () => {
@ -470,4 +470,25 @@ describe("Abilities - Parental Bond", () => {
expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(1); 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);
});
}); });

View File

@ -24,7 +24,7 @@ describe("Items - Multi Lens", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override 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) .ability(Abilities.BALL_FETCH)
.startingHeldItems([{ name: "MULTI_LENS" }]) .startingHeldItems([{ name: "MULTI_LENS" }])
.battleType("single") .battleType("single")
@ -170,6 +170,7 @@ describe("Items - Multi Lens", () => {
await game.phaseInterceptor.to("MoveEndPhase"); await game.phaseInterceptor.to("MoveEndPhase");
expect(enemyPokemon.getHpRatio()).toBeCloseTo(0.5, 5); expect(enemyPokemon.getHpRatio()).toBeCloseTo(0.5, 5);
}); });
it("should result in correct damage for hp% attacks with 2 lenses + Parental Bond", async () => { it("should result in correct damage for hp% attacks with 2 lenses + Parental Bond", async () => {
game.override.startingHeldItems([{ name: "MULTI_LENS", count: 2 }]) game.override.startingHeldItems([{ name: "MULTI_LENS", count: 2 }])
.moveset(Moves.SUPER_FANG) .moveset(Moves.SUPER_FANG)
@ -188,4 +189,24 @@ describe("Items - Multi Lens", () => {
await game.phaseInterceptor.to("MoveEndPhase"); await game.phaseInterceptor.to("MoveEndPhase");
expect(enemyPokemon.getHpRatio()).toBeCloseTo(0.25, 5); 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);
});
}); });