pokerogue/src/ui/ui.ts
James Diefenbach e822c91a16
[Enhancement] Skip egg hatching and show summary (#3726)
* cherry picked commits / manual copy

* better dex tracking for summary after regular egg hatching

* ui changes

* updated egg hatch bg, added candy tracker, icon anims for new shiny or new form unlock

* added i18 line, reset overrides

* touchup

* code cleanup, documentation and slight refactor

* sprite display fix

* load interrupts, simple sfx and no summary for small egg amounts

* Garbage Collection + Eslint/Docs approved.

* time logging and optimisation

* skip redundant load

* more time logs and fix pre-load issues

* more detailed loading logs

* changed loading to be on demand from cursor nav

* fix missing variant icon fallback

* removing redundant time logs and code touchup

* code cleanup

* Comments so developer doesn't get bugged about garbage collecton

* remove logs n stuff

* lang settings touchup and final touchup plus uploading blank egg summary bg

* fix nits, js imports, extra docs, magic numbers changed

* extra docs and spacing nits

* Update Github

---------

Co-authored-by: James Diefenbach <z5421232@ad.unsw.edu.au>
Co-authored-by: Frederico Santos <frederico.f.santos@tecnico.ulisboa.pt>
Co-authored-by: frutescens <info@laptop>
Co-authored-by: Mumble <171087428+frutescens@users.noreply.github.com>
2024-09-04 16:59:25 +00:00

564 lines
18 KiB
TypeScript

import { default as BattleScene } from "../battle-scene";
import UiHandler from "./ui-handler";
import BattleMessageUiHandler from "./battle-message-ui-handler";
import CommandUiHandler from "./command-ui-handler";
import PartyUiHandler from "./party-ui-handler";
import FightUiHandler from "./fight-ui-handler";
import MessageUiHandler from "./message-ui-handler";
import ConfirmUiHandler from "./confirm-ui-handler";
import ModifierSelectUiHandler from "./modifier-select-ui-handler";
import BallUiHandler from "./ball-ui-handler";
import SummaryUiHandler from "./summary-ui-handler";
import StarterSelectUiHandler from "./starter-select-ui-handler";
import EvolutionSceneHandler from "./evolution-scene-handler";
import TargetSelectUiHandler from "./target-select-ui-handler";
import SettingsUiHandler from "./settings/settings-ui-handler";
import SettingsGamepadUiHandler from "./settings/settings-gamepad-ui-handler";
import GameChallengesUiHandler from "./challenges-select-ui-handler";
import { TextStyle, addTextObject } from "./text";
import AchvBar from "./achv-bar";
import MenuUiHandler from "./menu-ui-handler";
import AchvsUiHandler from "./achvs-ui-handler";
import OptionSelectUiHandler from "./settings/option-select-ui-handler";
import EggHatchSceneHandler from "./egg-hatch-scene-handler";
import EggListUiHandler from "./egg-list-ui-handler";
import EggGachaUiHandler from "./egg-gacha-ui-handler";
import { addWindow } from "./ui-theme";
import LoginFormUiHandler from "./login-form-ui-handler";
import RegistrationFormUiHandler from "./registration-form-ui-handler";
import LoadingModalUiHandler from "./loading-modal-ui-handler";
import * as Utils from "../utils";
import GameStatsUiHandler from "./game-stats-ui-handler";
import AwaitableUiHandler from "./awaitable-ui-handler";
import SaveSlotSelectUiHandler from "./save-slot-select-ui-handler";
import TitleUiHandler from "./title-ui-handler";
import SavingIconHandler from "./saving-icon-handler";
import UnavailableModalUiHandler from "./unavailable-modal-ui-handler";
import OutdatedModalUiHandler from "./outdated-modal-ui-handler";
import SessionReloadModalUiHandler from "./session-reload-modal-ui-handler";
import { Button } from "#enums/buttons";
import i18next from "i18next";
import GamepadBindingUiHandler from "./settings/gamepad-binding-ui-handler";
import SettingsKeyboardUiHandler from "#app/ui/settings/settings-keyboard-ui-handler";
import KeyboardBindingUiHandler from "#app/ui/settings/keyboard-binding-ui-handler";
import SettingsDisplayUiHandler from "./settings/settings-display-ui-handler";
import SettingsAudioUiHandler from "./settings/settings-audio-ui-handler";
import { PlayerGender } from "#enums/player-gender";
import BgmBar from "#app/ui/bgm-bar";
import RenameFormUiHandler from "./rename-form-ui-handler";
import AdminUiHandler from "./admin-ui-handler";
import RunHistoryUiHandler from "./run-history-ui-handler";
import RunInfoUiHandler from "./run-info-ui-handler";
import EggSummaryUiHandler from "./egg-summary-ui-handler";
import TestDialogueUiHandler from "#app/ui/test-dialogue-ui-handler";
import AutoCompleteUiHandler from "./autocomplete-ui-handler";
export enum Mode {
MESSAGE,
TITLE,
COMMAND,
FIGHT,
BALL,
TARGET_SELECT,
MODIFIER_SELECT,
SAVE_SLOT,
PARTY,
SUMMARY,
STARTER_SELECT,
EVOLUTION_SCENE,
EGG_HATCH_SCENE,
EGG_HATCH_SUMMARY,
CONFIRM,
OPTION_SELECT,
MENU,
MENU_OPTION_SELECT,
SETTINGS,
SETTINGS_DISPLAY,
SETTINGS_AUDIO,
SETTINGS_GAMEPAD,
GAMEPAD_BINDING,
SETTINGS_KEYBOARD,
KEYBOARD_BINDING,
ACHIEVEMENTS,
GAME_STATS,
EGG_LIST,
EGG_GACHA,
LOGIN_FORM,
REGISTRATION_FORM,
LOADING,
SESSION_RELOAD,
UNAVAILABLE,
OUTDATED,
CHALLENGE_SELECT,
RENAME_POKEMON,
RUN_HISTORY,
RUN_INFO,
TEST_DIALOGUE,
AUTO_COMPLETE,
ADMIN,
}
const transitionModes = [
Mode.SAVE_SLOT,
Mode.PARTY,
Mode.SUMMARY,
Mode.STARTER_SELECT,
Mode.EVOLUTION_SCENE,
Mode.EGG_HATCH_SCENE,
Mode.EGG_LIST,
Mode.EGG_GACHA,
Mode.CHALLENGE_SELECT,
Mode.RUN_HISTORY,
];
const noTransitionModes = [
Mode.TITLE,
Mode.CONFIRM,
Mode.OPTION_SELECT,
Mode.MENU,
Mode.MENU_OPTION_SELECT,
Mode.GAMEPAD_BINDING,
Mode.KEYBOARD_BINDING,
Mode.SETTINGS,
Mode.SETTINGS_AUDIO,
Mode.SETTINGS_DISPLAY,
Mode.SETTINGS_GAMEPAD,
Mode.SETTINGS_KEYBOARD,
Mode.ACHIEVEMENTS,
Mode.GAME_STATS,
Mode.LOGIN_FORM,
Mode.REGISTRATION_FORM,
Mode.LOADING,
Mode.SESSION_RELOAD,
Mode.UNAVAILABLE,
Mode.OUTDATED,
Mode.RENAME_POKEMON,
Mode.TEST_DIALOGUE,
Mode.AUTO_COMPLETE,
Mode.ADMIN,
];
export default class UI extends Phaser.GameObjects.Container {
private mode: Mode;
private modeChain: Mode[];
public handlers: UiHandler[];
private overlay: Phaser.GameObjects.Rectangle;
public achvBar: AchvBar;
public bgmBar: BgmBar;
public savingIcon: SavingIconHandler;
private tooltipContainer: Phaser.GameObjects.Container;
private tooltipBg: Phaser.GameObjects.NineSlice;
private tooltipTitle: Phaser.GameObjects.Text;
private tooltipContent: Phaser.GameObjects.Text;
private overlayActive: boolean;
constructor(scene: BattleScene) {
super(scene, 0, scene.game.canvas.height / 6);
this.mode = Mode.MESSAGE;
this.modeChain = [];
this.handlers = [
new BattleMessageUiHandler(scene),
new TitleUiHandler(scene),
new CommandUiHandler(scene),
new FightUiHandler(scene),
new BallUiHandler(scene),
new TargetSelectUiHandler(scene),
new ModifierSelectUiHandler(scene),
new SaveSlotSelectUiHandler(scene),
new PartyUiHandler(scene),
new SummaryUiHandler(scene),
new StarterSelectUiHandler(scene),
new EvolutionSceneHandler(scene),
new EggHatchSceneHandler(scene),
new EggSummaryUiHandler(scene),
new ConfirmUiHandler(scene),
new OptionSelectUiHandler(scene),
new MenuUiHandler(scene),
new OptionSelectUiHandler(scene, Mode.MENU_OPTION_SELECT),
// settings
new SettingsUiHandler(scene),
new SettingsDisplayUiHandler(scene),
new SettingsAudioUiHandler(scene),
new SettingsGamepadUiHandler(scene),
new GamepadBindingUiHandler(scene),
new SettingsKeyboardUiHandler(scene),
new KeyboardBindingUiHandler(scene),
new AchvsUiHandler(scene),
new GameStatsUiHandler(scene),
new EggListUiHandler(scene),
new EggGachaUiHandler(scene),
new LoginFormUiHandler(scene),
new RegistrationFormUiHandler(scene),
new LoadingModalUiHandler(scene),
new SessionReloadModalUiHandler(scene),
new UnavailableModalUiHandler(scene),
new OutdatedModalUiHandler(scene),
new GameChallengesUiHandler(scene),
new RenameFormUiHandler(scene),
new RunHistoryUiHandler(scene),
new RunInfoUiHandler(scene),
new TestDialogueUiHandler(scene, Mode.TEST_DIALOGUE),
new AutoCompleteUiHandler(scene),
new AdminUiHandler(scene),
];
}
setup(): void {
this.setName(`ui-${Mode[this.mode]}`);
for (const handler of this.handlers) {
handler.setup();
}
this.overlay = this.scene.add.rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6, 0);
this.overlay.setName("rect-ui-overlay");
this.overlay.setOrigin(0, 0);
(this.scene as BattleScene).uiContainer.add(this.overlay);
this.overlay.setVisible(false);
this.setupTooltip();
this.achvBar = new AchvBar(this.scene as BattleScene);
this.achvBar.setup();
(this.scene as BattleScene).uiContainer.add(this.achvBar);
this.savingIcon = new SavingIconHandler(this.scene as BattleScene);
this.savingIcon.setup();
(this.scene as BattleScene).uiContainer.add(this.savingIcon);
}
private setupTooltip() {
this.tooltipContainer = this.scene.add.container(0, 0);
this.tooltipContainer.setName("tooltip");
this.tooltipContainer.setVisible(false);
this.tooltipBg = addWindow(this.scene as BattleScene, 0, 0, 128, 31);
this.tooltipBg.setName("window-tooltip-bg");
this.tooltipBg.setOrigin(0, 0);
this.tooltipTitle = addTextObject(this.scene, 64, 4, "", TextStyle.TOOLTIP_TITLE);
this.tooltipTitle.setName("text-tooltip-title");
this.tooltipTitle.setOrigin(0.5, 0);
this.tooltipContent = addTextObject(this.scene, 6, 16, "", TextStyle.TOOLTIP_CONTENT);
this.tooltipContent.setName("text-tooltip-content");
this.tooltipContent.setWordWrapWidth(696);
this.tooltipContainer.add(this.tooltipBg);
this.tooltipContainer.add(this.tooltipTitle);
this.tooltipContainer.add(this.tooltipContent);
(this.scene as BattleScene).uiContainer.add(this.tooltipContainer);
}
getHandler<H extends UiHandler = UiHandler>(): H {
return this.handlers[this.mode] as H;
}
getMessageHandler(): BattleMessageUiHandler {
return this.handlers[Mode.MESSAGE] as BattleMessageUiHandler;
}
processInfoButton(pressed: boolean) {
if (this.overlayActive) {
return false;
}
const battleScene = this.scene as BattleScene;
if ([Mode.CONFIRM, Mode.COMMAND, Mode.FIGHT, Mode.MESSAGE].includes(this.mode)) {
battleScene?.processInfoButton(pressed);
return true;
}
battleScene?.processInfoButton(false);
return true;
}
processInput(button: Button): boolean {
if (this.overlayActive) {
return false;
}
const handler = this.getHandler();
if (handler instanceof AwaitableUiHandler && handler.tutorialActive) {
return handler.processTutorialInput(button);
}
return handler.processInput(button);
}
showText(text: string, delay?: integer | null, callback?: Function | null, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null): void {
if (prompt && text.indexOf("$") > -1) {
const messagePages = text.split(/\$/g).map(m => m.trim());
let showMessageAndCallback = () => callback && callback();
for (let p = messagePages.length - 1; p >= 0; p--) {
const originalFunc = showMessageAndCallback;
showMessageAndCallback = () => this.showText(messagePages[p], null, originalFunc, null, true);
}
showMessageAndCallback();
} else {
const handler = this.getHandler();
if (handler instanceof MessageUiHandler) {
(handler as MessageUiHandler).showText(text, delay, callback, callbackDelay, prompt, promptDelay);
} else {
this.getMessageHandler().showText(text, delay, callback, callbackDelay, prompt, promptDelay);
}
}
}
showDialogue(keyOrText: string, name: string | undefined, delay: integer | null = 0, callback: Function, callbackDelay?: integer, promptDelay?: integer): void {
const battleScene = this.scene as BattleScene;
// Get localized dialogue (if available)
let hasi18n = false;
let text = keyOrText;
const genderIndex = battleScene.gameData.gender ?? PlayerGender.UNSET;
const genderStr = PlayerGender[genderIndex].toLowerCase();
if (i18next.exists(keyOrText) ) {
const i18nKey = keyOrText;
hasi18n = true;
text = i18next.t(i18nKey, { context: genderStr }); // override text with translation
// Skip dialogue if the player has enabled the option and the dialogue has been already seen
if (this.shouldSkipDialogue(i18nKey)) {
console.log(`Dialogue ${i18nKey} skipped`);
callback();
return;
}
}
let showMessageAndCallback = () => {
hasi18n && battleScene.gameData.saveSeenDialogue(keyOrText);
callback();
};
if (text.indexOf("$") > -1) {
const messagePages = text.split(/\$/g).map(m => m.trim());
for (let p = messagePages.length - 1; p >= 0; p--) {
const originalFunc = showMessageAndCallback;
showMessageAndCallback = () => this.showDialogue(messagePages[p], name, null, originalFunc);
}
showMessageAndCallback();
} else {
const handler = this.getHandler();
if (handler instanceof MessageUiHandler) {
(handler as MessageUiHandler).showDialogue(text, name, delay, showMessageAndCallback, callbackDelay, true, promptDelay);
} else {
this.getMessageHandler().showDialogue(text, name, delay, showMessageAndCallback, callbackDelay, true, promptDelay);
}
}
}
shouldSkipDialogue(i18nKey: string): boolean {
const battleScene = this.scene as BattleScene;
if (i18next.exists(i18nKey) ) {
if (battleScene.skipSeenDialogues && battleScene.gameData.getSeenDialogues()[i18nKey] === true) {
return true;
}
}
return false;
}
showTooltip(title: string, content: string, overlap?: boolean): void {
this.tooltipContainer.setVisible(true);
this.tooltipTitle.setText(title || "");
const wrappedContent = this.tooltipContent.runWordWrap(content);
this.tooltipContent.setText(wrappedContent);
this.tooltipContent.y = title ? 16 : 4;
this.tooltipBg.width = Math.min(Math.max(this.tooltipTitle.displayWidth, this.tooltipContent.displayWidth) + 12, 684);
this.tooltipBg.height = (title ? 31 : 19) + 10.5 * (wrappedContent.split("\n").length - 1);
if (overlap) {
(this.scene as BattleScene).uiContainer.moveAbove(this.tooltipContainer, this);
} else {
(this.scene as BattleScene).uiContainer.moveBelow(this.tooltipContainer, this);
}
}
hideTooltip(): void {
this.tooltipContainer.setVisible(false);
this.tooltipTitle.clearTint();
}
update(): void {
if (this.tooltipContainer.visible) {
const reverse = this.scene.game.input.mousePointer && this.scene.game.input.mousePointer.x >= this.scene.game.canvas.width - this.tooltipBg.width * 6 - 12;
this.tooltipContainer.setPosition(!reverse ? this.scene.game.input.mousePointer!.x / 6 + 2 : this.scene.game.input.mousePointer!.x / 6 - this.tooltipBg.width - 2, this.scene.game.input.mousePointer!.y / 6 + 2); // TODO: are these bangs correct?
}
}
clearText(): void {
const handler = this.getHandler();
if (handler instanceof MessageUiHandler) {
(handler as MessageUiHandler).clearText();
} else {
this.getMessageHandler().clearText();
}
}
setCursor(cursor: integer): boolean {
const changed = this.getHandler().setCursor(cursor);
if (changed) {
this.playSelect();
}
return changed;
}
playSelect(): void {
(this.scene as BattleScene).playSound("ui/select");
}
playError(): void {
(this.scene as BattleScene).playSound("ui/error");
}
fadeOut(duration: integer): Promise<void> {
return new Promise(resolve => {
if (this.overlayActive) {
return resolve();
}
this.overlayActive = true;
this.overlay.setAlpha(0);
this.overlay.setVisible(true);
this.scene.tweens.add({
targets: this.overlay,
alpha: 1,
duration: duration,
ease: "Sine.easeOut",
onComplete: () => resolve()
});
});
}
fadeIn(duration: integer): Promise<void> {
return new Promise(resolve => {
if (!this.overlayActive) {
return resolve();
}
this.scene.tweens.add({
targets: this.overlay,
alpha: 0,
duration: duration,
ease: "Sine.easeIn",
onComplete: () => {
this.overlay.setVisible(false);
resolve();
}
});
this.overlayActive = false;
});
}
private setModeInternal(mode: Mode, clear: boolean, forceTransition: boolean, chainMode: boolean, args: any[]): Promise<void> {
return new Promise(resolve => {
if (this.mode === mode && !forceTransition) {
resolve();
return;
}
const doSetMode = () => {
if (this.mode !== mode) {
if (clear) {
this.getHandler().clear();
}
if (chainMode && this.mode && !clear) {
this.modeChain.push(this.mode);
(this.scene as BattleScene).updateGameInfo();
}
this.mode = mode;
const touchControls = document?.getElementById("touchControls");
if (touchControls) {
touchControls.dataset.uiMode = Mode[mode];
}
this.getHandler().show(args);
}
resolve();
};
if (((!chainMode && ((transitionModes.indexOf(this.mode) > -1 || transitionModes.indexOf(mode) > -1)
&& (noTransitionModes.indexOf(this.mode) === -1 && noTransitionModes.indexOf(mode) === -1)))
|| (chainMode && noTransitionModes.indexOf(mode) === -1))) {
this.fadeOut(250).then(() => {
this.scene.time.delayedCall(100, () => {
doSetMode();
this.fadeIn(250);
});
});
} else {
doSetMode();
}
});
}
getMode(): Mode {
return this.mode;
}
setMode(mode: Mode, ...args: any[]): Promise<void> {
return this.setModeInternal(mode, true, false, false, args);
}
setModeForceTransition(mode: Mode, ...args: any[]): Promise<void> {
return this.setModeInternal(mode, true, true, false, args);
}
setModeWithoutClear(mode: Mode, ...args: any[]): Promise<void> {
return this.setModeInternal(mode, false, false, false, args);
}
setOverlayMode(mode: Mode, ...args: any[]): Promise<void> {
return this.setModeInternal(mode, false, false, true, args);
}
resetModeChain(): void {
this.modeChain = [];
(this.scene as BattleScene).updateGameInfo();
}
revertMode(): Promise<boolean> {
return new Promise<boolean>(resolve => {
if (!this?.modeChain?.length) {
return resolve(false);
}
const lastMode = this.mode;
const doRevertMode = () => {
this.getHandler().clear();
this.mode = this.modeChain.pop()!; // TODO: is this bang correct?
(this.scene as BattleScene).updateGameInfo();
const touchControls = document.getElementById("touchControls");
if (touchControls) {
touchControls.dataset.uiMode = Mode[this.mode];
}
resolve(true);
};
if (noTransitionModes.indexOf(lastMode) === -1) {
this.fadeOut(250).then(() => {
this.scene.time.delayedCall(100, () => {
doRevertMode();
this.fadeIn(250);
});
});
} else {
doRevertMode();
}
});
}
revertModes(): Promise<void> {
return new Promise<void>(resolve => {
if (!this?.modeChain?.length) {
return resolve();
}
this.revertMode().then(success => Utils.executeIf(success, this.revertModes).then(() => resolve()));
});
}
public getModeChain(): Mode[] {
return this.modeChain;
}
}