mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-06-29 13:02:46 +02:00
Switch to priority queue approach
This commit is contained in:
parent
9ea26325ae
commit
aca6d2ce8d
@ -170,6 +170,13 @@ import { StatusEffect } from "#enums/status-effect";
|
||||
import { initGlobalScene } from "#app/global-scene";
|
||||
import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
|
||||
import { HideAbilityPhase } from "#app/phases/hide-ability-phase";
|
||||
import {
|
||||
type DynamicPhaseType,
|
||||
type PhasePriorityQueue,
|
||||
PostSummonPhasePriorityQueue,
|
||||
} from "#app/data/phase-priority-queue";
|
||||
import { PostSummonPhase } from "#app/phases/post-summon-phase";
|
||||
import { ActivatePriorityQueuePhase } from "#app/phases/activate-priority-queue-phase";
|
||||
|
||||
export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1";
|
||||
|
||||
@ -302,6 +309,10 @@ export default class BattleScene extends SceneBase {
|
||||
/** overrides default of inserting phases to end of phaseQueuePrepend array, useful or inserting Phases "out of order" */
|
||||
private phaseQueuePrependSpliceIndex: number;
|
||||
private nextCommandPhaseQueue: Phase[];
|
||||
/** Storage for {@linkcode PhasePriorityQueue}s which hold phases whose order dynamically changes */
|
||||
private dynamicPhaseQueues: PhasePriorityQueue[];
|
||||
/** Parallel array to {@linkcode dynamicPhaseQueues} - matches phase types to their queues */
|
||||
private dynamicPhaseTypes: Constructor<Phase>[];
|
||||
|
||||
private currentPhase: Phase | null;
|
||||
private standbyPhase: Phase | null;
|
||||
@ -397,6 +408,8 @@ export default class BattleScene extends SceneBase {
|
||||
this.conditionalQueue = [];
|
||||
this.phaseQueuePrependSpliceIndex = -1;
|
||||
this.nextCommandPhaseQueue = [];
|
||||
this.dynamicPhaseQueues = [new PostSummonPhasePriorityQueue()];
|
||||
this.dynamicPhaseTypes = [PostSummonPhase];
|
||||
this.eventManager = new TimedEventManager();
|
||||
this.updateGameInfo();
|
||||
initGlobalScene(this);
|
||||
@ -2693,12 +2706,16 @@ export default class BattleScene extends SceneBase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a phase to nextCommandPhaseQueue, as long as boolean passed in is false
|
||||
* Adds a phase to the end of the appropriate queue (dynamic or {@linkcode phaseQueue} / {@linkcode nextCommandPhaseQueue})
|
||||
* @param phase {@linkcode Phase} the phase to add
|
||||
* @param defer boolean on which queue to add to, defaults to false, and adds to phaseQueue
|
||||
* @param defer If `true`, add to {@linkcode nextCommandPhaseQueue} instead of {@linkcode phaseQueue}
|
||||
*/
|
||||
pushPhase(phase: Phase, defer = false): void {
|
||||
(!defer ? this.phaseQueue : this.nextCommandPhaseQueue).push(phase);
|
||||
if (this.getDynamicPhaseType(phase) !== undefined) {
|
||||
this.pushDynamicPhase(phase);
|
||||
} else {
|
||||
(!defer ? this.phaseQueue : this.nextCommandPhaseQueue).push(phase);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2867,13 +2884,14 @@ export default class BattleScene extends SceneBase {
|
||||
* 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: Constructor<Phase>): boolean {
|
||||
appendToPhase(phase: Phase | Phase[], targetPhase: Constructor<Phase>, condition?: (p: Phase) => boolean): boolean {
|
||||
if (!Array.isArray(phase)) {
|
||||
phase = [phase];
|
||||
}
|
||||
const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof targetPhase);
|
||||
const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof targetPhase && (!condition || condition(ph)));
|
||||
|
||||
if (targetIndex !== -1 && this.phaseQueue.length > targetIndex) {
|
||||
this.phaseQueue.splice(targetIndex + 1, 0, ...phase);
|
||||
@ -2884,22 +2902,65 @@ export default class BattleScene extends SceneBase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the first consecutive set of occurences of {@linkcode targetPhase} in {@linkcode phaseQueue}
|
||||
* @param targetPhase The type of phase to search for and sort
|
||||
* @param by A function to compare the phases with
|
||||
* @see {@linkcode Array.sort} for the comparison function
|
||||
* 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`
|
||||
*/
|
||||
sortPhaseType(targetPhase: Constructor<Phase>, by: (a: Phase, b: Phase) => number): void {
|
||||
const startIndex = this.phaseQueue.findIndex(phase => phase instanceof targetPhase);
|
||||
if (startIndex === -1) {
|
||||
public getDynamicPhaseType(phase: Phase | null): DynamicPhaseType | undefined {
|
||||
let phaseType: DynamicPhaseType | undefined;
|
||||
this.dynamicPhaseTypes.forEach((cls, index) => {
|
||||
if (phase instanceof cls) {
|
||||
phaseType = index;
|
||||
}
|
||||
});
|
||||
|
||||
return phaseType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 dynamic queue (not necessarily {@linkcode phase})
|
||||
* @param phase The phase to push
|
||||
*/
|
||||
public pushDynamicPhase(phase: Phase): void {
|
||||
const type = this.getDynamicPhaseType(phase);
|
||||
if (type === undefined) {
|
||||
return;
|
||||
}
|
||||
const endIndex = this.phaseQueue.findIndex((phase, index) => index > startIndex && !(phase instanceof targetPhase));
|
||||
|
||||
const sortedSubset = this.phaseQueue
|
||||
.slice(startIndex, endIndex !== -1 ? endIndex + 1 : this.phaseQueue.length)
|
||||
.sort(by);
|
||||
this.phaseQueue.splice(startIndex, sortedSubset.length, ...sortedSubset);
|
||||
this.pushPhase(new ActivatePriorityQueuePhase(type));
|
||||
this.dynamicPhaseQueues[type].push(phase);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
if (phase) {
|
||||
this.unshiftPhase(phase);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unshifts an {@linkcode ActivatePriorityQueuePhase} for {@linkcode phase}, then pushes {@linkcode phase} to its dynamic queue
|
||||
*
|
||||
* This is the same as {@linkcode pushDynamicPhase}, except the activation phase is unshifted
|
||||
*
|
||||
* {@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);
|
||||
if (type === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.unshiftPhase(new ActivatePriorityQueuePhase(type));
|
||||
this.dynamicPhaseQueues[type].push(phase);
|
||||
}
|
||||
|
||||
/**
|
||||
|
84
src/data/phase-priority-queue.ts
Normal file
84
src/data/phase-priority-queue.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import type { Phase } from "#app/phase";
|
||||
import { ActivatePriorityQueuePhase } from "#app/phases/activate-priority-queue-phase";
|
||||
import { type PostSummonPhase, PostSummonActivateAbilityPhase } from "#app/phases/post-summon-phase";
|
||||
import { Stat } from "#enums/stat";
|
||||
|
||||
/**
|
||||
* Stores a list of {@linkcode Phase}s
|
||||
*
|
||||
* Dynamically updates ordering to always pop the highest "priority", based on implementation of {@linkcode reorder}
|
||||
*/
|
||||
export abstract class PhasePriorityQueue {
|
||||
protected abstract queue: Phase[];
|
||||
|
||||
/**
|
||||
* Sorts the elements in the queue
|
||||
*/
|
||||
public abstract reorder(): void;
|
||||
|
||||
/**
|
||||
* Calls {@linkcode reorder} and shifts the queue
|
||||
* @returns The front element of the queue after sorting
|
||||
*/
|
||||
public pop(): Phase | undefined {
|
||||
this.reorder();
|
||||
return this.queue.shift();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a phase to the queue
|
||||
* @param phase The phase to add
|
||||
*/
|
||||
public push(phase: Phase): void {
|
||||
this.queue.push(phase);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Priority Queue for {@linkcode PostSummonPhase} and {@linkcode PostSummonActivateAbilityPhase}
|
||||
*
|
||||
* Orders phases first by ability priority, then by the {@linkcode Pokemon}'s effective speed
|
||||
*/
|
||||
export class PostSummonPhasePriorityQueue extends PhasePriorityQueue {
|
||||
protected override queue: PostSummonPhase[] = [];
|
||||
|
||||
public override reorder(): void {
|
||||
this.queue.sort((phaseA: PostSummonPhase, phaseB: PostSummonPhase) => {
|
||||
if (phaseA.getPriority() === phaseB.getPriority()) {
|
||||
return phaseB.getPokemon().getEffectiveStat(Stat.SPD) - phaseA.getPokemon().getEffectiveStat(Stat.SPD);
|
||||
}
|
||||
|
||||
return phaseB.getPriority() - phaseA.getPriority();
|
||||
});
|
||||
}
|
||||
|
||||
public override push(phase: PostSummonPhase): void {
|
||||
super.push(phase);
|
||||
this.queueAbilityPhase(phase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues all necessary {@linkcode PostSummonActivateAbilityPhase}s for each pushed {@linkcode PostSummonPhase}
|
||||
* @param phase The {@linkcode PostSummonPhase} that was pushed onto the queue
|
||||
*/
|
||||
private queueAbilityPhase(phase: PostSummonPhase): void {
|
||||
const phasePokemon = phase.getPokemon();
|
||||
|
||||
phasePokemon.getAbilityPriorities().forEach(priority => {
|
||||
this.queue.push(new PostSummonActivateAbilityPhase(phasePokemon.getBattlerIndex(), priority));
|
||||
globalScene.appendToPhase(
|
||||
new ActivatePriorityQueuePhase(DynamicPhaseType.POST_SUMMON),
|
||||
ActivatePriorityQueuePhase,
|
||||
(p: ActivatePriorityQueuePhase) => p.getType() === DynamicPhaseType.POST_SUMMON,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum representation of the phase types held by implementations of {@linkcode PhasePriorityQueue}
|
||||
*/
|
||||
export enum DynamicPhaseType {
|
||||
POST_SUMMON,
|
||||
}
|
22
src/phases/activate-priority-queue-phase.ts
Normal file
22
src/phases/activate-priority-queue-phase.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import type { DynamicPhaseType } from "#app/data/phase-priority-queue";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { Phase } from "#app/phase";
|
||||
|
||||
export class ActivatePriorityQueuePhase extends Phase {
|
||||
private type: DynamicPhaseType;
|
||||
|
||||
constructor(type: DynamicPhaseType) {
|
||||
super();
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
override start() {
|
||||
super.start();
|
||||
globalScene.startDynamicPhaseType(this.type);
|
||||
this.end();
|
||||
}
|
||||
|
||||
public getType(): DynamicPhaseType {
|
||||
return this.type;
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
import type { BattlerIndex } from "#app/battle";
|
||||
import { applyPostSummonAbAttrs, PostSummonAbAttr } from "#app/data/ability";
|
||||
import { PokemonPhase } from "#app/phases/pokemon-phase";
|
||||
|
||||
/**
|
||||
* Phase to apply (post-summon) ability attributes for abilities with nonzero priority
|
||||
*
|
||||
* Priority abilities activate before others and before hazards
|
||||
*
|
||||
* @see Example - {@link https://bulbapedia.bulbagarden.net/wiki/Neutralizing_Gas_(Ability) | Neutralizing Gas}
|
||||
*/
|
||||
export class PostSummonActivateAbilityPhase extends PokemonPhase {
|
||||
private priority: number;
|
||||
|
||||
constructor(battlerIndex: BattlerIndex, priority: number) {
|
||||
super(battlerIndex);
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
start() {
|
||||
super.start();
|
||||
|
||||
applyPostSummonAbAttrs(PostSummonAbAttr, this.getPokemon(), false, (p: number) => p === this.priority);
|
||||
|
||||
this.end();
|
||||
}
|
||||
|
||||
public getPriority() {
|
||||
return this.priority;
|
||||
}
|
||||
}
|
@ -6,42 +6,12 @@ import { StatusEffect } from "#app/enums/status-effect";
|
||||
import { PokemonPhase } from "./pokemon-phase";
|
||||
import { MysteryEncounterPostSummonTag } from "#app/data/battler-tags";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { PostSummonActivateAbilityPhase } from "#app/phases/post-summon-activate-ability-phase";
|
||||
|
||||
export class PostSummonPhase extends PokemonPhase {
|
||||
/** Represents whether or not this phase has already been placed in the correct (speed) order */
|
||||
private ordered: boolean;
|
||||
|
||||
constructor(battlerIndex?: BattlerIndex, ordered = false) {
|
||||
super(battlerIndex);
|
||||
|
||||
this.ordered = ordered;
|
||||
}
|
||||
|
||||
start() {
|
||||
super.start();
|
||||
|
||||
const pokemon = this.getPokemon();
|
||||
let indexAfterPostSummon = globalScene.phaseQueue.findIndex(phase => !(phase instanceof PostSummonPhase));
|
||||
indexAfterPostSummon = indexAfterPostSummon === -1 ? globalScene.phaseQueue.length : indexAfterPostSummon;
|
||||
|
||||
if (
|
||||
!this.ordered &&
|
||||
globalScene.findPhase(phase => phase instanceof PostSummonPhase && phase.getPokemon() !== pokemon)
|
||||
) {
|
||||
globalScene.phaseQueue.splice(indexAfterPostSummon++, 0, new PostSummonPhase(pokemon.getBattlerIndex(), true));
|
||||
|
||||
this.orderPostSummonPhases();
|
||||
this.queueAbilityActivationPhases(indexAfterPostSummon);
|
||||
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.ordered) {
|
||||
applyPostSummonAbAttrs(PostSummonAbAttr, pokemon, false, (p: number) => p > 0);
|
||||
}
|
||||
|
||||
if (pokemon.status?.effect === StatusEffect.TOXIC) {
|
||||
pokemon.status.toxicTurnCount = 0;
|
||||
@ -56,10 +26,6 @@ export class PostSummonPhase extends PokemonPhase {
|
||||
pokemon.lapseTag(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON);
|
||||
}
|
||||
|
||||
if (!this.ordered) {
|
||||
applyPostSummonAbAttrs(PostSummonAbAttr, pokemon, false, (p: number) => p <= 0);
|
||||
}
|
||||
|
||||
const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
||||
for (const p of field) {
|
||||
applyAbAttrs(CommanderAbAttr, p, null, false);
|
||||
@ -68,47 +34,33 @@ export class PostSummonPhase extends PokemonPhase {
|
||||
this.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the {@linkcode PostSummonPhase}s in the queue by effective speed
|
||||
*/
|
||||
private orderPostSummonPhases() {
|
||||
globalScene.sortPhaseType(
|
||||
PostSummonPhase,
|
||||
(phaseA: PostSummonPhase, phaseB: PostSummonPhase) =>
|
||||
phaseB.getPokemon().getEffectiveStat(Stat.SPD) - phaseA.getPokemon().getEffectiveStat(Stat.SPD),
|
||||
);
|
||||
|
||||
for (let i = 0; i < globalScene.phaseQueue.length && globalScene.phaseQueue[i] instanceof PostSummonPhase; i++) {
|
||||
(globalScene.phaseQueue[i] as PostSummonPhase).ordered = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds {@linkcode PostSummonActivateAbilityPhase}s for all {@linkcode PostSummonPhase}s in the queue
|
||||
* @param endIndex The index of the first non-{@linkcode PostSummonPhase} Phase in the queue, or the length if none exists
|
||||
*/
|
||||
private queueAbilityActivationPhases(endIndex: number) {
|
||||
const abilityPhases: PostSummonActivateAbilityPhase[] = [];
|
||||
|
||||
globalScene.phaseQueue.slice(0, endIndex).forEach((phase: PostSummonPhase) => {
|
||||
const phasePokemon = phase.getPokemon();
|
||||
|
||||
phasePokemon
|
||||
.getAbilityPriorities()
|
||||
.forEach(priority =>
|
||||
abilityPhases.push(new PostSummonActivateAbilityPhase(phasePokemon.getBattlerIndex(), priority)),
|
||||
);
|
||||
});
|
||||
|
||||
abilityPhases.sort(
|
||||
(phaseA: PostSummonActivateAbilityPhase, phaseB: PostSummonActivateAbilityPhase) =>
|
||||
phaseB.getPriority() - phaseA.getPriority(),
|
||||
);
|
||||
|
||||
let zeroIndex = abilityPhases.findIndex(phase => phase.getPriority() === 0);
|
||||
zeroIndex = zeroIndex === -1 ? abilityPhases.length : zeroIndex;
|
||||
|
||||
globalScene.unshiftPhase(...abilityPhases.slice(0, zeroIndex));
|
||||
globalScene.phaseQueue.splice(endIndex, 0, ...abilityPhases.slice(zeroIndex));
|
||||
public getPriority() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Phase to apply (post-summon) ability attributes for abilities with nonzero priority
|
||||
*
|
||||
* Priority abilities activate before others and before hazards
|
||||
*
|
||||
* @see Example - {@link https://bulbapedia.bulbagarden.net/wiki/Neutralizing_Gas_(Ability) | Neutralizing Gas}
|
||||
*/
|
||||
export class PostSummonActivateAbilityPhase extends PostSummonPhase {
|
||||
private priority: number;
|
||||
|
||||
constructor(battlerIndex: BattlerIndex, priority: number) {
|
||||
super(battlerIndex);
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
start() {
|
||||
applyPostSummonAbAttrs(PostSummonAbAttr, this.getPokemon(), false, (p: number) => p === this.priority);
|
||||
|
||||
this.end();
|
||||
}
|
||||
|
||||
public override getPriority() {
|
||||
return this.priority;
|
||||
}
|
||||
}
|
||||
|
@ -246,6 +246,6 @@ export class SwitchSummonPhase extends SummonPhase {
|
||||
}
|
||||
|
||||
queuePostSummon(): void {
|
||||
globalScene.unshiftPhase(new PostSummonPhase(this.getPokemon().getBattlerIndex()));
|
||||
globalScene.startDynamicPhase(new PostSummonPhase(this.getPokemon().getBattlerIndex()));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user