pokerogue/src/ui-inputs.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

221 lines
8.2 KiB
TypeScript

import type Phaser from "phaser";
import { Mode } from "./ui/ui";
import type { InputsController } from "./inputs-controller";
import type MessageUiHandler from "./ui/message-ui-handler";
import StarterSelectUiHandler from "./ui/starter-select-ui-handler";
import { Setting, SettingKeys, settingIndex } from "./system/settings/settings";
import SettingsUiHandler from "./ui/settings/settings-ui-handler";
import { Button } from "#enums/buttons";
import SettingsGamepadUiHandler from "./ui/settings/settings-gamepad-ui-handler";
import SettingsKeyboardUiHandler from "#app/ui/settings/settings-keyboard-ui-handler";
import { globalScene } from "#app/global-scene";
import SettingsDisplayUiHandler from "./ui/settings/settings-display-ui-handler";
import SettingsAudioUiHandler from "./ui/settings/settings-audio-ui-handler";
import RunInfoUiHandler from "./ui/run-info-ui-handler";
import PokedexUiHandler from "./ui/pokedex-ui-handler";
import PokedexPageUiHandler from "./ui/pokedex-page-ui-handler";
type ActionKeys = Record<Button, () => void>;
export class UiInputs {
private events: Phaser.Events.EventEmitter;
private inputsController: InputsController;
constructor(inputsController: InputsController) {
this.inputsController = inputsController;
this.init();
}
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 (evt.hasOwnProperty("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 (!actions.hasOwnProperty(event.button)) {
return;
}
actions[event.button]();
}, this);
this.events.on("input_up", (event) => {
const actions = this.getActionsKeyUp();
if (!actions.hasOwnProperty(event.button)) {
return;
}
actions[event.button]();
}, this);
}
doVibration(inputSuccess: boolean, vibrationLength: number): void {
if (inputSuccess && globalScene.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.V]: () => this.buttonCycleOption(Button.V),
[Button.SPEED_UP]: () => this.buttonSpeedChange(),
[Button.SLOW_DOWN]: () => this.buttonSpeedChange(false),
};
return actions;
}
getActionsKeyUp(): ActionKeys {
const actions: ActionKeys = {
[Button.UP]: () => undefined,
[Button.DOWN]: () => undefined,
[Button.LEFT]: () => undefined,
[Button.RIGHT]: () => undefined,
[Button.SUBMIT]: () => undefined,
[Button.ACTION]: () => undefined,
[Button.CANCEL]: () => undefined,
[Button.MENU]: () => undefined,
[Button.STATS]: () => this.buttonStats(false),
[Button.CYCLE_SHINY]: () => undefined,
[Button.CYCLE_FORM]: () => undefined,
[Button.CYCLE_GENDER]: () => undefined,
[Button.CYCLE_ABILITY]: () => undefined,
[Button.CYCLE_NATURE]: () => undefined,
[Button.V]: () => this.buttonInfo(false),
[Button.SPEED_UP]: () => undefined,
[Button.SLOW_DOWN]: () => undefined,
};
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: boolean = 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 p of globalScene.getField().filter(p => p?.isActive(true))) {
p.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: boolean = true): void {
if (globalScene.showMovesetFlyout ) {
for (const p of globalScene.getField().filter(p => p?.isActive(true))) {
p.toggleFlyout(pressed);
}
}
if (globalScene.showArenaFlyout) {
globalScene.ui.processInfoButton(pressed);
}
}
buttonMenu(): void {
if (globalScene.disableMenu) {
return;
}
switch (globalScene.ui?.getMode()) {
case Mode.MESSAGE:
const messageHandler = globalScene.ui.getHandler<MessageUiHandler>();
if (!messageHandler.pendingPrompt || messageHandler.isTextAnimationInProgress()) {
return;
}
case Mode.TITLE:
case Mode.COMMAND:
case Mode.MODIFIER_SELECT:
case Mode.MYSTERY_ENCOUNTER:
globalScene.ui.setOverlayMode(Mode.MENU);
break;
case Mode.STARTER_SELECT:
case Mode.POKEDEX_PAGE:
this.buttonTouch();
break;
case Mode.MENU:
globalScene.ui.revertMode();
globalScene.playSound("ui/select");
break;
default:
return;
}
}
buttonCycleOption(button: Button): void {
const whitelist = [ StarterSelectUiHandler, PokedexUiHandler, PokedexPageUiHandler, SettingsUiHandler, 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.V) {
this.buttonInfo(true);
}
}
buttonSpeedChange(up = true): void {
const settingGameSpeed = settingIndex(SettingKeys.Game_Speed);
if (up && globalScene.gameSpeed < 5) {
globalScene.gameData.saveSetting(SettingKeys.Game_Speed, Setting[settingGameSpeed].options.findIndex((item) => item.label === `${globalScene.gameSpeed}x`) + 1);
if (globalScene.ui?.getMode() === Mode.SETTINGS) {
(globalScene.ui.getHandler() as SettingsUiHandler).show([]);
}
} else if (!up && globalScene.gameSpeed > 1) {
globalScene.gameData.saveSetting(SettingKeys.Game_Speed, Math.max(Setting[settingGameSpeed].options.findIndex((item) => item.label === `${globalScene.gameSpeed}x`) - 1, 0));
if (globalScene.ui?.getMode() === Mode.SETTINGS) {
(globalScene.ui.getHandler() as SettingsUiHandler).show([]);
}
}
}
}