[Bug] Use InitEncounterPhase to queue PSPs for new encounters (#6614)

* Use InitEncounterPhase to queue PSPs for new encounters

* Add doc

* Add manual PSP queues
This commit is contained in:
Dean 2025-10-07 20:09:17 -07:00 committed by GitHub
parent d32e112194
commit a4b27eb05e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 95 additions and 105 deletions

View File

@ -3358,6 +3358,7 @@ export class BattleScene extends SceneBase {
this.phaseManager.pushNew("ToggleDoublePositionPhase", true); this.phaseManager.pushNew("ToggleDoublePositionPhase", true);
if (!availablePartyMembers[1].isOnField()) { if (!availablePartyMembers[1].isOnField()) {
this.phaseManager.pushNew("SummonPhase", 1); this.phaseManager.pushNew("SummonPhase", 1);
this.phaseManager.pushNew("PostSummonPhase", 1);
} }
} }

View File

@ -3,6 +3,7 @@ import { globalScene } from "#app/global-scene";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { NON_LEGEND_PARADOX_POKEMON } from "#balance/special-species-groups"; import { NON_LEGEND_PARADOX_POKEMON } from "#balance/special-species-groups";
import type { PokemonSpecies } from "#data/pokemon-species"; import type { PokemonSpecies } from "#data/pokemon-species";
import { BattlerIndex } from "#enums/battler-index";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
@ -324,6 +325,7 @@ async function summonSafariPokemon() {
encounter.misc.safariPokemonRemaining -= 1; encounter.misc.safariPokemonRemaining -= 1;
globalScene.phaseManager.unshiftNew("SummonPhase", 0, false); globalScene.phaseManager.unshiftNew("SummonPhase", 0, false);
globalScene.phaseManager.unshiftNew("PostSummonPhase", BattlerIndex.ENEMY);
encounter.setDialogueToken("pokemonName", getPokemonNameWithAffix(pokemon)); encounter.setDialogueToken("pokemonName", getPokemonNameWithAffix(pokemon));

View File

@ -322,7 +322,9 @@ export class PokemonTurnData {
public statStagesDecreased = false; public statStagesDecreased = false;
public moveEffectiveness: TypeDamageMultiplier | null = null; public moveEffectiveness: TypeDamageMultiplier | null = null;
public combiningPledge?: MoveId; public combiningPledge?: MoveId;
/** The Pokemon was brought in this turn by a switch action (not an intial encounter/summon) */
public switchedInThisTurn = false; public switchedInThisTurn = false;
public summonedThisTurn = false;
public failedRunAway = false; public failedRunAway = false;
public joinedRound = false; public joinedRound = false;
/** Tracker for a pending status effect /** Tracker for a pending status effect

View File

@ -12,7 +12,6 @@ import { DynamicQueueManager } from "#app/dynamic-queue-manager";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { Phase } from "#app/phase"; import type { Phase } from "#app/phase";
import { PhaseTree } from "#app/phase-tree"; import { PhaseTree } from "#app/phase-tree";
import { BattleType } from "#enums/battle-type";
import { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier"; import { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier";
import type { Pokemon } from "#field/pokemon"; import type { Pokemon } from "#field/pokemon";
import type { PokemonMove } from "#moves/pokemon-move"; import type { PokemonMove } from "#moves/pokemon-move";
@ -43,6 +42,7 @@ import { GameOverModifierRewardPhase } from "#phases/game-over-modifier-reward-p
import { GameOverPhase } from "#phases/game-over-phase"; import { GameOverPhase } from "#phases/game-over-phase";
import { HideAbilityPhase } from "#phases/hide-ability-phase"; import { HideAbilityPhase } from "#phases/hide-ability-phase";
import { HidePartyExpBarPhase } from "#phases/hide-party-exp-bar-phase"; import { HidePartyExpBarPhase } from "#phases/hide-party-exp-bar-phase";
import { InitEncounterPhase } from "#phases/init-encounter-phase";
import { LearnMovePhase } from "#phases/learn-move-phase"; import { LearnMovePhase } from "#phases/learn-move-phase";
import { LevelCapPhase } from "#phases/level-cap-phase"; import { LevelCapPhase } from "#phases/level-cap-phase";
import { LevelUpPhase } from "#phases/level-up-phase"; import { LevelUpPhase } from "#phases/level-up-phase";
@ -150,6 +150,7 @@ const PHASES = Object.freeze({
GameOverModifierRewardPhase, GameOverModifierRewardPhase,
HideAbilityPhase, HideAbilityPhase,
HidePartyExpBarPhase, HidePartyExpBarPhase,
InitEncounterPhase,
LearnMovePhase, LearnMovePhase,
LevelCapPhase, LevelCapPhase,
LevelUpPhase, LevelUpPhase,
@ -510,23 +511,6 @@ export class PhaseManager {
this.phaseQueue.addPhase(this.create("FaintPhase", ...args), true); this.phaseQueue.addPhase(this.create("FaintPhase", ...args), true);
} }
/**
* Attempts to add {@linkcode PostSummonPhase}s for the enemy pokemon
*
* This is used to ensure that wild pokemon (which have no {@linkcode SummonPhase}) do not queue a {@linkcode PostSummonPhase}
* until all pokemon are on the field.
*/
public tryAddEnemyPostSummonPhases(): void {
if (
![BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(globalScene.currentBattle.battleType)
&& !this.phaseQueue.exists("SummonPhase")
) {
globalScene.getEnemyField().forEach(p => {
this.pushPhase(new PostSummonPhase(p.getBattlerIndex(), "SummonPhase"));
});
}
}
/** /**
* Create a new phase and queue it to run after all others queued by the currently running phase. * Create a new phase and queue it to run after all others queued by the currently running phase.
* @param phase - The name of the phase to create * @param phase - The name of the phase to create

View File

@ -28,7 +28,7 @@ export class CheckSwitchPhase extends BattlePhase {
// ...if the user is playing in Set Mode // ...if the user is playing in Set Mode
if (globalScene.battleStyle === BattleStyle.SET) { if (globalScene.battleStyle === BattleStyle.SET) {
this.end(true); this.end();
return; return;
} }
@ -45,7 +45,7 @@ export class CheckSwitchPhase extends BattlePhase {
.slice(1) .slice(1)
.filter(p => p.isActive()).length === 0 .filter(p => p.isActive()).length === 0
) { ) {
this.end(true); this.end();
return; return;
} }
@ -55,7 +55,7 @@ export class CheckSwitchPhase extends BattlePhase {
|| pokemon.isTrapped() || pokemon.isTrapped()
|| globalScene.getPlayerField().some(p => p.getTag(BattlerTagType.COMMANDED)) || globalScene.getPlayerField().some(p => p.getTag(BattlerTagType.COMMANDED))
) { ) {
this.end(true); this.end();
return; return;
} }
@ -74,17 +74,10 @@ export class CheckSwitchPhase extends BattlePhase {
}, },
() => { () => {
globalScene.ui.setMode(UiMode.MESSAGE); globalScene.ui.setMode(UiMode.MESSAGE);
this.end(true); this.end();
}, },
); );
}, },
); );
} }
public override end(queuePostSummon = false): void {
if (queuePostSummon) {
globalScene.phaseManager.unshiftNew("PostSummonPhase", this.fieldIndex);
}
super.end();
}
} }

View File

@ -562,41 +562,41 @@ export class EncounterPhase extends BattlePhase {
if (!this.loaded) { if (!this.loaded) {
const availablePartyMembers = globalScene.getPokemonAllowedInBattle(); const availablePartyMembers = globalScene.getPokemonAllowedInBattle();
const minPartySize = globalScene.currentBattle.double ? 2 : 1;
const currentBattle = globalScene.currentBattle;
const checkSwitch =
currentBattle.battleType !== BattleType.TRAINER
&& (currentBattle.waveIndex > 1 || !globalScene.gameMode.isDaily)
&& availablePartyMembers.length > minPartySize;
const checkSwitchIndices: number[] = [];
const phaseManager = globalScene.phaseManager;
if (!availablePartyMembers[0].isOnField()) { if (!availablePartyMembers[0].isOnField()) {
phaseManager.pushNew("SummonPhase", 0, true, false, checkSwitch); globalScene.phaseManager.pushNew("SummonPhase", 0);
} else if (checkSwitch) {
checkSwitchIndices.push(0);
} }
if (currentBattle.double) { if (globalScene.currentBattle.double) {
if (availablePartyMembers.length > 1) { if (availablePartyMembers.length > 1) {
phaseManager.pushNew("ToggleDoublePositionPhase", true); globalScene.phaseManager.pushNew("ToggleDoublePositionPhase", true);
if (!availablePartyMembers[1].isOnField()) { if (!availablePartyMembers[1].isOnField()) {
phaseManager.pushNew("SummonPhase", 1, true, false, checkSwitch); globalScene.phaseManager.pushNew("SummonPhase", 1);
} else if (checkSwitch) {
checkSwitchIndices.push(1);
} }
} }
} else { } else {
if (availablePartyMembers.length > 1 && availablePartyMembers[1].isOnField()) { if (availablePartyMembers.length > 1 && availablePartyMembers[1].isOnField()) {
phaseManager.pushNew("ReturnPhase", 1); globalScene.phaseManager.pushNew("ReturnPhase", 1);
}
globalScene.phaseManager.pushNew("ToggleDoublePositionPhase", false);
}
if (
globalScene.currentBattle.battleType !== BattleType.TRAINER
&& (globalScene.currentBattle.waveIndex > 1 || !globalScene.gameMode.isDaily)
) {
const minPartySize = globalScene.currentBattle.double ? 2 : 1;
if (availablePartyMembers.length > minPartySize) {
globalScene.phaseManager.pushNew("CheckSwitchPhase", 0, globalScene.currentBattle.double);
if (globalScene.currentBattle.double) {
globalScene.phaseManager.pushNew("CheckSwitchPhase", 1, globalScene.currentBattle.double);
}
} }
phaseManager.pushNew("ToggleDoublePositionPhase", false);
} }
checkSwitchIndices.forEach(i => {
phaseManager.pushNew("CheckSwitchPhase", i, globalScene.currentBattle.double);
});
} }
handleTutorial(Tutorial.Access_Menu).then(() => super.end()); handleTutorial(Tutorial.Access_Menu).then(() => super.end());
globalScene.phaseManager.pushNew("InitEncounterPhase");
} }
tryOverrideForBattleSpec(): boolean { tryOverrideForBattleSpec(): boolean {

View File

@ -85,12 +85,19 @@ export class GameOverPhase extends BattlePhase {
globalScene.phaseManager.pushNew("EncounterPhase", true); globalScene.phaseManager.pushNew("EncounterPhase", true);
const availablePartyMembers = globalScene.getPokemonAllowedInBattle().length; const availablePartyMembers = globalScene.getPokemonAllowedInBattle().length;
const checkSwitch =
globalScene.currentBattle.waveIndex > 1 globalScene.phaseManager.pushNew("SummonPhase", 0);
&& globalScene.currentBattle.battleType !== BattleType.TRAINER;
globalScene.phaseManager.pushNew("SummonPhase", 0, true, false, checkSwitch);
if (globalScene.currentBattle.double && availablePartyMembers > 1) { if (globalScene.currentBattle.double && availablePartyMembers > 1) {
globalScene.phaseManager.pushNew("SummonPhase", 1, true, false, checkSwitch); globalScene.phaseManager.pushNew("SummonPhase", 1);
}
if (
globalScene.currentBattle.waveIndex > 1
&& globalScene.currentBattle.battleType !== BattleType.TRAINER
) {
globalScene.phaseManager.pushNew("CheckSwitchPhase", 0, globalScene.currentBattle.double);
if (globalScene.currentBattle.double && availablePartyMembers > 1) {
globalScene.phaseManager.pushNew("CheckSwitchPhase", 1, globalScene.currentBattle.double);
}
} }
globalScene.ui.fadeIn(1250); globalScene.ui.fadeIn(1250);

View File

@ -0,0 +1,20 @@
import { globalScene } from "#app/global-scene";
import { Phase } from "#app/phase";
/**
* Phase to handle actions on a new encounter that must take place after other setup
* (i.e. queue {@linkcode PostSummonPhase}s)
*/
export class InitEncounterPhase extends Phase {
public override readonly phaseName = "InitEncounterPhase";
public override start(): void {
for (const pokemon of globalScene.getField(true)) {
if (pokemon.isEnemy() || pokemon.turnData.summonedThisTurn) {
globalScene.phaseManager.unshiftNew("PostSummonPhase", pokemon.getBattlerIndex());
}
}
super.end();
}
}

View File

@ -413,26 +413,16 @@ export class MysteryEncounterBattlePhase extends Phase {
} }
const availablePartyMembers = globalScene.getPlayerParty().filter(p => p.isAllowedInBattle()); const availablePartyMembers = globalScene.getPlayerParty().filter(p => p.isAllowedInBattle());
const minPartySize = globalScene.currentBattle.double ? 2 : 1;
const checkSwitch =
encounterMode !== MysteryEncounterMode.TRAINER_BATTLE
&& !this.disableSwitch
&& availablePartyMembers.length > minPartySize;
const checkSwitchIndices: number[] = [];
if (!availablePartyMembers[0].isOnField()) { if (!availablePartyMembers[0].isOnField()) {
globalScene.phaseManager.pushNew("SummonPhase", 0, true, false, checkSwitch); globalScene.phaseManager.pushNew("SummonPhase", 0);
} else if (checkSwitch) {
checkSwitchIndices.push(0);
} }
if (globalScene.currentBattle.double) { if (globalScene.currentBattle.double) {
if (availablePartyMembers.length > 1) { if (availablePartyMembers.length > 1) {
globalScene.phaseManager.pushNew("ToggleDoublePositionPhase", true); globalScene.phaseManager.pushNew("ToggleDoublePositionPhase", true);
if (!availablePartyMembers[1].isOnField()) { if (!availablePartyMembers[1].isOnField()) {
globalScene.phaseManager.pushNew("SummonPhase", 1, true, false, checkSwitch); globalScene.phaseManager.pushNew("SummonPhase", 1);
} else if (checkSwitch) {
checkSwitchIndices.push(1);
} }
} }
} else { } else {
@ -443,9 +433,17 @@ export class MysteryEncounterBattlePhase extends Phase {
globalScene.phaseManager.pushNew("ToggleDoublePositionPhase", false); globalScene.phaseManager.pushNew("ToggleDoublePositionPhase", false);
} }
checkSwitchIndices.forEach(i => { if (encounterMode !== MysteryEncounterMode.TRAINER_BATTLE && !this.disableSwitch) {
globalScene.phaseManager.pushNew("CheckSwitchPhase", i, globalScene.currentBattle.double); const minPartySize = globalScene.currentBattle.double ? 2 : 1;
}); if (availablePartyMembers.length > minPartySize) {
globalScene.phaseManager.pushNew("CheckSwitchPhase", 0, globalScene.currentBattle.double);
if (globalScene.currentBattle.double) {
globalScene.phaseManager.pushNew("CheckSwitchPhase", 1, globalScene.currentBattle.double);
}
}
}
globalScene.phaseManager.pushNew("InitEncounterPhase");
this.end(); this.end();
} }

View File

@ -1,23 +1,14 @@
import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { applyAbAttrs } from "#abilities/apply-ab-attrs";
import type { PhaseString } from "#app/@types/phase-types";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { EntryHazardTag } from "#data/arena-tag"; import { EntryHazardTag } from "#data/arena-tag";
import { MysteryEncounterPostSummonTag } from "#data/battler-tags"; import { MysteryEncounterPostSummonTag } from "#data/battler-tags";
import { ArenaTagType } from "#enums/arena-tag-type"; import { ArenaTagType } from "#enums/arena-tag-type";
import type { BattlerIndex } from "#enums/battler-index";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { PokemonPhase } from "#phases/pokemon-phase"; import { PokemonPhase } from "#phases/pokemon-phase";
export class PostSummonPhase extends PokemonPhase { export class PostSummonPhase extends PokemonPhase {
public readonly phaseName = "PostSummonPhase"; public readonly phaseName = "PostSummonPhase";
/** Used to determine whether to push or unshift {@linkcode PostSummonActivateAbilityPhase}s */
public readonly source: PhaseString;
constructor(battlerIndex?: BattlerIndex | number, source: PhaseString = "SwitchSummonPhase") {
super(battlerIndex);
this.source = source;
}
start() { start() {
super.start(); super.start();

View File

@ -17,13 +17,11 @@ export class SummonPhase extends PartyMemberPokemonPhase {
// The union type is needed to keep typescript happy as these phases extend from SummonPhase // The union type is needed to keep typescript happy as these phases extend from SummonPhase
public readonly phaseName: "SummonPhase" | "SummonMissingPhase" | "SwitchSummonPhase" | "ReturnPhase" = "SummonPhase"; public readonly phaseName: "SummonPhase" | "SummonMissingPhase" | "SwitchSummonPhase" | "ReturnPhase" = "SummonPhase";
private readonly loaded: boolean; private readonly loaded: boolean;
private readonly checkSwitch: boolean;
constructor(fieldIndex: number, player = true, loaded = false, checkSwitch = false) { constructor(fieldIndex: number, player = true, loaded = false) {
super(fieldIndex, player); super(fieldIndex, player);
this.loaded = loaded; this.loaded = loaded;
this.checkSwitch = checkSwitch;
} }
start() { start() {
@ -279,14 +277,6 @@ export class SummonPhase extends PartyMemberPokemonPhase {
pokemon.resetTurnData(); pokemon.resetTurnData();
if (this.checkSwitch) {
globalScene.phaseManager.pushNew(
"CheckSwitchPhase",
this.getPokemon().getFieldIndex(),
globalScene.currentBattle.double,
);
}
if ( if (
!this.loaded !this.loaded
|| [BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(globalScene.currentBattle.battleType) || [BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(globalScene.currentBattle.battleType)
@ -298,11 +288,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
} }
queuePostSummon(): void { queuePostSummon(): void {
if (!this.checkSwitch) { this.getPokemon().turnData.summonedThisTurn = true;
globalScene.phaseManager.pushNew("PostSummonPhase", this.getPokemon().getBattlerIndex(), this.phaseName);
}
globalScene.phaseManager.tryAddEnemyPostSummonPhases();
} }
end() { end() {

View File

@ -313,15 +313,23 @@ export class TitlePhase extends Phase {
if (this.loaded) { if (this.loaded) {
const availablePartyMembers = globalScene.getPokemonAllowedInBattle().length; const availablePartyMembers = globalScene.getPokemonAllowedInBattle().length;
const minPartySize = globalScene.currentBattle.double ? 2 : 1;
const checkSwitch = globalScene.phaseManager.pushNew("SummonPhase", 0, true, true);
if (globalScene.currentBattle.double && availablePartyMembers > 1) {
globalScene.phaseManager.pushNew("SummonPhase", 1, true, true);
}
if (
globalScene.currentBattle.battleType !== BattleType.TRAINER globalScene.currentBattle.battleType !== BattleType.TRAINER
&& (globalScene.currentBattle.waveIndex > 1 || !globalScene.gameMode.isDaily) && (globalScene.currentBattle.waveIndex > 1 || !globalScene.gameMode.isDaily)
&& availablePartyMembers > minPartySize; ) {
const minPartySize = globalScene.currentBattle.double ? 2 : 1;
globalScene.phaseManager.pushNew("SummonPhase", 0, true, true, checkSwitch); if (availablePartyMembers > minPartySize) {
if (globalScene.currentBattle.double && availablePartyMembers > 1) { globalScene.phaseManager.pushNew("CheckSwitchPhase", 0, globalScene.currentBattle.double);
globalScene.phaseManager.pushNew("SummonPhase", 1, true, true, checkSwitch); if (globalScene.currentBattle.double) {
globalScene.phaseManager.pushNew("CheckSwitchPhase", 1, globalScene.currentBattle.double);
}
}
} }
} }

View File

@ -37,9 +37,7 @@ export class PostSummonPhasePriorityQueue extends PokemonPhasePriorityQueue<Post
priority, priority,
idx !== 0, idx !== 0,
); );
phase.source === "SummonPhase" globalScene.phaseManager.unshiftPhase(activateAbilityPhase);
? globalScene.phaseManager.pushPhase(activateAbilityPhase)
: globalScene.phaseManager.unshiftPhase(activateAbilityPhase);
}); });
} }
} }