From ce752c9d0758527a7d752dff4aceec2872050e83 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Mon, 18 Aug 2025 20:23:27 -0500 Subject: [PATCH] Adjust status checks to respect ignoreStatus useModes --- src/enums/battler-tag-lapse-type.ts | 12 ++++++++++- src/phases/move-phase.ts | 33 +++++++++++++++++------------ test/abilities/cud-chew.test.ts | 6 ++---- test/moves/gigaton-hammer.test.ts | 10 ++++----- 4 files changed, 38 insertions(+), 23 deletions(-) diff --git a/src/enums/battler-tag-lapse-type.ts b/src/enums/battler-tag-lapse-type.ts index f9486876993..cc93d6c6782 100644 --- a/src/enums/battler-tag-lapse-type.ts +++ b/src/enums/battler-tag-lapse-type.ts @@ -11,7 +11,17 @@ export enum BattlerTagLapseType { * @see MoveUseMode for more information */ MOVE, - /** Tag activates during (or just after) the first failure check sequence in the move phase. */ + /** + * Tag activates during (or just after) the first failure check sequence in the move phase. + * + * @remarks + * + * Note tags with this lapse type will lapse immediately after the first failure check sequence, + * regardless of whether the move was successful or not. + * + * To only lapse the tag between the first and second failure check sequences, use + * {@linkcode BattlerTagLapseType.MOVE} instead. + */ PRE_MOVE, /** Tag activates immediately after the holder's move finishes triggering (successful or not). */ AFTER_MOVE, diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 22731977187..c5e9d6dfad1 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -301,8 +301,8 @@ export class MovePhase extends BattlePhase { // if it attempted to move at all. user.turnData.acted = true; const useMode = this.useMode; - const virtual = isVirtual(useMode); - if (!virtual && this.firstFailureCheck()) { + const ignoreStatus = isIgnoreStatus(useMode); + if (!ignoreStatus && this.firstFailureCheck()) { // Lapse all other pre-move tags user.lapseTags(BattlerTagLapseType.PRE_MOVE); this.end(); @@ -314,11 +314,13 @@ export class MovePhase extends BattlePhase { - Protect, detect, ally switch, etc, resetting consecutive use count - Rollout / ice ball "unlocking" - protect / ally switch / other moves resetting their consecutive use count - - and others - In Pokerogue, these are instead handled by their respective methods, which generally + - and many others + In Pokerogue, these are instead handled elsewhere, and generally work in a way that aligns with cartridge behavior */ return; } + // Tags still need to be lapsed if no failure occured + user.lapseTags(BattlerTagLapseType.PRE_MOVE); // At this point, called moves should be decided. // For now, this comment works as a placeholder until we rework how called moves are handled @@ -329,7 +331,7 @@ export class MovePhase extends BattlePhase { this.post1stFailSleepOrThaw(); // Reset hit-related turn data when starting follow-up moves (e.g. Metronomed moves, Dancer repeats) - if (virtual) { + if (isVirtual(useMode)) { this.pokemon.turnData.hitsLeft = -1; this.pokemon.turnData.hitCount = 0; } @@ -369,11 +371,7 @@ export class MovePhase extends BattlePhase { globalScene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangePreMoveTrigger); } - // At this point, if the target index has not moved on from attacker, the move must fail - if (this.targets[0] === BattlerIndex.ATTACKER) { - this.fail(); - } - if (this.targets[0] === BattlerIndex.ATTACKER || this.secondFailureCheck()) { + if (this.secondFailureCheck()) { this.handlePreMoveFailures(); this.end(); return; @@ -393,7 +391,7 @@ export class MovePhase extends BattlePhase { } /** - * Check for cancellation edge cases - no targets remaining or the battler index being targeted is still the attacker + * Check for cancellation edge cases - no targets remaining * @returns Whether the move fails */ protected resolveFinalPreMoveCancellationChecks(): boolean { @@ -402,8 +400,7 @@ export class MovePhase extends BattlePhase { if ( (targets.length === 0 && !this.move.getMove().hasAttr("AddArenaTrapTagAttr")) || - (moveQueue.length > 0 && moveQueue[0].move === MoveId.NONE) || - this.targets[0] === BattlerIndex.ATTACKER + (moveQueue.length > 0 && moveQueue[0].move === MoveId.NONE) ) { this.showFailedText(); this.fail(); @@ -455,6 +452,7 @@ export class MovePhase extends BattlePhase { return false; } + // For some reason, dancer will immediately wake its user from sleep when triggering if (this.useMode === MoveUseMode.INDIRECT) { this.pokemon.resetStatus(false); return false; @@ -493,6 +491,12 @@ export class MovePhase extends BattlePhase { return false; } + // For some reason, dancer will immediately its user + if (this.useMode === MoveUseMode.INDIRECT) { + this.pokemon.resetStatus(false); + return false; + } + // Check if move use would heal the user if (Overrides.STATUS_ACTIVATION_OVERRIDE) { @@ -906,6 +910,9 @@ export class MovePhase extends BattlePhase { applyMoveAttrs("CounterRedirectAttr", this.pokemon, null, this.move.getMove(), targetHolder); this.targets[0] = targetHolder.value; + if (targetHolder.value === BattlerIndex.ATTACKER) { + this.fail(); + } } /** diff --git a/test/abilities/cud-chew.test.ts b/test/abilities/cud-chew.test.ts index f68141096eb..2c1850d8e42 100644 --- a/test/abilities/cud-chew.test.ts +++ b/test/abilities/cud-chew.test.ts @@ -1,4 +1,3 @@ -import { CudChewConsumeBerryAbAttr } from "#abilities/ability"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import { AbilityId } from "#enums/ability-id"; @@ -6,7 +5,6 @@ import { BerryType } from "#enums/berry-type"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; -import { Pokemon } from "#field/pokemon"; import { GameManager } from "#test/test-utils/game-manager"; import i18next from "i18next"; import Phaser from "phaser"; @@ -111,7 +109,6 @@ describe("Abilities - Cud Chew", () => { it("can store multiple berries across 2 turns with teatime", async () => { // always eat first berry for stuff cheeks & company - vi.spyOn(Pokemon.prototype, "randBattleSeedInt").mockReturnValue(0); game.override .startingHeldItems([ { name: "BERRY", type: BerryType.PETAYA, count: 3 }, @@ -122,6 +119,7 @@ describe("Abilities - Cud Chew", () => { const farigiraf = game.field.getPlayerPokemon(); farigiraf.hp = 1; // needed to allow berry procs + vi.spyOn(farigiraf, "randBattleSeedInt").mockReturnValue(0); game.move.select(MoveId.STUFF_CHEEKS); await game.toNextTurn(); @@ -196,10 +194,10 @@ describe("Abilities - Cud Chew", () => { describe("regurgiates berries", () => { it("re-triggers effects on eater without pushing to array", async () => { - const apply = vi.spyOn(CudChewConsumeBerryAbAttr.prototype, "apply"); await game.classicMode.startBattle([SpeciesId.FARIGIRAF]); const farigiraf = game.field.getPlayerPokemon(); + const apply = vi.spyOn(farigiraf.getAbilityAttrs("CudChewConsumeBerryAbAttr")[0], "apply"); farigiraf.hp = 1; game.move.select(MoveId.SPLASH); diff --git a/test/moves/gigaton-hammer.test.ts b/test/moves/gigaton-hammer.test.ts index e5009310de6..d217290034b 100644 --- a/test/moves/gigaton-hammer.test.ts +++ b/test/moves/gigaton-hammer.test.ts @@ -33,7 +33,7 @@ describe("Moves - Gigaton Hammer", () => { }); it("can't be used two turns in a row", async () => { - await game.classicMode.startBattle(); + await game.classicMode.startBattle([SpeciesId.MAGIKARP]); const enemy1 = game.field.getEnemyPokemon(); @@ -46,17 +46,17 @@ describe("Moves - Gigaton Hammer", () => { await game.doKillOpponents(); await game.toNextWave(); + // Attempting to use Gigaton Hammer again should result in struggle game.move.select(MoveId.GIGATON_HAMMER); await game.toNextTurn(); - const enemy2 = game.field.getEnemyPokemon(); - - expect(enemy2.hp).toBe(enemy2.getMaxHp()); + const player = game.field.getPlayerPokemon(); + expect(player.getLastXMoves()[0]?.move).toBe(MoveId.STRUGGLE); }); it("can be used again if recalled and sent back out", async () => { game.override.startingWave(4); - await game.classicMode.startBattle(); + await game.classicMode.startBattle([SpeciesId.MAGIKARP]); const enemy1 = game.field.getEnemyPokemon();