diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 8f5f267f7ef..6b7b36e4807 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -1670,44 +1670,39 @@ export class AllyStatMultiplierAbAttr extends AbAttr { } /** - * Ability attribute for Gorilla Tactics - * @extends PostAttackAbAttr + * Takes effect whenever a move succesfully executes, such as gorilla tactics' move-locking. + * (More specifically, whenever a move is pushed to the move history) + * @extends AbAttr */ -export class GorillaTacticsAbAttr extends PostAttackAbAttr { - constructor() { - super((user, target, move) => true, false); +export class ExecutedMoveAbAttr extends AbAttr { + canApplyExecutedMove( + pokemon: Pokemon, + simulated: boolean, + ): boolean { + return true; } - override canApplyPostAttack( + applyExecutedMove( pokemon: Pokemon, - passive: boolean, simulated: boolean, - defender: Pokemon, - move: Move, - hitResult: HitResult | null, - args: any[]): boolean { - return super.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args) - && simulated || !pokemon.getTag(BattlerTagType.GORILLA_TACTICS); + ): void {} +} + +/** + * Ability attribute for Gorilla Tactics + * @extends ExecutedMoveAbAttr + */ +export class GorillaTacticsAbAttr extends ExecutedMoveAbAttr { + constructor(showAbility: boolean = false) { + super(showAbility); } - /** - * - * @param {Pokemon} pokemon the {@linkcode Pokemon} with this ability - * @param passive n/a - * @param simulated whether the ability is being simulated - * @param defender n/a - * @param move n/a - * @param hitResult n/a - * @param args n/a - */ - override applyPostAttack( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - defender: Pokemon, - move: Move, - hitResult: HitResult | null, - args: any[]): void { + override canApplyExecutedMove(pokemon: Pokemon, simulated: boolean): boolean { + return super.canApplyExecutedMove(pokemon, simulated) && + simulated || !pokemon.getTag(BattlerTagType.GORILLA_TACTICS); + } + + override applyExecutedMove(pokemon: Pokemon, simulated: boolean): void { if (!simulated) { pokemon.addTag(BattlerTagType.GORILLA_TACTICS); } @@ -6022,6 +6017,22 @@ export function applyPreAttackAbAttrs( ); } +export function applyExecutedMoveAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + attr => attr.applyExecutedMove(pokemon, simulated), + attr => attr.canApplyExecutedMove(pokemon, simulated), + args, + simulated, + ); +} + export function applyPostAttackAbAttrs( attrType: Constructor, pokemon: Pokemon, diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 636f85f0f82..d0972f836f9 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -3,10 +3,12 @@ import { globalScene } from "#app/global-scene"; import { AddSecondStrikeAbAttr, AlwaysHitAbAttr, + applyExecutedMoveAbAttrs, applyPostAttackAbAttrs, applyPostDamageAbAttrs, applyPostDefendAbAttrs, applyPreAttackAbAttrs, + ExecutedMoveAbAttr, IgnoreMoveEffectsAbAttr, MaxMultiHitAbAttr, PostAttackAbAttr, @@ -370,6 +372,7 @@ export class MoveEffectPhase extends PokemonPhase { // Add to the move history entry if (this.firstHit) { user.pushMoveHistory(this.moveHistoryEntry); + applyExecutedMoveAbAttrs(ExecutedMoveAbAttr, user); } try { diff --git a/test/abilities/gorilla_tactics.test.ts b/test/abilities/gorilla_tactics.test.ts index 55b8a4addcd..bfb4f95d144 100644 --- a/test/abilities/gorilla_tactics.test.ts +++ b/test/abilities/gorilla_tactics.test.ts @@ -73,9 +73,38 @@ describe("Abilities - Gorilla Tactics", () => { await game.toNextTurn(); game.move.select(MoveId.TACKLE); + await game.selectTarget(MoveId.SPLASH); //prevent protect from being used by the enemy await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.phaseInterceptor.to("MoveEndPhase"); expect(darmanitan.hp).toBeLessThan(darmanitan.getMaxHp()); }); + + it("should activate when the opponenet protects", async () => { + await game.classicMode.startBattle([SpeciesId.GALAR_DARMANITAN]); + + const darmanitan = game.scene.getPlayerPokemon()!; + + game.move.select(MoveId.TACKLE); + await game.move.selectEnemyMove(MoveId.PROTECT); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(darmanitan.isMoveRestricted(MoveId.SPLASH)).toBe(true); + expect(darmanitan.isMoveRestricted(MoveId.TACKLE)).toBe(false); + }); + + it("should activate when a move is succesfully executed but misses", async () => { + await game.classicMode.startBattle([SpeciesId.GALAR_DARMANITAN]); + + const darmanitan = game.scene.getPlayerPokemon()!; + + game.move.select(MoveId.TACKLE); + await game.move.selectEnemyMove(MoveId.SPLASH); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.move.forceMiss(); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(darmanitan.isMoveRestricted(MoveId.SPLASH)).toBe(true); + expect(darmanitan.isMoveRestricted(MoveId.TACKLE)).toBe(false); + }); });