From 0121589d6f9e49fc7a3f2ce47bf77e1dc47de453 Mon Sep 17 00:00:00 2001 From: Dean Date: Sat, 14 Jun 2025 17:40:18 -0700 Subject: [PATCH] Add dynamic queue manager --- src/field/pokemon.ts | 2 + src/phase-manager.ts | 57 ++++----------------- src/phases/activate-priority-queue-phase.ts | 10 ++-- src/phases/move-phase.ts | 6 +-- src/phases/turn-start-phase.ts | 2 +- src/queues/dynamic-queue-manager.ts | 35 +++++++++++++ 6 files changed, 57 insertions(+), 55 deletions(-) create mode 100644 src/queues/dynamic-queue-manager.ts diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 834c65437af..f12b2d44fb7 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -193,6 +193,7 @@ import { AiType } from "#enums/ai-type"; import type { MoveResult } from "#enums/move-result"; import { PokemonMove } from "#app/data/moves/pokemon-move"; import type { AbAttrMap, AbAttrString } from "#app/@types/ability-types"; +import type { TurnCommand } from "#app/battle"; /** Base typeclass for damage parameter methods, used for DRY */ type damageParams = { @@ -6868,6 +6869,7 @@ export class PokemonWaveData { * Resets at the start of a new turn, as well as on switch. */ export class PokemonTurnData { + public turnCommand?: TurnCommand; public flinched = false; public acted = false; /** How many times the current move should hit the target(s) */ diff --git a/src/phase-manager.ts b/src/phase-manager.ts index 8c22a45758c..5e5a5491db6 100644 --- a/src/phase-manager.ts +++ b/src/phase-manager.ts @@ -12,9 +12,8 @@ import { CheckStatusEffectPhase } from "#app/phases/check-status-effect-phase"; import { CheckSwitchPhase } from "#app/phases/check-switch-phase"; import { CommandPhase } from "#app/phases/command-phase"; import { CommonAnimPhase } from "#app/phases/common-anim-phase"; -import { coerceArray, type Constructor } from "#app/utils/common"; +import { coerceArray } from "#app/utils/common"; import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; -import type { DynamicPhaseType } from "#enums/dynamic-phase-type"; import { EggHatchPhase } from "#app/phases/egg-hatch-phase"; import { EggLapsePhase } from "#app/phases/egg-lapse-phase"; import { EggSummaryPhase } from "#app/phases/egg-summary-phase"; @@ -58,7 +57,6 @@ import { NextEncounterPhase } from "#app/phases/next-encounter-phase"; import { ObtainStatusEffectPhase } from "#app/phases/obtain-status-effect-phase"; import { PartyExpPhase } from "#app/phases/party-exp-phase"; import { PartyHealPhase } from "#app/phases/party-heal-phase"; -import { type PhasePriorityQueue, PostSummonPhasePriorityQueue } from "#app/data/phase-priority-queue"; import { PokemonAnimPhase } from "#app/phases/pokemon-anim-phase"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { PokemonTransformPhase } from "#app/phases/pokemon-transform-phase"; @@ -99,6 +97,7 @@ import { UnavailablePhase } from "#app/phases/unavailable-phase"; import { UnlockPhase } from "#app/phases/unlock-phase"; import { VictoryPhase } from "#app/phases/victory-phase"; import { WeatherEffectPhase } from "#app/phases/weather-effect-phase"; +import { DynamicQueueManager } from "#app/queues/dynamic-queue-manager"; /** * Manager for phases used by battle scene. @@ -227,19 +226,11 @@ export class PhaseManager { private phaseQueuePrependSpliceIndex = -1; private nextCommandPhaseQueue: Phase[] = []; - /** Storage for {@linkcode PhasePriorityQueue}s which hold phases whose order dynamically changes */ - private dynamicPhaseQueues: PhasePriorityQueue[]; - /** Parallel array to {@linkcode dynamicPhaseQueues} - matches phase types to their queues */ - private dynamicPhaseTypes: Constructor[]; + private dynamicQueueManager = new DynamicQueueManager(); private currentPhase: Phase | null = null; private standbyPhase: Phase | null = null; - constructor() { - this.dynamicPhaseQueues = [new PostSummonPhasePriorityQueue()]; - this.dynamicPhaseTypes = [PostSummonPhase]; - } - /* Phase Functions */ getCurrentPhase(): Phase | null { return this.currentPhase; @@ -269,7 +260,7 @@ export class PhaseManager { * @param defer boolean on which queue to add to, defaults to false, and adds to phaseQueue */ pushPhase(phase: Phase, defer = false): void { - if (this.getDynamicPhaseType(phase) !== undefined) { + if (this.dynamicQueueManager.isDynamicPhase(phase.phaseName)) { this.pushDynamicPhase(phase); } else { (!defer ? this.phaseQueue : this.nextCommandPhaseQueue).push(phase); @@ -302,7 +293,7 @@ export class PhaseManager { for (const queue of [this.phaseQueue, this.phaseQueuePrepend, this.conditionalQueue, this.nextCommandPhaseQueue]) { queue.splice(0, queue.length); } - this.dynamicPhaseQueues.forEach(queue => queue.clear()); + this.dynamicQueueManager.clearQueues(); this.currentPhase = null; this.standbyPhase = null; this.clearPhaseQueueSplice(); @@ -470,22 +461,6 @@ export class PhaseManager { return false; } - /** - * Checks a phase and returns the matching {@linkcode DynamicPhaseType}, or undefined if it does not match one - * @param phase The phase to check - * @returns The corresponding {@linkcode DynamicPhaseType} or `undefined` - */ - public getDynamicPhaseType(phase: Phase | null): DynamicPhaseType | undefined { - let phaseType: DynamicPhaseType | undefined; - this.dynamicPhaseTypes.forEach((cls, index) => { - if (phase instanceof cls) { - phaseType = index; - } - }); - - return phaseType; - } - /** * Pushes a phase onto its corresponding dynamic queue and marks the activation point in {@linkcode phaseQueue} * @@ -493,21 +468,16 @@ export class PhaseManager { * @param phase The phase to push */ public pushDynamicPhase(phase: Phase): void { - const type = this.getDynamicPhaseType(phase); - if (type === undefined) { - return; - } - - this.pushPhase(new ActivatePriorityQueuePhase(type)); - this.dynamicPhaseQueues[type].push(phase); + this.pushNew("ActivatePriorityQueuePhase", phase.phaseName); + this.dynamicQueueManager.queueDynamicPhase(phase); } /** * Unshifts the top phase from the corresponding dynamic queue onto {@linkcode phaseQueue} * @param type {@linkcode DynamicPhaseType} The type of dynamic phase to start */ - public startDynamicPhaseType(type: DynamicPhaseType): void { - const phase = this.dynamicPhaseQueues[type].pop(); + public startNextDynamicPhaseOfType(type: PhaseString): void { + const phase = this.dynamicQueueManager.popNextPhaseOfType(type); if (phase) { this.unshiftPhase(phase); } @@ -523,13 +493,8 @@ export class PhaseManager { * @returns */ public startDynamicPhase(phase: Phase): void { - const type = this.getDynamicPhaseType(phase); - if (type === undefined) { - return; - } - - this.unshiftPhase(new ActivatePriorityQueuePhase(type)); - this.dynamicPhaseQueues[type].push(phase); + this.unshiftNew("ActivatePriorityQueuePhase", phase.phaseName); + this.dynamicQueueManager.queueDynamicPhase(phase); } /** diff --git a/src/phases/activate-priority-queue-phase.ts b/src/phases/activate-priority-queue-phase.ts index df42c491676..b58e13be2e1 100644 --- a/src/phases/activate-priority-queue-phase.ts +++ b/src/phases/activate-priority-queue-phase.ts @@ -1,23 +1,23 @@ -import type { DynamicPhaseType } from "#enums/dynamic-phase-type"; +import type { PhaseString } from "#app/@types/phase-types"; import { globalScene } from "#app/global-scene"; import { Phase } from "#app/phase"; export class ActivatePriorityQueuePhase extends Phase { public readonly phaseName = "ActivatePriorityQueuePhase"; - private type: DynamicPhaseType; + private readonly type: PhaseString; - constructor(type: DynamicPhaseType) { + constructor(type: PhaseString) { super(); this.type = type; } override start() { super.start(); - globalScene.phaseManager.startDynamicPhaseType(this.type); + globalScene.phaseManager.startNextDynamicPhaseOfType(this.type); this.end(); } - public getType(): DynamicPhaseType { + public getType(): PhaseString { return this.type; } } diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index d72c7396f1f..2c36b922a7b 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -19,7 +19,6 @@ import type Pokemon from "#app/field/pokemon"; import { MoveResult } from "#enums/move-result"; import { getPokemonNameWithAffix } from "#app/messages"; import Overrides from "#app/overrides"; -import { BattlePhase } from "#app/phases/battle-phase"; import { NumberHolder } from "#app/utils/common"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagType } from "#enums/arena-tag-type"; @@ -28,8 +27,9 @@ import { MoveId } from "#enums/move-id"; import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; import { frenzyMissFunc } from "#app/data/moves/move-utils"; +import { PokemonPhase } from "#app/phases/pokemon-phase"; -export class MovePhase extends BattlePhase { +export class MovePhase extends PokemonPhase { public readonly phaseName = "MovePhase"; protected _pokemon: Pokemon; protected _move: PokemonMove; @@ -81,7 +81,7 @@ export class MovePhase extends BattlePhase { reflected = false, forcedLast = false, ) { - super(); + super(pokemon.getBattlerIndex()); this.pokemon = pokemon; this.targets = targets; diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index 6f062cb5fbe..14e8ec906ec 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -69,7 +69,7 @@ export class TurnStartPhase extends FieldPhase { applyAbAttrs("BypassSpeedChanceAbAttr", p, null, false, bypassSpeed); applyAbAttrs("PreventBypassSpeedChanceAbAttr", p, null, false, bypassSpeed, canCheckHeldItems); if (canCheckHeldItems.value) { - globalScene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed); + globalScene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p); } battlerBypassSpeed[p.getBattlerIndex()] = bypassSpeed; }); diff --git a/src/queues/dynamic-queue-manager.ts b/src/queues/dynamic-queue-manager.ts new file mode 100644 index 00000000000..3eebf107ed3 --- /dev/null +++ b/src/queues/dynamic-queue-manager.ts @@ -0,0 +1,35 @@ +import type { PhaseString } from "#app/@types/phase-types"; +import type { Phase } from "#app/phase"; +import type { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; +import { MovePhasePriorityQueue } from "#app/queues/move-phase-priority-queue"; +import type { PhasePriorityQueue } from "#app/queues/phase-priority-queue"; +import { PokemonPhasePriorityQueue } from "#app/queues/pokemon-phase-priority-queue"; +import { PostSummonPhasePriorityQueue } from "#app/queues/post-summon-phase-priority-queue"; +export class DynamicQueueManager { + private dynamicPhaseMap: Map>; + + constructor() { + this.dynamicPhaseMap = new Map(); + this.dynamicPhaseMap.set("SwitchSummonPhase", new PokemonPhasePriorityQueue()); + this.dynamicPhaseMap.set("PostSummonPhase", new PostSummonPhasePriorityQueue()); + this.dynamicPhaseMap.set("MovePhase", new MovePhasePriorityQueue()); + } + + public clearQueues(): void { + for (const queue of this.dynamicPhaseMap.values()) { + queue.clear(); + } + } + + public queueDynamicPhase(phase: Phase): void { + this.dynamicPhaseMap.get(phase.phaseName)?.push(phase); + } + + public popNextPhaseOfType(type: PhaseString): Phase | undefined { + return this.dynamicPhaseMap.get(type)?.pop(); + } + + public isDynamicPhase(type: PhaseString): boolean { + return this.dynamicPhaseMap.has(type); + } +}