mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-15 20:09:30 +02:00
Merge c1c66a473b
into cb3ae4ab87
This commit is contained in:
commit
fb967022dd
@ -44,6 +44,33 @@ export abstract class PhasePriorityQueue {
|
|||||||
public clear(): void {
|
public clear(): void {
|
||||||
this.queue.splice(0, this.queue.length);
|
this.queue.splice(0, this.queue.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to remove one or more Phases from the current queue.
|
||||||
|
* @param phaseFilter - The function to select phases for removal
|
||||||
|
* @param removeCount - The maximum number of phases to remove, or `all` to remove all matching phases;
|
||||||
|
* default `1`
|
||||||
|
* @returns The number of successfully removed phases
|
||||||
|
* @todo Remove this eventually once the patchwork bug this is used for is fixed
|
||||||
|
*/
|
||||||
|
public tryRemovePhase(phaseFilter: (phase: Phase) => boolean, removeCount: number | "all" = 1): number {
|
||||||
|
if (removeCount === "all") {
|
||||||
|
removeCount = Number.MAX_SAFE_INTEGER;
|
||||||
|
} else if (removeCount < 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let numRemoved = 0;
|
||||||
|
let phaseIndex = this.queue.findIndex(phaseFilter);
|
||||||
|
if (phaseIndex === -1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
while (numRemoved < removeCount && phaseIndex !== -1) {
|
||||||
|
this.queue.splice(phaseIndex, 1);
|
||||||
|
numRemoved++;
|
||||||
|
phaseIndex = this.queue.findIndex(phaseFilter);
|
||||||
|
}
|
||||||
|
return numRemoved;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,6 +106,7 @@ export class PostSummonPhasePriorityQueue extends PhasePriorityQueue {
|
|||||||
private queueAbilityPhase(phase: PostSummonPhase): void {
|
private queueAbilityPhase(phase: PostSummonPhase): void {
|
||||||
const phasePokemon = phase.getPokemon();
|
const phasePokemon = phase.getPokemon();
|
||||||
|
|
||||||
|
console.log(phasePokemon.getNameToRender());
|
||||||
phasePokemon.getAbilityPriorities().forEach((priority, idx) => {
|
phasePokemon.getAbilityPriorities().forEach((priority, idx) => {
|
||||||
this.queue.push(new PostSummonActivateAbilityPhase(phasePokemon.getBattlerIndex(), priority, !!idx));
|
this.queue.push(new PostSummonActivateAbilityPhase(phasePokemon.getBattlerIndex(), priority, !!idx));
|
||||||
globalScene.phaseManager.appendToPhase(
|
globalScene.phaseManager.appendToPhase(
|
||||||
|
@ -355,14 +355,23 @@ export class PhaseManager {
|
|||||||
if (this.phaseQueuePrependSpliceIndex > -1) {
|
if (this.phaseQueuePrependSpliceIndex > -1) {
|
||||||
this.clearPhaseQueueSplice();
|
this.clearPhaseQueueSplice();
|
||||||
}
|
}
|
||||||
if (this.phaseQueuePrepend.length) {
|
this.phaseQueue.unshift(...this.phaseQueuePrepend);
|
||||||
while (this.phaseQueuePrepend.length) {
|
this.phaseQueuePrepend.splice(0);
|
||||||
const poppedPhase = this.phaseQueuePrepend.pop();
|
|
||||||
if (poppedPhase) {
|
const unactivatedConditionalPhases: [() => boolean, Phase][] = [];
|
||||||
this.phaseQueue.unshift(poppedPhase);
|
// Check if there are any conditional phases queued
|
||||||
}
|
for (const [condition, phase] of this.conditionalQueue) {
|
||||||
|
// Evaluate the condition associated with the phase
|
||||||
|
if (condition()) {
|
||||||
|
// If the condition is met, add the phase to the phase queue
|
||||||
|
this.pushPhase(phase);
|
||||||
|
} else {
|
||||||
|
// If the condition is not met, re-add the phase back to the end of the conditional queue
|
||||||
|
unactivatedConditionalPhases.push([condition, phase]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.conditionalQueue = unactivatedConditionalPhases;
|
||||||
|
|
||||||
if (!this.phaseQueue.length) {
|
if (!this.phaseQueue.length) {
|
||||||
this.populatePhaseQueue();
|
this.populatePhaseQueue();
|
||||||
// Clear the conditionalQueue if there are no phases left in the phaseQueue
|
// Clear the conditionalQueue if there are no phases left in the phaseQueue
|
||||||
@ -371,24 +380,6 @@ export class PhaseManager {
|
|||||||
|
|
||||||
this.currentPhase = this.phaseQueue.shift() ?? null;
|
this.currentPhase = this.phaseQueue.shift() ?? null;
|
||||||
|
|
||||||
const unactivatedConditionalPhases: [() => boolean, Phase][] = [];
|
|
||||||
// Check if there are any conditional phases queued
|
|
||||||
while (this.conditionalQueue?.length) {
|
|
||||||
// Retrieve the first conditional phase from the queue
|
|
||||||
const conditionalPhase = this.conditionalQueue.shift();
|
|
||||||
// Evaluate the condition associated with the phase
|
|
||||||
if (conditionalPhase?.[0]()) {
|
|
||||||
// If the condition is met, add the phase to the phase queue
|
|
||||||
this.pushPhase(conditionalPhase[1]);
|
|
||||||
} else if (conditionalPhase) {
|
|
||||||
// If the condition is not met, re-add the phase back to the front of the conditional queue
|
|
||||||
unactivatedConditionalPhases.push(conditionalPhase);
|
|
||||||
} else {
|
|
||||||
console.warn("condition phase is undefined/null!", conditionalPhase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.conditionalQueue.push(...unactivatedConditionalPhases);
|
|
||||||
|
|
||||||
if (this.currentPhase) {
|
if (this.currentPhase) {
|
||||||
console.log(`%cStart Phase ${this.currentPhase.constructor.name}`, "color:green;");
|
console.log(`%cStart Phase ${this.currentPhase.constructor.name}`, "color:green;");
|
||||||
this.currentPhase.start();
|
this.currentPhase.start();
|
||||||
@ -520,6 +511,25 @@ export class PhaseManager {
|
|||||||
this.dynamicPhaseQueues[type].push(phase);
|
this.dynamicPhaseQueues[type].push(phase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to remove one or more Phases from the given DynamicPhaseQueue, removing the equivalent amount of {@linkcode ActivatePriorityQueuePhase}s from the queue.
|
||||||
|
* @param type - The {@linkcode DynamicPhaseType} to check
|
||||||
|
* @param phaseFilter - The function to select phases for removal
|
||||||
|
* @param removeCount - The maximum number of phases to remove, or `all` to remove all matching phases;
|
||||||
|
* default `1`
|
||||||
|
* @todo Remove this eventually once the patchwork bug this is used for is fixed
|
||||||
|
*/
|
||||||
|
public tryRemoveDynamicPhase(
|
||||||
|
type: DynamicPhaseType,
|
||||||
|
phaseFilter: (phase: Phase) => boolean,
|
||||||
|
removeCount: number | "all" = 1,
|
||||||
|
): void {
|
||||||
|
const numRemoved = this.dynamicPhaseQueues[type].tryRemovePhase(phaseFilter, removeCount);
|
||||||
|
for (let x = 0; x < numRemoved; x++) {
|
||||||
|
this.tryRemovePhase(p => p.is("ActivatePriorityQueuePhase"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 DynamicPhaseType} The type of dynamic phase to start
|
||||||
|
@ -2,6 +2,7 @@ import { globalScene } from "#app/global-scene";
|
|||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { BattleStyle } from "#enums/battle-style";
|
import { BattleStyle } from "#enums/battle-style";
|
||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
|
import { DynamicPhaseType } from "#enums/dynamic-phase-type";
|
||||||
import { SwitchType } from "#enums/switch-type";
|
import { SwitchType } from "#enums/switch-type";
|
||||||
import { UiMode } from "#enums/ui-mode";
|
import { UiMode } from "#enums/ui-mode";
|
||||||
import { BattlePhase } from "#phases/battle-phase";
|
import { BattlePhase } from "#phases/battle-phase";
|
||||||
@ -66,6 +67,17 @@ export class CheckSwitchPhase extends BattlePhase {
|
|||||||
UiMode.CONFIRM,
|
UiMode.CONFIRM,
|
||||||
() => {
|
() => {
|
||||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
globalScene.ui.setMode(UiMode.MESSAGE);
|
||||||
|
/*
|
||||||
|
Remove any pending `ActivatePriorityQueuePhase`s for the currently leaving Pokemon produced by the prior `SwitchSummonPhase`.
|
||||||
|
This is required to avoid triggering on-switch abilities twice on initial entrance.
|
||||||
|
TODO: Separate the animations from `SwitchSummonPhase` to another phase and call that on initial switch - this is a band-aid fix
|
||||||
|
TODO: Confirm with @emdeann what the maximum number of these things that gets unshifted can be
|
||||||
|
*/
|
||||||
|
globalScene.phaseManager.tryRemoveDynamicPhase(
|
||||||
|
DynamicPhaseType.POST_SUMMON,
|
||||||
|
p => p.is("PostSummonPhase") && p.getPokemon() === pokemon,
|
||||||
|
4,
|
||||||
|
);
|
||||||
globalScene.phaseManager.unshiftNew("SwitchPhase", SwitchType.INITIAL_SWITCH, this.fieldIndex, false, true);
|
globalScene.phaseManager.unshiftNew("SwitchPhase", SwitchType.INITIAL_SWITCH, this.fieldIndex, false, true);
|
||||||
this.end();
|
this.end();
|
||||||
},
|
},
|
||||||
|
@ -35,13 +35,43 @@ describe("Abilities - Intimidate", () => {
|
|||||||
it("should lower all opponents' ATK by 1 stage on entry and switch", async () => {
|
it("should lower all opponents' ATK by 1 stage on entry and switch", async () => {
|
||||||
await game.classicMode.startBattle([SpeciesId.MIGHTYENA, SpeciesId.POOCHYENA]);
|
await game.classicMode.startBattle([SpeciesId.MIGHTYENA, SpeciesId.POOCHYENA]);
|
||||||
|
|
||||||
|
const [mightyena, poochyena] = game.scene.getPlayerParty();
|
||||||
|
|
||||||
const enemy = game.field.getEnemyPokemon();
|
const enemy = game.field.getEnemyPokemon();
|
||||||
expect(enemy.getStatStage(Stat.ATK)).toBe(-1);
|
expect(enemy.getStatStage(Stat.ATK)).toBe(-1);
|
||||||
|
expect(mightyena).toHaveAbilityApplied(AbilityId.INTIMIDATE);
|
||||||
|
|
||||||
game.doSwitchPokemon(1);
|
game.doSwitchPokemon(1);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(poochyena.isActive()).toBe(true);
|
||||||
expect(enemy.getStatStage(Stat.ATK)).toBe(-2);
|
expect(enemy.getStatStage(Stat.ATK)).toBe(-2);
|
||||||
|
expect(poochyena).toHaveAbilityApplied(AbilityId.INTIMIDATE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should trigger once on initial switch prompt without cancelling opposing abilities", async () => {
|
||||||
|
await game.classicMode.runToSummon([SpeciesId.MIGHTYENA, SpeciesId.POOCHYENA]);
|
||||||
|
await game.classicMode.startBattleWithSwitch(1);
|
||||||
|
|
||||||
|
const [poochyena, mightyena] = game.scene.getPlayerParty();
|
||||||
|
expect(poochyena.species.speciesId).toBe(SpeciesId.POOCHYENA);
|
||||||
|
|
||||||
|
const enemy = game.field.getEnemyPokemon();
|
||||||
|
expect(enemy).toHaveStatStage(Stat.ATK, -1);
|
||||||
|
expect(poochyena).toHaveStatStage(Stat.ATK, -1);
|
||||||
|
|
||||||
|
expect(poochyena).toHaveAbilityApplied(AbilityId.INTIMIDATE);
|
||||||
|
expect(mightyena).not.toHaveAbilityApplied(AbilityId.INTIMIDATE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should activate on reload with single party", async () => {
|
||||||
|
await game.classicMode.startBattle([SpeciesId.MIGHTYENA]);
|
||||||
|
|
||||||
|
expect(game.field.getEnemyPokemon()).toHaveStatStage(Stat.ATK, -1);
|
||||||
|
|
||||||
|
await game.reload.reloadSession();
|
||||||
|
|
||||||
|
expect(game.field.getEnemyPokemon()).toHaveStatStage(Stat.ATK, -1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should lower ATK of all opponents in a double battle", async () => {
|
it("should lower ATK of all opponents in a double battle", async () => {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { getGameMode } from "#app/game-mode";
|
import { getGameMode } from "#app/game-mode";
|
||||||
import overrides from "#app/overrides";
|
import overrides from "#app/overrides";
|
||||||
import { BattleStyle } from "#enums/battle-style";
|
import { BattleStyle } from "#enums/battle-style";
|
||||||
|
import { Button } from "#enums/buttons";
|
||||||
import { GameModes } from "#enums/game-modes";
|
import { GameModes } from "#enums/game-modes";
|
||||||
import { Nature } from "#enums/nature";
|
import { Nature } from "#enums/nature";
|
||||||
import type { SpeciesId } from "#enums/species-id";
|
import type { SpeciesId } from "#enums/species-id";
|
||||||
@ -100,4 +101,33 @@ export class ClassicModeHelper extends GameManagerHelper {
|
|||||||
await this.game.phaseInterceptor.to(CommandPhase);
|
await this.game.phaseInterceptor.to(CommandPhase);
|
||||||
console.log("==================[New Turn]==================");
|
console.log("==================[New Turn]==================");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queue inputs to switch at the start of the next battle, and then start it.
|
||||||
|
* @param pokemonIndex - The 0-indexed position of the party pokemon to switch to.
|
||||||
|
* Should never be called with 0 as that will select the currently active pokemon and freeze
|
||||||
|
* @returns A Promise that resolves once the battle has been started and the switch prompt resolved
|
||||||
|
* @todo Make this work for double battles
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* await game.classicMode.runToSummon([SpeciesId.MIGHTYENA, SpeciesId.POOCHYENA])
|
||||||
|
* await game.startBattleWithSwitch(1);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public async startBattleWithSwitch(pokemonIndex: number): Promise<void> {
|
||||||
|
this.game.scene.battleStyle = BattleStyle.SWITCH;
|
||||||
|
this.game.onNextPrompt(
|
||||||
|
"CheckSwitchPhase",
|
||||||
|
UiMode.CONFIRM,
|
||||||
|
() => {
|
||||||
|
this.game.scene.ui.getHandler().setCursor(0);
|
||||||
|
this.game.scene.ui.getHandler().processInput(Button.ACTION);
|
||||||
|
},
|
||||||
|
() => this.game.isCurrentPhase("CommandPhase") || this.game.isCurrentPhase("TurnInitPhase"),
|
||||||
|
);
|
||||||
|
this.game.doSelectPartyPokemon(pokemonIndex);
|
||||||
|
|
||||||
|
await this.game.phaseInterceptor.to("CommandPhase");
|
||||||
|
console.log("==================[New Battle (Initial Switch)]==================");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user