diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index b33b38a0f78..4b461e7e9fa 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -2535,48 +2535,38 @@ 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); - } - - override canApplyPostAttack( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - defender: Pokemon, - move: Move, - hitResult: HitResult | null, - args: any[], +export class ExecutedMoveAbAttr extends AbAttr { + canApplyExecutedMove( + _pokemon: Pokemon, + _simulated: boolean, ): boolean { - return ( - (super.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args) && simulated) || - !pokemon.getTag(BattlerTagType.GORILLA_TACTICS) - ); + return true; } - /** - * - * @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 { + applyExecutedMove( + _pokemon: Pokemon, + _simulated: boolean, + ): void {} +} + +/** + * Ability attribute for Gorilla Tactics + * @extends ExecutedMoveAbAttr + */ +export class GorillaTacticsAbAttr extends ExecutedMoveAbAttr { + constructor(showAbility: boolean = false) { + super(showAbility); + } + + override canApplyExecutedMove(pokemon: Pokemon, simulated: boolean): boolean { + return simulated || !pokemon.getTag(BattlerTagType.GORILLA_TACTICS); + } + + override applyExecutedMove(pokemon: Pokemon, simulated: boolean): void { if (!simulated) { pokemon.addTag(BattlerTagType.GORILLA_TACTICS); } @@ -7789,6 +7779,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/data/balance/tms.ts b/src/data/balance/tms.ts index e95fa12151d..2b0e8f5142d 100644 --- a/src/data/balance/tms.ts +++ b/src/data/balance/tms.ts @@ -5988,6 +5988,7 @@ export const tmSpecies: TmSpecies = { SpeciesId.FEZANDIPITI, SpeciesId.ARCHALUDON, SpeciesId.IRON_CROWN, + SpeciesId.TERAPAGOS, SpeciesId.ALOLA_RATICATE, SpeciesId.ALOLA_RAICHU, SpeciesId.ALOLA_SANDSLASH, @@ -16248,6 +16249,7 @@ export const tmSpecies: TmSpecies = { SpeciesId.CALYREX, SpeciesId.SANDY_SHOCKS, SpeciesId.IRON_JUGULIS, + SpeciesId.TERAPAGOS, SpeciesId.ALOLA_DUGTRIO, SpeciesId.GALAR_SLOWPOKE, SpeciesId.GALAR_SLOWBRO, @@ -39466,6 +39468,8 @@ export const tmSpecies: TmSpecies = { SpeciesId.FARFETCHD, SpeciesId.DODUO, SpeciesId.DODRIO, + SpeciesId.DEWGONG, + SpeciesId.GRIMER, SpeciesId.MUK, SpeciesId.GASTLY, SpeciesId.HAUNTER, @@ -39477,6 +39481,7 @@ export const tmSpecies: TmSpecies = { SpeciesId.CUBONE, SpeciesId.MAROWAK, SpeciesId.HITMONLEE, + SpeciesId.HITMONCHAN, SpeciesId.LICKITUNG, SpeciesId.TANGELA, SpeciesId.GOLDEEN, @@ -48806,6 +48811,7 @@ export const tmSpecies: TmSpecies = { SpeciesId.GARGANACL, SpeciesId.GLIMMET, SpeciesId.GLIMMORA, + SpeciesId.TERAPAGOS, SpeciesId.ALOLA_GEODUDE, SpeciesId.ALOLA_GRAVELER, SpeciesId.ALOLA_GOLEM, @@ -53077,6 +53083,7 @@ export const tmSpecies: TmSpecies = { SpeciesId.MIRAIDON, SpeciesId.ARCHALUDON, SpeciesId.IRON_CROWN, + SpeciesId.TERAPAGOS, [ SpeciesId.WORMADAM, "trash", diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 477d0779d61..5c208059803 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, @@ -374,6 +376,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/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 42c24434f40..329c8df3dc0 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -32,6 +32,8 @@ import Overrides from "#app/overrides"; import type { CustomModifierSettings } from "#app/modifier/modifier-type"; import { isNullOrUndefined, NumberHolder } from "#app/utils/common"; +export type ModifierSelectCallback = (rowCursor: number, cursor: number) => boolean; + export class SelectModifierPhase extends BattlePhase { public readonly phaseName = "SelectModifierPhase"; private rerollCount: number; @@ -58,6 +60,10 @@ export class SelectModifierPhase extends BattlePhase { start() { super.start(); + if (!this.isPlayer()) { + return false; + } + if (!this.rerollCount && !this.isCopy) { this.updateSeed(); } else if (this.rerollCount) { @@ -70,6 +76,9 @@ export class SelectModifierPhase extends BattlePhase { } const modifierCount = this.getModifierCount(); + this.typeOptions = this.getModifierTypeOptions(modifierCount); + const modifierCount = this.getModifierCount(); + this.typeOptions = this.getModifierTypeOptions(modifierCount); const modifierSelectCallback = (rowCursor: number, cursor: number) => { @@ -121,7 +130,7 @@ export class SelectModifierPhase extends BattlePhase { } // Pick a modifier from among the rewards and apply it - private selectRewardModifierOption(cursor: number, modifierSelectCallback): boolean { + private selectRewardModifierOption(cursor: number, modifierSelectCallback: ModifierSelectCallback): boolean { if (this.typeOptions.length === 0) { globalScene.ui.clearText(); globalScene.ui.setMode(UiMode.MESSAGE); @@ -133,7 +142,11 @@ export class SelectModifierPhase extends BattlePhase { } // Pick a modifier from the shop and apply it - private selectShopModifierOption(rowCursor: number, cursor: number, modifierSelectCallback): boolean { + private selectShopModifierOption( + rowCursor: number, + cursor: number, + modifierSelectCallback: ModifierSelectCallback, + ): boolean { const shopOptions = getPlayerShopModifierTypeOptionsForWave( globalScene.currentBattle.waveIndex, globalScene.getWaveMoneyAmount(1), @@ -157,18 +170,19 @@ export class SelectModifierPhase extends BattlePhase { } // Apply a chosen modifier: do an effect or open the party menu - private applyChosenModifier(modifierType: ModifierType, cost: number, modifierSelectCallback): boolean { - if (modifierType! instanceof PokemonModifierType) { - //TODO: is the bang correct? - if (modifierType instanceof PokemonHeldItemReward) { - this.openGiveHeldItemMenu(modifierType, modifierSelectCallback); - } else if (modifierType instanceof FusePokemonModifierType) { + private applyChosenModifier( + modifierType: ModifierType, + cost: number, + modifierSelectCallback: ModifierSelectCallback, + ): boolean { + if (modifierType instanceof PokemonModifierType) { + if (modifierType instanceof FusePokemonModifierType) { this.openFusionMenu(modifierType, cost, modifierSelectCallback); } else { this.openModifierMenu(modifierType, cost, modifierSelectCallback); } } else { - this.applyModifier(modifierType!.newModifier()!); // TODO: is the bang correct? + this.applyModifier(modifierType.newModifier()!); } return !cost; } @@ -198,7 +212,7 @@ export class SelectModifierPhase extends BattlePhase { } // Transfer modifiers among party pokemon - private openModifierTransferScreen(modifierSelectCallback) { + private openModifierTransferScreen(modifierSelectCallback: ModifierSelectCallback) { const party = globalScene.getPlayerParty(); globalScene.ui.setModeWithoutClear( UiMode.PARTY, @@ -251,7 +265,7 @@ export class SelectModifierPhase extends BattlePhase { } // Applies the effects of the chosen modifier - private applyModifier(modifier: Modifier, cost = 0, playSound = false) { + private applyModifier(modifier: Modifier, cost = 0, playSound = false): void { const result = globalScene.addModifier(modifier, false, playSound, undefined, undefined, cost); // Queue a copy of this phase when applying a TM or Memory Mushroom. // If the player selects either of these, then escapes out of consuming them, @@ -399,7 +413,7 @@ export class SelectModifierPhase extends BattlePhase { // Function that resets the reward selection screen, // e.g. after pressing cancel in the party ui or while learning a move - private resetModifierSelect(modifierSelectCallback) { + private resetModifierSelect(modifierSelectCallback: ModifierSelectCallback) { globalScene.ui.setMode( UiMode.MODIFIER_SELECT, this.isPlayer(), diff --git a/test/abilities/dancer.test.ts b/test/abilities/dancer.test.ts index ae702e4b1e7..518c96ea124 100644 --- a/test/abilities/dancer.test.ts +++ b/test/abilities/dancer.test.ts @@ -25,7 +25,7 @@ describe("Abilities - Dancer", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("double"); + game.override.battleStyle("double").enemyAbility(AbilityId.BALL_FETCH); }); // Reference Link: https://bulbapedia.bulbagarden.net/wiki/Dancer_(Ability) diff --git a/test/abilities/gorilla_tactics.test.ts b/test/abilities/gorilla_tactics.test.ts index 55b8a4addcd..3ad138749a8 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.move.forceEnemyMove(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.field.getPlayerPokemon(); + + game.move.select(MoveId.TACKLE); + await game.move.selectEnemyMove(MoveId.PROTECT); + + await game.toEndOfTurn(); + 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.field.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.toEndOfTurn(); + + expect(darmanitan.isMoveRestricted(MoveId.SPLASH)).toBe(true); + expect(darmanitan.isMoveRestricted(MoveId.TACKLE)).toBe(false); + }); });