diff --git a/port/include/input.h b/port/include/input.h index 6171c26961..88d147c76c 100644 --- a/port/include/input.h +++ b/port/include/input.h @@ -121,6 +121,12 @@ enum mouselockmode { MLOCK_AUTO = 2 }; +enum ConfirmCancelButtonSwap { + CONFIRMCANCEL_SWAP_AUTO = -1, // auto-detect based on controller type + CONFIRMCANCEL_SWAP_OFF = 0, // use standard button layout + CONFIRMCANCEL_SWAP_ON = 1, // use japanese button layout +}; + // returns bitmask of connected controllers or -1 if failed s32 inputInit(void); @@ -181,6 +187,9 @@ s32 inputKeyJustPressed(u32 vk); // idx is controller index, contbtn is one of the CONT_ constants s32 inputButtonPressed(s32 idx, u32 contbtn); +// Remaps UI button for a controller (handles Confirm/Cancel Face Button swap) +u32 inputConfirmCancelButtonSwap(int cidx, u32 button); + // bind virtkey vk to n64 pad #idx's button/axis ck as represented by its contkey value // if bind is -1, picks a bind slot automatically void inputKeyBind(s32 idx, u32 ck, s32 bind, u32 vk); diff --git a/port/src/input.c b/port/src/input.c index 5dc230f8d6..8b27bcc744 100644 --- a/port/src/input.c +++ b/port/src/input.c @@ -11,6 +11,7 @@ #include "utils.h" #include "system.h" #include "fs.h" +#include "constants.h" #if !SDL_VERSION_ATLEAST(2, 0, 14) // this was added in 2.0.14 @@ -46,6 +47,7 @@ static SDL_GameController *pads[INPUT_MAX_CONTROLLERS]; .swapSticks = 1, \ .deviceIndex = -1, \ .cancelCButtons = 0, \ + .swapAcceptCancelButtons = -1, \ } static struct controllercfg { @@ -58,6 +60,7 @@ static struct controllercfg { s32 swapSticks; s32 deviceIndex; s32 cancelCButtons; + s32 swapAcceptCancelButtons; } padsCfg[INPUT_MAX_CONTROLLERS] = { CONTROLLERCFG_DEFAULT, CONTROLLERCFG_DEFAULT, @@ -350,6 +353,51 @@ static inline void inputInitController(const s32 cidx, const s32 jidx) } } +static int inputIsNintendoSwitchController(SDL_GameController *controller) { +#if SDL_VERSION_ATLEAST(2, 0, 12) + switch (SDL_GameControllerGetType(controller)) { + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO: +#if SDL_VERSION_ATLEAST(2, 24, 0) + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: +#endif + return 1; + default: + break; + } +#endif + + return 0; +} + +static int inputConfirmCancelSwapActive(int cidx) +{ + if (cidx < 0 || cidx >= INPUT_MAX_CONTROLLERS) { + return 0; + } + + switch (padsCfg[cidx].swapAcceptCancelButtons) { + case CONFIRMCANCEL_SWAP_ON: + return 1; + case CONFIRMCANCEL_SWAP_OFF: + return 0; + case CONFIRMCANCEL_SWAP_AUTO: + default: + // Nintendo controllers uses BAYX Layout (B=A, A=B) + return pads[cidx] && inputIsNintendoSwitchController(pads[cidx]); + } +} + +u32 inputConfirmCancelButtonSwap(int cidx, u32 button) +{ + if (inputConfirmCancelSwapActive(cidx)) { + if (button == BUTTON_UI_ACCEPT) return BUTTON_UI_CANCEL; + if (button == BUTTON_UI_CANCEL) return BUTTON_UI_ACCEPT; + } + return button; +} + static inline void inputCloseController(const s32 cidx) { sysLogPrintf(LOG_NOTE, "input: removed controller '%d: (%s)' (id %d) from player %d", @@ -761,9 +809,14 @@ s32 inputInit(void) static inline s32 inputBindPressed(const s32 idx, const u32 ck) { + u32 swapped_ck = ck; + if (inputConfirmCancelButtonSwap(idx, ck)) { + if (ck == CK_A) swapped_ck = CK_B; + else if (ck == CK_B) swapped_ck = CK_A; + } for (s32 i = 0; i < INPUT_MAX_BINDS; ++i) { - if (binds[idx][ck][i]) { - if (inputKeyPressed(binds[idx][ck][i])) { + if (binds[idx][swapped_ck][i]) { + if (inputKeyPressed(binds[idx][swapped_ck][i])) { return 1; } } @@ -1543,6 +1596,7 @@ PD_CONSTRUCTOR static void inputConfigInit(void) configRegisterInt(strFmt("%s.CancelCButtons", secname), &padsCfg[c].cancelCButtons, 0, 1); configRegisterInt(strFmt("%s.SwapSticks", secname), &padsCfg[c].swapSticks, 0, 1); configRegisterInt(strFmt("%s.ControllerIndex", secname), &padsCfg[c].deviceIndex, -1, 0x7FFFFFFF); + configRegisterInt(strFmt("%s.swapAcceptCancelButtons", secname), &padsCfg[c].swapAcceptCancelButtons, CONFIRMCANCEL_SWAP_AUTO, CONFIRMCANCEL_SWAP_ON); secname[13] = '.'; for (u32 ck = 0; ck < CK_TOTAL_COUNT; ++ck) { snprintf(keyname, sizeof(keyname), "%s.%s", secname, inputGetContKeyName(ck)); diff --git a/port/src/optionsmenu.c b/port/src/optionsmenu.c index 1390598003..9564210ce1 100644 --- a/port/src/optionsmenu.c +++ b/port/src/optionsmenu.c @@ -1754,6 +1754,32 @@ struct menuitem g_ExtendedBindsMenuItems[] = { { MENUITEMTYPE_END }, }; +static s32 menuSwapConfirmCancel(s32 key, u32 bindKey) +{ + // Only swap for CK_ACCEPT/CK_CANCEL + if ((bindKey != CK_ACCEPT && bindKey != CK_CANCEL) || + key < VK_JOY_BEGIN || key >= VK_TOTAL_COUNT) { + return key; + } + + // Check which controller this key belongs to + s32 controllerIdx = (key - VK_JOY_BEGIN) / INPUT_MAX_CONTROLLER_BUTTONS; + + // Check if swap is NOT active for this controller + if (inputConfirmCancelButtonSwap(controllerIdx, BUTTON_UI_ACCEPT) == BUTTON_UI_ACCEPT) { + return key; + } + + s32 buttonIdx = (key - VK_JOY_BEGIN) % INPUT_MAX_CONTROLLER_BUTTONS; + if (buttonIdx == 0) { + return key + 1; + } else if (buttonIdx == 1) { + return key - 1; + } + + return key; +} + static MenuItemHandlerResult menuhandlerDoBind(s32 operation, struct menuitem *item, union handlerdata *data) { if (!menuIsDialogOpen(&g_ExtendedBindKeyMenuDialog)) { @@ -1766,8 +1792,13 @@ static MenuItemHandlerResult menuhandlerDoBind(s32 operation, struct menuitem *i } const s32 key = inputGetLastKey(); - if (key && key != VK_ESCAPE) { - inputKeyBind(g_ExtMenuPlayer, g_BindContKey, g_BindIndex, (key == VK_DELETE ? 0 : key)); + if (key && key != VK_DELETE && key != VK_ESCAPE) { + s32 adjustedKey = menuSwapConfirmCancel(key, g_BindContKey); + + inputKeyBind(g_ExtMenuPlayer, g_BindContKey, g_BindIndex, adjustedKey); + menuPopDialog(); + } else if (key == VK_DELETE) { + inputKeyBind(g_ExtMenuPlayer, g_BindContKey, g_BindIndex, 0); menuPopDialog(); } @@ -1776,9 +1807,12 @@ static MenuItemHandlerResult menuhandlerDoBind(s32 operation, struct menuitem *i static const char *menutextBind(struct menuitem *item) { - return g_PlayerExtCfg[g_ExtMenuPlayer].extcontrols ? - menuBinds[item - g_ExtendedBindsMenuItems].name : - menuBinds[item - g_ExtendedBindsMenuItems].n64name; + int idx = item - g_ExtendedBindsMenuItems; + u32 ck = menuBinds[idx].ck; + + return g_PlayerExtCfg[g_ExtMenuPlayer].extcontrols ? + menuBinds[idx].name : + menuBinds[idx].n64name; } static MenuItemHandlerResult menuhandlerBind(s32 operation, struct menuitem *item, union handlerdata *data) @@ -1795,6 +1829,8 @@ static MenuItemHandlerResult menuhandlerBind(s32 operation, struct menuitem *ite case MENUOP_GETOPTIONTEXT: binds = inputKeyGetBinds(g_ExtMenuPlayer, menuBinds[idx].ck); if (binds && binds[data->dropdown.value]) { + s32 displayKey = menuSwapConfirmCancel(binds[data->dropdown.value], menuBinds[idx].ck); + strncpy(keyname, inputGetKeyName(binds[data->dropdown.value]), sizeof(keyname) - 1); for (char *p = keyname; *p; ++p) { if (*p == '_') *p = ' '; diff --git a/src/game/menu.c b/src/game/menu.c index c9f4cca3a6..6c435be727 100644 --- a/src/game/menu.c +++ b/src/game/menu.c @@ -4831,13 +4831,16 @@ void menuProcessInput(void) #ifndef PLATFORM_N64 // separate buttons for UI accept/cancel - if (buttonsnow & BUTTON_UI_ACCEPT) { - inputs.select = 1; - } - - if (buttonsnow & BUTTON_UI_CANCEL) { - inputs.back = 1; - } + // cancel/confirm layout can be swapped either controller type or user preference + u32 acceptBtn = inputConfirmCancelButtonSwap(contpadnums[i], BUTTON_UI_ACCEPT); + u32 cancelBtn = inputConfirmCancelButtonSwap(contpadnums[i], BUTTON_UI_CANCEL); + + if (buttonsnow & acceptBtn) { + inputs.select = 1; + } + if (buttonsnow & cancelBtn) { + inputs.back = 1; + } #endif if (buttonsnow & B_BUTTON) {