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 { PokemonType } from "#enums/pokemon-type";
|
||||
import type { SpeciesId } from "#enums/species-id";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import type { AttackMoveResult } from "#types/attack-move-result";
|
||||
import type { IllusionData } from "#types/illusion-data";
|
||||
import type { TurnMove } from "#types/turn-move";
|
||||
@ -326,6 +327,14 @@ export class PokemonTurnData {
|
||||
public switchedInThisTurn = false;
|
||||
public failedRunAway = 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.
|
||||
* 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) {
|
||||
// Status-overriding moves (i.e. Rest) fail if their respective status already exists;
|
||||
// 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
|
||||
return false;
|
||||
}
|
||||
@ -4955,6 +4955,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
|
||||
if (overrideStatus) {
|
||||
this.resetStatus(false);
|
||||
} else {
|
||||
this.turnData.pendingStatus = effect;
|
||||
}
|
||||
|
||||
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.
|
||||
* @param effect - The {@linkcode StatusEffect} to set
|
||||
* @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.
|
||||
*/
|
||||
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 sleepTurnsRemaining - The number of turns to inflict sleep for; defaults to a random number between 2 and 4
|
||||
* @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.
|
||||
*/
|
||||
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
|
||||
* and is unused for all non-sleep Statuses
|
||||
* @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.
|
||||
*/
|
||||
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
|
||||
* and is unused for all non-sleep Statuses
|
||||
* @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.
|
||||
* @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,
|
||||
sleepTurnsRemaining = effect !== StatusEffect.SLEEP ? 0 : this.randBattleSeedIntRange(2, 4),
|
||||
): void {
|
||||
// Reset any pending status
|
||||
this.turnData.pendingStatus = StatusEffect.NONE;
|
||||
switch (effect) {
|
||||
case StatusEffect.POISON:
|
||||
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