Ctrl K
ComunidadGuías…

src/dynamic-queue-manager.ts

Rama beta · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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);
  }
}