Remove BYPASS_SLEEP battler tag in favor of boolean holder

This commit is contained in:
Sirz Benjie 2025-08-19 16:22:55 -05:00
parent 4273345371
commit 77948a8ff8
No known key found for this signature in database
GPG Key ID: 4A524B4D196C759E
6 changed files with 36 additions and 33 deletions

View File

@ -3788,8 +3788,6 @@ export function getBattlerTag(
case BattlerTagType.ALWAYS_GET_HIT: case BattlerTagType.ALWAYS_GET_HIT:
case BattlerTagType.RECEIVE_DOUBLE_DAMAGE: case BattlerTagType.RECEIVE_DOUBLE_DAMAGE:
return new SerializableBattlerTag(tagType, BattlerTagLapseType.PRE_MOVE, 1, sourceMove); 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: case BattlerTagType.IGNORE_FLYING:
return new GroundedTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove); return new GroundedTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove);
case BattlerTagType.ROOSTED: case BattlerTagType.ROOSTED:
@ -3963,7 +3961,6 @@ export type BattlerTagTypeMap = {
[BattlerTagType.IGNORE_ACCURACY]: GenericSerializableBattlerTag<BattlerTagType.IGNORE_ACCURACY>; [BattlerTagType.IGNORE_ACCURACY]: GenericSerializableBattlerTag<BattlerTagType.IGNORE_ACCURACY>;
[BattlerTagType.ALWAYS_GET_HIT]: GenericSerializableBattlerTag<BattlerTagType.ALWAYS_GET_HIT>; [BattlerTagType.ALWAYS_GET_HIT]: GenericSerializableBattlerTag<BattlerTagType.ALWAYS_GET_HIT>;
[BattlerTagType.RECEIVE_DOUBLE_DAMAGE]: GenericSerializableBattlerTag<BattlerTagType.RECEIVE_DOUBLE_DAMAGE>; [BattlerTagType.RECEIVE_DOUBLE_DAMAGE]: GenericSerializableBattlerTag<BattlerTagType.RECEIVE_DOUBLE_DAMAGE>;
[BattlerTagType.BYPASS_SLEEP]: BattlerTag;
[BattlerTagType.IGNORE_FLYING]: GroundedTag; [BattlerTagType.IGNORE_FLYING]: GroundedTag;
[BattlerTagType.ROOSTED]: RoostedTag; [BattlerTagType.ROOSTED]: RoostedTag;
[BattlerTagType.BURNED_UP]: RemovedTypeTag; [BattlerTagType.BURNED_UP]: RemovedTypeTag;

View File

@ -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}. * Used by {@linkcode MoveId.SNORE} and {@linkcode MoveId.SLEEP_TALK}.
*/ */
// TODO: Should this use a battler tag?
// TODO: Give this `userSleptOrComatoseCondition` by default // TODO: Give this `userSleptOrComatoseCondition` by default
export class BypassSleepAttr extends MoveAttr { export class BypassSleepAttr extends MoveAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: [BooleanHolder, ...any[]]): boolean {
if (user.status?.effect === StatusEffect.SLEEP) { const bypassSleep = args[0];
user.addTag(BattlerTagType.BYPASS_SLEEP, 1, move.id, user.id); if (bypassSleep.value) {
return true; return false;
} }
bypassSleep.value = true;
return false; return true
} }
/** /**

View File

@ -47,7 +47,6 @@ export enum BattlerTagType {
CRIT_BOOST = "CRIT_BOOST", CRIT_BOOST = "CRIT_BOOST",
ALWAYS_CRIT = "ALWAYS_CRIT", ALWAYS_CRIT = "ALWAYS_CRIT",
IGNORE_ACCURACY = "IGNORE_ACCURACY", IGNORE_ACCURACY = "IGNORE_ACCURACY",
BYPASS_SLEEP = "BYPASS_SLEEP",
IGNORE_FLYING = "IGNORE_FLYING", IGNORE_FLYING = "IGNORE_FLYING",
SALT_CURED = "SALT_CURED", SALT_CURED = "SALT_CURED",
CURSED = "CURSED", CURSED = "CURSED",

View File

@ -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 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 * - 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; const user = this.pokemon;
if (isIgnoreStatus(this.useMode)) { if (isIgnoreStatus(this.useMode)) {
return; return;
} }
if (user.status?.effect === StatusEffect.SLEEP) { if (this.thaw) {
this.triggerStatus(StatusEffect.SLEEP, false);
} else if (this.thaw) {
this.cureStatus( this.cureStatus(
StatusEffect.FREEZE, StatusEffect.FREEZE,
i18next.t("statusEffect:freeze.healByMove", { 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. // 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) { if (!isFollowUp) {
this.post1stFailSleepOrThaw(); this.doThawCheck();
} }
// Reset hit-related turn data when starting follow-up moves (e.g. Metronomed moves, Dancer repeats) // 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 * @returns Whether the move was cancelled due to sleep
*/ */
protected checkSleep(): boolean { protected checkSleep(): boolean {
if (this.pokemon.status?.effect !== StatusEffect.SLEEP) { const user = this.pokemon;
if (user.status?.effect !== StatusEffect.SLEEP) {
return false; return false;
} }
// For some reason, dancer will immediately wake its user from sleep when triggering // For some reason, dancer will immediately wake its user from sleep when triggering
if (this.useMode === MoveUseMode.INDIRECT) { if (this.useMode === MoveUseMode.INDIRECT) {
this.pokemon.resetStatus(false); user.resetStatus(false);
return false; return false;
} }
this.pokemon.status.incrementTurn(); user.status.incrementTurn();
applyMoveAttrs("BypassSleepAttr", this.pokemon, null, this.move.getMove()); const turnsRemaining = new NumberHolder(user.status.sleepTurnsRemaining ?? 0);
const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0);
applyAbAttrs("ReduceStatusEffectDurationAbAttr", { applyAbAttrs("ReduceStatusEffectDurationAbAttr", {
pokemon: this.pokemon, pokemon: user,
statusEffect: this.pokemon.status.effect, statusEffect: user.status.effect,
duration: turnsRemaining, 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); this.cureStatus(StatusEffect.SLEEP);
return false; return false;
} }
this.triggerStatus(StatusEffect.SLEEP); const bypassSleepHolder = new BooleanHolder(false);
return true; applyMoveAttrs("BypassSleepAttr", this.pokemon, null, this.move.getMove(), bypassSleepHolder);
const cancel = !bypassSleepHolder.value;
this.triggerStatus(StatusEffect.SLEEP, cancel);
return cancel;
} }
/** /**

View File

@ -97,9 +97,16 @@ describe("Moves - Sleep Talk", () => {
game.move.select(MoveId.SLEEP_TALK); game.move.select(MoveId.SLEEP_TALK);
await game.toNextTurn(); await game.toNextTurn();
expect(game.field.getPlayerPokemon().getStatStage(Stat.ATK));
});
const feebas = game.field.getPlayerPokemon(); it("should apply secondary effects of a move", async () => {
expect(feebas.getStatStage(Stat.SPD)).toBe(1); 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
expect(feebas.getStatStage(Stat.DEF)).toBe(-1); 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
}); });
}); });

View File

@ -48,7 +48,6 @@ describe("Moves - Throat Chop", () => {
await game.toNextTurn(); await game.toNextTurn();
expect(player.trySelectMove(MoveId.GROWL)[0]).toBe(false); expect(player.trySelectMove(MoveId.GROWL)[0]).toBe(false);
game.move.select(MoveId.GROWL); game.move.select(MoveId.GROWL);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);