From c8db5fa106a813c4e76941d49233d482ee7dbefc Mon Sep 17 00:00:00 2001 From: fgsfds Date: Sat, 28 Oct 2023 18:17:35 +0200 Subject: [PATCH] port: add keybind menu --- port/include/input.h | 8 ++ port/src/input.c | 96 ++++++++++++-- port/src/optionsmenu.c | 288 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 378 insertions(+), 14 deletions(-) diff --git a/port/include/input.h b/port/include/input.h index e3f21f0b3..9e68791f7 100644 --- a/port/include/input.h +++ b/port/include/input.h @@ -6,6 +6,7 @@ #define INPUT_MAX_CONTROLLERS MAXCONTROLLERS #define INPUT_MAX_CONTROLLER_BUTTONS 32 +#define INPUT_MAX_BINDS 4 #define CONT_STICK_XNEG 0x10000 #define CONT_STICK_XPOS 0x20000 @@ -115,6 +116,8 @@ s32 inputButtonPressed(s32 idx, u32 contbtn); // if bind is -1, picks a bind slot automatically void inputKeyBind(s32 idx, u32 ck, s32 bind, u32 vk); +const u32 *inputKeyGetBinds(s32 idx, u32 ck); + // get VK_ value from human-readable name s32 inputGetKeyByName(const char *name); @@ -165,4 +168,9 @@ void inputUpdate(void); // call this before configSave() void inputSaveConfig(void); +void inputSetDefaultKeyBinds(void); + +void inputClearLastKey(void); +s32 inputGetLastKey(void); + #endif diff --git a/port/src/input.c b/port/src/input.c index 5103dd037..ddb5b5675 100644 --- a/port/src/input.c +++ b/port/src/input.c @@ -10,7 +10,6 @@ #include "config.h" #include "system.h" -#define MAX_BINDS 4 #define TRIG_THRESHOLD (30 * 256) #define DEFAULT_DEADZONE 4096 #define DEFAULT_DEADZONE_RY 6144 @@ -21,7 +20,7 @@ static SDL_GameController *pads[INPUT_MAX_CONTROLLERS]; static s32 rumbleSupported[INPUT_MAX_CONTROLLERS]; -static u32 binds[MAXCONTROLLERS][CK_TOTAL_COUNT][MAX_BINDS]; // [i][CK_][b] = [VK_] +static u32 binds[MAXCONTROLLERS][CK_TOTAL_COUNT][INPUT_MAX_BINDS]; // [i][CK_][b] = [VK_] static s32 connectedMask = 0; @@ -37,6 +36,8 @@ static f32 mouseSensY = 1.5f; static f32 rumbleScale = 0.5f; +static s32 lastKey = 0; + // NOTE: by default this gets inverted for 1.2: "right stick" here means left stick on your controller static u32 axisMap[2][2] = { { SDL_CONTROLLER_AXIS_LEFTX, SDL_CONTROLLER_AXIS_LEFTY }, @@ -186,6 +187,8 @@ void inputSetDefaultKeyBinds(void) { CK_8000, SDL_CONTROLLER_BUTTON_LEFTSTICK }, }; + memset(binds, 0, sizeof(binds)); + for (u32 i = 0; i < sizeof(kbbinds) / sizeof(kbbinds[0]); ++i) { for (s32 j = 1; j < 3; ++j) { if (kbbinds[i][j]) { @@ -233,6 +236,18 @@ static inline void inputCloseController(const s32 cidx) } } + +static inline s32 inputControllerGetIndex(SDL_GameController *ctrl) { + if (ctrl) { + for (s32 i = 0; i < INPUT_MAX_CONTROLLERS; ++i) { + if (pads[i] == ctrl) { + return i; + } + } + } + return -1; +} + static int inputEventFilter(void *data, SDL_Event *event) { switch (event->type) { @@ -250,19 +265,54 @@ static int inputEventFilter(void *data, SDL_Event *event) case SDL_CONTROLLERDEVICEREMOVED: { SDL_GameController *ctrl = SDL_GameControllerFromInstanceID(event->cdevice.which); - if (ctrl) { - for (s32 i = 0; i < INPUT_MAX_CONTROLLERS; ++i) { - if (pads[i] == ctrl) { - inputCloseController(i); - break; - } - } + const s32 idx = inputControllerGetIndex(ctrl); + if (idx >= 0) { + inputCloseController(idx); } break; } case SDL_MOUSEWHEEL: mouseWheel = event->wheel.y; + if (!lastKey && mouseWheel) { + lastKey = (mouseWheel < 0) + VK_MOUSE_WHEEL_UP; + } + break; + + case SDL_MOUSEBUTTONDOWN: + if (!lastKey) { + lastKey = VK_MOUSE_BEGIN - 1 + event->button.button; + } + break; + + case SDL_KEYDOWN: + if (!lastKey) { + lastKey = VK_KEYBOARD_BEGIN + event->key.keysym.scancode; + } + break; + + case SDL_CONTROLLERBUTTONDOWN: + if (!lastKey) { + lastKey = VK_JOY1_BEGIN + event->cbutton.button; + SDL_GameController *ctrl = SDL_GameControllerFromInstanceID(event->cdevice.which); + const s32 idx = inputControllerGetIndex(ctrl); + if (idx >= 0) { + lastKey += idx * INPUT_MAX_CONTROLLER_BUTTONS; + } + } + break; + + case SDL_CONTROLLERAXISMOTION: + if (!lastKey) { + if (event->caxis.axis >= SDL_CONTROLLER_AXIS_TRIGGERLEFT && event->caxis.value > TRIG_THRESHOLD) { + lastKey = VK_JOY1_LTRIG + (event->caxis.axis - SDL_CONTROLLER_AXIS_TRIGGERLEFT); + SDL_GameController *ctrl = SDL_GameControllerFromInstanceID(event->cdevice.which); + const s32 idx = inputControllerGetIndex(ctrl); + if (idx >= 0) { + lastKey += idx * INPUT_MAX_CONTROLLER_BUTTONS; + } + } + } break; default: @@ -349,7 +399,7 @@ static inline void inputSaveBinds(void) for (u32 ck = 0; ck < CK_TOTAL_COUNT; ++ck) { snprintf(keyname, sizeof(keyname), "%s.%s", secname, inputGetContKeyName(ck)); bindstr[0] = '\0'; - for (s32 b = 0; b < MAX_BINDS; ++b) { + for (s32 b = 0; b < INPUT_MAX_BINDS; ++b) { if (binds[i][ck][b]) { if (b) { strncat(bindstr, ", ", sizeof(bindstr) - 1); @@ -500,7 +550,7 @@ s32 inputInit(void) static inline s32 inputBindPressed(const s32 idx, const u32 ck) { - for (s32 i = 0; i < MAX_BINDS; ++i) { + for (s32 i = 0; i < INPUT_MAX_BINDS; ++i) { if (binds[idx][ck][i]) { if (inputKeyPressed(binds[idx][ck][i])) { return 1; @@ -733,26 +783,34 @@ void inputControllerSetAxisDeadzone(s32 stick, s32 axis, f32 value) void inputKeyBind(s32 idx, u32 ck, s32 bind, u32 vk) { - if (idx < 0 || idx >= INPUT_MAX_CONTROLLERS || bind >= MAX_BINDS || ck >= CK_TOTAL_COUNT) { + if (idx < 0 || idx >= INPUT_MAX_CONTROLLERS || bind >= INPUT_MAX_BINDS || ck >= CK_TOTAL_COUNT) { return; } if (bind < 0) { - for (s32 i = 0; i < MAX_BINDS; ++i) { + for (s32 i = 0; i < INPUT_MAX_BINDS; ++i) { if (binds[idx][ck][i] == 0) { bind = i; break; } } if (bind < 0) { - bind = MAX_BINDS - 1; // just overwrite last + bind = INPUT_MAX_BINDS - 1; // just overwrite last } } binds[idx][ck][bind] = vk; } +const u32 *inputKeyGetBinds(s32 idx, u32 ck) +{ + if (idx < 0 || idx >= INPUT_MAX_CONTROLLERS || ck >= CK_TOTAL_COUNT) { + return NULL; + } + return binds[idx][ck]; +} + s32 inputKeyPressed(u32 vk) { if (vk >= VK_KEYBOARD_BEGIN && vk < VK_MOUSE_BEGIN) { @@ -935,3 +993,13 @@ s32 inputGetKeyByName(const char *name) return -1; } + +void inputClearLastKey(void) +{ + lastKey = 0; +} + +s32 inputGetLastKey(void) +{ + return lastKey; +} diff --git a/port/src/optionsmenu.c b/port/src/optionsmenu.c index f1d827108..3fde8001f 100644 --- a/port/src/optionsmenu.c +++ b/port/src/optionsmenu.c @@ -11,6 +11,11 @@ #include "input.h" #include "config.h" +static s32 g_BindsPlayer = 0; +static s32 g_BindIndex = 0; +static u32 g_BindContKey = 0; +static char g_BindsPlayerText[] = "Player 1 Bindings"; + static MenuItemHandlerResult menuhandlerMouseEnabled(s32 operation, struct menuitem *item, union handlerdata *data) { switch (operation) { @@ -691,6 +696,281 @@ struct menudialogdef g_ExtendedGameMenuDialog = { NULL, }; +static MenuItemHandlerResult menuhandlerDoBind(s32 operation, struct menuitem *item, union handlerdata *data); + +struct menuitem g_ExtendedBindKeyMenuItems[] = { + { + MENUITEMTYPE_LABEL, + 0, + MENUITEMFLAG_SELECTABLE_CENTRE | MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"\n", + 0, + NULL, + }, + { + MENUITEMTYPE_SEPARATOR, + 0, + 0, + 0, + 0, + NULL, + }, + { + MENUITEMTYPE_SELECTABLE, + 0, + MENUITEMFLAG_SELECTABLE_CENTRE | MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Press new key or button...\n", + 0, + menuhandlerDoBind, + }, + { MENUITEMTYPE_END }, +}; + +struct menudialogdef g_ExtendedBindKeyMenuDialog = { + MENUDIALOGTYPE_WHITE, + (uintptr_t)"Bind", + g_ExtendedBindKeyMenuItems, + NULL, + MENUDIALOGFLAG_LITERAL_TEXT | MENUDIALOGFLAG_IGNOREBACK | MENUDIALOGFLAG_STARTSELECTS, + NULL, +}; + +struct menubind { + u32 ck; + const char *name; +}; + +static const struct menubind menuBinds[] = { + { CK_ZTRIG, "Fire [ZT]\n" }, + { CK_LTRIG, "Fire Mode [LT]\n" }, + { CK_RTRIG, "Aim Mode [RT]\n" }, + { CK_A, "Use / Accept [A]\n" }, + { CK_B, "Use / Cancel [B]\n" }, + { CK_X, "Reload [X]\n" }, + { CK_Y, "Next Weapon [Y]\n" }, + { CK_DPAD_L, "Prev Weapon [DL]\n" }, + { CK_DPAD_L, "Radial Menu [DD]\n" }, + { CK_START, "Pause Menu [ST]\n" }, + { CK_8000, "Cycle Crouch [+]\n" }, + { CK_4000, "Half Crouch [+]\n" }, + { CK_2000, "Full Crouch [+]\n" }, +}; + +static const char *menutextBind(struct menuitem *item); +static MenuItemHandlerResult menuhandlerBind(s32 operation, struct menuitem *item, union handlerdata *data); +static MenuItemHandlerResult menuhandlerResetBinds(s32 operation, struct menuitem *item, union handlerdata *data); + +#define DEFINE_MENU_BIND() \ + { \ + MENUITEMTYPE_DROPDOWN, \ + 0, \ + 0, \ + (uintptr_t)menutextBind, \ + 0, \ + menuhandlerBind, \ + } + +struct menuitem g_ExtendedBindsMenuItems[] = { + DEFINE_MENU_BIND(), + DEFINE_MENU_BIND(), + DEFINE_MENU_BIND(), + DEFINE_MENU_BIND(), + DEFINE_MENU_BIND(), + DEFINE_MENU_BIND(), + DEFINE_MENU_BIND(), + DEFINE_MENU_BIND(), + DEFINE_MENU_BIND(), + DEFINE_MENU_BIND(), + DEFINE_MENU_BIND(), + DEFINE_MENU_BIND(), + DEFINE_MENU_BIND(), + { + MENUITEMTYPE_SEPARATOR, + 0, + 0, + 0, + 0, + NULL, + }, + { + MENUITEMTYPE_SELECTABLE, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Reset to Defaults\n", + 0, + menuhandlerResetBinds, + }, + { + MENUITEMTYPE_SEPARATOR, + 0, + 0, + 0, + 0, + NULL, + }, + { + MENUITEMTYPE_SELECTABLE, + 0, + MENUITEMFLAG_SELECTABLE_CLOSESDIALOG, + L_OPTIONS_213, // "Back" + 0, + NULL, + }, + { MENUITEMTYPE_END }, +}; + +static MenuItemHandlerResult menuhandlerDoBind(s32 operation, struct menuitem *item, union handlerdata *data) +{ + if (!menuIsDialogOpen(&g_ExtendedBindKeyMenuDialog)) { + return 0; + } + + if (inputKeyPressed(VK_ESCAPE)) { + menuPopDialog(); + return 0; + } + + const s32 key = inputGetLastKey(); + if (key && key != VK_ESCAPE) { + inputKeyBind(g_BindsPlayer, g_BindContKey, g_BindIndex, key); + menuPopDialog(); + } + + return 0; +} + +static const char *menutextBind(struct menuitem *item) +{ + return menuBinds[item - g_ExtendedBindsMenuItems].name; +} + +static MenuItemHandlerResult menuhandlerBind(s32 operation, struct menuitem *item, union handlerdata *data) +{ + const s32 idx = item - g_ExtendedBindsMenuItems; + const u32 *binds; + + static char keyname[128]; + + switch (operation) { + case MENUOP_GETOPTIONCOUNT: + data->dropdown.value = INPUT_MAX_BINDS; + break; + case MENUOP_GETOPTIONTEXT: + binds = inputKeyGetBinds(g_BindsPlayer, menuBinds[idx].ck); + if (binds && binds[data->dropdown.value]) { + strncpy(keyname, inputGetKeyName(binds[data->dropdown.value]), sizeof(keyname) - 1); + for (char *p = keyname; *p; ++p) { + if (*p == '_') *p = ' '; + } + return (intptr_t)keyname; + } + return (intptr_t)"NONE"; + case MENUOP_SET: + g_ExtendedBindKeyMenuItems[0].param2 = (uintptr_t)menuBinds[idx].name; + g_BindIndex = data->dropdown.value; + g_BindContKey = menuBinds[idx].ck; + inputClearLastKey(); + menuPushDialog(&g_ExtendedBindKeyMenuDialog); + break; + case MENUOP_GETSELECTEDINDEX: + data->dropdown.value = 0; + } + + return 0; +} + +static MenuItemHandlerResult menuhandlerResetBinds(s32 operation, struct menuitem *item, union handlerdata *data) +{ + if (operation == MENUOP_SET) { + inputSetDefaultKeyBinds(); + } + + return 0; +} + +struct menudialogdef g_ExtendedBindsMenuDialog = { + MENUDIALOGTYPE_DEFAULT, + (uintptr_t)g_BindsPlayerText, + g_ExtendedBindsMenuItems, + NULL, + MENUDIALOGFLAG_LITERAL_TEXT | MENUDIALOGFLAG_STARTSELECTS | MENUDIALOGFLAG_IGNOREBACK, + NULL, +}; + +static MenuItemHandlerResult menuhandlerOpenBinds(s32 operation, struct menuitem *item, union handlerdata *data); + +struct menuitem g_ExtendedSelectBindsMenuItems[] = { + { + MENUITEMTYPE_SELECTABLE, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Player 1\n", + 0, + menuhandlerOpenBinds, + }, + { + MENUITEMTYPE_SELECTABLE, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Player 2\n", + 0, + menuhandlerOpenBinds, + }, + { + MENUITEMTYPE_SELECTABLE, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Player 3\n", + 0, + menuhandlerOpenBinds, + }, + { + MENUITEMTYPE_SELECTABLE, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Player 4\n", + 0, + menuhandlerOpenBinds, + }, + { + MENUITEMTYPE_SEPARATOR, + 0, + 0, + 0, + 0, + NULL, + }, + { + MENUITEMTYPE_SELECTABLE, + 0, + MENUITEMFLAG_SELECTABLE_CLOSESDIALOG, + L_OPTIONS_213, // "Back" + 0, + NULL, + }, + { MENUITEMTYPE_END }, +}; + +static MenuItemHandlerResult menuhandlerOpenBinds(s32 operation, struct menuitem *item, union handlerdata *data) +{ + if (operation == MENUOP_SET) { + g_BindsPlayer = item - g_ExtendedSelectBindsMenuItems; + g_BindsPlayerText[7] = g_BindsPlayer + '1'; + menuPushDialog(&g_ExtendedBindsMenuDialog); + } + + return 0; +} + +struct menudialogdef g_ExtendedSelectBindsMenuDialog = { + MENUDIALOGTYPE_DEFAULT, + (uintptr_t)"Key Bindings", + g_ExtendedSelectBindsMenuItems, + NULL, + MENUDIALOGFLAG_LITERAL_TEXT, + NULL, +}; + struct menuitem g_ExtendedMenuItems[] = { { MENUITEMTYPE_SELECTABLE, @@ -724,6 +1004,14 @@ struct menuitem g_ExtendedMenuItems[] = { 0, (void *)&g_ExtendedGameMenuDialog, }, + { + MENUITEMTYPE_SELECTABLE, + 0, + MENUITEMFLAG_SELECTABLE_OPENSDIALOG | MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Key Bindings\n", + 0, + (void *)&g_ExtendedSelectBindsMenuDialog, + }, { MENUITEMTYPE_SEPARATOR, 0,