mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-03 23:12:20 +02:00
Automatically queue consecutive Pokemon phases as dynamic
This commit is contained in:
parent
6e762ad80a
commit
301e12e92a
@ -1,3 +1,5 @@
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import type { Phase } from "#app/phase";
|
||||
import type { PhaseConstructorMap } from "#app/phase-manager";
|
||||
|
||||
// Intentionally export the types of everything in phase-manager, as this file is meant to be
|
||||
@ -27,3 +29,7 @@ export type PhaseString = keyof PhaseMap;
|
||||
export type DynamicPhaseString = "PostSummonPhase" | "SwitchSummonPhase" | "MovePhase" | "MoveHeaderPhase";
|
||||
|
||||
export type StaticPhaseString = Exclude<PhaseString, DynamicPhaseString>;
|
||||
|
||||
export interface DynamicPhase extends Phase {
|
||||
getPokemon(): Pokemon;
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ import type { Localizable } from "#app/@types/locales";
|
||||
import { applyAbAttrs } from "./apply-ab-attrs";
|
||||
import { MovePriorityModifier } from "#enums/move-priority-modifier";
|
||||
import { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier";
|
||||
import { MovePhase } from "#app/phases/move-phase";
|
||||
import type { MovePhase } from "#app/phases/move-phase";
|
||||
|
||||
export class Ability implements Localizable {
|
||||
public id: AbilityId;
|
||||
@ -6102,7 +6102,14 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr {
|
||||
// If the move is an AttackMove or a StatusMove the Dancer must replicate the move on the source of the Dance
|
||||
if (move.getMove().is("AttackMove") || move.getMove().is("StatusMove")) {
|
||||
const target = this.getTarget(dancer, source, targets);
|
||||
globalScene.phaseManager.pushNew("MovePhase", dancer, target, move, MoveUseMode.INDIRECT, MovePhaseTimingModifier.FIRST);
|
||||
globalScene.phaseManager.pushNew(
|
||||
"MovePhase",
|
||||
dancer,
|
||||
target,
|
||||
move,
|
||||
MoveUseMode.INDIRECT,
|
||||
MovePhaseTimingModifier.FIRST,
|
||||
);
|
||||
} else if (move.getMove().is("SelfStatusMove")) {
|
||||
// If the move is a SelfStatusMove (ie. Swords Dance) the Dancer should replicate it on itself
|
||||
globalScene.phaseManager.pushNew(
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { Phase } from "#app/phase";
|
||||
import type { default as Pokemon } from "#app/field/pokemon";
|
||||
import type { DynamicPhaseString, PhaseMap, PhaseString, StaticPhaseString } from "./@types/phase-types";
|
||||
import type { PhaseMap, PhaseString, DynamicPhase, StaticPhaseString } from "./@types/phase-types";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { AddEnemyBuffModifierPhase } from "#app/phases/add-enemy-buff-modifier-phase";
|
||||
import { AttemptCapturePhase } from "#app/phases/attempt-capture-phase";
|
||||
@ -214,6 +214,12 @@ const PHASES = Object.freeze({
|
||||
/** Maps Phase strings to their constructors */
|
||||
export type PhaseConstructorMap = typeof PHASES;
|
||||
|
||||
const turnEndPhases: PhaseString[] = ["WeatherEffectPhase", "BerryPhase", "CheckStatusEffectPhase", "TurnEndPhase"];
|
||||
|
||||
const ignorablePhases: PhaseString[] = ["ShowAbilityPhase", "HideAbilityPhase"];
|
||||
// TODO might be easier to define which phases should be dynamic instead
|
||||
const nonDynamicPokemonPhases: PhaseString[] = ["SummonPhase", "CommandPhase"];
|
||||
|
||||
/**
|
||||
* PhaseManager is responsible for managing the phases in the battle scene
|
||||
*/
|
||||
@ -232,6 +238,8 @@ export class PhaseManager {
|
||||
private currentPhase: Phase | null = null;
|
||||
private standbyPhase: Phase | null = null;
|
||||
|
||||
public turnEnded = false;
|
||||
|
||||
/* Phase Functions */
|
||||
getCurrentPhase(): Phase | null {
|
||||
return this.currentPhase;
|
||||
@ -247,11 +255,11 @@ export class PhaseManager {
|
||||
* @param defer boolean on which queue to add to, defaults to false, and adds to phaseQueue
|
||||
*/
|
||||
pushPhase(phase: Phase, defer = false): void {
|
||||
if (this.dynamicQueueManager.isDynamicPhase(phase.phaseName)) {
|
||||
if (this.isDynamicPhase(phase) && this.dynamicQueueManager.activeQueueExists(phase.phaseName)) {
|
||||
this.dynamicQueueManager.queueDynamicPhase(phase);
|
||||
} else {
|
||||
(!defer ? this.phaseQueue : this.nextCommandPhaseQueue).push(phase);
|
||||
return;
|
||||
}
|
||||
(!defer ? this.phaseQueue : this.nextCommandPhaseQueue).push(phase);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -259,6 +267,10 @@ export class PhaseManager {
|
||||
* @param phases {@linkcode Phase} the phase(s) to add
|
||||
*/
|
||||
unshiftPhase(...phases: Phase[]): void {
|
||||
if (this.isDynamicPhase(phases[0]) && this.dynamicQueueManager.activeQueueExists(phases[0].phaseName)) {
|
||||
phases.forEach((p: DynamicPhase) => this.dynamicQueueManager.queueDynamicPhase(p));
|
||||
return;
|
||||
}
|
||||
if (this.phaseQueuePrependSpliceIndex === -1) {
|
||||
this.phaseQueuePrepend.push(...phases);
|
||||
} else {
|
||||
@ -283,6 +295,7 @@ export class PhaseManager {
|
||||
this.dynamicQueueManager.clearQueues();
|
||||
this.currentPhase = null;
|
||||
this.standbyPhase = null;
|
||||
this.turnEnded = false;
|
||||
this.clearPhaseQueueSplice();
|
||||
}
|
||||
|
||||
@ -323,14 +336,16 @@ export class PhaseManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!this.phaseQueue.length) {
|
||||
this.populatePhaseQueue();
|
||||
|
||||
this.queueDynamicPhasesAtFront();
|
||||
|
||||
if (this.phaseQueue.every(p => ignorablePhases.includes(p.phaseName))) {
|
||||
this.startNextDynamicPhase();
|
||||
}
|
||||
|
||||
if (this.phaseQueue[0].is("WeatherEffectPhase")) {
|
||||
const dynamicPhase = this.dynamicQueueManager.popNextPhase();
|
||||
if (dynamicPhase) {
|
||||
this.phaseQueue.unshift(dynamicPhase);
|
||||
if (!this.turnEnded && (!this.phaseQueue.length || this.phaseQueue[0].is("BattleEndPhase"))) {
|
||||
if (!this.startNextDynamicPhase()) {
|
||||
this.turnEndSequence();
|
||||
}
|
||||
}
|
||||
|
||||
@ -356,8 +371,8 @@ export class PhaseManager {
|
||||
}
|
||||
|
||||
public hasPhaseOfType(type: PhaseString, condition?: PhaseConditionFunc): boolean {
|
||||
if (this.dynamicQueueManager.isDynamicPhase(type)) {
|
||||
return this.dynamicQueueManager.exists(type, condition);
|
||||
if (this.dynamicQueueManager.exists(type, condition)) {
|
||||
return true;
|
||||
}
|
||||
return [this.phaseQueue, this.phaseQueuePrepend].some((queue: Phase[]) =>
|
||||
queue.find(phase => phase.is(type) && (!condition || condition(phase))),
|
||||
@ -374,7 +389,7 @@ export class PhaseManager {
|
||||
phaseType: P,
|
||||
phaseFilter?: (phase: PhaseMap[P]) => boolean,
|
||||
): PhaseMap[P] | undefined {
|
||||
if (this.dynamicQueueManager.isDynamicPhase(phaseType)) {
|
||||
if (this.dynamicQueueManager.exists(phaseType, phaseFilter)) {
|
||||
return this.dynamicQueueManager.findPhaseOfType(phaseType, phaseFilter) as PhaseMap[P];
|
||||
}
|
||||
return this.phaseQueue.find(phase => phase.is(phaseType) && (!phaseFilter || phaseFilter(phase))) as PhaseMap[P];
|
||||
@ -457,17 +472,6 @@ export class PhaseManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unshifts the top phase from the corresponding dynamic queue onto {@linkcode phaseQueue}
|
||||
* @param type {@linkcode DynamicPhaseString} The type of dynamic phase to start
|
||||
*/
|
||||
public startNextDynamicPhase(type?: DynamicPhaseString): void {
|
||||
const phase = this.dynamicQueueManager.popNextPhase(type);
|
||||
if (phase) {
|
||||
this.unshiftPhase(phase);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a MessagePhase, either to PhaseQueuePrepend or nextCommandPhaseQueue
|
||||
* @param message - string for MessagePhase
|
||||
@ -518,12 +522,10 @@ export class PhaseManager {
|
||||
/**
|
||||
* Moves everything from nextCommandPhaseQueue to phaseQueue (keeping order)
|
||||
*/
|
||||
private populatePhaseQueue(): void {
|
||||
const dynamicPhase = this.dynamicQueueManager.popNextPhase();
|
||||
if (dynamicPhase) {
|
||||
this.phaseQueue.unshift(dynamicPhase);
|
||||
return;
|
||||
}
|
||||
private turnEndSequence(): void {
|
||||
this.turnEnded = true;
|
||||
this.dynamicQueueManager.clearQueues();
|
||||
this.queueTurnEndPhases();
|
||||
if (this.nextCommandPhaseQueue.length) {
|
||||
this.phaseQueue.push(...this.nextCommandPhaseQueue);
|
||||
this.nextCommandPhaseQueue.splice(0, this.nextCommandPhaseQueue.length);
|
||||
@ -565,10 +567,7 @@ export class PhaseManager {
|
||||
* @param phase - The name of the phase to create
|
||||
* @param args - The arguments to pass to the phase constructor
|
||||
*/
|
||||
public unshiftNew<T extends StaticPhaseString>(
|
||||
phase: T,
|
||||
...args: ConstructorParameters<PhaseConstructorMap[T]>
|
||||
): void {
|
||||
public unshiftNew<T extends PhaseString>(phase: T, ...args: ConstructorParameters<PhaseConstructorMap[T]>): void {
|
||||
this.unshiftPhase(this.create(phase, ...args));
|
||||
}
|
||||
|
||||
@ -615,4 +614,44 @@ export class PhaseManager {
|
||||
public changePhaseMove(phaseCondition: PhaseConditionFunc, move: PokemonMove) {
|
||||
this.dynamicQueueManager.setMoveForPhase(phaseCondition, move);
|
||||
}
|
||||
|
||||
public queueTurnEndPhases(): void {
|
||||
turnEndPhases
|
||||
.slice()
|
||||
.reverse()
|
||||
.forEach(p => this.phaseQueue.unshift(this.create(p)));
|
||||
}
|
||||
|
||||
private consecutivePokemonPhases(): DynamicPhase[] | undefined {
|
||||
if (this.phaseQueue.length < 1 || !this.isDynamicPhase(this.phaseQueue[0])) {
|
||||
return;
|
||||
}
|
||||
|
||||
let spliceLength = this.phaseQueue.findIndex(p => !p.is(this.phaseQueue[0].phaseName));
|
||||
spliceLength = spliceLength !== -1 ? spliceLength : this.phaseQueue.length;
|
||||
if (spliceLength > 1) {
|
||||
return this.phaseQueue.splice(0, spliceLength) as DynamicPhase[];
|
||||
}
|
||||
}
|
||||
|
||||
private queueDynamicPhasesAtFront(): void {
|
||||
const dynamicPhases = this.consecutivePokemonPhases();
|
||||
if (dynamicPhases) {
|
||||
dynamicPhases.forEach((p: DynamicPhase) => {
|
||||
globalScene.phaseManager.dynamicQueueManager.queueDynamicPhase(p);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public startNextDynamicPhase(): boolean {
|
||||
const dynamicPhase = this.dynamicQueueManager.popNextPhase();
|
||||
if (dynamicPhase) {
|
||||
this.phaseQueue.unshift(dynamicPhase);
|
||||
}
|
||||
return !!dynamicPhase;
|
||||
}
|
||||
|
||||
private isDynamicPhase(phase: Phase): phase is DynamicPhase {
|
||||
return typeof (phase as any).getPokemon === "function" && !nonDynamicPokemonPhases.includes(phase.phaseName);
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,14 @@
|
||||
import { Phase } from "#app/phase";
|
||||
import type { BattlerIndex } from "#enums/battler-index";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
|
||||
export class CheckStatusEffectPhase extends Phase {
|
||||
public readonly phaseName = "CheckStatusEffectPhase";
|
||||
private order: BattlerIndex[];
|
||||
constructor(order: BattlerIndex[]) {
|
||||
super();
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
start() {
|
||||
const field = globalScene.getField();
|
||||
for (const o of this.order) {
|
||||
if (field[o].status?.isPostTurn()) {
|
||||
globalScene.phaseManager.unshiftNew("PostTurnStatusEffectPhase", o);
|
||||
for (const p of field) {
|
||||
if (p?.status?.isPostTurn()) {
|
||||
globalScene.phaseManager.unshiftNew("PostTurnStatusEffectPhase", p.getBattlerIndex());
|
||||
}
|
||||
}
|
||||
this.end();
|
||||
|
@ -562,7 +562,6 @@ export class EncounterPhase extends BattlePhase {
|
||||
});
|
||||
|
||||
if (![BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(globalScene.currentBattle.battleType)) {
|
||||
enemyField.map(p => globalScene.phaseManager.pushNew("PostSummonPhase", p.getBattlerIndex()));
|
||||
const ivScannerModifier = globalScene.findModifier(m => m instanceof IvScannerModifier);
|
||||
if (ivScannerModifier) {
|
||||
enemyField.map(p => globalScene.phaseManager.pushNew("ScanIvsPhase", p.getBattlerIndex()));
|
||||
@ -603,6 +602,9 @@ export class EncounterPhase extends BattlePhase {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (![BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(globalScene.currentBattle.battleType)) {
|
||||
enemyField.map(p => globalScene.phaseManager.pushNew("PostSummonPhase", p.getBattlerIndex()));
|
||||
}
|
||||
handleTutorial(Tutorial.Access_Menu).then(() => super.end());
|
||||
}
|
||||
|
||||
|
@ -36,11 +36,6 @@ export class PostSummonPhase extends PokemonPhase {
|
||||
this.end();
|
||||
}
|
||||
|
||||
override end() {
|
||||
globalScene.phaseManager.startNextDynamicPhase("PostSummonPhase");
|
||||
super.end();
|
||||
}
|
||||
|
||||
public getPriority() {
|
||||
return 0;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ export class TurnInitPhase extends FieldPhase {
|
||||
start() {
|
||||
super.start();
|
||||
|
||||
globalScene.phaseManager.turnEnded = false;
|
||||
globalScene.getPlayerField().forEach(p => {
|
||||
// If this pokemon is in play and evolved into something illegal under the current challenge, force a switch
|
||||
if (p.isOnField() && !p.isAllowedInBattle()) {
|
||||
|
@ -163,14 +163,6 @@ export class TurnStartPhase extends FieldPhase {
|
||||
}
|
||||
}
|
||||
|
||||
phaseManager.pushNew("WeatherEffectPhase");
|
||||
phaseManager.pushNew("BerryPhase");
|
||||
|
||||
/** Add a new phase to check who should be taking status damage */
|
||||
phaseManager.pushNew("CheckStatusEffectPhase", moveOrder);
|
||||
|
||||
phaseManager.pushNew("TurnEndPhase");
|
||||
|
||||
/**
|
||||
* this.end() will call shiftPhase(), which dumps everything from PrependQueue (aka everything that is unshifted()) to the front
|
||||
* of the queue and dequeues to start the next phase
|
||||
|
@ -1,9 +1,8 @@
|
||||
import type { PhaseConditionFunc } from "#app/@types/phase-condition";
|
||||
import type { DynamicPhaseString, PhaseString } from "#app/@types/phase-types";
|
||||
import type { PhaseString, DynamicPhase } from "#app/@types/phase-types";
|
||||
import type { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import type { Phase } from "#app/phase";
|
||||
import type { MoveHeaderPhase } from "#app/phases/move-header-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";
|
||||
@ -14,11 +13,12 @@ import type { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier"
|
||||
|
||||
export class DynamicQueueManager {
|
||||
private dynamicPhaseMap: Map<PhaseString, PhasePriorityQueue<Phase>>;
|
||||
private alwaysDynamic: PhaseString[] = ["SwitchSummonPhase", "PostSummonPhase", "MovePhase"];
|
||||
private popOrder: PhaseString[] = [];
|
||||
|
||||
constructor() {
|
||||
this.dynamicPhaseMap = new Map();
|
||||
this.dynamicPhaseMap.set("SwitchSummonPhase", new SwitchSummonPhasePriorityQueue());
|
||||
this.dynamicPhaseMap.set("MoveHeaderPhase", new PokemonPhasePriorityQueue<MoveHeaderPhase>());
|
||||
this.dynamicPhaseMap.set("PostSummonPhase", new PostSummonPhasePriorityQueue());
|
||||
this.dynamicPhaseMap.set("MovePhase", new MovePhasePriorityQueue());
|
||||
}
|
||||
@ -27,27 +27,39 @@ export class DynamicQueueManager {
|
||||
for (const queue of this.dynamicPhaseMap.values()) {
|
||||
queue.clear();
|
||||
}
|
||||
this.popOrder.splice(0, this.popOrder.length);
|
||||
}
|
||||
|
||||
public queueDynamicPhase(phase: Phase): void {
|
||||
public queueDynamicPhase<T extends DynamicPhase>(phase: T): void {
|
||||
if (!this.dynamicPhaseMap.has(phase.phaseName)) {
|
||||
this.dynamicPhaseMap.set(phase.phaseName, new PokemonPhasePriorityQueue<T>());
|
||||
}
|
||||
this.dynamicPhaseMap.get(phase.phaseName)?.push(phase);
|
||||
this.popOrder.push(phase.phaseName);
|
||||
}
|
||||
|
||||
public popNextPhase(type?: DynamicPhaseString): Phase | undefined {
|
||||
const queue = type
|
||||
? this.dynamicPhaseMap.get(type)
|
||||
: [...this.dynamicPhaseMap.values()].find(queue => !queue.isEmpty());
|
||||
return queue?.pop();
|
||||
}
|
||||
|
||||
public isDynamicPhase(type: PhaseString): boolean {
|
||||
return this.dynamicPhaseMap.has(type);
|
||||
public popNextPhase(): Phase | undefined {
|
||||
const type = this.popOrder.pop();
|
||||
if (!type) {
|
||||
return;
|
||||
}
|
||||
if (!this.alwaysDynamic.includes(type)) {
|
||||
return this.dynamicPhaseMap.get(type)?.pop();
|
||||
}
|
||||
return this.alwaysDynamic
|
||||
.map((p: PhaseString) => this.dynamicPhaseMap.get(p))
|
||||
.find(q => q && !q.isEmpty())
|
||||
?.pop();
|
||||
}
|
||||
|
||||
public findPhaseOfType(type: PhaseString, condition?: PhaseConditionFunc): Phase | undefined {
|
||||
return this.dynamicPhaseMap.get(type)?.findPhase(condition);
|
||||
}
|
||||
|
||||
public activeQueueExists(type: PhaseString) {
|
||||
return this.alwaysDynamic.includes(type) || this.dynamicPhaseMap.get(type)?.isEmpty() === false;
|
||||
}
|
||||
|
||||
public exists(type: PhaseString, condition?: PhaseConditionFunc): boolean {
|
||||
return !!this.dynamicPhaseMap.get(type)?.hasPhaseWithCondition(condition);
|
||||
}
|
||||
@ -84,4 +96,8 @@ export class DynamicQueueManager {
|
||||
private getMovePhaseQueue(): MovePhasePriorityQueue {
|
||||
return this.dynamicPhaseMap.get("MovePhase") as MovePhasePriorityQueue;
|
||||
}
|
||||
|
||||
public addPopType(type: PhaseString): void {
|
||||
this.popOrder.push(type);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import type { PartyMemberPokemonPhase } from "#app/phases/party-member-pokemon-phase";
|
||||
import type { PokemonPhase } from "#app/phases/pokemon-phase";
|
||||
import type { DynamicPhase } from "#app/@types/phase-types";
|
||||
import { PhasePriorityQueue } from "#app/queues/phase-priority-queue";
|
||||
import { sortInSpeedOrder } from "#app/utils/speed-order";
|
||||
import type { BattlerIndex } from "#enums/battler-index";
|
||||
|
||||
export class PokemonPhasePriorityQueue<T extends PokemonPhase | PartyMemberPokemonPhase> extends PhasePriorityQueue<T> {
|
||||
export class PokemonPhasePriorityQueue<T extends DynamicPhase> extends PhasePriorityQueue<T> {
|
||||
protected setOrder: BattlerIndex[] | undefined;
|
||||
public override reorder(): void {
|
||||
this.queue = this.queue.filter(phase => phase.getPokemon()?.isActive(true));
|
||||
if (this.setOrder) {
|
||||
this.queue.sort(
|
||||
(a, b) =>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { PostSummonActivateAbilityPhase } from "#app/phases/post-summon-activate-ability-phase";
|
||||
import type { PostSummonPhase } from "#app/phases/post-summon-phase";
|
||||
import { PokemonPhasePriorityQueue } from "#app/queues/pokemon-phase-priority-queue";
|
||||
@ -29,10 +30,9 @@ export class PostSummonPhasePriorityQueue extends PokemonPhasePriorityQueue<Post
|
||||
private queueAbilityPhase(phase: PostSummonPhase): void {
|
||||
const phasePokemon = phase.getPokemon();
|
||||
|
||||
phasePokemon
|
||||
.getAbilityPriorities()
|
||||
.forEach((priority, idx) =>
|
||||
this.queue.push(new PostSummonActivateAbilityPhase(phasePokemon.getBattlerIndex(), priority, !!idx)),
|
||||
);
|
||||
phasePokemon.getAbilityPriorities().forEach((priority, idx) => {
|
||||
this.queue.push(new PostSummonActivateAbilityPhase(phasePokemon.getBattlerIndex(), priority, !!idx));
|
||||
globalScene.phaseManager.dynamicQueueManager.addPopType("PostSummonPhase");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user