From 8f4243853d962e535be23efa7369eca0b63e39e9 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Thu, 18 Dec 2025 22:11:11 -0500 Subject: [PATCH] [Refactor] Make `phaseManager#unshiftPhase` and `#pushPhase` variadic https://github.com/pagefaultgames/pokerogue/pull/6776 --- src/phase-manager.ts | 32 +++++++++++++------ .../a-trainers-test-encounter.test.ts | 6 ++-- .../dancing-lessons-encounter.test.ts | 12 +++---- .../uncommon-breed-encounter.test.ts | 6 ++-- 4 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/phase-manager.ts b/src/phase-manager.ts index 927c343c9f2..5f1637d1378 100644 --- a/src/phase-manager.ts +++ b/src/phase-manager.ts @@ -113,6 +113,7 @@ import { UnlockPhase } from "#phases/unlock-phase"; import { VictoryPhase } from "#phases/victory-phase"; import { WeatherEffectPhase } from "#phases/weather-effect-phase"; import type { PhaseConditionFunc, PhaseMap, PhaseString } from "#types/phase-types"; +import type { NonEmptyTuple } from "type-fest"; /** * Object that holds all of the phase constructors. @@ -275,21 +276,34 @@ export class PhaseManager { } /** - * Adds a phase to the end of the queue - * @param phase - The {@linkcode Phase} to add + * Add one or more Phases to the end of the queue. + * They will run once all phases already in the queue have ended. + * @param phases - One or more {@linkcode Phase}s to add */ - public pushPhase(phase: Phase): void { - this.phaseQueue.pushPhase(this.checkDynamic(phase)); + public pushPhase(...phases: NonEmptyTuple): void { + for (const phase of phases) { + this.phaseQueue.pushPhase(this.checkDynamic(phase)); + } } /** - * Queue a phase to be run immediately after the current phase finishes. \ + * Queue one or more phases to be run immediately after the current phase finishes. \ * Unshifted phases are run in FIFO order if multiple are queued during a single phase's execution. - * @param phase - The {@linkcode Phase} to add + * @param phases - One or more {@linkcode Phase}s to add + * @privateRemarks + * Any newly-unshifted `MovePhase`s will be queued after the next `MoveEndPhase`. */ - public unshiftPhase(phase: Phase): void { - const toAdd = this.checkDynamic(phase); - phase.is("MovePhase") ? this.phaseQueue.addAfter(toAdd, "MoveEndPhase") : this.phaseQueue.addPhase(toAdd); + // NB: I'd like to restrict this to only allow passing 1 `MovePhase` at a time, but this causes TS to + // flip the hell out with `Parameters`... + public unshiftPhase(...phases: NonEmptyTuple): void { + for (const phase of phases) { + const toAdd = this.checkDynamic(phase); + if (phase.is("MovePhase")) { + this.phaseQueue.addAfter(toAdd, "MoveEndPhase"); + } else { + this.phaseQueue.addPhase(toAdd); + } + } } /** diff --git a/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts b/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts index 8dc4348adae..55ffa65d8f0 100644 --- a/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts +++ b/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts @@ -9,7 +9,7 @@ import { ATrainersTestEncounter } from "#mystery-encounters/a-trainers-test-enco import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; import { HUMAN_TRANSITABLE_BIOMES } from "#mystery-encounters/mystery-encounters"; -import { PartyHealPhase } from "#phases/party-heal-phase"; +import type { PartyHealPhase } from "#phases/party-heal-phase"; import { SelectModifierPhase } from "#phases/select-modifier-phase"; import { runMysteryEncounterToEnd, @@ -165,8 +165,8 @@ describe("A Trainer's Test - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.A_TRAINERS_TEST, defaultParty); await runMysteryEncounterToEnd(game, 2); - const partyHealPhases = phaseSpy.mock.calls.filter(p => p[0] instanceof PartyHealPhase).map(p => p[0]); - expect(partyHealPhases.length).toBe(1); + const partyHealPhases = phaseSpy.mock.calls.flat().filter((p): p is PartyHealPhase => p.is("PartyHealPhase")); + expect(partyHealPhases).toHaveLength(1); }); it("Should reward the player with a Rare egg", async () => { diff --git a/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts b/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts index 81a2fc7463c..714c7706e41 100644 --- a/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts +++ b/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts @@ -9,8 +9,8 @@ import { UiMode } from "#enums/ui-mode"; import { DancingLessonsEncounter } from "#mystery-encounters/dancing-lessons-encounter"; import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; -import { LearnMovePhase } from "#phases/learn-move-phase"; -import { MovePhase } from "#phases/move-phase"; +import type { LearnMovePhase } from "#phases/learn-move-phase"; +import type { MovePhase } from "#phases/move-phase"; import { MysteryEncounterPhase } from "#phases/mystery-encounter-phases"; import { runMysteryEncounterToEnd, @@ -110,9 +110,9 @@ describe("Dancing Lessons - Mystery Encounter", () => { const moveset = enemyField[0].moveset.map(m => m.moveId); expect(moveset.some(m => m === MoveId.REVELATION_DANCE)).toBeTruthy(); - const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof MovePhase).map(p => p[0]); - expect(movePhases.length).toBe(1); - expect(movePhases.filter(p => (p as MovePhase).move.moveId === MoveId.REVELATION_DANCE).length).toBe(1); // Revelation Dance used before battle + const movePhases = phaseSpy.mock.calls.flat().filter((p): p is MovePhase => p.is("MovePhase")); + expect(movePhases).toHaveLength(1); + expect(movePhases[0].move.moveId).toBe(MoveId.REVELATION_DANCE); // Revelation Dance used before battle }); it("should have a Baton in the rewards after battle", async () => { @@ -159,7 +159,7 @@ describe("Dancing Lessons - Mystery Encounter", () => { game.field.getPlayerPokemon().moveset = []; await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1 }); - const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof LearnMovePhase).map(p => p[0]); + const movePhases = phaseSpy.mock.calls.filter(p => p.some(i => i.is("LearnMovePhase"))).map(p => p[0]); expect(movePhases.length).toBe(1); expect(movePhases.filter(p => (p as LearnMovePhase)["moveId"] === MoveId.REVELATION_DANCE).length).toBe(1); // Revelation Dance taught to pokemon }); diff --git a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts index 021aa336205..cbce233f90b 100644 --- a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts +++ b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts @@ -123,8 +123,10 @@ describe("Uncommon Breed - Mystery Encounter", () => { expect(enemyField.length).toBe(1); expect(enemyField[0].species.speciesId).toBe(speciesToSpawn); - const statStagePhases = unshiftPhaseSpy.mock.calls.find(p => p[0] instanceof StatStageChangePhase)?.[0] as any; - expect(statStagePhases.stats).toEqual([Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD]); + const statStagePhase = unshiftPhaseSpy.mock.calls + .flat() + .find((p): p is StatStageChangePhase => p.is("StatStageChangePhase")); + expect(statStagePhase?.["stats"]).toEqual([Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD]); // Should have used its egg move pre-battle const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof MovePhase).map(p => p[0]);