diff --git a/src/phase-manager.ts b/src/phase-manager.ts index e713c21b932..2d884476d33 100644 --- a/src/phase-manager.ts +++ b/src/phase-manager.ts @@ -217,28 +217,13 @@ export type PhaseConstructorMap = typeof PHASES; * PhaseManager is responsible for managing the phases in the battle scene */ export class PhaseManager { - /** - * A queue of yet-unexecuted {@linkcode Phase}s to be run. \ - * Each time the current phase ends, all phases from {@linkcode phaseQueuePrepend} are added - * to the front of this queue and the next phase is started. - */ + /** PhaseQueue: dequeue/remove the first element to get the next phase */ public phaseQueue: Phase[] = []; - /** - * A queue of yet-unexecuted {@linkcode Phase}s with conditions for their execution. \ - * Each entry is evaluated whenever a new phase starts, being added to the {@linkcode phaseQueue} if the condition is satisfied. - * - */ - public conditionalQueue: Array<[condition: () => boolean, phase: Phase]> = []; - /** A temporary storage of {@linkcode Phase}s */ + public conditionalQueue: Array<[() => boolean, Phase]> = []; + /** PhaseQueuePrepend: is a temp storage of what will be added to PhaseQueue */ private phaseQueuePrepend: Phase[] = []; - /** - * If set, will cause subsequent calls to {@linkcode unshiftPhase} to insert phases at this index in **LIFO** order. - * Useful for inserting Phases "out of order". - * - * Is cleared whenever a phase ends, or when {@linkcode clearPhaseQueueSplice} is called. - * @defaultValue `-1` - */ + /** overrides default of inserting phases to end of phaseQueuePrepend array. Useful for inserting Phases "out of order" */ private phaseQueuePrependSpliceIndex = -1; private nextCommandPhaseQueue: Phase[] = []; @@ -247,7 +232,6 @@ export class PhaseManager { /** Parallel array to {@linkcode dynamicPhaseQueues} - matches phase types to their queues */ private dynamicPhaseTypes: Constructor[]; - /** The currently running Phase, or `null` if none have started yet. */ private currentPhase: Phase | null = null; private standbyPhase: Phase | null = null; @@ -272,14 +256,6 @@ export class PhaseManager { } /* Phase Functions */ - - /** - * Getter function to return the currently-in-progess {@linkcode Phase}. - * @returns The current Phase, or `null` if no phase is currently running - * (due to the PhaseManager not having been started yet). - */ - // TODO: Investigate if we can drop the `null` from this - it is only ever `null` when the manager hasn't started - // (which should never happen once the animations have loaded) getCurrentPhase(): Phase | null { return this.currentPhase; } @@ -294,17 +270,18 @@ export class PhaseManager { * This method allows deferring the execution of a phase until certain conditions are met, which is useful for handling * situations like abilities and entry hazards that depend on specific game states. * - * @param phase - The {@linkcode Phase} to add to the conditional queue. + * @param phase - The phase to be added to the conditional queue. * @param condition - A function that returns a boolean indicating whether the phase should be executed. + * */ pushConditionalPhase(phase: Phase, condition: () => boolean): void { this.conditionalQueue.push([condition, phase]); } /** - * Add a phase to the end of the {@linkcode phaseQueue}. - * @param phase - The {@linkcode Phase} to be queued. - * @param defer If `true`, will add the phase to {@linkcode nextCommandPhaseQueue} instead of the normal {@linkcode phaseQueue}; default `false`. + * Adds a phase to nextCommandPhaseQueue, as long as boolean passed in is false + * @param phase {@linkcode Phase} the phase to add + * @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) { @@ -315,13 +292,8 @@ export class PhaseManager { } /** - * Adds one or more phase(s) to the **END** of {@linkcode phaseQueuePrepend}. - * If called multiple times, phases will be ran in **FIFO** order. - * @param phases - One or more {@linkcode Phase}s to add. - * @todo Find a better name for this given that "unshift" implies adding to the front. - * @remarks - * If {@linkcode phaseQueuePrependSpliceIndex} is set, the phases will be inserted at that index - * in **LIFO** order. + * Adds Phase(s) to the end of phaseQueuePrepend, or at phaseQueuePrependSpliceIndex + * @param phases {@linkcode Phase} the phase(s) to add */ unshiftPhase(...phases: Phase[]): void { if (this.phaseQueuePrependSpliceIndex === -1) { @@ -339,48 +311,36 @@ export class PhaseManager { } /** - * Clears all phase-related stuff, including all phase queues, the current and standby phases, and splice index. + * Clears all phase-related stuff, including all phase queues, the current and standby phases, and a splice index */ clearAllPhases(): void { - this.clearPhaseQueue(); - for (const queue of [this.phaseQueuePrepend, this.conditionalQueue, this.nextCommandPhaseQueue]) { + for (const queue of [this.phaseQueue, this.phaseQueuePrepend, this.conditionalQueue, this.nextCommandPhaseQueue]) { queue.splice(0, queue.length); } - this.dynamicPhaseQueues.forEach(queue => { - queue.clear(); - }); + this.dynamicPhaseQueues.forEach(queue => queue.clear()); this.currentPhase = null; this.standbyPhase = null; this.clearPhaseQueueSplice(); } /** - * Set {@linkcode phaseQueuePrependSpliceIndex} to the current length of {@linkcode phaseQueuePrepend}, - * causing subsequent calls to {@linkcode unshiftPhase} to insert phases in LIFO order. - * @see {@linkcode clearPhaseQueueSplice} to clear queue splice + * Used by function unshiftPhase(), sets index to start inserting at current length instead of the end of the array, useful if phaseQueuePrepend gets longer with Phases */ setPhaseQueueSplice(): void { this.phaseQueuePrependSpliceIndex = this.phaseQueuePrepend.length; } /** - * Reset {@linkcode phaseQueuePrependSpliceIndex} to `-1`, - * causing subsequent calls to {@linkcode unshiftPhase} to append phases at the end of {@linkcode phaseQueuePrepend} - * in FIFO order. - * @see {@linkcode setPhaseQueueSplice} to set queue splice - * @remarks - * Is called automatically upon phase end. + * Resets phaseQueuePrependSpliceIndex to -1, implies that calls to unshiftPhase will insert at end of phaseQueuePrepend */ clearPhaseQueueSplice(): void { this.phaseQueuePrependSpliceIndex = -1; } /** - * End the currently running phase and start the next one. - * We dump everything from {@linkcode phaseQueuePrepend} to the start of {@linkcode phaseQueue}, - * then remove the first Phase and start it. - * @remarks - * Called by {@linkcode Phase.end} by default. + * Is called by each Phase implementations "end()" by default + * We dump everything from phaseQueuePrepend to the start of of phaseQueue + * then removes first Phase and starts it */ shiftPhase(): void { if (this.standbyPhase) { @@ -389,40 +349,49 @@ export class PhaseManager { return; } - this.clearPhaseQueueSplice(); - this.phaseQueue.unshift(...this.phaseQueuePrepend); - this.phaseQueuePrepend = []; - + if (this.phaseQueuePrependSpliceIndex > -1) { + this.clearPhaseQueueSplice(); + } + if (this.phaseQueuePrepend.length) { + while (this.phaseQueuePrepend.length) { + const poppedPhase = this.phaseQueuePrepend.pop(); + if (poppedPhase) { + this.phaseQueue.unshift(poppedPhase); + } + } + } if (!this.phaseQueue.length) { this.populatePhaseQueue(); // Clear the conditionalQueue if there are no phases left in the phaseQueue this.conditionalQueue = []; } - const nextPhase = this.phaseQueue.shift(); - if (!nextPhase) { - throw new Error("No phases in queue; aborting"); - } - - this.currentPhase = nextPhase; + this.currentPhase = this.phaseQueue.shift() ?? null; const unactivatedConditionalPhases: [() => boolean, Phase][] = []; - // Check each queued conditional phase, either adding it to the end of the queue (if met) - // or keeping it on (if not). - for (const [condition, phase] of this.conditionalQueue) { - if (condition()) { - this.pushPhase(phase); + // Check if there are any conditional phases queued + while (this.conditionalQueue?.length) { + // Retrieve the first conditional phase from the queue + const conditionalPhase = this.conditionalQueue.shift(); + // Evaluate the condition associated with the phase + if (conditionalPhase?.[0]()) { + // If the condition is met, add the phase to the phase queue + this.pushPhase(conditionalPhase[1]); + } else if (conditionalPhase) { + // If the condition is not met, re-add the phase back to the front of the conditional queue + unactivatedConditionalPhases.push(conditionalPhase); } else { - unactivatedConditionalPhases.push([condition, phase]); + console.warn("condition phase is undefined/null!", conditionalPhase); } } - this.conditionalQueue = unactivatedConditionalPhases; + this.conditionalQueue.push(...unactivatedConditionalPhases); - console.log(`%cStart Phase ${this.currentPhase.phaseName}`, "color:green;"); - this.currentPhase.start(); + if (this.currentPhase) { + console.log(`%cStart Phase ${this.currentPhase.constructor.name}`, "color:green;"); + this.currentPhase.start(); + } } - // TODO: Review if we can remove this overridePhase(phase: Phase): boolean { if (this.standbyPhase) { return false; @@ -430,7 +399,7 @@ export class PhaseManager { this.standbyPhase = this.currentPhase; this.currentPhase = phase; - console.log(`%cStart Phase ${phase.phaseName}`, "color:green;"); + console.log(`%cStart Phase ${phase.constructor.name}`, "color:green;"); phase.start(); return true; @@ -439,64 +408,56 @@ export class PhaseManager { /** * Find a specific {@linkcode Phase} in the phase queue. * - * @param phaseFilter - The predicate function to use to find a queued phase - * @returns The first phase for which {@linkcode phaseFilter} returns `true`, or `undefined` if none match. + * @param phaseFilter filter function to use to find the wanted phase + * @returns the found phase or undefined if none found */ findPhase

(phaseFilter: (phase: P) => boolean): P | undefined { return this.phaseQueue.find(phaseFilter) as P | undefined; } - // TODO: This is used exclusively by encore - tryReplacePhase(phaseFilter: (phase: Phase) => boolean, phaseTarget: Phase): boolean { + tryReplacePhase(phaseFilter: (phase: Phase) => boolean, phase: Phase): boolean { const phaseIndex = this.phaseQueue.findIndex(phaseFilter); - if (phaseIndex === -1) { - return false; + if (phaseIndex > -1) { + this.phaseQueue[phaseIndex] = phase; + return true; } - this.phaseQueue[phaseIndex] = phaseTarget; - return true; + return false; } - /** - * Search for a specific phase in the {@linkcode phaseQueue} and remove the first matching result. - * @param phaseFilter - The function to filter the phase queue by - * @returns Whether a matching phase was found and removed. - */ tryRemovePhase(phaseFilter: (phase: Phase) => boolean): boolean { const phaseIndex = this.phaseQueue.findIndex(phaseFilter); - if (phaseIndex === -1) { - return false; + if (phaseIndex > -1) { + this.phaseQueue.splice(phaseIndex, 1); + return true; } - this.phaseQueue.splice(phaseIndex, 1); - return true; + return false; } /** - * Search for a specific phase in {@linkcode phaseQueuePrepend} and remove the first matching result. - * @param phaseFilter - The function to filter the phase queue by - * @returns Whether a matching phase was found and removed. + * Will search for a specific phase in {@linkcode phaseQueuePrepend} via filter, and remove the first result if a match is found. + * @param phaseFilter filter function */ tryRemoveUnshiftedPhase(phaseFilter: (phase: Phase) => boolean): boolean { const phaseIndex = this.phaseQueuePrepend.findIndex(phaseFilter); - if (phaseIndex === -1) { - return false; + if (phaseIndex > -1) { + this.phaseQueuePrepend.splice(phaseIndex, 1); + return true; } - this.phaseQueuePrepend.splice(phaseIndex, 1); - return true; + return false; } /** - * Attempt to add {@linkcode phase} directly **before** the first instance of {@linkcode target} in the {@linkcode phaseQueue}. - * If none are found, will call {@linkcode unshiftPhase()} to add the phase to the end of {@linkcode phaseQueuePrepend}. - * @param phase - The {@linkcode Phase} (single or array) to add - * @param targetPhase - The {@linkcode PhaseString | name} of the Phase to search for - * @returns Whether a {@linkcode targetPhase} was found successfully. + * Tries to add the input phase to index before target phase in the phaseQueue, else simply calls unshiftPhase() + * @param phase - The phase to be added + * @param targetPhase - The phase to search for in phaseQueue + * @returns boolean if a targetPhase was found and added */ prependToPhase(phase: Phase | Phase[], targetPhase: PhaseString): boolean { phase = coerceArray(phase); const target = PHASES[targetPhase]; const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target); - if (targetIndex > -1) { + if (targetIndex !== -1) { this.phaseQueue.splice(targetIndex, 0, ...phase); return true; } @@ -505,12 +466,11 @@ export class PhaseManager { } /** - * Attempt to add {@linkcode phase} directly **after** the first instance of {@linkcode target} in the {@linkcode phaseQueue}. - * If none are found, will call {@linkcode unshiftPhase()} to add the phase to the end of {@linkcode phaseQueuePrepend}. - * @param phase - The {@linkcode Phase} (single or array) to add - * @param targetPhase - The {@linkcode PhaseString | name} of the queued Phase to search for - * @param condition - If provided, will only consider target phases passing the condition - * @returns Whether a {@linkcode targetPhase} was found successfully + * Tries to add the input phase(s) to index after target phase in the {@linkcode phaseQueue}, else simply calls {@linkcode unshiftPhase()} + * @param phase {@linkcode Phase} the phase(s) to be added + * @param targetPhase {@linkcode Phase} the type of phase to search for in {@linkcode phaseQueue} + * @param condition Condition the target phase must meet to be appended to + * @returns `true` if a `targetPhase` was found to append to */ appendToPhase(phase: Phase | Phase[], targetPhase: PhaseString, condition?: (p: Phase) => boolean): boolean { phase = coerceArray(phase); @@ -526,9 +486,9 @@ export class PhaseManager { } /** - * Check a phase and return its matching {@linkcode DynamicPhaseType} - * @param phase - The {@linkcode Phase} to check - * @returns The corresponding {@linkcode DynamicPhaseType}, or `undefined` if it lacks one. + * 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; @@ -542,13 +502,10 @@ export class PhaseManager { } /** - * Unshift an {@linkcode ActivatePriorityQueuePhase} for {@linkcode phase}, then push {@linkcode phase} - * onto its respective dynamic queue. + * Pushes a phase onto its corresponding dynamic queue and marks the activation point in {@linkcode phaseQueue} * - * The {@linkcode ActivatePriorityQueuePhase} will run the top phase in the queue (not necessarily {@linkcode phase}) - * each time a queued phase would otherwise execute. - * @param phase - The {@linkcode Phase} to push - * @see {@linkcode unshiftDynamicPhase} Similar method that unshifts instead of pushing + * The {@linkcode ActivatePriorityQueuePhase} will run the top phase in the dynamic queue (not necessarily {@linkcode phase}) + * @param phase The phase to push */ public pushDynamicPhase(phase: Phase): void { const type = this.getDynamicPhaseType(phase); @@ -561,8 +518,8 @@ export class PhaseManager { } /** - * Unshifts the top phase from the corresponding dynamic queue onto {@linkcode phaseQueue}. - * @param type - The {@linkcode DynamicPhaseType} corresponding to the dynamic phase being started + * 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(); @@ -572,15 +529,13 @@ export class PhaseManager { } /** - * Unshift an {@linkcode ActivatePriorityQueuePhase} for {@linkcode phase}, then push {@linkcode phase} - * onto its respective dynamic queue. + * Unshifts an {@linkcode ActivatePriorityQueuePhase} for {@linkcode phase}, then pushes {@linkcode phase} to its dynamic queue * - * The {@linkcode ActivatePriorityQueuePhase} will run the top phase in the queue (not necessarily {@linkcode phase}) - * each time a queued phase would otherwise execute. + * This is the same as {@linkcode pushDynamicPhase}, except the activation phase is unshifted * - * @param phase - The {@linkcode Phase} to unshift - * @see {@linkcode pushDynamicPhase} Similar method that pushes instead of unshifting - * @todo - Consider renaming to `unshiftDymanicPhase` + * {@linkcode phase} is not guaranteed to be the next phase from the queue to run (if the queue is not empty) + * @param phase The phase to add + * @returns */ public startDynamicPhase(phase: Phase): void { const type = this.getDynamicPhaseType(phase); @@ -594,7 +549,7 @@ export class PhaseManager { /** * Adds a MessagePhase, either to PhaseQueuePrepend or nextCommandPhaseQueue - * @param message - The message to be displayed. + * @param message - string for MessagePhase * @param callbackDelay - optional param for MessagePhase constructor * @param prompt - optional param for MessagePhase constructor * @param promptDelay - optional param for MessagePhase constructor @@ -602,7 +557,7 @@ export class PhaseManager { * * @see {@linkcode MessagePhase} for more details on the parameters */ - public queueMessage( + queueMessage( message: string, callbackDelay?: number | null, prompt?: boolean | null, @@ -614,24 +569,24 @@ export class PhaseManager { // adds to the end of PhaseQueuePrepend this.unshiftPhase(phase); } else { - // remember that pushPhase adds it to nextCommandPhaseQueue + //remember that pushPhase adds it to nextCommandPhaseQueue this.pushPhase(phase); } } /** - * Queue an ability bar flyout phase. - * @param pokemon - The pokemon with the ability - * @param passive - Whether the ability is a passive - * @param show - Whether to show or hide the bar + * Queues an ability bar flyout phase + * @param pokemon The pokemon who has the ability + * @param passive Whether the ability is a passive + * @param show Whether to show or hide the bar */ public queueAbilityDisplay(pokemon: Pokemon, passive: boolean, show: boolean): void { this.unshiftPhase(show ? new ShowAbilityPhase(pokemon.getBattlerIndex(), passive) : new HideAbilityPhase()); - this.clearPhaseQueueSplice(); // TODO: Is this necessary? + this.clearPhaseQueueSplice(); } /** - * Hide the ability bar if it is currently visible. + * Hides the ability bar if it is currently visible */ public hideAbilityBar(): void { if (globalScene.abilityBar.isVisible()) { @@ -640,25 +595,24 @@ export class PhaseManager { } /** - * Moves everything from nextCommandPhaseQueue to phaseQueue (keeping order), - * then adds a new {@linkcode TurnInitPhase} to start a new turn. + * Moves everything from nextCommandPhaseQueue to phaseQueue (keeping order) */ private populatePhaseQueue(): void { if (this.nextCommandPhaseQueue.length) { this.phaseQueue.push(...this.nextCommandPhaseQueue); this.nextCommandPhaseQueue.splice(0, this.nextCommandPhaseQueue.length); } - this.pushNew("TurnInitPhase"); + this.phaseQueue.push(new TurnInitPhase()); } /** - * Dynamically create the named phase from the provided arguments. + * Dynamically create the named phase from the provided arguments * * @remarks * Used to avoid importing each phase individually, allowing for dynamic creation of phases. - * @param phase - The {@linkcode PhaseString | name} of the Phase to create - * @param args - The arguments to pass to the phase constructor - * @returns The requested phase instance. + * @param phase - The name of the phase to create. + * @param args - The arguments to pass to the phase constructor. + * @returns The requested phase instance */ public create(phase: T, ...args: ConstructorParameters): PhaseMap[T] { const PhaseClass = PHASES[phase]; @@ -672,9 +626,8 @@ export class PhaseManager { } /** - * Create a new phase and immediately push it to the phase queue. - * Equivalent to calling {@linkcode create} immediately followed by {@linkcode pushPhase}. - * @param phase - The {@linkcode PhaseString | name} of the Phase to create + * Create a new phase and immediately push it to the phase queue. Equivalent to calling {@linkcode create} followed by {@linkcode pushPhase}. + * @param phase - The name of the phase to create * @param args - The arguments to pass to the phase constructor */ public pushNew(phase: T, ...args: ConstructorParameters): void { @@ -682,9 +635,8 @@ export class PhaseManager { } /** - * Create a new phase and immediately unshift it to the phase queue. - * Equivalent to calling {@linkcode create} followed by {@linkcode unshiftPhase}. - * @param phase - The {@linkcode PhaseString | name} of the Phase to create + * Create a new phase and immediately unshift it to the phase queue. Equivalent to calling {@linkcode create} followed by {@linkcode unshiftPhase}. + * @param phase - The name of the phase to create * @param args - The arguments to pass to the phase constructor */ public unshiftNew(phase: T, ...args: ConstructorParameters): void { @@ -694,10 +646,10 @@ export class PhaseManager { /** * Create a new phase and immediately prepend it to an existing phase in the phase queue. * Equivalent to calling {@linkcode create} followed by {@linkcode prependToPhase}. - * @param targetPhase - The {@linkcode PhaseString | name} of the Phase to search for - * @param phase - The {@linkcode PhaseString | name} of the Phase to create + * @param targetPhase - The phase to search for in phaseQueue + * @param phase - The name of the phase to create * @param args - The arguments to pass to the phase constructor - * @returns Whether a {@linkcode targetPhase} was found successfully. + * @returns `true` if a `targetPhase` was found to prepend to */ public prependNewToPhase( targetPhase: PhaseString, @@ -708,12 +660,12 @@ export class PhaseManager { } /** - * Create a new phase and immediately append it to an existing phase in the phase queue. + * Create a new phase and immediately append it to an existing phase the phase queue. * Equivalent to calling {@linkcode create} followed by {@linkcode appendToPhase}. - * @param targetPhase - The {@linkcode PhaseString | name} of the Phase to search for - * @param phase - The {@linkcode PhaseString | name} of the Phase to create + * @param targetPhase - The phase to search for in phaseQueue + * @param phase - The name of the phase to create * @param args - The arguments to pass to the phase constructor - * @returns Whether a {@linkcode targetPhase} was found successfully. + * @returns `true` if a `targetPhase` was found to append to */ public appendNewToPhase( targetPhase: PhaseString, @@ -723,13 +675,6 @@ export class PhaseManager { return this.appendToPhase(this.create(phase, ...args), targetPhase); } - /** - * Create a new dynamic phase, unshift a {@linkcode ActivatePriorityQueuePhase} for it, - * then push it onto its corresponnding dynamic queue - * Equivalent to calling {@linkcode create} followed by {@linkcode startDynamicPhase}. - * @param phase - The {@linkcode PhaseString | name} of the Phase to create - * @param args - The arguments to pass to the phase constructor - */ public startNewDynamicPhase( phase: T, ...args: ConstructorParameters