Modify existing phase manager methods to check dyanmic queues

This commit is contained in:
Dean 2025-06-15 11:17:39 -07:00
parent 30ab803df9
commit 70edead47e
13 changed files with 105 additions and 49 deletions

View File

@ -23,3 +23,7 @@ export type PhaseClass = PhaseConstructorMap[keyof PhaseConstructorMap];
* Union type of all phase names as strings. * Union type of all phase names as strings.
*/ */
export type PhaseString = keyof PhaseMap; export type PhaseString = keyof PhaseMap;
export type DynamicPhaseString = "PostSummonPhase" | "SwitchSummonPhase" | "MovePhase";
export type StaticPhaseString = Exclude<PhaseString, DynamicPhaseString>;

View File

@ -1151,16 +1151,9 @@ export class EncoreTag extends MoveRestrictionBattlerTag {
}), }),
); );
const movePhase = globalScene.phaseManager.findPhase("MovePhase", (m: MovePhase) => m.pokemon === pokemon); const movesetMove = pokemon.getMoveset().find(m => m.moveId === this.moveId);
if (movePhase) { if (movesetMove) {
const movesetMove = pokemon.getMoveset().find(m => m.moveId === this.moveId); globalScene.phaseManager.changePhaseMove((phase: MovePhase) => phase.pokemon === pokemon, movesetMove);
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),
);
}
} }
} }

View File

@ -101,6 +101,7 @@ import { ChargingMove, MoveAttrMap, MoveAttrString, MoveKindString, MoveClassMap
import { applyMoveAttrs } from "./apply-attrs"; import { applyMoveAttrs } from "./apply-attrs";
import { frenzyMissFunc, getMoveTargets } from "./move-utils"; import { frenzyMissFunc, getMoveTargets } from "./move-utils";
import { MovePriorityModifier } from "#enums/move-priority-modifier"; import { MovePriorityModifier } from "#enums/move-priority-modifier";
import { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier";
type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean; type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean;
export type UserMoveConditionFunc = (user: 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)) { if (globalScene.currentBattle.double && globalScene.getEnemyParty().length > 1 && !isNullOrUndefined(allyPokemon)) {
// Handle cases where revived pokemon needs to get switched in on same turn // Handle cases where revived pokemon needs to get switched in on same turn
if (allyPokemon.isFainted() || allyPokemon === pokemon) { 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 // 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) // (revived pokemon can't move in the turn they're brought back)
globalScene.phaseManager.findPhase("MovePhase", (phase: MovePhase) => phase.pokemon === pokemon)?.cancel(); 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.getMoveQueue().unshift({ move: lastMove.move, targets: moveTargets, ignorePP: false });
target.turnData.extraTurns++; 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; return true;
} }
@ -7884,12 +7883,7 @@ export class AfterYouAttr extends MoveEffectAttr {
*/ */
override apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { override apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:afterYou", { targetName: getPokemonNameWithAffix(target) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:afterYou", { targetName: getPokemonNameWithAffix(target) }));
globalScene.phaseManager.forceMoveNext((phase: MovePhase) => phase.pokemon === 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);
}
return true; return true;
} }

View File

@ -1,6 +0,0 @@
/**
* Enum representation of the phase types held by implementations of {@linkcode PhasePriorityQueue}
*/
export enum DynamicPhaseType {
POST_SUMMON
}

View File

@ -1,6 +1,6 @@
import type { Phase } from "#app/phase"; import type { Phase } from "#app/phase";
import type { default as Pokemon } from "#app/field/pokemon"; 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 { globalScene } from "#app/global-scene";
import { ActivatePriorityQueuePhase } from "#app/phases/activate-priority-queue-phase"; import { ActivatePriorityQueuePhase } from "#app/phases/activate-priority-queue-phase";
import { AddEnemyBuffModifierPhase } from "#app/phases/add-enemy-buff-modifier-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 { CheckSwitchPhase } from "#app/phases/check-switch-phase";
import { CommandPhase } from "#app/phases/command-phase"; import { CommandPhase } from "#app/phases/command-phase";
import { CommonAnimPhase } from "#app/phases/common-anim-phase"; import { CommonAnimPhase } from "#app/phases/common-anim-phase";
import { coerceArray } from "#app/utils/common";
import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { DamageAnimPhase } from "#app/phases/damage-anim-phase";
import { EggHatchPhase } from "#app/phases/egg-hatch-phase"; import { EggHatchPhase } from "#app/phases/egg-hatch-phase";
import { EggLapsePhase } from "#app/phases/egg-lapse-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 { DynamicQueueManager } from "#app/queues/dynamic-queue-manager";
import type { PhaseConditionFunc } from "#app/@types/phase-condition"; import type { PhaseConditionFunc } from "#app/@types/phase-condition";
import { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier"; import { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier";
import type { PokemonMove } from "#app/data/moves/pokemon-move";
/** /**
* Manager for phases used by battle scene. * Manager for phases used by battle scene.
@ -423,7 +423,7 @@ export class PhaseManager {
this.phaseQueue.splice(phaseIndex, 1); this.phaseQueue.splice(phaseIndex, 1);
return true; 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 * @param targetPhase - The phase to search for in phaseQueue
* @returns boolean if a targetPhase was found and added * @returns boolean if a targetPhase was found and added
*/ */
prependToPhase(phase: Phase | Phase[], targetPhase: PhaseString): boolean { prependToPhase(phase: Phase, targetPhase: PhaseString): boolean {
phase = coerceArray(phase); const insertPhase = this.dynamicQueueManager.isDynamicPhase(phase.phaseName)
? new ActivatePriorityQueuePhase(phase.phaseName)
: phase;
const target = PHASES[targetPhase]; const target = PHASES[targetPhase];
const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target); const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target);
if (this.dynamicQueueManager.isDynamicPhase(phase.phaseName)) {
this.dynamicQueueManager.queueDynamicPhase(phase);
}
if (targetIndex !== -1) { if (targetIndex !== -1) {
this.phaseQueue.splice(targetIndex, 0, ...phase); this.phaseQueue.splice(targetIndex, 0, insertPhase);
return true; return true;
} }
this.unshiftPhase(...phase); this.unshiftPhase(insertPhase);
return false; return false;
} }
@ -470,16 +477,22 @@ export class PhaseManager {
* @param condition Condition the target phase must meet to be appended to * @param condition Condition the target phase must meet to be appended to
* @returns `true` if a `targetPhase` was found to append to * @returns `true` if a `targetPhase` was found to append to
*/ */
appendToPhase(phase: Phase | Phase[], targetPhase: PhaseString, condition?: PhaseConditionFunc): boolean { appendToPhase(phase: Phase, targetPhase: StaticPhaseString, condition?: PhaseConditionFunc): boolean {
phase = coerceArray(phase); const insertPhase = this.dynamicQueueManager.isDynamicPhase(phase.phaseName)
? new ActivatePriorityQueuePhase(phase.phaseName)
: phase;
const target = PHASES[targetPhase]; const target = PHASES[targetPhase];
const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target && (!condition || condition(ph))); 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) { if (targetIndex !== -1 && this.phaseQueue.length > targetIndex) {
this.phaseQueue.splice(targetIndex + 1, 0, ...phase); this.phaseQueue.splice(targetIndex + 1, 0, insertPhase);
return true; return true;
} }
this.unshiftPhase(...phase); this.unshiftPhase(insertPhase);
return false; return false;
} }
@ -496,10 +509,10 @@ export class PhaseManager {
/** /**
* Unshifts the top phase from the corresponding dynamic queue onto {@linkcode phaseQueue} * 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 { public startNextDynamicPhase(): void {
const phase = this.dynamicQueueManager.popNextPhaseOfType(type); const phase = this.dynamicQueueManager.popNextPhase();
if (phase) { if (phase) {
this.unshiftPhase(phase); this.unshiftPhase(phase);
} }
@ -640,14 +653,14 @@ export class PhaseManager {
* @returns `true` if a `targetPhase` was found to append to * @returns `true` if a `targetPhase` was found to append to
*/ */
public appendNewToPhase<T extends PhaseString>( public appendNewToPhase<T extends PhaseString>(
targetPhase: PhaseString, targetPhase: StaticPhaseString,
phase: T, phase: T,
...args: ConstructorParameters<PhaseConstructorMap[T]> ...args: ConstructorParameters<PhaseConstructorMap[T]>
): boolean { ): boolean {
return this.appendToPhase(this.create(phase, ...args), targetPhase); return this.appendToPhase(this.create(phase, ...args), targetPhase);
} }
public startNewDynamicPhase<T extends PhaseString>( public startNewDynamicPhase<T extends DynamicPhaseString>(
phase: T, phase: T,
...args: ConstructorParameters<PhaseConstructorMap[T]> ...args: ConstructorParameters<PhaseConstructorMap[T]>
): void { ): void {
@ -661,4 +674,8 @@ export class PhaseManager {
public forceMoveLast(phaseCondition: PhaseConditionFunc) { public forceMoveLast(phaseCondition: PhaseConditionFunc) {
this.dynamicQueueManager.setMoveTimingModifier(phaseCondition, MovePhaseTimingModifier.LAST); this.dynamicQueueManager.setMoveTimingModifier(phaseCondition, MovePhaseTimingModifier.LAST);
} }
public changePhaseMove(phaseCondition: PhaseConditionFunc, move: PokemonMove) {
this.dynamicQueueManager.setMoveForPhase(phaseCondition, move);
}
} }

View File

@ -13,7 +13,7 @@ export class ActivatePriorityQueuePhase extends Phase {
override start() { override start() {
super.start(); super.start();
globalScene.phaseManager.startNextDynamicPhaseOfType(this.type); globalScene.phaseManager.startNextDynamicPhase();
this.end(); this.end();
} }

View File

@ -54,7 +54,7 @@ export class MovePhase extends PokemonPhase {
return this._move; return this._move;
} }
protected set move(move: PokemonMove) { public set move(move: PokemonMove) {
this._move = move; this._move = move;
} }
@ -88,6 +88,7 @@ export class MovePhase extends PokemonPhase {
followUp = false, followUp = false,
ignorePp = false, ignorePp = false,
reflected = false, reflected = false,
timingModifier = MovePhaseTimingModifier.NORMAL,
) { ) {
super(pokemon.getBattlerIndex()); super(pokemon.getBattlerIndex());
@ -97,7 +98,7 @@ export class MovePhase extends PokemonPhase {
this.followUp = followUp; this.followUp = followUp;
this.ignorePp = ignorePp; this.ignorePp = ignorePp;
this.reflected = reflected; this.reflected = reflected;
this.timingModifier = MovePhaseTimingModifier.NORMAL; this.timingModifier = timingModifier;
} }
/** /**

View File

@ -22,4 +22,8 @@ export abstract class PartyMemberPokemonPhase extends FieldPhase {
getPokemon(): Pokemon { getPokemon(): Pokemon {
return this.getParty()[this.partyMemberIndex]; return this.getParty()[this.partyMemberIndex];
} }
isPlayer(): boolean {
return this.player;
}
} }

View File

@ -296,4 +296,8 @@ export class SummonPhase extends PartyMemberPokemonPhase {
super.end(); super.end();
} }
public getFieldIndex(): number {
return this.fieldIndex;
}
} }

View File

@ -1,19 +1,20 @@
import type { PhaseConditionFunc } from "#app/@types/phase-condition"; import type { PhaseConditionFunc } from "#app/@types/phase-condition";
import type { PhaseString } from "#app/@types/phase-types"; 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 { Phase } from "#app/phase";
import type { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
import { MovePhasePriorityQueue } from "#app/queues/move-phase-priority-queue"; import { MovePhasePriorityQueue } from "#app/queues/move-phase-priority-queue";
import type { PhasePriorityQueue } from "#app/queues/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 { 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 { BattlerIndex } from "#enums/battler-index";
import type { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier"; import type { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier";
export class DynamicQueueManager { export class DynamicQueueManager {
private dynamicPhaseMap: Map<PhaseString, PhasePriorityQueue<Phase>>; private dynamicPhaseMap: Map<PhaseString, PhasePriorityQueue<Phase>>;
constructor() { constructor() {
this.dynamicPhaseMap = new Map(); this.dynamicPhaseMap = new Map();
this.dynamicPhaseMap.set("SwitchSummonPhase", new PokemonPhasePriorityQueue<SwitchSummonPhase>()); this.dynamicPhaseMap.set("SwitchSummonPhase", new SwitchSummonPhasePriorityQueue());
this.dynamicPhaseMap.set("PostSummonPhase", new PostSummonPhasePriorityQueue()); this.dynamicPhaseMap.set("PostSummonPhase", new PostSummonPhasePriorityQueue());
this.dynamicPhaseMap.set("MovePhase", new MovePhasePriorityQueue()); this.dynamicPhaseMap.set("MovePhase", new MovePhasePriorityQueue());
} }
@ -28,8 +29,8 @@ export class DynamicQueueManager {
this.dynamicPhaseMap.get(phase.phaseName)?.push(phase); this.dynamicPhaseMap.get(phase.phaseName)?.push(phase);
} }
public popNextPhaseOfType(type: PhaseString): Phase | undefined { public popNextPhase(): Phase | undefined {
return this.dynamicPhaseMap.get(type)?.pop(); return [...this.dynamicPhaseMap.values()].find(queue => !queue.isEmpty())?.pop();
} }
public isDynamicPhase(type: PhaseString): boolean { public isDynamicPhase(type: PhaseString): boolean {
@ -44,11 +45,24 @@ export class DynamicQueueManager {
return !!this.dynamicPhaseMap.get(type)?.hasPhaseWithCondition(condition); 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) { public setMoveTimingModifier(condition: PhaseConditionFunc, modifier: MovePhaseTimingModifier) {
const movePhaseQueue: MovePhasePriorityQueue = this.dynamicPhaseMap.get("MovePhase") as MovePhasePriorityQueue; const movePhaseQueue: MovePhasePriorityQueue = this.dynamicPhaseMap.get("MovePhase") as MovePhasePriorityQueue;
movePhaseQueue.setTimingModifier(condition, modifier); movePhaseQueue.setTimingModifier(condition, modifier);
} }
public setMoveForPhase(condition: PhaseConditionFunc, move: PokemonMove) {
(this.dynamicPhaseMap.get("MovePhase") as MovePhasePriorityQueue).setMoveForPhase(condition, move);
}
public setMoveOrder(order: BattlerIndex[]) { public setMoveOrder(order: BattlerIndex[]) {
(this.dynamicPhaseMap.get("MovePhase") as MovePhasePriorityQueue).setMoveOrder(order); (this.dynamicPhaseMap.get("MovePhase") as MovePhasePriorityQueue).setMoveOrder(order);
} }

View File

@ -1,4 +1,5 @@
import type { PhaseConditionFunc } from "#app/@types/phase-condition"; 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 type { MovePhase } from "#app/phases/move-phase";
import { PokemonPhasePriorityQueue } from "#app/queues/pokemon-phase-priority-queue"; import { PokemonPhasePriorityQueue } from "#app/queues/pokemon-phase-priority-queue";
import { isNullOrUndefined } from "#app/utils/common"; import { isNullOrUndefined } from "#app/utils/common";
@ -18,6 +19,13 @@ export class MovePhasePriorityQueue extends PokemonPhasePriorityQueue<MovePhase>
} }
} }
public setMoveForPhase(condition: PhaseConditionFunc, move: PokemonMove) {
const phase = this.queue.find(phase => condition(phase));
if (!isNullOrUndefined(phase)) {
phase.move = move;
}
}
public setMoveOrder(order: BattlerIndex[]) { public setMoveOrder(order: BattlerIndex[]) {
this.setOrder = order; this.setOrder = order;
} }

View File

@ -42,6 +42,15 @@ export abstract class PhasePriorityQueue<T extends Phase> {
return !this.queue.length; 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 { public findPhase(condition?: PhaseConditionFunc): Phase | undefined {
return this.queue.find(phase => !condition || condition(phase)); return this.queue.find(phase => !condition || condition(phase));
} }

View File

@ -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<SwitchSummonPhase> {
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);
}
}