mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-09-23 06:53:27 +02:00
[Bug] Fix status effects overwriting each other (#6392)
* Ensure status effects from same source interaction cannot override each other * Update test/status-effects/general-status-effect.test.ts Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com>
This commit is contained in:
parent
ae58f9de4f
commit
049932c001
@ -11,6 +11,7 @@ import type { MoveId } from "#enums/move-id";
|
|||||||
import type { Nature } from "#enums/nature";
|
import type { Nature } from "#enums/nature";
|
||||||
import type { PokemonType } from "#enums/pokemon-type";
|
import type { PokemonType } from "#enums/pokemon-type";
|
||||||
import type { SpeciesId } from "#enums/species-id";
|
import type { SpeciesId } from "#enums/species-id";
|
||||||
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
import type { AttackMoveResult } from "#types/attack-move-result";
|
import type { AttackMoveResult } from "#types/attack-move-result";
|
||||||
import type { IllusionData } from "#types/illusion-data";
|
import type { IllusionData } from "#types/illusion-data";
|
||||||
import type { TurnMove } from "#types/turn-move";
|
import type { TurnMove } from "#types/turn-move";
|
||||||
@ -326,6 +327,14 @@ export class PokemonTurnData {
|
|||||||
public switchedInThisTurn = false;
|
public switchedInThisTurn = false;
|
||||||
public failedRunAway = false;
|
public failedRunAway = false;
|
||||||
public joinedRound = false;
|
public joinedRound = false;
|
||||||
|
/** Tracker for a pending status effect
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* Set whenever {@linkcode Pokemon#trySetStatus} succeeds in order to prevent subsequent status effects
|
||||||
|
* from being applied. Necessary because the status is not actually set until the {@linkcode ObtainStatusEffectPhase} runs,
|
||||||
|
* which may not happen before another status effect is attempted to be applied.
|
||||||
|
*/
|
||||||
|
public pendingStatus: StatusEffect = StatusEffect.NONE;
|
||||||
/**
|
/**
|
||||||
* The amount of times this Pokemon has acted again and used a move in the current turn.
|
* The amount of times this Pokemon has acted again and used a move in the current turn.
|
||||||
* Used to make sure multi-hits occur properly when the user is
|
* Used to make sure multi-hits occur properly when the user is
|
||||||
|
@ -4803,7 +4803,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
if (effect !== StatusEffect.FAINT) {
|
if (effect !== StatusEffect.FAINT) {
|
||||||
// Status-overriding moves (i.e. Rest) fail if their respective status already exists;
|
// Status-overriding moves (i.e. Rest) fail if their respective status already exists;
|
||||||
// all other moves fail if the target already has _any_ status
|
// all other moves fail if the target already has _any_ status
|
||||||
if (overrideStatus ? this.status?.effect === effect : this.status) {
|
if (overrideStatus ? this.status?.effect === effect : this.status || this.turnData.pendingStatus) {
|
||||||
this.queueStatusImmuneMessage(quiet, overrideStatus ? "overlap" : "other"); // having different status displays generic fail message
|
this.queueStatusImmuneMessage(quiet, overrideStatus ? "overlap" : "other"); // having different status displays generic fail message
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -4955,6 +4955,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
if (overrideStatus) {
|
if (overrideStatus) {
|
||||||
this.resetStatus(false);
|
this.resetStatus(false);
|
||||||
|
} else {
|
||||||
|
this.turnData.pendingStatus = effect;
|
||||||
}
|
}
|
||||||
|
|
||||||
globalScene.phaseManager.unshiftNew(
|
globalScene.phaseManager.unshiftNew(
|
||||||
@ -4974,6 +4976,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
* Set this Pokemon's {@linkcode status | non-volatile status condition} to the specified effect.
|
* Set this Pokemon's {@linkcode status | non-volatile status condition} to the specified effect.
|
||||||
* @param effect - The {@linkcode StatusEffect} to set
|
* @param effect - The {@linkcode StatusEffect} to set
|
||||||
* @remarks
|
* @remarks
|
||||||
|
* Clears this pokemon's `pendingStatus` in its {@linkcode Pokemon.turnData | turnData}.
|
||||||
|
*
|
||||||
* ⚠️ This method does **not** check for feasibility; that is the responsibility of the caller.
|
* ⚠️ This method does **not** check for feasibility; that is the responsibility of the caller.
|
||||||
*/
|
*/
|
||||||
doSetStatus(effect: Exclude<StatusEffect, StatusEffect.SLEEP>): void;
|
doSetStatus(effect: Exclude<StatusEffect, StatusEffect.SLEEP>): void;
|
||||||
@ -4982,6 +4986,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
* @param effect - {@linkcode StatusEffect.SLEEP}
|
* @param effect - {@linkcode StatusEffect.SLEEP}
|
||||||
* @param sleepTurnsRemaining - The number of turns to inflict sleep for; defaults to a random number between 2 and 4
|
* @param sleepTurnsRemaining - The number of turns to inflict sleep for; defaults to a random number between 2 and 4
|
||||||
* @remarks
|
* @remarks
|
||||||
|
* Clears this pokemon's `pendingStatus` in its {@linkcode Pokemon#turnData}.
|
||||||
|
*
|
||||||
* ⚠️ This method does **not** check for feasibility; that is the responsibility of the caller.
|
* ⚠️ This method does **not** check for feasibility; that is the responsibility of the caller.
|
||||||
*/
|
*/
|
||||||
doSetStatus(effect: StatusEffect.SLEEP, sleepTurnsRemaining?: number): void;
|
doSetStatus(effect: StatusEffect.SLEEP, sleepTurnsRemaining?: number): void;
|
||||||
@ -4991,6 +4997,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
* @param sleepTurnsRemaining - The number of turns to inflict sleep for; defaults to a random number between 2 and 4
|
* @param sleepTurnsRemaining - The number of turns to inflict sleep for; defaults to a random number between 2 and 4
|
||||||
* and is unused for all non-sleep Statuses
|
* and is unused for all non-sleep Statuses
|
||||||
* @remarks
|
* @remarks
|
||||||
|
* Clears this pokemon's `pendingStatus` in its {@linkcode Pokemon#turnData}.
|
||||||
|
*
|
||||||
* ⚠️ This method does **not** check for feasibility; that is the responsibility of the caller.
|
* ⚠️ This method does **not** check for feasibility; that is the responsibility of the caller.
|
||||||
*/
|
*/
|
||||||
doSetStatus(effect: StatusEffect, sleepTurnsRemaining?: number): void;
|
doSetStatus(effect: StatusEffect, sleepTurnsRemaining?: number): void;
|
||||||
@ -5000,6 +5008,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
* @param sleepTurnsRemaining - The number of turns to inflict sleep for; defaults to a random number between 2 and 4
|
* @param sleepTurnsRemaining - The number of turns to inflict sleep for; defaults to a random number between 2 and 4
|
||||||
* and is unused for all non-sleep Statuses
|
* and is unused for all non-sleep Statuses
|
||||||
* @remarks
|
* @remarks
|
||||||
|
* Clears this pokemon's `pendingStatus` in its {@linkcode Pokemon#turnData}.
|
||||||
|
*
|
||||||
* ⚠️ This method does **not** check for feasibility; that is the responsibility of the caller.
|
* ⚠️ This method does **not** check for feasibility; that is the responsibility of the caller.
|
||||||
* @todo Make this and all related fields private and change tests to use a field-based helper or similar
|
* @todo Make this and all related fields private and change tests to use a field-based helper or similar
|
||||||
*/
|
*/
|
||||||
@ -5007,6 +5017,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
effect: StatusEffect,
|
effect: StatusEffect,
|
||||||
sleepTurnsRemaining = effect !== StatusEffect.SLEEP ? 0 : this.randBattleSeedIntRange(2, 4),
|
sleepTurnsRemaining = effect !== StatusEffect.SLEEP ? 0 : this.randBattleSeedIntRange(2, 4),
|
||||||
): void {
|
): void {
|
||||||
|
// Reset any pending status
|
||||||
|
this.turnData.pendingStatus = StatusEffect.NONE;
|
||||||
switch (effect) {
|
switch (effect) {
|
||||||
case StatusEffect.POISON:
|
case StatusEffect.POISON:
|
||||||
case StatusEffect.TOXIC:
|
case StatusEffect.TOXIC:
|
||||||
|
60
test/status-effects/general-status-effect.test.ts
Normal file
60
test/status-effects/general-status-effect.test.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { allAbilities } from "#data/data-lists";
|
||||||
|
import { AbilityId } from "#enums/ability-id";
|
||||||
|
import { MoveId } from "#enums/move-id";
|
||||||
|
import { SpeciesId } from "#enums/species-id";
|
||||||
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
|
import { ObtainStatusEffectPhase } from "#phases/obtain-status-effect-phase";
|
||||||
|
import { GameManager } from "#test/test-utils/game-manager";
|
||||||
|
import type { PostAttackContactApplyStatusEffectAbAttr } from "#types/ability-types";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
|
||||||
|
|
||||||
|
describe("Status Effects - General", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.override
|
||||||
|
.battleStyle("single")
|
||||||
|
.enemyLevel(5)
|
||||||
|
.enemySpecies(SpeciesId.MAGIKARP)
|
||||||
|
.enemyAbility(AbilityId.BALL_FETCH)
|
||||||
|
.enemyMoveset(MoveId.SPLASH)
|
||||||
|
.ability(AbilityId.BALL_FETCH);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("multiple status effects from the same interaction should not overwrite each other", async () => {
|
||||||
|
game.override.ability(AbilityId.POISON_TOUCH).moveset([MoveId.NUZZLE]);
|
||||||
|
await game.classicMode.startBattle([SpeciesId.PIKACHU]);
|
||||||
|
|
||||||
|
// Force poison touch to always apply
|
||||||
|
vi.spyOn(
|
||||||
|
allAbilities[AbilityId.POISON_TOUCH].getAttrs(
|
||||||
|
"PostAttackContactApplyStatusEffectAbAttr",
|
||||||
|
// expose chance, which is private, for testing purpose, but keep type safety otherwise
|
||||||
|
)[0] as unknown as Omit<PostAttackContactApplyStatusEffectAbAttr, "chance"> & { chance: number },
|
||||||
|
"chance",
|
||||||
|
"get",
|
||||||
|
).mockReturnValue(100);
|
||||||
|
const statusEffectPhaseSpy = vi.spyOn(ObtainStatusEffectPhase.prototype, "start");
|
||||||
|
|
||||||
|
game.move.select(MoveId.NUZZLE);
|
||||||
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
|
expect(statusEffectPhaseSpy).toHaveBeenCalledOnce();
|
||||||
|
const enemy = game.field.getEnemyPokemon();
|
||||||
|
// This test does not care which status effect is applied, as long as one is.
|
||||||
|
expect(enemy.status?.effect).toBeOneOf([StatusEffect.POISON, StatusEffect.PARALYSIS]);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user