Pledges now only bypass redirection from abilities

This commit is contained in:
innerthunder 2024-10-04 12:59:25 -07:00
parent c1809a16bd
commit 742b8d4258
3 changed files with 65 additions and 8 deletions

View File

@ -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 * 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.
*/ */
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 { export class FrenzyAttr extends MoveEffectAttr {
constructor() { constructor() {
@ -8516,7 +8524,7 @@ export function initMoves() {
.attr(CombinedPledgeStabBoostAttr) .attr(CombinedPledgeStabBoostAttr)
.attr(AddPledgeEffectAttr, ArenaTagType.WATER_FIRE_PLEDGE, Moves.FIRE_PLEDGE, true) .attr(AddPledgeEffectAttr, ArenaTagType.WATER_FIRE_PLEDGE, Moves.FIRE_PLEDGE, true)
.attr(AddPledgeEffectAttr, ArenaTagType.GRASS_WATER_PLEDGE, Moves.GRASS_PLEDGE) .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) new AttackMove(Moves.FIRE_PLEDGE, Type.FIRE, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 5)
.attr(AwaitCombinedPledgeAttr) .attr(AwaitCombinedPledgeAttr)
.attr(CombinedPledgeTypeAttr) .attr(CombinedPledgeTypeAttr)
@ -8524,7 +8532,7 @@ export function initMoves() {
.attr(CombinedPledgeStabBoostAttr) .attr(CombinedPledgeStabBoostAttr)
.attr(AddPledgeEffectAttr, ArenaTagType.FIRE_GRASS_PLEDGE, Moves.GRASS_PLEDGE) .attr(AddPledgeEffectAttr, ArenaTagType.FIRE_GRASS_PLEDGE, Moves.GRASS_PLEDGE)
.attr(AddPledgeEffectAttr, ArenaTagType.WATER_FIRE_PLEDGE, Moves.WATER_PLEDGE, true) .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) new AttackMove(Moves.GRASS_PLEDGE, Type.GRASS, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 5)
.attr(AwaitCombinedPledgeAttr) .attr(AwaitCombinedPledgeAttr)
.attr(CombinedPledgeTypeAttr) .attr(CombinedPledgeTypeAttr)
@ -8532,7 +8540,7 @@ export function initMoves() {
.attr(CombinedPledgeStabBoostAttr) .attr(CombinedPledgeStabBoostAttr)
.attr(AddPledgeEffectAttr, ArenaTagType.GRASS_WATER_PLEDGE, Moves.WATER_PLEDGE) .attr(AddPledgeEffectAttr, ArenaTagType.GRASS_WATER_PLEDGE, Moves.WATER_PLEDGE)
.attr(AddPledgeEffectAttr, ArenaTagType.FIRE_GRASS_PLEDGE, Moves.FIRE_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) new AttackMove(Moves.VOLT_SWITCH, Type.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 20, -1, 0, 5)
.attr(ForceSwitchOutAttr, true), .attr(ForceSwitchOutAttr, true),
new AttackMove(Moves.STRUGGLE_BUG, Type.BUG, MoveCategory.SPECIAL, 50, 100, 20, 100, 0, 5) new AttackMove(Moves.STRUGGLE_BUG, Type.BUG, MoveCategory.SPECIAL, 50, 100, 20, 100, 0, 5)

View File

@ -331,22 +331,30 @@ export class MovePhase extends BattlePhase {
// check move redirection abilities of every pokemon *except* the user. // 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)); 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) // check for center-of-attention tags (note that this will override redirect abilities)
this.pokemon.getOpponents().forEach(p => { this.pokemon.getOpponents().forEach(p => {
const redirectTag = p.getTag(CenterOfAttentionTag) as CenterOfAttentionTag; const redirectTag = p.getTag(CenterOfAttentionTag);
// TODO: don't hardcode this interaction. // 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) // 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)))) { if (redirectTag && (!redirectTag.powder || (!this.pokemon.isOfType(Type.GRASS) && !this.pokemon.hasAbility(Abilities.OVERCOAT)))) {
redirectTarget.value = p.getBattlerIndex(); redirectTarget.value = p.getBattlerIndex();
redirectedByAbility = false;
} }
}); });
if (currentTarget !== redirectTarget.value) { if (currentTarget !== redirectTarget.value) {
if (this.move.getMove().hasAttr(BypassRedirectAttr)) { const bypassRedirectAttrs = this.move.getMove().getAttrs(BypassRedirectAttr);
redirectTarget.value = currentTarget; bypassRedirectAttrs.forEach((attr) => {
if (!attr.abilitiesOnly || redirectedByAbility) {
redirectTarget.value = currentTarget;
}
});
} else if (this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr)) { if (this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr)) {
redirectTarget.value = currentTarget; redirectTarget.value = currentTarget;
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)));
} }

View File

@ -1,4 +1,5 @@
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
import { allAbilities } from "#app/data/ability";
import { ArenaTagSide } from "#app/data/arena-tag"; import { ArenaTagSide } from "#app/data/arena-tag";
import { allMoves, FlinchAttr } from "#app/data/move"; import { allMoves, FlinchAttr } from "#app/data/move";
import { Type } from "#app/data/type"; import { Type } from "#app/data/type";
@ -293,4 +294,44 @@ describe("Moves - Pledge Moves", () => {
enemyPokemon.forEach((p) => expect(p.hp).toBe(p.getMaxHp())); 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());
}
);
}); });