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.
*/
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);
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);
}
}

View File

@ -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;
}

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 { 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<T extends PhaseString>(
targetPhase: PhaseString,
targetPhase: StaticPhaseString,
phase: T,
...args: ConstructorParameters<PhaseConstructorMap[T]>
): boolean {
return this.appendToPhase(this.create(phase, ...args), targetPhase);
}
public startNewDynamicPhase<T extends PhaseString>(
public startNewDynamicPhase<T extends DynamicPhaseString>(
phase: T,
...args: ConstructorParameters<PhaseConstructorMap[T]>
): 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);
}
}

View File

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

View File

@ -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;
}
/**

View File

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

View File

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

View File

@ -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<PhaseString, PhasePriorityQueue<Phase>>;
constructor() {
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("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);
}

View File

@ -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<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[]) {
this.setOrder = order;
}

View File

@ -42,6 +42,15 @@ export abstract class PhasePriorityQueue<T extends Phase> {
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));
}

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);
}
}