import type { Phase } from "#app/phase"; import type { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier"; import type { Pokemon } from "#field/pokemon"; import type { MovePhase } from "#phases/move-phase"; import { MovePhasePriorityQueue } from "#queues/move-phase-priority-queue"; import { DynamicPhasePriorityQueue } from "#queues/pokemon-phase-priority-queue"; import { PostSummonPhasePriorityQueue } from "#queues/post-summon-phase-priority-queue"; import type { PriorityQueue } from "#queues/priority-queue"; import type { DynamicPhase, PhaseConditionFunc, PhaseMap, PhaseString } from "#types/phase-types"; // TODO: might be easier to define which phases should be dynamic instead /** All phases which have defined a `getPokemon` method but should not be sorted dynamically */ const nonDynamicPokemonPhases: readonly PhaseString[] = [ "SummonPhase", "CommandPhase", "LearnMovePhase", "MoveEffectPhase", "MoveEndPhase", "FaintPhase", "DamageAnimPhase", "VictoryPhase", "PokemonHealPhase", "WeatherEffectPhase", "ShowAbilityPhase", "HideAbilityPhase", "ExpPhase", "ShowPartyExpBarPhase", "HidePartyExpBarPhase", ] as const; /** * The dynamic queue manager holds priority queues for phases which are queued as dynamic. * * Dynamic phases are generally those which hold a pokemon and are unshifted, not pushed. \ * Queues work by sorting their entries in speed order (and possibly with more complex ordering) before each time a phase is popped. * * As the holder, this structure is also used to access and modify queued phases. * This is mostly used in redirection, cancellation, etc. of {@linkcode MovePhase}s. */ export class DynamicQueueManager { /** A Map matching `Phase` names to their corresponding priority queues. */ private readonly dynamicPhaseMap: Map>; constructor() { this.dynamicPhaseMap = new Map(); // PostSummon and Move phases have specialized queues this.dynamicPhaseMap.set("PostSummonPhase", new PostSummonPhasePriorityQueue()); this.dynamicPhaseMap.set("MovePhase", new MovePhasePriorityQueue()); } /** Remove all phases from the manager. */ public clearQueues(): void { for (const queue of this.dynamicPhaseMap.values()) { queue.clear(); } } /** * Adds a new phase to the manager and creates the priority queue for it if one does not exist. * @param phase - The {@linkcode Phase} to add * @returns `true` if the phase was added, or `false` if it is not dynamic */ public queueDynamicPhase(phase: Phase): phase is DynamicPhase { if (!this.isDynamicPhase(phase)) { return false; } if (!this.dynamicPhaseMap.has(phase.phaseName)) { this.dynamicPhaseMap.set(phase.phaseName, new DynamicPhasePriorityQueue()); } this.dynamicPhaseMap.get(phase.phaseName)!.push(phase); return true; } /** * Remove a {@linkcode Phase} from its corresponding queue and return it. * @param name - The {@linkcode PhaseString | name} of the Phase to retrieve * @returns The highest-priority `Phase` of the given type, or `undefined` if none of the specified type exist */ public popNextPhase(name: PhaseString): Phase | undefined { return this.dynamicPhaseMap.get(name)?.pop(); } /** * Determines if there is a queued dynamic {@linkcode Phase} meeting the conditions * @param name - The {@linkcode PhaseString | name} of the Phase to search for * @param condition - An optional {@linkcode PhaseConditionFunc} to add conditions to the search * @returns Whether a matching phase exists */ public exists(name: T, condition: PhaseConditionFunc = () => true): boolean { return !!(this.dynamicPhaseMap.get(name) as PriorityQueue | undefined)?.has(condition); } /** * Finds and removes a single queued {@linkcode Phase} * @param name - The {@linkcode PhaseString | name} of the Phase to search for * @param phaseFilter - An optional {@linkcode PhaseConditionFunc} to specify conditions for the phase * @returns Whether a removal occurred */ public removePhase(name: T, condition: PhaseConditionFunc = () => true): boolean { return !!(this.dynamicPhaseMap.get(name) as PriorityQueue | undefined)?.remove(condition); } /** * Sets the timing modifier of a move (i.e. to force it first or last) * @param condition - A {@linkcode PhaseConditionFunc} to specify conditions for the move * @param modifier - The {@linkcode MovePhaseTimingModifier} to switch the move to */ public setMoveTimingModifier(condition: PhaseConditionFunc<"MovePhase">, modifier: MovePhaseTimingModifier): void { this.getMovePhaseQueue().setTimingModifier(condition, modifier); } /** * Redirects moves which were targeted at a {@linkcode Pokemon} that has been removed * @param removedPokemon - The removed {@linkcode Pokemon} * @param allyPokemon - The ally of the removed pokemon */ public redirectMoves(removedPokemon: Pokemon, allyPokemon: Pokemon): void { this.getMovePhaseQueue().redirectMoves(removedPokemon, allyPokemon); } /** * Find and return the first {@linkcode MovePhase} meeting the given condition. * @param phaseCondition - The {@linkcode PhaseConditionFunc | condition} function used to retrieve the phase * @returns The retrieved `MovePhase`, or `undefined` if none meet the criteria. */ public getMovePhase(condition: PhaseConditionFunc<"MovePhase">): MovePhase | undefined { return this.getMovePhaseQueue().find(condition); } /** * Find and cancel the first {@linkcode MovePhase} meeting the given condition. * @param phaseCondition - The {@linkcode PhaseConditionFunc | condition} function used to retrieve the phase */ public cancelMovePhase(condition: PhaseConditionFunc<"MovePhase">): void { this.getMovePhaseQueue().cancelMove(condition); } /** * @returns An in-order array of {@linkcode Pokemon}, representing the turn order as played out in the most recent turn */ public getLastTurnOrder(): Pokemon[] { return this.getMovePhaseQueue().getTurnOrder(); } /** Clears the stored `Move` turn order */ public clearLastTurnOrder(): void { this.getMovePhaseQueue().clearTurnOrder(); } /** Internal helper to get the {@linkcode MovePhasePriorityQueue} */ private getMovePhaseQueue(): MovePhasePriorityQueue { return this.dynamicPhaseMap.get("MovePhase") as MovePhasePriorityQueue; } /** * Internal helper to determine if a phase is dynamic. * @param phase - The {@linkcode Phase} to check * @returns Whether `phase` is dynamic. * @privateRemarks * Currently, this checks that `phase` has a `getPokemon` method * and is not blacklisted in `nonDynamicPokemonPhases`. */ private isDynamicPhase(phase: Phase): phase is DynamicPhase { return typeof (phase as any).getPokemon === "function" && !nonDynamicPokemonPhases.includes(phase.phaseName); } }