From 70edead47eec1fe43280ce66acafa8fb25f75eee Mon Sep 17 00:00:00 2001 From: Dean Date: Sun, 15 Jun 2025 11:17:39 -0700 Subject: [PATCH] Modify existing phase manager methods to check dyanmic queues --- src/@types/phase-types.ts | 4 ++ src/data/battler-tags.ts | 13 ++--- src/data/moves/move.ts | 12 ++--- src/enums/dynamic-phase-type.ts | 6 --- src/phase-manager.ts | 49 +++++++++++++------ src/phases/activate-priority-queue-phase.ts | 2 +- src/phases/move-phase.ts | 5 +- src/phases/party-member-pokemon-phase.ts | 4 ++ src/phases/summon-phase.ts | 4 ++ src/queues/dynamic-queue-manager.ts | 24 +++++++-- src/queues/move-phase-priority-queue.ts | 8 +++ src/queues/phase-priority-queue.ts | 9 ++++ .../switch-summon-phase-priority-queue.ts | 14 ++++++ 13 files changed, 105 insertions(+), 49 deletions(-) delete mode 100644 src/enums/dynamic-phase-type.ts create mode 100644 src/queues/switch-summon-phase-priority-queue.ts diff --git a/src/@types/phase-types.ts b/src/@types/phase-types.ts index 1d68c7921dd..dab8bfeea12 100644 --- a/src/@types/phase-types.ts +++ b/src/@types/phase-types.ts @@ -23,3 +23,7 @@ export type PhaseClass = PhaseConstructorMap[keyof PhaseConstructorMap]; * Union type of all phase names as strings. */ export type PhaseString = keyof PhaseMap; + +export type DynamicPhaseString = "PostSummonPhase" | "SwitchSummonPhase" | "MovePhase"; + +export type StaticPhaseString = Exclude; diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 2cbe960ddff..42643183eca 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1151,16 +1151,9 @@ export class EncoreTag extends MoveRestrictionBattlerTag { }), ); - const movePhase = globalScene.phaseManager.findPhase("MovePhase", (m: MovePhase) => m.pokemon === pokemon); - if (movePhase) { - const movesetMove = pokemon.getMoveset().find(m => m.moveId === this.moveId); - if (movesetMove) { - const lastMove = pokemon.getLastXMoves(1)[0]; - globalScene.phaseManager.tryReplacePhase( - m => m.is("MovePhase") && m.pokemon === pokemon, - globalScene.phaseManager.create("MovePhase", pokemon, lastMove.targets ?? [], movesetMove), - ); - } + const movesetMove = pokemon.getMoveset().find(m => m.moveId === this.moveId); + if (movesetMove) { + globalScene.phaseManager.changePhaseMove((phase: MovePhase) => phase.pokemon === pokemon, movesetMove); } } diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index ec92aadb504..2261f959f58 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -101,6 +101,7 @@ import { ChargingMove, MoveAttrMap, MoveAttrString, MoveKindString, MoveClassMap import { applyMoveAttrs } from "./apply-attrs"; import { frenzyMissFunc, getMoveTargets } from "./move-utils"; import { MovePriorityModifier } from "#enums/move-priority-modifier"; +import { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier"; type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean; export type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean; @@ -6212,8 +6213,6 @@ export class RevivalBlessingAttr extends MoveEffectAttr { if (globalScene.currentBattle.double && globalScene.getEnemyParty().length > 1 && !isNullOrUndefined(allyPokemon)) { // Handle cases where revived pokemon needs to get switched in on same turn if (allyPokemon.isFainted() || allyPokemon === pokemon) { - // Enemy switch phase should be removed and replaced with the revived pkmn switching in - globalScene.phaseManager.tryRemovePhase((phase: SwitchSummonPhase) => phase.is("SwitchSummonPhase") && phase.getPokemon() === pokemon); // If the pokemon being revived was alive earlier in the turn, cancel its move // (revived pokemon can't move in the turn they're brought back) globalScene.phaseManager.findPhase("MovePhase", (phase: MovePhase) => phase.pokemon === pokemon)?.cancel(); @@ -7086,7 +7085,7 @@ export class RepeatMoveAttr extends MoveEffectAttr { })); target.getMoveQueue().unshift({ move: lastMove.move, targets: moveTargets, ignorePP: false }); target.turnData.extraTurns++; - globalScene.phaseManager.appendNewToPhase("MoveEndPhase", "MovePhase", target, moveTargets, movesetMove); + globalScene.phaseManager.appendNewToPhase("MoveEndPhase", "MovePhase", target, moveTargets, movesetMove, false, false, false, MovePhaseTimingModifier.FIRST); return true; } @@ -7884,12 +7883,7 @@ export class AfterYouAttr extends MoveEffectAttr { */ override apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:afterYou", { targetName: getPokemonNameWithAffix(target) })); - - //Will find next acting phase of the targeted pokémon, delete it and queue it next on successful delete. - const nextAttackPhase = globalScene.phaseManager.findPhase("MovePhase", (phase) => phase.pokemon === target); - if (nextAttackPhase && globalScene.phaseManager.tryRemovePhase((phase: MovePhase) => phase.pokemon === target)) { - globalScene.phaseManager.prependNewToPhase("MovePhase", "MovePhase", target, [ ...nextAttackPhase.targets ], nextAttackPhase.move); - } + globalScene.phaseManager.forceMoveNext((phase: MovePhase) => phase.pokemon === target); return true; } diff --git a/src/enums/dynamic-phase-type.ts b/src/enums/dynamic-phase-type.ts deleted file mode 100644 index a34ac371668..00000000000 --- a/src/enums/dynamic-phase-type.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Enum representation of the phase types held by implementations of {@linkcode PhasePriorityQueue} - */ -export enum DynamicPhaseType { - POST_SUMMON -} diff --git a/src/phase-manager.ts b/src/phase-manager.ts index d38dd9fdbea..fb9f7914b41 100644 --- a/src/phase-manager.ts +++ b/src/phase-manager.ts @@ -1,6 +1,6 @@ import type { Phase } from "#app/phase"; import type { default as Pokemon } from "#app/field/pokemon"; -import type { PhaseMap, PhaseString } from "./@types/phase-types"; +import type { DynamicPhaseString, PhaseMap, PhaseString, StaticPhaseString } from "./@types/phase-types"; import { globalScene } from "#app/global-scene"; import { ActivatePriorityQueuePhase } from "#app/phases/activate-priority-queue-phase"; import { AddEnemyBuffModifierPhase } from "#app/phases/add-enemy-buff-modifier-phase"; @@ -12,7 +12,6 @@ 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 } from "#app/utils/common"; import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { EggHatchPhase } from "#app/phases/egg-hatch-phase"; import { EggLapsePhase } from "#app/phases/egg-lapse-phase"; @@ -100,6 +99,7 @@ import { WeatherEffectPhase } from "#app/phases/weather-effect-phase"; import { DynamicQueueManager } from "#app/queues/dynamic-queue-manager"; import type { PhaseConditionFunc } from "#app/@types/phase-condition"; import { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier"; +import type { PokemonMove } from "#app/data/moves/pokemon-move"; /** * Manager for phases used by battle scene. @@ -423,7 +423,7 @@ export class PhaseManager { this.phaseQueue.splice(phaseIndex, 1); return true; } - return false; + return this.dynamicQueueManager.removePhase(phaseFilter); } /** @@ -450,16 +450,23 @@ export class PhaseManager { * @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); + prependToPhase(phase: Phase, targetPhase: PhaseString): boolean { + const insertPhase = this.dynamicQueueManager.isDynamicPhase(phase.phaseName) + ? new ActivatePriorityQueuePhase(phase.phaseName) + : phase; const target = PHASES[targetPhase]; const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target); + if (this.dynamicQueueManager.isDynamicPhase(phase.phaseName)) { + this.dynamicQueueManager.queueDynamicPhase(phase); + } + if (targetIndex !== -1) { - this.phaseQueue.splice(targetIndex, 0, ...phase); + this.phaseQueue.splice(targetIndex, 0, insertPhase); return true; } - this.unshiftPhase(...phase); + this.unshiftPhase(insertPhase); + return false; } @@ -470,16 +477,22 @@ export class PhaseManager { * @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?: PhaseConditionFunc): boolean { - phase = coerceArray(phase); + appendToPhase(phase: Phase, targetPhase: StaticPhaseString, condition?: PhaseConditionFunc): boolean { + const insertPhase = this.dynamicQueueManager.isDynamicPhase(phase.phaseName) + ? new ActivatePriorityQueuePhase(phase.phaseName) + : phase; const target = PHASES[targetPhase]; const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target && (!condition || condition(ph))); + if (this.dynamicQueueManager.isDynamicPhase(phase.phaseName)) { + this.dynamicQueueManager.queueDynamicPhase(phase); + } + if (targetIndex !== -1 && this.phaseQueue.length > targetIndex) { - this.phaseQueue.splice(targetIndex + 1, 0, ...phase); + this.phaseQueue.splice(targetIndex + 1, 0, insertPhase); return true; } - this.unshiftPhase(...phase); + this.unshiftPhase(insertPhase); return false; } @@ -496,10 +509,10 @@ export class PhaseManager { /** * Unshifts the top phase from the corresponding dynamic queue onto {@linkcode phaseQueue} - * @param type {@linkcode DynamicPhaseType} The type of dynamic phase to start + * @param type {@linkcode DynamicPhaseString} The type of dynamic phase to start */ - public startNextDynamicPhaseOfType(type: PhaseString): void { - const phase = this.dynamicQueueManager.popNextPhaseOfType(type); + public startNextDynamicPhase(): void { + const phase = this.dynamicQueueManager.popNextPhase(); if (phase) { this.unshiftPhase(phase); } @@ -640,14 +653,14 @@ export class PhaseManager { * @returns `true` if a `targetPhase` was found to append to */ public appendNewToPhase( - targetPhase: PhaseString, + targetPhase: StaticPhaseString, phase: T, ...args: ConstructorParameters ): boolean { return this.appendToPhase(this.create(phase, ...args), targetPhase); } - public startNewDynamicPhase( + public startNewDynamicPhase( phase: T, ...args: ConstructorParameters ): void { @@ -661,4 +674,8 @@ export class PhaseManager { public forceMoveLast(phaseCondition: PhaseConditionFunc) { this.dynamicQueueManager.setMoveTimingModifier(phaseCondition, MovePhaseTimingModifier.LAST); } + + public changePhaseMove(phaseCondition: PhaseConditionFunc, move: PokemonMove) { + this.dynamicQueueManager.setMoveForPhase(phaseCondition, move); + } } diff --git a/src/phases/activate-priority-queue-phase.ts b/src/phases/activate-priority-queue-phase.ts index b58e13be2e1..4e467446055 100644 --- a/src/phases/activate-priority-queue-phase.ts +++ b/src/phases/activate-priority-queue-phase.ts @@ -13,7 +13,7 @@ export class ActivatePriorityQueuePhase extends Phase { override start() { super.start(); - globalScene.phaseManager.startNextDynamicPhaseOfType(this.type); + globalScene.phaseManager.startNextDynamicPhase(); this.end(); } diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 32889bae07a..416be08653d 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -54,7 +54,7 @@ export class MovePhase extends PokemonPhase { return this._move; } - protected set move(move: PokemonMove) { + public set move(move: PokemonMove) { this._move = move; } @@ -88,6 +88,7 @@ export class MovePhase extends PokemonPhase { followUp = false, ignorePp = false, reflected = false, + timingModifier = MovePhaseTimingModifier.NORMAL, ) { super(pokemon.getBattlerIndex()); @@ -97,7 +98,7 @@ export class MovePhase extends PokemonPhase { this.followUp = followUp; this.ignorePp = ignorePp; this.reflected = reflected; - this.timingModifier = MovePhaseTimingModifier.NORMAL; + this.timingModifier = timingModifier; } /** diff --git a/src/phases/party-member-pokemon-phase.ts b/src/phases/party-member-pokemon-phase.ts index a782eabda38..ac9de5a2b15 100644 --- a/src/phases/party-member-pokemon-phase.ts +++ b/src/phases/party-member-pokemon-phase.ts @@ -22,4 +22,8 @@ export abstract class PartyMemberPokemonPhase extends FieldPhase { getPokemon(): Pokemon { return this.getParty()[this.partyMemberIndex]; } + + isPlayer(): boolean { + return this.player; + } } diff --git a/src/phases/summon-phase.ts b/src/phases/summon-phase.ts index ad93452331f..8ed33e12870 100644 --- a/src/phases/summon-phase.ts +++ b/src/phases/summon-phase.ts @@ -296,4 +296,8 @@ export class SummonPhase extends PartyMemberPokemonPhase { super.end(); } + + public getFieldIndex(): number { + return this.fieldIndex; + } } diff --git a/src/queues/dynamic-queue-manager.ts b/src/queues/dynamic-queue-manager.ts index e0b5ab3df6c..c140c8bcc9e 100644 --- a/src/queues/dynamic-queue-manager.ts +++ b/src/queues/dynamic-queue-manager.ts @@ -1,19 +1,20 @@ import type { PhaseConditionFunc } from "#app/@types/phase-condition"; import type { PhaseString } from "#app/@types/phase-types"; +import type { PokemonMove } from "#app/data/moves/pokemon-move"; 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"; +import { SwitchSummonPhasePriorityQueue } from "#app/queues/switch-summon-phase-priority-queue"; import type { BattlerIndex } from "#enums/battler-index"; import type { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier"; + export class DynamicQueueManager { private dynamicPhaseMap: Map>; constructor() { this.dynamicPhaseMap = new Map(); - this.dynamicPhaseMap.set("SwitchSummonPhase", new PokemonPhasePriorityQueue()); + this.dynamicPhaseMap.set("SwitchSummonPhase", new SwitchSummonPhasePriorityQueue()); this.dynamicPhaseMap.set("PostSummonPhase", new PostSummonPhasePriorityQueue()); this.dynamicPhaseMap.set("MovePhase", new MovePhasePriorityQueue()); } @@ -28,8 +29,8 @@ export class DynamicQueueManager { this.dynamicPhaseMap.get(phase.phaseName)?.push(phase); } - public popNextPhaseOfType(type: PhaseString): Phase | undefined { - return this.dynamicPhaseMap.get(type)?.pop(); + public popNextPhase(): Phase | undefined { + return [...this.dynamicPhaseMap.values()].find(queue => !queue.isEmpty())?.pop(); } public isDynamicPhase(type: PhaseString): boolean { @@ -44,11 +45,24 @@ export class DynamicQueueManager { return !!this.dynamicPhaseMap.get(type)?.hasPhaseWithCondition(condition); } + public removePhase(condition: PhaseConditionFunc) { + for (const queue of this.dynamicPhaseMap.values()) { + if (queue.remove(condition)) { + return true; + } + } + return false; + } + public setMoveTimingModifier(condition: PhaseConditionFunc, modifier: MovePhaseTimingModifier) { const movePhaseQueue: MovePhasePriorityQueue = this.dynamicPhaseMap.get("MovePhase") as MovePhasePriorityQueue; movePhaseQueue.setTimingModifier(condition, modifier); } + public setMoveForPhase(condition: PhaseConditionFunc, move: PokemonMove) { + (this.dynamicPhaseMap.get("MovePhase") as MovePhasePriorityQueue).setMoveForPhase(condition, move); + } + public setMoveOrder(order: BattlerIndex[]) { (this.dynamicPhaseMap.get("MovePhase") as MovePhasePriorityQueue).setMoveOrder(order); } diff --git a/src/queues/move-phase-priority-queue.ts b/src/queues/move-phase-priority-queue.ts index cbd70d864c7..81ec1120156 100644 --- a/src/queues/move-phase-priority-queue.ts +++ b/src/queues/move-phase-priority-queue.ts @@ -1,4 +1,5 @@ import type { PhaseConditionFunc } from "#app/@types/phase-condition"; +import type { PokemonMove } from "#app/data/moves/pokemon-move"; import type { MovePhase } from "#app/phases/move-phase"; import { PokemonPhasePriorityQueue } from "#app/queues/pokemon-phase-priority-queue"; import { isNullOrUndefined } from "#app/utils/common"; @@ -18,6 +19,13 @@ export class MovePhasePriorityQueue extends PokemonPhasePriorityQueue } } + public setMoveForPhase(condition: PhaseConditionFunc, move: PokemonMove) { + const phase = this.queue.find(phase => condition(phase)); + if (!isNullOrUndefined(phase)) { + phase.move = move; + } + } + public setMoveOrder(order: BattlerIndex[]) { this.setOrder = order; } diff --git a/src/queues/phase-priority-queue.ts b/src/queues/phase-priority-queue.ts index 2ca6a077154..0b9a10b2e45 100644 --- a/src/queues/phase-priority-queue.ts +++ b/src/queues/phase-priority-queue.ts @@ -42,6 +42,15 @@ export abstract class PhasePriorityQueue { return !this.queue.length; } + public remove(condition: PhaseConditionFunc): boolean { + const phaseIndex = this.queue.findIndex(condition); + if (phaseIndex > -1) { + this.queue.splice(phaseIndex, 1); + return true; + } + return false; + } + public findPhase(condition?: PhaseConditionFunc): Phase | undefined { return this.queue.find(phase => !condition || condition(phase)); } diff --git a/src/queues/switch-summon-phase-priority-queue.ts b/src/queues/switch-summon-phase-priority-queue.ts new file mode 100644 index 00000000000..eb4ad1d7c28 --- /dev/null +++ b/src/queues/switch-summon-phase-priority-queue.ts @@ -0,0 +1,14 @@ +import type { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; +import { PokemonPhasePriorityQueue } from "#app/queues/pokemon-phase-priority-queue"; + +export class SwitchSummonPhasePriorityQueue extends PokemonPhasePriorityQueue { + public override push(phase: SwitchSummonPhase): void { + // The same pokemon or slot cannot be switched into at the same time + this.queue.filter( + old => + old.getPokemon() !== phase.getPokemon() && + !(old.isPlayer() === phase.isPlayer() && old.getFieldIndex() === phase.getFieldIndex()), + ); + super.push(phase); + } +}