import { audioManager } from "#app/global-audio-manager"; import { globalScene } from "#app/global-scene"; import { settings } from "#app/global-settings-manager"; import type { InputsController } from "#app/inputs-controller"; import { isDev } from "#constants/app-constants"; import { SETTINGS_UI_MODES } from "#constants/ui-constants"; import { Button } from "#enums/buttons"; import { GameSpeed } from "#enums/game-speed"; import { UiMode } from "#enums/ui-mode"; import { SettingsAudioUiHandler } from "#ui/audio-settings-ui-handler"; import { GameChallengesUiHandler } from "#ui/challenges-select-ui-handler"; import { SettingsDisplayUiHandler } from "#ui/display-settings-ui-handler"; import { SettingsGamepadUiHandler } from "#ui/gamepad-settings-ui-handler"; import { GeneralSettingsUiHandler } from "#ui/general-settings-ui-handler"; import { SettingsKeyboardUiHandler } from "#ui/keyboard-settings-ui-handler"; import type { MessageUiHandler } from "#ui/message-ui-handler"; import { PokedexPageUiHandler } from "#ui/pokedex-page-ui-handler"; import { PokedexUiHandler } from "#ui/pokedex-ui-handler"; import { RunInfoUiHandler } from "#ui/run-info-ui-handler"; import { StarterSelectUiHandler } from "#ui/starter-select-ui-handler"; import type Phaser from "phaser"; type ActionKeys = Record void>; export class UiInputs { private events: Phaser.Events.EventEmitter; private readonly inputsController: InputsController; constructor(inputsController: InputsController) { this.inputsController = inputsController; this.init(); } private init(): void { this.events = this.inputsController.events; this.listenInputs(); } detectInputMethod(evt): void { if (evt.controller_type === "keyboard") { //if the touch property is present and defined, then this is a simulated keyboard event from the touch screen if (Object.hasOwn(evt, "isTouch") && evt.isTouch) { globalScene.inputMethod = "touch"; } else { globalScene.inputMethod = "keyboard"; } } else if (evt.controller_type === "gamepad") { globalScene.inputMethod = "gamepad"; } } listenInputs(): void { this.events.on( "input_down", event => { this.detectInputMethod(event); const actions = this.getActionsKeyDown(); if (!Object.hasOwn(actions, event.button)) { return; } actions[event.button](); }, this, ); this.events.on( "input_up", event => { const actions = this.getActionsKeyUp(); if (!Object.hasOwn(actions, event.button)) { return; } actions[event.button](); }, this, ); } doVibration(inputSuccess: boolean, vibrationLength: number): void { if (inputSuccess && settings.general.enableVibration && typeof navigator.vibrate !== "undefined") { navigator.vibrate(vibrationLength); } } getActionsKeyDown(): ActionKeys { const actions: ActionKeys = { [Button.UP]: () => this.buttonDirection(Button.UP), [Button.DOWN]: () => this.buttonDirection(Button.DOWN), [Button.LEFT]: () => this.buttonDirection(Button.LEFT), [Button.RIGHT]: () => this.buttonDirection(Button.RIGHT), [Button.SUBMIT]: () => this.buttonTouch(), [Button.ACTION]: () => this.buttonAb(Button.ACTION), [Button.CANCEL]: () => this.buttonAb(Button.CANCEL), [Button.MENU]: () => this.buttonMenu(), [Button.STATS]: () => this.buttonGoToFilter(Button.STATS), [Button.CYCLE_SHINY]: () => this.buttonCycleOption(Button.CYCLE_SHINY), [Button.CYCLE_FORM]: () => this.buttonCycleOption(Button.CYCLE_FORM), [Button.CYCLE_GENDER]: () => this.buttonCycleOption(Button.CYCLE_GENDER), [Button.CYCLE_ABILITY]: () => this.buttonCycleOption(Button.CYCLE_ABILITY), [Button.CYCLE_NATURE]: () => this.buttonCycleOption(Button.CYCLE_NATURE), [Button.CYCLE_TERA]: () => this.buttonCycleOption(Button.CYCLE_TERA), [Button.SPEED_UP]: () => this.buttonSpeedChange(), [Button.SLOW_DOWN]: () => this.buttonSpeedChange(false), [Button.DEV_CUSTOM]: () => { if (isDev) { import("./dev-function").then(m => m.customDevFunction()); } }, }; return actions; } getActionsKeyUp(): ActionKeys { const actions: ActionKeys = { [Button.UP]: () => {}, [Button.DOWN]: () => {}, [Button.LEFT]: () => {}, [Button.RIGHT]: () => {}, [Button.SUBMIT]: () => {}, [Button.ACTION]: () => {}, [Button.CANCEL]: () => {}, [Button.MENU]: () => {}, [Button.STATS]: () => this.buttonStats(false), [Button.CYCLE_SHINY]: () => {}, [Button.CYCLE_FORM]: () => {}, [Button.CYCLE_GENDER]: () => {}, [Button.CYCLE_ABILITY]: () => {}, [Button.CYCLE_NATURE]: () => {}, [Button.CYCLE_TERA]: () => this.buttonInfo(false), [Button.SPEED_UP]: () => {}, [Button.SLOW_DOWN]: () => {}, [Button.DEV_CUSTOM]: () => {}, }; return actions; } buttonDirection(direction: Button): void { const inputSuccess = globalScene.ui.processInput(direction); const vibrationLength = 5; this.doVibration(inputSuccess, vibrationLength); } buttonAb(button: Button): void { globalScene.ui.processInput(button); } buttonTouch(): void { globalScene.ui.processInput(Button.SUBMIT) || globalScene.ui.processInput(Button.ACTION); } buttonStats(pressed = true): void { // allow access to Button.STATS as a toggle for other elements for (const t of globalScene.getInfoToggles(true)) { t.toggleInfo(pressed); } // handle normal pokemon battle ui for (const pkmn of globalScene.getField().filter(p => p?.isActive(true))) { pkmn.toggleStats(pressed); } } buttonGoToFilter(button: Button): void { const whitelist = [StarterSelectUiHandler, PokedexUiHandler, PokedexPageUiHandler]; const uiHandler = globalScene.ui?.getHandler(); if (whitelist.some(handler => uiHandler instanceof handler)) { globalScene.ui.processInput(button); } else { this.buttonStats(true); } } buttonInfo(pressed = true): void { if (settings.display.showMovesetFlyout) { for (const pkmn of globalScene.getEnemyField().filter(p => p?.isActive(true))) { pkmn.toggleFlyout(pressed); } } if (settings.display.showArenaFlyout) { globalScene.ui.processInfoButton(pressed); } } buttonMenu(): void { if (globalScene.disableMenu) { return; } switch (globalScene.ui?.mode) { // biome-ignore lint/suspicious/noFallthroughSwitchClause: falls through to show menu overlay case UiMode.MESSAGE: { const messageHandler = globalScene.ui.getHandler(); if (!messageHandler.pendingPrompt || messageHandler.isTextAnimationInProgress()) { return; } } case UiMode.TITLE: case UiMode.COMMAND: case UiMode.MODIFIER_SELECT: case UiMode.MYSTERY_ENCOUNTER: globalScene.ui.setOverlayMode(UiMode.MENU); break; case UiMode.STARTER_SELECT: case UiMode.POKEDEX_PAGE: this.buttonTouch(); break; case UiMode.MENU: globalScene.ui.revertMode(); audioManager.playSound("ui/select"); break; default: return; } } buttonCycleOption(button: Button): void { const whitelist = [ StarterSelectUiHandler, PokedexUiHandler, PokedexPageUiHandler, GameChallengesUiHandler, GeneralSettingsUiHandler, RunInfoUiHandler, SettingsDisplayUiHandler, SettingsAudioUiHandler, SettingsGamepadUiHandler, SettingsKeyboardUiHandler, ]; const uiHandler = globalScene.ui?.getHandler(); if (whitelist.some(handler => uiHandler instanceof handler)) { globalScene.ui.processInput(button); } else if (button === Button.CYCLE_TERA) { this.buttonInfo(true); } } private buttonSpeedChange(up = true): void { const { ui } = globalScene; if (SETTINGS_UI_MODES.includes(ui?.mode)) { return; } const gameSpeeds = Object.values(GameSpeed); const gameSpeedIndex = gameSpeeds.indexOf(settings.general.gameSpeed); const lastIndex = gameSpeeds.length - 1; const newIndex = up ? Math.min(gameSpeedIndex + 1, lastIndex) : Math.max(gameSpeedIndex - 1, 0); settings.update("general", "gameSpeed", gameSpeeds[newIndex]); } }