From f0eac001798cd320ffeac081c82825ff861710cd Mon Sep 17 00:00:00 2001 From: Alex Van Liew Date: Thu, 15 Aug 2024 15:29:04 -0700 Subject: [PATCH] fix follow me with pursuit --- src/data/move.ts | 16 +++++++++++++++- src/phases.ts | 6 ++++-- src/test/moves/pursuit.test.ts | 24 +++++++++++++++++++++--- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index 7833bf510c8..fad88e19470 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -4203,8 +4203,21 @@ export class TypelessAttr extends MoveAttr { } /** * Attribute used for moves which ignore redirection effects, and always target their original target, i.e. Snipe Shot * Bypasses Storm Drain, Follow Me, Ally Switch, and the like. +* +* Optionally accepts a function to run which can be used to conditionally bypass redirection effects. */ -export class BypassRedirectAttr extends MoveAttr { } +export class BypassRedirectAttr extends MoveAttr { + private bypassConditionFn?: (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean; + + constructor(bypassConditionFn?: (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean) { + super(); + this.bypassConditionFn = bypassConditionFn; + } + + apply(user: Pokemon | null, target: Pokemon | null, move: Move) { + return this.bypassConditionFn?.(user, target, move) ?? true; + } +} export class DisableMoveAttr extends MoveEffectAttr { constructor() { @@ -6876,6 +6889,7 @@ export function initMoves() { .condition((user, target, move) => new EncoreTag(user.id).canAdd(target)), new AttackMove(Moves.PURSUIT, Type.DARK, MoveCategory.PHYSICAL, 40, 100, 20, -1, 0, 2) .attr(PursuitAccuracyAttr) + .attr(BypassRedirectAttr, (user, target) => Boolean(user && target && isPursuingFunc(user, target))) .attr(AddBattlerTagHeaderAttr, BattlerTagType.ANTICIPATING_ACTION) .attr(RemoveBattlerTagAttr, [BattlerTagType.ANTICIPATING_ACTION], true, MoveEffectTrigger.POST_APPLY) .attr(MovePowerMultiplierAttr, (user, target) => isPursuingFunc(user, target) ? 2 : 1), diff --git a/src/phases.ts b/src/phases.ts index ba1c9021f4c..0a140e171ac 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -2777,9 +2777,11 @@ export class MovePhase extends BattlePhase { } }); //Check if this move is immune to being redirected, and restore its target to the intended target if it is. - if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) || this.move.getMove().hasAttr(BypassRedirectAttr))) { + const abilityRedirectImmune = this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr); + const moveRedirectImmune = this.move.getMove().getAttrs(BypassRedirectAttr).some(attr => attr.apply(this.pokemon, this.scene.getField(false)[oldTarget], this.move.getMove())); + if (abilityRedirectImmune || moveRedirectImmune) { //If an ability prevented this move from being redirected, display its ability pop up. - if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) && !this.move.getMove().hasAttr(BypassRedirectAttr)) && oldTarget !== moveTarget.value) { + if (abilityRedirectImmune && !moveRedirectImmune && oldTarget !== moveTarget.value) { this.scene.unshiftPhase(new ShowAbilityPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr))); } moveTarget.value = oldTarget; diff --git a/src/test/moves/pursuit.test.ts b/src/test/moves/pursuit.test.ts index 56276ed7f8d..b8c877769a6 100644 --- a/src/test/moves/pursuit.test.ts +++ b/src/test/moves/pursuit.test.ts @@ -495,7 +495,6 @@ describe("Moves - Pursuit", () => { game.override.battleType("double"); }); - // fails: pursuit does not ignore follow me it("should bypass follow me when hitting a switching target", async () => { // arrange await startBattle(); @@ -509,13 +508,32 @@ describe("Moves - Pursuit", () => { await runCombatTurn(game.scene.getEnemyField()[1]); // assert - expectPursuitPowerUnchanged(); + expectPursuitPowerDoubled(); expectWasHit(findPartyMember(game.scene.getParty(), Species.RAICHU)) .and(expectNotOnField); expectWasNotHit(findPartyMember(game.scene.getParty(), playerLead)); }); - // fails: fainting a pursuiter still runs the enemy SwitchSummonPhase + it("should not bypass follow me when hitting a non-switching target", async () => { + // arrange + await startBattle(); + forceMovesLast(game.scene.getEnemyPokemon()); + + // act + game.override.moveset([Moves.FOLLOW_ME, Moves.SPLASH]); + game.doAttack(getMovePosition(game.scene, 0, Moves.FOLLOW_ME)); + game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); + enemyUses(Moves.PURSUIT); + game.move.forceAiTargets(game.scene.getEnemyPokemon(), BattlerIndex.PLAYER_2); + await runCombatTurn(game.scene.getEnemyField()[1]); + + // assert + expectPursuitPowerUnchanged(); + expectWasHit(findPartyMember(game.scene.getParty(), playerLead)); + expectWasNotHit(findPartyMember(game.scene.getParty(), Species.RAICHU)); + }); + + // fails: fainting an escapee still runs the enemy SwitchSummonPhase it("should fail if both pokemon use pursuit on a target that is switching out and it faints after the first one", async () => { // arrange await startBattle();