Menu - Controls Rebind - Gamepad & Keyboard (Cleaner git log) (#1666)

* squased merge rebind_menu

* azerty to qwerty

* add a check to preven a crash in firefox

* reset navigation menu on quit

* removed dual lock mekanism

* navigation display update icons on new bind

* added submit binding

* removed attribute no longer used

* change protected to abstract

* remove last bind protection since action and cancel are protected + renamed default controller to controller

* removed default alt qwerty keys in config

* fix some errors for doc

* fix tests

* fix some more errors for docs

* fix some more errors for docs final ?

* added alt bind for menu navigation + update icons on delete/home
This commit is contained in:
Greenlamp2 2024-06-01 14:56:32 +02:00 committed by GitHub
parent 5cf9a98ee0
commit 060b1b2ccc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 5318 additions and 511 deletions

View File

@ -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 > .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 .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;
}
@ -176,3 +178,69 @@ input:-internal-autofill-selected {
-webkit-background-clip: text;
background-clip: text;
}
#banner {
display: none;
position: absolute;
top: 33.2%;
left: 0;
text-align: center;
z-index: 1000; /* Ensures the banner is on top of other elements */
& > img {
opacity: 50%;
}
}
/* Firefox old*/
@-moz-keyframes blink {
0% {
opacity:1;
}
50% {
opacity:0;
}
100% {
opacity:1;
}
}
@-webkit-keyframes blink {
0% {
opacity:1;
}
50% {
opacity:0;
}
100% {
opacity:1;
}
}
/* IE */
@-ms-keyframes blink {
0% {
opacity:1;
}
50% {
opacity:0;
}
100% {
opacity:1;
}
}
/* Opera and prob css3 final iteration */
@keyframes blink {
0% {
opacity:1;
}
50% {
opacity:0;
}
100% {
opacity:1;
}
}
.blink-image {
-moz-animation: blink normal 4s infinite ease-in-out; /* Firefox */
-webkit-animation: blink normal 4s infinite ease-in-out; /* Webkit */
-ms-animation: blink normal 4s infinite ease-in-out; /* IE */
animation: blink normal 4s infinite ease-in-out; /* Opera and prob css3 final iteration */
}

View File

@ -0,0 +1,148 @@
{"frames": [
{
"filename": "CIRCLE.png",
"frame": {"x":0,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "CROSS.png",
"frame": {"x":12,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "DOWN.png",
"frame": {"x":24,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "L1.png",
"frame": {"x":36,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "L2.png",
"frame": {"x":48,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "L3.png",
"frame": {"x":60,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "LEFT.png",
"frame": {"x":72,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "R1.png",
"frame": {"x":84,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "R2.png",
"frame": {"x":96,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "R3.png",
"frame": {"x":108,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "RIGHT.png",
"frame": {"x":120,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "SELECT.png",
"frame": {"x":132,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "SQUARE.png",
"frame": {"x":144,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "START.png",
"frame": {"x":156,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "TOUCH.png",
"frame": {"x":168,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "TRIANGLE.png",
"frame": {"x":180,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "UP.png",
"frame": {"x":192,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
}],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "1.0",
"image": "dualshock.png",
"format": "RGBA8888",
"size": {"w":204,"h":12},
"scale": "1",
"smartupdate": "$TexturePacker:SmartUpdate:47df68ade4299adf7d334f25cb833ece:039b9ac469e3616fb9635a6a19cca50e:adc25708364be3d9e70074e95305c745$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,596 @@
{"frames": [
{
"filename": "0.png",
"frame": {"x":0,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "1.png",
"frame": {"x":12,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "2.png",
"frame": {"x":24,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "3.png",
"frame": {"x":36,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "4.png",
"frame": {"x":48,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "5.png",
"frame": {"x":60,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "6.png",
"frame": {"x":72,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "7.png",
"frame": {"x":84,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "8.png",
"frame": {"x":96,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "9.png",
"frame": {"x":108,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "A.png",
"frame": {"x":120,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "ALT.png",
"frame": {"x":132,"y":0,"w":16,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":16,"h":12},
"sourceSize": {"w":16,"h":12}
},
{
"filename": "B.png",
"frame": {"x":148,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "BACK.png",
"frame": {"x":160,"y":0,"w":24,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":24,"h":12},
"sourceSize": {"w":24,"h":12}
},
{
"filename": "C.png",
"frame": {"x":184,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "CTRL.png",
"frame": {"x":196,"y":0,"w":22,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":22,"h":12},
"sourceSize": {"w":22,"h":12}
},
{
"filename": "D.png",
"frame": {"x":218,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "DEL.png",
"frame": {"x":230,"y":0,"w":17,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":17,"h":12},
"sourceSize": {"w":17,"h":12}
},
{
"filename": "E.png",
"frame": {"x":247,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "END.png",
"frame": {"x":259,"y":0,"w":18,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":18,"h":12},
"sourceSize": {"w":18,"h":12}
},
{
"filename": "ENTER.png",
"frame": {"x":277,"y":0,"w":27,"h":11},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":27,"h":11},
"sourceSize": {"w":27,"h":11}
},
{
"filename": "ESC.png",
"frame": {"x":304,"y":0,"w":17,"h":11},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":17,"h":11},
"sourceSize": {"w":17,"h":11}
},
{
"filename": "F.png",
"frame": {"x":321,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "F1.png",
"frame": {"x":333,"y":0,"w":13,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":13,"h":12},
"sourceSize": {"w":13,"h":12}
},
{
"filename": "F2.png",
"frame": {"x":346,"y":0,"w":13,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":13,"h":12},
"sourceSize": {"w":13,"h":12}
},
{
"filename": "F3.png",
"frame": {"x":359,"y":0,"w":13,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":13,"h":12},
"sourceSize": {"w":13,"h":12}
},
{
"filename": "F4.png",
"frame": {"x":372,"y":0,"w":13,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":13,"h":12},
"sourceSize": {"w":13,"h":12}
},
{
"filename": "F5.png",
"frame": {"x":385,"y":0,"w":13,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":13,"h":12},
"sourceSize": {"w":13,"h":12}
},
{
"filename": "F6.png",
"frame": {"x":398,"y":0,"w":13,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":13,"h":12},
"sourceSize": {"w":13,"h":12}
},
{
"filename": "F7.png",
"frame": {"x":411,"y":0,"w":13,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":13,"h":12},
"sourceSize": {"w":13,"h":12}
},
{
"filename": "F8.png",
"frame": {"x":424,"y":0,"w":13,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":13,"h":12},
"sourceSize": {"w":13,"h":12}
},
{
"filename": "F9.png",
"frame": {"x":437,"y":0,"w":13,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":13,"h":12},
"sourceSize": {"w":13,"h":12}
},
{
"filename": "F10.png",
"frame": {"x":450,"y":0,"w":16,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":16,"h":12},
"sourceSize": {"w":16,"h":12}
},
{
"filename": "F11.png",
"frame": {"x":466,"y":0,"w":15,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":15,"h":12},
"sourceSize": {"w":15,"h":12}
},
{
"filename": "F12.png",
"frame": {"x":481,"y":0,"w":16,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":16,"h":12},
"sourceSize": {"w":16,"h":12}
},
{
"filename": "G.png",
"frame": {"x":497,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "H.png",
"frame": {"x":509,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "HOME.png",
"frame": {"x":521,"y":0,"w":23,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":23,"h":12},
"sourceSize": {"w":23,"h":12}
},
{
"filename": "I.png",
"frame": {"x":544,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "INS.png",
"frame": {"x":556,"y":0,"w":16,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":16,"h":12},
"sourceSize": {"w":16,"h":12}
},
{
"filename": "J.png",
"frame": {"x":572,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "K.png",
"frame": {"x":584,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "KEY_ARROW_DOWN.png",
"frame": {"x":596,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "KEY_ARROW_LEFT.png",
"frame": {"x":608,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "KEY_ARROW_RIGHT.png",
"frame": {"x":620,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "KEY_ARROW_UP.png",
"frame": {"x":632,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "L.png",
"frame": {"x":644,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "LEFT_BRACKET.png",
"frame": {"x":656,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "M.png",
"frame": {"x":668,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "MINUS.png",
"frame": {"x":680,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "N.png",
"frame": {"x":692,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "O.png",
"frame": {"x":704,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "P.png",
"frame": {"x":716,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "PAGE_DOWN.png",
"frame": {"x":728,"y":0,"w":20,"h":11},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":20,"h":11},
"sourceSize": {"w":20,"h":11}
},
{
"filename": "PAGE_UP.png",
"frame": {"x":748,"y":0,"w":20,"h":11},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":20,"h":11},
"sourceSize": {"w":20,"h":11}
},
{
"filename": "PLUS.png",
"frame": {"x":768,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "Q.png",
"frame": {"x":780,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "QUOTE.png",
"frame": {"x":792,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "R.png",
"frame": {"x":804,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "RIGHT_BRACKET.png",
"frame": {"x":816,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "S.png",
"frame": {"x":828,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "SEMICOLON.png",
"frame": {"x":840,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "SHIFT.png",
"frame": {"x":852,"y":0,"w":23,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":23,"h":12},
"sourceSize": {"w":23,"h":12}
},
{
"filename": "SPACE.png",
"frame": {"x":875,"y":0,"w":25,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":25,"h":12},
"sourceSize": {"w":25,"h":12}
},
{
"filename": "T.png",
"frame": {"x":900,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "TAB.png",
"frame": {"x":912,"y":0,"w":19,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":19,"h":12},
"sourceSize": {"w":19,"h":12}
},
{
"filename": "TILDE.png",
"frame": {"x":931,"y":0,"w":15,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":15,"h":12},
"sourceSize": {"w":15,"h":12}
},
{
"filename": "U.png",
"frame": {"x":946,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "V.png",
"frame": {"x":958,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "W.png",
"frame": {"x":970,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "X.png",
"frame": {"x":982,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "Y.png",
"frame": {"x":994,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "Z.png",
"frame": {"x":1006,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
}],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "1.0",
"image": "keyboard.png",
"format": "RGBA8888",
"size": {"w":1018,"h":12},
"scale": "1",
"smartupdate": "$TexturePacker:SmartUpdate:085d4353a5c4d18c90f82f8926710d72:45908b22b446cf7f4904d4e0b658b16a:bad03abb89ad027d879c383c13fd51bc$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,140 @@
{"frames": [
{
"filename": "Bumper_L.png",
"frame": {"x":0,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "Bumper_R.png",
"frame": {"x":12,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "DOWN.png",
"frame": {"x":24,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "LEFT.png",
"frame": {"x":36,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "LS.png",
"frame": {"x":48,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "RIGHT.png",
"frame": {"x":60,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "RS.png",
"frame": {"x":72,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "SELECT.png",
"frame": {"x":84,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "START.png",
"frame": {"x":96,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "Trigger_L.png",
"frame": {"x":108,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "Trigger_R.png",
"frame": {"x":120,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "UP.png",
"frame": {"x":132,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "XB_Letter_A_OL.png",
"frame": {"x":144,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "XB_Letter_B_OL.png",
"frame": {"x":156,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "XB_Letter_X_OL.png",
"frame": {"x":168,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
},
{
"filename": "XB_Letter_Y_OL.png",
"frame": {"x":180,"y":0,"w":12,"h":12},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
"sourceSize": {"w":12,"h":12}
}],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "1.0",
"image": "xbox.png",
"format": "RGBA8888",
"size": {"w":192,"h":12},
"scale": "1",
"smartupdate": "$TexturePacker:SmartUpdate:dda9e220b2ea223723253388c465ea25:8ab4a5ecdc22848a8718a1285590a78c:7ad6008cd8fa3f9f4bfb17e0cfcbbb64$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -131,8 +131,6 @@ export default class BattleScene extends SceneBase {
public fusionPaletteSwaps: boolean = true;
public enableTouchControls: boolean = false;
public enableVibration: boolean = false;
public gamepadSupport: boolean = false;
public abSwapped: boolean = false;
public disableMenu: boolean = false;

View File

@ -0,0 +1,293 @@
import {Button} from "#app/enums/buttons";
import {SettingKeyboard} from "#app/system/settings-keyboard";
const cfg_keyboard_qwerty = {
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: "A.png",
KEY_B: "B.png",
KEY_C: "C.png",
KEY_D: "D.png",
KEY_E: "E.png",
KEY_F: "F.png",
KEY_G: "G.png",
KEY_H: "H.png",
KEY_I: "I.png",
KEY_J: "J.png",
KEY_K: "K.png",
KEY_L: "L.png",
KEY_M: "M.png",
KEY_N: "N.png",
KEY_O: "O.png",
KEY_P: "P.png",
KEY_Q: "Q.png",
KEY_R: "R.png",
KEY_S: "S.png",
KEY_T: "T.png",
KEY_U: "U.png",
KEY_V: "V.png",
KEY_W: "W.png",
KEY_X: "X.png",
KEY_Y: "Y.png",
KEY_Z: "Z.png",
KEY_0: "0.png",
KEY_1: "1.png",
KEY_2: "2.png",
KEY_3: "3.png",
KEY_4: "4.png",
KEY_5: "5.png",
KEY_6: "6.png",
KEY_7: "7.png",
KEY_8: "8.png",
KEY_9: "9.png",
KEY_F1: "F1.png",
KEY_F2: "F2.png",
KEY_F3: "F3.png",
KEY_F4: "F4.png",
KEY_F5: "F5.png",
KEY_F6: "F6.png",
KEY_F7: "F7.png",
KEY_F8: "F8.png",
KEY_F9: "F9.png",
KEY_F10: "F10.png",
KEY_F11: "F11.png",
KEY_F12: "F12.png",
KEY_PAGE_DOWN: "PAGE_DOWN.png",
KEY_PAGE_UP: "PAGE_UP.png",
KEY_CTRL: "CTRL.png",
KEY_DEL: "DEL.png",
KEY_END: "END.png",
KEY_ENTER: "ENTER.png",
KEY_ESC: "ESC.png",
KEY_HOME: "HOME.png",
KEY_INSERT: "INS.png",
KEY_PLUS: "PLUS.png",
KEY_MINUS: "MINUS.png",
KEY_QUOTATION: "QUOTE.png",
KEY_SHIFT: "SHIFT.png",
KEY_SPACE: "SPACE.png",
KEY_TAB: "TAB.png",
KEY_TILDE: "TILDE.png",
KEY_ARROW_UP: "KEY_ARROW_UP.png",
KEY_ARROW_DOWN: "KEY_ARROW_DOWN.png",
KEY_ARROW_LEFT: "KEY_ARROW_LEFT.png",
KEY_ARROW_RIGHT: "KEY_ARROW_RIGHT.png",
KEY_LEFT_BRACKET: "LEFT_BRACKET.png",
KEY_RIGHT_BRACKET: "RIGHT_BRACKET.png",
KEY_SEMICOLON: "SEMICOLON.png",
KEY_BACKSPACE: "BACK.png",
KEY_ALT: "ALT.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.V,
[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.V,
[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: -1,
KEY_MINUS: -1,
KEY_A: SettingKeyboard.Alt_Button_Left,
KEY_B: -1,
KEY_D: SettingKeyboard.Alt_Button_Right,
KEY_H: -1,
KEY_I: -1,
KEY_J: -1,
KEY_K: -1,
KEY_L: -1,
KEY_M: SettingKeyboard.Alt_Button_Menu,
KEY_O: -1,
KEY_P: -1,
KEY_Q: -1,
KEY_S: SettingKeyboard.Alt_Button_Down,
KEY_T: SettingKeyboard.Alt_Button_Cycle_Form,
KEY_U: -1,
KEY_W: SettingKeyboard.Alt_Button_Up,
KEY_X: SettingKeyboard.Alt_Button_Cancel,
KEY_Y: SettingKeyboard.Alt_Button_Cycle_Shiny,
KEY_Z: SettingKeyboard.Alt_Button_Action,
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: -1,
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.Button_Slow_Down,
KEY_PAGE_UP: SettingKeyboard.Button_Speed_Up,
KEY_QUOTATION: -1,
KEY_SHIFT: SettingKeyboard.Alt_Button_Stats,
KEY_TAB: -1,
KEY_TILDE: -1,
KEY_LEFT_BRACKET: -1,
KEY_RIGHT_BRACKET: -1,
KEY_SEMICOLON: -1,
KEY_ALT: -1
},
blacklist: [
"KEY_ENTER",
"KEY_ESC",
"KEY_SPACE",
"KEY_BACKSPACE",
"KEY_ARROW_UP",
"KEY_ARROW_DOWN",
"KEY_ARROW_LEFT",
"KEY_ARROW_RIGHT",
"KEY_DEL",
"KEY_HOME",
]
};
export default cfg_keyboard_qwerty;

View File

@ -0,0 +1,208 @@
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)) {
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);
// || isTheLatestBind(config, settingName) no longer needed since action and cancel are protected
if (config.blacklist?.includes(key)) {
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;
// }

View File

@ -0,0 +1,88 @@
import {SettingGamepad} from "../../system/settings-gamepad";
import {Button} from "../../enums/buttons";
/**
* Dualshock mapping
*/
const pad_dualshock = {
padID: "Dualshock",
padType: "dualshock",
deviceMapping: {
RC_S: 0,
RC_E: 1,
RC_W: 2,
RC_N: 3,
START: 9, // Options
SELECT: 8, // Share
LB: 4,
RB: 5,
LT: 6,
RT: 7,
LS: 10,
RS: 11,
LC_N: 12,
LC_S: 13,
LC_W: 14,
LC_E: 15,
TOUCH: 17
},
icons: {
RC_S: "CROSS.png",
RC_E: "CIRCLE.png",
RC_W: "SQUARE.png",
RC_N: "TRIANGLE.png",
START: "START.png",
SELECT: "SELECT.png",
LB: "L1.png",
RB: "R1.png",
LT: "L2.png",
RT: "R2.png",
LS: "L3.png",
RS: "R3.png",
LC_N: "UP.png",
LC_S: "DOWN.png",
LC_W: "LEFT.png",
LC_E: "RIGHT.png",
TOUCH: "TOUCH.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.V,
[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;

View File

@ -0,0 +1,90 @@
import {SettingGamepad} from "../../system/settings-gamepad";
import {Button} from "../../enums/buttons";
/**
* Generic pad mapping
*/
const pad_generic = {
padID: "Generic",
padType: "xbox",
deviceMapping: {
RC_S: 0,
RC_E: 1,
RC_W: 2,
RC_N: 3,
START: 9,
SELECT: 8,
LB: 4,
RB: 5,
LT: 6,
RT: 7,
LS: 10,
RS: 11,
LC_N: 12,
LC_S: 13,
LC_W: 14,
LC_E: 15
},
icons: {
RC_S: "XB_Letter_A_OL.png",
RC_E: "XB_Letter_B_OL.png",
RC_W: "XB_Letter_X_OL.png",
RC_N: "XB_Letter_Y_OL.png",
START: "START.png",
SELECT: "SELECT.png",
LB: "Bumper_L.png",
RB: "Bumper_R.png",
LT: "Trigger_L.png",
RT: "Trigger_R.png",
LS: "LS.png",
RS: "RS.png",
LC_N: "UP.png",
LC_S: "DOWN.png",
LC_W: "LEFT.png",
LC_E: "RIGHT.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.V,
[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;

View File

@ -0,0 +1,85 @@
import {SettingGamepad} from "#app/system/settings-gamepad";
import {Button} from "#app/enums/buttons";
/**
* Nintendo Pro Controller mapping
*/
const pad_procon = {
padID: "Pro Controller",
padType: "xbox",
deviceMapping: {
RC_S: 1,
RC_E: 0,
RC_W: 3,
RC_N: 2,
START: 9, // +
SELECT: 8, // -
LB: 4,
RB: 5,
LT: 6,
RT: 7,
LS: 10,
RS: 11,
LC_N: 12,
LC_S: 13,
LC_W: 14,
LC_E: 15,
MENU: 16, // Home
},
icons: {
RC_S: "XB_Letter_B_OL.png",
RC_E: "XB_Letter_A_OL.png",
RC_W: "XB_Letter_Y_OL.png",
RC_N: "XB_Letter_X_OL.png",
START: "START.png",
SELECT: "SELECT.png",
LB: "Bumper_L.png",
RB: "Bumper_R.png",
LT: "Trigger_L.png",
RT: "Trigger_R.png",
LS: "LS.png",
RS: "RS.png",
LC_N: "UP.png",
LC_S: "DOWN.png",
LC_W: "LEFT.png",
LC_E: "RIGHT.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.V,
[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
},
};
export default pad_procon;

View File

@ -0,0 +1,76 @@
import {SettingGamepad} from "../../system/settings-gamepad";
import {Button} from "../../enums/buttons";
/**
* 081f-e401 - UnlicensedSNES
*/
const pad_unlicensedSNES = {
padID: "081f-e401",
padType: "xbox",
deviceMapping : {
RC_S: 2,
RC_E: 1,
RC_W: 3,
RC_N: 0,
START: 9,
SELECT: 8,
LB: 4,
RB: 5,
LC_N: 12,
LC_S: 13,
LC_W: 14,
LC_E: 15
},
icons: {
RC_S: "XB_Letter_A_OL.png",
RC_E: "XB_Letter_B_OL.png",
RC_W: "XB_Letter_X_OL.png",
RC_N: "XB_Letter_Y_OL.png",
START: "START.png",
SELECT: "SELECT.png",
LB: "Bumper_L.png",
RB: "Bumper_R.png",
LC_N: "UP.png",
LC_S: "DOWN.png",
LC_W: "LEFT.png",
LC_E: "RIGHT.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.V,
[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;

View File

@ -0,0 +1,84 @@
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",
deviceMapping: {
RC_S: 0,
RC_E: 1,
RC_W: 2,
RC_N: 3,
START: 9,
SELECT: 8,
LB: 4,
RB: 5,
LT: 6,
RT: 7,
LS: 10,
RS: 11,
LC_N: 12,
LC_S: 13,
LC_W: 14,
LC_E: 15
},
icons: {
RC_S: "XB_Letter_A_OL.png",
RC_E: "XB_Letter_B_OL.png",
RC_W: "XB_Letter_X_OL.png",
RC_N: "XB_Letter_Y_OL.png",
START: "START.png",
SELECT: "SELECT.png",
LB: "Bumper_L.png",
RB: "Bumper_R.png",
LT: "Trigger_L.png",
RT: "Trigger_R.png",
LS: "LS.png",
RS: "RS.png",
LC_N: "UP.png",
LC_S: "DOWN.png",
LC_W: "LEFT.png",
LC_E: "RIGHT.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.V,
[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
},
};
export default pad_xbox360;

View File

@ -1,29 +0,0 @@
/**
* Dualshock mapping
*/
const pad_dualshock = {
padID: "Dualshock",
padType: "Sony",
gamepadMapping: {
RC_S: 0,
RC_E: 1,
RC_W: 2,
RC_N: 3,
START: 9, // Options
SELECT: 8, // Share
LB: 4,
RB: 5,
LT: 6,
RT: 7,
LS: 10,
RS: 11,
LC_N: 12,
LC_S: 13,
LC_W: 14,
LC_E: 15,
MENU: 16,
TOUCH: 17
},
};
export default pad_dualshock;

View File

@ -1,27 +0,0 @@
/**
* Generic pad mapping
*/
const pad_generic = {
padID: "Generic",
padType: "generic",
gamepadMapping: {
RC_S: 0,
RC_E: 1,
RC_W: 2,
RC_N: 3,
START: 9,
SELECT: 8,
LB: 4,
RB: 5,
LT: 6,
RT: 7,
LS: 10,
RS: 11,
LC_N: 12,
LC_S: 13,
LC_W: 14,
LC_E: 15
},
};
export default pad_generic;

View File

@ -1,28 +0,0 @@
/**
* Nintendo Pro Controller mapping
*/
const pad_procon = {
padID: "Pro Controller",
padType: "Nintendo",
gamepadMapping: {
RC_S: 1,
RC_E: 0,
RC_W: 3,
RC_N: 2,
START: 9, // +
SELECT: 8, // -
LB: 4,
RB: 5,
LT: 6,
RT: 7,
LS: 10,
RS: 11,
LC_N: 12,
LC_S: 13,
LC_W: 14,
LC_E: 15,
MENU: 16, // Home
},
};
export default pad_procon;

View File

@ -1,23 +0,0 @@
/**
* 081f-e401 - UnlicensedSNES
*/
const pad_unlicensedSNES = {
padID: "081f-e401",
padType: "snes",
gamepadMapping : {
RC_S: 2,
RC_E: 1,
RC_W: 3,
RC_N: 0,
START: 9,
SELECT: 8,
LB: 4,
RB: 5,
LC_N: 12,
LC_S: 13,
LC_W: 14,
LC_E: 15
}
};
export default pad_unlicensedSNES;

View File

@ -1,28 +0,0 @@
/**
* Generic pad mapping
*/
const pad_xbox360 = {
padID: "Xbox 360 controller (XInput STANDARD GAMEPAD)",
padType: "xbox",
gamepadMapping: {
RC_S: 0,
RC_E: 1,
RC_W: 2,
RC_N: 3,
START: 9,
SELECT: 8,
LB: 4,
RB: 5,
LT: 6,
RT: 7,
LS: 10,
RS: 11,
LC_N: 12,
LC_S: 13,
LC_W: 14,
LC_E: 15,
MENU: 16
},
};
export default pad_xbox360;

4
src/enums/devices.ts Normal file
View File

@ -0,0 +1,4 @@
export enum Device {
GAMEPAD,
KEYBOARD,
}

View File

@ -1,30 +1,70 @@
import Phaser from "phaser";
import * as Utils from "./utils";
import {ButtonKey, 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 pad_procon from "./configs/pad_procon";
import {deepCopy} from "./utils";
import {initTouchControls} from "./touch-controls";
import pad_generic from "./configs/inputs/pad_generic";
import pad_unlicensedSNES from "./configs/inputs/pad_unlicensedSNES";
import pad_xbox360 from "./configs/inputs/pad_xbox360";
import pad_dualshock from "./configs/inputs/pad_dualshock";
import pad_procon from "./configs/inputs/pad_procon";
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_qwerty from "./configs/inputs/cfg_keyboard_qwerty";
import {Device} from "#app/enums/devices";
import {
assign,
getButtonWithKeycode,
getIconForLatestInput, swap,
} from "#app/configs/inputs/configHandler";
import BattleScene from "./battle-scene";
import {SettingGamepad} from "#app/system/settings-gamepad";
import {SettingKeyboard} from "#app/system/settings-keyboard";
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]: Button;
}
export interface MappingLayout {
[key: string]: SettingGamepad | SettingKeyboard | number;
}
export interface InterfaceConfig {
padID: string;
padType: string;
deviceMapping: DeviceMapping;
icons: IconsMapping;
settings: SettingMapping;
default: MappingLayout;
custom?: MappingLayout;
}
const repeatInputDelayMillis = 250;
// Phaser.Input.Gamepad.GamepadPlugin#refreshPads
declare module "phaser" {
namespace Input {
namespace Gamepad {
interface GamepadPlugin {
/**
* Refreshes the list of connected Gamepads.
* This is called automatically when a gamepad is connected or disconnected, and during the update loop.
*/
refreshPads(): void;
}
}
}
}
/**
* Manages and handles all input controls for the game, including keyboard and gamepad interactions.
*
@ -49,17 +89,24 @@ const repeatInputDelayMillis = 250;
*/
export class InputsController {
private buttonKeys: Phaser.Input.Keyboard.Key[][];
private gamepads: Phaser.Input.Gamepad.Gamepad[] = new Array();
private gamepads: Array<Phaser.Input.Gamepad.Gamepad> = new Array();
private scene: BattleScene;
public events: Phaser.Events.EventEmitter;
private buttonLock: Button;
private buttonLock2: Button;
private interactions: Map<Button, Map<string, boolean>> = new Map();
private time: Phaser.Time.Clock;
private player: GamepadMapping;
private configs: Map<string, InterfaceConfig> = new Map();
private gamepadSupport: boolean = true;
public events: Phaser.Events.EventEmitter;
public gamepadSupport: boolean = true;
public selectedDevice;
private disconnectedGamepads: Array<String> = new Array();
private pauseUpdate: boolean = false;
public lastSource: string = "keyboard";
private keys: Array<number> = [];
/**
* Initializes a new instance of the game control system, setting up initial state and configurations.
@ -72,10 +119,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: BattleScene) {
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] = {
@ -99,18 +151,28 @@ 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
this.scene.input.gamepad.refreshPads();
if (this.scene.input.gamepad.total) {
this.refreshGamepads();
for (const thisGamepad of this.gamepads) {
@ -120,10 +182,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);
}
/**
@ -149,35 +211,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.
@ -185,25 +270,104 @@ 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<String> An array of strings representing the IDs of the connected gamepads.
*/
getGamepadsName(): Array<String> {
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 {
const gamepadID = thisGamepad.id.toLowerCase();
const mappedPad = this.mapGamepad(gamepadID);
this.player = 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)) as InterfaceConfig;
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)) as InterfaceConfig;
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]);
}
/**
@ -226,89 +390,110 @@ 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) {
return gamepadMapping;
ensureKeyboardIsInit(): void {
if (!this.getActiveConfig(Device.KEYBOARD)?.padID) {
this.setupKeyboard();
}
gamepadMapping[this.player.LC_N] = Button.UP;
gamepadMapping[this.player.LC_S] = Button.DOWN;
gamepadMapping[this.player.LC_W] = Button.LEFT;
gamepadMapping[this.player.LC_E] = Button.RIGHT;
gamepadMapping[this.player.TOUCH] = Button.SUBMIT;
gamepadMapping[this.player.RC_S] = this.scene.abSwapped ? Button.CANCEL : Button.ACTION;
gamepadMapping[this.player.RC_E] = this.scene.abSwapped ? Button.ACTION : Button.CANCEL;
gamepadMapping[this.player.SELECT] = Button.STATS;
gamepadMapping[this.player.START] = Button.MENU;
gamepadMapping[this.player.RB] = Button.CYCLE_SHINY;
gamepadMapping[this.player.LB] = Button.CYCLE_FORM;
gamepadMapping[this.player.LT] = Button.CYCLE_GENDER;
gamepadMapping[this.player.RT] = Button.CYCLE_ABILITY;
gamepadMapping[this.player.RC_W] = Button.CYCLE_NATURE;
gamepadMapping[this.player.RC_N] = Button.V;
gamepadMapping[this.player.LS] = Button.SPEED_UP;
gamepadMapping[this.player.RS] = Button.SLOW_DOWN;
return gamepadMapping;
}
/**
* 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.
*
* @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.
*/
gamepadButtonDown(pad: Phaser.Input.Gamepad.Gamepad, button: Phaser.Input.Gamepad.Button, value: number): void {
if (!this.gamepadSupport) {
keyboardKeyDown(event): void {
this.lastSource = "keyboard";
const keyDown = event.keyCode;
this.ensureKeyboardIsInit();
if (this.keys.includes(keyDown)) {
return;
}
const actionMapping = this.getActionGamepadMapping();
const buttonDown = actionMapping.hasOwnProperty(button.index) && actionMapping[button.index];
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.
*
* @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.configs[this.selectedDevice[Device.KEYBOARD]]?.padID) {
this.setupKeyboard();
}
if (!pad) {
return;
}
this.lastSource = "gamepad";
if (!this.selectedDevice[Device.GAMEPAD] || (this.scene.ui.getMode() !== Mode.GAMEPAD_BINDING && this.selectedDevice[Device.GAMEPAD] !== pad.id.toLowerCase())) {
this.setChosenGamepad(pad.id);
}
if (!this.gamepadSupport || pad.id.toLowerCase() !== this.selectedDevice[Device.GAMEPAD].toLowerCase()) {
return;
}
const activeConfig = this.getActiveConfig(Device.GAMEPAD);
const buttonDown = activeConfig && getButtonWithKeycode(activeConfig, 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) {
if (!pad) {
return;
}
const actionMapping = this.getActionGamepadMapping();
const buttonUp = actionMapping.hasOwnProperty(button.index) && actionMapping[button.index];
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",
@ -319,114 +504,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.V]: [keyCodes.V],
[Button.SPEED_UP]: [keyCodes.PLUS],
[Button.SLOW_DOWN]: [keyCodes.MINUS]
};
const mobileKeyConfig = new Map<string, ButtonKey>();
for (const b of Utils.getEnumValues(Button)) {
const keys: Phaser.Input.Keyboard.Key[] = [];
if (keyConfig.hasOwnProperty(b)) {
for (const 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 the ID includes both '057e' and '2009', it is identified as a Pro controller 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")) {
@ -442,6 +527,20 @@ 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_qwerty;
}
return cfg_keyboard_qwerty;
}
/**
* repeatInputDurationJustPassed returns true if @param button has been held down long
* enough to fire a repeated input. A button must claim the buttonLock before
@ -471,7 +570,7 @@ 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;
}
@ -479,6 +578,7 @@ export class InputsController {
this.interactions[button].pressTime = this.time.now;
this.interactions[button].isPressed = true;
this.interactions[button].source = source;
this.interactions[button].sourceName = sourceName.toLowerCase();
}
/**
@ -503,6 +603,7 @@ export class InputsController {
this.interactions[button].pressTime = null;
this.interactions[button].isPressed = false;
this.interactions[button].source = null;
this.interactions[button].sourceName = null;
}
/**
@ -512,7 +613,7 @@ export class InputsController {
* This method is used to reset the state of all buttons within the `interactions` dictionary,
* effectively deactivating any currently pressed keys. It performs the following actions:
*
* - Releases button locks for predefined buttons (`buttonLock` and `buttonLock2`), allowing them
* - Releases button lock for predefined buttons, allowing them
* to be pressed again or properly re-initialized in future interactions.
* - Iterates over all possible button values obtained via `Utils.getEnumValues(Button)`, and for
* each button:
@ -524,55 +625,44 @@ 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)) {
if (this.interactions.hasOwnProperty(b)) {
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);
}
/**
* Checks if a specific button is currently locked.
*
* @param button - The button to check for a lock status.
* @returns `true` if the button is either of the two potentially locked buttons (`buttonLock` or `buttonLock2`), otherwise `false`.
* @returns `true` if the button is locked, otherwise `false`.
*
* @remarks
* This method is used to determine if a given button is currently prevented from being processed due to a lock.
* It checks against two separate lock variables, allowing for up to two buttons to be locked simultaneously.
*/
isButtonLocked(button: Button): boolean {
return (this.buttonLock === button || this.buttonLock2 === button);
return this.buttonLock === button;
}
/**
* Sets a lock on a given button if it is not already locked.
* Sets a lock on a given button.
*
* @param button - The button to lock.
*
* @remarks
* This method ensures that a button is not processed multiple times inadvertently.
* It checks if the button is already locked by either of the two lock variables (`buttonLock` or `buttonLock2`).
* If not, it locks the button using the first available lock variable.
* This mechanism allows for up to two buttons to be locked at the same time.
* It checks if the button is already locked.
*/
setButtonLock(button: Button): void {
if (this.buttonLock === button || this.buttonLock2 === button) {
return;
}
if (this.buttonLock === button) {
this.buttonLock2 = button;
} else if (this.buttonLock2 === button) {
this.buttonLock = button;
} else if (!!this.buttonLock) {
this.buttonLock2 = button;
} else {
this.buttonLock = button;
}
}
/**
@ -581,15 +671,93 @@ export class InputsController {
* @param button - The button whose lock is to be released.
*
* @remarks
* This method checks both lock variables (`buttonLock` and `buttonLock2`).
* This method checks lock variable.
* If either lock matches the specified button, that lock is cleared.
* This action frees the button to be processed again, ensuring it can respond to new inputs.
*/
releaseButtonLock(button: Button): void {
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);
}
}
}

View File

@ -251,6 +251,10 @@ export class LoadingScene extends SceneBase {
}
}
this.loadAtlas("dualshock", "inputs");
this.loadAtlas("xbox", "inputs");
this.loadAtlas("keyboard", "inputs");
this.loadSe("select");
this.loadSe("menu_open");
this.loadSe("hit");

View File

@ -30,6 +30,8 @@ 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 {setSettingKeyboard, SettingKeyboard, settingKeyboardDefaults} from "#app/system/settings-keyboard";
import { TerrainChangedEvent, WeatherChangedEvent } from "#app/field/arena-events.js";
const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet necessary
@ -243,6 +245,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 = {};
@ -565,6 +569,125 @@ export class GameData {
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]));
@ -579,6 +702,19 @@ export class GameData {
}
}
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 (const 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")) {

View File

@ -0,0 +1,147 @@
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 {
Controller = "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.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.V.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.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:
case SettingGamepad.Button_Submit:
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.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.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: string) => ({
label: truncateString(g, 30), // Truncate the gamepad name for display
handler: () => changeGamepadHandler(g)
})), {
label: "Cancel",
handler: cancelHandler,
}]
});
return false;
}
}
break;
}
return true;
}

View File

@ -0,0 +1,208 @@
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.V.toString()}`, "Press action to assign"],
[SettingKeyboard.Alt_Button_Cycle_Variant]: [`KEY ${Button.V.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_Cancel,
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:
case SettingKeyboard.Alt_Button_Submit:
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;
}

View File

@ -1,4 +1,3 @@
import SettingsUiHandler from "#app/ui/settings-ui-handler";
import { Mode } from "#app/ui/ui";
import i18next from "i18next";
import BattleScene from "../battle-scene";
@ -7,6 +6,7 @@ import { updateWindowType } from "../ui/ui-theme";
import { PlayerGender } from "./game-data";
import { CandyUpgradeNotificationChangedEvent } from "#app/battle-scene-events.js";
import { MoneyFormat } from "../enums/money-format";
import SettingsUiHandler from "#app/ui/settings/settings-ui-handler";
export enum Setting {
Game_Speed = "GAME_SPEED",
@ -31,8 +31,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"
}
@ -68,8 +66,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"]
};
@ -97,8 +93,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
};
@ -194,14 +188,6 @@ export function setSetting(scene: BattleScene, setting: Setting, value: integer)
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");

View File

@ -0,0 +1,79 @@
import {
getIconForLatestInput,
getSettingNameWithKeycode
} from "#app/configs/inputs/configHandler";
import {expect} from "vitest";
import {SettingKeyboard} from "#app/system/settings-keyboard";
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 = SettingKeyboard[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 = SettingKeyboard[this.normalizeSettingNameString(settingName)];
expect(getSettingNameWithKeycode(this.config, this.keycode)).toEqual(this.settingName);
return this;
}
}

View File

@ -0,0 +1,131 @@
import {expect} from "vitest";
import {
deleteBind,
getIconWithKeycode,
getIconWithSettingName,
getKeyWithKeycode,
getKeyWithSettingName,
assign,
getSettingNameWithKeycode, canIAssignThisKey, canIDeleteThisKey, canIOverrideThisSetting
} from "#app/configs/inputs/configHandler";
import {SettingKeyboard} from "#app/system/settings-keyboard";
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 = SettingKeyboard[settingName];
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 = SettingKeyboard[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 = SettingKeyboard[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;
}
}

View File

@ -0,0 +1,417 @@
import {beforeEach, describe, expect, it} from "vitest";
import {Button} from "#app/enums/buttons";
import {deepCopy} from "#app/utils";
import {
getKeyWithKeycode,
getKeyWithSettingName,
} from "#app/configs/inputs/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";
import cfg_keyboard_qwerty from "#app/configs/inputs/cfg_keyboard_qwerty";
import {SettingKeyboard} from "#app/system/settings-keyboard";
describe("Test Rebinding", () => {
let config;
let inGame;
let inTheSettingMenu;
const configs: Map<string, InterfaceConfig> = new Map();
const selectedDevice = {
[Device.GAMEPAD]: null,
[Device.KEYBOARD]: "default",
};
beforeEach(() => {
config = deepCopy(cfg_keyboard_qwerty);
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 = SettingKeyboard.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.A);
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 = SettingKeyboard.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 = SettingKeyboard.Alt_Button_Left;
const key = getKeyWithSettingName(config, settingName);
expect(key).toEqual("KEY_A");
});
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("KEY_ARROW_LEFT.png");
});
it("Check icon for currenly Assigned to key code", () => {
const keycode = Phaser.Input.Keyboard.KeyCodes.A;
const key = getKeyWithKeycode(config, keycode);
const icon = config.icons[key];
expect(icon).toEqual("A.png");
});
it("Check icon for currenly Assigned to setting name", () => {
const settingName = SettingKeyboard.Button_Left;
const key = getKeyWithSettingName(config, settingName);
const icon = config.icons[key];
expect(icon).toEqual("KEY_ARROW_LEFT.png");
});
it("Check icon for currenly Assigned to setting name alt", () => {
const settingName = SettingKeyboard.Alt_Button_Left;
const key = getKeyWithSettingName(config, settingName);
const icon = config.icons[key];
expect(icon).toEqual("A.png");
});
it("Check if is working", () => {
inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("A");
inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Right").iconDisplayedIs("D");
inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("A").weWantThisBindInstead("D").confirm();
inGame.whenWePressOnKeyboard("A").nothingShouldHappen();
inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Left");
});
it("Check prevent rebind indirectly the d-pad buttons", () => {
inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("A");
inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Right").iconDisplayedIs("D");
inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("A").weWantThisBindInstead("LEFT").weCantAssignThisKey().butLetsForceIt();
inGame.whenWePressOnKeyboard("LEFT").weShouldTriggerTheButton("Left");
inGame.whenWePressOnKeyboard("A").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("W").weShouldTriggerTheButton("Alt_Button_Up");
inTheSettingMenu.whenCursorIsOnSetting("Button_Up").iconDisplayedIs("KEY_ARROW_UP").weWantThisBindInstead("W").weCantOverrideThisBind().butLetsForceIt();
inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up");
inGame.whenWePressOnKeyboard("W").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("A").weShouldTriggerTheButton("Alt_Button_Left");
inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right");
inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Alt_Button_Up");
inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("KEY_A").weWantThisBindInstead("D").confirm();
inGame.whenWePressOnKeyboard("A").nothingShouldHappen();
inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Left");
inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Alt_Button_Up");
inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("KEY_D").weWantThisBindInstead("W").confirm();
inGame.whenWePressOnKeyboard("A").nothingShouldHappen();
inGame.whenWePressOnKeyboard("D").nothingShouldHappen();
inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Alt_Button_Left");
inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("KEY_W").weWantThisBindInstead("D").confirm();
inGame.whenWePressOnKeyboard("A").nothingShouldHappen();
inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Left");
inGame.whenWePressOnKeyboard("W").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("A").weShouldTriggerTheButton("Alt_Button_Left");
inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right");
inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Alt_Button_Up");
inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("KEY_A").weWantThisBindInstead("D").confirm();
inGame.whenWePressOnKeyboard("A").nothingShouldHappen();
inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Left");
inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Alt_Button_Up");
inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Right").thereShouldBeNoIcon().weWantThisBindInstead("W").confirm();
inGame.whenWePressOnKeyboard("A").nothingShouldHappen();
inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Left");
inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Alt_Button_Right");
inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").iconDisplayedIs("KEY_D").weWantThisBindInstead("A").confirm();
inGame.whenWePressOnKeyboard("A").weShouldTriggerTheButton("Alt_Button_Left");
inGame.whenWePressOnKeyboard("D").nothingShouldHappen();
inGame.whenWePressOnKeyboard("W").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("A").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("A").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("A").weShouldTriggerTheButton("Alt_Button_Left");
inGame.whenWePressOnKeyboard("F").nothingShouldHappen();
});
it("Swap alt with a key not binded yet", () => {
inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Alt_Button_Up");
inGame.whenWePressOnKeyboard("B").nothingShouldHappen();
inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Up").iconDisplayedIs("KEY_W").weWantThisBindInstead("B").confirm();
inGame.whenWePressOnKeyboard("W").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("A").weShouldTriggerTheButton("Alt_Button_Left");
inTheSettingMenu.whenWeDelete("Alt_Button_Left").thereShouldBeNoIconAnymore();
inGame.whenWePressOnKeyboard("A").nothingShouldHappen();
});
it("Delete bind then assign a not yet binded button", () => {
inTheSettingMenu.whenWeDelete("Alt_Button_Left").thereShouldBeNoIconAnymore();
inGame.whenWePressOnKeyboard("A").nothingShouldHappen();
inGame.whenWePressOnKeyboard("B").nothingShouldHappen();
inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").thereShouldBeNoIcon().weWantThisBindInstead("B").confirm();
inGame.whenWePressOnKeyboard("A").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("W").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("W").weShouldTriggerTheButton("Alt_Button_Up");
inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Button_Cycle_Shiny");
inTheSettingMenu.whenCursorIsOnSetting("Button_Cycle_Form").iconDisplayedIs("KEY_F").weWantThisBindInstead("W").confirm();
inGame.whenWePressOnKeyboard("R").nothingShouldHappen();
inGame.whenWePressOnKeyboard("F").nothingShouldHappen();
inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Button_Cycle_Form");
inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Button_Cycle_Shiny");
inGame.whenWePressOnKeyboard("A").weShouldTriggerTheButton("Alt_Button_Left");
inTheSettingMenu.whenWeDelete("Alt_Button_Left").thereShouldBeNoIconAnymore();
inGame.whenWePressOnKeyboard("R").nothingShouldHappen();
inGame.whenWePressOnKeyboard("F").nothingShouldHappen();
inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Button_Cycle_Form");
inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Button_Cycle_Shiny");
inGame.whenWePressOnKeyboard("S").weShouldTriggerTheButton("Alt_Button_Down");
inGame.whenWePressOnKeyboard("A").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("W").weShouldTriggerTheButton("Button_Cycle_Form");
inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Button_Cycle_Shiny");
inGame.whenWePressOnKeyboard("S").nothingShouldHappen();
inGame.whenWePressOnKeyboard("A").nothingShouldHappen();
inGame.whenWePressOnKeyboard("B").weShouldTriggerTheButton("Alt_Button_Down");
});
it("Delete bind then assign not already existing button", () => {
inGame.whenWePressOnKeyboard("A").weShouldTriggerTheButton("Alt_Button_Left");
inGame.whenWePressOnKeyboard("B").nothingShouldHappen();
inTheSettingMenu.whenWeDelete("Alt_Button_Left").thereShouldBeNoIconAnymore();
inGame.whenWePressOnKeyboard("A").nothingShouldHappen();
inGame.whenWePressOnKeyboard("B").nothingShouldHappen();
inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").thereShouldBeNoIcon().weWantThisBindInstead("B").confirm();
inGame.whenWePressOnKeyboard("A").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("A").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_A").weWantThisBindInstead("B").confirm();
inGame.whenWePressOnKeyboard("A").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("A").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("W").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("W").weShouldTriggerTheButton("Alt_Button_Up");
inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right");
inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Up").iconDisplayedIs("KEY_W").weWantThisBindInstead("D").confirm();
inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up");
inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right");
inGame.whenWePressOnKeyboard("W").nothingShouldHappen();
inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Up");
inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Up").iconDisplayedIs("KEY_D").weWantThisBindInstead("W").confirm();
inGame.whenWePressOnKeyboard("UP").weShouldTriggerTheButton("Button_Up");
inGame.whenWePressOnKeyboard("RIGHT").weShouldTriggerTheButton("Button_Right");
inGame.whenWePressOnKeyboard("W").weShouldTriggerTheButton("Alt_Button_Up");
inGame.whenWePressOnKeyboard("D").nothingShouldHappen();
});
it("Delete 2 bind then reassign one of them", () => {
inGame.whenWePressOnKeyboard("A").weShouldTriggerTheButton("Alt_Button_Left");
inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right");
inTheSettingMenu.whenWeDelete("Alt_Button_Left").thereShouldBeNoIconAnymore();
inGame.whenWePressOnKeyboard("A").nothingShouldHappen();
inGame.whenWePressOnKeyboard("D").weShouldTriggerTheButton("Alt_Button_Right");
inTheSettingMenu.whenWeDelete("Alt_Button_Right").thereShouldBeNoIconAnymore();
inGame.whenWePressOnKeyboard("A").nothingShouldHappen();
inGame.whenWePressOnKeyboard("D").nothingShouldHappen();
inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Left").thereShouldBeNoIcon().weWantThisBindInstead("A").confirm();
inGame.whenWePressOnKeyboard("A").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_W").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("W").nothingShouldHappen();
});
it("check to delete all the binds of an action", () => {
inGame.whenWePressOnKeyboard("V").weShouldTriggerTheButton("Button_Cycle_Variant");
inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Cycle_Variant").thereShouldBeNoIcon().weWantThisBindInstead("K").confirm();
inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Cycle_Variant").iconDisplayedIs("KEY_K").whenWeDelete().thereShouldBeNoIconAnymore();
inTheSettingMenu.whenCursorIsOnSetting("Button_Cycle_Variant").iconDisplayedIs("KEY_V").whenWeDelete().thereShouldBeNoIconAnymore();
});
});

View File

@ -1,153 +1,137 @@
export interface ButtonKey {
onDown: (opt: object) => void;
onUp: (opt: object) => void;
}
import {Button} from "./enums/buttons";
import EventEmitter = Phaser.Events.EventEmitter;
export type ButtonMap = Map<string, ButtonKey>;
export const keys = new Map();
export const keysDown = new Map();
// Create a map to store key bindings
export const keys = new Map<string, string>();
// Create a map to store keys that are currently pressed
export const keysDown = new Map<string, string>();
// Variable to store the ID of the last touched element
let lastTouchedId: string;
/**
* Initializes all touch controls
* Initialize touch controls by binding keys to buttons.
*
* @param buttonMap Map of buttons to key objects
* @param events - The event emitter for handling input events.
*/
export function initTouchControls(buttonMap: ButtonMap) {
export function initTouchControls(events: EventEmitter): void {
preventElementZoom(document.querySelector("#dpad"));
preventElementZoom(document.querySelector("#apad"));
for (const button of document.querySelectorAll<HTMLElement>("[data-key]")) {
bindKey(button, button.dataset.key, buttonMap);
// Select all elements with the 'data-key' attribute and bind keys to them
for (const button of document.querySelectorAll("[data-key]")) {
// @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
* Check if the device has a touchscreen.
*
* @returns `true` if the device has a touchscreen, otherwise `false`.
*/
export function hasTouchscreen() {
export function hasTouchscreen(): boolean {
return window.matchMedia("(hover: none), (pointer: coarse)").matches;
}
/**
* Check if it's a mobile device through the user-agent
* Check if the device is a mobile device.
*
* @returns `true` if the device is a mobile device, otherwise `false`.
*/
export function isMobile() {
const userAgent = navigator.userAgent || navigator.vendor || window["opera"];
return (/(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(userAgent)||/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(userAgent.substr(0, 4)));
export function isMobile(): boolean {
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"]);
return ret;
}
/**
* Simulate a keyboard event on the canvas
* Simulates a keyboard event on the canvas.
*
* @param eventType Type of the keyboard event
* @param button Button to simulate
* @param 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: string, button: string, buttonMap: ButtonMap) {
const key = buttonMap[button];
function simulateKeyboardEvent(eventType: string, key: string, events: EventEmitter) {
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 key Key to simulate
* @param 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: Element, key: string, buttonMap: ButtonMap) {
function bindKey(node: HTMLElement, key: string, events) {
keys.set(node.id, key);
node.addEventListener("touchstart", (event: TouchEvent) => {
node.addEventListener("touchstart", event => {
event.preventDefault();
simulateKeyboardEvent("keydown", key, buttonMap);
if (!(event.target instanceof Element)) {
return;
}
keysDown.set(event.target.id, node.id);
simulateKeyboardEvent("keydown", key, events);
keysDown.set(event.target["id"], node.id);
node.classList.add("active");
});
node.addEventListener("touchend", (event: TouchEvent) => {
node.addEventListener("touchend", event => {
event.preventDefault();
if (!(event.target instanceof Element)) {
return;
}
const pressedKey = keysDown.get(event.target.id);
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);
keysDown.delete(event.target["id"]);
node.classList.remove("active");
if (lastTouchedId) {
document.getElementById(lastTouchedId).classList.remove("active");
}
});
// Inspired by https://github.com/pulsejet/mkxp-web/blob/262a2254b684567311c9f0e135ee29f6e8c3613e/extra/js/dpad.js
node.addEventListener("touchmove", (event : TouchEvent) => {
if (!(event.changedTouches[0].target instanceof Element)) {
return;
}
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");
}
});
}
/**
* Prevent zoom on specified element
*
* {@link https://stackoverflow.com/a/39778831/4622620|Source}
*
* @param element The element to prevent zoom on
* Prevent zoom on specified element
* @param {HTMLElement} element
*/
function preventElementZoom(element: HTMLElement) {
function preventElementZoom(element: HTMLElement): void {
if (!element) {
return;
}
element.addEventListener("touchstart", (event: TouchEvent) => {
if (!(event.currentTarget instanceof HTMLElement)) {

View File

@ -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";
import BattleScene from "./battle-scene";
type ActionKeys = Record<Button, () => void>;
@ -159,7 +161,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);
} else if (button === Button.V) {
this.buttonInfo(true);

View File

@ -0,0 +1,259 @@
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 {NavigationManager} from "#app/ui/settings/navigationMenu";
/**
* 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;
abstract swapAction(): boolean;
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();
NavigationManager.getInstance().updateIcons();
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);
}
}

View File

@ -0,0 +1,647 @@
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} from "#app/configs/inputs/configHandler";
import NavigationMenu, {NavigationManager} from "#app/ui/settings/navigationMenu";
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<String>;
}
/**
* 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 navigationContainer: NavigationMenu;
protected scrollCursor: integer;
protected optionCursors: integer[];
protected cursorObj: 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<string, LayoutConfig> = new Map<string, LayoutConfig>();
// 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<String>;
// Store the specific settings related to key bindings for the current gamepad configuration.
protected bindingSettings: Array<String>;
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.navigationIcons = {};
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.navigationContainer = new NavigationMenu(this.scene, 0, 0);
this.optionsBg = addWindow(this.scene, 0, this.navigationContainer.height, (this.scene.game.canvas.width / 6) - 2, (this.scene.game.canvas.height / 6) - 16 - this.navigationContainer.height - 2);
this.optionsBg.setOrigin(0, 0);
this.actionsBg = addWindow(this.scene, 0, (this.scene.game.canvas.height / 6) - this.navigationContainer.height, (this.scene.game.canvas.width / 6) - 2, 22);
this.actionsBg.setOrigin(0, 0);
const iconAction = this.scene.add.sprite(0, 0, "keyboard");
iconAction.setOrigin(0, -0.1);
iconAction.setPositionRelative(this.actionsBg, this.navigationContainer.width - 32, 4);
this.navigationIcons["BUTTON_ACTION"] = iconAction;
const actionText = addTextObject(this.scene, 0, 0, "Action", TextStyle.SETTINGS_LABEL);
actionText.setOrigin(0, 0.15);
actionText.setPositionRelative(iconAction, -actionText.width/6-2, 0);
const iconCancel = this.scene.add.sprite(0, 0, "keyboard");
iconCancel.setOrigin(0, -0.1);
iconCancel.setPositionRelative(this.actionsBg, this.navigationContainer.width - 100, 4);
this.navigationIcons["BUTTON_CANCEL"] = iconCancel;
const cancelText = addTextObject(this.scene, 0, 0, "Cancel", TextStyle.SETTINGS_LABEL);
cancelText.setOrigin(0, 0.15);
cancelText.setPositionRelative(iconCancel, -cancelText.width/6-2, 0);
const iconReset = this.scene.add.sprite(0, 0, "keyboard");
iconReset.setOrigin(0, -0.1);
iconReset.setPositionRelative(this.actionsBg, this.navigationContainer.width - 180, 4);
this.navigationIcons["BUTTON_HOME"] = iconReset;
const resetText = addTextObject(this.scene, 0, 0, "Reset all", TextStyle.SETTINGS_LABEL);
resetText.setOrigin(0, 0.15);
resetText.setPositionRelative(iconReset, -resetText.width/6-2, 0);
this.settingsContainer.add(this.optionsBg);
this.settingsContainer.add(this.actionsBg);
this.settingsContainer.add(this.navigationContainer);
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 'Controller', 'Gamepad Support', etc.
const settingLabels: Phaser.GameObjects.Text[] = [];
// Array to hold options for each setting, e.g., 'Auto', 'Disabled'.
const optionValueLabels: Phaser.GameObjects.GameObject[][] = [];
// Object to store sprites for each button configuration.
const inputsIcons: InputsIcons = {};
// Fetch common setting keys such as '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.
const 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.GameObject[] = [];
// 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.setOrigin(0, -0.15);
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 as Phaser.GameObjects.Text).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(130, 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 (const value of optionValueLabels[s]) {
// Set the option label's position right next to the setting label, adjusted by xOffset
(value as Phaser.GameObjects.Text).setPositionRelative(settingLabels[s], labelWidth + xOffset, 0);
// Move the xOffset to the right for the next label, ensuring each label is spaced evenly
xOffset += (value as Phaser.GameObjects.Text).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 as string) ? settings[key as string] : 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 as string].setFrame(icon);
this.inputsIcons[elm as string].alpha = 1;
} else {
this.inputsIcons[elm as string].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": "HOME.png",
"BUTTON_DELETE": "DEL.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();
NavigationManager.getInstance().updateIcons();
// 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;
NavigationManager.getInstance().reset();
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:
case Button.CYCLE_SHINY:
success = this.navigationContainer.navigate(button);
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, 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 (const 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;
}
}

View File

@ -0,0 +1,83 @@
import BattleScene from "../../battle-scene";
import AbstractBindingUiHandler from "./abstract-binding-ui-handler";
import {Mode} from "../ui";
import {Device} from "#app/enums/devices";
import {getIconWithSettingName, getKeyWithKeycode} from "#app/configs/inputs/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.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.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);
}
}

View File

@ -0,0 +1,73 @@
import BattleScene from "../../battle-scene";
import AbstractBindingUiHandler from "./abstract-binding-ui-handler";
import {Mode} from "../ui";
import { getKeyWithKeycode} from "#app/configs/inputs/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);
}
setup() {
super.setup();
// New button icon setup.
this.newButtonIcon = this.scene.add.sprite(0, 0, "keyboard");
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;
}
}

View File

@ -0,0 +1,217 @@
import BattleScene from "#app/battle-scene";
import {Mode} from "#app/ui/ui";
import {InputsIcons} from "#app/ui/settings/abstract-settings-ui-handler";
import {addTextObject, setTextStyle, TextStyle} from "#app/ui/text";
import {addWindow} from "#app/ui/ui-theme";
import {Button} from "#app/enums/buttons";
/**
* Manages navigation and menus tabs within the setting menu.
*/
export class NavigationManager {
private static instance: NavigationManager;
public modes: Mode[];
public selectedMode: Mode = Mode.SETTINGS;
public navigationMenus: NavigationMenu[] = new Array<NavigationMenu>();
public labels: string[];
/**
* Creates an instance of NavigationManager.
* To create a new tab in the menu, add the mode to the modes array and the label to the labels array.
* and instantiate a new NavigationMenu instance in your handler
* like: this.navigationContainer = new NavigationMenu(this.scene, 0, 0);
*/
constructor() {
this.modes = [
Mode.SETTINGS,
Mode.SETTINGS_GAMEPAD,
Mode.SETTINGS_KEYBOARD,
];
this.labels = ["General", "Gamepad", "Keyboard"];
}
public reset() {
this.selectedMode = Mode.SETTINGS;
}
/**
* Gets the singleton instance of the NavigationManager.
* @returns The singleton instance of NavigationManager.
*/
public static getInstance(): NavigationManager {
if (!NavigationManager.instance) {
NavigationManager.instance = new NavigationManager();
}
return NavigationManager.instance;
}
/**
* Navigates to the previous mode in the modes array.
* @param scene The current BattleScene instance.
*/
public navigateLeft(scene) {
const pos = this.modes.indexOf(this.selectedMode);
const maxPos = this.modes.length - 1;
if (pos === 0) {
this.selectedMode = this.modes[maxPos];
} else {
this.selectedMode = this.modes[pos - 1];
}
scene.ui.setMode(this.selectedMode);
this.updateNavigationMenus();
}
/**
* Navigates to the next mode in the modes array.
* @param scene The current BattleScene instance.
*/
public navigateRight(scene) {
const pos = this.modes.indexOf(this.selectedMode);
const maxPos = this.modes.length - 1;
if (pos === maxPos) {
this.selectedMode = this.modes[0];
} else {
this.selectedMode = this.modes[pos + 1];
}
scene.ui.setMode(this.selectedMode);
this.updateNavigationMenus();
}
/**
* Updates all navigation menus.
*/
public updateNavigationMenus() {
for (const instance of this.navigationMenus) {
instance.update();
}
}
/**
* Updates icons for all navigation menus.
*/
public updateIcons() {
for (const instance of this.navigationMenus) {
instance.updateIcons();
}
}
}
export default class NavigationMenu extends Phaser.GameObjects.Container {
private navigationIcons: InputsIcons;
public scene: BattleScene;
protected headerTitles: Phaser.GameObjects.Text[] = new Array<Phaser.GameObjects.Text>();
/**
* Creates an instance of NavigationMenu.
* @param scene The current BattleScene instance.
* @param x The x position of the NavigationMenu.
* @param y The y position of the NavigationMenu.
*/
constructor(scene: BattleScene, x: number, y: number) {
super(scene, x, y);
this.scene = scene;
this.setup();
}
/**
* Sets up the NavigationMenu by adding windows, icons, and labels.
*/
setup() {
const navigationManager = NavigationManager.getInstance();
const headerBg = addWindow(this.scene, 0, 0, (this.scene.game.canvas.width / 6) - 2, 24);
headerBg.setOrigin(0, 0);
this.add(headerBg);
this.width = headerBg.width;
this.height = headerBg.height;
this.navigationIcons = {};
const iconPreviousTab = this.scene.add.sprite(8, 4, "keyboard");
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.setOrigin(0, -0.1);
iconNextTab.setPositionRelative(headerBg, headerBg.width - 20, 4);
this.navigationIcons["BUTTON_CYCLE_SHINY"] = iconNextTab;
let relative: Phaser.GameObjects.Sprite | Phaser.GameObjects.Text = iconPreviousTab;
let relativeWidth: number = iconPreviousTab.width*6;
for (const label of navigationManager.labels) {
const labelText = addTextObject(this.scene, 0, 0, label, TextStyle.SETTINGS_LABEL);
labelText.setOrigin(0, 0);
labelText.setPositionRelative(relative, 6 + relativeWidth/6, 0);
this.add(labelText);
this.headerTitles.push(labelText);
relative = labelText;
relativeWidth = labelText.width;
}
this.add(iconPreviousTab);
this.add(iconNextTab);
navigationManager.navigationMenus.push(this);
navigationManager.updateNavigationMenus();
}
/**
* Updates the NavigationMenu's header titles based on the selected mode.
*/
update() {
const navigationManager = NavigationManager.getInstance();
const posSelected = navigationManager.modes.indexOf(navigationManager.selectedMode);
for (const [index, title] of this.headerTitles.entries()) {
setTextStyle(title, this.scene, index === posSelected ? TextStyle.SETTINGS_SELECTED : TextStyle.SETTINGS_LABEL);
}
}
/**
* Updates the icons in the NavigationMenu based on the latest input recorded.
*/
updateIcons() {
const specialIcons = {
"BUTTON_HOME": "HOME.png",
"BUTTON_DELETE": "DEL.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;
}
}
}
/**
* Handles navigation based on the button pressed.
* @param button The button pressed for navigation.
* @returns A boolean indicating if the navigation was handled.
*/
navigate(button: Button): boolean {
const navigationManager = NavigationManager.getInstance();
switch (button) {
case Button.CYCLE_FORM:
navigationManager.navigateLeft(this.scene);
return true;
break;
case Button.CYCLE_SHINY:
navigationManager.navigateRight(this.scene);
return true;
break;
}
return false;
}
}

View File

@ -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) {

View File

@ -0,0 +1,168 @@
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/inputs/pad_xbox360";
import pad_dualshock from "#app/configs/inputs/pad_dualshock";
import pad_unlicensedSNES from "#app/configs/inputs/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";
/**
* 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 controller setting.
if (setting === this.settingDevice.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], 20));
}
}
}
}
/**
* 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.Controller) {
this.scene.gameData.saveGamepadSetting(setting, cursor);
}
}
}

View File

@ -0,0 +1,224 @@
import BattleScene from "../../battle-scene";
import {Mode} from "../ui";
import cfg_keyboard_qwerty from "#app/configs/inputs/cfg_keyboard_qwerty";
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/inputs/configHandler";
import {Device} from "#app/enums/devices";
import {NavigationManager} from "#app/ui/settings/navigationMenu";
/**
* 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_qwerty];
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.setOrigin(0, -0.1);
iconDelete.setPositionRelative(this.actionsBg, this.navigationContainer.width - 260, 4);
this.navigationIcons["BUTTON_DELETE"] = iconDelete;
const deleteText = addTextObject(this.scene, 0, 0, "Delete", TextStyle.SETTINGS_LABEL);
deleteText.setOrigin(0, 0.15);
deleteText.setPositionRelative(iconDelete, -deleteText.width/6-2, 0);
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();
NavigationManager.getInstance().updateIcons();
}
/**
* 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();
NavigationManager.getInstance().updateIcons();
}
}
/**
* 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 layout 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);
}
}
}

View File

@ -1,15 +1,18 @@
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";
import NavigationMenu, {NavigationManager} from "#app/ui/settings/navigationMenu";
export default class SettingsUiHandler extends UiHandler {
private settingsContainer: Phaser.GameObjects.Container;
private optionsContainer: Phaser.GameObjects.Container;
private navigationContainer: NavigationMenu;
private scrollCursor: integer;
@ -20,16 +23,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 +44,36 @@ 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);
const headerBg = addWindow(this.scene, 0, 0, (this.scene.game.canvas.width / 6) - 2, 24);
headerBg.setOrigin(0, 0);
this.navigationIcons = {};
const headerText = addTextObject(this.scene, 0, 0, "Options", TextStyle.SETTINGS_LABEL);
headerText.setOrigin(0, 0);
headerText.setPositionRelative(headerBg, 8, 4);
this.navigationContainer = new NavigationMenu(this.scene, 0, 0);
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, this.navigationContainer.height, (this.scene.game.canvas.width / 6) - 2, (this.scene.game.canvas.height / 6) - 16 - this.navigationContainer.height - 2);
this.optionsBg.setOrigin(0, 0);
const actionsBg = addWindow(this.scene, 0, (this.scene.game.canvas.height / 6) - this.navigationContainer.height, (this.scene.game.canvas.width / 6) - 2, 22);
actionsBg.setOrigin(0, 0);
const iconAction = this.scene.add.sprite(0, 0, "keyboard");
iconAction.setOrigin(0, -0.1);
iconAction.setPositionRelative(actionsBg, this.navigationContainer.width - 32, 4);
this.navigationIcons["BUTTON_ACTION"] = iconAction;
const actionText = addTextObject(this.scene, 0, 0, "Action", TextStyle.SETTINGS_LABEL);
actionText.setOrigin(0, 0.15);
actionText.setPositionRelative(iconAction, -actionText.width/6-2, 0);
const iconCancel = this.scene.add.sprite(0, 0, "keyboard");
iconCancel.setOrigin(0, -0.1);
iconCancel.setPositionRelative(actionsBg, this.navigationContainer.width - 100, 4);
this.navigationIcons["BUTTON_CANCEL"] = iconCancel;
const cancelText = addTextObject(this.scene, 0, 0, "Cancel", TextStyle.SETTINGS_LABEL);
cancelText.setOrigin(0, 0.15);
cancelText.setPositionRelative(iconCancel, -cancelText.width/6-2, 0);
this.optionsContainer = this.scene.add.container(0, 0);
this.settingLabels = [];
@ -91,10 +116,14 @@ export default class SettingsUiHandler extends UiHandler {
this.optionCursors = Object.values(settingDefaults);
this.settingsContainer.add(headerBg);
this.settingsContainer.add(headerText);
this.settingsContainer.add(this.optionsBg);
this.settingsContainer.add(this.navigationContainer);
this.settingsContainer.add(actionsBg);
this.settingsContainer.add(this.optionsContainer);
this.settingsContainer.add(iconAction);
this.settingsContainer.add(iconCancel);
this.settingsContainer.add(actionText);
this.settingsContainer.add(cancelText);
ui.add(this.settingsContainer);
@ -104,8 +133,30 @@ 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("HOME.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;
}
}
NavigationManager.getInstance().updateIcons();
}
show(args: any[]): boolean {
super.show(args);
this.updateBindings();
const settings: object = localStorage.hasOwnProperty("settings") ? JSON.parse(localStorage.getItem("settings")) : {};
@ -133,12 +184,12 @@ 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;
if (button === Button.CANCEL) {
success = true;
NavigationManager.getInstance().reset();
// Reverts UI to its previous state on cancel.
this.scene.ui.revertMode();
} else {
@ -154,17 +205,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 {
@ -187,6 +238,10 @@ export default class SettingsUiHandler extends UiHandler {
success = this.setOptionCursor(cursor, this.optionCursors[cursor] + 1, true);
}
break;
case Button.CYCLE_FORM:
case Button.CYCLE_SHINY:
success = this.navigationContainer.navigate(button);
break;
}
}
@ -263,7 +318,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 (const option of this.optionValueLabels[s]) {
option.setVisible(visible);

View File

@ -742,7 +742,7 @@ export default class SummaryUiHandler extends UiHandler {
allAbilityInfo.push(this.passiveContainer);
// Sets up the pixel button prompt image
this.abilityPrompt = this.scene.add.image(0, 0, !this.scene.gamepadSupport ? "summary_profile_prompt_z" : "summary_profile_prompt_a");
this.abilityPrompt = this.scene.add.image(0, 0, !this.scene.inputController?.gamepadSupport ? "summary_profile_prompt_z" : "summary_profile_prompt_a");
this.abilityPrompt.setPosition(8, 43);
this.abilityPrompt.setVisible(true);
this.abilityPrompt.setOrigin(0, 0);

View File

@ -26,6 +26,7 @@ export enum TextStyle {
STATS_VALUE,
SETTINGS_LABEL,
SETTINGS_SELECTED,
SETTINGS_LOCKED,
TOOLTIP_TITLE,
TOOLTIP_CONTENT,
MOVE_INFO_CONTENT
@ -63,6 +64,15 @@ export function addTextObject(scene: Phaser.Scene, x: number, y: number, content
return ret;
}
export function setTextStyle(obj: Phaser.GameObjects.Text, scene: Phaser.Scene, style: TextStyle, extraStyleOptions?: Phaser.Types.GameObjects.Text.TextStyle) {
const [ styleOptions, shadowColor, shadowXpos, shadowYpos ] = getTextStyleOptions(style, (scene as BattleScene).uiTheme, extraStyleOptions);
obj.setScale(0.1666666667);
obj.setShadow(shadowXpos, shadowYpos, shadowColor);
if (!(styleOptions as Phaser.Types.GameObjects.Text.TextStyle).lineSpacing) {
obj.setLineSpacing(5);
}
}
export function addBBCodeTextObject(scene: Phaser.Scene, x: number, y: number, content: string, style: TextStyle, extraStyleOptions?: Phaser.Types.GameObjects.Text.TextStyle): BBCodeText {
const [ styleOptions, shadowColor, shadowXpos, shadowYpos ] = getTextStyleOptions(style, (scene as BattleScene).uiTheme, extraStyleOptions);
@ -143,6 +153,7 @@ function getTextStyleOptions(style: TextStyle, uiTheme: UiTheme, extraStyleOptio
break;
case TextStyle.MESSAGE:
case TextStyle.SETTINGS_LABEL:
case TextStyle.SETTINGS_LOCKED:
case TextStyle.SETTINGS_SELECTED:
styleOptions.fontSize = languageSettings[lang]?.summaryFontSize || "96px";
break;
@ -226,6 +237,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.STATS_LABEL:

View File

@ -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";

View File

@ -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 {addTextObject, TextStyle} from "./text";
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";
@ -38,6 +39,9 @@ import SessionReloadModalUiHandler from "./session-reload-modal-ui-handler";
import {Button} from "../enums/buttons";
import i18next, {ParseKeys} from "i18next";
import {PlayerGender} from "#app/system/game-data";
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,
@ -58,6 +62,10 @@ export enum Mode {
MENU,
MENU_OPTION_SELECT,
SETTINGS,
SETTINGS_GAMEPAD,
GAMEPAD_BINDING,
SETTINGS_KEYBOARD,
KEYBOARD_BINDING,
ACHIEVEMENTS,
GAME_STATS,
VOUCHERS,
@ -88,7 +96,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,
@ -103,7 +115,7 @@ const noTransitionModes = [
export default class UI extends Phaser.GameObjects.Container {
private mode: Mode;
private modeChain: Mode[];
private handlers: UiHandler[];
public handlers: UiHandler[];
private overlay: Phaser.GameObjects.Rectangle;
public achvBar: AchvBar;
public savingIcon: SavingIconHandler;
@ -139,6 +151,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),

View File

@ -423,3 +423,49 @@ export function printContainerList(container: Phaser.GameObjects.Container): voi
return {type: go.type, name: go.name};
}));
}
/**
* 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("_");
}