From 1c72b3022e7473318d5d8e6fb20a3416f16408e5 Mon Sep 17 00:00:00 2001 From: fgsfds Date: Sun, 26 Nov 2023 19:46:06 +0100 Subject: [PATCH 01/10] port: prevent extended options from cutting off --- src/game/mainmenu.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/game/mainmenu.c b/src/game/mainmenu.c index c0a918d9f..663e6160e 100644 --- a/src/game/mainmenu.c +++ b/src/game/mainmenu.c @@ -3524,7 +3524,7 @@ struct menuitem g_SoloMissionOptionsMenuItems[] = { MENUITEMTYPE_SELECTABLE, 0, MENUITEMFLAG_SELECTABLE_OPENSDIALOG | MENUITEMFLAG_BIGFONT | MENUITEMFLAG_LITERAL_TEXT, - (uintptr_t)"Extended", + (uintptr_t)"Extended\n", 0, (void *)&g_ExtendedMenuDialog, }, @@ -3570,7 +3570,7 @@ struct menuitem g_2PMissionOptionsHMenuItems[] = { MENUITEMTYPE_SELECTABLE, 0, MENUITEMFLAG_SELECTABLE_OPENSDIALOG | MENUITEMFLAG_LITERAL_TEXT, - (uintptr_t)"Extended", + (uintptr_t)"Extended\n", 0, (void *)&g_ExtendedMenuDialog, }, @@ -3636,7 +3636,7 @@ struct menuitem g_2PMissionOptionsVMenuItems[] = { MENUITEMTYPE_SELECTABLE, 0, MENUITEMFLAG_SELECTABLE_OPENSDIALOG | MENUITEMFLAG_LITERAL_TEXT, - (uintptr_t)"Extended", + (uintptr_t)"Extended\n", 0, (void *)&g_ExtendedMenuDialog, }, @@ -3714,7 +3714,7 @@ struct menuitem g_CiOptionsMenuItems[] = { MENUITEMTYPE_SELECTABLE, 0, MENUITEMFLAG_SELECTABLE_OPENSDIALOG | MENUITEMFLAG_BIGFONT | MENUITEMFLAG_LITERAL_TEXT, - (uintptr_t)"Extended", + (uintptr_t)"Extended\n", 7, (void *)&g_ExtendedMenuDialog, }, From 228851f8676a81de4eba714e8495f75dffb35609 Mon Sep 17 00:00:00 2001 From: fgsfds Date: Sun, 26 Nov 2023 20:27:22 +0100 Subject: [PATCH 02/10] port: add c-buttons to bind menu --- port/src/optionsmenu.c | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/port/src/optionsmenu.c b/port/src/optionsmenu.c index 46d720066..43895c23a 100644 --- a/port/src/optionsmenu.c +++ b/port/src/optionsmenu.c @@ -961,21 +961,25 @@ struct menubind { }; static const struct menubind menuBinds[] = { - { CK_ZTRIG, "Fire [ZT]\n", "N64 Z Trigger\n" }, - { CK_LTRIG, "Fire Mode [LT]\n", "N64 L Trigger\n"}, - { CK_RTRIG, "Aim Mode [RT]\n", "N64 R Trigger\n" }, - { CK_A, "Use / Accept [A]\n", "N64 A Button\n" }, - { CK_B, "Use / Cancel [B]\n", "N64 B Button\n" }, - { CK_X, "Reload [X]\n", "N64 Ext X\n" }, - { CK_Y, "Next Weapon [Y]\n", "N64 Ext Y\n" }, - { CK_DPAD_U, "D-Pad Up [DU]\n", "N64 D-Pad Up\n" }, - { CK_DPAD_R, "D-Pad Right [DR]\n", "N64 D-Pad Right\n" }, - { CK_DPAD_L, "Prev Weapon [DL]\n", "N64 D-Pad Left\n" }, - { CK_DPAD_D, "Radial Menu [DD]\n", "N64 D-Pad Down\n" }, - { CK_START, "Pause Menu [ST]\n", "N64 Start\n" }, - { CK_8000, "Cycle Crouch [+]\n", "N64 Ext 8000\n" }, - { CK_4000, "Half Crouch [+]\n", "N64 Ext 4000\n" }, - { CK_2000, "Full Crouch [+]\n", "N64 Ext 2000\n" }, + { CK_ZTRIG, "Fire [ZT]\n", "N64 Z Trigger\n" }, + { CK_LTRIG, "Fire Mode [LT]\n", "N64 L Trigger\n"}, + { CK_RTRIG, "Aim Mode [RT]\n", "N64 R Trigger\n" }, + { CK_A, "Use / Accept [A]\n", "N64 A Button\n" }, + { CK_B, "Use / Cancel [B]\n", "N64 B Button\n" }, + { CK_START, "Pause Menu [ST]\n", "N64 Start\n" }, + { CK_DPAD_U, "D-Pad Up [DU]\n", "N64 D-Pad Up\n" }, + { CK_DPAD_R, "D-Pad Right [DR]\n", "N64 D-Pad Right\n" }, + { CK_DPAD_L, "Prev Weapon [DL]\n", "N64 D-Pad Left\n" }, + { CK_DPAD_D, "Radial Menu [DD]\n", "N64 D-Pad Down\n" }, + { CK_C_U, "Forward [CU]\n", "N64 C-Up\n" }, + { CK_C_D, "Backward [CD]\n", "N64 C-Down\n" }, + { CK_C_R, "Strafe Right [CR]\n", "N64 C-Right\n" }, + { CK_C_L, "Strafe Left [CL]\n", "N64 C-Left\n" }, + { CK_X, "Reload [X]\n", "N64 Ext X\n" }, + { CK_Y, "Next Weapon [Y]\n", "N64 Ext Y\n" }, + { CK_8000, "Cycle Crouch [+]\n", "N64 Ext 8000\n" }, + { CK_4000, "Half Crouch [+]\n", "N64 Ext 4000\n" }, + { CK_2000, "Full Crouch [+]\n", "N64 Ext 2000\n" }, }; static const char *menutextBind(struct menuitem *item); @@ -1009,6 +1013,10 @@ 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(), { MENUITEMTYPE_SEPARATOR, 0, From 626b5937ce43a877f0bba87275d045950c254e45 Mon Sep 17 00:00:00 2001 From: fgsfds Date: Sun, 26 Nov 2023 20:32:46 +0100 Subject: [PATCH 03/10] port: possibly fix reloading and some altmodes in N64 schemes --- src/game/bondgun.c | 34 ++++++++++++++++++++++++++++------ src/game/bondmove.c | 3 ++- src/game/lv.c | 31 +++++++++++++++++++------------ 3 files changed, 49 insertions(+), 19 deletions(-) diff --git a/src/game/bondgun.c b/src/game/bondgun.c index b614d2c33..b63ab0d2c 100644 --- a/src/game/bondgun.c +++ b/src/game/bondgun.c @@ -11719,25 +11719,43 @@ void bgunSetTriggerOn(s32 handnum, bool on) */ s32 bgunConsiderToggleGunFunction(s32 usedowntime, bool trigpressed, bool fromactivemenu, bool fromdedicatedbutton) { +#ifndef PLATFORM_N64 + const bool extcontrols = PLAYER_EXTCFG().extcontrols; +#endif switch (bgunGetWeaponNum(HAND_RIGHT)) { case WEAPON_SNIPERRIFLE: - if (usedowntime < 0) { + if (extcontrols && usedowntime < 0) { return USETIMER_CONTINUE; } - // g_Vars.currentplayer->gunctrl.invertgunfunc = !g_Vars.currentplayer->gunctrl.invertgunfunc; + + // At 25 ticks (or B+Z), start showing the new function g_Vars.currentplayer->gunctrl.invertgunfunc = true; // B+Z immediately triggers crouch or stand if (trigpressed) { - g_Vars.currentplayer->hands[HAND_RIGHT].activatesecondary = true; + if (extcontrols) { + g_Vars.currentplayer->hands[HAND_RIGHT].activatesecondary = true; + } return USETIMER_STOP; } + if (fromdedicatedbutton) { g_Vars.currentplayer->hands[HAND_RIGHT].activatesecondary = true; return USETIMER_CONTINUE; } - if (ABS(usedowntime) < 0) { +#ifndef PLATFORM_N64 + bool docontinue; + if (extcontrols) { + docontinue = (ABS(usedowntime) < 0); + } else { + // Don't do anything if B hasn't been held for 50/60ths of a second + docontinue = (usedowntime < TICKS(50)); + } + if (docontinue) { +#else + if (usedowntime < TICKS(50)) { +#endif return USETIMER_CONTINUE; } @@ -11747,13 +11765,17 @@ s32 bgunConsiderToggleGunFunction(s32 usedowntime, bool trigpressed, bool fromac // Do crouch or stand g_Vars.currentplayer->hands[HAND_RIGHT].activatesecondary = true; - return USETIMER_STOP; + return (extcontrols ? USETIMER_STOP : USETIMER_REPEAT); case WEAPON_RCP120: case WEAPON_LAPTOPGUN: case WEAPON_DRAGON: case WEAPON_REMOTEMINE: // These weapons use temporary alt functions - g_Vars.currentplayer->gunctrl.invertgunfunc = !g_Vars.currentplayer->gunctrl.invertgunfunc; + if (extcontrols) { + g_Vars.currentplayer->gunctrl.invertgunfunc = !g_Vars.currentplayer->gunctrl.invertgunfunc; + } else { + g_Vars.currentplayer->gunctrl.invertgunfunc = true; + } if (fromactivemenu && bgunIsUsingSecondaryFunction() == true) { g_Vars.currentplayer->hands[HAND_RIGHT].activatesecondary = true; diff --git a/src/game/bondmove.c b/src/game/bondmove.c index 5718ac203..da6886b12 100644 --- a/src/game/bondmove.c +++ b/src/game/bondmove.c @@ -1806,7 +1806,8 @@ void bmoveProcessInput(bool allowc1x, bool allowc1y, bool allowc1buttons, bool i if (controlmode == CONTROLMODE_PC) { g_Vars.currentplayer->bondactivateorreload = g_Vars.currentplayer->bondactivateorreload | JO_ACTION_ACTIVATE; } else { - g_Vars.currentplayer->bondactivateorreload = movedata.btapcount; + g_Vars.currentplayer->bondactivateorreload = movedata.btapcount ? + (g_Vars.currentplayer->bondactivateorreload | JO_ACTION_ACTIVATE | JO_ACTION_RELOAD) : 0; } bmoveHandleActivate(); diff --git a/src/game/lv.c b/src/game/lv.c index e100aeda9..1c746b77d 100644 --- a/src/game/lv.c +++ b/src/game/lv.c @@ -1287,23 +1287,30 @@ Gfx *lvRender(Gfx *gdl) } // Handle opening doors and reloading - if (g_Vars.currentplayer->bondactivateorreload & JO_ACTION_RELOAD) { - if (g_Vars.currentplayer->hands[HAND_RIGHT].state != HANDSTATE_RELOAD) { - bgunReloadIfPossible(HAND_RIGHT); - } - - if (g_Vars.currentplayer->hands[HAND_LEFT].state != HANDSTATE_RELOAD) { - bgunReloadIfPossible(HAND_LEFT); - } - - g_Vars.currentplayer->bondactivateorreload = (g_Vars.currentplayer->bondactivateorreload & ~JO_ACTION_RELOAD) | (g_Vars.currentplayer->bondactivateorreload & 0x0); - } if (g_Vars.currentplayer->bondactivateorreload & JO_ACTION_ACTIVATE) { - currentPlayerInteract(false); + if (!currentPlayerInteract(false)) { +#ifndef PLATFORM_N64 + // n64 behavior: interact sucessful, cancel reload + if (!PLAYER_EXTCFG().extcontrols) { + g_Vars.currentplayer->bondactivateorreload = (g_Vars.currentplayer->bondactivateorreload & ~JO_ACTION_RELOAD); + } +#endif + } } else if (g_Vars.currentplayer->eyespy && g_Vars.currentplayer->eyespy->active && g_Vars.currentplayer->eyespy->opendoor) { currentPlayerInteract(true); + g_Vars.currentplayer->bondactivateorreload = (g_Vars.currentplayer->bondactivateorreload & ~JO_ACTION_RELOAD); + } + + if (g_Vars.currentplayer->bondactivateorreload & JO_ACTION_RELOAD) { + if (g_Vars.currentplayer->hands[HAND_RIGHT].state != HANDSTATE_RELOAD) { + bgunReloadIfPossible(HAND_RIGHT); + } + if (g_Vars.currentplayer->hands[HAND_LEFT].state != HANDSTATE_RELOAD) { + bgunReloadIfPossible(HAND_LEFT); + } + g_Vars.currentplayer->bondactivateorreload = (g_Vars.currentplayer->bondactivateorreload & ~JO_ACTION_RELOAD); } propsTestForPickup(); From c6a36633f557cd9ff573714a16e9bc6c1cfd9af7 Mon Sep 17 00:00:00 2001 From: fgsfds Date: Sun, 26 Nov 2023 20:43:31 +0100 Subject: [PATCH 04/10] port: untie radial menu open delay from framerate --- src/game/bondmove.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/game/bondmove.c b/src/game/bondmove.c index da6886b12..9b59f3847 100644 --- a/src/game/bondmove.c +++ b/src/game/bondmove.c @@ -1008,7 +1008,7 @@ void bmoveProcessInput(bool allowc1x, bool allowc1y, bool allowc1buttons, bool i amOpen(); g_Vars.currentplayer->invdowntime = -1; } else { - g_Vars.currentplayer->invdowntime++; + g_Vars.currentplayer->invdowntime += g_Vars.lvupdate60; } } } @@ -1467,7 +1467,7 @@ void bmoveProcessInput(bool allowc1x, bool allowc1y, bool allowc1buttons, bool i amOpen(); g_Vars.currentplayer->invdowntime = -1; } else { - g_Vars.currentplayer->invdowntime++; + g_Vars.currentplayer->invdowntime += g_Vars.lvupdate60; } } } From 523e6ba9d9e494c6aefbe74cecb85df374f2741d Mon Sep 17 00:00:00 2001 From: fgsfds Date: Sun, 26 Nov 2023 20:45:54 +0100 Subject: [PATCH 05/10] port: don't randomly close radial menu at 240fps --- src/game/activemenutick.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/activemenutick.c b/src/game/activemenutick.c index e5dabda84..570508ea4 100644 --- a/src/game/activemenutick.c +++ b/src/game/activemenutick.c @@ -263,7 +263,7 @@ void amTick(void) stayopen = false; } -#if VERSION >= VERSION_NTSC_1_0 +#if (VERSION >= VERSION_NTSC_1_0) && defined(PLATFORM_N64) if (g_Vars.lvupdate240 == 0) { stayopen = false; } From 4a3c3a29b7cee1b1f78dd67ec25c28c292b8ac8f Mon Sep 17 00:00:00 2001 From: fgsfds Date: Sun, 26 Nov 2023 21:15:47 +0100 Subject: [PATCH 06/10] port: measure nbomb age based on timestamps makes them last the same amount of time regardless of fps --- src/game/nbomb.c | 10 +++++++++- src/include/types.h | 3 +++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/game/nbomb.c b/src/game/nbomb.c index 28bbb643a..b5f4fa72b 100644 --- a/src/game/nbomb.c +++ b/src/game/nbomb.c @@ -281,6 +281,9 @@ void nbombReset(struct nbomb *nbomb) #if VERSION >= VERSION_PAL_BETA nbomb->radius = 0; #endif +#ifndef PLATFORM_N64 + nbomb->spawnframe240 = g_Vars.lvframe240; +#endif } /** @@ -523,9 +526,14 @@ void nbombTick(struct nbomb *nbomb) { if (nbomb->age240 >= 0) { s32 age60; +#ifdef PLATFORM_N64 s32 increment = (g_Vars.lvupdate240 + 2) >> 2; - nbomb->age240 += increment; +#else + s32 oldage240 = nbomb->age240; + nbomb->age240 = (g_Vars.lvframe240 - nbomb->spawnframe240) >> 2; + s32 increment = nbomb->age240 - oldage240; +#endif if (nbomb->age240 < TICKS(80)) { nbomb->radius = nbomb->age240 / (PAL ? 66.0f : 80.0f); diff --git a/src/include/types.h b/src/include/types.h index 159558c67..67a8eddaf 100644 --- a/src/include/types.h +++ b/src/include/types.h @@ -5510,6 +5510,9 @@ struct nbomb { struct prop *ownerprop; struct sndstate *audiohandle20; struct sndstate *audiohandle24; +#ifndef PLATFORM_N64 + s32 spawnframe240; // spawned on this frame +#endif }; struct roomacousticdata { From 8d99ac92a0c5a6f922e3a151dd0b2bccb0c53ee9 Mon Sep 17 00:00:00 2001 From: Catherine Reprobate Date: Sun, 26 Nov 2023 14:46:26 -0800 Subject: [PATCH 07/10] fixup: PC controls camspy instant abort only consider first controller for camspy input --- src/game/bondeyespy.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/game/bondeyespy.c b/src/game/bondeyespy.c index fe611a183..1250f1f0a 100644 --- a/src/game/bondeyespy.c +++ b/src/game/bondeyespy.c @@ -765,8 +765,8 @@ void eyespyProcessInput(bool allowbuttons) shootpressed = c1buttons & Z_TRIG; #ifndef PLATFORM_N64 if (controlmode == CONTROLMODE_PC) { - exitpressed = (c1buttons | c2buttons) & (BUTTON_WPNBACK | BUTTON_RADIAL); - activatepressed = (c1buttons | c2buttons) & (BUTTON_CANCEL_USE | BUTTON_ACCEPT_USE); + exitpressed = c1buttons & (BUTTON_WPNBACK | BUTTON_RADIAL); + activatepressed = c1buttons & (BUTTON_CANCEL_USE | BUTTON_ACCEPT_USE); } else #endif { From 116dc7e8b66026b91f0661861c610263d22ed589 Mon Sep 17 00:00:00 2001 From: fgsfds Date: Mon, 27 Nov 2023 00:57:52 +0100 Subject: [PATCH 08/10] port: allow assigning controllers manually --- port/include/input.h | 33 ++++++- port/src/input.c | 216 ++++++++++++++++++++++++++++++++++++----- port/src/optionsmenu.c | 60 +++++++++--- 3 files changed, 270 insertions(+), 39 deletions(-) diff --git a/port/include/input.h b/port/include/input.h index 60c7a73ef..2ddaab94a 100644 --- a/port/include/input.h +++ b/port/include/input.h @@ -5,6 +5,7 @@ #include #define INPUT_MAX_CONTROLLERS MAXCONTROLLERS +#define INPUT_MAX_CONNECTED_CONTROLLERS 8 #define INPUT_MAX_CONTROLLER_BUTTONS 32 #define INPUT_MAX_BINDS 4 @@ -83,32 +84,51 @@ enum contkey { // returns bitmask of connected controllers or -1 if failed s32 inputInit(void); +// read the specified player's inputs into the N64 pad struct // returns 0 if read, non-0 if failed s32 inputReadController(s32 idx, OSContPad *npad); -// returns 1 if rumble is supported for specified controller +// returns 1 if rumble is supported for specified player's controller s32 inputRumbleSupported(s32 idx); -// returns 1 if specified controller is connected +// returns 1 if specified player has a live controller assigned s32 inputControllerConnected(s32 idx); -// returns bitmask of connected controllers +// returns bitmask of players with assigned controllers s32 inputControllerMask(void); +// get/set Input.Player%d.SwapSticks s32 inputControllerGetSticksSwapped(s32 cidx); void inputControllerSetSticksSwapped(s32 cidx, s32 swapped); +// get/set Input.Player%d.StickCButtons (DualAnalog is 1 if StickCButtons is 0) s32 inputControllerGetDualAnalog(s32 cidx); void inputControllerSetDualAnalog(s32 cidx, s32 enable); +// get/set sensitivity for a given player f32 inputControllerGetAxisScale(s32 cidx, s32 stick, s32 axis); void inputControllerSetAxisScale(s32 cidx, s32 stick, s32 axis, f32 value); +// get/set deadzone for a given player f32 inputControllerGetAxisDeadzone(s32 cidx, s32 stick, s32 axis); void inputControllerSetAxisDeadzone(s32 cidx, s32 stick, s32 axis, f32 value); -void inputControllerSetFirstIndex(s32 cidx); -s32 inputControllerGetFirstIndex(void); +// writes array of up to INPUT_MAX_CONNECTED_CONTROLLERS controller IDs +// for all the controllers available on this machine into out if it's not NULL +// returns number of IDs that would've been written (or were written if out is not NULL) +s32 inputGetConnectedControllers(s32 *out); + +// get name of connected controller id +// returns "Invalid" on failure +const char *inputGetConnectedControllerName(s32 id); + +// get id of the controller currently assigned to player cidx or -1 if none +s32 inputGetAssignedControllerId(s32 cidx); + +// assign connected controller id to player cidx +// if id is -1, unassigns controller from player, if any +// returns true on success, false on failure +s32 inputAssignController(s32 cidx, s32 id); // vk is a value from the virtkey enum above s32 inputKeyPressed(u32 vk); @@ -172,11 +192,14 @@ void inputUpdate(void); // call this before configSave() void inputSaveBinds(void); +// reset given player's binds to either PC or N64 defaults void inputSetDefaultKeyBinds(s32 cidx, s32 n64mode); +// clear or get the last pressed button void inputClearLastKey(void); s32 inputGetLastKey(void); +// get/set Input.MouseDefaultLocked s32 inputGetMouseDefaultLocked(void); void inputSetMouseDefaultLocked(s32 defaultLocked); diff --git a/port/src/input.c b/port/src/input.c index 9fe2a67b9..da383f156 100644 --- a/port/src/input.c +++ b/port/src/input.c @@ -32,7 +32,8 @@ static SDL_GameController *pads[INPUT_MAX_CONTROLLERS]; .sens = { 1.f, 1.f, 1.f, 1.f }, \ .deadzone = { DEFAULT_DEADZONE, DEFAULT_DEADZONE, DEFAULT_DEADZONE, DEFAULT_DEADZONE_RY }, \ .stickCButtons = 0, \ - .swapSticks = 1 \ + .swapSticks = 1, \ + .deviceIndex = -1 \ } static struct controllercfg { @@ -43,6 +44,7 @@ static struct controllercfg { s32 deadzone[4]; s32 stickCButtons; s32 swapSticks; + s32 deviceIndex; } padsCfg[INPUT_MAX_CONTROLLERS] = { CONTROLLERCFG_DEFAULT, CONTROLLERCFG_DEFAULT, @@ -57,6 +59,8 @@ static s32 fakeControllers = 0; static s32 firstController = 0; static s32 connectedMask = 0; +static s32 numJoysticks = 0; + static s32 mouseEnabled = 1; static s32 mouseLocked = 0; static s32 mouseDefaultLocked = 0; @@ -267,7 +271,21 @@ void inputSetDefaultKeyBinds(s32 cidx, s32 n64mode) } } -static inline void inputInitController(const s32 cidx) +static inline s32 inputDeviceIndexFromId(const SDL_JoystickID id) { + for (s32 jidx = 0; jidx < numJoysticks; ++jidx) { + if (SDL_JoystickGetDeviceInstanceID(jidx) == id) { + return jidx; + } + } + return -1; +} + +static inline SDL_JoystickID inputControllerGetId(SDL_GameController *ctrl) +{ + return SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(ctrl)); +} + +static inline void inputInitController(const s32 cidx, const s32 jidx) { #if SDL_VERSION_ATLEAST(2, 0, 18) // SDL_GameControllerHasRumble() appeared in 2.0.18 even though SDL_GameControllerRumble() is in 2.0.9 @@ -286,21 +304,32 @@ static inline void inputInitController(const s32 cidx) // make the LEDs on the controller indicate which player it's for SDL_GameControllerSetPlayerIndex(pads[cidx], cidx); + // remember the joystick index + padsCfg[cidx].deviceIndex = jidx; + connectedMask |= (1 << cidx); + + sysLogPrintf(LOG_NOTE, "input: assigned controller '%d: (%s)' (id %d) to player %d", + jidx, SDL_GameControllerName(pads[cidx]), inputControllerGetId(pads[cidx]), cidx); } static inline void inputCloseController(const s32 cidx) { + sysLogPrintf(LOG_NOTE, "input: removed controller '%d: (%s)' (id %d) from player %d", + padsCfg[cidx].deviceIndex, SDL_GameControllerName(pads[cidx]), inputControllerGetId(pads[cidx]), cidx); + SDL_GameControllerClose(pads[cidx]); + pads[cidx] = NULL; padsCfg[cidx].rumbleOn = 0; + if (cidx) { connectedMask &= ~(1 << cidx); } } - -static inline s32 inputControllerGetIndex(SDL_GameController *ctrl) { +static inline s32 inputControllerGetIndex(SDL_GameController *ctrl) +{ if (ctrl) { for (s32 i = 0; i < INPUT_MAX_CONTROLLERS; ++i) { if (pads[i] == ctrl) { @@ -311,11 +340,33 @@ static inline s32 inputControllerGetIndex(SDL_GameController *ctrl) { return -1; } -static inline void inputShutdownAllControllers(void) +static inline s32 inputControllerGetIndexByDeviceIndex(const s32 jidx) +{ + for (s32 cidx = 0; cidx < INPUT_MAX_CONTROLLERS; ++cidx) { + if (pads[cidx] && padsCfg[cidx].deviceIndex == jidx) { + return cidx; + } + } + return -1; +} + +static inline s32 inputControllerGetIndexById(const SDL_JoystickID jid) { for (s32 cidx = 0; cidx < INPUT_MAX_CONTROLLERS; ++cidx) { if (pads[cidx]) { - SDL_GameControllerClose(pads[cidx]); + if (inputControllerGetId(pads[cidx]) == jid) { + return cidx; + } + } + } + return -1; +} + +static inline void inputCloseAllControllers(void) +{ + for (s32 cidx = 0; cidx < INPUT_MAX_CONTROLLERS; ++cidx) { + if (pads[cidx]) { + inputCloseController(cidx); pads[cidx] = NULL; } } @@ -323,22 +374,50 @@ static inline void inputShutdownAllControllers(void) connectedMask = 1; // always report first controller as connected } +static inline s32 inputTryController(const s32 cidx, const s32 jidx) +{ + if (!pads[cidx]) { + pads[cidx] = SDL_GameControllerOpen(jidx); + if (pads[cidx]) { + inputInitController(cidx, jidx); + return 1; + } + } + return 0; +} + static inline void inputInitAllControllers(void) { SDL_GameControllerUpdate(); - const s32 numJoys = SDL_NumJoysticks(); + numJoysticks = SDL_NumJoysticks(); connectedMask = 1; // always report first controller as connected - // if firstController is set to 1, keyboard will count as a separate controller on its own, - // so the first connected gamepad will go to player 2 instead of player 1 - for (s32 jidx = 0, cidx = firstController; jidx < numJoys && cidx < INPUT_MAX_CONTROLLERS; ++jidx) { - if (SDL_IsGameController(jidx)) { - pads[cidx] = SDL_GameControllerOpen(jidx); - if (pads[cidx]) { - inputInitController(cidx); - ++cidx; + // first try to assign the controllers that we had last time + // we're still free to check by device index before any controller device events fire + for (s32 cidx = 0; cidx < INPUT_MAX_CONTROLLERS; ++cidx) { + const s32 jidx = padsCfg[cidx].deviceIndex; + if (jidx >= 0 && jidx < numJoysticks) { + if (SDL_IsGameController(jidx) && inputControllerGetIndexByDeviceIndex(jidx) < 0) { + // using the full assign function in case user sets same index for several players + if (inputTryController(cidx, jidx)) { + // success + continue; + } + } + // nothing was there, forget it + padsCfg[cidx].deviceIndex = -1; + } + } + + // now try autofilling the rest, starting with firstController + for (s32 jidx = 0; jidx < numJoysticks; ++jidx) { + if (SDL_IsGameController(jidx) && inputControllerGetIndexByDeviceIndex(jidx) < 0) { + for (s32 cidx = firstController; cidx < INPUT_MAX_CONTROLLERS; ++cidx) { + if (inputTryController(cidx, jidx)) { + break; + } } } } @@ -357,7 +436,7 @@ static int inputEventFilter(void *data, SDL_Event *event) if (!pads[i]) { pads[i] = SDL_GameControllerOpen(event->cdevice.which); if (pads[i]) { - inputInitController(i); + inputInitController(i, event->cdevice.which); } break; } @@ -369,10 +448,16 @@ static int inputEventFilter(void *data, SDL_Event *event) const s32 idx = inputControllerGetIndex(ctrl); if (idx >= 0) { inputCloseController(idx); + padsCfg[idx].deviceIndex = -1; } break; } + case SDL_JOYDEVICEADDED: + case SDL_JOYDEVICEREMOVED: + numJoysticks = SDL_NumJoysticks(); // joystick count has changed + break; + case SDL_MOUSEWHEEL: mouseWheel = event->wheel.y; if (!lastKey && mouseWheel) { @@ -812,14 +897,100 @@ void inputControllerSetAxisDeadzone(s32 cidx, s32 stick, s32 axis, f32 value) padsCfg[cidx].deadzone[stick * 2 + axis] = value * 32767.f; } -void inputControllerSetFirstIndex(s32 cidx) { - firstController = cidx; - inputShutdownAllControllers(); - inputInitAllControllers(); +s32 inputGetConnectedControllers(s32 *out) +{ + s32 count = 0; + + for (s32 jidx = 0; jidx < numJoysticks; ++jidx) { + if (SDL_IsGameController(jidx)) { + if (out && count < INPUT_MAX_CONNECTED_CONTROLLERS) { + out[count] = SDL_JoystickGetDeviceInstanceID(jidx); + } + ++count; + } + } + + return count; } -s32 inputControllerGetFirstIndex(void) { - return firstController; +s32 inputGetAssignedControllerId(s32 cidx) +{ + if (cidx < 0 || cidx >= INPUT_MAX_CONTROLLERS) { + return -1; + } + + if (pads[cidx] == NULL) { + return -1; + } + + return inputControllerGetId(pads[cidx]); +} + +const char *inputGetConnectedControllerName(s32 id) +{ + static char fullName[256]; + + if (id < 0) { + return "Invalid"; + } + + const s32 jidx = inputDeviceIndexFromId(id); + if (jidx < 0) { + return "Invalid"; + } + + const char *name = SDL_GameControllerNameForIndex(jidx); + if (!name || !name[0]) { + name = "Unnamed Controller"; + } + + snprintf(fullName, sizeof(fullName), "%d: %s", jidx, name); + + return fullName; +} + +s32 inputAssignController(s32 cidx, s32 id) +{ + if (cidx < 0 || cidx >= INPUT_MAX_CONTROLLERS) { + return 0; + } + + if (id < 0) { + // close current controller, if any + if (pads[cidx]) { + inputCloseController(cidx); + return 1; + } + return 0; + } + + const s32 jidx = inputDeviceIndexFromId(id); + if (jidx < 0 || jidx >= SDL_NumJoysticks() || !SDL_IsGameController(jidx)) { + return 0; + } + + // try to unassign any other instances of this controller + for (s32 i = 0; i < INPUT_MAX_CONTROLLERS; ++i) { + if (pads[i] && inputControllerGetId(pads[i]) == id) { + inputCloseController(i); + pads[i] = NULL; + padsCfg[i].deviceIndex = -1; + } + } + + SDL_GameController *newpad = SDL_GameControllerOpen(jidx); + if (!newpad) { + return 0; + } + + if (pads[cidx]) { + inputCloseController(cidx); + } + + pads[cidx] = newpad; + inputInitController(cidx, id); + + return 1; } void inputKeyBind(s32 idx, u32 ck, s32 bind, u32 vk) @@ -1081,6 +1252,7 @@ PD_CONSTRUCTOR static void inputConfigInit(void) configRegisterFloat(strFmt("%s.RStickScaleY", secname), &padsCfg[c].sens[3], -10.f, 10.f); configRegisterInt(strFmt("%s.StickCButtons", secname), &padsCfg[c].stickCButtons, 0, 1); configRegisterInt(strFmt("%s.SwapSticks", secname), &padsCfg[c].swapSticks, 0, 1); + configRegisterInt(strFmt("%s.ControllerIndex", secname), &padsCfg[c].deviceIndex, -1, 0x7FFFFFFF); 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 43895c23a..94513f770 100644 --- a/port/src/optionsmenu.c +++ b/port/src/optionsmenu.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -489,6 +490,12 @@ static MenuItemHandlerResult menuhandlerVibration(s32 operation, struct menuitem case MENUOP_SET: inputRumbleSetStrength(g_ExtMenuPlayer, (f32)data->slider.value / 10.f); break; + case MENUOP_CHECKHIDDEN: + case MENUOP_CHECKDISABLED: + if (!inputRumbleSupported(g_ExtMenuPlayer)) { + return true; + } + break; } return 0; @@ -520,13 +527,42 @@ static MenuItemHandlerResult menuhandlerSwapSticks(s32 operation, struct menuite return 0; } -static MenuItemHandlerResult menuhandlerFirstController(s32 operation, struct menuitem *item, union handlerdata *data) +static MenuItemHandlerResult menuhandlerController(s32 operation, struct menuitem *item, union handlerdata *data) { + static char ctrlname[35]; + s32 ctrls[INPUT_MAX_CONNECTED_CONTROLLERS]; + const s32 numCtrls = inputGetConnectedControllers(ctrls); + const s32 curCtrl = inputGetAssignedControllerId(g_ExtMenuPlayer); + switch (operation) { - case MENUOP_GET: - return (inputControllerGetFirstIndex() == g_ExtMenuPlayer); + case MENUOP_GETOPTIONCOUNT: + data->dropdown.value = numCtrls + 1; // first option is "None" + break; + case MENUOP_GETOPTIONTEXT: + if (data->dropdown.value) { + const s32 jid = ctrls[data->dropdown.value - 1]; + const char *name = inputGetConnectedControllerName(jid); + strncpy(ctrlname, name, sizeof(ctrlname) - 1); + return (intptr_t)ctrlname; + } else { + return (intptr_t)"None"; + } case MENUOP_SET: - inputControllerSetFirstIndex(g_ExtMenuPlayer); + if (data->dropdown.value == 0) { + // unassign controller + inputAssignController(g_ExtMenuPlayer, -1); + } else if (data->dropdown.value <= numCtrls) { + inputAssignController(g_ExtMenuPlayer, ctrls[data->dropdown.value - 1]); + } + break; + case MENUOP_GETSELECTEDINDEX: + for (s32 i = 0; i < numCtrls; ++i) { + if (curCtrl == ctrls[i]) { + data->dropdown.value = i + 1; + return 0; + } + } + data->dropdown.value = 0; break; } @@ -534,6 +570,14 @@ static MenuItemHandlerResult menuhandlerFirstController(s32 operation, struct me } struct menuitem g_ExtendedControllerMenuItems[] = { + { + MENUITEMTYPE_DROPDOWN, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Controller", + 0, + menuhandlerController, + }, { MENUITEMTYPE_CHECKBOX, 0, @@ -550,14 +594,6 @@ struct menuitem g_ExtendedControllerMenuItems[] = { 0, menuhandlerSwapSticks, }, - { - MENUITEMTYPE_CHECKBOX, - 0, - MENUITEMFLAG_LITERAL_TEXT, - (uintptr_t)"Assign First Controller", - 0, - menuhandlerFirstController, - }, { MENUITEMTYPE_SELECTABLE, 0, From 31ad9351f5030a10f14c55b0725b4f96ba57e84d Mon Sep 17 00:00:00 2001 From: fgsfds Date: Mon, 27 Nov 2023 01:20:09 +0100 Subject: [PATCH 09/10] port: load gamecontrollerdb if available --- port/src/input.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/port/src/input.c b/port/src/input.c index da383f156..e1af7e079 100644 --- a/port/src/input.c +++ b/port/src/input.c @@ -10,6 +10,9 @@ #include "config.h" #include "utils.h" #include "system.h" +#include "fs.h" + +#define CONTROLLERDB_FNAME "gamecontrollerdb.txt" #define MAX_BIND_STR 256 @@ -637,6 +640,15 @@ s32 inputInit(void) SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC); } + // try to load controller db from an external file in the save folder + if (fsFileSize("$S/" CONTROLLERDB_FNAME)) { + const char *dbpath = fsFullPath("$S/" CONTROLLERDB_FNAME); + const s32 dbcount = SDL_GameControllerAddMappingsFromFile(dbpath); + if (dbcount >= 0) { + sysLogPrintf(LOG_NOTE, "input: added %d controller mappings from %s", dbcount, dbpath); + } + } + inputInitAllControllers(); // since the main event loop is elsewhere, we can receive some events we need using a watcher From e0249067b934d4b38dde0f465e79db6b8e47fd54 Mon Sep 17 00:00:00 2001 From: fgsfds Date: Mon, 27 Nov 2023 01:26:45 +0100 Subject: [PATCH 10/10] port: reset the controller LEDs on unassign --- port/src/input.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/port/src/input.c b/port/src/input.c index e1af7e079..a6306e14f 100644 --- a/port/src/input.c +++ b/port/src/input.c @@ -321,6 +321,9 @@ static inline void inputCloseController(const s32 cidx) sysLogPrintf(LOG_NOTE, "input: removed controller '%d: (%s)' (id %d) from player %d", padsCfg[cidx].deviceIndex, SDL_GameControllerName(pads[cidx]), inputControllerGetId(pads[cidx]), cidx); + // reset player LEDs + SDL_GameControllerSetPlayerIndex(pads[cidx], -1); + SDL_GameControllerClose(pads[cidx]); pads[cidx] = NULL;