src/dynamic-queue-manager.ts
Rama beta · réplica de lectura del repositorio original.
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<PhaseString, PriorityQueue<Phase>>;
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<T extends PhaseString>(name: T, condition: PhaseConditionFunc<T> = () => true): boolean {
return !!(this.dynamicPhaseMap.get(name) as PriorityQueue<PhaseMap[T]> | 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<T extends PhaseString>(name: T, condition: PhaseConditionFunc<T> = () => true): boolean {
return !!(this.dynamicPhaseMap.get(name) as PriorityQueue<PhaseMap[T]> | 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);
}
}