pokerogue/src/ui/menu-ui-handler.ts
Wlowscha 66c70b07a7
[UI/UX] In-Game Pokedex (#5083)
* Working ui, missing logic, logs

* Filtering starters by name is working

* Filtering moves and abilities correctly

* Opening starter page on button.action

* Removed ugly leftover from title

* Added container for text with different colors and titles

* Showing all species in pokedex with no decorations and shinies

* Filtering includes extra forms; moving cursor from filterText to starters does not reset scrollIndex; toggle button for decorations

* Can access evolution page

* Abilities are colored properly (still missing info overlay)

* Biome filter; displays for baseStats, biomes and evolutions

* Removed lockable select ui handler, replaced by changes to standard ui handler.

* Evolutions are selectable from list and displayed properly

* Keeps shiny variant, gender and form when switching to evolutions; show ability descriptions; properly displaying sprites for megas and other forms

* Listing prevolutions and base forms

* Fixed filtering of baby forms with no biome assigned; Caught filter is ALL by default

* Highlighting text filters, resetting all filters when starting up

* No error messag when cursor on uncaught species, showing sprite again after toggling stats

* Simplified Pokemon Scan logic, accepts separate words as input

* Dynamically resizing ability box, showing ability description on first hover. Removed debug logs.

* Removed some more debug messages.

* Filter bar can adjust cursorOffset and x padding

* Fixed some type definitions

* Fixed more warnings; added localization strings in the pokedex scan overlay.

* Fixed fatal bug due to using Object.keys

* Removed debug messages

* Added try catch construct to prevent error that was breaking reloadHelper tests

* Added filter for starters / evolutions

* Biome filter option for uncatchable mons

* C and V buttons snap cursor to filters

* Changing background to make instructions visible

* Can buy candy upgrades through pokedex

* Displaying base stats as bars in an overlay

* Including baby forms among uncatchable mons

* Including evolutions when filtering by biome

* Working logic for select ui handler with skips and scroll

* -Pokedex page showing biomes from prevolutions; displaying correct biomes for forms of Rotom, Burmy and Lycanroc

* Fixed bug in base stats overlay

* Regional forms display name of region in evolutions and prevolutions

* Better messages for evolution conditions

* Showing proper descriptions for menu

* Adding sound effects to menu, and pokemon cry when opening page

* Changing menu colors to textstyle options supporting a legacy version.

* Fix to getStarterSpeciesId to work with all-unlocks files

* Passing a TextStyle to option select ui handler to allow for shadowed text

* Fixed bug of overlapping labels in text filters

* Fixed bug with supportHover and skipped indices in option select ui handler

* Localization of pokemon number label

* Fix to pokemon number localization

* Fix to pokemon number localization

* Adding some comments, removing useless elements

* More cleanup

* Removed candy upgrade instructions from evolved pokemon; attempting to buy candies from evolution now gives error sound instead of crashing the game

* Attempting to exit from filter text is now allowed if current option is empty

* UI changes to make dex pages work in legacy style

* Pokemon name shown while in alt form is no more capitalized

* Handling uncaught pokemon

* Showing types on Pokémon page

* Introducing globalScene everywhere

* Showing evolution requirements in message box

* Displaying form changing items; now using pokemonFormChanges to only show reachable forms

* Playing correct cry

* Pokemon cry in setSpeciesDetails

* Left and right buttons to turn previous or next pokedex page

* Cleaned up "last" from this.species; turning pages now preserves memories of unlocks

* Pokerus cursor is now treated as decoration

* Correctly displaying prevolutions for Pikachu and Gholdengo

* Uncaught forms can be cycled through (with black sprite and no options available)

* Filtering by moves now shows icons to distinguish egg and tm moves

* Added icons for passive abilities

* Added icons to legacy mode; fixed bug that caused game to hang when switching to or from legacy mode

* Pokedex entries are accessible through party screen

* Adding sort criteria for consistency with starter select screen

* Added options to cost reduction filter for consistency with starter select screen

* Updating optionSelectUiHandler to simplify logic and fix bug of autocomplete showing options incorrectly

* Adding Pokedéx option in starter select screen

* Prevolutions are shown properly again; battle forms are considered caught as long as the base form is caught

* Small fixes to evolution and form change descriptions

* Reworked evolutions menu to incorporate condition descriptions

* Moving evolution condition description logic entirely to the SpeciesEvolution class

* Removed extra Miraidon and Koraidon forms

* Properly showing evolution text for Dunsparce and Maushold

* Displaying uncaught forms for Dudunsparce and Maushold properly

* Displaying correct forms for Urshifu and Toxicitry after evolution

* Cleared up comments

* Updating test for tandemaus evolution

* Localized labels for egg moves and abilities

* Added button to show back sprites

* Back to showing only caught battleforms; added dexForDevs option

* Merging shiny and variant buttons

* Uncaught battle forms options are shown in dark text, like evolutions

* Showing proper gender for mons that can only be (or have only caught in) one gender

* Apply suggestions from code review

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* Removed unused options from base-stats-overlay

* Fixed import of BaseStatsOverlay

* Displaying form-specific TMs properly; adjusting for passives rework

* Removed logging messages

* resetting containers to prevent memory leaks

* Updating integer to number in pokedex

* Implemented suggestion

* Removed some stray comments

* Fixed logic for cursor coming down from filter bar

* Transition from filters to dex box now works in a visually pleasing way

---------

Co-authored-by: Lugiad <2070109+Adri1@users.noreply.github.com>
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
Co-authored-by: damocleas <damocleas25@gmail.com>
2025-02-08 11:48:06 -05:00

723 lines
25 KiB
TypeScript

import { bypassLogin } from "#app/battle-scene";
import { globalScene } from "#app/global-scene";
import { TextStyle, addTextObject, getTextStyleOptions } from "./text";
import { Mode } from "./ui";
import * as Utils from "../utils";
import { addWindow, WindowVariant } from "./ui-theme";
import MessageUiHandler from "./message-ui-handler";
import type { OptionSelectConfig, OptionSelectItem } from "./abstact-option-select-ui-handler";
import { Tutorial, handleTutorial } from "../tutorial";
import { loggedInUser, updateUserInfo } from "../account";
import i18next from "i18next";
import { Button } from "#enums/buttons";
import { GameDataType } from "#enums/game-data-type";
import BgmBar from "#app/ui/bgm-bar";
import type AwaitableUiHandler from "./awaitable-ui-handler";
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
import { AdminMode, getAdminModeName } from "./admin-ui-handler";
import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
enum MenuOptions {
GAME_SETTINGS,
ACHIEVEMENTS,
STATS,
EGG_LIST,
EGG_GACHA,
POKEDEX,
MANAGE_DATA,
COMMUNITY,
SAVE_AND_QUIT,
LOG_OUT,
}
let wikiUrl = "https://wiki.pokerogue.net/start";
const discordUrl = "https://discord.gg/uWpTfdKG49";
const githubUrl = "https://github.com/pagefaultgames/pokerogue";
const redditUrl = "https://www.reddit.com/r/pokerogue";
const donateUrl = "https://github.com/sponsors/pagefaultgames";
export default class MenuUiHandler extends MessageUiHandler {
private readonly textPadding = 8;
private readonly defaultMessageBoxWidth = 220;
private readonly defaultWordWrapWidth = 1224;
private menuContainer: Phaser.GameObjects.Container;
private menuMessageBoxContainer: Phaser.GameObjects.Container;
private menuOverlay: Phaser.GameObjects.Rectangle;
private menuBg: Phaser.GameObjects.NineSlice;
protected optionSelectText: Phaser.GameObjects.Text;
private cursorObj: Phaser.GameObjects.Image | null;
private excludedMenus: () => ConditionalMenu[];
private menuOptions: MenuOptions[];
protected manageDataConfig: OptionSelectConfig;
protected communityConfig: OptionSelectConfig;
// Windows for the default message box and the message box for testing dialogue
private menuMessageBox: Phaser.GameObjects.NineSlice;
private dialogueMessageBox: Phaser.GameObjects.NineSlice;
protected scale: number = 0.1666666667;
public bgmBar: BgmBar;
constructor(mode: Mode | null = null) {
super(mode);
this.excludedMenus = () => [
{ condition: [ Mode.COMMAND, Mode.TITLE ].includes(mode ?? Mode.TITLE), options: [ MenuOptions.EGG_GACHA, MenuOptions.EGG_LIST ]},
{ condition: bypassLogin, options: [ MenuOptions.LOG_OUT ]}
];
this.menuOptions = Utils.getEnumKeys(MenuOptions)
.map(m => parseInt(MenuOptions[m]) as MenuOptions)
.filter(m => {
return !this.excludedMenus().some(exclusion => exclusion.condition && exclusion.options.includes(m));
});
}
setup(): void {
const ui = this.getUi();
// wiki url directs based on languges available on wiki
const lang = i18next.resolvedLanguage?.substring(0, 2)!; // TODO: is this bang correct?
if ([ "de", "fr", "ko", "zh" ].includes(lang)) {
wikiUrl = `https://wiki.pokerogue.net/${lang}:start`;
}
this.bgmBar = new BgmBar();
this.bgmBar.setup();
ui.bgmBar = this.bgmBar;
this.menuContainer = globalScene.add.container(1, -(globalScene.game.canvas.height / 6) + 1);
this.menuContainer.setName("menu");
this.menuContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains);
this.menuOverlay = new Phaser.GameObjects.Rectangle(globalScene, -1, -1, globalScene.scaledCanvas.width, globalScene.scaledCanvas.height, 0xffffff, 0.3);
this.menuOverlay.setName("menu-overlay");
this.menuOverlay.setOrigin(0, 0);
this.menuContainer.add(this.menuOverlay);
this.menuContainer.add(this.bgmBar);
this.menuContainer.setVisible(false);
}
render() {
const ui = this.getUi();
this.excludedMenus = () => [
{ condition: globalScene.getCurrentPhase() instanceof SelectModifierPhase, options: [ MenuOptions.EGG_GACHA ]},
{ condition: bypassLogin, options: [ MenuOptions.LOG_OUT ]}
];
this.menuOptions = Utils.getEnumKeys(MenuOptions)
.map(m => parseInt(MenuOptions[m]) as MenuOptions)
.filter(m => {
return !this.excludedMenus().some(exclusion => exclusion.condition && exclusion.options.includes(m));
});
this.optionSelectText = addTextObject(0, 0, this.menuOptions.map(o => `${i18next.t(`menuUiHandler:${MenuOptions[o]}`)}`).join("\n"), TextStyle.WINDOW, { maxLines: this.menuOptions.length });
this.optionSelectText.setLineSpacing(12);
this.scale = getTextStyleOptions(TextStyle.WINDOW, globalScene.uiTheme).scale;
this.menuBg = addWindow(
(globalScene.game.canvas.width / 6) - (this.optionSelectText.displayWidth + 25),
0,
this.optionSelectText.displayWidth + 19 + 24 * this.scale,
(globalScene.game.canvas.height / 6) - 2
);
this.menuBg.setOrigin(0, 0);
this.optionSelectText.setPositionRelative(this.menuBg, 10 + 24 * this.scale, 6);
this.menuContainer.add(this.menuBg);
this.menuContainer.add(this.optionSelectText);
ui.add(this.menuContainer);
this.menuMessageBoxContainer = globalScene.add.container(0, 130);
this.menuMessageBoxContainer.setName("menu-message-box");
this.menuMessageBoxContainer.setVisible(false);
// Window for general messages
this.menuMessageBox = addWindow(0, 0, this.defaultMessageBoxWidth, 48);
this.menuMessageBox.setOrigin(0, 0);
this.menuMessageBoxContainer.add(this.menuMessageBox);
// Full-width window used for testing dialog messages in debug mode
this.dialogueMessageBox = addWindow(-this.textPadding, 0, globalScene.game.canvas.width / 6 + this.textPadding * 2, 49, false, false, 0, 0, WindowVariant.THIN);
this.dialogueMessageBox.setOrigin(0, 0);
this.menuMessageBoxContainer.add(this.dialogueMessageBox);
const menuMessageText = addTextObject(this.textPadding, this.textPadding, "", TextStyle.WINDOW, { maxLines: 2 });
menuMessageText.setName("menu-message");
menuMessageText.setOrigin(0, 0);
this.menuMessageBoxContainer.add(menuMessageText);
this.initTutorialOverlay(this.menuContainer);
this.initPromptSprite(this.menuMessageBoxContainer);
this.message = menuMessageText;
// By default we use the general purpose message window
this.setDialogTestMode(false);
this.menuContainer.add(this.menuMessageBoxContainer);
const manageDataOptions: any[] = []; // TODO: proper type
const confirmSlot = (message: string, slotFilter: (i: number) => boolean, callback: (i: number) => void) => {
ui.revertMode();
ui.showText(message, null, () => {
const config: OptionSelectConfig = {
options: new Array(5).fill(null).map((_, i) => i).filter(slotFilter).map(i => {
return {
label: i18next.t("menuUiHandler:slot", { slotNumber: i + 1 }),
handler: () => {
callback(i);
ui.revertMode();
ui.showText("", 0);
return true;
}
};
}).concat([{
label: i18next.t("menuUiHandler:cancel"),
handler: () => {
ui.revertMode();
ui.showText("", 0);
return true;
}
}]),
xOffset: 98
};
ui.setOverlayMode(Mode.MENU_OPTION_SELECT, config);
});
};
if (Utils.isLocal || Utils.isBeta) {
manageDataOptions.push({
label: i18next.t("menuUiHandler:importSession"),
handler: () => {
confirmSlot(i18next.t("menuUiHandler:importSlotSelect"), () => true, slotId => globalScene.gameData.importData(GameDataType.SESSION, slotId));
return true;
},
keepOpen: true
});
}
manageDataOptions.push({
label: i18next.t("menuUiHandler:exportSession"),
handler: () => {
const dataSlots: number[] = [];
Promise.all(
new Array(5).fill(null).map((_, i) => {
const slotId = i;
return globalScene.gameData.getSession(slotId).then(data => {
if (data) {
dataSlots.push(slotId);
}
});
})).then(() => {
confirmSlot(i18next.t("menuUiHandler:exportSlotSelect"),
i => dataSlots.indexOf(i) > -1,
slotId => globalScene.gameData.tryExportData(GameDataType.SESSION, slotId));
});
return true;
},
keepOpen: true
});
manageDataOptions.push({
label: i18next.t("menuUiHandler:importRunHistory"),
handler: () => {
globalScene.gameData.importData(GameDataType.RUN_HISTORY);
return true;
},
keepOpen: true
});
manageDataOptions.push({
label: i18next.t("menuUiHandler:exportRunHistory"),
handler: () => {
globalScene.gameData.tryExportData(GameDataType.RUN_HISTORY);
return true;
},
keepOpen: true
});
if (Utils.isLocal || Utils.isBeta) {
manageDataOptions.push({
label: i18next.t("menuUiHandler:importData"),
handler: () => {
ui.revertMode();
globalScene.gameData.importData(GameDataType.SYSTEM);
return true;
},
keepOpen: true
});
}
manageDataOptions.push({
label: i18next.t("menuUiHandler:exportData"),
handler: () => {
globalScene.gameData.tryExportData(GameDataType.SYSTEM);
return true;
},
keepOpen: true
},
{
label: i18next.t("menuUiHandler:consentPreferences"),
handler: () => {
const consentLink = document.querySelector(".termly-display-preferences") as HTMLInputElement;
const clickEvent = new MouseEvent("click", {
view: window,
bubbles: true,
cancelable: true
});
consentLink.dispatchEvent(clickEvent);
consentLink.focus();
return true;
},
keepOpen: true
});
if (Utils.isLocal || Utils.isBeta) { // this should make sure we don't have this option in live
manageDataOptions.push({
label: "Test Dialogue",
handler: () => {
ui.playSelect();
const prefilledText = "";
const buttonAction: any = {};
buttonAction["buttonActions"] = [
(sanitizedName: string) => {
ui.revertMode();
ui.playSelect();
const dialogueTestName = sanitizedName;
const dialogueName = decodeURIComponent(escape(atob(dialogueTestName)));
const handler = ui.getHandler() as AwaitableUiHandler;
handler.tutorialActive = true;
const interpolatorOptions: any = {};
const splitArr = dialogueName.split(" "); // this splits our inputted text into words to cycle through later
const translatedString = splitArr[0]; // this is our outputted i18 string
const regex = RegExp("\\{\\{(\\w*)\\}\\}", "g"); // this is a regex expression to find all the text between {{ }} in the i18 output
const matches = i18next.t(translatedString).match(regex) ?? [];
if (matches.length > 0) {
for (let match = 0; match < matches.length; match++) {
// we add 1 here because splitArr[0] is our first value for the translatedString, and after that is where the variables are
// the regex here in the replace (/\W/g) is to remove the {{ and }} and just give us all alphanumeric characters
if (typeof splitArr[match + 1] !== "undefined") {
interpolatorOptions[matches[match].replace(/\W/g, "")] = i18next.t(splitArr[match + 1]);
}
}
}
// Switch to the dialog test window
this.setDialogTestMode(true);
ui.showText(String(i18next.t(translatedString, interpolatorOptions)), null, () => globalScene.ui.showText("", 0, () => {
handler.tutorialActive = false;
// Go back to the default message window
this.setDialogTestMode(false);
}), null, true);
},
() => {
ui.revertMode();
}
];
ui.setMode(Mode.TEST_DIALOGUE, buttonAction, prefilledText);
return true;
},
keepOpen: true
});
}
manageDataOptions.push({
label: i18next.t("menuUiHandler:cancel"),
handler: () => {
globalScene.ui.revertMode();
return true;
},
keepOpen: true
});
//Thank you Vassiat
this.manageDataConfig = {
xOffset: 98,
options: manageDataOptions,
maxOptions: 7
};
const communityOptions: OptionSelectItem[] = [
{
label: "Wiki",
handler: () => {
window.open(wikiUrl, "_blank")?.focus();
return true;
},
keepOpen: true
},
{
label: "Discord",
handler: () => {
window.open(discordUrl, "_blank")?.focus();
return true;
},
keepOpen: true
},
{
label: "GitHub",
handler: () => {
window.open(githubUrl, "_blank")?.focus();
return true;
},
keepOpen: true
},
{
label: "Reddit",
handler: () => {
window.open(redditUrl, "_blank")?.focus();
return true;
},
keepOpen: true
},
{
label: i18next.t("menuUiHandler:donate"),
handler: () => {
window.open(donateUrl, "_blank")?.focus();
return true;
},
keepOpen: true
}
];
if (!bypassLogin && loggedInUser?.hasAdminRole) {
communityOptions.push({
label: "Admin",
handler: () => {
const skippedAdminModes: AdminMode[] = [ AdminMode.ADMIN ]; // this is here so that we can skip the menu populating enums that aren't meant for the menu, such as the AdminMode.ADMIN
const options: OptionSelectItem[] = [];
Object.values(AdminMode).filter((v) => !isNaN(Number(v)) && !skippedAdminModes.includes(v as AdminMode)).forEach((mode) => { // this gets all the enums in a way we can use
options.push({
label: getAdminModeName(mode as AdminMode),
handler: () => {
ui.playSelect();
ui.setOverlayMode(Mode.ADMIN, {
buttonActions: [
// we double revert here and below to go back 2 layers of menus
() => {
ui.revertMode();
ui.revertMode();
},
() => {
ui.revertMode();
ui.revertMode();
}
]
}, mode); // mode is our AdminMode enum
return true;
}
});
});
options.push({
label: "Cancel",
handler: () => {
ui.revertMode();
return true;
}
});
globalScene.ui.setOverlayMode(Mode.OPTION_SELECT, {
options: options,
delay: 0
});
return true;
},
keepOpen: true
});
}
communityOptions.push({
label: i18next.t("menuUiHandler:cancel"),
handler: () => {
globalScene.ui.revertMode();
return true;
}
});
this.communityConfig = {
xOffset: 98,
options: communityOptions
};
this.setCursor(0);
}
show(args: any[]): boolean {
this.render();
super.show(args);
this.menuOptions = Utils.getEnumKeys(MenuOptions)
.map(m => parseInt(MenuOptions[m]) as MenuOptions)
.filter(m => {
return !this.excludedMenus().some(exclusion => exclusion.condition && exclusion.options.includes(m));
});
this.menuContainer.setVisible(true);
this.setCursor(0);
this.getUi().moveTo(this.menuContainer, this.getUi().length - 1);
this.getUi().hideTooltip();
globalScene.playSound("ui/menu_open");
// Make sure the tutorial overlay sits above everything, but below the message box
this.menuContainer.bringToTop(this.tutorialOverlay);
this.menuContainer.bringToTop(this.menuMessageBoxContainer);
handleTutorial(Tutorial.Menu);
this.bgmBar.toggleBgmBar(true);
return true;
}
processInput(button: Button): boolean {
const ui = this.getUi();
let success = false;
let error = false;
if (button === Button.ACTION) {
let adjustedCursor = this.cursor;
const excludedMenu = this.excludedMenus().find(e => e.condition);
if (excludedMenu !== undefined && excludedMenu.options !== undefined && excludedMenu.options.length > 0) {
const sortedOptions = excludedMenu.options.sort();
for (const imo of sortedOptions) {
if (adjustedCursor >= imo) {
adjustedCursor++;
} else {
break;
}
}
}
this.showText("", 0);
switch (adjustedCursor) {
case MenuOptions.GAME_SETTINGS:
ui.setOverlayMode(Mode.SETTINGS);
success = true;
break;
case MenuOptions.ACHIEVEMENTS:
ui.setOverlayMode(Mode.ACHIEVEMENTS);
success = true;
break;
case MenuOptions.STATS:
ui.setOverlayMode(Mode.GAME_STATS);
success = true;
break;
case MenuOptions.EGG_LIST:
if (globalScene.gameData.eggs.length) {
ui.revertMode();
ui.setOverlayMode(Mode.EGG_LIST);
success = true;
} else {
ui.showText(i18next.t("menuUiHandler:noEggs"), null, () => ui.showText(""), Utils.fixedInt(1500));
error = true;
}
break;
case MenuOptions.EGG_GACHA:
ui.revertMode();
ui.setOverlayMode(Mode.EGG_GACHA);
success = true;
break;
case MenuOptions.POKEDEX:
ui.revertMode();
ui.setOverlayMode(Mode.POKEDEX);
success = true;
break;
case MenuOptions.MANAGE_DATA:
if (!bypassLogin && !this.manageDataConfig.options.some(o => o.label === i18next.t("menuUiHandler:linkDiscord") || o.label === i18next.t("menuUiHandler:unlinkDiscord"))) {
this.manageDataConfig.options.splice(this.manageDataConfig.options.length - 1, 0,
{
label: loggedInUser?.discordId === "" ? i18next.t("menuUiHandler:linkDiscord") : i18next.t("menuUiHandler:unlinkDiscord"),
handler: () => {
if (loggedInUser?.discordId === "") {
const token = Utils.getCookie(Utils.sessionIdKey);
const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/discord/callback`);
const discordId = import.meta.env.VITE_DISCORD_CLIENT_ID;
const discordUrl = `https://discord.com/api/oauth2/authorize?client_id=${discordId}&redirect_uri=${redirectUri}&response_type=code&scope=identify&state=${token}&prompt=none`;
window.open(discordUrl, "_self");
return true;
} else {
pokerogueApi.unlinkDiscord().then(_isSuccess => {
updateUserInfo().then(() => globalScene.reset(true, true));
});
return true;
}
}
},
{
label: loggedInUser?.googleId === "" ? i18next.t("menuUiHandler:linkGoogle") : i18next.t("menuUiHandler:unlinkGoogle"),
handler: () => {
if (loggedInUser?.googleId === "") {
const token = Utils.getCookie(Utils.sessionIdKey);
const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/google/callback`);
const googleId = import.meta.env.VITE_GOOGLE_CLIENT_ID;
const googleUrl = `https://accounts.google.com/o/oauth2/auth?client_id=${googleId}&response_type=code&redirect_uri=${redirectUri}&scope=openid&state=${token}`;
window.open(googleUrl, "_self");
return true;
} else {
pokerogueApi.unlinkGoogle().then(_isSuccess => {
updateUserInfo().then(() => globalScene.reset(true, true));
});
return true;
}
}
});
}
ui.setOverlayMode(Mode.MENU_OPTION_SELECT, this.manageDataConfig);
success = true;
break;
case MenuOptions.COMMUNITY:
ui.setOverlayMode(Mode.MENU_OPTION_SELECT, this.communityConfig);
success = true;
break;
case MenuOptions.SAVE_AND_QUIT:
if (globalScene.currentBattle) {
success = true;
const doSaveQuit = () => {
ui.setMode(Mode.LOADING, {
buttonActions: [], fadeOut: () =>
globalScene.gameData.saveAll(true, true, true, true).then(() => {
globalScene.reset(true);
})
});
};
if (globalScene.currentBattle.turn > 1) {
ui.showText(i18next.t("menuUiHandler:losingProgressionWarning"), null, () => {
if (!this.active) {
this.showText("", 0);
return;
}
ui.setOverlayMode(Mode.CONFIRM, doSaveQuit, () => {
ui.revertMode();
this.showText("", 0);
}, false, -98);
});
} else {
doSaveQuit();
}
} else {
error = true;
}
break;
case MenuOptions.LOG_OUT:
success = true;
const doLogout = () => {
ui.setMode(Mode.LOADING, {
buttonActions: [], fadeOut: () => pokerogueApi.account.logout().then(() => {
updateUserInfo().then(() => globalScene.reset(true, true));
})
});
};
if (globalScene.currentBattle) {
ui.showText(i18next.t("menuUiHandler:losingProgressionWarning"), null, () => {
if (!this.active) {
this.showText("", 0);
return;
}
ui.setOverlayMode(Mode.CONFIRM, doLogout, () => {
ui.revertMode();
this.showText("", 0);
}, false, -98);
});
} else {
doLogout();
}
break;
}
} else if (button === Button.CANCEL) {
success = true;
ui.revertMode().then(result => {
if (!result) {
ui.setMode(Mode.MESSAGE);
}
});
} else {
switch (button) {
case Button.UP:
if (this.cursor) {
success = this.setCursor(this.cursor - 1);
} else {
success = this.setCursor(this.menuOptions.length - 1);
}
break;
case Button.DOWN:
if (this.cursor + 1 < this.menuOptions.length) {
success = this.setCursor(this.cursor + 1);
} else {
success = this.setCursor(0);
}
break;
}
}
if (success) {
ui.playSelect();
} else if (error) {
ui.playError();
}
return success || error;
}
/**
* Switch the message window style and size when we are replaying dialog for debug purposes
* In "dialog test mode", the window takes the whole width of the screen and the text
* is set up to wrap around the same way as the dialogue during the game
* @param isDialogMode whether to use the dialog test
*/
setDialogTestMode(isDialogMode: boolean) {
this.menuMessageBox.setVisible(!isDialogMode);
this.dialogueMessageBox.setVisible(isDialogMode);
// If we're testing dialog, we use the same word wrapping as the battle message handler
this.message.setWordWrapWidth(isDialogMode ? globalScene.ui.getMessageHandler().wordWrapWidth : this.defaultWordWrapWidth);
this.message.setX(isDialogMode ? this.textPadding + 1 : this.textPadding);
this.message.setY(isDialogMode ? this.textPadding + 0.4 : this.textPadding);
}
showText(text: string, delay?: number, callback?: Function, callbackDelay?: number, prompt?: boolean, promptDelay?: number): void {
this.menuMessageBoxContainer.setVisible(!!text);
super.showText(text, delay, callback, callbackDelay, prompt, promptDelay);
}
setCursor(cursor: number): boolean {
const ret = super.setCursor(cursor);
if (!this.cursorObj) {
this.cursorObj = globalScene.add.image(0, 0, "cursor");
this.cursorObj.setOrigin(0, 0);
this.menuContainer.add(this.cursorObj);
}
this.cursorObj.setScale(this.scale * 6);
this.cursorObj.setPositionRelative(this.menuBg, 7, 6 + (18 + this.cursor * 96) * this.scale);
return ret;
}
clear() {
super.clear();
this.menuContainer.setVisible(false);
this.bgmBar.toggleBgmBar(false);
this.eraseCursor();
}
eraseCursor() {
if (this.cursorObj) {
this.cursorObj.destroy();
}
this.cursorObj = null;
}
}
interface ConditionalMenu {
condition: boolean;
options: MenuOptions[];
}