From 9e5b5c2a4289da1efe205aa933ad584a26baa0b9 Mon Sep 17 00:00:00 2001 From: Greenlamp Date: Tue, 21 May 2024 14:28:17 +0200 Subject: [PATCH] merge squash feat/mapping_setting --- index.css | 4 +- public/images/inputs/dualshock.json | 348 ++++++++++ public/images/inputs/dualshock.png | Bin 0 -> 82008 bytes public/images/inputs/keyboard.json | 644 +++++++++++++++++ public/images/inputs/keyboard.png | Bin 0 -> 51888 bytes public/images/inputs/nswitch.json | 356 ++++++++++ public/images/inputs/nswitch.png | Bin 0 -> 31039 bytes public/images/inputs/snes.json | 108 +++ public/images/inputs/snes.png | Bin 0 -> 7993 bytes public/images/inputs/xbox.json | 348 ++++++++++ public/images/inputs/xbox.png | Bin 0 -> 67950 bytes src/battle-scene.ts | 4 - src/configs/cfg_keyboard_azerty.ts | 291 ++++++++ src/configs/configHandler.ts | 186 +++++ src/configs/pad_dualshock.ts | 65 +- src/configs/pad_generic.ts | 67 +- src/configs/pad_unlicensedSNES.ts | 57 +- src/configs/pad_xbox360.ts | 62 +- src/enums/devices.ts | 4 + src/inputs-controller.ts | 538 ++++++++------ src/loading-scene.ts | 8 + src/system/game-data.ts | 128 ++++ src/system/settings-gamepad.ts | 146 ++++ src/system/settings-keyboard.ts | 206 ++++++ src/system/settings.ts | 18 +- src/test/cfg_keyboard.example.ts | 330 +++++++++ src/test/helpers/inGameManip.ts | 73 ++ src/test/helpers/menuManip.ts | 131 ++++ src/test/rebinding_setting.test.ts | 417 +++++++++++ src/touch-controls.js | 112 +-- src/ui-inputs.ts | 8 +- src/ui/settings/abrast-binding-ui-handler.ts | 255 +++++++ .../settings/abstract-settings-ui-handler.ts | 654 ++++++++++++++++++ src/ui/settings/gamepad-binding-ui-handler.ts | 81 +++ .../settings/keyboard-binding-ui-handler.ts | 71 ++ .../option-select-ui-handler.ts | 6 +- .../settings/settings-gamepad-ui-handler.ts | 166 +++++ .../settings/settings-keyboard-ui-handler.ts | 215 ++++++ src/ui/{ => settings}/settings-ui-handler.ts | 129 +++- src/ui/text.ts | 3 + src/ui/title-ui-handler.ts | 2 +- src/ui/ui.ts | 20 +- src/utils.ts | 43 ++ 43 files changed, 5992 insertions(+), 312 deletions(-) create mode 100644 public/images/inputs/dualshock.json create mode 100644 public/images/inputs/dualshock.png create mode 100644 public/images/inputs/keyboard.json create mode 100644 public/images/inputs/keyboard.png create mode 100644 public/images/inputs/nswitch.json create mode 100644 public/images/inputs/nswitch.png create mode 100644 public/images/inputs/snes.json create mode 100644 public/images/inputs/snes.png create mode 100644 public/images/inputs/xbox.json create mode 100644 public/images/inputs/xbox.png create mode 100644 src/configs/cfg_keyboard_azerty.ts create mode 100644 src/configs/configHandler.ts create mode 100644 src/enums/devices.ts create mode 100644 src/system/settings-gamepad.ts create mode 100644 src/system/settings-keyboard.ts create mode 100644 src/test/cfg_keyboard.example.ts create mode 100644 src/test/helpers/inGameManip.ts create mode 100644 src/test/helpers/menuManip.ts create mode 100644 src/test/rebinding_setting.test.ts create mode 100644 src/ui/settings/abrast-binding-ui-handler.ts create mode 100644 src/ui/settings/abstract-settings-ui-handler.ts create mode 100644 src/ui/settings/gamepad-binding-ui-handler.ts create mode 100644 src/ui/settings/keyboard-binding-ui-handler.ts rename src/ui/{ => settings}/option-select-ui-handler.ts (59%) create mode 100644 src/ui/settings/settings-gamepad-ui-handler.ts create mode 100644 src/ui/settings/settings-keyboard-ui-handler.ts rename src/ui/{ => settings}/settings-ui-handler.ts (65%) diff --git a/index.css b/index.css index dd47387adee..31d552a0e01 100644 --- a/index.css +++ b/index.css @@ -146,7 +146,9 @@ body { margin-left: 10%; } -#touchControls:not([data-ui-mode='STARTER_SELECT']) #apad .apadRectBtnContainer > .apadSqBtn, #touchControls:not([data-ui-mode='STARTER_SELECT']) #apad .apadSqBtnContainer { +#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadRectBtnContainer > .apadSqBtn, +#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadSqBtnContainer +{ display: none; } diff --git a/public/images/inputs/dualshock.json b/public/images/inputs/dualshock.json new file mode 100644 index 00000000000..484ead03674 --- /dev/null +++ b/public/images/inputs/dualshock.json @@ -0,0 +1,348 @@ +{"frames": { + +"T_P4_Circle_Color_Default.png": +{ + "frame": {"x":0,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_Circle_Default.png": +{ + "frame": {"x":128,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_Cross_Color_Default.png": +{ + "frame": {"x":256,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_Cross_Default.png": +{ + "frame": {"x":384,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_Dpad_Default.png": +{ + "frame": {"x":512,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_Dpad_Down_Default.png": +{ + "frame": {"x":640,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_Dpad_Left_Default.png": +{ + "frame": {"x":768,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_Dpad_Right_Default.png": +{ + "frame": {"x":896,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_Dpad_UP_Default.png": +{ + "frame": {"x":1024,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_Dpad_X_Default.png": +{ + "frame": {"x":1152,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_Dpad_Y_Default.png": +{ + "frame": {"x":1280,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_L1_Default.png": +{ + "frame": {"x":1408,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_L2_Default.png": +{ + "frame": {"x":1536,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_L_2D_Default.png": +{ + "frame": {"x":1664,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_L_Default.png": +{ + "frame": {"x":0,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_L_Down_Default.png": +{ + "frame": {"x":128,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_L_Left_Default.png": +{ + "frame": {"x":256,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_L_Right_Default.png": +{ + "frame": {"x":384,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_L_UP_Default.png": +{ + "frame": {"x":512,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_L_X_Default.png": +{ + "frame": {"x":640,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_L_Y_Default.png": +{ + "frame": {"x":768,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_Left_Stick_Click_Alt_Default.png": +{ + "frame": {"x":896,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_Left_Stick_Click_Default-1.png": +{ + "frame": {"x":1024,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_Left_Stick_Click_Default.png": +{ + "frame": {"x":1152,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_Options_Default.png": +{ + "frame": {"x":1280,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_R1_Default.png": +{ + "frame": {"x":1408,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_R2_Default.png": +{ + "frame": {"x":1536,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_R_2D_Default.png": +{ + "frame": {"x":1664,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_R_Default.png": +{ + "frame": {"x":0,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_R_Down_Default.png": +{ + "frame": {"x":128,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_R_Left_Default.png": +{ + "frame": {"x":256,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_R_Right_Default.png": +{ + "frame": {"x":384,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_R_UP_Default.png": +{ + "frame": {"x":512,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_R_X_Default.png": +{ + "frame": {"x":640,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_R_Y_Default.png": +{ + "frame": {"x":768,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_Right_Stick_Click_Alt_Default.png": +{ + "frame": {"x":896,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_Share_Default.png": +{ + "frame": {"x":1024,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_Square_Color_Default.png": +{ + "frame": {"x":1152,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_Square_Default.png": +{ + "frame": {"x":1280,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_Touch_Pad_Default.png": +{ + "frame": {"x":1408,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_Triangle_Color_Default.png": +{ + "frame": {"x":1536,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +"T_P4_Triangle_Default.png": +{ + "frame": {"x":1664,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}}, +"meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "1.0", + "image": "dualshock.png", + "format": "RGBA8888", + "size": {"w":1792,"h":384}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:987743e379168b07fdf2bb169758063f:318efd1c2db07d7c85f4d230278c0da3:adc25708364be3d9e70074e95305c745$" +} +} diff --git a/public/images/inputs/dualshock.png b/public/images/inputs/dualshock.png new file mode 100644 index 0000000000000000000000000000000000000000..84bd1a022de66991fd3b0437102abd3ec0479ce9 GIT binary patch literal 82008 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 Wqk4&Z-a-)q{*>g@WlN>aU;GE-4t}u! literal 0 HcmV?d00001 diff --git a/public/images/inputs/keyboard.json b/public/images/inputs/keyboard.json new file mode 100644 index 00000000000..e86dcc1d5ac --- /dev/null +++ b/public/images/inputs/keyboard.json @@ -0,0 +1,644 @@ +{"frames": [ + +{ + "filename": "T_0_Key_Dark.png", + "frame": {"x":0,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_1_Key_Dark.png", + "frame": {"x":128,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_2_Key_Dark.png", + "frame": {"x":256,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_3_Key_Dark-1.png", + "frame": {"x":384,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_3_Key_Dark.png", + "frame": {"x":512,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_5_Key_Dark.png", + "frame": {"x":640,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_6_Key_Dark.png", + "frame": {"x":768,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_7_Key_Dark.png", + "frame": {"x":896,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_8_Key_Dark.png", + "frame": {"x":1024,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_9_Key_Dark.png", + "frame": {"x":1152,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_A_Key_Dark.png", + "frame": {"x":1280,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Alt_Key_Dark.png", + "frame": {"x":1408,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Asterisk_Key_Dark.png", + "frame": {"x":1536,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_B_Key_Dark.png", + "frame": {"x":1664,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Backspace_Alt_Key_Dark.png", + "frame": {"x":1792,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_BackSpace_Key_Dark.png", + "frame": {"x":1920,"y":0,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Brackets_L_Key_Dark.png", + "frame": {"x":0,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Brackets_R_Key_Dark.png", + "frame": {"x":128,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_C_Key_Dark.png", + "frame": {"x":256,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Crtl_Key_Dark.png", + "frame": {"x":384,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_D_Key_Dark.png", + "frame": {"x":512,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Del_Key_Dark.png", + "frame": {"x":640,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Down_Key_Dark.png", + "frame": {"x":768,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_E_Key_Dark.png", + "frame": {"x":896,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_End_Key_Dark.png", + "frame": {"x":1024,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Enter_Alt_Key_Dark.png", + "frame": {"x":1152,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Esc_Key_Dark.png", + "frame": {"x":1280,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_F1_Key_Dark.png", + "frame": {"x":1408,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_F2_Key_Dark.png", + "frame": {"x":1536,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_F3_Key_Dark.png", + "frame": {"x":1664,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_F4_Key_Dark.png", + "frame": {"x":1792,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_F5_Key_Dark.png", + "frame": {"x":1920,"y":128,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_F6_Key_Dark.png", + "frame": {"x":0,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_F7_Key_Dark.png", + "frame": {"x":128,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_F8_Key_Dark.png", + "frame": {"x":256,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_F9_Key_Dark.png", + "frame": {"x":384,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_F10_Key_Dark.png", + "frame": {"x":512,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_F11_Key_Dark.png", + "frame": {"x":640,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_F12_Key_Dark.png", + "frame": {"x":768,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_F_Key_Dark.png", + "frame": {"x":896,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_G_Key_Dark.png", + "frame": {"x":1024,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_H_Key_Dark.png", + "frame": {"x":1152,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Home_Key_Dark.png", + "frame": {"x":1280,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_I_Key_Dark.png", + "frame": {"x":1408,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Ins_Key_Dark.png", + "frame": {"x":1536,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_J_Key_Dark.png", + "frame": {"x":1664,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_K_Key_Dark.png", + "frame": {"x":1792,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Keyboard_R_Key_Dark-1.png", + "frame": {"x":1920,"y":256,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Keyboard_R_Key_Dark.png", + "frame": {"x":0,"y":384,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_L_Key_Dark.png", + "frame": {"x":128,"y":384,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Left_Key_Dark.png", + "frame": {"x":256,"y":384,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_M_Key_Dark.png", + "frame": {"x":384,"y":384,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Minus_Key_Dark.png", + "frame": {"x":512,"y":384,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_N_Key_Dark.png", + "frame": {"x":640,"y":384,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_O_Key_Dark.png", + "frame": {"x":768,"y":384,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_P_Key_Dark.png", + "frame": {"x":896,"y":384,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_PageDown_Key_Dark.png", + "frame": {"x":1024,"y":384,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_PageUp_Key_Dark.png", + "frame": {"x":1152,"y":384,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Plus_Tall_Key_Dark.png", + "frame": {"x":1280,"y":384,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Q_Key_Dark.png", + "frame": {"x":1408,"y":384,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Quotation_Key_Dark.png", + "frame": {"x":1536,"y":384,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_R_Key_Dark.png", + "frame": {"x":1664,"y":384,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Right_Key_Dark.png", + "frame": {"x":1792,"y":384,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_S_Key_Dark.png", + "frame": {"x":1920,"y":384,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Semicolon_Key_Dark.png", + "frame": {"x":0,"y":512,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Shift_Key_Dark.png", + "frame": {"x":128,"y":512,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Shift_Super_Wide_Key_Dark.png", + "frame": {"x":256,"y":512,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Slash_Key_Dark.png", + "frame": {"x":384,"y":512,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Space_Key_Dark.png", + "frame": {"x":512,"y":512,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_T_Key_Dark.png", + "frame": {"x":640,"y":512,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Tab_Key_Dark.png", + "frame": {"x":768,"y":512,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Tilde_Key_Dark.png", + "frame": {"x":896,"y":512,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_U_Key_Dark.png", + "frame": {"x":1024,"y":512,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Up_Key_Dark.png", + "frame": {"x":1152,"y":512,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_V_Key_Dark.png", + "frame": {"x":1280,"y":512,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_W_Key_Dark.png", + "frame": {"x":1408,"y":512,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_X_Key_Dark.png", + "frame": {"x":1536,"y":512,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Y_Key_Dark.png", + "frame": {"x":1664,"y":512,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}, +{ + "filename": "T_Z_Key_Dark.png", + "frame": {"x":1792,"y":512,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128} +}], +"meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "1.0", + "image": "keyboard.png", + "format": "RGBA8888", + "size": {"w":2048,"h":640}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:c63c48370eadc7845a8cc15895f925a0:5ddaf57801c3bd84e190cd9b4786d52d:bad03abb89ad027d879c383c13fd51bc$" +} +} diff --git a/public/images/inputs/keyboard.png b/public/images/inputs/keyboard.png new file mode 100644 index 0000000000000000000000000000000000000000..af019a60da1d842bd6f180290b5dd59a71db73c9 GIT binary patch literal 51888 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%0A7^RghOpkiV;R4Vb)%-(5=?CK^Co$uN+0 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%0A6t3B>(c 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 config.deviceMapping[key] === keycode); +} + +/** + * Retrieves the setting name associated with the specified keycode. + * + * @param config - The configuration object containing custom settings. + * @param keycode - The keycode to search for. + * @returns The setting name associated with the specified keycode. + */ +export function getSettingNameWithKeycode(config, keycode) { + const key = getKeyWithKeycode(config, keycode); + return config.custom[key]; +} + +/** + * Retrieves the icon associated with the specified keycode. + * + * @param config - The configuration object containing icons. + * @param keycode - The keycode to search for. + * @returns The icon associated with the specified keycode. + */ +export function getIconWithKeycode(config, keycode) { + const key = getKeyWithKeycode(config, keycode); + return config.icons[key]; +} + +/** + * Retrieves the button associated with the specified keycode. + * + * @param config - The configuration object containing settings. + * @param keycode - The keycode to search for. + * @returns The button associated with the specified keycode. + */ +export function getButtonWithKeycode(config, keycode) { + const settingName = getSettingNameWithKeycode(config, keycode); + return config.settings[settingName]; +} + +/** + * Retrieves the key associated with the specified setting name. + * + * @param config - The configuration object containing custom settings. + * @param settingName - The setting name to search for. + * @returns The key associated with the specified setting name. + */ +export function getKeyWithSettingName(config, settingName) { + return Object.keys(config.custom).find(key => config.custom[key] === settingName); +} + +/** + * Retrieves the setting name associated with the specified key. + * + * @param config - The configuration object containing custom settings. + * @param key - The key to search for. + * @returns The setting name associated with the specified key. + */ +export function getSettingNameWithKey(config, key) { + return config.custom[key]; +} + +/** + * Retrieves the icon associated with the specified key. + * + * @param config - The configuration object containing icons. + * @param key - The key to search for. + * @returns The icon associated with the specified key. + */ +export function getIconWithKey(config, key) { + return config.icons[key]; +} + +/** + * Retrieves the icon associated with the specified setting name. + * + * @param config - The configuration object containing icons. + * @param settingName - The setting name to search for. + * @returns The icon associated with the specified setting name. + */ +export function getIconWithSettingName(config, settingName) { + const key = getKeyWithSettingName(config, settingName); + return getIconWithKey(config, key); +} + +export function getIconForLatestInput(configs, source, devices, settingName) { + let config; + if (source === 'gamepad') config = configs[devices[Device.GAMEPAD]]; + else config = configs[devices[Device.KEYBOARD]]; + const icon = getIconWithSettingName(config, settingName); + if (!icon) { + const isAlt = settingName.includes("ALT_"); + let altSettingName; + if (isAlt) + altSettingName = settingName.split("ALT_").splice(1)[0]; + else + altSettingName = `ALT_${settingName}`; + return getIconWithSettingName(config, altSettingName); + } + return icon; +} + +export function assign(config, settingNameTarget, keycode): boolean { + // first, we need to check if this keycode is already used on another settingName + if (!canIAssignThisKey(config, getKeyWithKeycode(config, keycode)) || !canIOverrideThisSetting(config, settingNameTarget)) return false; + const previousSettingName = getSettingNameWithKeycode(config, keycode); + // if it was already bound, we delete the bind + if (previousSettingName) { + const previousKey = getKeyWithSettingName(config, previousSettingName); + config.custom[previousKey] = -1; + } + // then, we need to delete the current key for this settingName + const currentKey = getKeyWithSettingName(config, settingNameTarget); + config.custom[currentKey] = -1; + + // then, the new key is assigned to the new settingName + const newKey = getKeyWithKeycode(config, keycode); + config.custom[newKey] = settingNameTarget; + return true; +} + +export function swap(config, settingNameTarget, keycode) { + // only for gamepad + if (config.padType === 'keyboard') return false; + const prev_key = getKeyWithSettingName(config, settingNameTarget); + const prev_settingName = getSettingNameWithKey(config, prev_key); + + const new_key = getKeyWithKeycode(config, keycode); + const new_settingName = getSettingNameWithKey(config, new_key); + + config.custom[prev_key] = new_settingName; + config.custom[new_key] = prev_settingName; + return true; +} + +/** + * Deletes the binding of the specified setting name. + * + * @param config - The configuration object containing custom settings. + * @param settingName - The setting name to delete. + */ +export function deleteBind(config, settingName) { + const key = getKeyWithSettingName(config, settingName); + if (config.blacklist.includes(key) || isTheLatestBind(config, settingName)) return false; + config.custom[key] = -1; + return true; +} + +export function canIAssignThisKey(config, key) { + const settingName = getSettingNameWithKey(config, key); + if (config.blacklist?.includes(key)) return false; + if (settingName === -1) return true; + if (isTheLatestBind(config, settingName)) return false; + return true; +} + +export function canIOverrideThisSetting(config, settingName) { + const key = getKeyWithSettingName(config, settingName); + if (config.blacklist?.includes(key) || isTheLatestBind(config, settingName)) return false; + return true; +} + +export function canIDeleteThisKey(config, key) { + return canIAssignThisKey(config, key); +} + +export function isTheLatestBind(config, settingName) { + if (config.padType !== 'keyboard') return false; + const isAlt = settingName.includes("ALT_"); + let altSettingName; + if (isAlt) + altSettingName = settingName.split("ALT_").splice(1)[0]; + else + altSettingName = `ALT_${settingName}`; + const secondButton = getKeyWithSettingName(config, altSettingName); + return secondButton === undefined; +} \ No newline at end of file diff --git a/src/configs/pad_dualshock.ts b/src/configs/pad_dualshock.ts index 4f66ff8c00a..e4d2fa7387b 100644 --- a/src/configs/pad_dualshock.ts +++ b/src/configs/pad_dualshock.ts @@ -1,10 +1,13 @@ +import {SettingGamepad} from "../system/settings-gamepad"; +import {Button} from "../enums/buttons"; + /** * Dualshock mapping */ const pad_dualshock = { padID: 'Dualshock', - padType: 'Sony', - gamepadMapping: { + padType: 'dualshock', + deviceMapping: { RC_S: 0, RC_E: 1, RC_W: 2, @@ -21,9 +24,65 @@ const pad_dualshock = { LC_S: 13, LC_W: 14, LC_E: 15, - MENU: 16, TOUCH: 17 }, + icons: { + RC_S: "T_P4_Cross_Color_Default.png", + RC_E: "T_P4_Circle_Color_Default.png", + RC_W: "T_P4_Square_Color_Default.png", + RC_N: "T_P4_Triangle_Color_Default.png", + START: "T_P4_Options_Default.png", + SELECT: "T_P4_Share_Default.png", + LB: "T_P4_L1_Default.png", + RB: "T_P4_R1_Default.png", + LT: "T_P4_L2_Default.png", + RT: "T_P4_R2_Default.png", + LS: "T_P4_Left_Stick_Click_Default.png", + RS: "T_P4_Left_Stick_Click_Default-1.png", + LC_N: "T_P4_Dpad_UP_Default.png", + LC_S: "T_P4_Dpad_Down_Default.png", + LC_W: "T_P4_Dpad_Left_Default.png", + LC_E: "T_P4_Dpad_Right_Default.png", + TOUCH: "T_P4_Touch_Pad_Default.png" + }, + settings: { + [SettingGamepad.Button_Up]: Button.UP, + [SettingGamepad.Button_Down]: Button.DOWN, + [SettingGamepad.Button_Left]: Button.LEFT, + [SettingGamepad.Button_Right]: Button.RIGHT, + [SettingGamepad.Button_Action]: Button.ACTION, + [SettingGamepad.Button_Cancel]: Button.CANCEL, + [SettingGamepad.Button_Cycle_Nature]: Button.CYCLE_NATURE, + [SettingGamepad.Button_Cycle_Variant]: Button.CYCLE_VARIANT, + [SettingGamepad.Button_Menu]: Button.MENU, + [SettingGamepad.Button_Stats]: Button.STATS, + [SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM, + [SettingGamepad.Button_Cycle_Shiny]: Button.CYCLE_SHINY, + [SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER, + [SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY, + [SettingGamepad.Button_Speed_Up]: Button.SPEED_UP, + [SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN, + [SettingGamepad.Button_Submit]: Button.SUBMIT + }, + default: { + LC_N: SettingGamepad.Button_Up, + LC_S: SettingGamepad.Button_Down, + LC_W: SettingGamepad.Button_Left, + LC_E: SettingGamepad.Button_Right, + RC_S: SettingGamepad.Button_Action, + RC_E: SettingGamepad.Button_Cancel, + RC_W: SettingGamepad.Button_Cycle_Nature, + RC_N: SettingGamepad.Button_Cycle_Variant, + START: SettingGamepad.Button_Menu, + SELECT: SettingGamepad.Button_Stats, + LB: SettingGamepad.Button_Cycle_Form, + RB: SettingGamepad.Button_Cycle_Shiny, + LT: SettingGamepad.Button_Cycle_Gender, + RT: SettingGamepad.Button_Cycle_Ability, + LS: SettingGamepad.Button_Speed_Up, + RS: SettingGamepad.Button_Slow_Down, + TOUCH: SettingGamepad.Button_Submit, + }, }; export default pad_dualshock; diff --git a/src/configs/pad_generic.ts b/src/configs/pad_generic.ts index 19b5d3df16e..1fd5c413a12 100644 --- a/src/configs/pad_generic.ts +++ b/src/configs/pad_generic.ts @@ -1,10 +1,13 @@ +import {SettingGamepad} from "../system/settings-gamepad"; +import {Button} from "../enums/buttons"; + /** * Generic pad mapping */ const pad_generic = { padID: 'Generic', - padType: 'generic', - gamepadMapping: { + padType: 'xbox', + deviceMapping: { RC_S: 0, RC_E: 1, RC_W: 2, @@ -22,6 +25,66 @@ const pad_generic = { LC_W: 14, LC_E: 15 }, + icons: { + RC_S: "T_X_A_Color_Alt.png", + RC_E: "T_X_B_Color_Alt.png", + RC_W: "T_X_X_Color_Alt.png", + RC_N: "T_X_Y_Color_Alt.png", + START: "T_X_X_Alt.png", + SELECT: "T_X_Share_Alt.png", + LB: "T_X_LB_Alt.png", + RB: "T_X_RB_Alt.png", + LT: "T_X_LT_Alt.png", + RT: "T_X_RT_Alt.png", + LS: "T_X_Left_Stick_Click_Alt_Alt.png", + RS: "T_X_Right_Stick_Click_Alt_Alt.png", + LC_N: "T_X_Dpad_Up_Alt.png", + LC_S: "T_X_Dpad_Down_Alt.png", + LC_W: "T_X_Dpad_Left_Alt.png", + LC_E: "T_X_Dpad_Right_Alt.png", + }, + settings: { + [SettingGamepad.Button_Up]: Button.UP, + [SettingGamepad.Button_Down]: Button.DOWN, + [SettingGamepad.Button_Left]: Button.LEFT, + [SettingGamepad.Button_Right]: Button.RIGHT, + [SettingGamepad.Button_Action]: Button.ACTION, + [SettingGamepad.Button_Cancel]: Button.CANCEL, + [SettingGamepad.Button_Cycle_Nature]: Button.CYCLE_NATURE, + [SettingGamepad.Button_Cycle_Variant]: Button.CYCLE_VARIANT, + [SettingGamepad.Button_Menu]: Button.MENU, + [SettingGamepad.Button_Stats]: Button.STATS, + [SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM, + [SettingGamepad.Button_Cycle_Shiny]: Button.CYCLE_SHINY, + [SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER, + [SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY, + [SettingGamepad.Button_Speed_Up]: Button.SPEED_UP, + [SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN + }, + default: { + LC_N: SettingGamepad.Button_Up, + LC_S: SettingGamepad.Button_Down, + LC_W: SettingGamepad.Button_Left, + LC_E: SettingGamepad.Button_Right, + RC_S: SettingGamepad.Button_Action, + RC_E: SettingGamepad.Button_Cancel, + RC_W: SettingGamepad.Button_Cycle_Nature, + RC_N: SettingGamepad.Button_Cycle_Variant, + START: SettingGamepad.Button_Menu, + SELECT: SettingGamepad.Button_Stats, + LB: SettingGamepad.Button_Cycle_Form, + RB: SettingGamepad.Button_Cycle_Shiny, + LT: SettingGamepad.Button_Cycle_Gender, + RT: SettingGamepad.Button_Cycle_Ability, + LS: SettingGamepad.Button_Speed_Up, + RS: SettingGamepad.Button_Slow_Down + }, + blacklist: [ + "LC_N", + "LC_S", + "LC_W", + "LC_E", + ] }; export default pad_generic; diff --git a/src/configs/pad_unlicensedSNES.ts b/src/configs/pad_unlicensedSNES.ts index ba8ee538d30..047d8707030 100644 --- a/src/configs/pad_unlicensedSNES.ts +++ b/src/configs/pad_unlicensedSNES.ts @@ -1,10 +1,13 @@ +import {SettingGamepad} from "../system/settings-gamepad"; +import {Button} from "../enums/buttons"; + /** * 081f-e401 - UnlicensedSNES */ const pad_unlicensedSNES = { padID: '081f-e401', padType: 'snes', - gamepadMapping : { + deviceMapping : { RC_S: 2, RC_E: 1, RC_W: 3, @@ -17,7 +20,57 @@ const pad_unlicensedSNES = { LC_S: 13, LC_W: 14, LC_E: 15 - } + }, + icons: { + RC_S: "T_X_B_White_Alt.png", + RC_E: "T_X_A_White_Alt.png", + RC_W: "T_X_Y_White_Alt.png", + RC_N: "T_X_X_White_Alt.png", + START: "start.png", + SELECT: "select.png", + LB: "T_X_LB_Alt.png", + RB: "T_X_RB_Alt.png", + LC_N: "T_X_Dpad_Up_Alt.png", + LC_S: "T_X_Dpad_Down_Alt.png", + LC_W: "T_X_Dpad_Left_Alt.png", + LC_E: "T_X_Dpad_Right_Alt.png", + }, + settings: { + [SettingGamepad.Button_Up]: Button.UP, + [SettingGamepad.Button_Down]: Button.DOWN, + [SettingGamepad.Button_Left]: Button.LEFT, + [SettingGamepad.Button_Right]: Button.RIGHT, + [SettingGamepad.Button_Action]: Button.ACTION, + [SettingGamepad.Button_Cancel]: Button.CANCEL, + [SettingGamepad.Button_Cycle_Nature]: Button.CYCLE_NATURE, + [SettingGamepad.Button_Cycle_Variant]: Button.CYCLE_VARIANT, + [SettingGamepad.Button_Menu]: Button.MENU, + [SettingGamepad.Button_Stats]: Button.STATS, + [SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM, + [SettingGamepad.Button_Cycle_Shiny]: Button.CYCLE_SHINY, + [SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER, + [SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY, + [SettingGamepad.Button_Speed_Up]: Button.SPEED_UP, + [SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN + }, + default: { + LC_N: SettingGamepad.Button_Up, + LC_S: SettingGamepad.Button_Down, + LC_W: SettingGamepad.Button_Left, + LC_E: SettingGamepad.Button_Right, + RC_S: SettingGamepad.Button_Action, + RC_E: SettingGamepad.Button_Cancel, + RC_W: SettingGamepad.Button_Cycle_Nature, + RC_N: SettingGamepad.Button_Cycle_Variant, + START: SettingGamepad.Button_Menu, + SELECT: SettingGamepad.Button_Stats, + LB: SettingGamepad.Button_Cycle_Form, + RB: SettingGamepad.Button_Cycle_Shiny, + LT: -1, + RT: -1, + LS: -1, + RS: -1 + }, }; export default pad_unlicensedSNES; diff --git a/src/configs/pad_xbox360.ts b/src/configs/pad_xbox360.ts index e44ebb54b64..f92e06bc247 100644 --- a/src/configs/pad_xbox360.ts +++ b/src/configs/pad_xbox360.ts @@ -1,10 +1,13 @@ +import {SettingGamepad} from "../system/settings-gamepad"; +import {Button} from "#app/enums/buttons"; + /** * Generic pad mapping */ const pad_xbox360 = { padID: 'Xbox 360 controller (XInput STANDARD GAMEPAD)', padType: 'xbox', - gamepadMapping: { + deviceMapping: { RC_S: 0, RC_E: 1, RC_W: 2, @@ -20,8 +23,61 @@ const pad_xbox360 = { LC_N: 12, LC_S: 13, LC_W: 14, - LC_E: 15, - MENU: 16 + LC_E: 15 + }, + icons: { + RC_S: "T_X_A_Color_Alt.png", + RC_E: "T_X_B_Color_Alt.png", + RC_W: "T_X_X_Color_Alt.png", + RC_N: "T_X_Y_Color_Alt.png", + START: "T_X_X_Alt.png", + SELECT: "T_X_Share_Alt.png", + LB: "T_X_LB_Alt.png", + RB: "T_X_RB_Alt.png", + LT: "T_X_LT_Alt.png", + RT: "T_X_RT_Alt.png", + LS: "T_X_Left_Stick_Click_Alt_Alt.png", + RS: "T_X_Right_Stick_Click_Alt_Alt.png", + LC_N: "T_X_Dpad_Up_Alt.png", + LC_S: "T_X_Dpad_Down_Alt.png", + LC_W: "T_X_Dpad_Left_Alt.png", + LC_E: "T_X_Dpad_Right_Alt.png", + }, + settings: { + [SettingGamepad.Button_Up]: Button.UP, + [SettingGamepad.Button_Down]: Button.DOWN, + [SettingGamepad.Button_Left]: Button.LEFT, + [SettingGamepad.Button_Right]: Button.RIGHT, + [SettingGamepad.Button_Action]: Button.ACTION, + [SettingGamepad.Button_Cancel]: Button.CANCEL, + [SettingGamepad.Button_Cycle_Nature]: Button.CYCLE_NATURE, + [SettingGamepad.Button_Cycle_Variant]: Button.CYCLE_VARIANT, + [SettingGamepad.Button_Menu]: Button.MENU, + [SettingGamepad.Button_Stats]: Button.STATS, + [SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM, + [SettingGamepad.Button_Cycle_Shiny]: Button.CYCLE_SHINY, + [SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER, + [SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY, + [SettingGamepad.Button_Speed_Up]: Button.SPEED_UP, + [SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN + }, + default: { + LC_N: SettingGamepad.Button_Up, + LC_S: SettingGamepad.Button_Down, + LC_W: SettingGamepad.Button_Left, + LC_E: SettingGamepad.Button_Right, + RC_S: SettingGamepad.Button_Action, + RC_E: SettingGamepad.Button_Cancel, + RC_W: SettingGamepad.Button_Cycle_Nature, + RC_N: SettingGamepad.Button_Cycle_Variant, + START: SettingGamepad.Button_Menu, + SELECT: SettingGamepad.Button_Stats, + LB: SettingGamepad.Button_Cycle_Form, + RB: SettingGamepad.Button_Cycle_Shiny, + LT: SettingGamepad.Button_Cycle_Gender, + RT: SettingGamepad.Button_Cycle_Ability, + LS: SettingGamepad.Button_Speed_Up, + RS: SettingGamepad.Button_Slow_Down }, }; diff --git a/src/enums/devices.ts b/src/enums/devices.ts new file mode 100644 index 00000000000..b085dfbada3 --- /dev/null +++ b/src/enums/devices.ts @@ -0,0 +1,4 @@ +export enum Device { + GAMEPAD, + KEYBOARD, +} diff --git a/src/inputs-controller.ts b/src/inputs-controller.ts index 7de0969869d..b9649657a89 100644 --- a/src/inputs-controller.ts +++ b/src/inputs-controller.ts @@ -1,26 +1,50 @@ -import Phaser, {Time} from "phaser"; import * as Utils from "./utils"; +import {deepCopy} from "./utils"; import {initTouchControls} from './touch-controls'; import pad_generic from "./configs/pad_generic"; import pad_unlicensedSNES from "./configs/pad_unlicensedSNES"; import pad_xbox360 from "./configs/pad_xbox360"; import pad_dualshock from "./configs/pad_dualshock"; import {Button} from "./enums/buttons"; +import {Mode} from "./ui/ui"; +import SettingsGamepadUiHandler from "./ui/settings/settings-gamepad-ui-handler"; +import SettingsKeyboardUiHandler from "./ui/settings/settings-keyboard-ui-handler"; +import cfg_keyboard_azerty from "./configs/cfg_keyboard_azerty"; +import {Device} from "#app/enums/devices"; +import { + assign, + getButtonWithKeycode, + getIconForLatestInput, swap, +} from "#app/configs/configHandler"; -export interface GamepadMapping { +export interface DeviceMapping { [key: string]: number; } -export interface GamepadConfig { - padID: string; - padType: string; - gamepadMapping: GamepadMapping; +export interface IconsMapping { + [key: string]: string; } -export interface ActionGamepadMapping { +export interface SettingMapping { + [key: string]: string; +} + +export interface MappingLayout { [key: string]: Button; } +export interface InterfaceConfig { + padID: string; + padType: string; + deviceMapping: DeviceMapping; + icons: IconsMapping; + setting: SettingMapping; + default: MappingLayout; + custom: MappingLayout; + main: Array; + alt: Array; +} + const repeatInputDelayMillis = 250; /** @@ -47,16 +71,25 @@ const repeatInputDelayMillis = 250; */ export class InputsController { private buttonKeys: Phaser.Input.Keyboard.Key[][]; - private gamepads: Array = new Array(); + private gamepads: Array = new Array(); private scene: Phaser.Scene; + private events: Phaser.Events.EventEmitter; private buttonLock: Button; private buttonLock2: Button; private interactions: Map> = new Map(); - private time: Time; - private player: Map = new Map(); + private time: Phaser.Time.Clock; + private configs: Map = new Map(); private gamepadSupport: boolean = true; + public selectedDevice; + + private disconnectedGamepads: Array = new Array(); + + private pauseUpdate: boolean = false; + + public lastSource: string = 'keyboard'; + private keys: Array = []; /** * Initializes a new instance of the game control system, setting up initial state and configurations. @@ -69,10 +102,15 @@ export class InputsController { * Specific buttons like MENU and STATS are set not to repeat their actions. * It concludes by calling the `init` method to complete the setup. */ + constructor(scene: Phaser.Scene) { this.scene = scene; this.time = this.scene.time; this.buttonKeys = []; + this.selectedDevice = { + [Device.GAMEPAD]: null, + [Device.KEYBOARD]: 'default' + } for (const b of Utils.getEnumValues(Button)) { this.interactions[b] = { @@ -96,15 +134,22 @@ export class InputsController { * Additionally, it manages the game's behavior when it loses focus to prevent unwanted game actions during this state. */ init(): void { - this.events = new Phaser.Events.EventEmitter(); + this.events = this.scene.game.events; + this.scene.game.events.on(Phaser.Core.Events.BLUR, () => { this.loseFocus() }) if (typeof this.scene.input.gamepad !== 'undefined') { this.scene.input.gamepad.on('connected', function (thisGamepad) { + if (!thisGamepad) return; this.refreshGamepads(); this.setupGamepad(thisGamepad); + this.onReconnect(thisGamepad); + }, this); + + this.scene.input.gamepad.on('disconnected', function (thisGamepad) { + this.onDisconnect(thisGamepad); // when a gamepad is disconnected }, this); // Check to see if the gamepad has already been setup by the browser @@ -118,10 +163,10 @@ export class InputsController { this.scene.input.gamepad.on('down', this.gamepadButtonDown, this); this.scene.input.gamepad.on('up', this.gamepadButtonUp, this); + this.scene.input.keyboard.on('keydown', this.keyboardKeyDown, this); + this.scene.input.keyboard.on('keyup', this.keyboardKeyUp, this); } - - // Keyboard - this.setupKeyboardControls(); + initTouchControls(this.events); } /** @@ -147,35 +192,58 @@ export class InputsController { this.gamepadSupport = true; } else { this.gamepadSupport = false; - // if we disable the gamepad, we want to release every key pressed this.deactivatePressedKey(); } } + /** + * Sets the currently chosen gamepad and initializes related settings. + * This method first deactivates any active key presses and then initializes the gamepad settings. + * + * @param gamepad - The identifier of the gamepad to set as chosen. + */ + setChosenGamepad(gamepad: String): void { + this.deactivatePressedKey(); + this.initChosenGamepad(gamepad) + } + + /** + * Sets the currently chosen keyboard layout and initializes related settings. + * + * @param layoutKeyboard - The identifier of the keyboard layout to set as chosen. + */ + setChosenKeyboardLayout(layoutKeyboard: String): void { + this.deactivatePressedKey(); + this.initChosenLayoutKeyboard(layoutKeyboard) + } + /** * Updates the interaction handling by processing input states. * This method gives priority to certain buttons by reversing the order in which they are checked. + * This method loops through all button values, checks for valid and timely interactions, and conditionally processes + * or ignores them based on the current state of gamepad support and other criteria. * - * @remarks - * The method iterates over all possible buttons, checking for specific conditions such as: - * - If the button is registered in the `interactions` dictionary. - * - If the button has been held down long enough. - * - If the button is currently pressed. + * It handles special conditions such as the absence of gamepad support or mismatches between the source of the input and + * the currently chosen gamepad. It also respects the paused state of updates to prevent unwanted input processing. * - * Special handling is applied if gamepad support is disabled but a gamepad source is still triggering inputs, - * preventing potential infinite loops by removing the last processed movement time for the button. + * If an interaction is valid and should be processed, it emits an 'input_down' event with details of the interaction. */ update(): void { for (const b of Utils.getEnumValues(Button).reverse()) { if ( this.interactions.hasOwnProperty(b) && - this.repeatInputDurationJustPassed(b) && + this.repeatInputDurationJustPassed(b as Button) && this.interactions[b].isPressed ) { // Prevents repeating button interactions when gamepad support is disabled. - if (!this.gamepadSupport && this.interactions[b].source === 'gamepad') { + if ( + (!this.gamepadSupport && this.interactions[b].source === 'gamepad') || + (this.interactions[b].source === 'gamepad' && this.interactions[b].sourceName && this.interactions[b].sourceName !== this.selectedDevice[Device.GAMEPAD]) || + (this.interactions[b].source === 'keyboard' && this.interactions[b].sourceName && this.interactions[b].sourceName !== this.selectedDevice[Device.KEYBOARD]) || + this.pauseUpdate + ) { // Deletes the last interaction for a button if gamepad is disabled. - this.delLastProcessedMovementTime(b); + this.delLastProcessedMovementTime(b as Button); return; } // Emits an event for the button press. @@ -183,25 +251,101 @@ export class InputsController { controller_type: this.interactions[b].source, button: b, }); - this.setLastProcessedMovementTime(b, this.interactions[b].source); + this.setLastProcessedMovementTime(b as Button, this.interactions[b].source, this.interactions[b].sourceName); } } } /** - * Configures a gamepad for use based on its device ID. + * Retrieves the identifiers of all connected gamepads, excluding any that are currently marked as disconnected. + * @returns Array An array of strings representing the IDs of the connected gamepads. + */ + getGamepadsName(): Array { + return this.gamepads.filter(g => !this.disconnectedGamepads.includes(g.id)).map(g => g.id); + } + + /** + * Initializes the chosen gamepad by setting its identifier in the local storage and updating the UI to reflect the chosen gamepad. + * If a gamepad name is provided, it uses that as the chosen gamepad; otherwise, it defaults to the currently chosen gamepad. + * @param gamepadName Optional parameter to specify the name of the gamepad to initialize as chosen. + */ + initChosenGamepad(gamepadName?: String): void { + if (gamepadName) + this.selectedDevice[Device.GAMEPAD] = gamepadName.toLowerCase(); + const handler = this.scene.ui?.handlers[Mode.SETTINGS_GAMEPAD] as SettingsGamepadUiHandler; + handler && handler.updateChosenGamepadDisplay() + } + + /** + * Initializes the chosen keyboard layout by setting its identifier in the local storage and updating the UI to reflect the chosen layout. + * If a layout name is provided, it uses that as the chosen layout; otherwise, it defaults to the currently chosen layout. + * @param layoutKeyboard Optional parameter to specify the name of the keyboard layout to initialize as chosen. + */ + initChosenLayoutKeyboard(layoutKeyboard?: String): void { + if (layoutKeyboard) + this.selectedDevice[Device.KEYBOARD] = layoutKeyboard.toLowerCase(); + const handler = this.scene.ui?.handlers[Mode.SETTINGS_KEYBOARD] as SettingsKeyboardUiHandler; + handler && handler.updateChosenKeyboardDisplay() + } + + /** + * Handles the disconnection of a gamepad by adding its identifier to a list of disconnected gamepads. + * This is necessary because Phaser retains memory of previously connected gamepads, and without tracking + * disconnections, it would be impossible to determine the connection status of gamepads. This method ensures + * that disconnected gamepads are recognized and can be appropriately hidden in the gamepad selection menu. * - * @param thisGamepad - The gamepad to set up. + * @param thisGamepad The gamepad that has been disconnected. + */ + onDisconnect(thisGamepad: Phaser.Input.Gamepad.Gamepad): void { + this.disconnectedGamepads.push(thisGamepad.id); + } + + /** + * Updates the tracking of disconnected gamepads when a gamepad is reconnected. + * It removes the reconnected gamepad's identifier from the `disconnectedGamepads` array, + * effectively updating its status to connected. * - * @remarks - * This method initializes a gamepad by mapping its ID to a predefined configuration. - * It updates the player's gamepad mapping based on the identified configuration, ensuring - * that the gamepad controls are correctly mapped to in-game actions. + * @param thisGamepad The gamepad that has been reconnected. + */ + onReconnect(thisGamepad: Phaser.Input.Gamepad.Gamepad): void { + this.disconnectedGamepads = this.disconnectedGamepads.filter(g => g !== thisGamepad.id); + } + + /** + * Initializes or updates configurations for connected gamepads. + * It retrieves the names of all connected gamepads, sets up their configurations according to stored or default settings, + * and ensures these configurations are saved. If the connected gamepad is the currently chosen one, + * it reinitializes the chosen gamepad settings. + * + * @param thisGamepad The gamepad that is being set up. */ setupGamepad(thisGamepad: Phaser.Input.Gamepad.Gamepad): void { - let gamepadID = thisGamepad.id.toLowerCase(); - const mappedPad = this.mapGamepad(gamepadID); - this.player['mapping'] = mappedPad.gamepadMapping; + const allGamepads = this.getGamepadsName(); + for (const gamepad of allGamepads) { + const gamepadID = gamepad.toLowerCase(); + if (!this.selectedDevice[Device.GAMEPAD]) + this.setChosenGamepad(gamepadID); + const config = deepCopy(this.getConfig(gamepadID)); + config.custom = this.configs[gamepadID]?.custom || {...config.default}; + this.configs[gamepadID] = config; + this.scene.gameData?.saveMappingConfigs(gamepadID, this.configs[gamepadID]); + } + this.lastSource = 'gamepad'; + const handler = this.scene.ui?.handlers[Mode.SETTINGS_GAMEPAD] as SettingsGamepadUiHandler; + handler && handler.updateChosenGamepadDisplay() + } + + /** + * Initializes or updates configurations for connected keyboards. + */ + setupKeyboard(): void { + for (const layout of ['default']) { + const config = deepCopy(this.getConfigKeyboard(layout)); + config.custom = this.configs[layout]?.custom || {...config.default}; + this.configs[layout] = config; + this.scene.gameData?.saveMappingConfigs(this.selectedDevice[Device.KEYBOARD], this.configs[layout]); + } + this.initChosenLayoutKeyboard(this.selectedDevice[Device.KEYBOARD]) } /** @@ -224,83 +368,96 @@ export class InputsController { } /** - * Retrieves the current gamepad mapping for in-game actions. - * - * @returns An object mapping gamepad buttons to in-game actions based on the player's current gamepad configuration. - * - * @remarks - * This method constructs a mapping of gamepad buttons to in-game action buttons according to the player's - * current gamepad configuration. If no configuration is available, it returns an empty mapping. - * The mapping includes directional controls, action buttons, and system commands among others, - * adjusted for any custom settings such as swapped action buttons. + * Ensures the keyboard is initialized by checking if there is an active configuration for the keyboard. + * If not, it sets up the keyboard with default configurations. */ - getActionGamepadMapping(): ActionGamepadMapping { - const gamepadMapping = {}; - if (!this.player?.mapping) return gamepadMapping; - gamepadMapping[this.player.mapping.LC_N] = Button.UP; - gamepadMapping[this.player.mapping.LC_S] = Button.DOWN; - gamepadMapping[this.player.mapping.LC_W] = Button.LEFT; - gamepadMapping[this.player.mapping.LC_E] = Button.RIGHT; - gamepadMapping[this.player.mapping.TOUCH] = Button.SUBMIT; - gamepadMapping[this.player.mapping.RC_S] = this.scene.abSwapped ? Button.CANCEL : Button.ACTION; - gamepadMapping[this.player.mapping.RC_E] = this.scene.abSwapped ? Button.ACTION : Button.CANCEL; - gamepadMapping[this.player.mapping.SELECT] = Button.STATS; - gamepadMapping[this.player.mapping.START] = Button.MENU; - gamepadMapping[this.player.mapping.RB] = Button.CYCLE_SHINY; - gamepadMapping[this.player.mapping.LB] = Button.CYCLE_FORM; - gamepadMapping[this.player.mapping.LT] = Button.CYCLE_GENDER; - gamepadMapping[this.player.mapping.RT] = Button.CYCLE_ABILITY; - gamepadMapping[this.player.mapping.RC_W] = Button.CYCLE_NATURE; - gamepadMapping[this.player.mapping.RC_N] = Button.CYCLE_VARIANT; - gamepadMapping[this.player.mapping.LS] = Button.SPEED_UP; - gamepadMapping[this.player.mapping.RS] = Button.SLOW_DOWN; - - return gamepadMapping; + ensureKeyboardIsInit(): void { + if (!this.getActiveConfig(Device.KEYBOARD)?.padID) + this.setupKeyboard(); } /** - * Handles the 'down' event for gamepad buttons, emitting appropriate events and updating the interaction state. + * Handles the keydown event for the keyboard. * - * @param pad - The gamepad on which the button press occurred. - * @param button - The button that was pressed. - * @param value - The value associated with the button press, typically indicating pressure or degree of activation. + * @param event The keyboard event. + */ + keyboardKeyDown(event): void { + this.lastSource = 'keyboard'; + const keyDown = event.keyCode; + this.ensureKeyboardIsInit(); + if (this.keys.includes(keyDown)) return; + this.keys.push(keyDown); + const buttonDown = getButtonWithKeycode(this.getActiveConfig(Device.KEYBOARD), keyDown); + if (buttonDown !== undefined) { + this.events.emit('input_down', { + controller_type: 'keyboard', + button: buttonDown, + }); + this.setLastProcessedMovementTime(buttonDown, 'keyboard', this.selectedDevice[Device.KEYBOARD]); + } + } + + /** + * Handles the keyup event for the keyboard. * - * @remarks - * This method is triggered when a gamepad button is pressed. If gamepad support is enabled, it: - * - Retrieves the current gamepad action mapping. - * - Checks if the pressed button is mapped to a game action. - * - If mapped, emits an 'input_down' event with the controller type and button action, and updates the interaction of this button. + * @param event The keyboard event. + */ + keyboardKeyUp(event): void { + this.lastSource = 'keyboard'; + const keyDown = event.keyCode; + this.keys = this.keys.filter(k => k !== keyDown); + this.ensureKeyboardIsInit() + const buttonUp = getButtonWithKeycode(this.getActiveConfig(Device.KEYBOARD), keyDown); + if (buttonUp !== undefined) { + this.events.emit('input_up', { + controller_type: 'keyboard', + button: buttonUp, + }); + this.delLastProcessedMovementTime(buttonUp); + } + } + + /** + * Handles button press events on a gamepad. This method sets the gamepad as chosen on the first input if no gamepad is currently chosen. + * It checks if gamepad support is enabled and if the event comes from the chosen gamepad. If so, it maps the button press to a specific + * action using a custom configuration, emits an event for the button press, and records the time of the action. + * + * @param pad The gamepad on which the button was pressed. + * @param button The specific button that was pressed. + * @param value The intensity or value of the button press, if applicable. */ gamepadButtonDown(pad: Phaser.Input.Gamepad.Gamepad, button: Phaser.Input.Gamepad.Button, value: number): void { - if (!this.gamepadSupport) return; - const actionMapping = this.getActionGamepadMapping(); - const buttonDown = actionMapping.hasOwnProperty(button.index) && actionMapping[button.index]; + if (!this.configs[this.selectedDevice[Device.KEYBOARD]]?.padID) + this.setupKeyboard(); + if (!pad) return; + this.lastSource = 'gamepad'; + if (!this.selectedDevice[Device.GAMEPAD]) + this.setChosenGamepad(pad.id); + if (!this.gamepadSupport || pad.id.toLowerCase() !== this.selectedDevice[Device.GAMEPAD].toLowerCase()) return; + const buttonDown = getButtonWithKeycode(this.getActiveConfig(Device.GAMEPAD), button.index); if (buttonDown !== undefined) { this.events.emit('input_down', { controller_type: 'gamepad', button: buttonDown, }); - this.setLastProcessedMovementTime(buttonDown, 'gamepad'); + this.setLastProcessedMovementTime(buttonDown, 'gamepad', pad.id); } } /** - * Handles the 'up' event for gamepad buttons, emitting appropriate events and clearing the interaction state. + * Responds to a button release event on a gamepad by checking if the gamepad is supported and currently chosen. + * If conditions are met, it identifies the configured action for the button, emits an event signaling the button release, + * and clears the record of the button. * - * @param pad - The gamepad on which the button release occurred. - * @param button - The button that was released. - * @param value - The value associated with the button release, typically indicating pressure or degree of deactivation. - * - * @remarks - * This method is triggered when a gamepad button is released. If gamepad support is enabled, it: - * - Retrieves the current gamepad action mapping. - * - Checks if the released button is mapped to a game action. - * - If mapped, emits an 'input_up' event with the controller type and button action, and clears the interaction for this button. + * @param pad The gamepad from which the button was released. + * @param button The specific button that was released. + * @param value The intensity or value of the button release, if applicable. */ gamepadButtonUp(pad: Phaser.Input.Gamepad.Gamepad, button: Phaser.Input.Gamepad.Button, value: number): void { - if (!this.gamepadSupport) return; - const actionMapping = this.getActionGamepadMapping(); - const buttonUp = actionMapping.hasOwnProperty(button.index) && actionMapping[button.index]; + if (!pad) return; + this.lastSource = 'gamepad'; + if (!this.gamepadSupport || pad.id.toLowerCase() !== this.selectedDevice[Device.GAMEPAD]) return; + const buttonUp = getButtonWithKeycode(this.getActiveConfig(Device.GAMEPAD), button.index); if (buttonUp !== undefined) { this.events.emit('input_up', { controller_type: 'gamepad', @@ -311,112 +468,14 @@ export class InputsController { } /** - * Configures keyboard controls for the game, mapping physical keys to game actions. + * Retrieves the configuration object for a gamepad based on its identifier. The method identifies specific gamepad models + * based on substrings in the identifier and returns predefined configurations for recognized models. + * If no specific configuration matches, it defaults to a generic gamepad configuration. * - * @remarks - * This method sets up keyboard bindings for game controls using Phaser's `KeyCodes`. Each game action, represented - * by a button in the `Button` enum, is associated with one or more physical keys. For example, movement actions - * (up, down, left, right) are mapped to both arrow keys and WASD keys. Actions such as submit, cancel, and other - * game-specific functions are mapped to appropriate keys like Enter, Space, etc. - * - * The method does the following: - * - Defines a `keyConfig` object that associates each `Button` enum value with an array of `KeyCodes`. - * - Iterates over all values of the `Button` enum to set up these key bindings within the Phaser game scene. - * - For each button, it adds the respective keys to the game's input system and stores them in `this.buttonKeys`. - * - Additional configurations for mobile or alternative input schemes are stored in `mobileKeyConfig`. - * - * Post-setup, it initializes touch controls (if applicable) and starts listening for keyboard inputs using - * `listenInputKeyboard`, ensuring that all configured keys are actively monitored for player interactions. + * @param id The identifier string of the gamepad. + * @returns InterfaceConfig The configuration object corresponding to the identified gamepad type. */ - setupKeyboardControls(): void { - const keyCodes = Phaser.Input.Keyboard.KeyCodes; - const keyConfig = { - [Button.UP]: [keyCodes.UP, keyCodes.W], - [Button.DOWN]: [keyCodes.DOWN, keyCodes.S], - [Button.LEFT]: [keyCodes.LEFT, keyCodes.A], - [Button.RIGHT]: [keyCodes.RIGHT, keyCodes.D], - [Button.SUBMIT]: [keyCodes.ENTER], - [Button.ACTION]: [keyCodes.SPACE, keyCodes.Z], - [Button.CANCEL]: [keyCodes.BACKSPACE, keyCodes.X], - [Button.MENU]: [keyCodes.ESC, keyCodes.M], - [Button.STATS]: [keyCodes.SHIFT, keyCodes.C], - [Button.CYCLE_SHINY]: [keyCodes.R], - [Button.CYCLE_FORM]: [keyCodes.F], - [Button.CYCLE_GENDER]: [keyCodes.G], - [Button.CYCLE_ABILITY]: [keyCodes.E], - [Button.CYCLE_NATURE]: [keyCodes.N], - [Button.CYCLE_VARIANT]: [keyCodes.V], - [Button.SPEED_UP]: [keyCodes.PLUS], - [Button.SLOW_DOWN]: [keyCodes.MINUS] - }; - const mobileKeyConfig = {}; - for (const b of Utils.getEnumValues(Button)) { - const keys: Phaser.Input.Keyboard.Key[] = []; - if (keyConfig.hasOwnProperty(b)) { - for (let k of keyConfig[b]) - keys.push(this.scene.input.keyboard.addKey(k, false)); - mobileKeyConfig[Button[b]] = keys[0]; - } - this.buttonKeys[b] = keys; - } - - initTouchControls(mobileKeyConfig); - this.listenInputKeyboard(); - } - - /** - * Sets up event listeners for keyboard inputs on all registered keys. - * - * @remarks - * This method iterates over an array of keyboard button rows (`this.buttonKeys`), adding 'down' and 'up' - * event listeners for each key. These listeners handle key press and release actions respectively. - * - * - **Key Down Event**: When a key is pressed down, the method emits an 'input_down' event with the button - * and the source ('keyboard'). It also records the time and state of the key press by calling - * `setLastProcessedMovementTime`. - * - * - **Key Up Event**: When a key is released, the method emits an 'input_up' event similarly, specifying the button - * and source. It then clears the recorded press time and state by calling - * `delLastProcessedMovementTime`. - * - * This setup ensures that each key on the keyboard is monitored for press and release events, - * and that these events are properly communicated within the system. - */ - listenInputKeyboard(): void { - this.buttonKeys.forEach((row, index) => { - for (const key of row) { - key.on('down', () => { - this.events.emit('input_down', { - controller_type: 'keyboard', - button: index, - }); - this.setLastProcessedMovementTime(index, 'keyboard'); - }); - key.on('up', () => { - this.events.emit('input_up', { - controller_type: 'keyboard', - button: index, - }); - this.delLastProcessedMovementTime(index); - }); - } - }); - } - - /** - * Maps a gamepad ID to a specific gamepad configuration based on the ID's characteristics. - * - * @param id - The gamepad ID string, typically representing a unique identifier for a gamepad model or make. - * @returns A `GamepadConfig` object corresponding to the identified gamepad model. - * - * @remarks - * This function analyzes the provided gamepad ID and matches it to a predefined configuration based on known identifiers: - * - If the ID includes both '081f' and 'e401', it is identified as an unlicensed SNES gamepad. - * - If the ID contains 'xbox' and '360', it is identified as an Xbox 360 gamepad. - * - If the ID contains '054c', it is identified as a DualShock gamepad. - * If no specific identifiers are recognized, a generic gamepad configuration is returned. - */ - mapGamepad(id: string): GamepadConfig { + getConfig(id: string): InterfaceConfig { id = id.toLowerCase(); if (id.includes('081f') && id.includes('e401')) { @@ -430,6 +489,19 @@ export class InputsController { return pad_generic; } + /** + * Retrieves the configuration object for a keyboard layout based on its identifier. + * + * @param id The identifier string of the keyboard layout. + * @returns InterfaceConfig The configuration object corresponding to the identified keyboard layout. + */ + getConfigKeyboard(id: string): InterfaceConfig { + if (id === 'default') + return cfg_keyboard_azerty; + + return cfg_keyboard_azerty; + } + /** * repeatInputDurationJustPassed returns true if @param button has been held down long * enough to fire a repeated input. A button must claim the buttonLock before @@ -457,12 +529,13 @@ export class InputsController { * * Additionally, this method locks the button (by calling `setButtonLock`) to prevent it from being re-processed until it is released, ensuring that each press is handled distinctly. */ - setLastProcessedMovementTime(button: Button, source: String = 'keyboard'): void { + setLastProcessedMovementTime(button: Button, source: String = 'keyboard', sourceName?: String): void { if (!this.interactions.hasOwnProperty(button)) return; this.setButtonLock(button); this.interactions[button].pressTime = this.time.now; this.interactions[button].isPressed = true; this.interactions[button].source = source; + this.interactions[button].sourceName = sourceName.toLowerCase(); } /** @@ -485,6 +558,7 @@ export class InputsController { this.interactions[button].pressTime = null; this.interactions[button].isPressed = false; this.interactions[button].source = null; + this.interactions[button].sourceName = null; } /** @@ -506,6 +580,7 @@ export class InputsController { * This method is typically called when needing to ensure that all inputs are neutralized. */ deactivatePressedKey(): void { + this.pauseUpdate = true; this.releaseButtonLock(this.buttonLock); this.releaseButtonLock(this.buttonLock2); for (const b of Utils.getEnumValues(Button)) { @@ -513,8 +588,10 @@ export class InputsController { this.interactions[b].pressTime = null; this.interactions[b].isPressed = false; this.interactions[b].source = null; + this.interactions[b].sourceName = null; } } + setTimeout(() => this.pauseUpdate = false, 500); } /** @@ -564,4 +641,71 @@ export class InputsController { if (this.buttonLock === button) this.buttonLock = null; else if (this.buttonLock2 === button) this.buttonLock2 = null; } + + /** + * Retrieves the active configuration for the currently chosen device. + * It checks if a specific device ID is stored in configurations and returns it. + * + * @returns InterfaceConfig The configuration object for the active gamepad, or null if not set. + */ + getActiveConfig(device: Device) { + if (this.configs[this.selectedDevice[device]]?.padID) return this.configs[this.selectedDevice[device]] + return null; + } + + getIconForLatestInputRecorded(settingName) { + if (this.lastSource === 'keyboard') this.ensureKeyboardIsInit(); + return getIconForLatestInput(this.configs, this.lastSource, this.selectedDevice, settingName); + } + + getLastSourceDevice(): Device { + if (this.lastSource === 'gamepad') return Device.GAMEPAD; + else return Device.KEYBOARD; + } + + getLastSourceConfig() { + const sourceDevice = this.getLastSourceDevice(); + if (sourceDevice === Device.KEYBOARD) + this.ensureKeyboardIsInit(); + return this.getActiveConfig(sourceDevice); + } + + getLastSourceType() { + const config = this.getLastSourceConfig(); + return config?.padType; + } + + /** + * Injects a custom mapping configuration into the configuration for a specific gamepad. + * If the device does not have an existing configuration, it initializes one first. + * + * @param selectedDevice The identifier of the device to configure. + * @param mappingConfigs The mapping configuration to apply to the device. + */ + injectConfig(selectedDevice: string, mappingConfigs): void { + if (!this.configs[selectedDevice]) this.configs[selectedDevice] = {}; + this.configs[selectedDevice].custom = mappingConfigs.custom; + } + + resetConfigs(): void { + this.configs = new Map(); + if (this.getGamepadsName()?.length) + this.setupGamepad(this.selectedDevice[Device.GAMEPAD]); + this.setupKeyboard(); + } + + /** + * Swaps a binding in the configuration. + * + * @param config The configuration object. + * @param settingName The name of the setting to swap. + * @param pressedButton The button that was pressed. + */ + assignBinding(config, settingName, pressedButton): boolean { + this.pauseUpdate = true; + setTimeout(() => this.pauseUpdate = false, 500); + if (config.padType === 'keyboard') + return assign(config, settingName, pressedButton); + else return swap(config, settingName, pressedButton); + } } \ No newline at end of file diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 56d0ab47f13..fdd86e9a3c2 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -218,6 +218,14 @@ export class LoadingScene extends SceneBase { this.loadAtlas(`pokemon_icons_${i}v`, ''); } + // Free icons from: + // https://juliocacko.itch.io/free-input-prompts + this.loadAtlas('dualshock', 'inputs'); + this.loadAtlas('nswitch', 'inputs'); + this.loadAtlas('xbox', 'inputs'); + this.loadAtlas('snes', 'inputs'); + this.loadAtlas('keyboard', 'inputs'); + this.loadSe('select'); this.loadSe('menu_open'); this.loadSe('hit'); diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 8b09fe8b910..ddaa3869458 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -30,6 +30,9 @@ import { allMoves } from "../data/move"; import { TrainerVariant } from "../field/trainer"; import { OutdatedPhase, ReloadSessionPhase } from "#app/phases"; import { Variant, variantData } from "#app/data/variant"; +import {setSettingGamepad, SettingGamepad, settingGamepadDefaults} from "./settings-gamepad"; +import {MappingLayout} from "#app/inputs-controller"; +import {setSettingKeyboard, SettingKeyboard, settingKeyboardDefaults} from "#app/system/settings-keyboard"; const saveKey = 'x0i2O7WRiANTqPmZ'; // Temporary; secure encryption is not yet necessary @@ -241,6 +244,8 @@ export class GameData { constructor(scene: BattleScene) { this.scene = scene; this.loadSettings(); + this.loadGamepadSettings(); + this.loadMappingConfigs(); this.trainerId = Utils.randInt(65536); this.secretId = Utils.randInt(65536); this.starterData = {}; @@ -538,11 +543,123 @@ export class GameData { settings[s] = valueIndex; }); + localStorage.setItem('settings', JSON.stringify(settings)); return true; } + /** + * Saves the mapping configurations for a specified device. + * + * @param deviceName - The name of the device for which the configurations are being saved. + * @param config - The configuration object containing custom mapping details. + * @returns `true` if the configurations are successfully saved. + */ + public saveMappingConfigs(deviceName: string, config): boolean { + const key = deviceName.toLowerCase(); // Convert the gamepad name to lowercase to use as a key + let mappingConfigs: object = {}; // Initialize an empty object to hold the mapping configurations + if (localStorage.hasOwnProperty('mappingConfigs')) // Check if 'mappingConfigs' exists in localStorage + mappingConfigs = JSON.parse(localStorage.getItem('mappingConfigs')); // Parse the existing 'mappingConfigs' from localStorage + if (!mappingConfigs[key]) mappingConfigs[key] = {}; // If there is no configuration for the given key, create an empty object for it + mappingConfigs[key].custom = config.custom; // Assign the custom configuration to the mapping configuration for the given key + localStorage.setItem('mappingConfigs', JSON.stringify(mappingConfigs)); // Save the updated mapping configurations back to localStorage + return true; // Return true to indicate the operation was successful + } + + /** + * Loads the mapping configurations from localStorage and injects them into the input controller. + * + * @returns `true` if the configurations are successfully loaded and injected; `false` if no configurations are found in localStorage. + * + * @remarks + * This method checks if the 'mappingConfigs' entry exists in localStorage. If it does not exist, the method returns `false`. + * If 'mappingConfigs' exists, it parses the configurations and injects each configuration into the input controller + * for the corresponding gamepad or device key. The method then returns `true` to indicate success. + */ + public loadMappingConfigs(): boolean { + if (!localStorage.hasOwnProperty('mappingConfigs')) // Check if 'mappingConfigs' exists in localStorage + return false; // If 'mappingConfigs' does not exist, return false + + const mappingConfigs = JSON.parse(localStorage.getItem('mappingConfigs')); // Parse the existing 'mappingConfigs' from localStorage + + for (const key of Object.keys(mappingConfigs)) // Iterate over the keys of the mapping configurations + this.scene.inputController.injectConfig(key, mappingConfigs[key]); // Inject each configuration into the input controller for the corresponding key + + return true; // Return true to indicate the operation was successful + } + + public resetMappingToFactory(): boolean { + if (!localStorage.hasOwnProperty('mappingConfigs')) // Check if 'mappingConfigs' exists in localStorage + return false; // If 'mappingConfigs' does not exist, return false + localStorage.removeItem('mappingConfigs'); + this.scene.inputController.resetConfigs(); + } + + /** + * Saves a gamepad setting to localStorage. + * + * @param setting - The gamepad setting to save. + * @param valueIndex - The index of the value to set for the gamepad setting. + * @returns `true` if the setting is successfully saved. + * + * @remarks + * This method initializes an empty object for gamepad settings if none exist in localStorage. + * It then updates the setting in the current scene and iterates over the default gamepad settings + * to update the specified setting with the new value. Finally, it saves the updated settings back + * to localStorage and returns `true` to indicate success. + */ + public saveGamepadSetting(setting: SettingGamepad, valueIndex: integer): boolean { + let settingsGamepad: object = {}; // Initialize an empty object to hold the gamepad settings + + if (localStorage.hasOwnProperty('settingsGamepad')) { // Check if 'settingsGamepad' exists in localStorage + settingsGamepad = JSON.parse(localStorage.getItem('settingsGamepad')); // Parse the existing 'settingsGamepad' from localStorage + } + + setSettingGamepad(this.scene, setting as SettingGamepad, valueIndex); // Set the gamepad setting in the current scene + + Object.keys(settingGamepadDefaults).forEach(s => { // Iterate over the default gamepad settings + if (s === setting) // If the current setting matches, update its value + settingsGamepad[s] = valueIndex; + }); + + localStorage.setItem('settingsGamepad', JSON.stringify(settingsGamepad)); // Save the updated gamepad settings back to localStorage + + return true; // Return true to indicate the operation was successful + } + + /** + * Saves a keyboard setting to localStorage. + * + * @param setting - The keyboard setting to save. + * @param valueIndex - The index of the value to set for the keyboard setting. + * @returns `true` if the setting is successfully saved. + * + * @remarks + * This method initializes an empty object for keyboard settings if none exist in localStorage. + * It then updates the setting in the current scene and iterates over the default keyboard settings + * to update the specified setting with the new value. Finally, it saves the updated settings back + * to localStorage and returns `true` to indicate success. + */ + public saveKeyboardSetting(setting: SettingKeyboard, valueIndex: integer): boolean { + let settingsKeyboard: object = {}; // Initialize an empty object to hold the keyboard settings + + if (localStorage.hasOwnProperty('settingsKeyboard')) { // Check if 'settingsKeyboard' exists in localStorage + settingsKeyboard = JSON.parse(localStorage.getItem('settingsKeyboard')); // Parse the existing 'settingsKeyboard' from localStorage + } + + setSettingKeyboard(this.scene, setting as SettingKeyboard, valueIndex); // Set the keyboard setting in the current scene + + Object.keys(settingKeyboardDefaults).forEach(s => { // Iterate over the default keyboard settings + if (s === setting) // If the current setting matches, update its value + settingsKeyboard[s] = valueIndex; + }); + + localStorage.setItem('settingsKeyboard', JSON.stringify(settingsKeyboard)); // Save the updated keyboard settings back to localStorage + + return true; // Return true to indicate the operation was successful + } + private loadSettings(): boolean { Object.values(Setting).map(setting => setting as Setting).forEach(setting => setSetting(this.scene, setting, settingDefaults[setting])); @@ -555,6 +672,17 @@ export class GameData { setSetting(this.scene, setting as Setting, settings[setting]); } + private loadGamepadSettings(): boolean { + Object.values(SettingGamepad).map(setting => setting as SettingGamepad).forEach(setting => setSettingGamepad(this.scene, setting, settingGamepadDefaults[setting])); + + if (!localStorage.hasOwnProperty('settingsGamepad')) + return false; + const settingsGamepad = JSON.parse(localStorage.getItem('settingsGamepad')); + + for (let setting of Object.keys(settingsGamepad)) + setSettingGamepad(this.scene, setting as SettingGamepad, settingsGamepad[setting]); + } + public saveTutorialFlag(tutorial: Tutorial, flag: boolean): boolean { let tutorials: object = {}; if (localStorage.hasOwnProperty('tutorials')) diff --git a/src/system/settings-gamepad.ts b/src/system/settings-gamepad.ts new file mode 100644 index 00000000000..428d149841a --- /dev/null +++ b/src/system/settings-gamepad.ts @@ -0,0 +1,146 @@ +import BattleScene from "../battle-scene"; +import {SettingDefaults, SettingOptions} from "./settings"; +import SettingsGamepadUiHandler from "../ui/settings/settings-gamepad-ui-handler"; +import {Mode} from "../ui/ui"; +import {truncateString} from "../utils"; +import {Button} from "../enums/buttons"; +import {SettingKeyboard} from "#app/system/settings-keyboard"; + +export enum SettingGamepad { + Default_Controller = "DEFAULT_CONTROLLER", + Gamepad_Support = "GAMEPAD_SUPPORT", + Button_Up = "BUTTON_UP", + Button_Down = "BUTTON_DOWN", + Button_Left = "BUTTON_LEFT", + Button_Right = "BUTTON_RIGHT", + Button_Action = "BUTTON_ACTION", + Button_Cancel = "BUTTON_CANCEL", + Button_Menu = "BUTTON_MENU", + Button_Stats = "BUTTON_STATS", + Button_Cycle_Form = "BUTTON_CYCLE_FORM", + Button_Cycle_Shiny = "BUTTON_CYCLE_SHINY", + Button_Cycle_Gender = "BUTTON_CYCLE_GENDER", + Button_Cycle_Ability = "BUTTON_CYCLE_ABILITY", + Button_Cycle_Nature = "BUTTON_CYCLE_NATURE", + Button_Cycle_Variant = "BUTTON_CYCLE_VARIANT", + Button_Speed_Up = "BUTTON_SPEED_UP", + Button_Slow_Down = "BUTTON_SLOW_DOWN", + Button_Submit = "BUTTON_SUBMIT", +} + +export const settingGamepadOptions: SettingOptions = { + [SettingGamepad.Default_Controller]: ['Default', 'Change'], + [SettingGamepad.Gamepad_Support]: ['Auto', 'Disabled'], + [SettingGamepad.Button_Up]: [`KEY ${Button.UP.toString()}`, 'Press action to assign'], + [SettingGamepad.Button_Down]: [`KEY ${Button.DOWN.toString()}`, 'Press action to assign'], + [SettingGamepad.Button_Left]: [`KEY ${Button.LEFT.toString()}`, 'Press action to assign'], + [SettingGamepad.Button_Right]: [`KEY ${Button.RIGHT.toString()}`, 'Press action to assign'], + [SettingGamepad.Button_Action]: [`KEY ${Button.ACTION.toString()}`, 'Press action to assign'], + [SettingGamepad.Button_Cancel]: [`KEY ${Button.CANCEL.toString()}`, 'Press action to assign'], + [SettingGamepad.Button_Menu]: [`KEY ${Button.MENU.toString()}`, 'Press action to assign'], + [SettingGamepad.Button_Stats]: [`KEY ${Button.STATS.toString()}`, 'Press action to assign'], + [SettingGamepad.Button_Cycle_Form]: [`KEY ${Button.CYCLE_FORM.toString()}`, 'Press action to assign'], + [SettingGamepad.Button_Cycle_Shiny]: [`KEY ${Button.CYCLE_SHINY.toString()}`, 'Press action to assign'], + [SettingGamepad.Button_Cycle_Gender]: [`KEY ${Button.CYCLE_GENDER.toString()}`, 'Press action to assign'], + [SettingGamepad.Button_Cycle_Ability]: [`KEY ${Button.CYCLE_ABILITY.toString()}`, 'Press action to assign'], + [SettingGamepad.Button_Cycle_Nature]: [`KEY ${Button.CYCLE_NATURE.toString()}`, 'Press action to assign'], + [SettingGamepad.Button_Cycle_Variant]: [`KEY ${Button.CYCLE_VARIANT.toString()}`, 'Press action to assign'], + [SettingGamepad.Button_Speed_Up]: [`KEY ${Button.SPEED_UP.toString()}`, 'Press action to assign'], + [SettingGamepad.Button_Slow_Down]: [`KEY ${Button.SLOW_DOWN.toString()}`, 'Press action to assign'], + [SettingGamepad.Button_Submit]: [`KEY ${Button.SUBMIT.toString()}`, 'Press action to assign'], +}; + +export const settingGamepadDefaults: SettingDefaults = { + [SettingGamepad.Default_Controller]: 0, + [SettingGamepad.Gamepad_Support]: 0, + [SettingGamepad.Button_Up]: 0, + [SettingGamepad.Button_Down]: 0, + [SettingGamepad.Button_Left]: 0, + [SettingGamepad.Button_Right]: 0, + [SettingGamepad.Button_Action]: 0, + [SettingGamepad.Button_Cancel]: 0, + [SettingGamepad.Button_Menu]: 0, + [SettingGamepad.Button_Stats]: 0, + [SettingGamepad.Button_Cycle_Form]: 0, + [SettingGamepad.Button_Cycle_Shiny]: 0, + [SettingGamepad.Button_Cycle_Gender]: 0, + [SettingGamepad.Button_Cycle_Ability]: 0, + [SettingGamepad.Button_Cycle_Nature]: 0, + [SettingGamepad.Button_Cycle_Variant]: 0, + [SettingGamepad.Button_Speed_Up]: 0, + [SettingGamepad.Button_Slow_Down]: 0, + [SettingGamepad.Button_Submit]: 0, +}; + +export const settingGamepadBlackList = [ + SettingKeyboard.Button_Up, + SettingKeyboard.Button_Down, + SettingKeyboard.Button_Left, + SettingKeyboard.Button_Right, +]; + +export function setSettingGamepad(scene: BattleScene, setting: SettingGamepad, value: integer): boolean { + switch (setting) { + case SettingGamepad.Gamepad_Support: + // if we change the value of the gamepad support, we call a method in the inputController to + // activate or deactivate the controller listener + scene.inputController.setGamepadSupport(settingGamepadOptions[setting][value] !== 'Disabled'); + break; + case SettingGamepad.Button_Action: + case SettingGamepad.Button_Cancel: + case SettingGamepad.Button_Menu: + case SettingGamepad.Button_Stats: + case SettingGamepad.Button_Cycle_Shiny: + case SettingGamepad.Button_Cycle_Form: + case SettingGamepad.Button_Cycle_Gender: + case SettingGamepad.Button_Cycle_Ability: + case SettingGamepad.Button_Cycle_Nature: + case SettingGamepad.Button_Cycle_Variant: + case SettingGamepad.Button_Speed_Up: + case SettingGamepad.Button_Slow_Down: + if (value) { + if (scene.ui) { + const cancelHandler = (success: boolean = false) : boolean => { + scene.ui.revertMode(); + (scene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings(); + return success; + }; + scene.ui.setOverlayMode(Mode.GAMEPAD_BINDING, { + target: setting, + cancelHandler: cancelHandler, + }); + } + } + break; + case SettingGamepad.Default_Controller: + if (value) { + const gp = scene.inputController.getGamepadsName(); + if (scene.ui && gp) { + const cancelHandler = () => { + scene.ui.revertMode(); + (scene.ui.getHandler() as SettingsGamepadUiHandler).setOptionCursor(Object.values(SettingGamepad).indexOf(SettingGamepad.Default_Controller), 0, true); + (scene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings(); + return false; + }; + const changeGamepadHandler = (gamepad: string) => { + scene.inputController.setChosenGamepad(gamepad); + cancelHandler(); + return true; + }; + scene.ui.setOverlayMode(Mode.OPTION_SELECT, { + options: [...gp.map((g) => ({ + label: truncateString(g, 30), // Truncate the gamepad name for display + handler: () => changeGamepadHandler(g) + })), { + label: 'Cancel', + handler: cancelHandler, + }] + }); + return false; + } + } + break; + } + + return true; +} diff --git a/src/system/settings-keyboard.ts b/src/system/settings-keyboard.ts new file mode 100644 index 00000000000..33ced66e5d4 --- /dev/null +++ b/src/system/settings-keyboard.ts @@ -0,0 +1,206 @@ +import {SettingDefaults, SettingOptions} from "#app/system/settings"; +import {Button} from "#app/enums/buttons"; +import BattleScene from "#app/battle-scene"; +import {Mode} from "#app/ui/ui"; +import SettingsKeyboardUiHandler from "#app/ui/settings/settings-keyboard-ui-handler"; + +export enum SettingKeyboard { + // Default_Layout = "DEFAULT_LAYOUT", + Button_Up = "BUTTON_UP", + Alt_Button_Up = "ALT_BUTTON_UP", + Button_Down = "BUTTON_DOWN", + Alt_Button_Down = "ALT_BUTTON_DOWN", + Button_Left = "BUTTON_LEFT", + Alt_Button_Left = "ALT_BUTTON_LEFT", + Button_Right = "BUTTON_RIGHT", + Alt_Button_Right = "ALT_BUTTON_RIGHT", + Button_Action = "BUTTON_ACTION", + Alt_Button_Action = "ALT_BUTTON_ACTION", + Button_Cancel = "BUTTON_CANCEL", + Alt_Button_Cancel = "ALT_BUTTON_CANCEL", + Button_Menu = "BUTTON_MENU", + Alt_Button_Menu = "ALT_BUTTON_MENU", + Button_Stats = "BUTTON_STATS", + Alt_Button_Stats = "ALT_BUTTON_STATS", + Button_Cycle_Form = "BUTTON_CYCLE_FORM", + Alt_Button_Cycle_Form = "ALT_BUTTON_CYCLE_FORM", + Button_Cycle_Shiny = "BUTTON_CYCLE_SHINY", + Alt_Button_Cycle_Shiny = "ALT_BUTTON_CYCLE_SHINY", + Button_Cycle_Gender = "BUTTON_CYCLE_GENDER", + Alt_Button_Cycle_Gender = "ALT_BUTTON_CYCLE_GENDER", + Button_Cycle_Ability = "BUTTON_CYCLE_ABILITY", + Alt_Button_Cycle_Ability = "ALT_BUTTON_CYCLE_ABILITY", + Button_Cycle_Nature = "BUTTON_CYCLE_NATURE", + Alt_Button_Cycle_Nature = "ALT_BUTTON_CYCLE_NATURE", + Button_Cycle_Variant = "BUTTON_CYCLE_VARIANT", + Alt_Button_Cycle_Variant = "ALT_BUTTON_CYCLE_VARIANT", + Button_Speed_Up = "BUTTON_SPEED_UP", + Alt_Button_Speed_Up = "ALT_BUTTON_SPEED_UP", + Button_Slow_Down = "BUTTON_SLOW_DOWN", + Alt_Button_Slow_Down = "ALT_BUTTON_SLOW_DOWN", + Button_Submit = "BUTTON_SUBMIT", + Alt_Button_Submit = "ALT_BUTTON_SUBMIT", +} + +export const settingKeyboardOptions: SettingOptions = { + // [SettingKeyboard.Default_Layout]: ['Default'], + [SettingKeyboard.Button_Up]: [`KEY ${Button.UP.toString()}`, 'Press action to assign'], + [SettingKeyboard.Button_Down]: [`KEY ${Button.DOWN.toString()}`, 'Press action to assign'], + [SettingKeyboard.Alt_Button_Up]: [`KEY ${Button.UP.toString()}`, 'Press action to assign'], + [SettingKeyboard.Button_Left]: [`KEY ${Button.LEFT.toString()}`, 'Press action to assign'], + [SettingKeyboard.Button_Right]: [`KEY ${Button.RIGHT.toString()}`, 'Press action to assign'], + [SettingKeyboard.Button_Action]: [`KEY ${Button.ACTION.toString()}`, 'Press action to assign'], + [SettingKeyboard.Button_Menu]: [`KEY ${Button.MENU.toString()}`, 'Press action to assign'], + [SettingKeyboard.Button_Submit]: [`KEY ${Button.SUBMIT.toString()}`, 'Press action to assign'], + + [SettingKeyboard.Alt_Button_Down]: [`KEY ${Button.DOWN.toString()}`, 'Press action to assign'], + [SettingKeyboard.Alt_Button_Left]: [`KEY ${Button.LEFT.toString()}`, 'Press action to assign'], + [SettingKeyboard.Alt_Button_Right]: [`KEY ${Button.RIGHT.toString()}`, 'Press action to assign'], + [SettingKeyboard.Alt_Button_Action]: [`KEY ${Button.ACTION.toString()}`, 'Press action to assign'], + [SettingKeyboard.Button_Cancel]: [`KEY ${Button.CANCEL.toString()}`, 'Press action to assign'], + [SettingKeyboard.Alt_Button_Cancel]: [`KEY ${Button.CANCEL.toString()}`, 'Press action to assign'], + [SettingKeyboard.Alt_Button_Menu]: [`KEY ${Button.MENU.toString()}`, 'Press action to assign'], + [SettingKeyboard.Button_Stats]: [`KEY ${Button.STATS.toString()}`, 'Press action to assign'], + [SettingKeyboard.Alt_Button_Stats]: [`KEY ${Button.STATS.toString()}`, 'Press action to assign'], + [SettingKeyboard.Button_Cycle_Form]: [`KEY ${Button.CYCLE_FORM.toString()}`, 'Press action to assign'], + [SettingKeyboard.Alt_Button_Cycle_Form]: [`KEY ${Button.CYCLE_FORM.toString()}`, 'Press action to assign'], + [SettingKeyboard.Button_Cycle_Shiny]: [`KEY ${Button.CYCLE_SHINY.toString()}`, 'Press action to assign'], + [SettingKeyboard.Alt_Button_Cycle_Shiny]: [`KEY ${Button.CYCLE_SHINY.toString()}`, 'Press action to assign'], + [SettingKeyboard.Button_Cycle_Gender]: [`KEY ${Button.CYCLE_GENDER.toString()}`, 'Press action to assign'], + [SettingKeyboard.Alt_Button_Cycle_Gender]: [`KEY ${Button.CYCLE_GENDER.toString()}`, 'Press action to assign'], + [SettingKeyboard.Button_Cycle_Ability]: [`KEY ${Button.CYCLE_ABILITY.toString()}`, 'Press action to assign'], + [SettingKeyboard.Alt_Button_Cycle_Ability]: [`KEY ${Button.CYCLE_ABILITY.toString()}`, 'Press action to assign'], + [SettingKeyboard.Button_Cycle_Nature]: [`KEY ${Button.CYCLE_NATURE.toString()}`, 'Press action to assign'], + [SettingKeyboard.Alt_Button_Cycle_Nature]: [`KEY ${Button.CYCLE_NATURE.toString()}`, 'Press action to assign'], + [SettingKeyboard.Button_Cycle_Variant]: [`KEY ${Button.CYCLE_VARIANT.toString()}`, 'Press action to assign'], + [SettingKeyboard.Alt_Button_Cycle_Variant]: [`KEY ${Button.CYCLE_VARIANT.toString()}`, 'Press action to assign'], + [SettingKeyboard.Button_Speed_Up]: [`KEY ${Button.SPEED_UP.toString()}`, 'Press action to assign'], + [SettingKeyboard.Alt_Button_Speed_Up]: [`KEY ${Button.SPEED_UP.toString()}`, 'Press action to assign'], + [SettingKeyboard.Button_Slow_Down]: [`KEY ${Button.SLOW_DOWN.toString()}`, 'Press action to assign'], + [SettingKeyboard.Alt_Button_Slow_Down]: [`KEY ${Button.SLOW_DOWN.toString()}`, 'Press action to assign'], + [SettingKeyboard.Alt_Button_Submit]: [`KEY ${Button.SUBMIT.toString()}`, 'Press action to assign'], +}; + +export const settingKeyboardDefaults: SettingDefaults = { + // [SettingKeyboard.Default_Layout]: 0, + [SettingKeyboard.Button_Up]: 0, + [SettingKeyboard.Button_Down]: 0, + [SettingKeyboard.Button_Left]: 0, + [SettingKeyboard.Button_Right]: 0, + [SettingKeyboard.Button_Action]: 0, + [SettingKeyboard.Button_Menu]: 0, + [SettingKeyboard.Button_Submit]: 0, + + [SettingKeyboard.Alt_Button_Up]: 0, + [SettingKeyboard.Alt_Button_Down]: 0, + [SettingKeyboard.Alt_Button_Left]: 0, + [SettingKeyboard.Alt_Button_Right]: 0, + [SettingKeyboard.Alt_Button_Action]: 0, + [SettingKeyboard.Button_Cancel]: 0, + [SettingKeyboard.Alt_Button_Cancel]: 0, + [SettingKeyboard.Alt_Button_Menu]: 0, + [SettingKeyboard.Button_Stats]: 0, + [SettingKeyboard.Alt_Button_Stats]: 0, + [SettingKeyboard.Button_Cycle_Form]: 0, + [SettingKeyboard.Alt_Button_Cycle_Form]: 0, + [SettingKeyboard.Button_Cycle_Shiny]: 0, + [SettingKeyboard.Alt_Button_Cycle_Shiny]: 0, + [SettingKeyboard.Button_Cycle_Gender]: 0, + [SettingKeyboard.Alt_Button_Cycle_Gender]: 0, + [SettingKeyboard.Button_Cycle_Ability]: 0, + [SettingKeyboard.Alt_Button_Cycle_Ability]: 0, + [SettingKeyboard.Button_Cycle_Nature]: 0, + [SettingKeyboard.Alt_Button_Cycle_Nature]: 0, + [SettingKeyboard.Button_Cycle_Variant]: 0, + [SettingKeyboard.Alt_Button_Cycle_Variant]: 0, + [SettingKeyboard.Button_Speed_Up]: 0, + [SettingKeyboard.Alt_Button_Speed_Up]: 0, + [SettingKeyboard.Button_Slow_Down]: 0, + [SettingKeyboard.Alt_Button_Slow_Down]: 0, + [SettingKeyboard.Alt_Button_Submit]: 0, +}; + +export const settingKeyboardBlackList = [ + SettingKeyboard.Button_Submit, + SettingKeyboard.Button_Menu, + SettingKeyboard.Button_Action, + SettingKeyboard.Button_Up, + SettingKeyboard.Button_Down, + SettingKeyboard.Button_Left, + SettingKeyboard.Button_Right, +]; + + +export function setSettingKeyboard(scene: BattleScene, setting: SettingKeyboard, value: integer): boolean { + switch (setting) { + case SettingKeyboard.Button_Up: + case SettingKeyboard.Button_Down: + case SettingKeyboard.Button_Left: + case SettingKeyboard.Button_Right: + case SettingKeyboard.Button_Action: + case SettingKeyboard.Button_Cancel: + case SettingKeyboard.Button_Menu: + case SettingKeyboard.Button_Stats: + case SettingKeyboard.Button_Cycle_Shiny: + case SettingKeyboard.Button_Cycle_Form: + case SettingKeyboard.Button_Cycle_Gender: + case SettingKeyboard.Button_Cycle_Ability: + case SettingKeyboard.Button_Cycle_Nature: + case SettingKeyboard.Button_Cycle_Variant: + case SettingKeyboard.Button_Speed_Up: + case SettingKeyboard.Button_Slow_Down: + case SettingKeyboard.Alt_Button_Up: + case SettingKeyboard.Alt_Button_Down: + case SettingKeyboard.Alt_Button_Left: + case SettingKeyboard.Alt_Button_Right: + case SettingKeyboard.Alt_Button_Action: + case SettingKeyboard.Alt_Button_Cancel: + case SettingKeyboard.Alt_Button_Menu: + case SettingKeyboard.Alt_Button_Stats: + case SettingKeyboard.Alt_Button_Cycle_Shiny: + case SettingKeyboard.Alt_Button_Cycle_Form: + case SettingKeyboard.Alt_Button_Cycle_Gender: + case SettingKeyboard.Alt_Button_Cycle_Ability: + case SettingKeyboard.Alt_Button_Cycle_Nature: + case SettingKeyboard.Alt_Button_Cycle_Variant: + case SettingKeyboard.Alt_Button_Speed_Up: + case SettingKeyboard.Alt_Button_Slow_Down: + if (value) { + if (scene.ui) { + const cancelHandler = (success: boolean = false) : boolean => { + scene.ui.revertMode(); + (scene.ui.getHandler() as SettingsKeyboardUiHandler).updateBindings(); + return success; + } + scene.ui.setOverlayMode(Mode.KEYBOARD_BINDING, { + target: setting, + cancelHandler: cancelHandler, + }) + } + } + break; + // case SettingKeyboard.Default_Layout: + // if (value && scene.ui) { + // const cancelHandler = () => { + // scene.ui.revertMode(); + // (scene.ui.getHandler() as SettingsKeyboardUiHandler).setOptionCursor(Object.values(SettingKeyboard).indexOf(SettingKeyboard.Default_Layout), 0, true); + // (scene.ui.getHandler() as SettingsKeyboardUiHandler).updateBindings(); + // return false; + // }; + // const changeKeyboardHandler = (keyboardLayout: string) => { + // scene.inputController.setChosenKeyboardLayout(keyboardLayout); + // cancelHandler(); + // return true; + // }; + // scene.ui.setOverlayMode(Mode.OPTION_SELECT, { + // options: [{ + // label: 'Default', + // handler: changeKeyboardHandler, + // }] + // }); + // return false; + // } + } + return true; + +} \ No newline at end of file diff --git a/src/system/settings.ts b/src/system/settings.ts index aa2f4f5c682..fe58614262f 100644 --- a/src/system/settings.ts +++ b/src/system/settings.ts @@ -1,10 +1,10 @@ -import SettingsUiHandler from "#app/ui/settings-ui-handler"; -import { Mode } from "#app/ui/ui"; import i18next from "i18next"; import BattleScene from "../battle-scene"; import { hasTouchscreen } from "../touch-controls"; import { updateWindowType } from "../ui/ui-theme"; import { PlayerGender } from "./game-data"; +import { Mode } from "../ui/ui"; +import SettingsUiHandler from "../ui/settings/settings-ui-handler"; export enum Setting { Game_Speed = "GAME_SPEED", @@ -25,8 +25,6 @@ export enum Setting { HP_Bar_Speed = "HP_BAR_SPEED", Fusion_Palette_Swaps = "FUSION_PALETTE_SWAPS", Player_Gender = "PLAYER_GENDER", - Gamepad_Support = "GAMEPAD_SUPPORT", - Swap_A_and_B = "SWAP_A_B", // Swaps which gamepad button handles ACTION and CANCEL Touch_Controls = "TOUCH_CONTROLS", Vibration = "VIBRATION" } @@ -58,8 +56,6 @@ export const settingOptions: SettingOptions = { [Setting.HP_Bar_Speed]: ['Normal', 'Fast', 'Faster', 'Instant'], [Setting.Fusion_Palette_Swaps]: ['Off', 'On'], [Setting.Player_Gender]: ['Boy', 'Girl'], - [Setting.Gamepad_Support]: ['Auto', 'Disabled'], - [Setting.Swap_A_and_B]: ['Enabled', 'Disabled'], [Setting.Touch_Controls]: ['Auto', 'Disabled'], [Setting.Vibration]: ['Auto', 'Disabled'] }; @@ -83,8 +79,6 @@ export const settingDefaults: SettingDefaults = { [Setting.HP_Bar_Speed]: 0, [Setting.Fusion_Palette_Swaps]: 1, [Setting.Player_Gender]: 0, - [Setting.Gamepad_Support]: 0, - [Setting.Swap_A_and_B]: 1, // Set to 'Disabled' by default [Setting.Touch_Controls]: 0, [Setting.Vibration]: 0 }; @@ -154,14 +148,6 @@ export function setSetting(scene: BattleScene, setting: Setting, value: integer) } else return false; break; - case Setting.Gamepad_Support: - // if we change the value of the gamepad support, we call a method in the inputController to - // activate or deactivate the controller listener - scene.inputController.setGamepadSupport(settingOptions[setting][value] !== 'Disabled'); - break; - case Setting.Swap_A_and_B: - scene.abSwapped = settingOptions[setting][value] !== 'Disabled'; - break; case Setting.Touch_Controls: scene.enableTouchControls = settingOptions[setting][value] !== 'Disabled' && hasTouchscreen(); const touchControls = document.getElementById('touchControls'); diff --git a/src/test/cfg_keyboard.example.ts b/src/test/cfg_keyboard.example.ts new file mode 100644 index 00000000000..20e2bd2c5fb --- /dev/null +++ b/src/test/cfg_keyboard.example.ts @@ -0,0 +1,330 @@ +import {Button} from "#app/enums/buttons"; + +export enum SettingInterface { + Default_Layout = "DEFAULT_LAYOUT", + Button_Up = "BUTTON_UP", + Alt_Button_Up = "ALT_BUTTON_UP", + Button_Down = "BUTTON_DOWN", + Alt_Button_Down = "ALT_BUTTON_DOWN", + Button_Left = "BUTTON_LEFT", + Alt_Button_Left = "ALT_BUTTON_LEFT", + Button_Right = "BUTTON_RIGHT", + Alt_Button_Right = "ALT_BUTTON_RIGHT", + Button_Action = "BUTTON_ACTION", + Alt_Button_Action = "ALT_BUTTON_ACTION", + Button_Cancel = "BUTTON_CANCEL", + Alt_Button_Cancel = "ALT_BUTTON_CANCEL", + Button_Menu = "BUTTON_MENU", + Alt_Button_Menu = "ALT_BUTTON_MENU", + Button_Stats = "BUTTON_STATS", + Alt_Button_Stats = "ALT_BUTTON_STATS", + Button_Cycle_Form = "BUTTON_CYCLE_FORM", + Alt_Button_Cycle_Form = "ALT_BUTTON_CYCLE_FORM", + Button_Cycle_Shiny = "BUTTON_CYCLE_SHINY", + Alt_Button_Cycle_Shiny = "ALT_BUTTON_CYCLE_SHINY", + Button_Cycle_Gender = "BUTTON_CYCLE_GENDER", + Alt_Button_Cycle_Gender = "ALT_BUTTON_CYCLE_GENDER", + Button_Cycle_Ability = "BUTTON_CYCLE_ABILITY", + Alt_Button_Cycle_Ability = "ALT_BUTTON_CYCLE_ABILITY", + Button_Cycle_Nature = "BUTTON_CYCLE_NATURE", + Alt_Button_Cycle_Nature = "ALT_BUTTON_CYCLE_NATURE", + Button_Cycle_Variant = "BUTTON_CYCLE_VARIANT", + Alt_Button_Cycle_Variant = "ALT_BUTTON_CYCLE_VARIANT", + Button_Speed_Up = "BUTTON_SPEED_UP", + Alt_Button_Speed_Up = "ALT_BUTTON_SPEED_UP", + Button_Slow_Down = "BUTTON_SLOW_DOWN", + Alt_Button_Slow_Down = "ALT_BUTTON_SLOW_DOWN", + Button_Submit = "BUTTON_SUBMIT", + Alt_Button_Submit = "ALT_BUTTON_SUBMIT", +} + +const cfg_keyboard_azerty = { + padID: 'default', + padType: 'keyboard', + deviceMapping: { + KEY_A: Phaser.Input.Keyboard.KeyCodes.A, + KEY_B: Phaser.Input.Keyboard.KeyCodes.B, + KEY_C: Phaser.Input.Keyboard.KeyCodes.C, + KEY_D: Phaser.Input.Keyboard.KeyCodes.D, + KEY_E: Phaser.Input.Keyboard.KeyCodes.E, + KEY_F: Phaser.Input.Keyboard.KeyCodes.F, + KEY_G: Phaser.Input.Keyboard.KeyCodes.G, + KEY_H: Phaser.Input.Keyboard.KeyCodes.H, + KEY_I: Phaser.Input.Keyboard.KeyCodes.I, + KEY_J: Phaser.Input.Keyboard.KeyCodes.J, + KEY_K: Phaser.Input.Keyboard.KeyCodes.K, + KEY_L: Phaser.Input.Keyboard.KeyCodes.L, + KEY_M: Phaser.Input.Keyboard.KeyCodes.M, + KEY_N: Phaser.Input.Keyboard.KeyCodes.N, + KEY_O: Phaser.Input.Keyboard.KeyCodes.O, + KEY_P: Phaser.Input.Keyboard.KeyCodes.P, + KEY_Q: Phaser.Input.Keyboard.KeyCodes.Q, + KEY_R: Phaser.Input.Keyboard.KeyCodes.R, + KEY_S: Phaser.Input.Keyboard.KeyCodes.S, + KEY_T: Phaser.Input.Keyboard.KeyCodes.T, + KEY_U: Phaser.Input.Keyboard.KeyCodes.U, + KEY_V: Phaser.Input.Keyboard.KeyCodes.V, + KEY_W: Phaser.Input.Keyboard.KeyCodes.W, + KEY_X: Phaser.Input.Keyboard.KeyCodes.X, + KEY_Y: Phaser.Input.Keyboard.KeyCodes.Y, + KEY_Z: Phaser.Input.Keyboard.KeyCodes.Z, + KEY_0: Phaser.Input.Keyboard.KeyCodes.ZERO, + KEY_1: Phaser.Input.Keyboard.KeyCodes.ONE, + KEY_2: Phaser.Input.Keyboard.KeyCodes.TWO, + KEY_3: Phaser.Input.Keyboard.KeyCodes.THREE, + KEY_4: Phaser.Input.Keyboard.KeyCodes.FOUR, + KEY_5: Phaser.Input.Keyboard.KeyCodes.FIVE, + KEY_6: Phaser.Input.Keyboard.KeyCodes.SIX, + KEY_7: Phaser.Input.Keyboard.KeyCodes.SEVEN, + KEY_8: Phaser.Input.Keyboard.KeyCodes.EIGHT, + KEY_9: Phaser.Input.Keyboard.KeyCodes.NINE, + KEY_CTRL: Phaser.Input.Keyboard.KeyCodes.CTRL, + KEY_DEL: Phaser.Input.Keyboard.KeyCodes.DELETE, + KEY_END: Phaser.Input.Keyboard.KeyCodes.END, + KEY_ENTER: Phaser.Input.Keyboard.KeyCodes.ENTER, + KEY_ESC: Phaser.Input.Keyboard.KeyCodes.ESC, + KEY_F1: Phaser.Input.Keyboard.KeyCodes.F1, + KEY_F2: Phaser.Input.Keyboard.KeyCodes.F2, + KEY_F3: Phaser.Input.Keyboard.KeyCodes.F3, + KEY_F4: Phaser.Input.Keyboard.KeyCodes.F4, + KEY_F5: Phaser.Input.Keyboard.KeyCodes.F5, + KEY_F6: Phaser.Input.Keyboard.KeyCodes.F6, + KEY_F7: Phaser.Input.Keyboard.KeyCodes.F7, + KEY_F8: Phaser.Input.Keyboard.KeyCodes.F8, + KEY_F9: Phaser.Input.Keyboard.KeyCodes.F9, + KEY_F10: Phaser.Input.Keyboard.KeyCodes.F10, + KEY_F11: Phaser.Input.Keyboard.KeyCodes.F11, + KEY_F12: Phaser.Input.Keyboard.KeyCodes.F12, + KEY_HOME: Phaser.Input.Keyboard.KeyCodes.HOME, + KEY_INSERT: Phaser.Input.Keyboard.KeyCodes.INSERT, + KEY_PAGE_DOWN: Phaser.Input.Keyboard.KeyCodes.PAGE_DOWN, + KEY_PAGE_UP: Phaser.Input.Keyboard.KeyCodes.PAGE_UP, + KEY_PLUS: Phaser.Input.Keyboard.KeyCodes.NUMPAD_ADD, // Assuming numpad plus + KEY_MINUS: Phaser.Input.Keyboard.KeyCodes.NUMPAD_SUBTRACT, // Assuming numpad minus + KEY_QUOTATION: Phaser.Input.Keyboard.KeyCodes.QUOTES, + KEY_SHIFT: Phaser.Input.Keyboard.KeyCodes.SHIFT, + KEY_SPACE: Phaser.Input.Keyboard.KeyCodes.SPACE, + KEY_TAB: Phaser.Input.Keyboard.KeyCodes.TAB, + KEY_TILDE: Phaser.Input.Keyboard.KeyCodes.BACKTICK, + KEY_ARROW_UP: Phaser.Input.Keyboard.KeyCodes.UP, + KEY_ARROW_DOWN: Phaser.Input.Keyboard.KeyCodes.DOWN, + KEY_ARROW_LEFT: Phaser.Input.Keyboard.KeyCodes.LEFT, + KEY_ARROW_RIGHT: Phaser.Input.Keyboard.KeyCodes.RIGHT, + KEY_LEFT_BRACKET: Phaser.Input.Keyboard.KeyCodes.OPEN_BRACKET, + KEY_RIGHT_BRACKET: Phaser.Input.Keyboard.KeyCodes.CLOSED_BRACKET, + KEY_SEMICOLON: Phaser.Input.Keyboard.KeyCodes.SEMICOLON, + KEY_BACKSPACE: Phaser.Input.Keyboard.KeyCodes.BACKSPACE, + KEY_ALT: Phaser.Input.Keyboard.KeyCodes.ALT + }, + icons: { + KEY_A: "T_A_Key_Dark.png", + KEY_B: "T_B_Key_Dark.png", + KEY_C: "T_C_Key_Dark.png", + KEY_D: "T_D_Key_Dark.png", + KEY_E: "T_E_Key_Dark.png", + KEY_F: "T_F_Key_Dark.png", + KEY_G: "T_G_Key_Dark.png", + KEY_H: "T_H_Key_Dark.png", + KEY_I: "T_I_Key_Dark.png", + KEY_J: "T_J_Key_Dark.png", + KEY_K: "T_K_Key_Dark.png", + KEY_L: "T_L_Key_Dark.png", + KEY_M: "T_M_Key_Dark.png", + KEY_N: "T_N_Key_Dark.png", + KEY_O: "T_O_Key_Dark.png", + KEY_P: "T_P_Key_Dark.png", + KEY_Q: "T_Q_Key_Dark.png", + KEY_R: "T_R_Key_Dark.png", + KEY_S: "T_S_Key_Dark.png", + KEY_T: "T_T_Key_Dark.png", + KEY_U: "T_U_Key_Dark.png", + KEY_V: "T_V_Key_Dark.png", + KEY_W: "T_W_Key_Dark.png", + KEY_X: "T_X_Key_Dark.png", + KEY_Y: "T_Y_Key_Dark.png", + KEY_Z: "T_Z_Key_Dark.png", + + KEY_0: "T_0_Key_Dark.png", + KEY_1: "T_1_Key_Dark.png", + KEY_2: "T_2_Key_Dark.png", + KEY_3: "T_3_Key_Dark.png", + KEY_4: "T_4_Key_Dark.png", + KEY_5: "T_5_Key_Dark.png", + KEY_6: "T_6_Key_Dark.png", + KEY_7: "T_7_Key_Dark.png", + KEY_8: "T_8_Key_Dark.png", + KEY_9: "T_9_Key_Dark.png", + + KEY_F1: "T_F1_Key_Dark.png", + KEY_F2: "T_F2_Key_Dark.png", + KEY_F3: "T_F3_Key_Dark.png", + KEY_F4: "T_F4_Key_Dark.png", + KEY_F5: "T_F5_Key_Dark.png", + KEY_F6: "T_F6_Key_Dark.png", + KEY_F7: "T_F7_Key_Dark.png", + KEY_F8: "T_F8_Key_Dark.png", + KEY_F9: "T_F9_Key_Dark.png", + KEY_F10: "T_F10_Key_Dark.png", + KEY_F11: "T_F11_Key_Dark.png", + KEY_F12: "T_F12_Key_Dark.png", + + + KEY_PAGE_DOWN: "T_PageDown_Key_Dark.png", + KEY_PAGE_UP: "T_PageUp_Key_Dark.png", + + KEY_CTRL: "T_Crtl_Key_Dark.png", + KEY_DEL: "T_Del_Key_Dark.png", + KEY_END: "T_End_Key_Dark.png", + KEY_ENTER: "T_Enter_Alt_Key_Dark.png", + KEY_ESC: "T_Esc_Key_Dark.png", + KEY_HOME: "T_Home_Key_Dark.png", + KEY_INSERT: "T_Ins_Key_Dark.png", + + KEY_PLUS: "T_Plus_Tall_Key_Dark.png", + KEY_MINUS: "T_Minus_Key_Dark.png", + KEY_QUOTATION: "T_Quotation_Key_Dark.png", + KEY_SHIFT: "T_Shift_Key_Dark.png", + + KEY_SPACE: "T_Space_Key_Dark.png", + KEY_TAB: "T_Tab_Key_Dark.png", + KEY_TILDE: "T_Tilde_Key_Dark.png", + + KEY_ARROW_UP: "T_Up_Key_Dark.png", + KEY_ARROW_DOWN: "T_Down_Key_Dark.png", + KEY_ARROW_LEFT: "T_Left_Key_Dark.png", + KEY_ARROW_RIGHT: "T_Right_Key_Dark.png", + + KEY_LEFT_BRACKET: "T_Brackets_L_Key_Dark.png", + KEY_RIGHT_BRACKET: "T_Brackets_R_Key_Dark.png", + + KEY_SEMICOLON: "T_Semicolon_Key_Dark.png", + + KEY_BACKSPACE: "T_Backspace_Alt_Key_Dark.png", + KEY_ALT: "T_Alt_Key_Dark.png" + }, + settings: { + [SettingInterface.Button_Up]: Button.UP, + [SettingInterface.Button_Down]: Button.DOWN, + [SettingInterface.Button_Left]: Button.LEFT, + [SettingInterface.Button_Right]: Button.RIGHT, + [SettingInterface.Button_Submit]: Button.SUBMIT, + [SettingInterface.Button_Action]: Button.ACTION, + [SettingInterface.Button_Cancel]: Button.CANCEL, + [SettingInterface.Button_Menu]: Button.MENU, + [SettingInterface.Button_Stats]: Button.STATS, + [SettingInterface.Button_Cycle_Shiny]: Button.CYCLE_SHINY, + [SettingInterface.Button_Cycle_Form]: Button.CYCLE_FORM, + [SettingInterface.Button_Cycle_Gender]: Button.CYCLE_GENDER, + [SettingInterface.Button_Cycle_Ability]: Button.CYCLE_ABILITY, + [SettingInterface.Button_Cycle_Nature]: Button.CYCLE_NATURE, + [SettingInterface.Button_Cycle_Variant]: Button.CYCLE_VARIANT, + [SettingInterface.Button_Speed_Up]: Button.SPEED_UP, + [SettingInterface.Button_Slow_Down]: Button.SLOW_DOWN, + [SettingInterface.Alt_Button_Up]: Button.UP, + [SettingInterface.Alt_Button_Down]: Button.DOWN, + [SettingInterface.Alt_Button_Left]: Button.LEFT, + [SettingInterface.Alt_Button_Right]: Button.RIGHT, + [SettingInterface.Alt_Button_Submit]: Button.SUBMIT, + [SettingInterface.Alt_Button_Action]: Button.ACTION, + [SettingInterface.Alt_Button_Cancel]: Button.CANCEL, + [SettingInterface.Alt_Button_Menu]: Button.MENU, + [SettingInterface.Alt_Button_Stats]: Button.STATS, + [SettingInterface.Alt_Button_Cycle_Shiny]: Button.CYCLE_SHINY, + [SettingInterface.Alt_Button_Cycle_Form]: Button.CYCLE_FORM, + [SettingInterface.Alt_Button_Cycle_Gender]: Button.CYCLE_GENDER, + [SettingInterface.Alt_Button_Cycle_Ability]: Button.CYCLE_ABILITY, + [SettingInterface.Alt_Button_Cycle_Nature]: Button.CYCLE_NATURE, + [SettingInterface.Alt_Button_Cycle_Variant]: Button.CYCLE_VARIANT, + [SettingInterface.Alt_Button_Speed_Up]: Button.SPEED_UP, + [SettingInterface.Alt_Button_Slow_Down]: Button.SLOW_DOWN, + }, + default: { + KEY_ARROW_UP: SettingInterface.Button_Up, + KEY_ARROW_DOWN: SettingInterface.Button_Down, + KEY_ARROW_LEFT: SettingInterface.Button_Left, + KEY_ARROW_RIGHT: SettingInterface.Button_Right, + KEY_ENTER: SettingInterface.Button_Submit, + KEY_SPACE: SettingInterface.Button_Action, + KEY_BACKSPACE: SettingInterface.Button_Cancel, + KEY_ESC: SettingInterface.Button_Menu, + KEY_C: SettingInterface.Button_Stats, + KEY_R: SettingInterface.Button_Cycle_Shiny, + KEY_F: SettingInterface.Button_Cycle_Form, + KEY_G: SettingInterface.Button_Cycle_Gender, + KEY_E: SettingInterface.Button_Cycle_Ability, + KEY_N: SettingInterface.Button_Cycle_Nature, + KEY_V: SettingInterface.Button_Cycle_Variant, + KEY_PLUS: SettingInterface.Button_Speed_Up, + KEY_MINUS: SettingInterface.Button_Slow_Down, + KEY_A: -1, + KEY_B: -1, + KEY_D: SettingInterface.Alt_Button_Right, + KEY_H: -1, + KEY_I: SettingInterface.Alt_Button_Cycle_Nature, + KEY_J: -1, + KEY_K: SettingInterface.Alt_Button_Cycle_Variant, + KEY_L: SettingInterface.Alt_Button_Cycle_Ability, + KEY_M: SettingInterface.Alt_Button_Cycle_Form, + KEY_O: SettingInterface.Alt_Button_Cycle_Gender, + KEY_P: SettingInterface.Alt_Button_Cycle_Shiny, + KEY_Q: SettingInterface.Alt_Button_Left, + KEY_S: SettingInterface.Alt_Button_Down, + KEY_T: -1, + KEY_U: -1, + KEY_W: SettingInterface.Alt_Button_Action, + KEY_X: SettingInterface.Alt_Button_Cancel, + KEY_Y: -1, + KEY_Z: SettingInterface.Alt_Button_Up, + KEY_0: -1, + KEY_1: -1, + KEY_2: -1, + KEY_3: -1, + KEY_4: -1, + KEY_5: -1, + KEY_6: -1, + KEY_7: -1, + KEY_8: -1, + KEY_9: -1, + KEY_CTRL: SettingInterface.Alt_Button_Submit, + KEY_DEL: -1, + KEY_END: -1, + KEY_F1: -1, + KEY_F2: -1, + KEY_F3: -1, + KEY_F4: -1, + KEY_F5: -1, + KEY_F6: -1, + KEY_F7: -1, + KEY_F8: -1, + KEY_F9: -1, + KEY_F10: -1, + KEY_F11: -1, + KEY_F12: -1, + KEY_HOME: -1, + KEY_INSERT: -1, + KEY_PAGE_DOWN: SettingInterface.Alt_Button_Slow_Down, + KEY_PAGE_UP: SettingInterface.Alt_Button_Speed_Up, + KEY_QUOTATION: -1, + KEY_SHIFT: SettingInterface.Alt_Button_Stats, + KEY_TAB: SettingInterface.Alt_Button_Menu, + KEY_TILDE: -1, + KEY_LEFT_BRACKET: -1, + KEY_RIGHT_BRACKET: -1, + KEY_SEMICOLON: -1, + KEY_ALT: -1 + }, + main: [], + alt: [], + blacklist: [ + "KEY_ENTER", + "KEY_ESC", + "KEY_ARROW_UP", + "KEY_ARROW_DOWN", + "KEY_ARROW_LEFT", + "KEY_ARROW_RIGHT", + "KEY_DEL", + "KEY_HOME", + ] +}; + +export default cfg_keyboard_azerty; diff --git a/src/test/helpers/inGameManip.ts b/src/test/helpers/inGameManip.ts new file mode 100644 index 00000000000..0d345ba93ca --- /dev/null +++ b/src/test/helpers/inGameManip.ts @@ -0,0 +1,73 @@ +import { + getIconForLatestInput, + getSettingNameWithKeycode +} from "#app/configs/configHandler"; +import {expect} from "vitest"; +import {SettingInterface} from "#app/test/cfg_keyboard.example"; + +export class InGameManip { + private config; + private keycode; + private settingName; + private icon; + private configs; + private latestSource; + private selectedDevice; + + constructor(configs, config, selectedDevice) { + this.config = config; + this.configs = configs; + this.selectedDevice = selectedDevice; + this.keycode = null; + this.settingName = null; + this.icon = null; + this.latestSource = null; + } + + whenWePressOnKeyboard(keycode) { + this.keycode = Phaser.Input.Keyboard.KeyCodes[keycode.toUpperCase()]; + return this; + } + + nothingShouldHappen() { + const settingName = getSettingNameWithKeycode(this.config, this.keycode); + expect(settingName).toEqual(-1); + return this; + } + + forTheWantedBind(settingName) { + if (!settingName.includes("Button_")) settingName = "Button_" + settingName; + this.settingName = SettingInterface[settingName]; + return this; + } + + weShouldSeeTheIcon(icon) { + if (!icon.includes("KEY_")) icon = "KEY_" + icon; + this.icon = this.config.icons[icon]; + expect(getIconForLatestInput(this.configs, this.latestSource, this.selectedDevice, this.settingName)).toEqual(this.icon); + return this; + } + + forTheSource(source) { + this.latestSource = source; + return this; + } + + normalizeSettingNameString(input) { + // Convert the input string to lower case + const lowerCasedInput = input.toLowerCase(); + + // Replace underscores with spaces, capitalize the first letter of each word, and join them back with underscores + const words = lowerCasedInput.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1)); + const result = words.join('_'); + + return result; + } + + weShouldTriggerTheButton(settingName) { + if (!settingName.includes("Button_")) settingName = "Button_" + settingName; + this.settingName = SettingInterface[this.normalizeSettingNameString(settingName)]; + expect(getSettingNameWithKeycode(this.config, this.keycode)).toEqual(this.settingName); + return this; + } +} diff --git a/src/test/helpers/menuManip.ts b/src/test/helpers/menuManip.ts new file mode 100644 index 00000000000..6b86f4828d3 --- /dev/null +++ b/src/test/helpers/menuManip.ts @@ -0,0 +1,131 @@ +import {SettingInterface} from "#app/test/cfg_keyboard.example"; +import {expect} from "vitest"; +import {Button} from "#app/enums/buttons"; +import { + deleteBind, + getIconWithKeycode, + getIconWithSettingName, + getKeyWithKeycode, + getKeyWithSettingName, + assign, + getSettingNameWithKeycode, canIAssignThisKey, canIDeleteThisKey, canIOverrideThisSetting +} from "#app/configs/configHandler"; + +export class MenuManip { + private config; + private settingName; + private keycode; + private icon; + private iconDisplayed; + private specialCaseIcon; + + constructor(config) { + this.config = config; + this.settingName = null; + this.icon = null; + this.iconDisplayed = null; + this.specialCaseIcon = null; + } + + convertNameToButtonString(input) { + // Check if the input starts with "Alt_Button" + if (input.startsWith("Alt_Button")) { + // Return the last part in uppercase + return input.split('_').pop().toUpperCase(); + } + + // Split the input string by underscore + const parts = input.split('_'); + + // Skip the first part and join the rest with an underscore + const result = parts.slice(1).map(part => part.toUpperCase()).join('_'); + + return result; + } + + whenCursorIsOnSetting(settingName) { + if (!settingName.includes("Button_")) settingName = "Button_" + settingName; + this.settingName = SettingInterface[settingName]; + const isAlt = settingName.includes("ALT_"); + const buttonName = isAlt ? settingName.toUpperCase().split("ALT_BUTTON_").splice(1)[0] : settingName.toUpperCase().split("BUTTON_").splice(1)[0]; + expect(this.config.settings[this.settingName]).toEqual(Button[buttonName]); + return this; + } + + iconDisplayedIs(icon) { + if (!(icon.toUpperCase().includes("KEY_"))) icon = "KEY_" + icon.toUpperCase(); + this.iconDisplayed = this.config.icons[icon]; + expect(getIconWithSettingName(this.config, this.settingName)).toEqual(this.iconDisplayed); + return this; + } + + thereShouldBeNoIconAnymore() { + const icon = getIconWithSettingName(this.config, this.settingName); + expect(icon === undefined).toEqual(true); + return this; + } + + thereShouldBeNoIcon() { + return this.thereShouldBeNoIconAnymore(); + } + + nothingShouldHappen() { + const settingName = getSettingNameWithKeycode(this.config, this.keycode); + expect(settingName).toEqual(-1); + return this; + } + + weWantThisBindInstead(keycode) { + this.keycode = Phaser.Input.Keyboard.KeyCodes[keycode]; + const icon = getIconWithKeycode(this.config, this.keycode); + const key = getKeyWithKeycode(this.config, this.keycode); + const _keys = key.toLowerCase().split("_"); + const iconIdentifier = _keys[_keys.length-1]; + expect(icon.toLowerCase().includes(iconIdentifier)).toEqual(true); + return this; + } + + whenWeDelete(settingName?: string) { + this.settingName = SettingInterface[settingName] || this.settingName; + const key = getKeyWithSettingName(this.config, this.settingName); + deleteBind(this.config, this.settingName); + // expect(this.config.custom[key]).toEqual(-1); + return this; + } + + whenWeTryToDelete(settingName?: string) { + this.settingName = SettingInterface[settingName] || this.settingName; + deleteBind(this.config, this.settingName); + return this; + } + + confirmAssignment() { + assign(this.config, this.settingName, this.keycode); + } + + butLetsForceIt() { + this.confirm(); + } + + + confirm() { + assign(this.config, this.settingName, this.keycode); + } + + weCantAssignThisKey() { + const key = getKeyWithKeycode(this.config, this.keycode); + expect(canIAssignThisKey(this.config, key)).toEqual(false); + return this; + } + + weCantOverrideThisBind() { + expect(canIOverrideThisSetting(this.config, this.settingName)).toEqual(false); + return this; + } + + weCantDelete() { + const key = getKeyWithSettingName(this.config, this.settingName); + expect(canIDeleteThisKey(this.config, key)).toEqual(false); + return this; + } +} diff --git a/src/test/rebinding_setting.test.ts b/src/test/rebinding_setting.test.ts new file mode 100644 index 00000000000..5c29f66727f --- /dev/null +++ b/src/test/rebinding_setting.test.ts @@ -0,0 +1,417 @@ +import {afterEach, beforeEach, describe, expect, it} from "vitest"; +import cfg_keyboard_azerty from "#app/test/cfg_keyboard.example"; +import {SettingInterface} from "#app/test/cfg_keyboard.example"; +import {Button} from "#app/enums/buttons"; +import {deepCopy} from "#app/utils"; +import { + getKeyWithKeycode, + getKeyWithSettingName, +} from "#app/configs/configHandler"; +import {MenuManip} from "#app/test/helpers/menuManip"; +import {InGameManip} from "#app/test/helpers/inGameManip"; +import {Device} from "#app/enums/devices"; +import {InterfaceConfig} from "#app/inputs-controller"; + + +describe('Test Rebinding', () => { + let config; + let inGame; + let inTheSettingMenu; + const configs: Map = new Map(); + const selectedDevice = { + [Device.GAMEPAD]: null, + [Device.KEYBOARD]: 'default', + } + + beforeEach(() => { + config = deepCopy(cfg_keyboard_azerty); + config.custom = {...config.default} + configs.default = config; + inGame = new InGameManip(configs, config, selectedDevice); + inTheSettingMenu = new MenuManip(config); + }); + + it('Check if config is loaded', () => { + expect(config).not.toBeNull(); + }); + it('Check button for setting name', () => { + const settingName = SettingInterface.Button_Left; + const button = config.settings[settingName]; + expect(button).toEqual(Button.LEFT); + }); + it('Check key for Keyboard KeyCode', () => { + const key = getKeyWithKeycode(config, Phaser.Input.Keyboard.KeyCodes.LEFT); + const settingName = config.custom[key]; + const button = config.settings[settingName]; + expect(button).toEqual(Button.LEFT); + }); + it('Check key for currenly Assigned to action not alt', () => { + const key = getKeyWithKeycode(config, Phaser.Input.Keyboard.KeyCodes.Q); + const settingName = config.custom[key]; + const button = config.settings[settingName]; + expect(button).toEqual(Button.LEFT); + }); + + it('Check key for currenly Assigned to setting name', () => { + const settingName = SettingInterface.Button_Left; + const key = getKeyWithSettingName(config, settingName); + expect(key).toEqual('KEY_ARROW_LEFT'); + }); + it('Check key for currenly Assigned to setting name alt', () => { + const settingName = SettingInterface.Alt_Button_Left; + const key = getKeyWithSettingName(config, settingName); + expect(key).toEqual('KEY_Q'); + }); + it('Check key from key code', () => { + const keycode = Phaser.Input.Keyboard.KeyCodes.LEFT; + const key = getKeyWithKeycode(config, keycode); + expect(key).toEqual('KEY_ARROW_LEFT'); + }); + it('Check icon for currenly Assigned to key code', () => { + const keycode = Phaser.Input.Keyboard.KeyCodes.LEFT; + const key = getKeyWithKeycode(config, keycode); + const icon = config.icons[key]; + expect(icon).toEqual('T_Left_Key_Dark.png'); + }); + it('Check icon for currenly Assigned to key code', () => { + const keycode = Phaser.Input.Keyboard.KeyCodes.Q; + const key = getKeyWithKeycode(config, keycode); + const icon = config.icons[key]; + expect(icon).toEqual('T_Q_Key_Dark.png'); + }); + it('Check icon for currenly Assigned to setting name', () => { + const settingName = SettingInterface.Button_Left; + const key = getKeyWithSettingName(config, settingName); + const icon = config.icons[key]; + expect(icon).toEqual('T_Left_Key_Dark.png'); + }); + it('Check icon for currenly Assigned to setting name alt', () => { + const settingName = SettingInterface.Alt_Button_Left; + const key = getKeyWithSettingName(config, settingName); + const icon = config.icons[key]; + expect(icon).toEqual('T_Q_Key_Dark.png'); + }); + + it('Check if is working', () => { + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("Q") + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Right").iconDisplayedIs("D") + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("Q").weWantThisBindInstead("D").confirm(); + inGame.whenWePressOnKeyboard("Q").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Left"); + }); + + it('Check prevent rebind indirectly the d-pad buttons', () => { + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("Q") + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Right").iconDisplayedIs("D") + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("Q").weWantThisBindInstead("LEFT").weCantAssignThisKey().butLetsForceIt(); + inGame.whenWePressOnKeyboard("LEFT").weShouldTriggerTheButton("Left"); + inGame.whenWePressOnKeyboard("Q").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right"); + }); + + it('Swap alt with a d-pad main', () => { + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + inGame.whenWePressOnKeyboard("Z").weShouldTriggerTheButton("Alt_Button_Up"); + inTheSettingMenu.whenCursorIsOnSetting("Button_Up").iconDisplayedIs("KEY_ARROW_UP").weWantThisBindInstead("Z").weCantOverrideThisBind().butLetsForceIt(); + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + inGame.whenWePressOnKeyboard("Z").weShouldTriggerTheButton("Alt_Button_Up"); + }); + + it('Check if double assign d-pad is blocked', () => { + inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right"); + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + + inTheSettingMenu.whenCursorIsOnSetting("Button_Left").iconDisplayedIs("KEY_ARROW_LEFT").weWantThisBindInstead("RIGHT").weCantOverrideThisBind().weCantAssignThisKey().butLetsForceIt(); + + inGame.whenWePressOnKeyboard("LEFT").weShouldTriggerTheButton("Button_Left"); + inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right"); + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + + inTheSettingMenu.whenCursorIsOnSetting("Button_Left").iconDisplayedIs("KEY_ARROW_LEFT").weWantThisBindInstead("UP").weCantOverrideThisBind().weCantAssignThisKey().butLetsForceIt(); + + inGame.whenWePressOnKeyboard("LEFT").weShouldTriggerTheButton("Button_Left"); + inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right"); + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + + inTheSettingMenu.whenCursorIsOnSetting("Button_Left").iconDisplayedIs("KEY_ARROW_LEFT").weWantThisBindInstead("RIGHT").weCantOverrideThisBind().weCantAssignThisKey().butLetsForceIt(); + + inGame.whenWePressOnKeyboard("LEFT").weShouldTriggerTheButton("Button_Left"); + inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right"); + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + }); + + it('Check if double assign is working', () => { + inGame.whenWePressOnKeyboard("Q").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right"); + inGame.whenWePressOnKeyboard("Z").weShouldTriggerTheButton("Alt_Button_Up"); + + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("KEY_Q").weWantThisBindInstead("D").confirm(); + + inGame.whenWePressOnKeyboard("Q").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("Z").weShouldTriggerTheButton("Alt_Button_Up"); + + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("KEY_D").weWantThisBindInstead("Z").confirm(); + + inGame.whenWePressOnKeyboard("Q").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("D").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("Z").weShouldTriggerTheButton("Alt_Button_Left"); + + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("KEY_Z").weWantThisBindInstead("D").confirm(); + + inGame.whenWePressOnKeyboard("Q").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("Z").nothingShouldHappen(); + }); + + it('Check if triple swap d-pad is prevented', () => { + inTheSettingMenu.whenCursorIsOnSetting("Button_Left").iconDisplayedIs("KEY_ARROW_LEFT").weWantThisBindInstead("RIGHT").weCantOverrideThisBind().weCantAssignThisKey().butLetsForceIt(); + + inGame.whenWePressOnKeyboard("LEFT").weShouldTriggerTheButton("Button_Left"); + inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right"); + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + + inTheSettingMenu.whenCursorIsOnSetting("Button_Right").iconDisplayedIs("KEY_ARROW_RIGHT").weWantThisBindInstead("UP").weCantOverrideThisBind().weCantAssignThisKey().butLetsForceIt(); + inGame.whenWePressOnKeyboard("LEFT").weShouldTriggerTheButton("Button_Left"); + inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right"); + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + + inTheSettingMenu.whenCursorIsOnSetting("Button_Left").iconDisplayedIs("KEY_ARROW_LEFT").weWantThisBindInstead("LEFT").weCantOverrideThisBind().weCantAssignThisKey().butLetsForceIt(); + inGame.whenWePressOnKeyboard("LEFT").weShouldTriggerTheButton("Button_Left"); + inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right"); + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + }); + + it('Check if triple swap is working', () => { + inGame.whenWePressOnKeyboard("Q").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right"); + inGame.whenWePressOnKeyboard("Z").weShouldTriggerTheButton("Alt_Button_Up"); + + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("KEY_Q").weWantThisBindInstead("D").confirm(); + + inGame.whenWePressOnKeyboard("Q").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("Z").weShouldTriggerTheButton("Alt_Button_Up"); + + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Right").thereShouldBeNoIcon().weWantThisBindInstead("Z").confirm(); + + inGame.whenWePressOnKeyboard("Q").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("Z").weShouldTriggerTheButton("Alt_Button_Right"); + + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("KEY_D").weWantThisBindInstead("Q").confirm(); + + inGame.whenWePressOnKeyboard("Q").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("D").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("Z").weShouldTriggerTheButton("Alt_Button_Right"); + }); + + it('Swap alt with a main', () => { + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right"); + inGame.whenWePressOnKeyboard("R").weShouldTriggerTheButton("Cycle_Shiny"); + inTheSettingMenu.whenCursorIsOnSetting("Cycle_Shiny").iconDisplayedIs("KEY_R").weWantThisBindInstead("D").confirm(); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Cycle_Shiny"); + inGame.whenWePressOnKeyboard("R").nothingShouldHappen(); + }); + + it('multiple Swap alt with another main', () => { + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right"); + inGame.whenWePressOnKeyboard("R").weShouldTriggerTheButton("Button_Cycle_Shiny"); + inGame.whenWePressOnKeyboard("Q").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("F").weShouldTriggerTheButton("Button_Cycle_Form"); + inTheSettingMenu.whenCursorIsOnSetting("Button_Cycle_Shiny").iconDisplayedIs("KEY_R").weWantThisBindInstead("D").confirm(); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Button_Cycle_Shiny"); + inGame.whenWePressOnKeyboard("R").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("Q").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("F").weShouldTriggerTheButton("Button_Cycle_Form"); + inTheSettingMenu.whenCursorIsOnSetting("Button_Cycle_Form").iconDisplayedIs("KEY_F").weWantThisBindInstead("R").confirm(); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Button_Cycle_Shiny"); + inGame.whenWePressOnKeyboard("R").weShouldTriggerTheButton("Button_Cycle_Form"); + inGame.whenWePressOnKeyboard("Q").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("F").nothingShouldHappen(); + }); + + it('Swap alt with a key not binded yet', () => { + inGame.whenWePressOnKeyboard("Z").weShouldTriggerTheButton("Alt_Button_Up"); + inGame.whenWePressOnKeyboard("B").nothingShouldHappen(); + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Up").iconDisplayedIs("KEY_Z").weWantThisBindInstead("B").confirm(); + inGame.whenWePressOnKeyboard("Z").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("B").weShouldTriggerTheButton("Alt_Button_Up"); + }); + + it('Delete blacklisted bind', () => { + inGame.whenWePressOnKeyboard("LEFT").weShouldTriggerTheButton("Button_Left"); + inTheSettingMenu.whenWeDelete("Button_Left").weCantDelete().iconDisplayedIs("KEY_ARROW_LEFT"); + inGame.whenWePressOnKeyboard("LEFT").weShouldTriggerTheButton("Button_Left"); + }); + + it('Delete bind', () => { + inGame.whenWePressOnKeyboard("Q").weShouldTriggerTheButton("Alt_Button_Left"); + inTheSettingMenu.whenWeDelete("Alt_Button_Left").thereShouldBeNoIconAnymore(); + inGame.whenWePressOnKeyboard("Q").nothingShouldHappen(); + }); + + it('Delete bind then assign a not yet binded button', () => { + inTheSettingMenu.whenWeDelete("Alt_Button_Left").thereShouldBeNoIconAnymore(); + inGame.whenWePressOnKeyboard("Q").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("B").nothingShouldHappen(); + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").thereShouldBeNoIcon().weWantThisBindInstead("B").confirm(); + inGame.whenWePressOnKeyboard("Q").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("B").weShouldTriggerTheButton("Alt_Button_Left"); + }) + it('swap 2 bind, than delete 1 bind than assign another bind', () => { + inGame.whenWePressOnKeyboard("R").weShouldTriggerTheButton("Button_Cycle_Shiny"); + inGame.whenWePressOnKeyboard("F").weShouldTriggerTheButton("Button_Cycle_Form"); + inGame.whenWePressOnKeyboard("Z").weShouldTriggerTheButton("Alt_Button_Up"); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right"); + inTheSettingMenu.whenCursorIsOnSetting("Button_Cycle_Shiny").iconDisplayedIs("KEY_R").weWantThisBindInstead("D").confirm(); + inGame.whenWePressOnKeyboard("R").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("F").weShouldTriggerTheButton("Button_Cycle_Form"); + inGame.whenWePressOnKeyboard("Z").weShouldTriggerTheButton("Alt_Button_Up"); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Button_Cycle_Shiny"); + + inTheSettingMenu.whenCursorIsOnSetting("Button_Cycle_Form").iconDisplayedIs("KEY_F").weWantThisBindInstead("Z").confirm(); + inGame.whenWePressOnKeyboard("R").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("F").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("Z").weShouldTriggerTheButton("Button_Cycle_Form"); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Button_Cycle_Shiny"); + inGame.whenWePressOnKeyboard("Q").weShouldTriggerTheButton("Alt_Button_Left"); + + inTheSettingMenu.whenWeDelete("Alt_Button_Left").thereShouldBeNoIconAnymore(); + inGame.whenWePressOnKeyboard("R").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("F").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("Z").weShouldTriggerTheButton("Button_Cycle_Form"); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Button_Cycle_Shiny"); + inGame.whenWePressOnKeyboard("S").weShouldTriggerTheButton("Alt_Button_Down"); + inGame.whenWePressOnKeyboard("Q").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("B").nothingShouldHappen(); + + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Down").iconDisplayedIs("KEY_S").weWantThisBindInstead("B").confirm(); + inGame.whenWePressOnKeyboard("R").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("F").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("Z").weShouldTriggerTheButton("Button_Cycle_Form"); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Button_Cycle_Shiny"); + inGame.whenWePressOnKeyboard("S").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("Q").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("B").weShouldTriggerTheButton("Alt_Button_Down"); + }); + + + it('Delete bind then assign not already existing button', () => { + + inGame.whenWePressOnKeyboard("Q").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("B").nothingShouldHappen(); + + inTheSettingMenu.whenWeDelete("Alt_Button_Left").thereShouldBeNoIconAnymore(); + inGame.whenWePressOnKeyboard("Q").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("B").nothingShouldHappen(); + + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").thereShouldBeNoIcon().weWantThisBindInstead("B").confirm(); + inGame.whenWePressOnKeyboard("Q").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("B").weShouldTriggerTheButton("Alt_Button_Left"); + }); + + + it('change alt bind to not already existing button, than another one alt bind with another not already existing button', () => { + inGame.whenWePressOnKeyboard("Q").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right"); + inGame.whenWePressOnKeyboard("B").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("U").nothingShouldHappen(); + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("KEY_Q").weWantThisBindInstead("B").confirm(); + inGame.whenWePressOnKeyboard("Q").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right"); + inGame.whenWePressOnKeyboard("B").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("U").nothingShouldHappen(); + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Right").iconDisplayedIs("KEY_D").weWantThisBindInstead("U").confirm(); + inGame.whenWePressOnKeyboard("Q").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("D").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("B").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("U").weShouldTriggerTheButton("Alt_Button_Right"); + }); + + it('Swap multiple touch alt and main', () => { + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right"); + inGame.whenWePressOnKeyboard("Z").weShouldTriggerTheButton("Alt_Button_Up"); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right"); + inTheSettingMenu.whenCursorIsOnSetting("Button_Up").iconDisplayedIs("KEY_ARROW_UP").weWantThisBindInstead("RIGHT").weCantOverrideThisBind().weCantAssignThisKey().butLetsForceIt(); + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right"); + inGame.whenWePressOnKeyboard("Z").weShouldTriggerTheButton("Alt_Button_Up"); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right"); + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Up").iconDisplayedIs("KEY_Z").weWantThisBindInstead("D").confirm(); + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right"); + inGame.whenWePressOnKeyboard("Z").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Up"); + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Up").iconDisplayedIs("KEY_D").weWantThisBindInstead("Z").confirm(); + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right"); + inGame.whenWePressOnKeyboard("Z").weShouldTriggerTheButton("Alt_Button_Up"); + inGame.whenWePressOnKeyboard("D").nothingShouldHappen(); + }); + + + it('Delete 2 bind then reassign one of them', () => { + + inGame.whenWePressOnKeyboard("Q").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right"); + + inTheSettingMenu.whenWeDelete("Alt_Button_Left").thereShouldBeNoIconAnymore(); + inGame.whenWePressOnKeyboard("Q").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right"); + + inTheSettingMenu.whenWeDelete("Alt_Button_Right").thereShouldBeNoIconAnymore(); + inGame.whenWePressOnKeyboard("Q").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("D").nothingShouldHappen(); + + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").thereShouldBeNoIcon().weWantThisBindInstead("Q").confirm(); + inGame.whenWePressOnKeyboard("Q").weShouldTriggerTheButton("Alt_Button_Left"); + inGame.whenWePressOnKeyboard("D").nothingShouldHappen(); + }); + + it("test keyboard listener", () => { + const keyDown = Phaser.Input.Keyboard.KeyCodes.S; + const key = getKeyWithKeycode(config, keyDown); + const settingName = config.custom[key]; + const buttonDown = config.settings[settingName]; + expect(buttonDown).toEqual(Button.DOWN); + }); + + it("retrieve the correct icon for a given source", () => { + inTheSettingMenu.whenCursorIsOnSetting("Cycle_Shiny").iconDisplayedIs("KEY_R"); + inTheSettingMenu.whenCursorIsOnSetting("Cycle_Form").iconDisplayedIs("KEY_F"); + inGame.forTheSource("keyboard").forTheWantedBind("Cycle_Shiny").weShouldSeeTheIcon("R") + inGame.forTheSource("keyboard").forTheWantedBind("Cycle_Form").weShouldSeeTheIcon("F") + }); + + it("check the key displayed on confirm", () => { + inGame.whenWePressOnKeyboard("ENTER").weShouldTriggerTheButton("Button_Submit"); + inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up"); + inGame.whenWePressOnKeyboard("DOWN").weShouldTriggerTheButton("Button_Down"); + inGame.whenWePressOnKeyboard("LEFT").weShouldTriggerTheButton("Button_Left"); + inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right"); + inGame.whenWePressOnKeyboard("ESC").weShouldTriggerTheButton("Button_Menu"); + inGame.whenWePressOnKeyboard("HOME").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("DELETE").nothingShouldHappen(); + inTheSettingMenu.whenCursorIsOnSetting("Button_Submit").iconDisplayedIs("KEY_ENTER").whenWeDelete().iconDisplayedIs("KEY_ENTER") + inTheSettingMenu.whenCursorIsOnSetting("Button_Up").iconDisplayedIs("KEY_ARROW_UP").whenWeDelete().iconDisplayedIs("KEY_ARROW_UP") + inTheSettingMenu.whenCursorIsOnSetting("Button_Down").iconDisplayedIs("KEY_ARROW_DOWN").whenWeDelete().iconDisplayedIs("KEY_ARROW_DOWN") + inTheSettingMenu.whenCursorIsOnSetting("Button_Left").iconDisplayedIs("KEY_ARROW_LEFT").whenWeDelete().iconDisplayedIs("KEY_ARROW_LEFT") + inTheSettingMenu.whenCursorIsOnSetting("Button_Right").iconDisplayedIs("KEY_ARROW_RIGHT").whenWeDelete().iconDisplayedIs("KEY_ARROW_RIGHT") + inTheSettingMenu.whenCursorIsOnSetting("Button_Menu").iconDisplayedIs("KEY_ESC").whenWeDelete().iconDisplayedIs("KEY_ESC") + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Up").iconDisplayedIs("KEY_Z").whenWeDelete().thereShouldBeNoIconAnymore(); + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Up").thereShouldBeNoIcon().weWantThisBindInstead("DELETE").weCantAssignThisKey().butLetsForceIt(); + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Up").thereShouldBeNoIcon().weWantThisBindInstead("HOME").weCantAssignThisKey().butLetsForceIt(); + inGame.whenWePressOnKeyboard("DELETE").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("HOME").nothingShouldHappen(); + inGame.whenWePressOnKeyboard("Z").nothingShouldHappen(); + }); + + it("check to delete all the binds of an action", () => { + inGame.whenWePressOnKeyboard("V").weShouldTriggerTheButton("Button_Cycle_Variant"); + inGame.whenWePressOnKeyboard("K").weShouldTriggerTheButton("Alt_Button_Cycle_Variant"); + inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Cycle_Variant").iconDisplayedIs("KEY_K").whenWeDelete().thereShouldBeNoIconAnymore(); + inTheSettingMenu.whenCursorIsOnSetting("Button_Cycle_Variant").iconDisplayedIs("KEY_V").whenWeDelete().iconDisplayedIs("KEY_V") + }); +}); diff --git a/src/touch-controls.js b/src/touch-controls.js index d3e8e37abdf..13ab8d0d55f 100644 --- a/src/touch-controls.js +++ b/src/touch-controls.js @@ -1,21 +1,43 @@ +import {Button} from "./enums/buttons"; + +// Create a map to store key bindings export const keys = new Map(); +// Create a map to store keys that are currently pressed export const keysDown = new Map(); +// Variable to store the ID of the last touched element let lastTouchedId; -export function initTouchControls(buttonMap) { +/** + * Initialize touch controls by binding keys to buttons. + * + * @param events - The event emitter for handling input events. + */ +export function initTouchControls(events) { + // Select all elements with the 'data-key' attribute and bind keys to them for (const button of document.querySelectorAll('[data-key]')) { - // @ts-ignore - bindKey(button, button.dataset.key, buttonMap); + // @ts-ignore - Bind the key to the button using the dataset key + bindKey(button, button.dataset.key, events); } } +/** + * Check if the device has a touchscreen. + * + * @returns `true` if the device has a touchscreen, otherwise `false`. + */ export function hasTouchscreen() { return window.matchMedia('(hover: none), (pointer: coarse)').matches; } +/** + * Check if the device is a mobile device. + * + * @returns `true` if the device is a mobile device, otherwise `false`. + */ export function isMobile() { let ret = false; (function (a) { + // Check the user agent string against a regex for mobile devices if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) ret = true; })(navigator.userAgent || navigator.vendor || window['opera']); @@ -23,51 +45,55 @@ export function isMobile() { } /** - * Simulate a keyboard event on the canvas + * Simulates a keyboard event on the canvas. * - * @param {string} eventType Type of the keyboard event - * @param {string} button Button to simulate - * @param {object} buttonMap Map of buttons to key objects + * @param eventType - The type of the keyboard event ('keydown' or 'keyup'). + * @param key - The key to simulate. + * @param events - The event emitter for handling input events. + * + * @remarks + * This function checks if the key exists in the Button enum. If it does, it retrieves the corresponding button + * and emits the appropriate event ('input_down' or 'input_up') based on the event type. */ -function simulateKeyboardEvent(eventType, button, buttonMap) { - const key = buttonMap[button]; +function simulateKeyboardEvent(eventType, key, events) { + if (!Button.hasOwnProperty(key)) return; + const button = Button[key]; switch (eventType) { case 'keydown': - key.onDown({}); + events.emit('input_down', { + controller_type: 'keyboard', + button: button, + }); break; case 'keyup': - key.onUp({}); + events.emit('input_up', { + controller_type: 'keyboard', + button: button, + }); break; } } /** - * Simulate a keyboard input from 'keydown' to 'keyup' + * Binds a node to a specific key to simulate keyboard events on touch. * - * @param {string} key Key to simulate - * @param {object} buttonMap Map of buttons to key objects - */ -function simulateKeyboardInput(key, buttonMap) { - simulateKeyboardEvent('keydown', key, buttonMap); - window.setTimeout(() => { - simulateKeyboardEvent('keyup', key, buttonMap); - }, 100); -} - -/** - * Bind a node by a specific key to simulate on touch + * @param node - The DOM element to bind the key to. + * @param key - The key to simulate. + * @param events - The event emitter for handling input events. * - * @param {*} node The node to bind a key to - * @param {string} key Key to simulate - * @param {object} buttonMap Map of buttons to key objects + * @remarks + * This function binds touch events to a node to simulate 'keydown' and 'keyup' keyboard events. + * It adds the key to the keys map and tracks the keydown state. When a touch starts, it simulates + * a 'keydown' event and adds an 'active' class to the node. When the touch ends, it simulates a 'keyup' + * event, removes the keydown state, and removes the 'active' class from the node and the last touched element. */ -function bindKey(node, key, buttonMap) { +function bindKey(node, key, events) { keys.set(node.id, key); node.addEventListener('touchstart', event => { event.preventDefault(); - simulateKeyboardEvent('keydown', key, buttonMap); + simulateKeyboardEvent('keydown', key, events); keysDown.set(event.target.id, node.id); node.classList.add('active'); }); @@ -78,7 +104,7 @@ function bindKey(node, key, buttonMap) { const pressedKey = keysDown.get(event.target.id); if (pressedKey && keys.has(pressedKey)) { const key = keys.get(pressedKey); - simulateKeyboardEvent('keyup', key, buttonMap); + simulateKeyboardEvent('keyup', key, events); } keysDown.delete(event.target.id); @@ -88,28 +114,4 @@ function bindKey(node, key, buttonMap) { document.getElementById(lastTouchedId).classList.remove('active'); } }); - - // Inspired by https://github.com/pulsejet/mkxp-web/blob/262a2254b684567311c9f0e135ee29f6e8c3613e/extra/js/dpad.js - node.addEventListener('touchmove', event => { - const { target, clientX, clientY } = event.changedTouches[0]; - const origTargetId = keysDown.get(target.id); - const nextTargetId = document.elementFromPoint(clientX, clientY).id; - if (origTargetId === nextTargetId) - return; - - if (origTargetId) { - const key = keys.get(origTargetId); - simulateKeyboardEvent('keyup', key, buttonMap); - keysDown.delete(target.id); - document.getElementById(origTargetId).classList.remove('active'); - } - - if (keys.has(nextTargetId)) { - const key = keys.get(nextTargetId); - simulateKeyboardEvent('keydown', key, buttonMap); - keysDown.set(target.id, nextTargetId); - lastTouchedId = nextTargetId; - document.getElementById(nextTargetId).classList.add('active'); - } - }); -} \ No newline at end of file +} diff --git a/src/ui-inputs.ts b/src/ui-inputs.ts index 38d8e7830c4..b2d820eb525 100644 --- a/src/ui-inputs.ts +++ b/src/ui-inputs.ts @@ -4,8 +4,10 @@ import {InputsController} from "./inputs-controller"; import MessageUiHandler from "./ui/message-ui-handler"; import StarterSelectUiHandler from "./ui/starter-select-ui-handler"; import {Setting, settingOptions} from "./system/settings"; -import SettingsUiHandler from "./ui/settings-ui-handler"; +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"; export interface ActionKeys { [key in Button]: () => void; @@ -130,7 +132,9 @@ export class UiInputs { } buttonCycleOption(button: Button): void { - if (this.scene.ui?.getHandler() instanceof StarterSelectUiHandler) { + const whitelist = [StarterSelectUiHandler, SettingsUiHandler, SettingsGamepadUiHandler, SettingsKeyboardUiHandler]; + const uiHandler = this.scene.ui?.getHandler(); + if (whitelist.some(handler => uiHandler instanceof handler)) { this.scene.ui.processInput(button); } } diff --git a/src/ui/settings/abrast-binding-ui-handler.ts b/src/ui/settings/abrast-binding-ui-handler.ts new file mode 100644 index 00000000000..1c0c5c94baf --- /dev/null +++ b/src/ui/settings/abrast-binding-ui-handler.ts @@ -0,0 +1,255 @@ +import UiHandler from "../ui-handler"; +import BattleScene from "../../battle-scene"; +import {Mode} from "../ui"; +import {addWindow} from "../ui-theme"; +import {addTextObject, TextStyle} from "../text"; +import {Button} from "../../enums/buttons"; +import {SettingKeyboard} from "#app/system/settings-keyboard"; + +/** + * Abstract class for handling UI elements related to button bindings. + */ +export default abstract class AbstractBindingUiHandler extends UiHandler { + // Containers for different segments of the UI. + protected optionSelectContainer: Phaser.GameObjects.Container; + protected actionsContainer: Phaser.GameObjects.Container; + + // Background elements for titles and action areas. + protected titleBg: Phaser.GameObjects.NineSlice; + protected actionBg: Phaser.GameObjects.NineSlice; + protected optionSelectBg: Phaser.GameObjects.NineSlice; + + // Text elements for displaying instructions and actions. + protected unlockText: Phaser.GameObjects.Text; + protected timerText: Phaser.GameObjects.Text; + protected swapText: Phaser.GameObjects.Text; + protected actionLabel: Phaser.GameObjects.Text; + protected cancelLabel: Phaser.GameObjects.Text; + + protected listening: boolean = false; + protected buttonPressed: number | null = null; + + // Icons for displaying current and new button assignments. + protected newButtonIcon: Phaser.GameObjects.Sprite; + protected targetButtonIcon: Phaser.GameObjects.Sprite; + + // Function to call on cancel or completion of binding. + protected cancelFn: (boolean?) => boolean; + protected swapAction: () => boolean; + + protected confirmText: string; + protected timeLeftAutoClose: number = 5; + protected countdownTimer; + + // The specific setting being modified. + protected target; + + /** + * Constructor for the AbstractBindingUiHandler. + * + * @param scene - The BattleScene instance. + * @param mode - The UI mode. + */ + constructor(scene: BattleScene, mode: Mode) { + super(scene, mode); + } + + /** + * Setup UI elements. + */ + setup() { + const ui = this.getUi(); + this.optionSelectContainer = this.scene.add.container(0, 0); + this.actionsContainer = this.scene.add.container(0, 0); + // Initially, containers are not visible. + this.optionSelectContainer.setVisible(false); + this.actionsContainer.setVisible(false); + + // Add containers to the UI. + ui.add(this.optionSelectContainer); + ui.add(this.actionsContainer); + + // Setup backgrounds and text objects for UI. + this.titleBg = addWindow(this.scene, (this.scene.game.canvas.width / 6) - this.getWindowWidth(), -(this.scene.game.canvas.height / 6) + 28 + 21, this.getWindowWidth(), 24); + this.titleBg.setOrigin(0.5); + this.optionSelectContainer.add(this.titleBg); + + this.actionBg = addWindow(this.scene, (this.scene.game.canvas.width / 6) - this.getWindowWidth(), -(this.scene.game.canvas.height / 6) + this.getWindowHeight() + 28 + 21 + 21, this.getWindowWidth(), 24); + this.actionBg.setOrigin(0.5); + this.actionsContainer.add(this.actionBg); + + // Text prompts and instructions for the user. + this.unlockText = addTextObject(this.scene, 0, 0, 'Press a button...', TextStyle.WINDOW); + this.unlockText.setOrigin(0, 0); + this.unlockText.setPositionRelative(this.titleBg, 36, 4); + this.optionSelectContainer.add(this.unlockText); + + this.timerText = addTextObject(this.scene, 0, 0, '(5)', TextStyle.WINDOW); + this.timerText.setOrigin(0, 0); + this.timerText.setPositionRelative(this.unlockText, (this.unlockText.width/6) + 5, 0); + this.optionSelectContainer.add(this.timerText); + + this.optionSelectBg = addWindow(this.scene, (this.scene.game.canvas.width / 6) - this.getWindowWidth(), -(this.scene.game.canvas.height / 6) + this.getWindowHeight() + 28, this.getWindowWidth(), this.getWindowHeight()); + this.optionSelectBg.setOrigin(0.5); + this.optionSelectContainer.add(this.optionSelectBg); + + this.cancelLabel = addTextObject(this.scene, 0, 0, 'Cancel', TextStyle.SETTINGS_LABEL); + this.cancelLabel.setOrigin(0, 0.5); + this.cancelLabel.setPositionRelative(this.actionBg, 10, this.actionBg.height / 2); + this.actionsContainer.add(this.cancelLabel); + } + + manageAutoCloseTimer(){ + clearTimeout(this.countdownTimer); + this.countdownTimer = setTimeout(() => { + this.timeLeftAutoClose -= 1; + this.timerText.setText(`(${this.timeLeftAutoClose})`); + if (this.timeLeftAutoClose >= 0) + this.manageAutoCloseTimer(); + else + this.cancelFn(); + }, 1000); + } + + /** + * Show the UI with the provided arguments. + * + * @param args - Arguments to be passed to the show method. + * @returns `true` if successful. + */ + show(args: any[]): boolean { + super.show(args); + this.buttonPressed = null; + this.timeLeftAutoClose = 5; + this.cancelFn = args[0].cancelHandler; + this.target = args[0].target; + + // Bring the option and action containers to the front of the UI. + this.getUi().bringToTop(this.optionSelectContainer); + this.getUi().bringToTop(this.actionsContainer); + + this.optionSelectContainer.setVisible(true); + setTimeout(() => { + this.listening = true; + this.manageAutoCloseTimer(); + }, 100); + return true; + } + + /** + * Get the width of the window. + * + * @returns The window width. + */ + getWindowWidth(): number { + return 160; + } + + /** + * Get the height of the window. + * + * @returns The window height. + */ + getWindowHeight(): number { + return 64; + } + + /** + * Process the input for the given button. + * + * @param button - The button to process. + * @returns `true` if the input was processed successfully. + */ + processInput(button: Button): boolean { + if (this.buttonPressed === null) return; + const ui = this.getUi(); + let success = false; + switch (button) { + case Button.LEFT: + case Button.RIGHT: + // Toggle between action and cancel options. + const cursor = this.cursor ? 0 : 1; + success = this.setCursor(cursor); + break + case Button.ACTION: + // Process actions based on current cursor position. + if (this.cursor === 0) { + this.cancelFn(); + } else { + success = this.swapAction(); + this.cancelFn(success); + } + break; + } + + // Plays a select sound effect if an action was successfully processed. + if (success) + ui.playSelect(); + else + ui.playError(); + + return success; + } + + /** + * Set the cursor to the specified position. + * + * @param cursor - The cursor position to set. + * @returns `true` if the cursor was set successfully. + */ + setCursor(cursor: integer): boolean { + this.cursor = cursor; + if (cursor === 1) { + this.actionLabel.setColor(this.getTextColor(TextStyle.SETTINGS_SELECTED)); + this.actionLabel.setShadowColor(this.getTextColor(TextStyle.SETTINGS_SELECTED, true)); + this.cancelLabel.setColor(this.getTextColor(TextStyle.WINDOW)); + this.cancelLabel.setShadowColor(this.getTextColor(TextStyle.WINDOW, true)); + return true; + } + this.actionLabel.setColor(this.getTextColor(TextStyle.WINDOW)); + this.actionLabel.setShadowColor(this.getTextColor(TextStyle.WINDOW, true)); + this.cancelLabel.setColor(this.getTextColor(TextStyle.SETTINGS_SELECTED)); + this.cancelLabel.setShadowColor(this.getTextColor(TextStyle.SETTINGS_SELECTED, true)); + return true; + } + + /** + * Clear the UI elements and state. + */ + clear() { + super.clear(); + clearTimeout(this.countdownTimer); + this.timerText.setText('(5)'); + this.timeLeftAutoClose = 5; + this.listening = false; + this.target = null; + this.cancelFn = null; + this.optionSelectContainer.setVisible(false); + this.actionsContainer.setVisible(false); + this.newButtonIcon.setVisible(false); + this.buttonPressed = null; + } + + /** + * Handle input down events. + * + * @param buttonIcon - The icon of the button that was pressed. + * @param assignedButtonIcon - The icon of the button that is assigned. + * @param type - The type of button press. + */ + onInputDown(buttonIcon: string, assignedButtonIcon: string, type: string): void { + clearTimeout(this.countdownTimer); + this.timerText.setText(""); + this.newButtonIcon.setTexture(type); + this.newButtonIcon.setFrame(buttonIcon); + if (assignedButtonIcon) { + this.targetButtonIcon.setTexture(type); + this.targetButtonIcon.setFrame(assignedButtonIcon); + this.targetButtonIcon.setVisible(true); + this.swapText.setVisible(true); + } + this.newButtonIcon.setVisible(true); + this.setCursor(0); + this.actionsContainer.setVisible(true); + } +} \ No newline at end of file diff --git a/src/ui/settings/abstract-settings-ui-handler.ts b/src/ui/settings/abstract-settings-ui-handler.ts new file mode 100644 index 00000000000..3c34cdf0662 --- /dev/null +++ b/src/ui/settings/abstract-settings-ui-handler.ts @@ -0,0 +1,654 @@ +import UiHandler from "../ui-handler"; +import BattleScene from "../../battle-scene"; +import {Mode} from "../ui"; +import {InterfaceConfig} from "../../inputs-controller"; +import {addWindow} from "../ui-theme"; +import {addTextObject, TextStyle} from "../text"; +import {Button} from "../../enums/buttons"; +import {getIconWithSettingName, getKeyWithSettingName} from "#app/configs/configHandler"; +import {setSettingKeyboard, SettingKeyboard} from "#app/system/settings-keyboard"; + +export interface InputsIcons { + [key: string]: Phaser.GameObjects.Sprite; +} + +export interface LayoutConfig { + optionsContainer: Phaser.GameObjects.Container; + inputsIcons: InputsIcons; + settingLabels: Phaser.GameObjects.Text[]; + optionValueLabels: Phaser.GameObjects.Text[][]; + optionCursors: integer[]; + keys: string[]; + bindingSettings: Array; +} +/** + * Abstract class for handling UI elements related to settings. + */ +export default abstract class AbstractSettingsUiUiHandler extends UiHandler { + protected settingsContainer: Phaser.GameObjects.Container; + protected optionsContainer: Phaser.GameObjects.Container; + + protected scrollCursor: integer; + protected optionCursors: integer[]; + protected cursorObj: Phaser.GameObjects.NineSlice; + + protected headerBg: Phaser.GameObjects.NineSlice; + protected optionsBg: Phaser.GameObjects.NineSlice; + protected actionsBg: Phaser.GameObjects.NineSlice; + + protected settingLabels: Phaser.GameObjects.Text[]; + protected optionValueLabels: Phaser.GameObjects.Text[][]; + + // layout will contain the 3 Gamepad tab for each config - dualshock, xbox, snes + protected layout: Map = new Map(); + // Will contain the input icons from the selected layout + protected inputsIcons: InputsIcons; + protected navigationIcons: InputsIcons; + // list all the setting keys used in the selected layout (because dualshock has more buttons than xbox) + protected keys: Array; + + // Store the specific settings related to key bindings for the current gamepad configuration. + protected bindingSettings: Array; + + protected settingDevice; + protected settingBlacklisted; + protected settingDeviceDefaults; + protected settingDeviceOptions; + protected configs; + protected commonSettingsCount; + protected textureOverride; + protected titleSelected; + protected localStoragePropertyName; + protected rowsToDisplay: number; + + abstract getLocalStorageSetting(): object; + abstract navigateMenuLeft(): boolean; + abstract navigateMenuRight(): boolean; + abstract saveSettingToLocalStorage(setting, cursor): void; + abstract getActiveConfig(): InterfaceConfig; + abstract setSetting(scene: BattleScene, setting, value: integer): boolean; + + /** + * Constructor for the AbstractSettingsUiUiHandler. + * + * @param scene - The BattleScene instance. + * @param mode - The UI mode. + */ + constructor(scene: BattleScene, mode?: Mode) { + super(scene, mode); + this.rowsToDisplay = 8; + } + + /** + * Setup UI elements. + */ + setup() { + const ui = this.getUi(); + + this.settingsContainer = this.scene.add.container(1, -(this.scene.game.canvas.height / 6) + 1); + + this.settingsContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains); + + this.headerBg = addWindow(this.scene, 0, 0, (this.scene.game.canvas.width / 6) - 2, 24); + this.headerBg.setOrigin(0, 0); + + this.navigationIcons = {}; + + const iconPreviousTab = this.scene.add.sprite(0, 0, 'keyboard'); + iconPreviousTab.setScale(.1); + iconPreviousTab.setOrigin(0, -0.1); + iconPreviousTab.setPositionRelative(this.headerBg, 8, 4); + this.navigationIcons['BUTTON_CYCLE_FORM'] = iconPreviousTab; + + const iconNextTab = this.scene.add.sprite(0, 0, 'keyboard'); + iconNextTab.setScale(.1); + iconNextTab.setOrigin(0, -0.1); + iconNextTab.setPositionRelative(this.headerBg, this.headerBg.width - 20, 4); + this.navigationIcons['BUTTON_CYCLE_SHINY'] = iconNextTab; + + const headerText = addTextObject(this.scene, 0, 0, 'General', TextStyle.SETTINGS_LABEL); + headerText.setOrigin(0, 0); + headerText.setPositionRelative(this.headerBg, 8 + iconPreviousTab.width/6 - 4, 4); + + const gamepadText = addTextObject(this.scene, 0, 0, 'Gamepad', this.titleSelected === 'Gamepad' ? TextStyle.SETTINGS_SELECTED : TextStyle.SETTINGS_LABEL); + gamepadText.setOrigin(0, 0); + gamepadText.setPositionRelative(this.headerBg, 50 + iconPreviousTab.width/6 - 4, 4); + + const keyboardText = addTextObject(this.scene, 0, 0, 'Keyboard', this.titleSelected === 'Keyboard' ? TextStyle.SETTINGS_SELECTED : TextStyle.SETTINGS_LABEL); + keyboardText.setOrigin(0, 0); + keyboardText.setPositionRelative(this.headerBg, 97 + iconPreviousTab.width/6 - 4, 4); + + this.optionsBg = addWindow(this.scene, 0, this.headerBg.height, (this.scene.game.canvas.width / 6) - 2, (this.scene.game.canvas.height / 6) - 16 - this.headerBg.height - 2); + this.optionsBg.setOrigin(0, 0); + + this.actionsBg = addWindow(this.scene, 0, (this.scene.game.canvas.height / 6) - this.headerBg.height, (this.scene.game.canvas.width / 6) - 2, 22); + this.actionsBg.setOrigin(0, 0); + + const iconAction = this.scene.add.sprite(0, 0, 'keyboard'); + iconAction.setScale(.1); + iconAction.setOrigin(0, -0.1); + iconAction.setPositionRelative(this.actionsBg, this.headerBg.width - 20, 4); + this.navigationIcons['BUTTON_ACTION'] = iconAction; + + const actionText = addTextObject(this.scene, 0, 0, 'Action', TextStyle.SETTINGS_LABEL); + actionText.setOrigin(0, 0); + actionText.setPositionRelative(iconAction, 0, 2); + actionText.setPositionRelative(iconAction, -actionText.width/6, -(actionText.height/6)/2 - 6); + + const iconCancel = this.scene.add.sprite(0, 0, 'keyboard'); + iconCancel.setScale(.1); + iconCancel.setOrigin(0, -0.1); + iconCancel.setPositionRelative(this.actionsBg, this.headerBg.width - 100, 4); + this.navigationIcons['BUTTON_CANCEL'] = iconCancel; + + const cancelText = addTextObject(this.scene, 0, 0, 'Cancel', TextStyle.SETTINGS_LABEL); + cancelText.setOrigin(0, 0); + cancelText.setPositionRelative(iconCancel, -cancelText.width/6, -(cancelText.height/6)/2 - 6); + + const iconReset = this.scene.add.sprite(0, 0, 'keyboard'); + iconReset.setScale(.1); + iconReset.setOrigin(0, -0.1); + iconReset.setPositionRelative(this.actionsBg, this.headerBg.width - 180, 4); + this.navigationIcons['BUTTON_HOME'] = iconReset; + + const resetText = addTextObject(this.scene, 0, 0, 'Reset all', TextStyle.SETTINGS_LABEL); + resetText.setOrigin(0, 0); + resetText.setPositionRelative(iconReset, -resetText.width/6, -(resetText.height/6)/2 - 6); + + this.settingsContainer.add(this.headerBg); + this.settingsContainer.add(headerText); + this.settingsContainer.add(gamepadText); + this.settingsContainer.add(keyboardText); + this.settingsContainer.add(this.optionsBg); + this.settingsContainer.add(iconNextTab) + this.settingsContainer.add(iconPreviousTab) + this.settingsContainer.add(this.actionsBg) + this.settingsContainer.add(iconAction) + this.settingsContainer.add(iconCancel) + this.settingsContainer.add(iconReset) + this.settingsContainer.add(actionText) + this.settingsContainer.add(cancelText) + this.settingsContainer.add(resetText) + + /// Initialize a new configuration "screen" for each type of gamepad. + for (const config of this.configs) { + // Create a map to store layout settings based on the pad type. + this.layout[config.padType] = new Map(); + // Create a container for gamepad options in the scene, initially hidden. + + const optionsContainer = this.scene.add.container(0, 0); + optionsContainer.setVisible(false); + + // Gather all binding settings from the configuration. + const bindingSettings = Object.keys(config.settings); + + // Array to hold labels for different settings such as 'Default Controller', 'Gamepad Support', etc. + const settingLabels: Phaser.GameObjects.Text[] = []; + + // Array to hold options for each setting, e.g., 'Auto', 'Disabled'. + const optionValueLabels: Phaser.GameObjects.Text[][] = []; + + // Object to store sprites for each button configuration. + const inputsIcons: InputsIcons = {}; + + // Fetch common setting keys such as 'Default Controller' and 'Gamepad Support' from gamepad settings. + const commonSettingKeys = Object.keys(this.settingDevice).slice(0, this.commonSettingsCount).map(key => this.settingDevice[key]); + // Combine common and specific bindings into a single array. + const specificBindingKeys = [...commonSettingKeys, ...Object.keys(config.settings)]; + // Fetch default values for these settings and prepare to highlight selected options. + const optionCursors = Object.values(Object.keys(this.settingDeviceDefaults).filter(s => specificBindingKeys.includes(s)).map(k => this.settingDeviceDefaults[k])); + // Filter out settings that are not relevant to the current gamepad configuration. + const settingFiltered = Object.keys(this.settingDevice).filter(_key => specificBindingKeys.includes(this.settingDevice[_key])) + // Loop through the filtered settings to manage display and options. + + settingFiltered.forEach((setting, s) => { + // Convert the setting key from format 'Key_Name' to 'Key name' for display. + let settingName = setting.replace(/\_/g, ' '); + + // Create and add a text object for the setting name to the scene. + const isLock = this.settingBlacklisted.includes(this.settingDevice[setting]); + const labelStyle = isLock ? TextStyle.SETTINGS_LOCKED : TextStyle.SETTINGS_LABEL + settingLabels[s] = addTextObject(this.scene, 8, 28 + s * 16, settingName, labelStyle); + settingLabels[s].setOrigin(0, 0); + optionsContainer.add(settingLabels[s]); + + // Initialize an array to store the option labels for this setting. + const valueLabels: Phaser.GameObjects.Text[] = [] + + // Process each option for the current setting. + for (const [o, option] of this.settingDeviceOptions[this.settingDevice[setting]].entries()) { + // Check if the current setting is for binding keys. + if (bindingSettings.includes(this.settingDevice[setting])) { + // Create a label for non-null options, typically indicating actionable options like 'change'. + if (o) { + const valueLabel = addTextObject(this.scene, 0, 0, isLock ? '' : option, TextStyle.WINDOW); + valueLabel.setOrigin(0, 0); + optionsContainer.add(valueLabel); + valueLabels.push(valueLabel); + continue; + } + // For null options, add an icon for the key. + const icon = this.scene.add.sprite(0, 0, this.textureOverride ? this.textureOverride : config.padType); + icon.setScale(0.1); + icon.setOrigin(0, -0.1); + inputsIcons[this.settingDevice[setting]] = icon; + optionsContainer.add(icon); + valueLabels.push(icon); + continue; + } + // For regular settings like 'Gamepad support', create a label and determine if it is selected. + const valueLabel = addTextObject(this.scene, 0, 0, option, this.settingDeviceDefaults[this.settingDevice[setting]] === o ? TextStyle.SETTINGS_SELECTED : TextStyle.WINDOW); + valueLabel.setOrigin(0, 0); + + optionsContainer.add(valueLabel); + + //if a setting has 2 options, valueLabels will be an array of 2 elements + valueLabels.push(valueLabel); + } + // Collect all option labels for this setting into the main array. + optionValueLabels.push(valueLabels); + + // Calculate the total width of all option labels within a specific setting + // This is achieved by summing the width of each option label + const totalWidth = optionValueLabels[s].map(o => o.width).reduce((total, width) => total += width, 0); + + // Define the minimum width for a label, ensuring it's at least 78 pixels wide or the width of the setting label plus some padding + const labelWidth = Math.max(90, settingLabels[s].displayWidth + 8); + + // Calculate the total available space for placing option labels next to their setting label + // We reserve space for the setting label and then distribute the remaining space evenly + const totalSpace = (300 - labelWidth) - totalWidth / 6; + // Calculate the spacing between options based on the available space divided by the number of gaps between labels + const optionSpacing = Math.floor(totalSpace / (optionValueLabels[s].length - 1)); + + // Initialize xOffset to zero, which will be used to position each option label horizontally + let xOffset = 0; + + // Start positioning each option label one by one + for (let value of optionValueLabels[s]) { + // Set the option label's position right next to the setting label, adjusted by xOffset + value.setPositionRelative(settingLabels[s], labelWidth + xOffset, 0); + // Move the xOffset to the right for the next label, ensuring each label is spaced evenly + xOffset += value.width / 6 + optionSpacing; + } + }); + + // Assigning the newly created components to the layout map under the specific gamepad type. + this.layout[config.padType].optionsContainer = optionsContainer; // Container for this pad's options. + this.layout[config.padType].inputsIcons = inputsIcons; // Icons for each input specific to this pad. + this.layout[config.padType].settingLabels = settingLabels; // Text labels for each setting available on this pad. + this.layout[config.padType].optionValueLabels = optionValueLabels; // Labels for values corresponding to each setting. + this.layout[config.padType].optionCursors = optionCursors; // Cursors to navigate through the options. + this.layout[config.padType].keys = specificBindingKeys; // Keys that identify each setting specifically bound to this pad. + this.layout[config.padType].bindingSettings = bindingSettings; // Settings that define how the keys are bound. + + // Add the options container to the overall settings container to be displayed in the UI. + this.settingsContainer.add(optionsContainer); + } + // Add the settings container to the UI. + ui.add(this.settingsContainer); + + // Initially hide the settings container until needed (e.g., when a gamepad is connected or a button is pressed). + this.settingsContainer.setVisible(false); + } + + /** + * Update the bindings for the current active device configuration. + */ + updateBindings(): void { + // Hide the options container for all layouts to reset the UI visibility. + Object.keys(this.layout).forEach((key) => this.layout[key].optionsContainer.setVisible(false)); + // Fetch the active gamepad configuration from the input controller. + const activeConfig = this.getActiveConfig(); + + // Set the UI layout for the active configuration. If unsuccessful, exit the function early. + if (!this.setLayout(activeConfig)) return; + + // Retrieve the gamepad settings from local storage or use an empty object if none exist. + const settings: object = this.getLocalStorageSetting(); + + // Update the cursor for each key based on the stored settings or default cursors. + this.keys.forEach((key, index) => { + this.setOptionCursor(index, settings.hasOwnProperty(key) ? settings[key] : this.optionCursors[index]) + }); + + // If the active configuration has no custom bindings set, exit the function early. + // by default, if custom does not exists, a default is assigned to it + // it only means the gamepad is not yet initalized + if (!activeConfig.custom) return; + + // For each element in the binding settings, update the icon according to the current assignment. + for (const elm of this.bindingSettings) { + const icon = getIconWithSettingName(activeConfig, elm); + if (icon) { + this.inputsIcons[elm].setFrame(icon); + this.inputsIcons[elm].alpha = 1; + } else { + this.inputsIcons[elm].alpha = 0; + } + } + + // Set the cursor and scroll cursor to their initial positions. + this.setCursor(this.cursor); + this.setScrollCursor(this.scrollCursor); + } + + updateNavigationDisplay() { + const specialIcons = { + 'BUTTON_HOME': 'T_Home_Key_Dark.png', + 'BUTTON_DELETE': 'T_Del_Key_Dark.png', + } + for (const settingName of Object.keys(this.navigationIcons)) { + if (Object.keys(specialIcons).includes(settingName)) { + this.navigationIcons[settingName].setTexture("keyboard"); + this.navigationIcons[settingName].setFrame(specialIcons[settingName]); + this.navigationIcons[settingName].alpha = 1; + continue + } + const icon = this.scene.inputController?.getIconForLatestInputRecorded(settingName); + if (icon) { + const type = this.scene.inputController?.getLastSourceType(); + this.navigationIcons[settingName].setTexture(type); + this.navigationIcons[settingName].setFrame(icon); + this.navigationIcons[settingName].alpha = 1; + } else { + this.navigationIcons[settingName].alpha = 0; + } + } + } + + /** + * Show the UI with the provided arguments. + * + * @param args - Arguments to be passed to the show method. + * @returns `true` if successful. + */ + show(args: any[]): boolean { + super.show(args); + + this.updateNavigationDisplay(); + // Update the bindings for the current active gamepad configuration. + this.updateBindings(); + + // Make the settings container visible to the user. + this.settingsContainer.setVisible(true); + // Reset the scroll cursor to the top of the settings container. + this.resetScroll(); + + // Move the settings container to the end of the UI stack to ensure it is displayed on top. + this.getUi().moveTo(this.settingsContainer, this.getUi().length - 1); + + // Hide any tooltips that might be visible before showing the settings container. + this.getUi().hideTooltip(); + + // Return true to indicate the UI was successfully shown. + return true; + } + + /** + * Set the UI layout for the active device configuration. + * + * @param activeConfig - The active device configuration. + * @returns `true` if the layout was successfully applied, otherwise `false`. + */ + setLayout(activeConfig: InterfaceConfig): boolean { + // Check if there is no active configuration (e.g., no gamepad connected). + if (!activeConfig) { + // Retrieve the layout for when no gamepads are connected. + const layout = this.layout['noGamepads']; + // Make the options container visible to show message. + layout.optionsContainer.setVisible(true); + // Return false indicating the layout application was not successful due to lack of gamepad. + return false; + } + // Extract the type of the gamepad from the active configuration. + const configType = activeConfig.padType; + + // Retrieve the layout settings based on the type of the gamepad. + const layout = this.layout[configType]; + // Update the main controller with configuration details from the selected layout. + this.keys = layout.keys; + this.optionsContainer = layout.optionsContainer; + this.optionsContainer.setVisible(true); + this.settingLabels = layout.settingLabels; + this.optionValueLabels = layout.optionValueLabels; + this.optionCursors = layout.optionCursors; + this.inputsIcons = layout.inputsIcons; + this.bindingSettings = layout.bindingSettings; + + // Return true indicating the layout was successfully applied. + return true; + } + + /** + * Process the input for the given button. + * + * @param button - The button to process. + * @returns `true` if the input was processed successfully. + */ + processInput(button: Button): boolean { + const ui = this.getUi(); + // Defines the maximum number of rows that can be displayed on the screen. + let success = false; + this.updateNavigationDisplay(); + + // Handle the input based on the button pressed. + if (button === Button.CANCEL) { + // Handle cancel button press, reverting UI mode to previous state. + success = true; + this.scene.ui.revertMode(); + } else { + const cursor = this.cursor + this.scrollCursor; // Calculate the absolute cursor position. + const setting = this.settingDevice[Object.keys(this.settingDevice)[cursor]]; + switch (button) { + case Button.ACTION: + if (!this.optionCursors || !this.optionValueLabels) return; + if (this.settingBlacklisted.includes(setting) || !setting.includes('BUTTON_')) success = false; + else + success = this.setSetting(this.scene, setting, 1); + break; + case Button.UP: // Move up in the menu. + if (!this.optionValueLabels) return false; + if (cursor) { // If not at the top, move the cursor up. + if (this.cursor) + success = this.setCursor(this.cursor - 1); + else // If at the top of the visible items, scroll up. + success = this.setScrollCursor(this.scrollCursor - 1); + } else { + // When at the top of the menu and pressing UP, move to the bottommost item. + // First, set the cursor to the last visible element, preparing for the scroll to the end. + const successA = this.setCursor(this.rowsToDisplay - 1); + // Then, adjust the scroll to display the bottommost elements of the menu. + const successB = this.setScrollCursor(this.optionValueLabels.length - this.rowsToDisplay); + success = successA && successB; // success is just there to play the little validation sound effect + } + break; + case Button.DOWN: // Move down in the menu. + if (!this.optionValueLabels) return false; + if (cursor < this.optionValueLabels.length - 1) { + if (this.cursor < this.rowsToDisplay - 1) + success = this.setCursor(this.cursor + 1); + else if (this.scrollCursor < this.optionValueLabels.length - this.rowsToDisplay) + success = this.setScrollCursor(this.scrollCursor + 1); + } else { + // When at the bottom of the menu and pressing DOWN, move to the topmost item. + // First, set the cursor to the first visible element, resetting the scroll to the top. + const successA = this.setCursor(0); + // Then, reset the scroll to start from the first element of the menu. + const successB = this.setScrollCursor(0); + success = successA && successB; // Indicates a successful cursor and scroll adjustment. + } + break; + case Button.LEFT: // Move selection left within the current option set. + if (!this.optionCursors || !this.optionValueLabels) return; + if (this.settingBlacklisted.includes(setting) || setting.includes('BUTTON_')) success = false; + else if (this.optionCursors[cursor]) { + success = this.setOptionCursor(cursor, this.optionCursors[cursor] - 1, true); + } + break; + case Button.RIGHT: // Move selection right within the current option set. + if (!this.optionCursors || !this.optionValueLabels) return; + if (this.settingBlacklisted.includes(setting) || setting.includes('BUTTON_')) success = false; + else if (this.optionCursors[cursor] < this.optionValueLabels[cursor].length - 1) { + success = this.setOptionCursor(cursor, this.optionCursors[cursor] + 1, true); + } + break; + case Button.CYCLE_FORM: + success = this.navigateMenuLeft(); + break; + case Button.CYCLE_SHINY: + success = this.navigateMenuRight(); + break; + } + } + + // If a change occurred, play the selection sound. + if (success) + ui.playSelect(); + + return success; // Return whether the input resulted in a successful action. + } + + resetScroll() { + this.cursorObj?.destroy(); + this.cursorObj = null; + this.cursor = null; + this.setCursor(0); + this.setScrollCursor(0); + this.updateSettingsScroll(); + } + + /** + * Set the cursor to the specified position. + * + * @param cursor - The cursor position to set. + * @returns `true` if the cursor was set successfully. + */ + setCursor(cursor: integer): boolean { + const ret = super.setCursor(cursor); + // If the optionsContainer is not initialized, return the result from the parent class directly. + if (!this.optionsContainer) return ret; + + // Check if the cursor object exists, if not, create it. + if (!this.cursorObj) { + this.cursorObj = this.scene.add.nineslice(0, 0, 'summary_moves_cursor', null, (this.scene.game.canvas.width / 6) - 10, 16, 1, 1, 1, 1); + this.cursorObj.setOrigin(0, 0); // Set the origin to the top-left corner. + this.optionsContainer.add(this.cursorObj); // Add the cursor to the options container. + } + + // Update the position of the cursor object relative to the options background based on the current cursor and scroll positions. + this.cursorObj.setPositionRelative(this.optionsBg, 4, 4 + (this.cursor + this.scrollCursor) * 16); + + return ret; // Return the result from the parent class's setCursor method. + } + + /** + * Set the scroll cursor to the specified position. + * + * @param scrollCursor - The scroll cursor position to set. + * @returns `true` if the scroll cursor was set successfully. + */ + setScrollCursor(scrollCursor: integer): boolean { + // Check if the new scroll position is the same as the current one; if so, do not update. + if (scrollCursor === this.scrollCursor) + return false; + + // Update the internal scroll cursor state + this.scrollCursor = scrollCursor; + + // Apply the new scroll position to the settings UI. + this.updateSettingsScroll(); + + // Reset the cursor to its current position to adjust its visibility after scrolling. + this.setCursor(this.cursor); + + return true; // Return true to indicate the scroll cursor was successfully updated. + } + + /** + * Set the option cursor to the specified position. + * + * @param settingIndex - The index of the setting. + * @param cursor - The cursor position to set. + * @param save - Whether to save the setting to local storage. + * @returns `true` if the option cursor was set successfully. + */ + setOptionCursor(settingIndex: integer, cursor: integer, save?: boolean): boolean { + // Retrieve the specific setting using the settingIndex from the settingDevice enumeration. + const setting = this.settingDevice[Object.keys(this.settingDevice)[settingIndex]]; + + // Get the current cursor position for this setting. + const lastCursor = this.optionCursors[settingIndex]; + + // Check if the setting is not part of the bindings (i.e., it's a regular setting). + if (!this.bindingSettings.includes(setting) && !setting.includes('BUTTON_')) { + // Get the label of the last selected option and revert its color to the default. + const lastValueLabel = this.optionValueLabels[settingIndex][lastCursor]; + lastValueLabel.setColor(this.getTextColor(TextStyle.WINDOW)); + lastValueLabel.setShadowColor(this.getTextColor(TextStyle.WINDOW, true)); + + // Update the cursor for the setting to the new position. + this.optionCursors[settingIndex] = cursor; + + // Change the color of the new selected option to indicate it's selected. + const newValueLabel = this.optionValueLabels[settingIndex][cursor]; + newValueLabel.setColor(this.getTextColor(TextStyle.SETTINGS_SELECTED)); + newValueLabel.setShadowColor(this.getTextColor(TextStyle.SETTINGS_SELECTED, true)); + } + + // If the save flag is set and the setting is not the default controller setting, save the setting to local storage + if (save) { + this.saveSettingToLocalStorage(setting, cursor); + } + + return true; // Return true to indicate the cursor was successfully updated. + } + + /** + * Update the scroll position of the settings UI. + */ + updateSettingsScroll(): void { + // Return immediately if the options container is not initialized. + if (!this.optionsContainer) return; + + // Set the vertical position of the options container based on the current scroll cursor, multiplying by the item height. + this.optionsContainer.setY(-16 * this.scrollCursor); + + // Iterate over all setting labels to update their visibility. + for (let s = 0; s < this.settingLabels.length; s++) { + // Determine if the current setting should be visible based on the scroll position. + const visible = s >= this.scrollCursor && s < this.scrollCursor + this.rowsToDisplay; + + // Set the visibility of the setting label and its corresponding options. + this.settingLabels[s].setVisible(visible); + for (let option of this.optionValueLabels[s]) + option.setVisible(visible); + } + } + + /** + * Clear the UI elements and state. + */ + clear(): void { + super.clear(); + + // Hide the settings container to remove it from the view. + this.settingsContainer.setVisible(false); + + // Remove the cursor from the UI. + this.eraseCursor(); + } + + /** + * Erase the cursor from the UI. + */ + eraseCursor(): void { + // Check if a cursor object exists. + if (this.cursorObj) + this.cursorObj.destroy(); // Destroy the cursor object to clean up resources. + + // Set the cursor object reference to null to fully dereference it. + this.cursorObj = null; + } + +} \ No newline at end of file diff --git a/src/ui/settings/gamepad-binding-ui-handler.ts b/src/ui/settings/gamepad-binding-ui-handler.ts new file mode 100644 index 00000000000..83cd1c2747d --- /dev/null +++ b/src/ui/settings/gamepad-binding-ui-handler.ts @@ -0,0 +1,81 @@ +import BattleScene from "../../battle-scene"; +import AbstractBindingUiHandler from "../settings/abrast-binding-ui-handler"; +import {Mode} from "../ui"; +import {Device} from "#app/enums/devices"; +import {getIconWithSettingName, getKeyWithKeycode} from "#app/configs/configHandler"; +import {addTextObject, TextStyle} from "#app/ui/text"; + + +export default class GamepadBindingUiHandler extends AbstractBindingUiHandler { + + constructor(scene: BattleScene, mode: Mode) { + super(scene, mode); + this.scene.input.gamepad.on('down', this.gamepadButtonDown, this); + } + setup() { + super.setup(); + + // New button icon setup. + this.newButtonIcon = this.scene.add.sprite(0, 0, 'xbox'); + this.newButtonIcon.setScale(0.15); + this.newButtonIcon.setPositionRelative(this.optionSelectBg, 78, 16); + this.newButtonIcon.setOrigin(0.5); + this.newButtonIcon.setVisible(false); + + this.swapText = addTextObject(this.scene, 0, 0, 'will swap with', TextStyle.WINDOW); + this.swapText.setOrigin(0.5); + this.swapText.setPositionRelative(this.optionSelectBg, this.optionSelectBg.width / 2 - 2, this.optionSelectBg.height / 2 - 2); + this.swapText.setVisible(false); + + this.targetButtonIcon = this.scene.add.sprite(0, 0, 'xbox'); + this.targetButtonIcon.setScale(0.15); + this.targetButtonIcon.setPositionRelative(this.optionSelectBg, 78, 48); + this.targetButtonIcon.setOrigin(0.5); + this.targetButtonIcon.setVisible(false); + + this.actionLabel = addTextObject(this.scene, 0, 0, "Confirm swap", TextStyle.SETTINGS_LABEL); + this.actionLabel.setOrigin(0, 0.5); + this.actionLabel.setPositionRelative(this.actionBg, this.actionBg.width - 75, this.actionBg.height / 2); + this.actionsContainer.add(this.actionLabel); + + this.optionSelectContainer.add(this.newButtonIcon); + this.optionSelectContainer.add(this.swapText); + this.optionSelectContainer.add(this.targetButtonIcon); + } + + getSelectedDevice() { + return this.scene.inputController?.selectedDevice[Device.GAMEPAD]; + } + + gamepadButtonDown(pad: Phaser.Input.Gamepad.Gamepad, button: Phaser.Input.Gamepad.Button, value: number): void { + const blacklist = [12, 13, 14, 15]; // d-pad buttons are blacklisted. + // Check conditions before processing the button press. + if (!this.listening || pad.id.toLowerCase() !== this.getSelectedDevice() || blacklist.includes(button.index) || this.buttonPressed !== null) return; + const activeConfig = this.scene.inputController.getActiveConfig(Device.GAMEPAD); + const type = activeConfig.padType + const key = getKeyWithKeycode(activeConfig, button.index); + const buttonIcon = activeConfig.icons[key]; + if (!buttonIcon) return; + this.buttonPressed = button.index; + const assignedButtonIcon = getIconWithSettingName(activeConfig, this.target); + this.onInputDown(buttonIcon, assignedButtonIcon, type); + } + + swapAction(): boolean { + const activeConfig = this.scene.inputController.getActiveConfig(Device.GAMEPAD); + if(this.scene.inputController.assignBinding(activeConfig, this.target, this.buttonPressed)) { + this.scene.gameData.saveMappingConfigs(this.getSelectedDevice(), activeConfig); + return true; + } + return false; + } + + /** + * Clear the UI elements and state. + */ + clear() { + super.clear(); + this.targetButtonIcon.setVisible(false); + this.swapText.setVisible(false); + } +} \ No newline at end of file diff --git a/src/ui/settings/keyboard-binding-ui-handler.ts b/src/ui/settings/keyboard-binding-ui-handler.ts new file mode 100644 index 00000000000..8943883131e --- /dev/null +++ b/src/ui/settings/keyboard-binding-ui-handler.ts @@ -0,0 +1,71 @@ +import BattleScene from "../../battle-scene"; +import AbstractBindingUiHandler from "../settings/abrast-binding-ui-handler"; +import {Mode} from "../ui"; +import { getIconWithSettingName, getKeyWithKeycode} from "#app/configs/configHandler"; +import {Device} from "#app/enums/devices"; +import {addTextObject, TextStyle} from "#app/ui/text"; + + +export default class KeyboardBindingUiHandler extends AbstractBindingUiHandler { + + constructor(scene: BattleScene, mode?: Mode) { + super(scene, mode); + // Listen to gamepad button down events to initiate binding. + scene.input.keyboard.on('keydown', this.onKeyDown, this); + this.confirmText = "Confirm"; + } + setup() { + super.setup(); + + // New button icon setup. + this.newButtonIcon = this.scene.add.sprite(0, 0, 'keyboard'); + this.newButtonIcon.setScale(0.15); + this.newButtonIcon.setPositionRelative(this.optionSelectBg, 78, 32); + this.newButtonIcon.setOrigin(0.5); + this.newButtonIcon.setVisible(false); + + this.actionLabel = addTextObject(this.scene, 0, 0, "Assign button", TextStyle.SETTINGS_LABEL); + this.actionLabel.setOrigin(0, 0.5); + this.actionLabel.setPositionRelative(this.actionBg, this.actionBg.width - 80, this.actionBg.height / 2); + this.actionsContainer.add(this.actionLabel); + + this.optionSelectContainer.add(this.newButtonIcon); + } + + getSelectedDevice() { + return this.scene.inputController?.selectedDevice[Device.KEYBOARD]; + } + + onKeyDown(event): void { + const blacklist = [ + Phaser.Input.Keyboard.KeyCodes.UP, + Phaser.Input.Keyboard.KeyCodes.DOWN, + Phaser.Input.Keyboard.KeyCodes.LEFT, + Phaser.Input.Keyboard.KeyCodes.RIGHT, + Phaser.Input.Keyboard.KeyCodes.HOME, + Phaser.Input.Keyboard.KeyCodes.ENTER, + Phaser.Input.Keyboard.KeyCodes.ESC, + Phaser.Input.Keyboard.KeyCodes.DELETE, + ]; + const key = event.keyCode; + // // Check conditions before processing the button press. + if (!this.listening || this.buttonPressed !== null || blacklist.includes(key)) return; + const activeConfig = this.scene.inputController.getActiveConfig(Device.KEYBOARD); + const _key = getKeyWithKeycode(activeConfig, key); + const buttonIcon = activeConfig.icons[_key]; + if (!buttonIcon) return; + this.buttonPressed = key; + const assignedButtonIcon = getIconWithSettingName(activeConfig, this.target); + this.onInputDown(buttonIcon, null, 'keyboard'); + } + + swapAction(): boolean { + const activeConfig = this.scene.inputController.getActiveConfig(Device.KEYBOARD); + if (this.scene.inputController.assignBinding(activeConfig, this.target, this.buttonPressed)) { + this.scene.gameData.saveMappingConfigs(this.getSelectedDevice(), activeConfig); + return true; + } + return false; + } + +} \ No newline at end of file diff --git a/src/ui/option-select-ui-handler.ts b/src/ui/settings/option-select-ui-handler.ts similarity index 59% rename from src/ui/option-select-ui-handler.ts rename to src/ui/settings/option-select-ui-handler.ts index 824fa153570..18979dca123 100644 --- a/src/ui/option-select-ui-handler.ts +++ b/src/ui/settings/option-select-ui-handler.ts @@ -1,6 +1,6 @@ -import BattleScene from "../battle-scene"; -import AbstractOptionSelectUiHandler from "./abstact-option-select-ui-handler"; -import { Mode } from "./ui"; +import BattleScene from "../../battle-scene"; +import AbstractOptionSelectUiHandler from "../abstact-option-select-ui-handler"; +import { Mode } from "../ui"; export default class OptionSelectUiHandler extends AbstractOptionSelectUiHandler { constructor(scene: BattleScene, mode: Mode = Mode.OPTION_SELECT) { diff --git a/src/ui/settings/settings-gamepad-ui-handler.ts b/src/ui/settings/settings-gamepad-ui-handler.ts new file mode 100644 index 00000000000..3d59b090591 --- /dev/null +++ b/src/ui/settings/settings-gamepad-ui-handler.ts @@ -0,0 +1,166 @@ +import BattleScene from "../../battle-scene"; +import {addTextObject, TextStyle} from "../text"; +import {Mode} from "../ui"; +import { + setSettingGamepad, + SettingGamepad, + settingGamepadBlackList, + settingGamepadDefaults, + settingGamepadOptions +} from "../../system/settings-gamepad"; +import pad_xbox360 from "#app/configs/pad_xbox360"; +import pad_dualshock from "#app/configs/pad_dualshock"; +import pad_unlicensedSNES from "#app/configs/pad_unlicensedSNES"; +import {InterfaceConfig} from "#app/inputs-controller"; +import AbstractSettingsUiUiHandler from "#app/ui/settings/abstract-settings-ui-handler"; +import {Device} from "#app/enums/devices"; +import {truncateString} from "#app/utils"; +import {setSettingKeyboard, SettingKeyboard} from "#app/system/settings-keyboard"; + +/** + * Class representing the settings UI handler for gamepads. + * + * @extends AbstractSettingsUiUiHandler + */ + +export default class SettingsGamepadUiHandler extends AbstractSettingsUiUiHandler { + + /** + * Creates an instance of SettingsGamepadUiHandler. + * + * @param scene - The BattleScene instance. + * @param mode - The UI mode, optional. + */ + constructor(scene: BattleScene, mode?: Mode) { + super(scene, mode); + this.titleSelected = 'Gamepad'; + this.settingDevice = SettingGamepad; + this.settingDeviceDefaults = settingGamepadDefaults; + this.settingDeviceOptions = settingGamepadOptions; + this.configs = [pad_xbox360, pad_dualshock, pad_unlicensedSNES] + this.commonSettingsCount = 2; + this.localStoragePropertyName = 'settingsGamepad'; + this.settingBlacklisted = settingGamepadBlackList; + } + + setSetting(scene: BattleScene, setting, value: integer): boolean { + return setSettingGamepad(scene, setting, value); + } + + /** + * Setup UI elements. + */ + setup() { + super.setup(); + // If no gamepads are detected, set up a default UI prompt in the settings container. + this.layout['noGamepads'] = new Map(); + const optionsContainer = this.scene.add.container(0, 0); + optionsContainer.setVisible(false); // Initially hide the container as no gamepads are connected. + const label = addTextObject(this.scene, 8, 28, 'Please plug a controller or press a button', TextStyle.SETTINGS_LABEL); + label.setOrigin(0, 0); + optionsContainer.add(label); + this.settingsContainer.add(optionsContainer); + + // Map the 'noGamepads' layout options for easy access. + this.layout['noGamepads'].optionsContainer = optionsContainer; + this.layout['noGamepads'].label = label; + } + + /** + * Get the active configuration. + * + * @returns The active gamepad configuration. + */ + getActiveConfig(): InterfaceConfig { + return this.scene.inputController.getActiveConfig(Device.GAMEPAD); + } + + /** + * Get the gamepad settings from local storage. + * + * @returns The gamepad settings from local storage. + */ + getLocalStorageSetting(): object { + // Retrieve the gamepad settings from local storage or use an empty object if none exist. + const settings: object = localStorage.hasOwnProperty('settingsGamepad') ? JSON.parse(localStorage.getItem('settingsGamepad')) : {}; + return settings; + } + + /** + * Set the layout for the active configuration. + * + * @param activeConfig - The active gamepad configuration. + * @returns `true` if the layout was successfully applied, otherwise `false`. + */ + setLayout(activeConfig: InterfaceConfig): boolean { + // Check if there is no active configuration (e.g., no gamepad connected). + if (!activeConfig) { + // Retrieve the layout for when no gamepads are connected. + const layout = this.layout['noGamepads']; + // Make the options container visible to show message. + layout.optionsContainer.setVisible(true); + // Return false indicating the layout application was not successful due to lack of gamepad. + return false; + } + + return super.setLayout(activeConfig); + } + + + /** + * Navigate to the left menu tab. + * + * @returns `true` indicating the navigation was successful. + */ + navigateMenuLeft(): boolean { + this.scene.ui.setMode(Mode.SETTINGS) + return true; + } + + /** + * Navigate to the right menu tab. + * + * @returns `true` indicating the navigation was successful. + */ + navigateMenuRight(): boolean { + this.scene.ui.setMode(Mode.SETTINGS_KEYBOARD) + return true; + } + + /** + * Update the display of the chosen gamepad. + */ + updateChosenGamepadDisplay(): void { + // Update any bindings that might have changed since the last update. + this.updateBindings(); + this.resetScroll(); + + // Iterate over the keys in the settingDevice enumeration. + for (const [index, key] of Object.keys(this.settingDevice).entries()) { + const setting = this.settingDevice[key] // Get the actual setting value using the key. + + // Check if the current setting corresponds to the default controller setting. + if (setting === this.settingDevice.Default_Controller) { + // Iterate over all layouts excluding the 'noGamepads' special case. + for (const _key of Object.keys(this.layout)) { + if (_key === 'noGamepads') continue; // Skip updating the no gamepad layout. + + // Update the text of the first option label under the current setting to the name of the chosen gamepad, + // truncating the name to 30 characters if necessary. + this.layout[_key].optionValueLabels[index][0].setText(truncateString(this.scene.inputController.selectedDevice[Device.GAMEPAD], 30)); + } + } + } + } + + /** + * Save the setting to local storage. + * + * @param setting - The setting to save. + * @param cursor - The cursor position to save. + */ + saveSettingToLocalStorage(setting, cursor): void { + if (this.settingDevice[setting] !== this.settingDevice.Default_Controller) + this.scene.gameData.saveGamepadSetting(setting, cursor) + } +} \ No newline at end of file diff --git a/src/ui/settings/settings-keyboard-ui-handler.ts b/src/ui/settings/settings-keyboard-ui-handler.ts new file mode 100644 index 00000000000..f51b7416da9 --- /dev/null +++ b/src/ui/settings/settings-keyboard-ui-handler.ts @@ -0,0 +1,215 @@ +import BattleScene from "../../battle-scene"; +import {Mode} from "../ui"; +import cfg_keyboard_azerty from "#app/configs/cfg_keyboard_azerty"; +import { + setSettingKeyboard, + SettingKeyboard, + settingKeyboardBlackList, + settingKeyboardDefaults, + settingKeyboardOptions +} from "#app/system/settings-keyboard"; +import {reverseValueToKeySetting, truncateString} from "#app/utils"; +import AbstractSettingsUiUiHandler from "#app/ui/settings/abstract-settings-ui-handler"; +import {InterfaceConfig} from "#app/inputs-controller"; +import {addTextObject, TextStyle} from "#app/ui/text"; +import {deleteBind} from "#app/configs/configHandler"; +import {Device} from "#app/enums/devices"; + +/** + * Class representing the settings UI handler for keyboards. + * + * @extends AbstractSettingsUiUiHandler + */ +export default class SettingsKeyboardUiHandler extends AbstractSettingsUiUiHandler { + /** + * Creates an instance of SettingsKeyboardUiHandler. + * + * @param scene - The BattleScene instance. + * @param mode - The UI mode, optional. + */ + constructor(scene: BattleScene, mode?: Mode) { + super(scene, mode); + this.titleSelected = 'Keyboard'; + this.settingDevice = SettingKeyboard; + this.settingDeviceDefaults = settingKeyboardDefaults; + this.settingDeviceOptions = settingKeyboardOptions; + this.configs = [cfg_keyboard_azerty]; + this.commonSettingsCount = 0; + this.textureOverride = 'keyboard'; + this.localStoragePropertyName = 'settingsKeyboard'; + this.settingBlacklisted = settingKeyboardBlackList; + + const deleteEvent = scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.DELETE); + const restoreDefaultEvent = scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.HOME); + deleteEvent.on('up', this.onDeleteDown, this); + restoreDefaultEvent.on('up', this.onHomeDown, this); + } + + setSetting(scene: BattleScene, setting, value: integer): boolean { + return setSettingKeyboard(scene, setting, value); + } + + /** + * Setup UI elements. + */ + setup() { + super.setup(); + // If no gamepads are detected, set up a default UI prompt in the settings container. + this.layout['noKeyboard'] = new Map(); + const optionsContainer = this.scene.add.container(0, 0); + optionsContainer.setVisible(false); // Initially hide the container as no gamepads are connected. + const label = addTextObject(this.scene, 8, 28, 'Please press a key on your keyboard', TextStyle.SETTINGS_LABEL); + label.setOrigin(0, 0); + optionsContainer.add(label); + this.settingsContainer.add(optionsContainer); + + const iconDelete = this.scene.add.sprite(0, 0, 'keyboard'); + iconDelete.setScale(.1); + iconDelete.setOrigin(0, -0.1); + iconDelete.setPositionRelative(this.actionsBg, this.headerBg.width - 260, 4); + this.navigationIcons['BUTTON_DELETE'] = iconDelete; + + const deleteText = addTextObject(this.scene, 0, 0, 'Delete', TextStyle.SETTINGS_LABEL); + deleteText.setOrigin(0, 0); + deleteText.setPositionRelative(iconDelete, -deleteText.width/6, -(deleteText.height/6)/2 - 6); + + this.settingsContainer.add(iconDelete) + this.settingsContainer.add(deleteText) + + + + // Map the 'noKeyboard' layout options for easy access. + this.layout['noKeyboard'].optionsContainer = optionsContainer; + this.layout['noKeyboard'].label = label; + } + + /** + * Handle the home key press event. + */ + onHomeDown(): void { + if (![Mode.SETTINGS_KEYBOARD, Mode.SETTINGS_GAMEPAD].includes(this.scene.ui.getMode())) return; + this.scene.gameData.resetMappingToFactory(); + } + + /** + * Handle the delete key press event. + */ + onDeleteDown(): void { + if (this.scene.ui.getMode() !== Mode.SETTINGS_KEYBOARD) return; + const cursor = this.cursor + this.scrollCursor; // Calculate the absolute cursor position. + const selection = this.settingLabels[cursor].text; + const key = reverseValueToKeySetting(selection); + const settingName = SettingKeyboard[key]; + const activeConfig = this.getActiveConfig(); + const success = deleteBind(this.getActiveConfig(), settingName); + if (success) { + this.saveCustomKeyboardMappingToLocalStorage(activeConfig); + this.updateBindings(); + } + } + + /** + * Get the active configuration. + * + * @returns The active keyboard configuration. + */ + getActiveConfig(): InterfaceConfig { + return this.scene.inputController.getActiveConfig(Device.KEYBOARD); + } + + /** + * Get the keyboard settings from local storage. + * + * @returns The keyboard settings from local storage. + */ + getLocalStorageSetting(): object { + // Retrieve the gamepad settings from local storage or use an empty object if none exist. + const settings: object = localStorage.hasOwnProperty('settingsKeyboard') ? JSON.parse(localStorage.getItem('settingsKeyboard')) : {}; + return settings; + } + + /** + * Set the layout for the active configuration. + * + * @param activeConfig - The active keyboard configuration. + * @returns `true` if the layout was successfully applied, otherwise `false`. + */ + setLayout(activeConfig: InterfaceConfig): boolean { + // Check if there is no active configuration (e.g., no gamepad connected). + if (!activeConfig) { + // Retrieve the layout for when no gamepads are connected. + const layout = this.layout['noKeyboard']; + // Make the options container visible to show message. + layout.optionsContainer.setVisible(true); + // Return false indicating the layout application was not successful due to lack of gamepad. + return false; + } + + return super.setLayout(activeConfig); + } + + /** + * Navigate to the left menu tab. + * + * @returns `true` indicating the navigation was successful. + */ + navigateMenuLeft(): boolean { + this.scene.ui.setMode(Mode.SETTINGS_GAMEPAD) + return true; + } + + /** + * Navigate to the right menu tab. + * + * @returns `true` indicating the navigation was successful. + */ + navigateMenuRight(): boolean { + this.scene.ui.setMode(Mode.SETTINGS) + return true; + } + + /** + * Update the display of the chosen keyboard layout. + */ + updateChosenKeyboardDisplay(): void { + // Update any bindings that might have changed since the last update. + this.updateBindings(); + + // Iterate over the keys in the settingDevice enumeration. + for (const [index, key] of Object.keys(this.settingDevice).entries()) { + const setting = this.settingDevice[key] // Get the actual setting value using the key. + + // Check if the current setting corresponds to the default controller setting. + if (setting === this.settingDevice.Default_Layout) { + // Iterate over all layouts excluding the 'noGamepads' special case. + for (const _key of Object.keys(this.layout)) { + if (_key === 'noKeyboard') continue; // Skip updating the no gamepad layout. + // Update the text of the first option label under the current setting to the name of the chosen gamepad, + // truncating the name to 30 characters if necessary. + this.layout[_key].optionValueLabels[index][0].setText(truncateString(this.scene.inputController.selectedDevice[Device.KEYBOARD], 22)); + } + } + } + + } + + /** + * Save the custom keyboard mapping to local storage. + * + * @param config - The configuration to save. + */ + saveCustomKeyboardMappingToLocalStorage(config): void { + this.scene.gameData.saveMappingConfigs(this.scene.inputController?.selectedDevice[Device.KEYBOARD], config); + } + + /** + * Save the setting to local storage. + * + * @param settingName - The name of the setting to save. + * @param cursor - The cursor position to save. + */ + saveSettingToLocalStorage(settingName, cursor): void { + if (this.settingDevice[settingName] !== this.settingDevice.Default_Layout) + this.scene.gameData.saveKeyboardSetting(settingName, cursor) + } +} \ No newline at end of file diff --git a/src/ui/settings-ui-handler.ts b/src/ui/settings/settings-ui-handler.ts similarity index 65% rename from src/ui/settings-ui-handler.ts rename to src/ui/settings/settings-ui-handler.ts index 6e40103b870..4b64343bed1 100644 --- a/src/ui/settings-ui-handler.ts +++ b/src/ui/settings/settings-ui-handler.ts @@ -1,11 +1,12 @@ -import BattleScene from "../battle-scene"; -import { Setting, reloadSettings, settingDefaults, settingOptions } from "../system/settings"; -import { hasTouchscreen, isMobile } from "../touch-controls"; -import { TextStyle, addTextObject } from "./text"; -import { Mode } from "./ui"; -import UiHandler from "./ui-handler"; -import { addWindow } from "./ui-theme"; -import {Button} from "../enums/buttons"; +import BattleScene from "../../battle-scene"; +import {Setting, reloadSettings, settingDefaults, settingOptions} from "../../system/settings"; +import { hasTouchscreen, isMobile } from "../../touch-controls"; +import { TextStyle, addTextObject } from "../text"; +import { Mode } from "../ui"; +import UiHandler from "../ui-handler"; +import { addWindow } from "../ui-theme"; +import {Button} from "../../enums/buttons"; +import {InputsIcons} from "#app/ui/settings/abstract-settings-ui-handler"; export default class SettingsUiHandler extends UiHandler { private settingsContainer: Phaser.GameObjects.Container; @@ -20,16 +21,20 @@ export default class SettingsUiHandler extends UiHandler { private settingLabels: Phaser.GameObjects.Text[]; private optionValueLabels: Phaser.GameObjects.Text[][]; + protected navigationIcons: InputsIcons; + private cursorObj: Phaser.GameObjects.NineSlice; private reloadRequired: boolean; private reloadI18n: boolean; + private rowsToDisplay: number; constructor(scene: BattleScene, mode?: Mode) { super(scene, mode); this.reloadRequired = false; this.reloadI18n = false; + this.rowsToDisplay = 8; } setup() { @@ -37,18 +42,64 @@ export default class SettingsUiHandler extends UiHandler { this.settingsContainer = this.scene.add.container(1, -(this.scene.game.canvas.height / 6) + 1); - this.settingsContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains); + this.settingsContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6) - 20, Phaser.Geom.Rectangle.Contains); + + this.navigationIcons = {}; const headerBg = addWindow(this.scene, 0, 0, (this.scene.game.canvas.width / 6) - 2, 24); headerBg.setOrigin(0, 0); - const headerText = addTextObject(this.scene, 0, 0, 'Options', TextStyle.SETTINGS_LABEL); - headerText.setOrigin(0, 0); - headerText.setPositionRelative(headerBg, 8, 4); - - this.optionsBg = addWindow(this.scene, 0, headerBg.height, (this.scene.game.canvas.width / 6) - 2, (this.scene.game.canvas.height / 6) - headerBg.height - 2); + this.optionsBg = addWindow(this.scene, 0, headerBg.height, (this.scene.game.canvas.width / 6) - 2, (this.scene.game.canvas.height / 6) - 16 - headerBg.height - 2); this.optionsBg.setOrigin(0, 0); + const actionsBg = addWindow(this.scene, 0, (this.scene.game.canvas.height / 6) - headerBg.height, (this.scene.game.canvas.width / 6) - 2, 22); + actionsBg.setOrigin(0, 0); + + const iconPreviousTab = this.scene.add.sprite(0, 0, 'keyboard'); + iconPreviousTab.setScale(.1); + iconPreviousTab.setOrigin(0, -0.1); + iconPreviousTab.setPositionRelative(headerBg, 8, 4); + this.navigationIcons['BUTTON_CYCLE_FORM'] = iconPreviousTab; + + const iconNextTab = this.scene.add.sprite(0, 0, 'keyboard'); + iconNextTab.setScale(.1); + iconNextTab.setOrigin(0, -0.1); + iconNextTab.setPositionRelative(headerBg, headerBg.width - 20, 4); + this.navigationIcons['BUTTON_CYCLE_SHINY'] = iconNextTab; + + const iconAction = this.scene.add.sprite(0, 0, 'keyboard'); + iconAction.setScale(.1); + iconAction.setOrigin(0, -0.1); + iconAction.setPositionRelative(actionsBg, headerBg.width - 20, 4); + this.navigationIcons['BUTTON_ACTION'] = iconAction; + + const actionText = addTextObject(this.scene, 0, 0, 'Action', TextStyle.SETTINGS_LABEL); + actionText.setOrigin(0, 0); + actionText.setPositionRelative(iconAction, 0, 2); + actionText.setPositionRelative(iconAction, -actionText.width/6, -(actionText.height/6)/2 - 6); + + const iconCancel = this.scene.add.sprite(0, 0, 'keyboard'); + iconCancel.setScale(.1); + iconCancel.setOrigin(0, -0.1); + iconCancel.setPositionRelative(actionsBg, headerBg.width - 100, 4); + this.navigationIcons['BUTTON_CANCEL'] = iconCancel; + + const cancelText = addTextObject(this.scene, 0, 0, 'Cancel', TextStyle.SETTINGS_LABEL); + cancelText.setOrigin(0, 0); + cancelText.setPositionRelative(iconCancel, -cancelText.width/6, -(cancelText.height/6)/2 - 6); + + const headerText = addTextObject(this.scene, 0, 0, 'General', TextStyle.SETTINGS_SELECTED); + headerText.setOrigin(0, 0); + headerText.setPositionRelative(headerBg, 8 + iconPreviousTab.width/6 - 4, 4); + + const gamepadText = addTextObject(this.scene, 0, 0, 'Gamepad', TextStyle.SETTINGS_LABEL); + gamepadText.setOrigin(0, 0); + gamepadText.setPositionRelative(headerBg, 50 + iconPreviousTab.width/6 - 4, 4); + + const keyboardText = addTextObject(this.scene, 0, 0, 'Keyboard', TextStyle.SETTINGS_LABEL); + keyboardText.setOrigin(0, 0); + keyboardText.setPositionRelative(headerBg, 97 + iconPreviousTab.width/6 - 4, 4); + this.optionsContainer = this.scene.add.container(0, 0); this.settingLabels = []; @@ -92,8 +143,17 @@ export default class SettingsUiHandler extends UiHandler { this.settingsContainer.add(headerBg); this.settingsContainer.add(headerText); + this.settingsContainer.add(gamepadText); + this.settingsContainer.add(keyboardText); this.settingsContainer.add(this.optionsBg); + this.settingsContainer.add(actionsBg); this.settingsContainer.add(this.optionsContainer); + this.settingsContainer.add(iconNextTab) + this.settingsContainer.add(iconAction) + this.settingsContainer.add(iconCancel) + this.settingsContainer.add(iconPreviousTab) + this.settingsContainer.add(actionText) + this.settingsContainer.add(cancelText) ui.add(this.settingsContainer); @@ -103,9 +163,29 @@ export default class SettingsUiHandler extends UiHandler { this.settingsContainer.setVisible(false); } + updateBindings(): void { + for (const settingName of Object.keys(this.navigationIcons)) { + if (settingName === 'BUTTON_HOME') { + this.navigationIcons[settingName].setTexture("keyboard"); + this.navigationIcons[settingName].setFrame("T_Home_Key_Dark.png"); + this.navigationIcons[settingName].alpha = 1; + continue + } + const icon = this.scene.inputController?.getIconForLatestInputRecorded(settingName); + if (icon) { + const type = this.scene.inputController?.getLastSourceType(); + this.navigationIcons[settingName].setTexture(type); + this.navigationIcons[settingName].setFrame(icon); + this.navigationIcons[settingName].alpha = 1; + } else + this.navigationIcons[settingName].alpha = 0; + } + } + show(args: any[]): boolean { super.show(args); - + this.updateBindings(); + const settings: object = localStorage.hasOwnProperty('settings') ? JSON.parse(localStorage.getItem('settings')) : {}; Object.keys(settingDefaults).forEach((setting, s) => this.setOptionCursor(s, settings.hasOwnProperty(setting) ? settings[setting] : settingDefaults[setting])); @@ -132,7 +212,6 @@ export default class SettingsUiHandler extends UiHandler { processInput(button: Button): boolean { const ui = this.getUi(); // Defines the maximum number of rows that can be displayed on the screen. - const rowsToDisplay = 9; let success = false; @@ -152,17 +231,17 @@ export default class SettingsUiHandler extends UiHandler { } else { // When at the top of the menu and pressing UP, move to the bottommost item. // First, set the cursor to the last visible element, preparing for the scroll to the end. - const successA = this.setCursor(rowsToDisplay - 1); + const successA = this.setCursor(this.rowsToDisplay - 1); // Then, adjust the scroll to display the bottommost elements of the menu. - const successB = this.setScrollCursor(this.optionValueLabels.length - rowsToDisplay); + const successB = this.setScrollCursor(this.optionValueLabels.length - this.rowsToDisplay); success = successA && successB; // success is just there to play the little validation sound effect } break; case Button.DOWN: if (cursor < this.optionValueLabels.length - 1) { - if (this.cursor < rowsToDisplay - 1) // if the visual cursor is in the frame of 0 to 8 + if (this.cursor < this.rowsToDisplay - 1) // if the visual cursor is in the frame of 0 to 8 success = this.setCursor(this.cursor + 1); - else if (this.scrollCursor < this.optionValueLabels.length - rowsToDisplay) + else if (this.scrollCursor < this.optionValueLabels.length - this.rowsToDisplay) success = this.setScrollCursor(this.scrollCursor + 1); } else { // When at the bottom of the menu and pressing DOWN, move to the topmost item. @@ -182,6 +261,14 @@ export default class SettingsUiHandler extends UiHandler { if (this.optionCursors[cursor] < this.optionValueLabels[cursor].length - 1) success = this.setOptionCursor(cursor, this.optionCursors[cursor] + 1, true); break; + case Button.CYCLE_FORM: // to the left + this.scene.ui.setMode(Mode.SETTINGS_KEYBOARD) + success = true; + break; + case Button.CYCLE_SHINY: // to the right + this.scene.ui.setMode(Mode.SETTINGS_GAMEPAD) + success = true; + break; } } @@ -255,7 +342,7 @@ export default class SettingsUiHandler extends UiHandler { this.optionsContainer.setY(-16 * this.scrollCursor); for (let s = 0; s < this.settingLabels.length; s++) { - const visible = s >= this.scrollCursor && s < this.scrollCursor + 9; + const visible = s >= this.scrollCursor && s < this.scrollCursor + this.rowsToDisplay; this.settingLabels[s].setVisible(visible); for (let option of this.optionValueLabels[s]) option.setVisible(visible); diff --git a/src/ui/text.ts b/src/ui/text.ts index 8be46b1b238..5edf1dfd2dd 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -24,6 +24,7 @@ export enum TextStyle { MONEY, SETTINGS_LABEL, SETTINGS_SELECTED, + SETTINGS_LOCKED, TOOLTIP_TITLE, TOOLTIP_CONTENT, MOVE_INFO_CONTENT @@ -110,6 +111,7 @@ function getTextStyleOptions(style: TextStyle, uiTheme: UiTheme, extraStyleOptio case TextStyle.WINDOW_ALT: case TextStyle.MESSAGE: case TextStyle.SETTINGS_LABEL: + case TextStyle.SETTINGS_LOCKED: case TextStyle.SETTINGS_SELECTED: styleOptions.fontSize = languageSettings[lang]?.summaryFontSize || '96px'; break; @@ -187,6 +189,7 @@ export function getTextColor(textStyle: TextStyle, shadow?: boolean, uiTheme: Ui case TextStyle.SUMMARY_GOLD: case TextStyle.MONEY: return !shadow ? '#e8e8a8' : '#a0a060'; + case TextStyle.SETTINGS_LOCKED: case TextStyle.SUMMARY_GRAY: return !shadow ? '#a0a0a0' : '#636363'; case TextStyle.SUMMARY_GREEN: diff --git a/src/ui/title-ui-handler.ts b/src/ui/title-ui-handler.ts index 4da4f189f6b..7851454d650 100644 --- a/src/ui/title-ui-handler.ts +++ b/src/ui/title-ui-handler.ts @@ -1,6 +1,6 @@ import BattleScene from "../battle-scene"; import { DailyRunScoreboard } from "./daily-run-scoreboard"; -import OptionSelectUiHandler from "./option-select-ui-handler"; +import OptionSelectUiHandler from "./settings/option-select-ui-handler"; import { Mode } from "./ui"; import * as Utils from "../utils"; import { TextStyle, addTextObject } from "./text"; diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 09deb2bdd7a..0f2ac2447a9 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -12,12 +12,13 @@ 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-ui-handler'; +import SettingsUiHandler from './settings/settings-ui-handler'; +import SettingsGamepadUiHandler from "./settings/settings-gamepad-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 './option-select-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'; @@ -36,6 +37,9 @@ 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 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"; export enum Mode { MESSAGE, @@ -56,6 +60,10 @@ export enum Mode { MENU, MENU_OPTION_SELECT, SETTINGS, + SETTINGS_GAMEPAD, + GAMEPAD_BINDING, + SETTINGS_KEYBOARD, + KEYBOARD_BINDING, ACHIEVEMENTS, GAME_STATS, VOUCHERS, @@ -86,7 +94,11 @@ const noTransitionModes = [ Mode.OPTION_SELECT, Mode.MENU, Mode.MENU_OPTION_SELECT, + Mode.GAMEPAD_BINDING, + Mode.KEYBOARD_BINDING, Mode.SETTINGS, + Mode.SETTINGS_GAMEPAD, + Mode.SETTINGS_KEYBOARD, Mode.ACHIEVEMENTS, Mode.GAME_STATS, Mode.VOUCHERS, @@ -137,6 +149,10 @@ export default class UI extends Phaser.GameObjects.Container { new MenuUiHandler(scene), new OptionSelectUiHandler(scene, Mode.MENU_OPTION_SELECT), new SettingsUiHandler(scene), + new SettingsGamepadUiHandler(scene), + new GamepadBindingUiHandler(scene), + new SettingsKeyboardUiHandler(scene), + new KeyboardBindingUiHandler(scene), new AchvsUiHandler(scene), new GameStatsUiHandler(scene), new VouchersUiHandler(scene), diff --git a/src/utils.ts b/src/utils.ts index 142de91c1ca..2799937c3f2 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -336,4 +336,47 @@ export function rgbHexToRgba(hex: string) { export function rgbaToInt(rgba: integer[]): integer { return (rgba[0] << 24) + (rgba[1] << 16) + (rgba[2] << 8) + rgba[3]; +} + +/** + * Truncate a string to a specified maximum length and add an ellipsis if it exceeds that length. + * + * @param str - The string to be truncated. + * @param maxLength - The maximum length of the truncated string, defaults to 10. + * @returns The truncated string with an ellipsis if it was longer than maxLength. + */ +export function truncateString(str: String, maxLength: number = 10) { + // Check if the string length exceeds the maximum length + if (str.length > maxLength) { + // Truncate the string and add an ellipsis + return str.slice(0, maxLength - 3) + "..."; // Subtract 3 to accommodate the ellipsis + } + // Return the original string if it does not exceed the maximum length + return str; +} + +/** + * Perform a deep copy of an object. + * + * @param values - The object to be deep copied. + * @returns A new object that is a deep copy of the input. + */ +export function deepCopy(values: object): object { + // Convert the object to a JSON string and parse it back to an object to perform a deep copy + return JSON.parse(JSON.stringify(values)); +} + +/** + * Convert a space-separated string into a capitalized and underscored string. + * + * @param input - The string to be converted. + * @returns The converted string with words capitalized and separated by underscores. + */ +export function reverseValueToKeySetting(input) { + // Split the input string into an array of words + const words = input.split(' '); + // Capitalize the first letter of each word and convert the rest to lowercase + const capitalizedWords = words.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()); + // Join the capitalized words with underscores and return the result + return capitalizedWords.join('_'); } \ No newline at end of file