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.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.IGNORE_ACCURACY>;
[BattlerTagType.ALWAYS_GET_HIT]: GenericSerializableBattlerTag<BattlerTagType.ALWAYS_GET_HIT>;
[BattlerTagType.RECEIVE_DOUBLE_DAMAGE]: GenericSerializableBattlerTag<BattlerTagType.RECEIVE_DOUBLE_DAMAGE>;
[BattlerTagType.BYPASS_SLEEP]: BattlerTag;
[BattlerTagType.IGNORE_FLYING]: GroundedTag;
[BattlerTagType.ROOSTED]: RoostedTag;
[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}.
*/
// 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
}
/**

View File

@ -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",

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 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;
}
/**

View File

@ -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
});
});

View File

@ -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]);