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 00000000000..84bd1a022de Binary files /dev/null and b/public/images/inputs/dualshock.png differ 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 00000000000..af019a60da1 Binary files /dev/null and b/public/images/inputs/keyboard.png differ diff --git a/public/images/inputs/nswitch.json b/public/images/inputs/nswitch.json new file mode 100644 index 00000000000..441e70d47e8 --- /dev/null +++ b/public/images/inputs/nswitch.json @@ -0,0 +1,356 @@ +{"frames": { + +"T_S_A_Alt.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_S_B_Alt.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_S_Con_Alt.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_S_Con_Left_Alt.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_S_Con_Right_Alt.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_S_Con_Separate_Alt.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_S_Down_Alt.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_S_Dpad_Alt.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_S_Dpad_Down_Alt.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_S_Dpad_Left_Alt.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_S_Dpad_Right_Alt.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_S_Dpad_Up_Alt.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_S_Dpad_X_Alt.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_S_Dpad_Y_Alt.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_S_Home_Alt.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_S_L_2D_Alt.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_S_L_Alt.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_S_L_Down_Alt.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_S_L_Left_Alt.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_S_L_Right_Alt.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_S_L_Up_Alt.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_S_L_X_Alt.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_S_L_Y_Alt.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_S_LB_Alt.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_S_Left_Alt.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_S_LT_Alt.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_S_Minus_Alt.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_S_Plus_Alt.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_S_R_2D_Alt.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_S_R_Alt.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_S_R_Down_Alt.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_S_R_Left_Alt.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_S_R_Right_Alt.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_S_R_Up_Alt.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} +}, +"T_S_R_X_Alt.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} +}, +"T_S_R_Y_Alt.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} +}, +"T_S_RB_Alt.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} +}, +"T_S_Right_Alt.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} +}, +"T_S_RT_Alt.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} +}, +"T_S_Square_Alt.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} +}, +"T_S_Up_Alt.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} +}, +"T_S_X_Alt.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} +}, +"T_S_Y_Alt.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} +}}, +"meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "1.0", + "image": "nswitch.png", + "format": "RGBA8888", + "size": {"w":1408,"h":512}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:9256c2fb9e9658d02a3c87f7803e4cf0:da5e7131e2452535087974b43fe1c89f:aeba0349fc8bd3742630ffd8f1f1df7a$" +} +} diff --git a/public/images/inputs/nswitch.png b/public/images/inputs/nswitch.png new file mode 100644 index 00000000000..49b54d46a76 Binary files /dev/null and b/public/images/inputs/nswitch.png differ diff --git a/public/images/inputs/snes.json b/public/images/inputs/snes.json new file mode 100644 index 00000000000..2ffe0876405 --- /dev/null +++ b/public/images/inputs/snes.json @@ -0,0 +1,108 @@ +{"frames": [ + +{ + "filename": "select.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": "start.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_X_A_White_Alt.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_X_B_White_Alt.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_X_Dpad_Down_Alt.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_X_Dpad_Left_Alt.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_X_Dpad_Right_Alt.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_X_Dpad_Up_Alt.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_X_LB_Alt.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_X_RB_Alt.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_X_X_White_Alt.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_X_Y_White_Alt.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} +}], +"meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "1.0", + "image": "snes.png", + "format": "RGBA8888", + "size": {"w":1536,"h":128}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:86d994650b80a3f876cc92e4d0928a65:60b6f706b30e87e11c3d01cec0eccf9c:7e443c950e063b6a235f0fd6295e2000$" +} +} diff --git a/public/images/inputs/snes.png b/public/images/inputs/snes.png new file mode 100644 index 00000000000..94b1d1ce72c Binary files /dev/null and b/public/images/inputs/snes.png differ diff --git a/public/images/inputs/xbox.json b/public/images/inputs/xbox.json new file mode 100644 index 00000000000..0422aac9e74 --- /dev/null +++ b/public/images/inputs/xbox.json @@ -0,0 +1,348 @@ +{"frames": { + +"T_X_A_Color_Alt.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_X_A_White_Alt.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_X_B_Color_Alt.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_X_B_White_Alt.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_X_Dpad_Alt.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_X_Dpad_Down_Alt.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_X_Dpad_Left_Alt.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_X_Dpad_Right_Alt.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_X_Dpad_Up_Alt.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_X_Dpad_X_Alt.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_X_Dpad_Y_Alt.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_X_L_2D_Alt.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_X_L_Alt.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_X_L_Down_Alt.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_X_L_Left_Alt.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_X_L_Right_Alt.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_X_L_UP_Alt.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_X_L_X_Alt.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_X_L_Y_Alt.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_X_LB_Alt.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_X_Left_Stick_Click_Alt.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_X_Left_Stick_Click_Alt_Alt.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_X_LT_Alt.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_X_R_2D_Alt.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_X_R_Alt.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_X_R_Down_Alt.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_X_R_Left_Alt.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_X_R_Right_Alt.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_X_R_UP_Alt.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_X_R_X_Alt.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_X_R_Y_Alt.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_X_RB_Alt.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_X_Right_Stick_Click_Alt.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_X_Right_Stick_Click_Alt_Alt.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_X_RT_Alt.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_X_Share_Alt-1.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_X_Share_Alt.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_X_X_Alt.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_X_X_Color_Alt.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_X_X_White_Alt.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_X_Y_Color_Alt.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_X_Y_White_Alt.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": "xbox.png", + "format": "RGBA8888", + "size": {"w":1792,"h":384}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:4e419a6ed1d6fe68e572af3e46338b9b:30674cf3e9fcf41b5fb5ab36fa87043f:7ad6008cd8fa3f9f4bfb17e0cfcbbb64$" +} +} diff --git a/public/images/inputs/xbox.png b/public/images/inputs/xbox.png new file mode 100644 index 00000000000..5b18161f863 Binary files /dev/null and b/public/images/inputs/xbox.png differ diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 0f75447a500..8845c11f12d 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -33,9 +33,6 @@ import TrainerData from './system/trainer-data'; import SoundFade from 'phaser3-rex-plugins/plugins/soundfade'; import { pokemonPrevolutions } from './data/pokemon-evolutions'; import PokeballTray from './ui/pokeball-tray'; -import { Setting, settingOptions } from './system/settings'; -import SettingsUiHandler from './ui/settings-ui-handler'; -import MessageUiHandler from './ui/message-ui-handler'; import { Species } from './data/enums/species'; import InvertPostFX from './pipelines/invert'; import { Achv, ModifierAchv, MoneyAchv, achvs } from './system/achv'; @@ -119,7 +116,6 @@ export default class BattleScene extends SceneBase { public fusionPaletteSwaps: boolean = true; public enableTouchControls: boolean = false; public enableVibration: boolean = false; - public abSwapped: boolean = false; public disableMenu: boolean = false; diff --git a/src/configs/cfg_keyboard_azerty.ts b/src/configs/cfg_keyboard_azerty.ts new file mode 100644 index 00000000000..38f1c5e381c --- /dev/null +++ b/src/configs/cfg_keyboard_azerty.ts @@ -0,0 +1,291 @@ +import {Button} from "#app/enums/buttons"; +import {SettingKeyboard} from "#app/system/settings-keyboard"; + +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: { + [SettingKeyboard.Button_Up]: Button.UP, + [SettingKeyboard.Button_Down]: Button.DOWN, + [SettingKeyboard.Button_Left]: Button.LEFT, + [SettingKeyboard.Button_Right]: Button.RIGHT, + [SettingKeyboard.Button_Submit]: Button.SUBMIT, + [SettingKeyboard.Button_Action]: Button.ACTION, + [SettingKeyboard.Button_Cancel]: Button.CANCEL, + [SettingKeyboard.Button_Menu]: Button.MENU, + [SettingKeyboard.Button_Stats]: Button.STATS, + [SettingKeyboard.Button_Cycle_Shiny]: Button.CYCLE_SHINY, + [SettingKeyboard.Button_Cycle_Form]: Button.CYCLE_FORM, + [SettingKeyboard.Button_Cycle_Gender]: Button.CYCLE_GENDER, + [SettingKeyboard.Button_Cycle_Ability]: Button.CYCLE_ABILITY, + [SettingKeyboard.Button_Cycle_Nature]: Button.CYCLE_NATURE, + [SettingKeyboard.Button_Cycle_Variant]: Button.CYCLE_VARIANT, + [SettingKeyboard.Button_Speed_Up]: Button.SPEED_UP, + [SettingKeyboard.Button_Slow_Down]: Button.SLOW_DOWN, + [SettingKeyboard.Alt_Button_Up]: Button.UP, + [SettingKeyboard.Alt_Button_Down]: Button.DOWN, + [SettingKeyboard.Alt_Button_Left]: Button.LEFT, + [SettingKeyboard.Alt_Button_Right]: Button.RIGHT, + [SettingKeyboard.Alt_Button_Submit]: Button.SUBMIT, + [SettingKeyboard.Alt_Button_Action]: Button.ACTION, + [SettingKeyboard.Alt_Button_Cancel]: Button.CANCEL, + [SettingKeyboard.Alt_Button_Menu]: Button.MENU, + [SettingKeyboard.Alt_Button_Stats]: Button.STATS, + [SettingKeyboard.Alt_Button_Cycle_Shiny]: Button.CYCLE_SHINY, + [SettingKeyboard.Alt_Button_Cycle_Form]: Button.CYCLE_FORM, + [SettingKeyboard.Alt_Button_Cycle_Gender]: Button.CYCLE_GENDER, + [SettingKeyboard.Alt_Button_Cycle_Ability]: Button.CYCLE_ABILITY, + [SettingKeyboard.Alt_Button_Cycle_Nature]: Button.CYCLE_NATURE, + [SettingKeyboard.Alt_Button_Cycle_Variant]: Button.CYCLE_VARIANT, + [SettingKeyboard.Alt_Button_Speed_Up]: Button.SPEED_UP, + [SettingKeyboard.Alt_Button_Slow_Down]: Button.SLOW_DOWN, + }, + default: { + KEY_ARROW_UP: SettingKeyboard.Button_Up, + KEY_ARROW_DOWN: SettingKeyboard.Button_Down, + KEY_ARROW_LEFT: SettingKeyboard.Button_Left, + KEY_ARROW_RIGHT: SettingKeyboard.Button_Right, + KEY_ENTER: SettingKeyboard.Button_Submit, + KEY_SPACE: SettingKeyboard.Button_Action, + KEY_BACKSPACE: SettingKeyboard.Button_Cancel, + KEY_ESC: SettingKeyboard.Button_Menu, + KEY_C: SettingKeyboard.Button_Stats, + KEY_R: SettingKeyboard.Button_Cycle_Shiny, + KEY_F: SettingKeyboard.Button_Cycle_Form, + KEY_G: SettingKeyboard.Button_Cycle_Gender, + KEY_E: SettingKeyboard.Button_Cycle_Ability, + KEY_N: SettingKeyboard.Button_Cycle_Nature, + KEY_V: SettingKeyboard.Button_Cycle_Variant, + KEY_PLUS: SettingKeyboard.Button_Speed_Up, + KEY_MINUS: SettingKeyboard.Button_Slow_Down, + KEY_A: -1, + KEY_B: -1, + KEY_D: SettingKeyboard.Alt_Button_Right, + KEY_H: -1, + KEY_I: SettingKeyboard.Alt_Button_Cycle_Nature, + KEY_J: -1, + KEY_K: SettingKeyboard.Alt_Button_Cycle_Variant, + KEY_L: SettingKeyboard.Alt_Button_Cycle_Ability, + KEY_M: SettingKeyboard.Alt_Button_Cycle_Form, + KEY_O: SettingKeyboard.Alt_Button_Cycle_Gender, + KEY_P: SettingKeyboard.Alt_Button_Cycle_Shiny, + KEY_Q: SettingKeyboard.Alt_Button_Left, + KEY_S: SettingKeyboard.Alt_Button_Down, + KEY_T: -1, + KEY_U: -1, + KEY_W: SettingKeyboard.Alt_Button_Action, + KEY_X: SettingKeyboard.Alt_Button_Cancel, + KEY_Y: -1, + KEY_Z: SettingKeyboard.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: SettingKeyboard.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: SettingKeyboard.Alt_Button_Slow_Down, + KEY_PAGE_UP: SettingKeyboard.Alt_Button_Speed_Up, + KEY_QUOTATION: -1, + KEY_SHIFT: SettingKeyboard.Alt_Button_Stats, + KEY_TAB: SettingKeyboard.Alt_Button_Menu, + KEY_TILDE: -1, + KEY_LEFT_BRACKET: -1, + KEY_RIGHT_BRACKET: -1, + KEY_SEMICOLON: -1, + KEY_ALT: -1 + }, + blacklist: [ + "KEY_ENTER", + "KEY_ESC", + "KEY_ARROW_UP", + "KEY_ARROW_DOWN", + "KEY_ARROW_LEFT", + "KEY_ARROW_RIGHT", + "KEY_DELETE", + "KEY_HOME", + ] +}; + +export default cfg_keyboard_azerty; diff --git a/src/configs/configHandler.ts b/src/configs/configHandler.ts new file mode 100644 index 00000000000..0758bfd4a6d --- /dev/null +++ b/src/configs/configHandler.ts @@ -0,0 +1,186 @@ +import {Device} from "#app/enums/devices"; + +/** + * Retrieves the key associated with the specified keycode from the mapping. + * + * @param config - The configuration object containing the mapping. + * @param keycode - The keycode to search for. + * @returns The key associated with the specified keycode. + */ +export function getKeyWithKeycode(config, keycode) { + return Object.keys(config.deviceMapping).find(key => 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