Added toTitlePhase; documented phase manager

This commit is contained in:
Bertie690 2025-07-18 13:06:08 -04:00
parent def80f2e10
commit 496d8a4c5b
7 changed files with 170 additions and 119 deletions

View File

@ -250,7 +250,7 @@ export class BattleScene extends SceneBase {
* - 0 = 'Switch' * - 0 = 'Switch'
* - 1 = 'Set' - The option to switch the active pokemon at the start of a battle will not display. * - 1 = 'Set' - The option to switch the active pokemon at the start of a battle will not display.
*/ */
public battleStyle: number = BattleStyle.SWITCH; public battleStyle: BattleStyle = BattleStyle.SWITCH;
/** /**
* Defines whether or not to show type effectiveness hints * Defines whether or not to show type effectiveness hints
@ -669,10 +669,7 @@ export class BattleScene extends SceneBase {
).then(() => loadMoveAnimAssets(defaultMoves, true)), ).then(() => loadMoveAnimAssets(defaultMoves, true)),
this.initStarterColors(), this.initStarterColors(),
]).then(() => { ]).then(() => {
this.phaseManager.pushNew("LoginPhase"); this.phaseManager.toTitleScreen("addLogin");
this.phaseManager.pushNew("TitlePhase");
this.phaseManager.shiftPhase();
}); });
} }
@ -713,16 +710,16 @@ export class BattleScene extends SceneBase {
if (expSpriteKeys.size > 0) { if (expSpriteKeys.size > 0) {
return; return;
} }
this.cachedFetch("./exp-sprites.json") const res = await this.cachedFetch("./exp-sprites.json");
.then(res => res.json()) const keys = await res.json();
.then(keys => { if (!Array.isArray(keys)) {
if (Array.isArray(keys)) { throw new Error("EXP Sprites were not array when fetched!");
for (const key of keys) {
expSpriteKeys.add(key);
} }
// TODO: Optimize this
for (const k of keys) {
expSpriteKeys.add(k);
} }
Promise.resolve();
});
} }
/** /**
@ -1280,13 +1277,12 @@ export class BattleScene extends SceneBase {
duration: 250, duration: 250,
ease: "Sine.easeInOut", ease: "Sine.easeInOut",
onComplete: () => { onComplete: () => {
this.phaseManager.clearPhaseQueue();
this.ui.freeUIData(); this.ui.freeUIData();
this.uiContainer.remove(this.ui, true); this.uiContainer.remove(this.ui, true);
this.uiContainer.destroy(); this.uiContainer.destroy();
this.children.removeAll(true); this.children.removeAll(true);
this.game.domContainer.innerHTML = ""; this.game.domContainer.innerHTML = "";
// TODO: `launchBattle` calls `reset(false, false, true)`
this.launchBattle(); this.launchBattle();
}, },
}); });

View File

@ -217,13 +217,28 @@ export type PhaseConstructorMap = typeof PHASES;
* PhaseManager is responsible for managing the phases in the battle scene * PhaseManager is responsible for managing the phases in the battle scene
*/ */
export class PhaseManager { export class PhaseManager {
/** PhaseQueue: dequeue/remove the first element to get the next phase */ /**
* A queue of yet-unexecuted {@linkcode Phase}s to be run. \
* Each time the current phase ends, all phases from {@linkcode phaseQueuePrepend} are added
* to the front of this queue and the next phase is started.
*/
public phaseQueue: Phase[] = []; public phaseQueue: Phase[] = [];
public conditionalQueue: Array<[() => boolean, Phase]> = []; /**
/** PhaseQueuePrepend: is a temp storage of what will be added to PhaseQueue */ * A queue of yet-unexecuted {@linkcode Phase}s with conditions for their execution. \
* Each entry is evaluated whenever a new phase starts, being added to the {@linkcode phaseQueue} if the condition is satisfied.
*
*/
public conditionalQueue: Array<[condition: () => boolean, phase: Phase]> = [];
/** A temporary storage of {@linkcode Phase}s */
private phaseQueuePrepend: Phase[] = []; private phaseQueuePrepend: Phase[] = [];
/** overrides default of inserting phases to end of phaseQueuePrepend array. Useful for inserting Phases "out of order" */ /**
* If set, will cause subsequent calls to {@linkcode unshiftPhase} to insert phases at this index in **LIFO** order.
* Useful for inserting Phases "out of order".
*
* Is cleared whenever a phase ends, or when {@linkcode clearPhaseQueueSplice} is called.
* @defaultValue `-1`
*/
private phaseQueuePrependSpliceIndex = -1; private phaseQueuePrependSpliceIndex = -1;
private nextCommandPhaseQueue: Phase[] = []; private nextCommandPhaseQueue: Phase[] = [];
@ -232,6 +247,7 @@ export class PhaseManager {
/** Parallel array to {@linkcode dynamicPhaseQueues} - matches phase types to their queues */ /** Parallel array to {@linkcode dynamicPhaseQueues} - matches phase types to their queues */
private dynamicPhaseTypes: Constructor<Phase>[]; private dynamicPhaseTypes: Constructor<Phase>[];
/** The currently running Phase, or `null` if none have started yet. */
private currentPhase: Phase | null = null; private currentPhase: Phase | null = null;
private standbyPhase: Phase | null = null; private standbyPhase: Phase | null = null;
@ -240,7 +256,33 @@ export class PhaseManager {
this.dynamicPhaseTypes = [PostSummonPhase]; this.dynamicPhaseTypes = [PostSummonPhase];
} }
/**
* Add a new {@linkcode TitlePhase}.
* @param clearPhaseQueue - Whether to clear the phase queue before adding a new {@linkcode TitlePhase}.
* If set to `addLogin`, will add a new {@linkcode LoginPhase} before the {@linkcode TitlePhase}
* (but reset everything else).
* Default `false`
*/
public toTitleScreen(clearPhaseQueue: boolean | "addLogin" = false): void {
if (clearPhaseQueue) {
this.clearAllPhases();
}
if (clearPhaseQueue === "addLogin") {
this.unshiftNew("LoginPhase");
}
this.unshiftNew("TitlePhase");
}
/* Phase Functions */ /* Phase Functions */
/**
* Getter function to return the currently-in-progess {@linkcode Phase}.
* @returns The current Phase, or `null` if no phase is currently running
* (due to the PhaseManager not having been started yet).
*/
// TODO: Investigate if we can drop the `null` from this - it is only ever `null` when the manager hasn't started
// (which should never happen once the animations have loaded)
getCurrentPhase(): Phase | null { getCurrentPhase(): Phase | null {
return this.currentPhase; return this.currentPhase;
} }
@ -255,18 +297,17 @@ export class PhaseManager {
* This method allows deferring the execution of a phase until certain conditions are met, which is useful for handling * This method allows deferring the execution of a phase until certain conditions are met, which is useful for handling
* situations like abilities and entry hazards that depend on specific game states. * situations like abilities and entry hazards that depend on specific game states.
* *
* @param phase - The phase to be added to the conditional queue. * @param phase - The {@linkcode Phase} to add to the conditional queue.
* @param condition - A function that returns a boolean indicating whether the phase should be executed. * @param condition - A function that returns a boolean indicating whether the phase should be executed.
*
*/ */
pushConditionalPhase(phase: Phase, condition: () => boolean): void { pushConditionalPhase(phase: Phase, condition: () => boolean): void {
this.conditionalQueue.push([condition, phase]); this.conditionalQueue.push([condition, phase]);
} }
/** /**
* Adds a phase to nextCommandPhaseQueue, as long as boolean passed in is false * Add a phase to the end of the {@linkcode phaseQueue}.
* @param phase {@linkcode Phase} the phase to add * @param phase - The {@linkcode Phase} to be queued.
* @param defer boolean on which queue to add to, defaults to false, and adds to phaseQueue * @param defer If `true`, will add the phase to {@linkcode nextCommandPhaseQueue} instead of the normal {@linkcode phaseQueue}; default `false`.
*/ */
pushPhase(phase: Phase, defer = false): void { pushPhase(phase: Phase, defer = false): void {
if (this.getDynamicPhaseType(phase) !== undefined) { if (this.getDynamicPhaseType(phase) !== undefined) {
@ -277,8 +318,13 @@ export class PhaseManager {
} }
/** /**
* Adds Phase(s) to the end of phaseQueuePrepend, or at phaseQueuePrependSpliceIndex * Adds one or more phase(s) to the **END** of {@linkcode phaseQueuePrepend}.
* @param phases {@linkcode Phase} the phase(s) to add * If called multiple times, phases will be ran in **FIFO** order.
* @param phases - One or more {@linkcode Phase}s to add.
* @todo Find a better name for this given that "unshift" implies adding to the front.
* @remarks
* If {@linkcode phaseQueuePrependSpliceIndex} is set, the phases will be inserted at that index
* in **LIFO** order.
*/ */
unshiftPhase(...phases: Phase[]): void { unshiftPhase(...phases: Phase[]): void {
if (this.phaseQueuePrependSpliceIndex === -1) { if (this.phaseQueuePrependSpliceIndex === -1) {
@ -296,36 +342,48 @@ export class PhaseManager {
} }
/** /**
* Clears all phase-related stuff, including all phase queues, the current and standby phases, and a splice index * Clears all phase-related stuff, including all phase queues, the current and standby phases, and splice index.
*/ */
clearAllPhases(): void { clearAllPhases(): void {
for (const queue of [this.phaseQueue, this.phaseQueuePrepend, this.conditionalQueue, this.nextCommandPhaseQueue]) { this.clearPhaseQueue();
for (const queue of [this.phaseQueuePrepend, this.conditionalQueue, this.nextCommandPhaseQueue]) {
queue.splice(0, queue.length); queue.splice(0, queue.length);
} }
this.dynamicPhaseQueues.forEach(queue => queue.clear()); this.dynamicPhaseQueues.forEach(queue => {
queue.clear();
});
this.currentPhase = null; this.currentPhase = null;
this.standbyPhase = null; this.standbyPhase = null;
this.clearPhaseQueueSplice(); this.clearPhaseQueueSplice();
} }
/** /**
* Used by function unshiftPhase(), sets index to start inserting at current length instead of the end of the array, useful if phaseQueuePrepend gets longer with Phases * Set {@linkcode phaseQueuePrependSpliceIndex} to the current length of {@linkcode phaseQueuePrepend},
* causing subsequent calls to {@linkcode unshiftPhase} to insert phases in LIFO order.
* @see {@linkcode clearPhaseQueueSplice} to clear queue splice
*/ */
setPhaseQueueSplice(): void { setPhaseQueueSplice(): void {
this.phaseQueuePrependSpliceIndex = this.phaseQueuePrepend.length; this.phaseQueuePrependSpliceIndex = this.phaseQueuePrepend.length;
} }
/** /**
* Resets phaseQueuePrependSpliceIndex to -1, implies that calls to unshiftPhase will insert at end of phaseQueuePrepend * Reset {@linkcode phaseQueuePrependSpliceIndex} to `-1`,
* causing subsequent calls to {@linkcode unshiftPhase} to append phases at the end of {@linkcode phaseQueuePrepend}
* in FIFO order.
* @see {@linkcode setPhaseQueueSplice} to set queue splice
* @remarks
* Is called automatically upon phase end.
*/ */
clearPhaseQueueSplice(): void { clearPhaseQueueSplice(): void {
this.phaseQueuePrependSpliceIndex = -1; this.phaseQueuePrependSpliceIndex = -1;
} }
/** /**
* Is called by each Phase implementations "end()" by default * End the currently running phase and start the next one.
* We dump everything from phaseQueuePrepend to the start of of phaseQueue * We dump everything from {@linkcode phaseQueuePrepend} to the start of {@linkcode phaseQueue},
* then removes first Phase and starts it * then remove the first Phase and start it.
* @remarks
* Called by {@linkcode Phase.end} by default.
*/ */
shiftPhase(): void { shiftPhase(): void {
if (this.standbyPhase) { if (this.standbyPhase) {
@ -334,49 +392,40 @@ export class PhaseManager {
return; return;
} }
if (this.phaseQueuePrependSpliceIndex > -1) {
this.clearPhaseQueueSplice(); this.clearPhaseQueueSplice();
} this.phaseQueue.unshift(...this.phaseQueuePrepend);
if (this.phaseQueuePrepend.length) { this.phaseQueuePrepend = [];
while (this.phaseQueuePrepend.length) {
const poppedPhase = this.phaseQueuePrepend.pop();
if (poppedPhase) {
this.phaseQueue.unshift(poppedPhase);
}
}
}
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
this.conditionalQueue = []; this.conditionalQueue = [];
} }
this.currentPhase = this.phaseQueue.shift() ?? null; const nextPhase = this.phaseQueue.shift();
if (!nextPhase) {
throw new Error("No phases in queue; aborting");
}
this.currentPhase = nextPhase;
const unactivatedConditionalPhases: [() => boolean, Phase][] = []; const unactivatedConditionalPhases: [() => boolean, Phase][] = [];
// Check if there are any conditional phases queued // Check each queued conditional phase, either adding it to the end of the queue (if met)
while (this.conditionalQueue?.length) { // or keeping it on (if not).
// Retrieve the first conditional phase from the queue for (const [condition, phase] of this.conditionalQueue) {
const conditionalPhase = this.conditionalQueue.shift(); if (condition()) {
// Evaluate the condition associated with the phase this.pushPhase(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 { } else {
console.warn("condition phase is undefined/null!", conditionalPhase); unactivatedConditionalPhases.push([condition, phase]);
} }
} }
this.conditionalQueue.push(...unactivatedConditionalPhases); this.conditionalQueue = unactivatedConditionalPhases;
if (this.currentPhase) { console.log(`%cStart Phase ${this.currentPhase.phaseName}`, "color:green;");
console.log(`%cStart Phase ${this.currentPhase.constructor.name}`, "color:green;");
this.currentPhase.start(); this.currentPhase.start();
} }
}
// TODO: Review if we can remove this
overridePhase(phase: Phase): boolean { overridePhase(phase: Phase): boolean {
if (this.standbyPhase) { if (this.standbyPhase) {
return false; return false;
@ -384,7 +433,7 @@ export class PhaseManager {
this.standbyPhase = this.currentPhase; this.standbyPhase = this.currentPhase;
this.currentPhase = phase; this.currentPhase = phase;
console.log(`%cStart Phase ${phase.constructor.name}`, "color:green;"); console.log(`%cStart Phase ${phase.phaseName}`, "color:green;");
phase.start(); phase.start();
return true; return true;
@ -393,34 +442,40 @@ export class PhaseManager {
/** /**
* Find a specific {@linkcode Phase} in the phase queue. * Find a specific {@linkcode Phase} in the phase queue.
* *
* @param phaseFilter filter function to use to find the wanted phase * @param phaseFilter - The predicate function to use to find a queued phase
* @returns the found phase or undefined if none found * @returns The first phase for which {@linkcode phaseFilter} returns `true`, or `undefined` if none match.
*/ */
findPhase<P extends Phase = Phase>(phaseFilter: (phase: P) => boolean): P | undefined { findPhase<P extends Phase = Phase>(phaseFilter: (phase: P) => boolean): P | undefined {
return this.phaseQueue.find(phaseFilter) as P | undefined; return this.phaseQueue.find(phaseFilter) as P | undefined;
} }
tryReplacePhase(phaseFilter: (phase: Phase) => boolean, phase: Phase): boolean { // TODO: This is used exclusively by encore
tryReplacePhase(phaseFilter: (phase: Phase) => boolean, phaseTarget: Phase): boolean {
const phaseIndex = this.phaseQueue.findIndex(phaseFilter); const phaseIndex = this.phaseQueue.findIndex(phaseFilter);
if (phaseIndex > -1) { if (phaseIndex === -1) {
this.phaseQueue[phaseIndex] = phase;
return true;
}
return false; return false;
} }
this.phaseQueue[phaseIndex] = phaseTarget;
tryRemovePhase(phaseFilter: (phase: Phase) => boolean): boolean {
const phaseIndex = this.phaseQueue.findIndex(phaseFilter);
if (phaseIndex > -1) {
this.phaseQueue.splice(phaseIndex, 1);
return true; return true;
} }
return false;
}
/** /**
* Will search for a specific phase in {@linkcode phaseQueuePrepend} via filter, and remove the first result if a match is found. * Search for a specific phase in the {@linkcode phaseQueue} and remove the first matching result.
* @param phaseFilter filter function * @param phaseFilter - The function to filter the phase queue by
* @returns Whether a matching phase was found and removed
*/
tryRemovePhase(phaseFilter: (phase: Phase) => boolean): boolean {
const phaseIndex = this.phaseQueue.findIndex(phaseFilter);
if (phaseIndex === -1) {
return false;
}
this.phaseQueue.splice(phaseIndex, 1);
return true;
}
/**
* Search for a specific phase in {@linkcode phaseQueuePrepend} and remove the first matching result. * @param phaseFilter - The function to filter the phase queue by
* @returns Whether a matching phase was found and removed
*/ */
tryRemoveUnshiftedPhase(phaseFilter: (phase: Phase) => boolean): boolean { tryRemoveUnshiftedPhase(phaseFilter: (phase: Phase) => boolean): boolean {
const phaseIndex = this.phaseQueuePrepend.findIndex(phaseFilter); const phaseIndex = this.phaseQueuePrepend.findIndex(phaseFilter);
@ -432,10 +487,10 @@ export class PhaseManager {
} }
/** /**
* Tries to add the input phase to index before target phase in the phaseQueue, else simply calls unshiftPhase() * Attempt to add the input phase to index before target phase in the phaseQueue, else simply calls unshiftPhase()
* @param phase - The phase to be added * @param phase - The phase to be added
* @param targetPhase - The phase to search for in phaseQueue * @param targetPhase - The phase to search for in phaseQueue
* @returns boolean if a targetPhase was found and added * @returns Whether a targetPhase was found and added
*/ */
prependToPhase(phase: Phase | Phase[], targetPhase: PhaseString): boolean { prependToPhase(phase: Phase | Phase[], targetPhase: PhaseString): boolean {
phase = coerceArray(phase); phase = coerceArray(phase);
@ -452,9 +507,9 @@ export class PhaseManager {
/** /**
* Tries to add the input phase(s) to index after target phase in the {@linkcode phaseQueue}, else simply calls {@linkcode unshiftPhase()} * 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 phase - One or more {@linkcode Phase}s to be added
* @param targetPhase {@linkcode Phase} the type of phase to search for in {@linkcode phaseQueue} * @param targetPhase - The type of target {@linkcode Phase} phase to search for in {@linkcode phaseQueue}
* @param condition Condition the target phase must meet to be appended to * @param condition - If provided, will only consider target phases passing the condition
* @returns `true` if a `targetPhase` was found to append to * @returns `true` if a `targetPhase` was found to append to
*/ */
appendToPhase(phase: Phase | Phase[], targetPhase: PhaseString, condition?: (p: Phase) => boolean): boolean { appendToPhase(phase: Phase | Phase[], targetPhase: PhaseString, condition?: (p: Phase) => boolean): boolean {
@ -472,7 +527,7 @@ export class PhaseManager {
/** /**
* Checks a phase and returns the matching {@linkcode DynamicPhaseType}, or undefined if it does not match one * Checks a phase and returns the matching {@linkcode DynamicPhaseType}, or undefined if it does not match one
* @param phase The phase to check * @param phase - The {@linkcode Phase} to check
* @returns The corresponding {@linkcode DynamicPhaseType} or `undefined` * @returns The corresponding {@linkcode DynamicPhaseType} or `undefined`
*/ */
public getDynamicPhaseType(phase: Phase | null): DynamicPhaseType | undefined { public getDynamicPhaseType(phase: Phase | null): DynamicPhaseType | undefined {
@ -490,7 +545,7 @@ export class PhaseManager {
* Pushes a phase onto its corresponding dynamic queue and marks the activation point in {@linkcode phaseQueue} * 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}) * The {@linkcode ActivatePriorityQueuePhase} will run the top phase in the dynamic queue (not necessarily {@linkcode phase})
* @param phase The phase to push * @param phase The {@linkcode Phase} to push
*/ */
public pushDynamicPhase(phase: Phase): void { public pushDynamicPhase(phase: Phase): void {
const type = this.getDynamicPhaseType(phase); const type = this.getDynamicPhaseType(phase);
@ -504,7 +559,7 @@ export class PhaseManager {
/** /**
* 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 - The {@linkcode DynamicPhaseType} corresponding to the dynamic phase being started
*/ */
public startDynamicPhaseType(type: DynamicPhaseType): void { public startDynamicPhaseType(type: DynamicPhaseType): void {
const phase = this.dynamicPhaseQueues[type].pop(); const phase = this.dynamicPhaseQueues[type].pop();
@ -519,8 +574,7 @@ export class PhaseManager {
* This is the same as {@linkcode pushDynamicPhase}, except the activation phase is unshifted * 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) * {@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 * @param phase - The {@linkcode Phase} to add
* @returns
*/ */
public startDynamicPhase(phase: Phase): void { public startDynamicPhase(phase: Phase): void {
const type = this.getDynamicPhaseType(phase); const type = this.getDynamicPhaseType(phase);
@ -542,7 +596,7 @@ export class PhaseManager {
* *
* @see {@linkcode MessagePhase} for more details on the parameters * @see {@linkcode MessagePhase} for more details on the parameters
*/ */
queueMessage( public queueMessage(
message: string, message: string,
callbackDelay?: number | null, callbackDelay?: number | null,
prompt?: boolean | null, prompt?: boolean | null,
@ -567,7 +621,7 @@ export class PhaseManager {
*/ */
public queueAbilityDisplay(pokemon: Pokemon, passive: boolean, show: boolean): void { public queueAbilityDisplay(pokemon: Pokemon, passive: boolean, show: boolean): void {
this.unshiftPhase(show ? new ShowAbilityPhase(pokemon.getBattlerIndex(), passive) : new HideAbilityPhase()); this.unshiftPhase(show ? new ShowAbilityPhase(pokemon.getBattlerIndex(), passive) : new HideAbilityPhase());
this.clearPhaseQueueSplice(); this.clearPhaseQueueSplice(); // TODO: Is this necessary?
} }
/** /**
@ -580,14 +634,15 @@ export class PhaseManager {
} }
/** /**
* Moves everything from nextCommandPhaseQueue to phaseQueue (keeping order) * Moves everything from nextCommandPhaseQueue to phaseQueue (keeping order),
* then adds a new {@linkcode TurnInitPhase} to start a new turn.
*/ */
private populatePhaseQueue(): void { private populatePhaseQueue(): void {
if (this.nextCommandPhaseQueue.length) { if (this.nextCommandPhaseQueue.length) {
this.phaseQueue.push(...this.nextCommandPhaseQueue); this.phaseQueue.push(...this.nextCommandPhaseQueue);
this.nextCommandPhaseQueue.splice(0, this.nextCommandPhaseQueue.length); this.nextCommandPhaseQueue.splice(0, this.nextCommandPhaseQueue.length);
} }
this.phaseQueue.push(new TurnInitPhase()); this.pushNew("TurnInitPhase");
} }
/** /**

View File

@ -24,10 +24,11 @@ export class SelectStarterPhase extends Phase {
globalScene.ui.setMode(UiMode.STARTER_SELECT, (starters: Starter[]) => { globalScene.ui.setMode(UiMode.STARTER_SELECT, (starters: Starter[]) => {
globalScene.ui.clearText(); globalScene.ui.clearText();
globalScene.ui.setMode(UiMode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: number) => { globalScene.ui.setMode(UiMode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: number) => {
// If clicking cancel, back out to title screen
if (slotId === -1) { if (slotId === -1) {
globalScene.phaseManager.clearPhaseQueue(); globalScene.phaseManager.toTitleScreen(true);
globalScene.phaseManager.pushNew("TitlePhase"); this.end();
return this.end(); return;
} }
globalScene.sessionSlotId = slotId; globalScene.sessionSlotId = slotId;
this.initBattle(starters); this.initBattle(starters);

View File

@ -114,11 +114,11 @@ export class TitlePhase extends Phase {
}); });
} }
} }
// Cancel button = back to title
options.push({ options.push({
label: i18next.t("menu:cancel"), label: i18next.t("menu:cancel"),
handler: () => { handler: () => {
globalScene.phaseManager.clearPhaseQueue(); globalScene.phaseManager.toTitleScreen(true);
globalScene.phaseManager.pushNew("TitlePhase");
super.end(); super.end();
return true; return true;
}, },
@ -191,11 +191,12 @@ export class TitlePhase extends Phase {
initDailyRun(): void { initDailyRun(): void {
globalScene.ui.clearText(); globalScene.ui.clearText();
globalScene.ui.setMode(UiMode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: number) => { globalScene.ui.setMode(UiMode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: number) => {
globalScene.phaseManager.clearPhaseQueue();
if (slotId === -1) { if (slotId === -1) {
globalScene.phaseManager.pushNew("TitlePhase"); globalScene.phaseManager.toTitleScreen(true);
return super.end(); super.end();
return;
} }
globalScene.phaseManager.clearPhaseQueue();
globalScene.sessionSlotId = slotId; globalScene.sessionSlotId = slotId;
const generateDaily = (seed: string) => { const generateDaily = (seed: string) => {

View File

@ -381,8 +381,7 @@ export class GameChallengesUiHandler extends UiHandler {
this.cursorObj?.setVisible(true); this.cursorObj?.setVisible(true);
this.updateChallengeArrows(this.startCursor.visible); this.updateChallengeArrows(this.startCursor.visible);
} else { } else {
globalScene.phaseManager.clearPhaseQueue(); globalScene.phaseManager.toTitleScreen(true);
globalScene.phaseManager.pushNew("TitlePhase");
globalScene.phaseManager.getCurrentPhase()?.end(); globalScene.phaseManager.getCurrentPhase()?.end();
} }
success = true; success = true;

View File

@ -4302,7 +4302,10 @@ export class StarterSelectUiHandler extends MessageUiHandler {
return true; return true;
} }
tryExit(): boolean { /**
* Attempt to back out of the starter selection screen into the appropriate parent modal
*/
tryExit(): void {
this.blockInput = true; this.blockInput = true;
const ui = this.getUi(); const ui = this.getUi();
@ -4316,12 +4319,13 @@ export class StarterSelectUiHandler extends MessageUiHandler {
UiMode.CONFIRM, UiMode.CONFIRM,
() => { () => {
ui.setMode(UiMode.STARTER_SELECT); ui.setMode(UiMode.STARTER_SELECT);
// Non-challenge modes go directly back to title, while challenge modes go to the selection screen.
if (!globalScene.gameMode.isChallenge) {
globalScene.phaseManager.toTitleScreen(true);
} else {
globalScene.phaseManager.clearPhaseQueue(); globalScene.phaseManager.clearPhaseQueue();
if (globalScene.gameMode.isChallenge) {
globalScene.phaseManager.pushNew("SelectChallengePhase"); globalScene.phaseManager.pushNew("SelectChallengePhase");
globalScene.phaseManager.pushNew("EncounterPhase"); globalScene.phaseManager.pushNew("EncounterPhase");
} else {
globalScene.phaseManager.pushNew("TitlePhase");
} }
this.clearText(); this.clearText();
globalScene.phaseManager.getCurrentPhase()?.end(); globalScene.phaseManager.getCurrentPhase()?.end();
@ -4332,8 +4336,6 @@ export class StarterSelectUiHandler extends MessageUiHandler {
19, 19,
); );
}); });
return true;
} }
tryStart(manualTrigger = false): boolean { tryStart(manualTrigger = false): boolean {

View File

@ -103,12 +103,9 @@ export class GameManager {
if (!firstTimeScene) { if (!firstTimeScene) {
this.scene.reset(false, true); this.scene.reset(false, true);
(this.scene.ui.handlers[UiMode.STARTER_SELECT] as StarterSelectUiHandler).clearStarterPreferences(); (this.scene.ui.handlers[UiMode.STARTER_SELECT] as StarterSelectUiHandler).clearStarterPreferences();
this.scene.phaseManager.clearAllPhases();
// Must be run after phase interceptor has been initialized. // Must be run after phase interceptor has been initialized.
this.scene.phaseManager.toTitlePhase("addLogin");
this.scene.phaseManager.pushNew("LoginPhase");
this.scene.phaseManager.pushNew("TitlePhase");
this.scene.phaseManager.shiftPhase(); this.scene.phaseManager.shiftPhase();
this.gameWrapper.scene = this.scene; this.gameWrapper.scene = this.scene;