From 742b8d425875de7c10f3fd00fe780daf21c9cdd1 Mon Sep 17 00:00:00 2001 From: innerthunder Date: Fri, 4 Oct 2024 12:59:25 -0700 Subject: [PATCH] Pledges now only bypass redirection from abilities --- src/data/move.ts | 16 ++++++++--- src/phases/move-phase.ts | 16 ++++++++--- src/test/moves/pledge_moves.test.ts | 41 +++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index 9b02bc8e106..62ac36b28ad 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -4648,7 +4648,15 @@ 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. */ -export class BypassRedirectAttr extends MoveAttr { } +export class BypassRedirectAttr extends MoveAttr { + /** `true` if this move only bypasses redirection from Abilities */ + public readonly abilitiesOnly: boolean; + + constructor(abilitiesOnly: boolean = false) { + super(); + this.abilitiesOnly = abilitiesOnly; + } +} export class FrenzyAttr extends MoveEffectAttr { constructor() { @@ -8516,7 +8524,7 @@ export function initMoves() { .attr(CombinedPledgeStabBoostAttr) .attr(AddPledgeEffectAttr, ArenaTagType.WATER_FIRE_PLEDGE, Moves.FIRE_PLEDGE, true) .attr(AddPledgeEffectAttr, ArenaTagType.GRASS_WATER_PLEDGE, Moves.GRASS_PLEDGE) - .attr(BypassRedirectAttr), // technically incorrect, should only bypass Storm Drain/Lightning Rod + .attr(BypassRedirectAttr, true), new AttackMove(Moves.FIRE_PLEDGE, Type.FIRE, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 5) .attr(AwaitCombinedPledgeAttr) .attr(CombinedPledgeTypeAttr) @@ -8524,7 +8532,7 @@ export function initMoves() { .attr(CombinedPledgeStabBoostAttr) .attr(AddPledgeEffectAttr, ArenaTagType.FIRE_GRASS_PLEDGE, Moves.GRASS_PLEDGE) .attr(AddPledgeEffectAttr, ArenaTagType.WATER_FIRE_PLEDGE, Moves.WATER_PLEDGE, true) - .attr(BypassRedirectAttr), // technically incorrect, should only bypass Storm Drain/Lightning Rod + .attr(BypassRedirectAttr, true), new AttackMove(Moves.GRASS_PLEDGE, Type.GRASS, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 5) .attr(AwaitCombinedPledgeAttr) .attr(CombinedPledgeTypeAttr) @@ -8532,7 +8540,7 @@ export function initMoves() { .attr(CombinedPledgeStabBoostAttr) .attr(AddPledgeEffectAttr, ArenaTagType.GRASS_WATER_PLEDGE, Moves.WATER_PLEDGE) .attr(AddPledgeEffectAttr, ArenaTagType.FIRE_GRASS_PLEDGE, Moves.FIRE_PLEDGE) - .attr(BypassRedirectAttr), // technically incorrect, should only bypass Storm Drain/Lightning Rod + .attr(BypassRedirectAttr, true), new AttackMove(Moves.VOLT_SWITCH, Type.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 20, -1, 0, 5) .attr(ForceSwitchOutAttr, true), new AttackMove(Moves.STRUGGLE_BUG, Type.BUG, MoveCategory.SPECIAL, 50, 100, 20, 100, 0, 5) diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 807f194bad5..6272358aa85 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -331,22 +331,30 @@ export class MovePhase extends BattlePhase { // check move redirection abilities of every pokemon *except* the user. this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, false, this.move.moveId, redirectTarget)); + /** `true` if an Ability is responsible for redirecting the move to another target; `false` otherwise */ + let redirectedByAbility = (currentTarget !== redirectTarget.value); + // check for center-of-attention tags (note that this will override redirect abilities) this.pokemon.getOpponents().forEach(p => { - const redirectTag = p.getTag(CenterOfAttentionTag) as CenterOfAttentionTag; + const redirectTag = p.getTag(CenterOfAttentionTag); // TODO: don't hardcode this interaction. // Handle interaction between the rage powder center-of-attention tag and moves used by grass types/overcoat-havers (which are immune to RP's redirect) if (redirectTag && (!redirectTag.powder || (!this.pokemon.isOfType(Type.GRASS) && !this.pokemon.hasAbility(Abilities.OVERCOAT)))) { redirectTarget.value = p.getBattlerIndex(); + redirectedByAbility = false; } }); if (currentTarget !== redirectTarget.value) { - if (this.move.getMove().hasAttr(BypassRedirectAttr)) { - redirectTarget.value = currentTarget; + const bypassRedirectAttrs = this.move.getMove().getAttrs(BypassRedirectAttr); + bypassRedirectAttrs.forEach((attr) => { + if (!attr.abilitiesOnly || redirectedByAbility) { + redirectTarget.value = currentTarget; + } + }); - } else if (this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr)) { + if (this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr)) { redirectTarget.value = currentTarget; this.scene.unshiftPhase(new ShowAbilityPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr))); } diff --git a/src/test/moves/pledge_moves.test.ts b/src/test/moves/pledge_moves.test.ts index 45daeaaff25..93fcf57cc60 100644 --- a/src/test/moves/pledge_moves.test.ts +++ b/src/test/moves/pledge_moves.test.ts @@ -1,4 +1,5 @@ import { BattlerIndex } from "#app/battle"; +import { allAbilities } from "#app/data/ability"; import { ArenaTagSide } from "#app/data/arena-tag"; import { allMoves, FlinchAttr } from "#app/data/move"; import { Type } from "#app/data/type"; @@ -293,4 +294,44 @@ describe("Moves - Pledge Moves", () => { enemyPokemon.forEach((p) => expect(p.hp).toBe(p.getMaxHp())); } ); + + it( + "Pledge Moves - should ignore redirection from another Pokemon's Storm Drain", + async () => { + await game.classicMode.startBattle([ Species.BLASTOISE, Species.CHARIZARD ]); + + const enemyPokemon = game.scene.getEnemyField(); + vi.spyOn(enemyPokemon[1], "getAbility").mockReturnValue(allAbilities[Abilities.STORM_DRAIN]); + + game.move.select(Moves.WATER_PLEDGE, 0, BattlerIndex.ENEMY); + game.move.select(Moves.SPLASH, 1); + + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ]); + + await game.phaseInterceptor.to("MoveEndPhase", false); + + expect(enemyPokemon[0].hp).toBeLessThan(enemyPokemon[0].getMaxHp()); + expect(enemyPokemon[1].getStatStage(Stat.SPATK)).toBe(0); + } + ); + + it( + "Pledge Moves - should not ignore redirection from another Pokemon's Follow Me", + async () => { + game.override.enemyMoveset([ Moves.FOLLOW_ME, Moves.SPLASH ]); + await game.classicMode.startBattle([ Species.BLASTOISE, Species.CHARIZARD ]); + + game.move.select(Moves.WATER_PLEDGE, 0, BattlerIndex.ENEMY); + game.move.select(Moves.SPLASH, 1); + + await game.forceEnemyMove(Moves.SPLASH); + await game.forceEnemyMove(Moves.FOLLOW_ME); + + await game.phaseInterceptor.to("BerryPhase", false); + + const enemyPokemon = game.scene.getEnemyField(); + expect(enemyPokemon[0].hp).toBe(enemyPokemon[0].getMaxHp()); + expect(enemyPokemon[1].hp).toBeLessThan(enemyPokemon[1].getMaxHp()); + } + ); });