fix follow me with pursuit

This commit is contained in:
Alex Van Liew 2024-08-15 15:29:04 -07:00
parent b057c144ac
commit f0eac00179
3 changed files with 40 additions and 6 deletions

View File

@ -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 * 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. * 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 { export class DisableMoveAttr extends MoveEffectAttr {
constructor() { constructor() {
@ -6876,6 +6889,7 @@ export function initMoves() {
.condition((user, target, move) => new EncoreTag(user.id).canAdd(target)), .condition((user, target, move) => new EncoreTag(user.id).canAdd(target)),
new AttackMove(Moves.PURSUIT, Type.DARK, MoveCategory.PHYSICAL, 40, 100, 20, -1, 0, 2) new AttackMove(Moves.PURSUIT, Type.DARK, MoveCategory.PHYSICAL, 40, 100, 20, -1, 0, 2)
.attr(PursuitAccuracyAttr) .attr(PursuitAccuracyAttr)
.attr(BypassRedirectAttr, (user, target) => Boolean(user && target && isPursuingFunc(user, target)))
.attr(AddBattlerTagHeaderAttr, BattlerTagType.ANTICIPATING_ACTION) .attr(AddBattlerTagHeaderAttr, BattlerTagType.ANTICIPATING_ACTION)
.attr(RemoveBattlerTagAttr, [BattlerTagType.ANTICIPATING_ACTION], true, MoveEffectTrigger.POST_APPLY) .attr(RemoveBattlerTagAttr, [BattlerTagType.ANTICIPATING_ACTION], true, MoveEffectTrigger.POST_APPLY)
.attr(MovePowerMultiplierAttr, (user, target) => isPursuingFunc(user, target) ? 2 : 1), .attr(MovePowerMultiplierAttr, (user, target) => isPursuingFunc(user, target) ? 2 : 1),

View File

@ -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. //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 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))); this.scene.unshiftPhase(new ShowAbilityPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr)));
} }
moveTarget.value = oldTarget; moveTarget.value = oldTarget;

View File

@ -495,7 +495,6 @@ describe("Moves - Pursuit", () => {
game.override.battleType("double"); game.override.battleType("double");
}); });
// fails: pursuit does not ignore follow me
it("should bypass follow me when hitting a switching target", async () => { it("should bypass follow me when hitting a switching target", async () => {
// arrange // arrange
await startBattle(); await startBattle();
@ -509,13 +508,32 @@ describe("Moves - Pursuit", () => {
await runCombatTurn(game.scene.getEnemyField()[1]); await runCombatTurn(game.scene.getEnemyField()[1]);
// assert // assert
expectPursuitPowerUnchanged(); expectPursuitPowerDoubled();
expectWasHit(findPartyMember(game.scene.getParty(), Species.RAICHU)) expectWasHit(findPartyMember(game.scene.getParty(), Species.RAICHU))
.and(expectNotOnField); .and(expectNotOnField);
expectWasNotHit(findPartyMember(game.scene.getParty(), playerLead)); 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 () => { it("should fail if both pokemon use pursuit on a target that is switching out and it faints after the first one", async () => {
// arrange // arrange
await startBattle(); await startBattle();