Compare commits

...

18 Commits

Author SHA1 Message Date
Bertie690
5707a0c4a1
Merge b4dd435d3e into 076ef81691 2025-08-14 00:28:25 -04:00
Sirz Benjie
076ef81691
[Bug] [UI/UX] [Beta] Fix icons not showing in save slot selection (#6262)
Fix icons not showing in save slot selection
2025-08-13 20:49:46 -05:00
fabske0
23271901cf
[Docs] Add locale key naming info to localization.md (#6260) 2025-08-14 01:12:00 +00:00
Inês Simões
1517e0512e
[UI/UX] [Feature] Save Management Tool (Rename/Delete Saves) (#5978)
* Implement Name Run Feat
Modified load session ui component, adding a submenu when selecting a 3
slot. This menu has 4 options:
Load Game -> Behaves as before, allowing the player to continue
progress from the last saved state in the slot.

Rename Run -> Overlays a rename form, allowing the player to type a
name for the run, checking for string validity, with the option to
cancel or confirm (Rename).

Delete Run -> Prompts user confirmation to delete save data, removing
the current save slot from the users save data.

Cancel -> Hides menu overlay.

Modified game data to implement a function to accept and store
runNameText to the users data.

Modified run info ui component, to display the chosen name when
viewing run information.

Example: When loading the game, the user can choose the Load Game
menu option, then select a save slot, prompting the menu, then choose
"Rename Run" and type the name "Monotype Water Run" then confirm,
thus being able to better organize their save files.

Signed-off-by: Matheus Alves <matheus.r.noya.alves@tecnico.ulisboa.pt>
Co-authored-by: Inês Simões <ines.p.simoes@tecnico.ulisboa.pt>

* Implement Rename Input Design and Tests for Name Run Feat
Created a test to verify Name Run Feature behaviour in the
backend (rename_run.test.ts), checking possible errors and
 expected behaviours.

Created a UiHandler RenameRunFormUiHandler
(rename-run-ui-handler.ts), creating a frontend input
overlay for the Name Run Feature.

Signed-off-by: Matheus Alves <matheus.r.noya.alves@tecnico.ulisboa.pt>
Co-authored-by: Inês Simões <ines.p.simoes@tecnico.ulisboa.pt>

* Fixed formating and best practices issues:
Rewrote renameSession to be more inline with other
API call funtions, removed debugging comments and
whitespaces.

Signed-off-by: Matheus Alves <matheus.r.noya.alves@tecnico.ulisboa.pt>
Co-authored-by: Inês Simões <ines.p.simoes@tecnico.ulisboa.pt>

* Minor Sanitization for aesthetics
Deleting the input when closing the overlay for
aesthetics purpose

Signed-off-by: Matheus Alves <matheus.r.noya.alves@tecnico.ulisboa.pt>
Co-authored-by: Inês Simões <ines.p.simoes@tecnico.ulisboa.pt>

* Fixed minor rebase alterations.

Signed-off-by: Matheus Alves matheus.r.noya.alves@tecnico.ulisboa.pt
Co-authored-by: Inês Simões ines.p.simoes@tecnico.ulisboa.pt

* Implemented Default Name Logic
Altered logic in save-slot-select-ui-handler.ts to
support default naming of runs based on the run
game mode with decideFallback function.

In game-data.ts, to prevent inconsistent naming,
added check for unfilled input, ignoring empty
rename requests.

Signed-off-by: Matheus Alves matheus.r.noya.alves@tecnico.ulisboa.pt
Co-authored-by: Inês Simões ines.p.simoes@tecnico.ulisboa.pt

* Replace fallback name logic: use first active challenge instead
of game mode

Previously used game mode as the fallback name, updated to use the
first active challenge instead (e.g. Monogen or Mono Type), which
better reflects the run's theme.
Signed-off-by: Matheus Alves <matheus.r.noya.alves@tecnico.ulisboa.pt>
Co-authored-by: Inês Simões <ines.p.simoes@tecnico.ulisboa.pt>

* Rebasing and conflict resolution

Signed-off-by: Matheus Alves <matheus.r.noya.alves@tecnico.ulisboa.pt>
Co-authored-by: Inês Simões <ines.p.simoes@tecnico.ulisboa.pt>

* Lint fix

Signed-off-by: Matheus Alves <matheus.r.noya.alves@tecnico.ulisboa.pt>
Co-authored-by: Inês Simões <ines.p.simoes@tecnico.ulisboa.pt>

* Minor compile fix

* Dependency resolved

* Format name respected

* Add all active challenges to default challenge session name if possible

If more than 3 challenges are active, only the first 3 are added
to the name (to prevent the text going off-screen)
and then "..." is appended to the end to indicate
there were more challenges active than the ones listed

* Allow deleting malformed sessions

---------

Signed-off-by: Matheus Alves <matheus.r.noya.alves@tecnico.ulisboa.pt>
Signed-off-by: Matheus Alves matheus.r.noya.alves@tecnico.ulisboa.pt
Co-authored-by: Matheus Alves <matheus.r.noya.alves@tecnico.ulisboa.pt>
Co-authored-by: Wlowscha <54003515+Wlowscha@users.noreply.github.com>
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com>
2025-08-13 20:08:12 -05:00
NightKev
b4dd435d3e
Re-add ! and add TODO comment instead 2025-08-11 05:57:07 -07:00
Bertie690
817654341a
Update phase-interceptor.ts
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
2025-08-11 08:47:55 -04:00
NightKev
d969d9f67b
Merge branch 'beta' into deprecated-func 2025-08-11 05:36:37 -07:00
Wlowscha
a9040f0048
Merge branch 'beta' into deprecated-func 2025-08-02 22:07:59 +02:00
Bertie690
5d7a3d28ff dddddd 2025-08-01 21:39:09 -04:00
Bertie690
040eaf8632 fixed another dumb error bc me big dumb bozo 2025-08-01 21:30:35 -04:00
Bertie690
c318a0cc59 Fixed tests 2025-08-01 21:25:12 -04:00
Bertie690
20582b43c0 maybe fixed? 2025-08-01 21:18:05 -04:00
Bertie690
c737a9206f Perhaps fixed things? 2025-08-01 20:52:05 -04:00
Bertie690
152f54ee7a somehow fixed reload bug by making things actively worse 2025-08-01 20:42:25 -04:00
Bertie690
1819712201 Fixed issues and syntax errors 2025-08-01 20:10:57 -04:00
Bertie690
9a19eac9ee Added selectStarterPhase to the end by set mode collection 2025-08-01 19:03:11 -04:00
Bertie690
bca9560f55 Added minor docs to the phase manager + renamed shift to shiftPhase 2025-08-01 18:57:31 -04:00
Bertie690
a1a3526c17 Removed deprecated functions from phase interceptor 2025-08-01 18:38:05 -04:00
44 changed files with 752 additions and 599 deletions

View File

@ -90,9 +90,13 @@ If this feature requires new text, the text should be integrated into the code w
- For any feature pulled from the mainline Pokémon games (e.g. a Move or Ability implementation), it's best practice to include a source link for any added text.
[Poké Corpus](https://abcboy101.github.io/poke-corpus/) is a great resource for finding text from the mainline games; otherwise, a video/picture showing the text being displayed should suffice.
- You should also [notify the current Head of Translation](#notifying-translation) to ensure a fast response.
3. At this point, you may begin [testing locales integration in your main PR](#documenting-locales-changes).
4. The Translation Team will approve the locale PR (after corrections, if necessary), then merge it into `pokerogue-locales`.
5. The Dev Team will approve your main PR for your feature, then merge it into PokéRogue's beta environment.
3. Your locales should use the following format:
- File names should be in `kebab-case`. Example: `trainer-names.json`
- Key names should be in `camelCase`. Example: `aceTrainer`
- If you make use of i18next's inbuilt [context support](https://www.i18next.com/translation-function/context), you need to use `snake_case` for the context key. Example: `aceTrainer_male`
4. At this point, you may begin [testing locales integration in your main PR](#documenting-locales-changes).
5. The Translation Team will approve the locale PR (after corrections, if necessary), then merge it into `pokerogue-locales`.
6. The Dev Team will approve your main PR for your feature, then merge it into PokéRogue's beta environment.
[^2]: For those wondering, the reason for choosing English specifically is due to it being the master language set in Pontoon (the program used by the Translation Team to perform locale updates).
If a key is present in any language _except_ the master language, it won't appear anywhere else in the translation tool, rendering missing English keys quite a hassle.

View File

@ -38,6 +38,7 @@ export enum UiMode {
UNAVAILABLE,
CHALLENGE_SELECT,
RENAME_POKEMON,
RENAME_RUN,
RUN_HISTORY,
RUN_INFO,
TEST_DIALOGUE,

View File

@ -2,8 +2,10 @@ import { globalScene } from "#app/global-scene";
import type { PhaseMap, PhaseString } from "#types/phase-types";
export abstract class Phase {
/** Start the current phase. */
start() {}
/** End the current phase and start a new one. */
end() {
globalScene.phaseManager.shiftPhase();
}

View File

@ -127,6 +127,7 @@ export interface SessionSaveData {
battleType: BattleType;
trainer: TrainerData;
gameVersion: string;
runNameText: string;
timestamp: number;
challenges: ChallengeData[];
mysteryEncounterType: MysteryEncounterType | -1; // Only defined when current wave is ME,
@ -979,6 +980,54 @@ export class GameData {
});
}
async renameSession(slotId: number, newName: string): Promise<boolean> {
return new Promise(async resolve => {
if (slotId < 0) {
return resolve(false);
}
const sessionData: SessionSaveData | null = await this.getSession(slotId);
if (!sessionData) {
return resolve(false);
}
if (newName === "") {
return resolve(true);
}
sessionData.runNameText = newName;
const updatedDataStr = JSON.stringify(sessionData);
const encrypted = encrypt(updatedDataStr, bypassLogin);
const secretId = this.secretId;
const trainerId = this.trainerId;
if (bypassLogin) {
localStorage.setItem(
`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`,
encrypt(updatedDataStr, bypassLogin),
);
resolve(true);
return;
}
pokerogueApi.savedata.session
.update({ slot: slotId, trainerId, secretId, clientSessionId }, encrypted)
.then(error => {
if (error) {
console.error("Failed to update session name:", error);
resolve(false);
} else {
localStorage.setItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`, encrypted);
updateUserInfo().then(success => {
if (success !== null && !success) {
return resolve(false);
}
});
resolve(true);
}
});
});
}
loadSession(slotId: number, sessionData?: SessionSaveData): Promise<boolean> {
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO: fix this
return new Promise(async (resolve, reject) => {

View File

@ -0,0 +1,54 @@
import i18next from "i18next";
import type { InputFieldConfig } from "./form-modal-ui-handler";
import { FormModalUiHandler } from "./form-modal-ui-handler";
import type { ModalConfig } from "./modal-ui-handler";
export class RenameRunFormUiHandler extends FormModalUiHandler {
getModalTitle(_config?: ModalConfig): string {
return i18next.t("menu:renamerun");
}
getWidth(_config?: ModalConfig): number {
return 160;
}
getMargin(_config?: ModalConfig): [number, number, number, number] {
return [0, 0, 48, 0];
}
getButtonLabels(_config?: ModalConfig): string[] {
return [i18next.t("menu:rename"), i18next.t("menu:cancel")];
}
getReadableErrorMessage(error: string): string {
const colonIndex = error?.indexOf(":");
if (colonIndex > 0) {
error = error.slice(0, colonIndex);
}
return super.getReadableErrorMessage(error);
}
override getInputFieldConfigs(): InputFieldConfig[] {
return [{ label: i18next.t("menu:runName") }];
}
show(args: any[]): boolean {
if (!super.show(args)) {
return false;
}
if (this.inputs?.length) {
this.inputs.forEach(input => {
input.text = "";
});
}
const config = args[0] as ModalConfig;
this.submitAction = _ => {
this.sanitizeInputs();
const sanitizedName = btoa(encodeURIComponent(this.inputs[0].text));
config.buttonActions[0](sanitizedName);
return true;
};
return true;
}
}

View File

@ -207,6 +207,10 @@ export class RunInfoUiHandler extends UiHandler {
headerText.setOrigin(0, 0);
headerText.setPositionRelative(headerBg, 8, 4);
this.runContainer.add(headerText);
const runName = addTextObject(0, 0, this.runInfo.runNameText, TextStyle.WINDOW);
runName.setOrigin(0, 0);
runName.setPositionRelative(headerBg, 60, 4);
this.runContainer.add(runName);
}
/**

View File

@ -1,12 +1,14 @@
import { GameMode } from "#app/game-mode";
import { globalScene } from "#app/global-scene";
import { Button } from "#enums/buttons";
import { GameModes } from "#enums/game-modes";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
// biome-ignore lint/performance/noNamespaceImport: See `src/system/game-data.ts`
import * as Modifier from "#modifiers/modifier";
import type { SessionSaveData } from "#system/game-data";
import type { PokemonData } from "#system/pokemon-data";
import type { OptionSelectConfig } from "#ui/abstract-option-select-ui-handler";
import { MessageUiHandler } from "#ui/message-ui-handler";
import { RunDisplayMode } from "#ui/run-info-ui-handler";
import { addTextObject } from "#ui/text";
@ -15,7 +17,7 @@ import { fixedInt, formatLargeNumber, getPlayTimeString, isNullOrUndefined } fro
import i18next from "i18next";
const SESSION_SLOTS_COUNT = 5;
const SLOTS_ON_SCREEN = 3;
const SLOTS_ON_SCREEN = 2;
export enum SaveSlotUiMode {
LOAD,
@ -33,6 +35,7 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
private uiMode: SaveSlotUiMode;
private saveSlotSelectCallback: SaveSlotSelectCallback | null;
protected manageDataConfig: OptionSelectConfig;
private scrollCursor = 0;
@ -101,6 +104,7 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
processInput(button: Button): boolean {
const ui = this.getUi();
const manageDataOptions: any[] = [];
let success = false;
let error = false;
@ -109,14 +113,115 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
const originalCallback = this.saveSlotSelectCallback;
if (button === Button.ACTION) {
const cursor = this.cursor + this.scrollCursor;
if (this.uiMode === SaveSlotUiMode.LOAD && !this.sessionSlots[cursor].hasData) {
const sessionSlot = this.sessionSlots[cursor];
if (this.uiMode === SaveSlotUiMode.LOAD && !sessionSlot.hasData) {
error = true;
} else {
switch (this.uiMode) {
case SaveSlotUiMode.LOAD:
this.saveSlotSelectCallback = null;
originalCallback?.(cursor);
if (!sessionSlot.malformed) {
manageDataOptions.push({
label: i18next.t("menu:loadGame"),
handler: () => {
globalScene.ui.revertMode();
originalCallback?.(cursor);
return true;
},
keepOpen: false,
});
manageDataOptions.push({
label: i18next.t("saveSlotSelectUiHandler:renameRun"),
handler: () => {
globalScene.ui.revertMode();
ui.setOverlayMode(
UiMode.RENAME_RUN,
{
buttonActions: [
(sanitizedName: string) => {
const name = decodeURIComponent(atob(sanitizedName));
globalScene.gameData.renameSession(cursor, name).then(response => {
if (response[0] === false) {
globalScene.reset(true);
} else {
this.clearSessionSlots();
this.cursorObj = null;
this.populateSessionSlots();
this.setScrollCursor(0);
this.setCursor(0);
ui.revertMode();
ui.showText("", 0);
}
});
},
() => {
ui.revertMode();
},
],
},
"",
);
return true;
},
});
}
this.manageDataConfig = {
xOffset: 0,
yOffset: 48,
options: manageDataOptions,
maxOptions: 4,
};
manageDataOptions.push({
label: i18next.t("saveSlotSelectUiHandler:deleteRun"),
handler: () => {
globalScene.ui.revertMode();
ui.showText(i18next.t("saveSlotSelectUiHandler:deleteData"), null, () => {
ui.setOverlayMode(
UiMode.CONFIRM,
() => {
globalScene.gameData.tryClearSession(cursor).then(response => {
if (response[0] === false) {
globalScene.reset(true);
} else {
this.clearSessionSlots();
this.cursorObj = null;
this.populateSessionSlots();
this.setScrollCursor(0);
this.setCursor(0);
ui.revertMode();
ui.showText("", 0);
}
});
},
() => {
ui.revertMode();
ui.showText("", 0);
},
false,
0,
19,
import.meta.env.DEV ? 300 : 2000,
);
});
return true;
},
keepOpen: false,
});
manageDataOptions.push({
label: i18next.t("menuUiHandler:cancel"),
handler: () => {
globalScene.ui.revertMode();
return true;
},
keepOpen: true,
});
ui.setOverlayMode(UiMode.MENU_OPTION_SELECT, this.manageDataConfig);
break;
case SaveSlotUiMode.SAVE: {
const saveAndCallback = () => {
const originalCallback = this.saveSlotSelectCallback;
@ -161,6 +266,7 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
}
} else {
this.saveSlotSelectCallback = null;
ui.showText("", 0);
originalCallback?.(-1);
success = true;
}
@ -267,33 +373,34 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
this.cursorObj = globalScene.add.container(0, 0);
const cursorBox = globalScene.add.nineslice(
0,
0,
15,
"select_cursor_highlight_thick",
undefined,
296,
44,
294,
this.sessionSlots[prevSlotIndex ?? 0]?.saveData?.runNameText ? 50 : 60,
6,
6,
6,
6,
);
const rightArrow = globalScene.add.image(0, 0, "cursor");
rightArrow.setPosition(160, 0);
rightArrow.setPosition(160, 15);
rightArrow.setName("rightArrow");
this.cursorObj.add([cursorBox, rightArrow]);
this.sessionSlotsContainer.add(this.cursorObj);
}
const cursorPosition = cursor + this.scrollCursor;
const cursorIncrement = cursorPosition * 56;
const cursorIncrement = cursorPosition * 76;
if (this.sessionSlots[cursorPosition] && this.cursorObj) {
const hasData = this.sessionSlots[cursorPosition].hasData;
const session = this.sessionSlots[cursorPosition];
const hasData = session.hasData && !session.malformed;
// If the session slot lacks session data, it does not move from its default, central position.
// Only session slots with session data will move leftwards and have a visible arrow.
if (!hasData) {
this.cursorObj.setPosition(151, 26 + cursorIncrement);
this.cursorObj.setPosition(151, 20 + cursorIncrement);
this.sessionSlots[cursorPosition].setPosition(0, cursorIncrement);
} else {
this.cursorObj.setPosition(145, 26 + cursorIncrement);
this.cursorObj.setPosition(145, 20 + cursorIncrement);
this.sessionSlots[cursorPosition].setPosition(-6, cursorIncrement);
}
this.setArrowVisibility(hasData);
@ -311,7 +418,8 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
revertSessionSlot(slotIndex: number): void {
const sessionSlot = this.sessionSlots[slotIndex];
if (sessionSlot) {
sessionSlot.setPosition(0, slotIndex * 56);
const valueHeight = 76;
sessionSlot.setPosition(0, slotIndex * valueHeight);
}
}
@ -340,7 +448,7 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
this.setCursor(this.cursor, prevSlotIndex);
globalScene.tweens.add({
targets: this.sessionSlotsContainer,
y: this.sessionSlotsContainerInitialY - 56 * scrollCursor,
y: this.sessionSlotsContainerInitialY - 76 * scrollCursor,
duration: fixedInt(325),
ease: "Sine.easeInOut",
});
@ -374,12 +482,14 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
class SessionSlot extends Phaser.GameObjects.Container {
public slotId: number;
public hasData: boolean;
/** Indicates the save slot ran into an error while being loaded */
public malformed: boolean;
private slotWindow: Phaser.GameObjects.NineSlice;
private loadingLabel: Phaser.GameObjects.Text;
public saveData: SessionSaveData;
constructor(slotId: number) {
super(globalScene, 0, slotId * 56);
super(globalScene, 0, slotId * 76);
this.slotId = slotId;
@ -387,32 +497,89 @@ class SessionSlot extends Phaser.GameObjects.Container {
}
setup() {
const slotWindow = addWindow(0, 0, 304, 52);
this.add(slotWindow);
this.slotWindow = addWindow(0, 0, 304, 70);
this.add(this.slotWindow);
this.loadingLabel = addTextObject(152, 26, i18next.t("saveSlotSelectUiHandler:loading"), TextStyle.WINDOW);
this.loadingLabel = addTextObject(152, 33, i18next.t("saveSlotSelectUiHandler:loading"), TextStyle.WINDOW);
this.loadingLabel.setOrigin(0.5, 0.5);
this.add(this.loadingLabel);
}
/**
* Generates a name for sessions that don't have a name yet.
* @param data - The {@linkcode SessionSaveData} being checked
* @returns The default name for the given data.
*/
decideFallback(data: SessionSaveData): string {
let fallbackName = `${GameMode.getModeName(data.gameMode)}`;
switch (data.gameMode) {
case GameModes.CLASSIC:
fallbackName += ` (${globalScene.gameData.gameStats.classicSessionsPlayed + 1})`;
break;
case GameModes.ENDLESS:
case GameModes.SPLICED_ENDLESS:
fallbackName += ` (${globalScene.gameData.gameStats.endlessSessionsPlayed + 1})`;
break;
case GameModes.DAILY: {
const runDay = new Date(data.timestamp).toLocaleDateString();
fallbackName += ` (${runDay})`;
break;
}
case GameModes.CHALLENGE: {
const activeChallenges = data.challenges.filter(c => c.value !== 0);
if (activeChallenges.length === 0) {
break;
}
fallbackName = "";
for (const challenge of activeChallenges.slice(0, 3)) {
if (fallbackName !== "") {
fallbackName += ", ";
}
fallbackName += challenge.toChallenge().getName();
}
if (activeChallenges.length > 3) {
fallbackName += ", ...";
} else if (fallbackName === "") {
// Something went wrong when retrieving the names of the active challenges,
// so fall back to just naming the run "Challenge"
fallbackName = `${GameMode.getModeName(data.gameMode)}`;
}
break;
}
}
return fallbackName;
}
async setupWithData(data: SessionSaveData) {
const hasName = data?.runNameText;
this.remove(this.loadingLabel, true);
if (hasName) {
const nameLabel = addTextObject(8, 5, data.runNameText, TextStyle.WINDOW);
this.add(nameLabel);
} else {
const fallbackName = this.decideFallback(data);
await globalScene.gameData.renameSession(this.slotId, fallbackName);
const nameLabel = addTextObject(8, 5, fallbackName, TextStyle.WINDOW);
this.add(nameLabel);
}
const gameModeLabel = addTextObject(
8,
5,
19,
`${GameMode.getModeName(data.gameMode) || i18next.t("gameMode:unknown")} - ${i18next.t("saveSlotSelectUiHandler:wave")} ${data.waveIndex}`,
TextStyle.WINDOW,
);
this.add(gameModeLabel);
const timestampLabel = addTextObject(8, 19, new Date(data.timestamp).toLocaleString(), TextStyle.WINDOW);
const timestampLabel = addTextObject(8, 33, new Date(data.timestamp).toLocaleString(), TextStyle.WINDOW);
this.add(timestampLabel);
const playTimeLabel = addTextObject(8, 33, getPlayTimeString(data.playTime), TextStyle.WINDOW);
const playTimeLabel = addTextObject(8, 47, getPlayTimeString(data.playTime), TextStyle.WINDOW);
this.add(playTimeLabel);
const pokemonIconsContainer = globalScene.add.container(144, 4);
const pokemonIconsContainer = globalScene.add.container(144, 16);
data.party.forEach((p: PokemonData, i: number) => {
const iconContainer = globalScene.add.container(26 * i, 0);
iconContainer.setScale(0.75);
@ -427,13 +594,9 @@ class SessionSlot extends Phaser.GameObjects.Container {
TextStyle.PARTY,
{ fontSize: "54px", color: "#f8f8f8" },
);
text.setShadow(0, 0, undefined);
text.setStroke("#424242", 14);
text.setOrigin(1, 0);
iconContainer.add(icon);
iconContainer.add(text);
text.setShadow(0, 0, undefined).setStroke("#424242", 14).setOrigin(1, 0);
iconContainer.add([icon, text]);
pokemonIconsContainer.add(iconContainer);
pokemon.destroy();
@ -441,7 +604,7 @@ class SessionSlot extends Phaser.GameObjects.Container {
this.add(pokemonIconsContainer);
const modifierIconsContainer = globalScene.add.container(148, 30);
const modifierIconsContainer = globalScene.add.container(148, 38);
modifierIconsContainer.setScale(0.5);
let visibleModifierIndex = 0;
for (const m of data.modifiers) {
@ -464,22 +627,33 @@ class SessionSlot extends Phaser.GameObjects.Container {
load(): Promise<boolean> {
return new Promise<boolean>(resolve => {
globalScene.gameData.getSession(this.slotId).then(async sessionData => {
// Ignore the results if the view was exited
if (!this.active) {
return;
}
if (!sessionData) {
this.hasData = false;
this.loadingLabel.setText(i18next.t("saveSlotSelectUiHandler:empty"));
resolve(false);
return;
}
this.hasData = true;
this.saveData = sessionData;
await this.setupWithData(sessionData);
resolve(true);
});
globalScene.gameData
.getSession(this.slotId)
.then(async sessionData => {
// Ignore the results if the view was exited
if (!this.active) {
return;
}
this.hasData = !!sessionData;
if (!sessionData) {
this.loadingLabel.setText(i18next.t("saveSlotSelectUiHandler:empty"));
resolve(false);
return;
}
this.saveData = sessionData;
this.setupWithData(sessionData);
resolve(true);
})
.catch(e => {
if (!this.active) {
return;
}
console.warn(`Failed to load session slot #${this.slotId}:`, e);
this.loadingLabel.setText(i18next.t("menu:failedToLoadSession"));
this.hasData = true;
this.malformed = true;
resolve(true);
});
});
}
}

View File

@ -60,6 +60,7 @@ import { addWindow } from "#ui/ui-theme";
import { UnavailableModalUiHandler } from "#ui/unavailable-modal-ui-handler";
import { executeIf } from "#utils/common";
import i18next from "i18next";
import { RenameRunFormUiHandler } from "./rename-run-ui-handler";
const transitionModes = [
UiMode.SAVE_SLOT,
@ -98,6 +99,7 @@ const noTransitionModes = [
UiMode.SESSION_RELOAD,
UiMode.UNAVAILABLE,
UiMode.RENAME_POKEMON,
UiMode.RENAME_RUN,
UiMode.TEST_DIALOGUE,
UiMode.AUTO_COMPLETE,
UiMode.ADMIN,
@ -168,6 +170,7 @@ export class UI extends Phaser.GameObjects.Container {
new UnavailableModalUiHandler(),
new GameChallengesUiHandler(),
new RenameFormUiHandler(),
new RenameRunFormUiHandler(),
new RunHistoryUiHandler(),
new RunInfoUiHandler(),
new TestDialogueUiHandler(UiMode.TEST_DIALOGUE),

View File

@ -3,9 +3,6 @@ import { BattlerIndex } from "#enums/battler-index";
import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id";
import { Stat } from "#enums/stat";
import { EnemyCommandPhase } from "#phases/enemy-command-phase";
import { TurnEndPhase } from "#phases/turn-end-phase";
import { VictoryPhase } from "#phases/victory-phase";
import { GameManager } from "#test/test-utils/game-manager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
@ -46,7 +43,7 @@ describe("Abilities - Moxie", () => {
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(0);
game.move.select(moveToUse);
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(VictoryPhase);
await game.phaseInterceptor.to("VictoryPhase");
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(1);
});
@ -67,7 +64,7 @@ describe("Abilities - Moxie", () => {
game.move.select(moveToUse, BattlerIndex.PLAYER_2);
await game.phaseInterceptor.to(TurnEndPhase);
await game.phaseInterceptor.to("TurnEndPhase");
expect(firstPokemon.getStatStage(Stat.ATK)).toBe(1);
},

View File

@ -1,9 +1,7 @@
import { AbilityId } from "#enums/ability-id";
import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id";
import { EnemyCommandPhase } from "#phases/enemy-command-phase";
import { SelectTargetPhase } from "#phases/select-target-phase";
import { TurnStartPhase } from "#phases/turn-start-phase";
import type { TurnStartPhase } from "#phases/turn-start-phase";
import { GameManager } from "#test/test-utils/game-manager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
@ -41,7 +39,7 @@ describe("Battle order", () => {
vi.spyOn(enemyPokemon, "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 150]); // set enemyPokemon's speed to 150
game.move.select(MoveId.TACKLE);
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.to("TurnStartPhase", false);
const playerPokemonIndex = playerPokemon.getBattlerIndex();
const enemyPokemonIndex = enemyPokemon.getBattlerIndex();
@ -60,7 +58,7 @@ describe("Battle order", () => {
vi.spyOn(enemyPokemon, "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 50]); // set enemyPokemon's speed to 50
game.move.select(MoveId.TACKLE);
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.to("TurnStartPhase", false);
const playerPokemonIndex = playerPokemon.getBattlerIndex();
const enemyPokemonIndex = enemyPokemon.getBattlerIndex();
@ -84,7 +82,7 @@ describe("Battle order", () => {
game.move.select(MoveId.TACKLE);
game.move.select(MoveId.TACKLE, 1);
await game.phaseInterceptor.runFrom(SelectTargetPhase).to(TurnStartPhase, false);
await game.phaseInterceptor.to("TurnStartPhase", false);
const phase = game.scene.phaseManager.getCurrentPhase() as TurnStartPhase;
const order = phase.getCommandOrder();
@ -108,7 +106,7 @@ describe("Battle order", () => {
game.move.select(MoveId.TACKLE);
game.move.select(MoveId.TACKLE, 1);
await game.phaseInterceptor.runFrom(SelectTargetPhase).to(TurnStartPhase, false);
await game.phaseInterceptor.to("TurnStartPhase", false);
const phase = game.scene.phaseManager.getCurrentPhase() as TurnStartPhase;
const order = phase.getCommandOrder();
@ -132,7 +130,7 @@ describe("Battle order", () => {
game.move.select(MoveId.TACKLE);
game.move.select(MoveId.TACKLE, 1);
await game.phaseInterceptor.runFrom(SelectTargetPhase).to(TurnStartPhase, false);
await game.phaseInterceptor.to("TurnStartPhase", false);
const phase = game.scene.phaseManager.getCurrentPhase() as TurnStartPhase;
const order = phase.getCommandOrder();

View File

@ -1,28 +1,13 @@
import { getGameMode } from "#app/game-mode";
import { allSpecies } from "#data/data-lists";
import { AbilityId } from "#enums/ability-id";
import { BiomeId } from "#enums/biome-id";
import { GameModes } from "#enums/game-modes";
import { MoveId } from "#enums/move-id";
import { PlayerGender } from "#enums/player-gender";
import { SpeciesId } from "#enums/species-id";
import { Stat } from "#enums/stat";
import { UiMode } from "#enums/ui-mode";
import { BattleEndPhase } from "#phases/battle-end-phase";
import { CommandPhase } from "#phases/command-phase";
import { DamageAnimPhase } from "#phases/damage-anim-phase";
import { EncounterPhase } from "#phases/encounter-phase";
import { EnemyCommandPhase } from "#phases/enemy-command-phase";
import { LoginPhase } from "#phases/login-phase";
import { NextEncounterPhase } from "#phases/next-encounter-phase";
import { SelectGenderPhase } from "#phases/select-gender-phase";
import { SelectStarterPhase } from "#phases/select-starter-phase";
import { SummonPhase } from "#phases/summon-phase";
import { SwitchPhase } from "#phases/switch-phase";
import { TitlePhase } from "#phases/title-phase";
import { TurnInitPhase } from "#phases/turn-init-phase";
import { GameManager } from "#test/test-utils/game-manager";
import { generateStarter } from "#test/test-utils/game-manager-utils";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
@ -45,55 +30,11 @@ describe("Phase - Battle Phase", () => {
game.scene.gameData.gender = undefined!; // just for these tests!
});
it("test phase interceptor with prompt", async () => {
await game.phaseInterceptor.run(LoginPhase);
game.onNextPrompt("SelectGenderPhase", UiMode.OPTION_SELECT, () => {
game.scene.gameData.gender = PlayerGender.MALE;
game.endPhase();
});
await game.phaseInterceptor.run(SelectGenderPhase);
await game.phaseInterceptor.run(TitlePhase);
await game.waitMode(UiMode.TITLE);
expect(game.scene.ui?.getMode()).toBe(UiMode.TITLE);
expect(game.scene.gameData.gender).toBe(PlayerGender.MALE);
});
it("test phase interceptor with prompt with preparation for a future prompt", async () => {
await game.phaseInterceptor.run(LoginPhase);
game.onNextPrompt("SelectGenderPhase", UiMode.OPTION_SELECT, () => {
game.scene.gameData.gender = PlayerGender.MALE;
game.endPhase();
});
game.onNextPrompt("CheckSwitchPhase", UiMode.CONFIRM, () => {
game.setMode(UiMode.MESSAGE);
game.endPhase();
});
await game.phaseInterceptor.run(SelectGenderPhase);
await game.phaseInterceptor.run(TitlePhase);
await game.waitMode(UiMode.TITLE);
expect(game.scene.ui?.getMode()).toBe(UiMode.TITLE);
expect(game.scene.gameData.gender).toBe(PlayerGender.MALE);
});
it("newGame one-liner", async () => {
await game.classicMode.startBattle();
expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND);
expect(game.scene.phaseManager.getCurrentPhase()?.phaseName).toBe("CommandPhase");
});
it("do attack wave 3 - single battle - regular - OHKO", async () => {
game.override.enemySpecies(SpeciesId.RATTATA).startingLevel(2000).battleStyle("single").startingWave(3);
await game.classicMode.startBattle([SpeciesId.MEWTWO]);
game.move.use(MoveId.TACKLE);
await game.phaseInterceptor.to("SelectModifierPhase");
await game.toNextWave();
});
it("do attack wave 3 - single battle - regular - NO OHKO with opponent using non damage attack", async () => {
@ -107,7 +48,7 @@ describe("Phase - Battle Phase", () => {
.battleStyle("single");
await game.classicMode.startBattle([SpeciesId.MEWTWO]);
game.move.select(MoveId.TACKLE);
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnInitPhase, false);
await game.phaseInterceptor.to("TurnInitPhase", false);
});
it("load 100% data file", async () => {
@ -135,68 +76,6 @@ describe("Phase - Battle Phase", () => {
}
});
it("wrong phase", async () => {
await game.phaseInterceptor.run(LoginPhase);
await game.phaseInterceptor.run(LoginPhase).catch(e => {
expect(e).toBe("Wrong phase: this is SelectGenderPhase and not LoginPhase");
});
});
it("wrong phase but skip", async () => {
await game.phaseInterceptor.run(LoginPhase);
await game.phaseInterceptor.run(LoginPhase, () => game.isCurrentPhase(SelectGenderPhase));
});
it("good run", async () => {
await game.phaseInterceptor.run(LoginPhase);
game.onNextPrompt(
"SelectGenderPhase",
UiMode.OPTION_SELECT,
() => {
game.scene.gameData.gender = PlayerGender.MALE;
game.endPhase();
},
() => game.isCurrentPhase(TitlePhase),
);
await game.phaseInterceptor.run(SelectGenderPhase, () => game.isCurrentPhase(TitlePhase));
await game.phaseInterceptor.run(TitlePhase);
});
it("good run from select gender to title", async () => {
await game.phaseInterceptor.run(LoginPhase);
game.onNextPrompt(
"SelectGenderPhase",
UiMode.OPTION_SELECT,
() => {
game.scene.gameData.gender = PlayerGender.MALE;
game.endPhase();
},
() => game.isCurrentPhase(TitlePhase),
);
await game.phaseInterceptor.runFrom(SelectGenderPhase).to(TitlePhase);
});
it("good run to SummonPhase phase", async () => {
await game.phaseInterceptor.run(LoginPhase);
game.onNextPrompt(
"SelectGenderPhase",
UiMode.OPTION_SELECT,
() => {
game.scene.gameData.gender = PlayerGender.MALE;
game.endPhase();
},
() => game.isCurrentPhase(TitlePhase),
);
game.onNextPrompt("TitlePhase", UiMode.TITLE, () => {
game.scene.gameMode = getGameMode(GameModes.CLASSIC);
const starters = generateStarter(game.scene);
const selectStarterPhase = new SelectStarterPhase();
game.scene.phaseManager.pushPhase(new EncounterPhase(false));
selectStarterPhase.initBattle(starters);
});
await game.phaseInterceptor.runFrom(SelectGenderPhase).to(SummonPhase);
});
it.each([
{ name: "1v1", double: false, qty: 1 },
{ name: "2v1", double: false, qty: 2 },
@ -232,7 +111,7 @@ describe("Phase - Battle Phase", () => {
await game.classicMode.startBattle([SpeciesId.DARMANITAN, SpeciesId.CHARIZARD]);
game.move.select(moveToUse);
await game.phaseInterceptor.to(DamageAnimPhase, false);
await game.phaseInterceptor.to("DamageAnimPhase", false);
await game.killPokemon(game.scene.currentBattle.enemyParty[0]);
expect(game.scene.currentBattle.enemyParty[0].isFainted()).toBe(true);
await game.phaseInterceptor.to("VictoryPhase");
@ -296,7 +175,7 @@ describe("Phase - Battle Phase", () => {
game.field.getPlayerPokemon().hp = 1;
game.move.select(moveToUse);
await game.phaseInterceptor.to(BattleEndPhase);
await game.phaseInterceptor.to("BattleEndPhase");
game.doRevivePokemon(0); // pretend max revive was picked
game.doSelectModifier();
@ -308,6 +187,6 @@ describe("Phase - Battle Phase", () => {
},
() => game.isCurrentPhase(NextEncounterPhase),
);
await game.phaseInterceptor.to(SwitchPhase);
await game.phaseInterceptor.to("SwitchPhase");
});
});

View File

@ -6,7 +6,6 @@ import { SpeciesId } from "#enums/species-id";
import { BATTLE_STATS, Stat } from "#enums/stat";
import { UiMode } from "#enums/ui-mode";
import { TempStatStageBoosterModifier } from "#modifiers/modifier";
import { TurnEndPhase } from "#phases/turn-end-phase";
import { GameManager } from "#test/test-utils/game-manager";
import type { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler";
import Phaser from "phaser";
@ -47,7 +46,7 @@ describe("Items - Temporary Stat Stage Boosters", () => {
game.move.select(MoveId.TACKLE);
await game.phaseInterceptor.runFrom("EnemyCommandPhase").to(TurnEndPhase);
await game.toEndOfTurn();
expect(partyMember.getStatStageMultiplier).toHaveReturnedWith(1.3);
});
@ -64,11 +63,11 @@ describe("Items - Temporary Stat Stage Boosters", () => {
// Raise ACC by +2 stat stages
game.move.select(MoveId.HONE_CLAWS);
await game.phaseInterceptor.to(TurnEndPhase);
await game.phaseInterceptor.to("TurnEndPhase");
game.move.select(MoveId.TACKLE);
await game.phaseInterceptor.to(TurnEndPhase);
await game.phaseInterceptor.to("TurnEndPhase");
// ACC at +3 stat stages yields a x2 multiplier
expect(partyMember.getAccuracyMultiplier).toHaveReturnedWith(2);
@ -84,11 +83,11 @@ describe("Items - Temporary Stat Stage Boosters", () => {
// Raise ATK by +1 stat stage
game.move.select(MoveId.HONE_CLAWS);
await game.phaseInterceptor.to(TurnEndPhase);
await game.phaseInterceptor.to("TurnEndPhase");
game.move.select(MoveId.TACKLE);
await game.phaseInterceptor.to(TurnEndPhase);
await game.phaseInterceptor.to("TurnEndPhase");
// ATK at +1 stat stage yields a x1.5 multiplier, add 0.3 from X_ATTACK
expect(partyMember.getStatStageMultiplier).toHaveReturnedWith(1.8);
@ -112,7 +111,7 @@ describe("Items - Temporary Stat Stage Boosters", () => {
game.move.select(MoveId.TACKLE);
await game.phaseInterceptor.to(TurnEndPhase);
await game.phaseInterceptor.to("TurnEndPhase");
expect(partyMember.getAccuracyMultiplier).toHaveReturnedWith(3);
expect(partyMember.getStatStageMultiplier).toHaveReturnedWith(4);

View File

@ -4,10 +4,7 @@ import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id";
import { Stat } from "#enums/stat";
import type { Move } from "#moves/move";
import { DamageAnimPhase } from "#phases/damage-anim-phase";
import { MoveEffectPhase } from "#phases/move-effect-phase";
import { MoveEndPhase } from "#phases/move-end-phase";
import { MovePhase } from "#phases/move-phase";
import type { MoveEffectPhase } from "#phases/move-effect-phase";
import { GameManager } from "#test/test-utils/game-manager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
@ -55,14 +52,14 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
// Force user party to act before enemy party
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
await game.phaseInterceptor.to(MoveEffectPhase, false);
await game.phaseInterceptor.to("MoveEffectPhase", false);
expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
await game.phaseInterceptor.to("DamageAnimPhase", false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(100);
await game.phaseInterceptor.to(MoveEffectPhase, false);
await game.phaseInterceptor.to("MoveEffectPhase", false);
expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
await game.phaseInterceptor.to("DamageAnimPhase", false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200);
});
@ -75,14 +72,14 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
// Force user party to act before enemy party
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
await game.phaseInterceptor.to(MoveEffectPhase, false);
await game.phaseInterceptor.to("MoveEffectPhase", false);
expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
await game.phaseInterceptor.to("DamageAnimPhase", false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
await game.phaseInterceptor.to(MoveEffectPhase, false);
await game.phaseInterceptor.to("MoveEffectPhase", false);
expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
await game.phaseInterceptor.to("DamageAnimPhase", false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
});
@ -95,19 +92,19 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
// Force first enemy to act (and fail) in between party
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY]);
await game.phaseInterceptor.to(MoveEffectPhase, false);
await game.phaseInterceptor.to("MoveEffectPhase", false);
expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
await game.phaseInterceptor.to("DamageAnimPhase", false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(100);
await game.phaseInterceptor.to(MoveEndPhase);
await game.phaseInterceptor.to("MoveEndPhase");
// Skip enemy move; because the enemy is at full HP, Rest should fail
await game.phaseInterceptor.runFrom(MovePhase).to(MoveEndPhase);
await game.phaseInterceptor.to("MoveEndPhase");
await game.phaseInterceptor.to(MoveEffectPhase, false);
await game.phaseInterceptor.to("MoveEffectPhase", false);
expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
await game.phaseInterceptor.to("DamageAnimPhase", false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200);
});
@ -121,18 +118,18 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
// Force first enemy to act in between party
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY]);
await game.phaseInterceptor.to(MoveEffectPhase, false);
await game.phaseInterceptor.to("MoveEffectPhase", false);
expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
await game.phaseInterceptor.to("DamageAnimPhase", false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(100);
await game.phaseInterceptor.to(MoveEndPhase);
await game.phaseInterceptor.to("MoveEndPhase");
// Skip enemy move
await game.phaseInterceptor.runFrom(MovePhase).to(MoveEndPhase);
await game.phaseInterceptor.to("MoveEndPhase");
await game.phaseInterceptor.to(MoveEffectPhase, false);
await game.phaseInterceptor.to("MoveEffectPhase", false);
expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
await game.phaseInterceptor.to("DamageAnimPhase", false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
});
@ -145,14 +142,14 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
// Force user party to act before enemy party
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
await game.phaseInterceptor.to(MoveEffectPhase, false);
await game.phaseInterceptor.to("MoveEffectPhase", false);
expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
await game.phaseInterceptor.to("DamageAnimPhase", false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
await game.phaseInterceptor.to(MoveEffectPhase, false);
await game.phaseInterceptor.to("MoveEffectPhase", false);
expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
await game.phaseInterceptor.to("DamageAnimPhase", false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
});
@ -189,24 +186,24 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
// Force first enemy to act in between party
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY]);
await game.phaseInterceptor.to(MoveEffectPhase, false);
await game.phaseInterceptor.to("MoveEffectPhase", false);
expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
await game.phaseInterceptor.to("DamageAnimPhase", false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
await game.phaseInterceptor.to(MoveEffectPhase, false);
await game.phaseInterceptor.to("MoveEffectPhase", false);
expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
await game.phaseInterceptor.to("DamageAnimPhase", false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
await game.phaseInterceptor.to(MoveEffectPhase, false);
await game.phaseInterceptor.to("MoveEffectPhase", false);
expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
await game.phaseInterceptor.to("DamageAnimPhase", false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200);
await game.phaseInterceptor.to(MoveEffectPhase, false);
await game.phaseInterceptor.to("MoveEffectPhase", false);
expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
await game.phaseInterceptor.to("DamageAnimPhase", false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
});
@ -243,24 +240,24 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
// Force first enemy to act in between party
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY]);
await game.phaseInterceptor.to(MoveEffectPhase, false);
await game.phaseInterceptor.to("MoveEffectPhase", false);
expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
await game.phaseInterceptor.to("DamageAnimPhase", false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
await game.phaseInterceptor.to(MoveEffectPhase, false);
await game.phaseInterceptor.to("MoveEffectPhase", false);
expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
await game.phaseInterceptor.to("DamageAnimPhase", false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
await game.phaseInterceptor.to(MoveEffectPhase, false);
await game.phaseInterceptor.to("MoveEffectPhase", false);
expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
await game.phaseInterceptor.to("DamageAnimPhase", false);
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200);
await game.phaseInterceptor.to(MoveEffectPhase, false);
await game.phaseInterceptor.to("MoveEffectPhase", false);
expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id);
await game.phaseInterceptor.to(DamageAnimPhase, false);
await game.phaseInterceptor.to("DamageAnimPhase", false);
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
});
});

View File

@ -2,8 +2,6 @@ import { AbilityId } from "#enums/ability-id";
import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id";
import { Stat } from "#enums/stat";
import { EnemyCommandPhase } from "#phases/enemy-command-phase";
import { TurnInitPhase } from "#phases/turn-init-phase";
import { GameManager } from "#test/test-utils/game-manager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
@ -40,7 +38,7 @@ describe("Moves - Growth", () => {
expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(0);
game.move.select(MoveId.GROWTH);
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnInitPhase);
await game.toEndOfTurn();
expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(1);
});

View File

@ -2,10 +2,6 @@ import { AbilityId } from "#enums/ability-id";
import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id";
import { Stat } from "#enums/stat";
import { BerryPhase } from "#phases/berry-phase";
import { FaintPhase } from "#phases/faint-phase";
import { MessagePhase } from "#phases/message-phase";
import { TurnInitPhase } from "#phases/turn-init-phase";
import { GameManager } from "#test/test-utils/game-manager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, test } from "vitest";
@ -43,7 +39,7 @@ describe("Moves - Parting Shot", () => {
game.move.select(MoveId.PARTING_SHOT);
await game.phaseInterceptor.to(BerryPhase, false);
await game.phaseInterceptor.to("BerryPhase", false);
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0);
expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0);
expect(game.scene.getPlayerField()[0].species.speciesId).toBe(SpeciesId.MURKROW);
@ -58,7 +54,7 @@ describe("Moves - Parting Shot", () => {
game.move.select(MoveId.PARTING_SHOT);
await game.phaseInterceptor.to(BerryPhase, false);
await game.phaseInterceptor.to("BerryPhase", false);
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0);
expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0);
expect(game.scene.getPlayerField()[0].species.speciesId).toBe(SpeciesId.MURKROW);
@ -79,24 +75,24 @@ describe("Moves - Parting Shot", () => {
// use Memento 3 times to debuff enemy
game.move.select(MoveId.MEMENTO);
await game.phaseInterceptor.to(FaintPhase);
await game.phaseInterceptor.to("FaintPhase");
expect(game.scene.getPlayerParty()[0].isFainted()).toBe(true);
game.doSelectPartyPokemon(1);
await game.phaseInterceptor.to(TurnInitPhase, false);
await game.phaseInterceptor.to("TurnInitPhase", false);
game.move.select(MoveId.MEMENTO);
await game.phaseInterceptor.to(FaintPhase);
await game.phaseInterceptor.to("FaintPhase");
expect(game.scene.getPlayerParty()[0].isFainted()).toBe(true);
game.doSelectPartyPokemon(2);
await game.phaseInterceptor.to(TurnInitPhase, false);
await game.phaseInterceptor.to("TurnInitPhase", false);
game.move.select(MoveId.MEMENTO);
await game.phaseInterceptor.to(FaintPhase);
await game.phaseInterceptor.to("FaintPhase");
expect(game.scene.getPlayerParty()[0].isFainted()).toBe(true);
game.doSelectPartyPokemon(3);
// set up done
await game.phaseInterceptor.to(TurnInitPhase, false);
await game.phaseInterceptor.to("TurnInitPhase", false);
const enemyPokemon = game.field.getEnemyPokemon();
expect(enemyPokemon).toBeDefined();
@ -106,7 +102,7 @@ describe("Moves - Parting Shot", () => {
// now parting shot should fail
game.move.select(MoveId.PARTING_SHOT);
await game.phaseInterceptor.to(BerryPhase, false);
await game.phaseInterceptor.to("BerryPhase", false);
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-6);
expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(-6);
expect(game.scene.getPlayerField()[0].species.speciesId).toBe(SpeciesId.MURKROW);
@ -125,7 +121,7 @@ describe("Moves - Parting Shot", () => {
game.move.select(MoveId.PARTING_SHOT);
await game.phaseInterceptor.to(BerryPhase, false);
await game.phaseInterceptor.to("BerryPhase", false);
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0);
expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0);
expect(game.scene.getPlayerField()[0].species.speciesId).toBe(SpeciesId.MURKROW);
@ -144,7 +140,7 @@ describe("Moves - Parting Shot", () => {
game.move.select(MoveId.PARTING_SHOT);
await game.phaseInterceptor.to(BerryPhase, false);
await game.phaseInterceptor.to("BerryPhase", false);
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0);
expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0);
expect(game.scene.getPlayerField()[0].species.speciesId).toBe(SpeciesId.MURKROW);
@ -153,43 +149,24 @@ describe("Moves - Parting Shot", () => {
it.todo(
// TODO: fix this bug to pass the test!
"Parting shot should de-buff and not fail if no party available to switch - party size 1",
async () => {
await game.classicMode.startBattle([SpeciesId.MURKROW]);
const enemyPokemon = game.field.getEnemyPokemon();
expect(enemyPokemon).toBeDefined();
game.move.select(MoveId.PARTING_SHOT);
await game.phaseInterceptor.to(BerryPhase, false);
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1);
expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(-1);
expect(game.scene.getPlayerField()[0].species.speciesId).toBe(SpeciesId.MURKROW);
},
);
it.todo(
// TODO: fix this bug to pass the test!
"Parting shot regularly not fail if no party available to switch - party fainted",
"should lower stats without failing if no alive party members available to switch",
async () => {
await game.classicMode.startBattle([SpeciesId.MURKROW, SpeciesId.MEOWTH]);
const meowth = game.scene.getPlayerParty()[1];
meowth.hp = 0;
game.move.select(MoveId.SPLASH);
await game.toNextTurn();
// intentionally kill party pokemon, switch to second slot (now 1 party mon is fainted)
await game.killPokemon(game.scene.getPlayerParty()[0]);
expect(game.scene.getPlayerParty()[0].isFainted()).toBe(true);
await game.phaseInterceptor.run(MessagePhase);
game.doSelectPartyPokemon(1);
await game.phaseInterceptor.to(TurnInitPhase, false);
game.move.select(MoveId.PARTING_SHOT);
game.doSelectPartyPokemon(1);
await game.toEndOfTurn();
await game.phaseInterceptor.to(BerryPhase, false);
const enemyPokemon = game.field.getEnemyPokemon();
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0);
expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0);
expect(game.scene.getPlayerField()[0].species.speciesId).toBe(SpeciesId.MEOWTH);
expect(game.field.getPlayerPokemon().species.speciesId).toBe(SpeciesId.MURKROW);
},
);
});

View File

@ -1,8 +1,6 @@
import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id";
import { Stat } from "#enums/stat";
import { EnemyCommandPhase } from "#phases/enemy-command-phase";
import { TurnEndPhase } from "#phases/turn-end-phase";
import { GameManager } from "#test/test-utils/game-manager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
@ -41,7 +39,7 @@ describe("Moves - Tackle", () => {
await game.classicMode.startBattle([SpeciesId.MIGHTYENA]);
const hpOpponent = game.scene.currentBattle.enemyParty[0].hp;
game.move.select(moveToUse);
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase);
await game.toEndOfTurn();
const hpLost = hpOpponent - game.scene.currentBattle.enemyParty[0].hp;
expect(hpLost).toBe(0);
});
@ -55,7 +53,7 @@ describe("Moves - Tackle", () => {
const hpOpponent = game.scene.currentBattle.enemyParty[0].hp;
game.move.select(moveToUse);
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase);
await game.toEndOfTurn();
const hpLost = hpOpponent - game.scene.currentBattle.enemyParty[0].hp;
expect(hpLost).toBeGreaterThan(0);
expect(hpLost).toBeLessThan(4);

View File

@ -2,8 +2,6 @@ import { AbilityId } from "#enums/ability-id";
import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id";
import { Stat } from "#enums/stat";
import { EnemyCommandPhase } from "#phases/enemy-command-phase";
import { TurnInitPhase } from "#phases/turn-init-phase";
import { GameManager } from "#test/test-utils/game-manager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
@ -43,7 +41,7 @@ describe("Moves - Tail whip", () => {
expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(0);
game.move.select(moveToUse);
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnInitPhase);
await game.toEndOfTurn();
expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(-1);
});

View File

@ -9,7 +9,6 @@ import { MessagePhase } from "#phases/message-phase";
import {
MysteryEncounterBattlePhase,
MysteryEncounterOptionSelectedPhase,
MysteryEncounterPhase,
MysteryEncounterRewardsPhase,
} from "#phases/mystery-encounter-phases";
import { VictoryPhase } from "#phases/victory-phase";
@ -89,9 +88,9 @@ export async function runMysteryEncounterToEnd(
uiHandler.processInput(Button.ACTION);
});
await game.phaseInterceptor.to(CommandPhase);
await game.toNextTurn();
} else {
await game.phaseInterceptor.to(MysteryEncounterRewardsPhase);
await game.phaseInterceptor.to("MysteryEncounterRewardsPhase");
}
}
@ -112,7 +111,7 @@ export async function runSelectMysteryEncounterOption(
);
if (game.isCurrentPhase(MessagePhase)) {
await game.phaseInterceptor.run(MessagePhase);
await game.phaseInterceptor.to("MessagePhase");
}
// dispose of intro messages
@ -126,7 +125,7 @@ export async function runSelectMysteryEncounterOption(
() => game.isCurrentPhase(MysteryEncounterOptionSelectedPhase),
);
await game.phaseInterceptor.to(MysteryEncounterPhase, true);
await game.phaseInterceptor.to("MysteryEncounterPhase", true);
// select the desired option
const uiHandler = game.scene.ui.getHandler<MysteryEncounterUiHandler>();
@ -205,7 +204,7 @@ export async function skipBattleRunMysteryEncounterRewardsPhase(game: GameManage
game.scene.field.remove(p);
});
game.scene.phaseManager.pushPhase(new VictoryPhase(0));
game.phaseInterceptor.superEndPhase();
game.endPhase();
game.setMode(UiMode.MESSAGE);
await game.phaseInterceptor.to(MysteryEncounterRewardsPhase, runRewardsPhase);
await game.phaseInterceptor.to("MysteryEncounterRewardsPhase", runRewardsPhase);
}

View File

@ -134,7 +134,7 @@ describe("Berries Abound - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
const berriesAfter = scene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
@ -147,9 +147,7 @@ describe("Berries Abound - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty);
await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(
@ -232,9 +230,9 @@ describe("Berries Abound - Mystery Encounter", () => {
});
await runMysteryEncounterToEnd(game, 2);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(

View File

@ -368,9 +368,9 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterRewardsPhase.name);
game.phaseInterceptor["prompts"] = []; // Clear out prompt handlers
game.onNextPrompt("MysteryEncounterRewardsPhase", UiMode.OPTION_SELECT, () => {
game.phaseInterceptor.superEndPhase();
game.endPhase();
});
await game.phaseInterceptor.run(MysteryEncounterRewardsPhase);
await game.phaseInterceptor.to("MysteryEncounterRewardsPhase");
expect(selectOptionSpy).toHaveBeenCalledTimes(1);
const optionData = selectOptionSpy.mock.calls[0][0];
@ -395,7 +395,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
it("should NOT be selectable if the player doesn't have any Bug types", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.BUG_TYPE_SUPERFAN, [SpeciesId.ABRA]);
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
await game.phaseInterceptor.to("MysteryEncounterPhase", false);
const encounterPhase = scene.phaseManager.getCurrentPhase();
expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name);
@ -417,7 +417,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 2);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(
@ -436,7 +436,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 2);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(
@ -458,7 +458,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 2);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(
@ -482,7 +482,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 2);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(
@ -530,7 +530,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
it("should NOT be selectable if the player doesn't have any Bug items", async () => {
game.scene.modifiers = [];
await game.runToMysteryEncounter(MysteryEncounterType.BUG_TYPE_SUPERFAN, defaultParty);
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
await game.phaseInterceptor.to("MysteryEncounterPhase", false);
game.scene.modifiers = [];
const encounterPhase = scene.phaseManager.getCurrentPhase();
@ -558,7 +558,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 });
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(

View File

@ -25,7 +25,6 @@ import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
import { CommandPhase } from "#phases/command-phase";
import { MovePhase } from "#phases/move-phase";
import { PostMysteryEncounterPhase } from "#phases/mystery-encounter-phases";
import { NewBattlePhase } from "#phases/new-battle-phase";
import { SelectModifierPhase } from "#phases/select-modifier-phase";
import {
runMysteryEncounterToEnd,
@ -200,9 +199,9 @@ describe("Clowning Around - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.CLOWNING_AROUND, defaultParty);
await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
const abilityToTrain = scene.currentBattle.mysteryEncounter?.misc.ability;
game.onNextPrompt("PostMysteryEncounterPhase", UiMode.MESSAGE, () => {
@ -215,7 +214,7 @@ describe("Clowning Around - Mystery Encounter", () => {
const partyUiHandler = game.scene.ui.handlers[UiMode.PARTY] as PartyUiHandler;
vi.spyOn(partyUiHandler, "show");
game.endPhase();
await game.phaseInterceptor.to(PostMysteryEncounterPhase);
await game.phaseInterceptor.to("PostMysteryEncounterPhase");
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(PostMysteryEncounterPhase.name);
// Wait for Yes/No confirmation to appear
@ -228,7 +227,7 @@ describe("Clowning Around - Mystery Encounter", () => {
// Click "Select" on Pokemon
partyUiHandler.processInput(Button.ACTION);
// Stop next battle before it runs
await game.phaseInterceptor.to(NewBattlePhase, false);
await game.phaseInterceptor.to("NewBattlePhase", false);
const leadPokemon = scene.getPlayerParty()[0];
expect(leadPokemon.customPokemonData?.ability).toBe(abilityToTrain);

View File

@ -126,9 +126,9 @@ describe("Dancing Lessons - Mystery Encounter", () => {
partyLead.calculateStats();
await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(
@ -215,7 +215,7 @@ describe("Dancing Lessons - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.DANCING_LESSONS, defaultParty);
const partyCountBefore = scene.getPlayerParty().length;
scene.getPlayerParty().forEach(p => (p.moveset = []));
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
await game.phaseInterceptor.to("MysteryEncounterPhase", false);
const encounterPhase = scene.phaseManager.getCurrentPhase();
expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name);

View File

@ -94,7 +94,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runMysteryEncounterToEnd(game, 1);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(
@ -131,7 +131,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runMysteryEncounterToEnd(game, 2);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(
@ -171,7 +171,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runMysteryEncounterToEnd(game, 3);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(
@ -211,7 +211,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runMysteryEncounterToEnd(game, 4);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(

View File

@ -122,9 +122,9 @@ describe("Fight or Flight - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(
@ -155,7 +155,7 @@ describe("Fight or Flight - Mystery Encounter", () => {
it("should NOT be selectable if the player doesn't have a Stealing move", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.FIGHT_OR_FLIGHT, defaultParty);
scene.getPlayerParty().forEach(p => (p.moveset = []));
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
await game.phaseInterceptor.to("MysteryEncounterPhase", false);
const encounterPhase = scene.phaseManager.getCurrentPhase();
expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name);
@ -182,9 +182,9 @@ describe("Fight or Flight - Mystery Encounter", () => {
const item = game.scene.currentBattle.mysteryEncounter!.misc;
await runMysteryEncounterToEnd(game, 2);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(

View File

@ -120,7 +120,7 @@ describe("Fun And Games! - Mystery Encounter", () => {
it("should NOT be selectable if the player doesn't have enough money", async () => {
game.scene.money = 0;
await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty);
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
await game.phaseInterceptor.to("MysteryEncounterPhase", false);
const encounterPhase = scene.phaseManager.getCurrentPhase();
expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name);
@ -162,7 +162,7 @@ describe("Fun And Games! - Mystery Encounter", () => {
// Turn 3
(game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, MoveUseMode.NORMAL);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
// Rewards
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
@ -181,11 +181,11 @@ describe("Fun And Games! - Mystery Encounter", () => {
// Skip minigame
scene.currentBattle.mysteryEncounter!.misc.turnsRemaining = 0;
(game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, MoveUseMode.NORMAL);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
// Rewards
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(
@ -210,11 +210,11 @@ describe("Fun And Games! - Mystery Encounter", () => {
wobbuffet.hp = Math.floor(0.2 * wobbuffet.getMaxHp());
scene.currentBattle.mysteryEncounter!.misc.turnsRemaining = 0;
(game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, MoveUseMode.NORMAL);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
// Rewards
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(
@ -240,11 +240,11 @@ describe("Fun And Games! - Mystery Encounter", () => {
wobbuffet.hp = Math.floor(0.1 * wobbuffet.getMaxHp());
scene.currentBattle.mysteryEncounter!.misc.turnsRemaining = 0;
(game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, MoveUseMode.NORMAL);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
// Rewards
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(
@ -270,11 +270,11 @@ describe("Fun And Games! - Mystery Encounter", () => {
wobbuffet.hp = 1;
scene.currentBattle.mysteryEncounter!.misc.turnsRemaining = 0;
(game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, MoveUseMode.NORMAL);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
// Rewards
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(

View File

@ -227,7 +227,7 @@ describe("Global Trade System - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 });
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(

View File

@ -161,9 +161,9 @@ describe("Mysterious Challengers - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty);
await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(
@ -205,9 +205,9 @@ describe("Mysterious Challengers - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty);
await runMysteryEncounterToEnd(game, 2, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(
@ -262,9 +262,9 @@ describe("Mysterious Challengers - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty);
await runMysteryEncounterToEnd(game, 3, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(

View File

@ -146,7 +146,7 @@ describe("Teleporting Hijinks - Mystery Encounter", () => {
it("should NOT be selectable if the player doesn't have enough money", async () => {
game.scene.money = 0;
await game.runToMysteryEncounter(MysteryEncounterType.TELEPORTING_HIJINKS, defaultParty);
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
await game.phaseInterceptor.to("MysteryEncounterPhase", false);
const encounterPhase = scene.phaseManager.getCurrentPhase();
expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name);
@ -218,7 +218,7 @@ describe("Teleporting Hijinks - Mystery Encounter", () => {
it("should NOT be selectable if the player doesn't the right type pokemon", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.TELEPORTING_HIJINKS, [SpeciesId.BLASTOISE]);
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
await game.phaseInterceptor.to("MysteryEncounterPhase", false);
const encounterPhase = scene.phaseManager.getCurrentPhase();
expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name);
@ -299,9 +299,9 @@ describe("Teleporting Hijinks - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.TELEPORTING_HIJINKS, defaultParty);
await runMysteryEncounterToEnd(game, 3, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(

View File

@ -13,7 +13,6 @@ import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
import { HUMAN_TRANSITABLE_BIOMES } from "#mystery-encounters/mystery-encounters";
import { TheExpertPokemonBreederEncounter } from "#mystery-encounters/the-expert-pokemon-breeder-encounter";
import { CommandPhase } from "#phases/command-phase";
import { PostMysteryEncounterPhase } from "#phases/mystery-encounter-phases";
import { SelectModifierPhase } from "#phases/select-modifier-phase";
import {
runMysteryEncounterToEnd,
@ -176,7 +175,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
const eggsAfter = scene.gameData.eggs;
@ -187,8 +186,8 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs);
expect(eggsAfter.filter(egg => egg.tier === EggTier.RARE).length).toBe(rareEggs);
game.phaseInterceptor.superEndPhase();
await game.phaseInterceptor.to(PostMysteryEncounterPhase);
game.endPhase();
await game.phaseInterceptor.to("PostMysteryEncounterPhase");
const friendshipAfter = scene.currentBattle.mysteryEncounter!.misc.pokemon1.friendship;
// 20 from ME + extra from winning battle (that extra is not accurate to what happens in game.
@ -261,7 +260,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 2, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
const eggsAfter = scene.gameData.eggs;
@ -272,8 +271,8 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs);
expect(eggsAfter.filter(egg => egg.tier === EggTier.RARE).length).toBe(rareEggs);
game.phaseInterceptor.superEndPhase();
await game.phaseInterceptor.to(PostMysteryEncounterPhase);
game.endPhase();
await game.phaseInterceptor.to("PostMysteryEncounterPhase");
const friendshipAfter = scene.currentBattle.mysteryEncounter!.misc.pokemon2.friendship;
expect(friendshipAfter).toBe(friendshipBefore + 20 + FRIENDSHIP_GAIN_FROM_BATTLE); // 20 from ME + extra for friendship gained from winning battle
@ -343,7 +342,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 3, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
const eggsAfter = scene.gameData.eggs;
@ -354,8 +353,8 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs);
expect(eggsAfter.filter(egg => egg.tier === EggTier.RARE).length).toBe(rareEggs);
game.phaseInterceptor.superEndPhase();
await game.phaseInterceptor.to(PostMysteryEncounterPhase);
game.endPhase();
await game.phaseInterceptor.to("PostMysteryEncounterPhase");
const friendshipAfter = scene.currentBattle.mysteryEncounter!.misc.pokemon3.friendship;
expect(friendshipAfter).toBe(friendshipBefore + 20 + FRIENDSHIP_GAIN_FROM_BATTLE); // 20 + extra for friendship gained from winning battle

View File

@ -229,9 +229,9 @@ describe("The Strong Stuff - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty);
await runMysteryEncounterToEnd(game, 2, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(

View File

@ -16,7 +16,6 @@ import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
import { HUMAN_TRANSITABLE_BIOMES } from "#mystery-encounters/mystery-encounters";
import { TheWinstrateChallengeEncounter } from "#mystery-encounters/the-winstrate-challenge-encounter";
import { CommandPhase } from "#phases/command-phase";
import { MysteryEncounterRewardsPhase } from "#phases/mystery-encounter-phases";
import { PartyHealPhase } from "#phases/party-heal-phase";
import { SelectModifierPhase } from "#phases/select-modifier-phase";
import { VictoryPhase } from "#phases/victory-phase";
@ -295,9 +294,9 @@ describe("The Winstrate Challenge - Mystery Encounter", () => {
// Should have Macho Brace in the rewards
await skipBattleToNextBattle(game, true);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(
@ -339,7 +338,7 @@ describe("The Winstrate Challenge - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.THE_WINSTRATE_CHALLENGE, defaultParty);
await runMysteryEncounterToEnd(game, 2);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(
@ -366,11 +365,10 @@ async function skipBattleToNextBattle(game: GameManager, isFinalBattle = false)
p.status = new Status(StatusEffect.FAINT);
game.scene.field.remove(p);
});
game.phaseInterceptor["onHold"] = [];
game.scene.phaseManager.pushPhase(new VictoryPhase(0));
game.phaseInterceptor.superEndPhase();
game.endPhase();
if (isFinalBattle) {
await game.phaseInterceptor.to(MysteryEncounterRewardsPhase);
await game.phaseInterceptor.to("MysteryEncounterRewardsPhase");
} else {
await game.toNextTurn();
}

View File

@ -172,7 +172,7 @@ describe("Trash to Treasure - Mystery Encounter", () => {
it("should give 2 Leftovers, 1 Shell Bell, and Black Sludge", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.TRASH_TO_TREASURE, defaultParty);
await runMysteryEncounterToEnd(game, 1);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
const leftovers = scene.findModifier(m => m instanceof TurnHealModifier) as TurnHealModifier;
@ -242,9 +242,9 @@ describe("Trash to Treasure - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.TRASH_TO_TREASURE, defaultParty);
await runMysteryEncounterToEnd(game, 2, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(

View File

@ -116,7 +116,7 @@ describe("Weird Dream - Mystery Encounter", () => {
const bstsPrior = pokemonPrior.map(species => species.getSpeciesForm().getBaseStatTotal());
await runMysteryEncounterToEnd(game, 1);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
const pokemonAfter = scene.getPlayerParty();
@ -139,9 +139,9 @@ describe("Weird Dream - Mystery Encounter", () => {
it("should have 1 Memory Mushroom, 5 Rogue Balls, and 3 Mints in rewards", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty);
await runMysteryEncounterToEnd(game, 1);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(
@ -196,9 +196,9 @@ describe("Weird Dream - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty);
await runMysteryEncounterToEnd(game, 2, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(

View File

@ -37,7 +37,7 @@ describe("Mystery Encounter Phases", () => {
SpeciesId.VOLCARONA,
]);
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
await game.phaseInterceptor.to("MysteryEncounterPhase", false);
expect(game.scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name);
});
@ -49,9 +49,9 @@ describe("Mystery Encounter Phases", () => {
game.onNextPrompt("MysteryEncounterPhase", UiMode.MYSTERY_ENCOUNTER, () => {
// End phase early for test
game.phaseInterceptor.superEndPhase();
game.endPhase();
});
await game.phaseInterceptor.run(MysteryEncounterPhase);
await game.phaseInterceptor.to("MysteryEncounterPhase");
expect(game.scene.mysteryEncounterSaveData.encounteredEvents.length).toBeGreaterThan(0);
expect(game.scene.mysteryEncounterSaveData.encounteredEvents[0].type).toEqual(
@ -75,7 +75,7 @@ describe("Mystery Encounter Phases", () => {
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.run(MysteryEncounterPhase);
await game.phaseInterceptor.to("MysteryEncounterPhase");
// Select option 1 for encounter
const handler = game.scene.ui.getHandler() as MysteryEncounterUiHandler;

View File

@ -241,7 +241,7 @@ describe("SelectModifierPhase", () => {
const selectModifierPhase = new SelectModifierPhase(0, undefined, customModifiers);
scene.phaseManager.unshiftPhase(selectModifierPhase);
game.move.select(MoveId.SPLASH);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(
@ -265,7 +265,7 @@ describe("SelectModifierPhase", () => {
const selectModifierPhase = new SelectModifierPhase(0, undefined, customModifiers);
scene.phaseManager.unshiftPhase(selectModifierPhase);
game.move.select(MoveId.SPLASH);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(

View File

@ -0,0 +1,82 @@
import * as account from "#app/account";
import * as bypassLoginModule from "#app/global-vars/bypass-login";
import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
import type { SessionSaveData } from "#app/system/game-data";
import { AbilityId } from "#enums/ability-id";
import { MoveId } from "#enums/move-id";
import { GameManager } from "#test/test-utils/game-manager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("System - Rename Run", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.moveset([MoveId.SPLASH])
.battleStyle("single")
.enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH);
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
describe("renameSession", () => {
beforeEach(() => {
vi.spyOn(bypassLoginModule, "bypassLogin", "get").mockReturnValue(false);
vi.spyOn(account, "updateUserInfo").mockImplementation(async () => [true, 1]);
});
it("should return false if slotId < 0", async () => {
const result = await game.scene.gameData.renameSession(-1, "Named Run");
expect(result).toEqual(false);
});
it("should return false if getSession returns null", async () => {
vi.spyOn(game.scene.gameData, "getSession").mockResolvedValue(null as unknown as SessionSaveData);
const result = await game.scene.gameData.renameSession(-1, "Named Run");
expect(result).toEqual(false);
});
it("should return true if bypassLogin is true", async () => {
vi.spyOn(bypassLoginModule, "bypassLogin", "get").mockReturnValue(true);
vi.spyOn(game.scene.gameData, "getSession").mockResolvedValue({} as SessionSaveData);
const result = await game.scene.gameData.renameSession(0, "Named Run");
expect(result).toEqual(true);
});
it("should return false if api returns error", async () => {
vi.spyOn(game.scene.gameData, "getSession").mockResolvedValue({} as SessionSaveData);
vi.spyOn(pokerogueApi.savedata.session, "update").mockResolvedValue("Unknown Error!");
const result = await game.scene.gameData.renameSession(0, "Named Run");
expect(result).toEqual(false);
});
it("should return true if api is succesfull", async () => {
vi.spyOn(game.scene.gameData, "getSession").mockResolvedValue({} as SessionSaveData);
vi.spyOn(pokerogueApi.savedata.session, "update").mockResolvedValue("");
const result = await game.scene.gameData.renameSession(0, "Named Run");
expect(result).toEqual(true);
expect(account.updateUserInfo).toHaveBeenCalled();
});
});
});

View File

@ -20,13 +20,11 @@ import { ModifierTypeOption } from "#modifiers/modifier-type";
import { CheckSwitchPhase } from "#phases/check-switch-phase";
import { CommandPhase } from "#phases/command-phase";
import { EncounterPhase } from "#phases/encounter-phase";
import { LoginPhase } from "#phases/login-phase";
import { MovePhase } from "#phases/move-phase";
import { MysteryEncounterPhase } from "#phases/mystery-encounter-phases";
import { NewBattlePhase } from "#phases/new-battle-phase";
import { SelectStarterPhase } from "#phases/select-starter-phase";
import type { SelectTargetPhase } from "#phases/select-target-phase";
import { TitlePhase } from "#phases/title-phase";
import { TurnEndPhase } from "#phases/turn-end-phase";
import { TurnInitPhase } from "#phases/turn-init-phase";
import { TurnStartPhase } from "#phases/turn-start-phase";
@ -188,10 +186,12 @@ export class GameManager {
* @returns A promise that resolves when the title phase is reached.
*/
async runToTitle(): Promise<void> {
await this.phaseInterceptor.whenAboutToRun(LoginPhase);
this.phaseInterceptor.pop();
await this.phaseInterceptor.run(TitlePhase);
// Go to login phase and skip past it
await this.phaseInterceptor.to("LoginPhase", false);
this.phaseInterceptor.shiftPhase(true);
await this.phaseInterceptor.to("TitlePhase");
// TODO: This should be moved to a separate initialization method
this.scene.gameSpeed = 5;
this.scene.moveAnimations = false;
this.scene.showLevelUpStats = false;
@ -270,7 +270,7 @@ export class GameManager {
true,
);
await this.phaseInterceptor.run(EncounterPhase);
await this.phaseInterceptor.to("EncounterPhase");
if (!isNullOrUndefined(encounterType)) {
expect(this.scene.currentBattle?.mysteryEncounter?.encounterType).toBe(encounterType);
}
@ -542,7 +542,7 @@ export class GameManager {
* ```
*/
async setTurnOrder(order: BattlerIndex[]): Promise<void> {
await this.phaseInterceptor.to(TurnStartPhase, false);
await this.phaseInterceptor.to("TurnStartPhase", false);
vi.spyOn(this.scene.phaseManager.getCurrentPhase() as TurnStartPhase, "getSpeedOrder").mockReturnValue(order);
}

View File

@ -49,7 +49,7 @@ export class ChallengeModeHelper extends GameManagerHelper {
selectStarterPhase.initBattle(starters);
});
await this.game.phaseInterceptor.run(EncounterPhase);
await this.game.phaseInterceptor.to("EncounterPhase");
if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0 && this.game.override.removeEnemyStartingItems) {
this.game.removeEnemyHeldItems();
}

View File

@ -57,7 +57,7 @@ export class ReloadHelper extends GameManagerHelper {
this.game.scene.modifiers = [];
}
titlePhase.loadSaveSlot(-1); // Load the desired session data
this.game.phaseInterceptor.shift(); // Loading the save slot also ended TitlePhase, clean it up
this.game.phaseInterceptor.shiftPhase(); // Loading the save slot also ended TitlePhase, clean it up
// Run through prompts for switching Pokemon, copied from classicModeHelper.ts
if (this.game.scene.battleStyle === BattleStyle.SWITCH) {

View File

@ -1,3 +1,4 @@
import type { BattleScene } from "#app/battle-scene";
import { Phase } from "#app/phase";
import { UiMode } from "#enums/ui-mode";
import { AttemptRunPhase } from "#phases/attempt-run-phase";
@ -64,6 +65,7 @@ import { UnlockPhase } from "#phases/unlock-phase";
import { VictoryPhase } from "#phases/victory-phase";
import { ErrorInterceptor } from "#test/test-utils/error-interceptor";
import type { PhaseClass, PhaseString } from "#types/phase-types";
import type { AwaitableUiHandler } from "#ui/awaitable-ui-handler";
import { UI } from "#ui/ui";
export interface PromptHandler {
@ -76,20 +78,39 @@ export interface PromptHandler {
type PhaseInterceptorPhase = PhaseClass | PhaseString;
interface PhaseStub {
start(): void;
endBySetMode: boolean;
}
interface InProgressStub {
name: string;
callback(): void;
onError(error: any): void;
}
interface onHoldStub {
name: string;
call(): void;
}
export class PhaseInterceptor {
public scene;
public phases = {};
public log: string[];
private onHold;
private interval;
private promptInterval;
private intervalRun;
public scene: BattleScene;
// @ts-expect-error: initialized in `initPhases`
public phases: Record<PhaseString, PhaseStub> = {};
public log: PhaseString[];
/**
* TODO: This should not be an array;
* Our linear phase system means only 1 phase is ever started at once (if any)
*/
private onHold: onHoldStub[];
private interval: NodeJS.Timeout;
private promptInterval: NodeJS.Timeout;
private intervalRun: NodeJS.Timeout;
private prompts: PromptHandler[];
private phaseFrom;
private inProgress;
private originalSetMode;
private originalSetOverlayMode;
private originalSuperEnd;
private inProgress?: InProgressStub;
private originalSetMode: UI["setMode"];
private originalSuperEnd: Phase["end"];
/**
* List of phases with their corresponding start methods.
@ -100,72 +121,73 @@ export class PhaseInterceptor {
* `initPhases()` so that its subclasses can use `super.start()` properly.
*/
private PHASES = [
[LoginPhase, this.startPhase],
[TitlePhase, this.startPhase],
[SelectGenderPhase, this.startPhase],
[NewBiomeEncounterPhase, this.startPhase],
[SelectStarterPhase, this.startPhase],
[PostSummonPhase, this.startPhase],
[SummonPhase, this.startPhase],
[ToggleDoublePositionPhase, this.startPhase],
[CheckSwitchPhase, this.startPhase],
[ShowAbilityPhase, this.startPhase],
[MessagePhase, this.startPhase],
[TurnInitPhase, this.startPhase],
[CommandPhase, this.startPhase],
[EnemyCommandPhase, this.startPhase],
[TurnStartPhase, this.startPhase],
[MovePhase, this.startPhase],
[MoveEffectPhase, this.startPhase],
[DamageAnimPhase, this.startPhase],
[FaintPhase, this.startPhase],
[BerryPhase, this.startPhase],
[TurnEndPhase, this.startPhase],
[BattleEndPhase, this.startPhase],
[EggLapsePhase, this.startPhase],
[SelectModifierPhase, this.startPhase],
[NextEncounterPhase, this.startPhase],
[NewBattlePhase, this.startPhase],
[VictoryPhase, this.startPhase],
[LearnMovePhase, this.startPhase],
[MoveEndPhase, this.startPhase],
[StatStageChangePhase, this.startPhase],
[ShinySparklePhase, this.startPhase],
[SelectTargetPhase, this.startPhase],
[UnavailablePhase, this.startPhase],
[QuietFormChangePhase, this.startPhase],
[SwitchPhase, this.startPhase],
[SwitchSummonPhase, this.startPhase],
[PartyHealPhase, this.startPhase],
[FormChangePhase, this.startPhase],
[EvolutionPhase, this.startPhase],
[EndEvolutionPhase, this.startPhase],
[LevelCapPhase, this.startPhase],
[AttemptRunPhase, this.startPhase],
[SelectBiomePhase, this.startPhase],
[PositionalTagPhase, this.startPhase],
[PokemonTransformPhase, this.startPhase],
[MysteryEncounterPhase, this.startPhase],
[MysteryEncounterOptionSelectedPhase, this.startPhase],
[MysteryEncounterBattlePhase, this.startPhase],
[MysteryEncounterRewardsPhase, this.startPhase],
[PostMysteryEncounterPhase, this.startPhase],
[RibbonModifierRewardPhase, this.startPhase],
[GameOverModifierRewardPhase, this.startPhase],
[ModifierRewardPhase, this.startPhase],
[PartyExpPhase, this.startPhase],
[ExpPhase, this.startPhase],
[EncounterPhase, this.startPhase],
[GameOverPhase, this.startPhase],
[UnlockPhase, this.startPhase],
[PostGameOverPhase, this.startPhase],
[RevivalBlessingPhase, this.startPhase],
LoginPhase,
TitlePhase,
SelectGenderPhase,
NewBiomeEncounterPhase,
SelectStarterPhase,
PostSummonPhase,
SummonPhase,
ToggleDoublePositionPhase,
CheckSwitchPhase,
ShowAbilityPhase,
MessagePhase,
TurnInitPhase,
CommandPhase,
EnemyCommandPhase,
TurnStartPhase,
MovePhase,
MoveEffectPhase,
DamageAnimPhase,
FaintPhase,
BerryPhase,
TurnEndPhase,
BattleEndPhase,
EggLapsePhase,
SelectModifierPhase,
NextEncounterPhase,
NewBattlePhase,
VictoryPhase,
LearnMovePhase,
MoveEndPhase,
StatStageChangePhase,
ShinySparklePhase,
SelectTargetPhase,
UnavailablePhase,
QuietFormChangePhase,
SwitchPhase,
SwitchSummonPhase,
PartyHealPhase,
FormChangePhase,
EvolutionPhase,
EndEvolutionPhase,
LevelCapPhase,
AttemptRunPhase,
SelectBiomePhase,
PositionalTagPhase,
PokemonTransformPhase,
MysteryEncounterPhase,
MysteryEncounterOptionSelectedPhase,
MysteryEncounterBattlePhase,
MysteryEncounterRewardsPhase,
PostMysteryEncounterPhase,
RibbonModifierRewardPhase,
GameOverModifierRewardPhase,
ModifierRewardPhase,
PartyExpPhase,
ExpPhase,
EncounterPhase,
GameOverPhase,
UnlockPhase,
PostGameOverPhase,
RevivalBlessingPhase,
];
private endBySetMode = [
TitlePhase,
SelectGenderPhase,
CommandPhase,
SelectStarterPhase,
SelectModifierPhase,
MysteryEncounterPhase,
PostMysteryEncounterPhase,
@ -175,7 +197,7 @@ export class PhaseInterceptor {
* Constructor to initialize the scene and properties, and to start the phase handling.
* @param scene - The scene to be managed.
*/
constructor(scene) {
constructor(scene: BattleScene) {
this.scene = scene;
this.onHold = [];
this.prompts = [];
@ -200,16 +222,6 @@ export class PhaseInterceptor {
}
}
/**
* Method to set the starting phase.
* @param phaseFrom - The phase to start from.
* @returns The instance of the PhaseInterceptor.
*/
runFrom(phaseFrom: PhaseInterceptorPhase): PhaseInterceptor {
this.phaseFrom = phaseFrom;
return this;
}
/**
* Method to transition to a target phase.
* @param phaseTo - The phase to transition to.
@ -219,59 +231,50 @@ export class PhaseInterceptor {
async to(phaseTo: PhaseInterceptorPhase, runTarget = true): Promise<void> {
return new Promise(async (resolve, reject) => {
ErrorInterceptor.getInstance().add(this);
if (this.phaseFrom) {
await this.run(this.phaseFrom).catch(e => reject(e));
this.phaseFrom = null;
}
const targetName = typeof phaseTo === "string" ? phaseTo : phaseTo.name;
this.intervalRun = setInterval(async () => {
const currentPhase = this.onHold?.length && this.onHold[0];
if (currentPhase && currentPhase.name === targetName) {
clearInterval(this.intervalRun);
if (!runTarget) {
return resolve();
}
await this.run(currentPhase).catch(e => {
if (!currentPhase) {
// No current phase means the manager either hasn't started yet
// or we were interrupted by prompt; wait for phase to finish
return;
}
// If current phase is different, run it and wait for it to finish.
if (currentPhase.name !== targetName) {
await this.run().catch(e => {
clearInterval(this.intervalRun);
return reject(e);
});
return;
}
// Hit target phase; run it and resolve
clearInterval(this.intervalRun);
if (!runTarget) {
return resolve();
}
if (currentPhase && currentPhase.name !== targetName) {
await this.run(currentPhase).catch(e => {
clearInterval(this.intervalRun);
return reject(e);
});
}
await this.run().catch(e => {
clearInterval(this.intervalRun);
return reject(e);
});
return resolve();
});
});
}
/**
* Method to run a phase with an optional skip function.
* @param phaseTarget - The phase to run.
* @param skipFn - Optional skip function.
* Method to run the current phase with an optional skip function.
* @returns A promise that resolves when the phase is run.
*/
run(phaseTarget: PhaseInterceptorPhase, skipFn?: (className: PhaseClass) => boolean): Promise<void> {
const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name;
this.scene.moveAnimations = null; // Mandatory to avoid crash
private run(): Promise<void> {
// @ts-expect-error: This is apparently mandatory to avoid a crash; review if this is needed
this.scene.moveAnimations = null;
return new Promise(async (resolve, reject) => {
ErrorInterceptor.getInstance().add(this);
const interval = setInterval(async () => {
const currentPhase = this.onHold.shift();
if (currentPhase) {
if (currentPhase.name !== targetName) {
clearInterval(interval);
const skip = skipFn?.(currentPhase.name);
if (skip) {
this.onHold.unshift(currentPhase);
ErrorInterceptor.getInstance().remove(this);
return resolve();
}
clearInterval(interval);
return reject(`Wrong phase: this is ${currentPhase.name} and not ${targetName}`);
}
clearInterval(interval);
this.inProgress = {
name: currentPhase.name,
@ -287,26 +290,6 @@ export class PhaseInterceptor {
});
}
whenAboutToRun(phaseTarget: PhaseInterceptorPhase, _skipFn?: (className: PhaseClass) => boolean): Promise<void> {
const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name;
this.scene.moveAnimations = null; // Mandatory to avoid crash
return new Promise(async (resolve, _reject) => {
ErrorInterceptor.getInstance().add(this);
const interval = setInterval(async () => {
const currentPhase = this.onHold[0];
if (currentPhase?.name === targetName) {
clearInterval(interval);
resolve();
}
});
});
}
pop() {
this.onHold.pop();
this.scene.phaseManager.shiftPhase();
}
/**
* Remove the current phase from the phase interceptor.
*
@ -316,7 +299,7 @@ export class PhaseInterceptor {
*
* @param shouldRun Whether or not the current scene should also be run.
*/
shift(shouldRun = false): void {
shiftPhase(shouldRun = false): void {
this.onHold.shift();
if (shouldRun) {
this.scene.phaseManager.shiftPhase();
@ -328,17 +311,16 @@ export class PhaseInterceptor {
*/
initPhases() {
this.originalSetMode = UI.prototype.setMode;
this.originalSetOverlayMode = UI.prototype.setOverlayMode;
this.originalSuperEnd = Phase.prototype.end;
UI.prototype.setMode = (mode, ...args) => this.setMode.call(this, mode, ...args);
Phase.prototype.end = () => this.superEndPhase.call(this);
for (const [phase, methodStart] of this.PHASES) {
for (const phase of this.PHASES) {
const originalStart = phase.prototype.start;
this.phases[phase.name] = {
start: originalStart,
endBySetMode: this.endBySetMode.some(elm => elm.name === phase.name),
};
phase.prototype.start = () => methodStart.call(this, phase);
phase.prototype.start = () => this.startPhase.call(this, phase);
}
}
@ -347,7 +329,7 @@ export class PhaseInterceptor {
* @param phase - The phase to start.
*/
startPhase(phase: PhaseClass) {
this.log.push(phase.name);
this.log.push(phase.name as PhaseString);
const instance = this.scene.phaseManager.getCurrentPhase();
this.onHold.push({
name: phase.name,
@ -357,16 +339,11 @@ export class PhaseInterceptor {
});
}
unlock() {
this.inProgress?.callback();
this.inProgress = undefined;
}
/**
* Method to end a phase and log it.
* @param phase - The phase to start.
*/
superEndPhase() {
private superEndPhase() {
const instance = this.scene.phaseManager.getCurrentPhase();
this.originalSuperEnd.apply(instance);
this.inProgress?.callback();
@ -379,7 +356,8 @@ export class PhaseInterceptor {
* @param args - Additional arguments to pass to the original method.
*/
setMode(mode: UiMode, ...args: unknown[]): Promise<void> {
const currentPhase = this.scene.phaseManager.getCurrentPhase();
// TODO: remove the `!` in PR 6243 / after PR 6243 is merged
const currentPhase = this.scene.phaseManager.getCurrentPhase()!;
const instance = this.scene.ui;
console.log("setMode", `${UiMode[mode]} (=${mode})`, args);
const ret = this.originalSetMode.apply(instance, [mode, ...args]);
@ -395,18 +373,6 @@ export class PhaseInterceptor {
return ret;
}
/**
* mock to set overlay mode
* @param mode - The {@linkcode Mode} to set.
* @param args - Additional arguments to pass to the original method.
*/
setOverlayMode(mode: UiMode, ...args: unknown[]): Promise<void> {
const instance = this.scene.ui;
console.log("setOverlayMode", `${UiMode[mode]} (=${mode})`, args);
const ret = this.originalSetOverlayMode.apply(instance, [mode, ...args]);
return ret;
}
/**
* Method to start the prompt handler.
*/
@ -425,7 +391,7 @@ export class PhaseInterceptor {
currentPhase === actionForNextPrompt.phaseTarget &&
currentHandler.active &&
(!actionForNextPrompt.awaitingActionInput ||
(actionForNextPrompt.awaitingActionInput && currentHandler.awaitingActionInput))
(actionForNextPrompt.awaitingActionInput && (currentHandler as AwaitableUiHandler)["awaitingActionInput"]))
) {
const prompt = this.prompts.shift();
if (prompt?.callback) {
@ -467,11 +433,10 @@ export class PhaseInterceptor {
* function stored in `this.phases`. Additionally, it clears the `promptInterval` and `interval`.
*/
restoreOg() {
for (const [phase] of this.PHASES) {
for (const phase of this.PHASES) {
phase.prototype.start = this.phases[phase.name].start;
}
UI.prototype.setMode = this.originalSetMode;
UI.prototype.setOverlayMode = this.originalSetOverlayMode;
Phase.prototype.end = this.originalSuperEnd;
clearInterval(this.promptInterval);
clearInterval(this.interval);

View File

@ -69,7 +69,7 @@ describe("UI - Pokedex", () => {
// Open the pokedex UI.
await game.runToTitle();
await game.phaseInterceptor.setOverlayMode(UiMode.POKEDEX);
await game.scene.ui.setOverlayMode(UiMode.POKEDEX);
// Get the handler for the current UI.
const handler = game.scene.ui.getHandler();
@ -89,7 +89,7 @@ describe("UI - Pokedex", () => {
// Open the pokedex UI.
await game.runToTitle();
await game.phaseInterceptor.setOverlayMode(UiMode.POKEDEX_PAGE, species, starterAttributes);
await game.scene.ui.setOverlayMode(UiMode.POKEDEX_PAGE, species, starterAttributes);
// Get the handler for the current UI.
const handler = game.scene.ui.getHandler();

View File

@ -6,8 +6,6 @@ import { GameModes } from "#enums/game-modes";
import { Nature } from "#enums/nature";
import { SpeciesId } from "#enums/species-id";
import { UiMode } from "#enums/ui-mode";
import { EncounterPhase } from "#phases/encounter-phase";
import { SelectStarterPhase } from "#phases/select-starter-phase";
import type { TitlePhase } from "#phases/title-phase";
import { GameManager } from "#test/test-utils/game-manager";
import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler";
@ -54,9 +52,8 @@ describe("UI - Starter select", () => {
handler.processInput(Button.RIGHT);
handler.processInput(Button.LEFT);
handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
});
await game.phaseInterceptor.run(SelectStarterPhase);
await game.phaseInterceptor.to("SelectStarterPhase");
let options: OptionSelectItem[] = [];
let optionSelectUiHandler: OptionSelectUiHandler | undefined;
await new Promise<void>(resolve => {
@ -88,7 +85,7 @@ describe("UI - Starter select", () => {
resolve();
});
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
await game.phaseInterceptor.to("EncounterPhase", false);
expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(SpeciesId.BULBASAUR);
expect(game.scene.getPlayerParty()[0].shiny).toBe(true);
@ -115,9 +112,8 @@ describe("UI - Starter select", () => {
handler.processInput(Button.LEFT);
handler.processInput(Button.CYCLE_GENDER);
handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
});
await game.phaseInterceptor.run(SelectStarterPhase);
await game.phaseInterceptor.to("SelectStarterPhase");
let options: OptionSelectItem[] = [];
let optionSelectUiHandler: OptionSelectUiHandler | undefined;
await new Promise<void>(resolve => {
@ -149,7 +145,7 @@ describe("UI - Starter select", () => {
resolve();
});
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
await game.phaseInterceptor.to("EncounterPhase", false);
expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(SpeciesId.BULBASAUR);
expect(game.scene.getPlayerParty()[0].shiny).toBe(true);
@ -179,9 +175,8 @@ describe("UI - Starter select", () => {
handler.processInput(Button.CYCLE_NATURE);
handler.processInput(Button.CYCLE_ABILITY);
handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
});
await game.phaseInterceptor.run(SelectStarterPhase);
await game.phaseInterceptor.to("SelectStarterPhase");
let options: OptionSelectItem[] = [];
let optionSelectUiHandler: OptionSelectUiHandler | undefined;
await new Promise<void>(resolve => {
@ -213,7 +208,7 @@ describe("UI - Starter select", () => {
resolve();
});
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
await game.phaseInterceptor.to("EncounterPhase", false);
expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(SpeciesId.BULBASAUR);
expect(game.scene.getPlayerParty()[0].shiny).toBe(true);
@ -242,9 +237,8 @@ describe("UI - Starter select", () => {
handler.processInput(Button.LEFT);
handler.processInput(Button.CYCLE_GENDER);
handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
});
await game.phaseInterceptor.run(SelectStarterPhase);
await game.phaseInterceptor.to("SelectStarterPhase");
let options: OptionSelectItem[] = [];
let optionSelectUiHandler: OptionSelectUiHandler | undefined;
await new Promise<void>(resolve => {
@ -276,7 +270,7 @@ describe("UI - Starter select", () => {
resolve();
});
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
await game.phaseInterceptor.to("EncounterPhase", false);
expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(SpeciesId.BULBASAUR);
expect(game.scene.getPlayerParty()[0].shiny).toBe(true);
@ -303,9 +297,8 @@ describe("UI - Starter select", () => {
handler.processInput(Button.LEFT);
handler.processInput(Button.ACTION);
handler.processInput(Button.CYCLE_SHINY);
game.phaseInterceptor.unlock();
});
await game.phaseInterceptor.run(SelectStarterPhase);
await game.phaseInterceptor.to("SelectStarterPhase");
let options: OptionSelectItem[] = [];
let optionSelectUiHandler: OptionSelectUiHandler | undefined;
await new Promise<void>(resolve => {
@ -337,7 +330,7 @@ describe("UI - Starter select", () => {
resolve();
});
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
await game.phaseInterceptor.to("EncounterPhase", false);
expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(SpeciesId.BULBASAUR);
expect(game.scene.getPlayerParty()[0].shiny).toBe(false);
@ -365,9 +358,8 @@ describe("UI - Starter select", () => {
handler.processInput(Button.CYCLE_SHINY);
handler.processInput(Button.CYCLE_SHINY);
handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
});
await game.phaseInterceptor.run(SelectStarterPhase);
await game.phaseInterceptor.to("SelectStarterPhase");
let options: OptionSelectItem[] = [];
let optionSelectUiHandler: OptionSelectUiHandler | undefined;
await new Promise<void>(resolve => {
@ -399,7 +391,7 @@ describe("UI - Starter select", () => {
resolve();
});
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
await game.phaseInterceptor.to("EncounterPhase", false);
expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(SpeciesId.BULBASAUR);
expect(game.scene.getPlayerParty()[0].shiny).toBe(true);
@ -426,9 +418,8 @@ describe("UI - Starter select", () => {
handler.processInput(Button.CYCLE_SHINY);
handler.processInput(Button.CYCLE_SHINY);
handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
});
await game.phaseInterceptor.run(SelectStarterPhase);
await game.phaseInterceptor.to("SelectStarterPhase");
let options: OptionSelectItem[] = [];
let optionSelectUiHandler: OptionSelectUiHandler | undefined;
await new Promise<void>(resolve => {
@ -460,7 +451,7 @@ describe("UI - Starter select", () => {
resolve();
});
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
await game.phaseInterceptor.to("EncounterPhase", false);
expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(SpeciesId.BULBASAUR);
expect(game.scene.getPlayerParty()[0].shiny).toBe(true);
@ -486,9 +477,8 @@ describe("UI - Starter select", () => {
handler.processInput(Button.RIGHT);
handler.processInput(Button.RIGHT);
handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
});
await game.phaseInterceptor.run(SelectStarterPhase);
await game.phaseInterceptor.to("SelectStarterPhase");
let options: OptionSelectItem[] = [];
let optionSelectUiHandler: OptionSelectUiHandler | undefined;
await new Promise<void>(resolve => {
@ -527,7 +517,7 @@ describe("UI - Starter select", () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
await game.phaseInterceptor.to("EncounterPhase", false);
expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(SpeciesId.CATERPIE);
});
@ -551,9 +541,8 @@ describe("UI - Starter select", () => {
handler.processInput(Button.RIGHT);
handler.processInput(Button.DOWN);
handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
});
await game.phaseInterceptor.run(SelectStarterPhase);
await game.phaseInterceptor.to("SelectStarterPhase");
let options: OptionSelectItem[] = [];
let optionSelectUiHandler: OptionSelectUiHandler | undefined;
await new Promise<void>(resolve => {
@ -593,7 +582,7 @@ describe("UI - Starter select", () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
await game.phaseInterceptor.to("EncounterPhase", false);
expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(SpeciesId.NIDORAN_M);
});
});

View File

@ -72,8 +72,6 @@ describe("UI - Transfer Items", () => {
expect(
handler.optionsContainer.list.some(option => RegExp(/Lum Berry\[color.*(2)/).exec((option as BBCodeText).text)),
).toBe(true);
game.phaseInterceptor.unlock();
});
await game.phaseInterceptor.to("SelectModifierPhase");
@ -93,8 +91,6 @@ describe("UI - Transfer Items", () => {
expect(handler.optionsContainer.list.some(option => (option as BBCodeText).text?.includes("Transfer"))).toBe(
true,
);
game.phaseInterceptor.unlock();
});
await game.phaseInterceptor.to("SelectModifierPhase");

View File

@ -2,7 +2,6 @@ import { Button } from "#enums/buttons";
import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id";
import { UiMode } from "#enums/ui-mode";
import { CommandPhase } from "#phases/command-phase";
import { GameManager } from "#test/test-utils/game-manager";
import type { MockText } from "#test/test-utils/mocks/mocks-container/mock-text";
import { FightUiHandler } from "#ui/fight-ui-handler";
@ -46,7 +45,6 @@ describe("UI - Type Hints", () => {
const { ui } = game.scene;
const handler = ui.getHandler<FightUiHandler>();
handler.processInput(Button.ACTION); // select "Fight"
game.phaseInterceptor.unlock();
});
game.onNextPrompt("CommandPhase", UiMode.FIGHT, () => {
@ -59,7 +57,7 @@ describe("UI - Type Hints", () => {
expect.soft(dragonClawText.color).toBe("#929292");
ui.getHandler().processInput(Button.ACTION);
});
await game.phaseInterceptor.to(CommandPhase);
await game.phaseInterceptor.to("CommandPhase");
});
it("check status move color", async () => {
@ -71,7 +69,6 @@ describe("UI - Type Hints", () => {
const { ui } = game.scene;
const handler = ui.getHandler<FightUiHandler>();
handler.processInput(Button.ACTION); // select "Fight"
game.phaseInterceptor.unlock();
});
game.onNextPrompt("CommandPhase", UiMode.FIGHT, () => {
@ -84,7 +81,7 @@ describe("UI - Type Hints", () => {
expect.soft(growlText.color).toBe(undefined);
ui.getHandler().processInput(Button.ACTION);
});
await game.phaseInterceptor.to(CommandPhase);
await game.phaseInterceptor.to("CommandPhase");
});
it("should show the proper hint for a move in doubles after one of the enemy pokemon flees", async () => {
@ -107,7 +104,6 @@ describe("UI - Type Hints", () => {
const { ui } = game.scene;
const handler = ui.getHandler<FightUiHandler>();
handler.processInput(Button.ACTION); // select "Fight"
game.phaseInterceptor.unlock();
});
game.onNextPrompt("CommandPhase", UiMode.FIGHT, () => {
@ -121,6 +117,6 @@ describe("UI - Type Hints", () => {
expect.soft(shadowBallText.color).toBe(undefined);
ui.getHandler().processInput(Button.ACTION);
});
await game.phaseInterceptor.to(CommandPhase);
await game.phaseInterceptor.to("CommandPhase");
});
});