From 77948a8ff86612e00a1a27c103157444251fdea8 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Tue, 19 Aug 2025 16:22:55 -0500 Subject: [PATCH] Remove BYPASS_SLEEP battler tag in favor of boolean holder --- src/data/battler-tags.ts | 3 --- src/data/moves/move.ts | 16 ++++++++-------- src/enums/battler-tag-type.ts | 1 - src/phases/move-phase.ts | 35 +++++++++++++++++----------------- test/moves/sleep-talk.test.ts | 13 ++++++++++--- test/moves/throat-chop.test.ts | 1 - 6 files changed, 36 insertions(+), 33 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 8f1cddb9e38..1b7736dfc6d 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -3788,8 +3788,6 @@ export function getBattlerTag( case BattlerTagType.ALWAYS_GET_HIT: case BattlerTagType.RECEIVE_DOUBLE_DAMAGE: return new SerializableBattlerTag(tagType, BattlerTagLapseType.PRE_MOVE, 1, sourceMove); - case BattlerTagType.BYPASS_SLEEP: - return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, turnCount, sourceMove); case BattlerTagType.IGNORE_FLYING: return new GroundedTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove); case BattlerTagType.ROOSTED: @@ -3963,7 +3961,6 @@ export type BattlerTagTypeMap = { [BattlerTagType.IGNORE_ACCURACY]: GenericSerializableBattlerTag; [BattlerTagType.ALWAYS_GET_HIT]: GenericSerializableBattlerTag; [BattlerTagType.RECEIVE_DOUBLE_DAMAGE]: GenericSerializableBattlerTag; - [BattlerTagType.BYPASS_SLEEP]: BattlerTag; [BattlerTagType.IGNORE_FLYING]: GroundedTag; [BattlerTagType.ROOSTED]: RoostedTag; [BattlerTagType.BURNED_UP]: RemovedTypeTag; diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index ce253e3288c..850c751772b 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -3187,19 +3187,19 @@ export class HealStatusEffectAttr extends MoveEffectAttr { } /** - * Attribute to add the {@linkcode BattlerTagType.BYPASS_SLEEP | BYPASS_SLEEP Battler Tag} for 1 turn to the user before move use. + * Attribute checked during the `MovePhase`'s {@linkcode MovePhase.checkSleep | checkSleep} failure sequence to allow + * the move to bypass the sleep condition * Used by {@linkcode MoveId.SNORE} and {@linkcode MoveId.SLEEP_TALK}. */ -// TODO: Should this use a battler tag? // TODO: Give this `userSleptOrComatoseCondition` by default export class BypassSleepAttr extends MoveAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (user.status?.effect === StatusEffect.SLEEP) { - user.addTag(BattlerTagType.BYPASS_SLEEP, 1, move.id, user.id); - return true; + apply(user: Pokemon, target: Pokemon, move: Move, args: [BooleanHolder, ...any[]]): boolean { + const bypassSleep = args[0]; + if (bypassSleep.value) { + return false; } - - return false; + bypassSleep.value = true; + return true } /** diff --git a/src/enums/battler-tag-type.ts b/src/enums/battler-tag-type.ts index 4f0ac491e8b..e4166619423 100644 --- a/src/enums/battler-tag-type.ts +++ b/src/enums/battler-tag-type.ts @@ -47,7 +47,6 @@ export enum BattlerTagType { CRIT_BOOST = "CRIT_BOOST", ALWAYS_CRIT = "ALWAYS_CRIT", IGNORE_ACCURACY = "IGNORE_ACCURACY", - BYPASS_SLEEP = "BYPASS_SLEEP", IGNORE_FLYING = "IGNORE_FLYING", SALT_CURED = "SALT_CURED", CURSED = "CURSED", diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 8008739cdb0..fc42c6fde39 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -182,15 +182,13 @@ export class MovePhase extends PokemonPhase { * - If the user is asleep but can use the move, the sleep animation and message is still shown * - If the user is frozen but is thawed from its move, the user's status is cured and the thaw message is shown */ - private post1stFailSleepOrThaw(): void { + private doThawCheck(): void { const user = this.pokemon; if (isIgnoreStatus(this.useMode)) { return; } - if (user.status?.effect === StatusEffect.SLEEP) { - this.triggerStatus(StatusEffect.SLEEP, false); - } else if (this.thaw) { + if (this.thaw) { this.cureStatus( StatusEffect.FREEZE, i18next.t("statusEffect:freeze.healByMove", { @@ -336,9 +334,8 @@ export class MovePhase extends PokemonPhase { } // If the first failure check passes (and this is not a sub-move) then thaw the user if its move will thaw it. - // The sleep message and animation should also play if the user is asleep but using a move anyway (snore, sleep talk, etc) if (!isFollowUp) { - this.post1stFailSleepOrThaw(); + this.doThawCheck(); } // Reset hit-related turn data when starting follow-up moves (e.g. Metronomed moves, Dancer repeats) @@ -474,32 +471,36 @@ export class MovePhase extends PokemonPhase { * @returns Whether the move was cancelled due to sleep */ protected checkSleep(): boolean { - if (this.pokemon.status?.effect !== StatusEffect.SLEEP) { + const user = this.pokemon; + if (user.status?.effect !== StatusEffect.SLEEP) { return false; } // For some reason, dancer will immediately wake its user from sleep when triggering if (this.useMode === MoveUseMode.INDIRECT) { - this.pokemon.resetStatus(false); + user.resetStatus(false); return false; } - this.pokemon.status.incrementTurn(); - applyMoveAttrs("BypassSleepAttr", this.pokemon, null, this.move.getMove()); - const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0); + user.status.incrementTurn(); + const turnsRemaining = new NumberHolder(user.status.sleepTurnsRemaining ?? 0); applyAbAttrs("ReduceStatusEffectDurationAbAttr", { - pokemon: this.pokemon, - statusEffect: this.pokemon.status.effect, + pokemon: user, + statusEffect: user.status.effect, duration: turnsRemaining, }); - this.pokemon.status.sleepTurnsRemaining = turnsRemaining.value; - if (this.pokemon.status.sleepTurnsRemaining <= 0) { + + user.status.sleepTurnsRemaining = turnsRemaining.value; + if (user.status.sleepTurnsRemaining <= 0) { this.cureStatus(StatusEffect.SLEEP); return false; } - this.triggerStatus(StatusEffect.SLEEP); - return true; + const bypassSleepHolder = new BooleanHolder(false); + applyMoveAttrs("BypassSleepAttr", this.pokemon, null, this.move.getMove(), bypassSleepHolder); + const cancel = !bypassSleepHolder.value; + this.triggerStatus(StatusEffect.SLEEP, cancel); + return cancel; } /** diff --git a/test/moves/sleep-talk.test.ts b/test/moves/sleep-talk.test.ts index 56dc7ba2121..ee639aaf1e8 100644 --- a/test/moves/sleep-talk.test.ts +++ b/test/moves/sleep-talk.test.ts @@ -97,9 +97,16 @@ describe("Moves - Sleep Talk", () => { game.move.select(MoveId.SLEEP_TALK); await game.toNextTurn(); + expect(game.field.getPlayerPokemon().getStatStage(Stat.ATK)); + }); - const feebas = game.field.getPlayerPokemon(); - expect(feebas.getStatStage(Stat.SPD)).toBe(1); - expect(feebas.getStatStage(Stat.DEF)).toBe(-1); + it("should apply secondary effects of a move", async () => { + game.override.moveset([MoveId.SLEEP_TALK, MoveId.DIG, MoveId.FLY, MoveId.WOOD_HAMMER]); // Dig and Fly are invalid moves, Wood Hammer should always be called + await game.classicMode.startBattle([SpeciesId.FEEBAS]); + + game.move.select(MoveId.SLEEP_TALK); + await game.toNextTurn(); + + expect(game.field.getPlayerPokemon().isFullHp()).toBeFalsy(); // Wood Hammer recoil effect should be applied }); }); diff --git a/test/moves/throat-chop.test.ts b/test/moves/throat-chop.test.ts index 335bfce2f40..b53b2c4c0e2 100644 --- a/test/moves/throat-chop.test.ts +++ b/test/moves/throat-chop.test.ts @@ -48,7 +48,6 @@ describe("Moves - Throat Chop", () => { await game.toNextTurn(); expect(player.trySelectMove(MoveId.GROWL)[0]).toBe(false); - game.move.select(MoveId.GROWL); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);