mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-04-22 17:44:19 +01:00
[QoL] Test dialogue menu option (#3725)
* Adding code to allow use of a testing dialogue translation menu * Updated to include autocomplete functionality * Added multiple inputs * Added locales for other languages as well as checks to make this only available on local/beta * Updated a few things to try get the dialogue to work for full length of the window * Fixed issue with message box not taking up full length of the screen (thanks Moka!) and some minor bugs * Whoops, forgot to stage a file * Updated locale files * Fixed broken tests and docs * Removed keys from json * Reordered and reorganised some things * Put admin enum at end to match handlers * Removed old unneeded line of code * Updated to include the ability to handle cases where i18 keys are null in the locales json
This commit is contained in:
parent
f7169868f3
commit
d1132a5765
@ -77,7 +77,21 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected setupOptions() {
|
protected setupOptions() {
|
||||||
const options = this.config?.options || [];
|
const configOptions = this.config?.options ?? [];
|
||||||
|
|
||||||
|
let options: OptionSelectItem[];
|
||||||
|
|
||||||
|
// for performance reasons, this limits how many options we can see at once. Without this, it would try to make text options for every single options
|
||||||
|
// which makes the performance take a hit. If there's not enough options to do this (set to 10 at the moment) and the ui mode !== Mode.AUTO_COMPLETE,
|
||||||
|
// this is ignored and the original code is untouched, with the options array being all the options from the config
|
||||||
|
if (configOptions.length >= 10 && this.scene.ui.getMode() === Mode.AUTO_COMPLETE) {
|
||||||
|
const optionsScrollTotal = configOptions.length;
|
||||||
|
const optionStartIndex = this.scrollCursor;
|
||||||
|
const optionEndIndex = Math.min(optionsScrollTotal, optionStartIndex + (!optionStartIndex || this.scrollCursor + (this.config?.maxOptions! - 1) >= optionsScrollTotal ? this.config?.maxOptions! - 1 : this.config?.maxOptions! - 2));
|
||||||
|
options = configOptions.slice(optionStartIndex, optionEndIndex + 2);
|
||||||
|
} else {
|
||||||
|
options = configOptions;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.optionSelectText) {
|
if (this.optionSelectText) {
|
||||||
this.optionSelectText.destroy();
|
this.optionSelectText.destroy();
|
||||||
@ -192,6 +206,19 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
|
|||||||
} else {
|
} else {
|
||||||
ui.playError();
|
ui.playError();
|
||||||
}
|
}
|
||||||
|
} else if (button === Button.SUBMIT && ui.getMode() === Mode.AUTO_COMPLETE) {
|
||||||
|
// this is here to differentiate between a Button.SUBMIT vs Button.ACTION within the autocomplete handler
|
||||||
|
// this is here because Button.ACTION is picked up as z on the keyboard, meaning if you're typing and hit z, it'll select the option you've chosen
|
||||||
|
success = true;
|
||||||
|
const option = this.config?.options[this.cursor + (this.scrollCursor - (this.scrollCursor ? 1 : 0))];
|
||||||
|
if (option?.handler()) {
|
||||||
|
if (!option.keepOpen) {
|
||||||
|
this.clear();
|
||||||
|
}
|
||||||
|
playSound = !option.overrideSound;
|
||||||
|
} else {
|
||||||
|
ui.playError();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
switch (button) {
|
switch (button) {
|
||||||
case Button.UP:
|
case Button.UP:
|
||||||
|
45
src/ui/autocomplete-ui-handler.ts
Normal file
45
src/ui/autocomplete-ui-handler.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { Button } from "#enums/buttons";
|
||||||
|
import BattleScene from "../battle-scene";
|
||||||
|
import AbstractOptionSelectUiHandler from "./abstact-option-select-ui-handler";
|
||||||
|
import { Mode } from "./ui";
|
||||||
|
|
||||||
|
export default class AutoCompleteUiHandler extends AbstractOptionSelectUiHandler {
|
||||||
|
modalContainer: Phaser.GameObjects.Container;
|
||||||
|
constructor(scene: BattleScene, mode: Mode = Mode.OPTION_SELECT) {
|
||||||
|
super(scene, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
getWindowWidth(): integer {
|
||||||
|
return 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
show(args: any[]): boolean {
|
||||||
|
if (args[0].modalContainer) {
|
||||||
|
const { modalContainer } = args[0];
|
||||||
|
const show = super.show(args);
|
||||||
|
this.modalContainer = modalContainer;
|
||||||
|
this.setupOptions();
|
||||||
|
|
||||||
|
return show;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected setupOptions() {
|
||||||
|
super.setupOptions();
|
||||||
|
if (this.modalContainer) {
|
||||||
|
this.optionSelectContainer.setSize(this.optionSelectContainer.height, Math.max(this.optionSelectText.displayWidth + 24, this.getWindowWidth()));
|
||||||
|
this.optionSelectContainer.setPositionRelative(this.modalContainer, this.optionSelectBg.width, this.optionSelectBg.height + 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processInput(button: Button): boolean {
|
||||||
|
// the cancel and action button are here because if you're typing, x and z are used for cancel/action. This means you could be typing something and accidentally cancel/select when you don't mean to
|
||||||
|
// the submit button is therefore used to select a choice (the enter button), though this does not work on my local dev testing for phones, as for my phone/keyboard combo, the enter and z key are both
|
||||||
|
// bound to Button.ACTION, which makes this not work on mobile
|
||||||
|
if (button !== Button.CANCEL && button !== Button.ACTION) {
|
||||||
|
return super.processInput(button);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,8 @@ export default class BattleMessageUiHandler extends MessageUiHandler {
|
|||||||
public movesWindowContainer: Phaser.GameObjects.Container;
|
public movesWindowContainer: Phaser.GameObjects.Container;
|
||||||
public nameBoxContainer: Phaser.GameObjects.Container;
|
public nameBoxContainer: Phaser.GameObjects.Container;
|
||||||
|
|
||||||
|
public readonly wordWrapWidth: number = 1780;
|
||||||
|
|
||||||
constructor(scene: BattleScene) {
|
constructor(scene: BattleScene) {
|
||||||
super(scene, Mode.MESSAGE);
|
super(scene, Mode.MESSAGE);
|
||||||
}
|
}
|
||||||
@ -63,7 +65,7 @@ export default class BattleMessageUiHandler extends MessageUiHandler {
|
|||||||
const message = addTextObject(this.scene, 0, 0, "", TextStyle.MESSAGE, {
|
const message = addTextObject(this.scene, 0, 0, "", TextStyle.MESSAGE, {
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
wordWrap: {
|
wordWrap: {
|
||||||
width: 1780
|
width: this.wordWrapWidth
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
messageContainer.add(message);
|
messageContainer.add(message);
|
||||||
@ -129,7 +131,7 @@ export default class BattleMessageUiHandler extends MessageUiHandler {
|
|||||||
|
|
||||||
this.commandWindow.setVisible(false);
|
this.commandWindow.setVisible(false);
|
||||||
this.movesWindowContainer.setVisible(false);
|
this.movesWindowContainer.setVisible(false);
|
||||||
this.message.setWordWrapWidth(1780);
|
this.message.setWordWrapWidth(this.wordWrapWidth);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -161,7 +163,9 @@ export default class BattleMessageUiHandler extends MessageUiHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showDialogue(text: string, name?: string, delay?: integer | null, callback?: Function, callbackDelay?: integer, prompt?: boolean, promptDelay?: integer) {
|
showDialogue(text: string, name?: string, delay?: integer | null, callback?: Function, callbackDelay?: integer, prompt?: boolean, promptDelay?: integer) {
|
||||||
name && this.showNameText(name);
|
if (name) {
|
||||||
|
this.showNameText(name);
|
||||||
|
}
|
||||||
super.showDialogue(text, name, delay, callback, callbackDelay, prompt, promptDelay);
|
super.showDialogue(text, name, delay, callback, callbackDelay, prompt, promptDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import BattleScene, { bypassLogin } from "../battle-scene";
|
|||||||
import { TextStyle, addTextObject, getTextStyleOptions } from "./text";
|
import { TextStyle, addTextObject, getTextStyleOptions } from "./text";
|
||||||
import { Mode } from "./ui";
|
import { Mode } from "./ui";
|
||||||
import * as Utils from "../utils";
|
import * as Utils from "../utils";
|
||||||
import { addWindow } from "./ui-theme";
|
import { addWindow, WindowVariant } from "./ui-theme";
|
||||||
import MessageUiHandler from "./message-ui-handler";
|
import MessageUiHandler from "./message-ui-handler";
|
||||||
import { OptionSelectConfig, OptionSelectItem } from "./abstact-option-select-ui-handler";
|
import { OptionSelectConfig, OptionSelectItem } from "./abstact-option-select-ui-handler";
|
||||||
import { Tutorial, handleTutorial } from "../tutorial";
|
import { Tutorial, handleTutorial } from "../tutorial";
|
||||||
@ -11,6 +11,7 @@ import i18next from "i18next";
|
|||||||
import { Button } from "#enums/buttons";
|
import { Button } from "#enums/buttons";
|
||||||
import { GameDataType } from "#enums/game-data-type";
|
import { GameDataType } from "#enums/game-data-type";
|
||||||
import BgmBar from "#app/ui/bgm-bar";
|
import BgmBar from "#app/ui/bgm-bar";
|
||||||
|
import AwaitableUiHandler from "./awaitable-ui-handler";
|
||||||
|
|
||||||
enum MenuOptions {
|
enum MenuOptions {
|
||||||
GAME_SETTINGS,
|
GAME_SETTINGS,
|
||||||
@ -31,6 +32,10 @@ const githubUrl = "https://github.com/pagefaultgames/pokerogue";
|
|||||||
const redditUrl = "https://www.reddit.com/r/pokerogue";
|
const redditUrl = "https://www.reddit.com/r/pokerogue";
|
||||||
|
|
||||||
export default class MenuUiHandler extends MessageUiHandler {
|
export default class MenuUiHandler extends MessageUiHandler {
|
||||||
|
private readonly textPadding = 8;
|
||||||
|
private readonly defaultMessageBoxWidth = 220;
|
||||||
|
private readonly defaultWordWrapWidth = 1224;
|
||||||
|
|
||||||
private menuContainer: Phaser.GameObjects.Container;
|
private menuContainer: Phaser.GameObjects.Container;
|
||||||
private menuMessageBoxContainer: Phaser.GameObjects.Container;
|
private menuMessageBoxContainer: Phaser.GameObjects.Container;
|
||||||
private menuOverlay: Phaser.GameObjects.Rectangle;
|
private menuOverlay: Phaser.GameObjects.Rectangle;
|
||||||
@ -46,11 +51,14 @@ export default class MenuUiHandler extends MessageUiHandler {
|
|||||||
protected manageDataConfig: OptionSelectConfig;
|
protected manageDataConfig: OptionSelectConfig;
|
||||||
protected communityConfig: 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;
|
protected scale: number = 0.1666666667;
|
||||||
|
|
||||||
public bgmBar: BgmBar;
|
public bgmBar: BgmBar;
|
||||||
|
|
||||||
|
|
||||||
constructor(scene: BattleScene, mode: Mode | null = null) {
|
constructor(scene: BattleScene, mode: Mode | null = null) {
|
||||||
super(scene, mode);
|
super(scene, mode);
|
||||||
|
|
||||||
@ -131,20 +139,27 @@ export default class MenuUiHandler extends MessageUiHandler {
|
|||||||
this.menuMessageBoxContainer = this.scene.add.container(0, 130);
|
this.menuMessageBoxContainer = this.scene.add.container(0, 130);
|
||||||
this.menuMessageBoxContainer.setName("menu-message-box");
|
this.menuMessageBoxContainer.setName("menu-message-box");
|
||||||
this.menuMessageBoxContainer.setVisible(false);
|
this.menuMessageBoxContainer.setVisible(false);
|
||||||
this.menuContainer.add(this.menuMessageBoxContainer);
|
|
||||||
|
|
||||||
const menuMessageBox = addWindow(this.scene, 0, -0, 220, 48);
|
// Window for general messages
|
||||||
menuMessageBox.setOrigin(0, 0);
|
this.menuMessageBox = addWindow(this.scene, 0, 0, this.defaultMessageBoxWidth, 48);
|
||||||
this.menuMessageBoxContainer.add(menuMessageBox);
|
this.menuMessageBox.setOrigin(0, 0);
|
||||||
|
this.menuMessageBoxContainer.add(this.menuMessageBox);
|
||||||
|
|
||||||
const menuMessageText = addTextObject(this.scene, 8, 8, "", TextStyle.WINDOW, { maxLines: 2 });
|
// Full-width window used for testing dialog messages in debug mode
|
||||||
|
this.dialogueMessageBox = addWindow(this.scene, -this.textPadding, 0, this.scene.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.scene, this.textPadding, this.textPadding, "", TextStyle.WINDOW, { maxLines: 2 });
|
||||||
menuMessageText.setName("menu-message");
|
menuMessageText.setName("menu-message");
|
||||||
menuMessageText.setWordWrapWidth(1224);
|
|
||||||
menuMessageText.setOrigin(0, 0);
|
menuMessageText.setOrigin(0, 0);
|
||||||
this.menuMessageBoxContainer.add(menuMessageText);
|
this.menuMessageBoxContainer.add(menuMessageText);
|
||||||
|
|
||||||
this.message = menuMessageText;
|
this.message = menuMessageText;
|
||||||
|
|
||||||
|
// By default we use the general purpose message window
|
||||||
|
this.setDialogTestMode(false);
|
||||||
|
|
||||||
this.menuContainer.add(this.menuMessageBoxContainer);
|
this.menuContainer.add(this.menuMessageBoxContainer);
|
||||||
|
|
||||||
const manageDataOptions: any[] = []; // TODO: proper type
|
const manageDataOptions: any[] = []; // TODO: proper type
|
||||||
@ -257,8 +272,55 @@ export default class MenuUiHandler extends MessageUiHandler {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
keepOpen: 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, () => this.scene.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"),
|
label: i18next.t("menuUiHandler:cancel"),
|
||||||
handler: () => {
|
handler: () => {
|
||||||
this.scene.ui.revertMode();
|
this.scene.ui.revertMode();
|
||||||
@ -547,6 +609,21 @@ export default class MenuUiHandler extends MessageUiHandler {
|
|||||||
return success || error;
|
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 ? this.scene.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 {
|
showText(text: string, delay?: number, callback?: Function, callbackDelay?: number, prompt?: boolean, promptDelay?: number): void {
|
||||||
this.menuMessageBoxContainer.setVisible(!!text);
|
this.menuMessageBoxContainer.setVisible(!!text);
|
||||||
|
|
||||||
|
147
src/ui/test-dialogue-ui-handler.ts
Normal file
147
src/ui/test-dialogue-ui-handler.ts
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import { FormModalUiHandler } from "./form-modal-ui-handler";
|
||||||
|
import { ModalConfig } from "./modal-ui-handler";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import { PlayerPokemon } from "#app/field/pokemon";
|
||||||
|
import { OptionSelectItem } from "./abstact-option-select-ui-handler";
|
||||||
|
import { isNullOrUndefined } from "#app/utils";
|
||||||
|
import { Mode } from "./ui";
|
||||||
|
|
||||||
|
export default class TestDialogueUiHandler extends FormModalUiHandler {
|
||||||
|
|
||||||
|
keys: string[];
|
||||||
|
|
||||||
|
constructor(scene, mode) {
|
||||||
|
super(scene, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
super.setup();
|
||||||
|
|
||||||
|
const flattenKeys = (object, topKey?: string, midleKey?: string[]): Array<any> => {
|
||||||
|
return Object.keys(object).map((t, i) => {
|
||||||
|
const value = Object.values(object)[i];
|
||||||
|
|
||||||
|
if (typeof value === "object" && !isNullOrUndefined(value)) { // we check for not null or undefined here because if the language json file has a null key, the typeof will still be an object, but that object will be null, causing issues
|
||||||
|
// If the value is an object, execute the same process
|
||||||
|
// si el valor es un objeto ejecuta el mismo proceso
|
||||||
|
|
||||||
|
return flattenKeys(value, topKey ?? t, topKey ? midleKey ? [...midleKey, t] : [t] : undefined).filter((t) => t.length > 0);
|
||||||
|
} else if (typeof value === "string" || isNullOrUndefined(value)) { // we check for null or undefined here as per above - the typeof is still an object but the value is null so we need to exit out of this and pass the null key
|
||||||
|
|
||||||
|
// Return in the format expected by i18next
|
||||||
|
return midleKey ? `${topKey}:${midleKey.map((m) => m).join(".")}.${t}` : `${topKey}:${t}`;
|
||||||
|
}
|
||||||
|
}).filter((t) => t);
|
||||||
|
};
|
||||||
|
|
||||||
|
const keysInArrays = flattenKeys(i18next.getDataByLanguage(String(i18next.resolvedLanguage))).filter((t) => t.length > 0); // Array of arrays
|
||||||
|
const keys = keysInArrays.flat(Infinity).map(String); // One array of string
|
||||||
|
this.keys = keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
getModalTitle(config?: ModalConfig): string {
|
||||||
|
return "Test Dialogue";
|
||||||
|
}
|
||||||
|
|
||||||
|
getFields(config?: ModalConfig): string[] {
|
||||||
|
return [ "Dialogue" ];
|
||||||
|
}
|
||||||
|
|
||||||
|
getWidth(config?: ModalConfig): number {
|
||||||
|
return 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
getMargin(config?: ModalConfig): [number, number, number, number] {
|
||||||
|
return [ 0, 0, 48, 0 ];
|
||||||
|
}
|
||||||
|
|
||||||
|
getButtonLabels(config?: ModalConfig): string[] {
|
||||||
|
return [ "Check", "Cancel" ];
|
||||||
|
}
|
||||||
|
|
||||||
|
getReadableErrorMessage(error: string): string {
|
||||||
|
const colonIndex = error?.indexOf(":");
|
||||||
|
if (colonIndex > 0) {
|
||||||
|
error = error.slice(0, colonIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getReadableErrorMessage(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
show(args: any[]): boolean {
|
||||||
|
const ui = this.getUi();
|
||||||
|
const input = this.inputs[0];
|
||||||
|
input.setMaxLength(255);
|
||||||
|
|
||||||
|
input.on("keydown", (inputObject, evt: KeyboardEvent) => {
|
||||||
|
if (["escape", "space"].some((v) => v === evt.key.toLowerCase() || v === evt.code.toLowerCase()) && ui.getMode() === Mode.AUTO_COMPLETE) {
|
||||||
|
// Delete autocomplete list and recovery focus.
|
||||||
|
inputObject.on("blur", () => inputObject.node.focus(), { once: true });
|
||||||
|
ui.revertMode();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
input.on("textchange", (inputObject, evt: InputEvent) => {
|
||||||
|
// Delete autocomplete.
|
||||||
|
if (ui.getMode() === Mode.AUTO_COMPLETE) {
|
||||||
|
ui.revertMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
let options: OptionSelectItem[] = [];
|
||||||
|
const splitArr = inputObject.text.split(" ");
|
||||||
|
const filteredKeys = this.keys.filter((command) => command.toLowerCase().includes(splitArr[splitArr.length - 1].toLowerCase()));
|
||||||
|
if (inputObject.text !== "" && filteredKeys.length > 0) {
|
||||||
|
// if performance is required, you could reduce the number of total results by changing the slice below to not have all ~8000 inputs going
|
||||||
|
options = filteredKeys.slice(0).map((value) => {
|
||||||
|
return {
|
||||||
|
label: value,
|
||||||
|
handler: () => {
|
||||||
|
// this is here to make sure that if you try to backspace then enter, the last known evt.data (backspace) is picked up
|
||||||
|
// this is because evt.data is null for backspace, so without this, the autocomplete windows just closes
|
||||||
|
if (!isNullOrUndefined(evt.data) || evt.inputType?.toLowerCase() === "deletecontentbackward") {
|
||||||
|
const separatedArray = inputObject.text.split(" ");
|
||||||
|
separatedArray[separatedArray.length - 1] = value;
|
||||||
|
inputObject.setText(separatedArray.join(" "));
|
||||||
|
}
|
||||||
|
ui.revertMode();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.length > 0) {
|
||||||
|
const modalOpts = {
|
||||||
|
options: options,
|
||||||
|
maxOptions: 5,
|
||||||
|
modalContainer: this.modalContainer
|
||||||
|
};
|
||||||
|
ui.setOverlayMode(Mode.AUTO_COMPLETE, modalOpts);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if (super.show(args)) {
|
||||||
|
const config = args[0] as ModalConfig;
|
||||||
|
this.inputs[0].resize(1150, 116);
|
||||||
|
this.inputContainers[0].list[0].width = 200;
|
||||||
|
if (args[1] && typeof (args[1] as PlayerPokemon).getNameToRender === "function") {
|
||||||
|
this.inputs[0].text = (args[1] as PlayerPokemon).getNameToRender();
|
||||||
|
} else {
|
||||||
|
this.inputs[0].text = args[1];
|
||||||
|
}
|
||||||
|
this.submitAction = (_) => {
|
||||||
|
if (ui.getMode() === Mode.TEST_DIALOGUE) {
|
||||||
|
this.sanitizeInputs();
|
||||||
|
const sanitizedName = btoa(unescape(encodeURIComponent(this.inputs[0].text)));
|
||||||
|
config.buttonActions[0](sanitizedName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -49,6 +49,8 @@ import RenameFormUiHandler from "./rename-form-ui-handler";
|
|||||||
import AdminUiHandler from "./admin-ui-handler";
|
import AdminUiHandler from "./admin-ui-handler";
|
||||||
import RunHistoryUiHandler from "./run-history-ui-handler";
|
import RunHistoryUiHandler from "./run-history-ui-handler";
|
||||||
import RunInfoUiHandler from "./run-info-ui-handler";
|
import RunInfoUiHandler from "./run-info-ui-handler";
|
||||||
|
import TestDialogueUiHandler from "#app/ui/test-dialogue-ui-handler";
|
||||||
|
import AutoCompleteUiHandler from "./autocomplete-ui-handler";
|
||||||
|
|
||||||
export enum Mode {
|
export enum Mode {
|
||||||
MESSAGE,
|
MESSAGE,
|
||||||
@ -89,6 +91,8 @@ export enum Mode {
|
|||||||
RENAME_POKEMON,
|
RENAME_POKEMON,
|
||||||
RUN_HISTORY,
|
RUN_HISTORY,
|
||||||
RUN_INFO,
|
RUN_INFO,
|
||||||
|
TEST_DIALOGUE,
|
||||||
|
AUTO_COMPLETE,
|
||||||
ADMIN,
|
ADMIN,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,6 +131,8 @@ const noTransitionModes = [
|
|||||||
Mode.UNAVAILABLE,
|
Mode.UNAVAILABLE,
|
||||||
Mode.OUTDATED,
|
Mode.OUTDATED,
|
||||||
Mode.RENAME_POKEMON,
|
Mode.RENAME_POKEMON,
|
||||||
|
Mode.TEST_DIALOGUE,
|
||||||
|
Mode.AUTO_COMPLETE,
|
||||||
Mode.ADMIN,
|
Mode.ADMIN,
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -191,6 +197,8 @@ export default class UI extends Phaser.GameObjects.Container {
|
|||||||
new RenameFormUiHandler(scene),
|
new RenameFormUiHandler(scene),
|
||||||
new RunHistoryUiHandler(scene),
|
new RunHistoryUiHandler(scene),
|
||||||
new RunInfoUiHandler(scene),
|
new RunInfoUiHandler(scene),
|
||||||
|
new TestDialogueUiHandler(scene, Mode.TEST_DIALOGUE),
|
||||||
|
new AutoCompleteUiHandler(scene),
|
||||||
new AdminUiHandler(scene),
|
new AdminUiHandler(scene),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user