diff --git a/src/am_map.c b/src/am_map.c index e5ae874f4..cea08918e 100644 --- a/src/am_map.c +++ b/src/am_map.c @@ -449,7 +449,7 @@ boolean AM_Responder(event_t *ev) { //faB: prevent alt-tab in win32 version to activate automap just before // minimizing the app; doesn't do any harm to the DOS version - if (!gamekeydown[0][KEY_LALT] && !gamekeydown[0][KEY_RALT]) + if (!G_GetDeviceGameKeyDownArray(0)[KEY_LALT] && !G_GetDeviceGameKeyDownArray(0)[KEY_RALT]) { bigstate = 0; //added : 24-01-98 : toggle off large view AM_Start(); @@ -1069,7 +1069,7 @@ static void AM_drawWalls(UINT8 pass) else if (backc1 != frontc1 || backc2 != frontc2) { if (!(pass & PASS_INTANGIBLE)) - ; + ; else if (abs(backc1 - frontc1) < maxstep || abs(backc2 - frontc2) < maxstep) { @@ -1115,7 +1115,7 @@ static void AM_drawWalls(UINT8 pass) else { ffloor_t *rover = NULL; - + if (lines[i].frontsector->ffloors || lines[i].backsector->ffloors) { if (lines[i].backsector->ffloors == NULL) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 76470b865..6fdf4b12b 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -1485,7 +1485,7 @@ void CL_UpdateServerList (void) static void M_ConfirmConnect(void) { - if (G_PlayerInputDown(0, gc_a, 1) || gamekeydown[0][KEY_ENTER]) + if (G_PlayerInputDown(0, gc_a, 1) || G_GetDeviceGameKeyDownArray(0)[KEY_ENTER]) { if (totalfilesrequestednum > 0) { @@ -1512,7 +1512,7 @@ static void M_ConfirmConnect(void) M_StopMessage(0); } - else if (G_PlayerInputDown(0, gc_b, 1) || G_PlayerInputDown(0, gc_x, 1) || gamekeydown[0][KEY_ESCAPE]) + else if (G_PlayerInputDown(0, gc_b, 1) || G_PlayerInputDown(0, gc_x, 1) || G_GetDeviceGameKeyDownArray(0)[KEY_ESCAPE]) { cl_mode = CL_ABORTED; M_StopMessage(0); @@ -1962,7 +1962,7 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic renderdeltatics = FRACUNIT; rendertimefrac = FRACUNIT; - memset(deviceResponding, false, sizeof (deviceResponding)); + G_ResetAllDeviceResponding(); if (netgame) { @@ -1979,7 +1979,7 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic { if (G_PlayerInputDown(0, gc_b, 1) || G_PlayerInputDown(0, gc_x, 1) - || gamekeydown[0][KEY_ESCAPE]) + || G_GetDeviceGameKeyDownArray(0)[KEY_ESCAPE]) cl_mode = CL_ABORTED; } } diff --git a/src/d_event.h b/src/d_event.h index 0c621e942..8b85eed6e 100644 --- a/src/d_event.h +++ b/src/d_event.h @@ -28,7 +28,9 @@ typedef enum ev_keyup, ev_console, ev_mouse, - ev_joystick, + ev_gamepad_axis, + ev_gamepad_device_added, + ev_gamepad_device_removed, } evtype_t; // Event structure. @@ -38,7 +40,7 @@ struct event_t INT32 data1; // keys / mouse/joystick buttons INT32 data2; // mouse/joystick x move INT32 data3; // mouse/joystick y move - INT32 device; // which player's device it belongs to + INT32 device; // which device ID it belongs to (controller ID) }; // diff --git a/src/d_main.c b/src/d_main.c index 53ae4d86c..46fc00136 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -171,6 +171,54 @@ UINT8 ctrldown = 0; // 0x1 left, 0x2 right UINT8 altdown = 0; // 0x1 left, 0x2 right boolean capslock = 0; // gee i wonder what this does. +static void HandleGamepadDeviceAdded(event_t *ev) +{ + I_Assert(ev != NULL); + I_Assert(ev->type == ev_gamepad_device_added); + + G_RegisterAvailableGamepad(ev->device); + CONS_Debug(DBG_GAMELOGIC, "Registered available gamepad device %d\n", ev->device); +} + +static void HandleGamepadDeviceRemoved(event_t *ev) +{ + int i = 0; + I_Assert(ev != NULL); + I_Assert(ev->type == ev_gamepad_device_removed); + + G_UnregisterAvailableGamepad(ev->device); + CONS_Debug(DBG_GAMELOGIC, "Unregistered available gamepad device %d\n", ev->device); + + // Downstream responders need to update player gamepad assignments, pause, etc + + for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) + { + INT32 device = G_GetDeviceForPlayer(i); + if (device == ev->device) + { + G_SetDeviceForPlayer(i, -1); + } + } +} + +/// Respond to added/removed device events, for bookkeeping available gamepads. +static void HandleGamepadDeviceEvents(event_t *ev) +{ + I_Assert(ev != NULL); + + switch (ev->type) + { + case ev_gamepad_device_added: + HandleGamepadDeviceAdded(ev); + break; + case ev_gamepad_device_removed: + HandleGamepadDeviceRemoved(ev); + break; + default: + break; + } +} + // // D_ProcessEvents // Send all the events of the given timestamp down the responder chain @@ -183,11 +231,13 @@ void D_ProcessEvents(void) boolean eaten; boolean menuresponse = false; - memset(deviceResponding, false, sizeof (deviceResponding)); + G_ResetAllDeviceResponding(); for (; eventtail != eventhead; eventtail = (eventtail+1) & (MAXEVENTS-1)) { ev = &events[eventtail]; + HandleGamepadDeviceEvents(ev); + // Screenshots over everything so that they can be taken anywhere. if (M_ScreenshotResponder(ev)) continue; // ate the event @@ -976,7 +1026,7 @@ void D_ClearState(void) // clear cmd building stuff memset(gamekeydown, 0, sizeof (gamekeydown)); - memset(deviceResponding, false, sizeof (deviceResponding)); + G_ResetAllDeviceResponding(); // Reset the palette if (rendermode != render_none) @@ -1507,6 +1557,8 @@ void D_SRB2Main(void) CONS_Printf("I_StartupGraphics()...\n"); I_StartupGraphics(); + I_StartupInput(); + if (rendermode != render_none) { I_NewTwodeeFrame(); diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 71483bc61..e59a362b8 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -237,9 +237,6 @@ static CV_PossibleValue_t usemouse_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Force #ifdef LJOYSTICK static CV_PossibleValue_t joyport_cons_t[] = {{1, "/dev/js0"}, {2, "/dev/js1"}, {3, "/dev/js2"}, {4, "/dev/js3"}, {0, NULL}}; -#else -// accept whatever value - it is in fact the joystick device number -static CV_PossibleValue_t usejoystick_cons_t[] = {{-1, "MIN"}, {MAXGAMEPADS, "MAX"}, {0, NULL}}; #endif static CV_PossibleValue_t teamscramble_cons_t[] = {{0, "Off"}, {1, "Random"}, {2, "Points"}, {0, NULL}}; @@ -332,13 +329,6 @@ consvar_t cv_skipmapcheck = CVAR_INIT ("skipmapcheck", "Off", CV_SAVE, CV_OnOff, consvar_t cv_usemouse = CVAR_INIT ("use_mouse", "Off", CV_SAVE|CV_CALL,usemouse_cons_t, I_StartupMouse); -consvar_t cv_usejoystick[MAXSPLITSCREENPLAYERS] = { - CVAR_INIT ("use_device", "1", CV_SAVE|CV_CALL, usejoystick_cons_t, I_InitJoystick1), - CVAR_INIT ("use_device2", "2", CV_SAVE|CV_CALL, usejoystick_cons_t, I_InitJoystick2), - CVAR_INIT ("use_device3", "3", CV_SAVE|CV_CALL, usejoystick_cons_t, I_InitJoystick3), - CVAR_INIT ("use_device4", "4", CV_SAVE|CV_CALL, usejoystick_cons_t, I_InitJoystick4) -}; - #if (defined (LJOYSTICK) || defined (HAVE_SDL)) consvar_t cv_joyscale[MAXSPLITSCREENPLAYERS] = { CVAR_INIT ("padscale", "1", CV_SAVE|CV_CALL, NULL, I_JoyScale), @@ -1039,7 +1029,6 @@ void D_RegisterClientCommands(void) for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) { - CV_RegisterVar(&cv_usejoystick[i]); CV_RegisterVar(&cv_joyscale[i]); #ifdef LJOYSTICK CV_RegisterVar(&cv_joyport[i]); diff --git a/src/d_netcmd.h b/src/d_netcmd.h index a126de023..664b816f1 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -47,7 +47,6 @@ extern consvar_t cv_splitplayers; extern consvar_t cv_seenames; extern consvar_t cv_usemouse; -extern consvar_t cv_usejoystick[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_joyscale[MAXSPLITSCREENPLAYERS]; #ifdef LJOYSTICK extern consvar_t cv_joyport[MAXSPLITSCREENPLAYERS]; diff --git a/src/g_game.c b/src/g_game.c index a0d187790..0e02a678f 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -513,7 +513,7 @@ void G_UpdateTimeStickerMedals(UINT16 map, boolean showownrecord) break; } case ET_MAP: - { + { if (emblem->flags & ME_SPBATTACK && cv_dummyspbattack.value) break; goto bademblem; @@ -884,6 +884,7 @@ static INT32 keyboardMenuDefaults[][2] = { INT32 G_PlayerInputAnalog(UINT8 p, INT32 gc, UINT8 menuPlayers) { INT32 deviceID; + INT32 avail_gamepad_id = 0; INT32 i, j; INT32 deadzone = 0; boolean trydefaults = true; @@ -900,7 +901,22 @@ INT32 G_PlayerInputAnalog(UINT8 p, INT32 gc, UINT8 menuPlayers) deadzone = (JOYAXISRANGE * cv_deadzone[p].value) / FRACUNIT; - deviceID = cv_usejoystick[p].value; + deviceID = G_GetDeviceForPlayer(p); + + if (deviceID == -1) + { + INT32 keyboard_player = G_GetPlayerForDevice(KEYBOARD_MOUSE_DEVICE); + + // Player 1 is always allowed to use the keyboard in 1P (there is a check for splitscreen later in this func) + if (p == KEYBOARD_MOUSE_DEVICE && keyboard_player == -1) + { + deviceID = KEYBOARD_MOUSE_DEVICE; + } + else + { + goto deviceunassigned; + } + } retrygetcontrol: for (i = 0; i < MAXINPUTMAPPING; i++) @@ -910,7 +926,6 @@ retrygetcontrol: INT32 value = 0; boolean processinput = true; - // for menus, keyboards have defaults! if (deviceID == 0) { @@ -951,9 +966,9 @@ retrygetcontrol: // It's possible to access this control right now, so let's disable the default control backup for later. trydefaults = false; - value = gamekeydown[deviceID][key]; - if (menukey && gamekeydown[deviceID][menukey]) - value = gamekeydown[deviceID][menukey]; + value = G_GetDeviceGameKeyDownArray(deviceID)[key]; + if (menukey && G_GetDeviceGameKeyDownArray(deviceID)[menukey]) + value = G_GetDeviceGameKeyDownArray(deviceID)[menukey]; if (value >= deadzone) { @@ -962,11 +977,13 @@ retrygetcontrol: } } +deviceunassigned: + // If you're on controller, try your keyboard-based binds as an immediate backup. // Do not do this if there are more than 1 local player. if (p == 0 && deviceID > 0 && !tryingotherID && menuPlayers < 2 && !splitscreen) { - deviceID = 0; + deviceID = KEYBOARD_MOUSE_DEVICE; goto retrygetcontrol; } @@ -981,16 +998,21 @@ retrygetcontrol: if (!tryingotherID) { - deviceID = MAXDEVICES; + avail_gamepad_id = 0; tryingotherID = true; } loweringid: - deviceID--; + if (avail_gamepad_id >= G_GetNumAvailableGamepads()) + { + return 0; + } + deviceID = G_GetAvailableGamepadDevice(avail_gamepad_id); + avail_gamepad_id += 1; if (deviceID > 0) { for (i = 0; i < menuPlayers; i++) { - if (deviceID != cv_usejoystick[i].value) + if (deviceID != G_GetDeviceForPlayer(i)) continue; // Controller taken? Try again... goto loweringid; @@ -1005,7 +1027,7 @@ loweringid: trydefaults = false; controltable = &(gamecontroldefault[gc][0]); tryingotherID = false; - deviceID = cv_usejoystick[p].value; + deviceID = G_GetDeviceForPlayer(p); goto retrygetcontrol; } @@ -1530,7 +1552,7 @@ void G_DoLoadLevel(boolean resetplayer) // clear cmd building stuff memset(gamekeydown, 0, sizeof (gamekeydown)); - memset(deviceResponding, false, sizeof (deviceResponding)); + G_ResetAllDeviceResponding(); // clear hud messages remains (usually from game startup) CON_ClearHUD(); @@ -1828,7 +1850,7 @@ boolean G_Responder(event_t *ev) case ev_mouse: return true; // eat events - case ev_joystick: + case ev_gamepad_axis: return true; // eat events default: diff --git a/src/g_input.c b/src/g_input.c index 119eaa834..7d9d09c35 100644 --- a/src/g_input.c +++ b/src/g_input.c @@ -19,6 +19,7 @@ #include "d_net.h" #include "console.h" #include "i_joy.h" // JOYAXISRANGE +#include "z_zone.h" #define MAXMOUSESENSITIVITY 100 // sensitivity steps @@ -35,7 +36,6 @@ consvar_t cv_controlperkey = CVAR_INIT ("controlperkey", "One", CV_SAVE, onecont // current state of the keys // FRACUNIT for fully pressed, 0 for not pressed INT32 gamekeydown[MAXDEVICES][NUMINPUTS]; -boolean deviceResponding[MAXDEVICES]; // two key codes (or virtual key) per game control INT32 gamecontrol[MAXSPLITSCREENPLAYERS][num_gamecontrols][MAXINPUTMAPPING]; @@ -68,13 +68,82 @@ const INT32 gcl_full[num_gcl_full] = { }; */ -INT32 G_GetDevicePlayer(INT32 deviceID) +static INT32 g_gamekeydown_device0[NUMINPUTS]; + +static INT32 g_available_gamepad_devices; +static INT32 g_gamepad_device_ids[MAXGAMEPADS]; +static INT32* g_gamepad_gamekeydown[MAXGAMEPADS]; +static boolean g_device0_responding; +static boolean g_gamepad_responding[MAXGAMEPADS]; +static INT32 g_player_devices[MAXSPLITSCREENPLAYERS] = {-1, -1, -1, -1}; + +void G_RegisterAvailableGamepad(INT32 device_id) +{ + I_Assert(device_id >= 1); + + if (g_available_gamepad_devices == MAXGAMEPADS) + { + // too many! + return; + } + + g_gamepad_device_ids[g_available_gamepad_devices] = device_id; + + g_gamepad_gamekeydown[g_available_gamepad_devices] = Z_CallocAlign(NUMINPUTS * sizeof(INT32), PU_STATIC, NULL, 4); + + g_gamepad_responding[g_available_gamepad_devices] = false; + + g_available_gamepad_devices += 1; +} + +void G_UnregisterAvailableGamepad(INT32 device_id) +{ + int i = 0; + + I_Assert(device_id >= 1); + + if (g_available_gamepad_devices <= 0) + { + return; + } + + for (i = 0; i < g_available_gamepad_devices; i++) + { + if (g_gamepad_device_ids[i] == device_id) + { + int32_t *old_gamekeydown = g_gamepad_gamekeydown[i]; + g_gamepad_device_ids[i] = g_gamepad_device_ids[g_available_gamepad_devices - 1]; + g_gamepad_gamekeydown[i] = g_gamepad_gamekeydown[g_available_gamepad_devices - 1]; + g_gamepad_responding[i] = g_gamepad_responding[g_available_gamepad_devices - 1]; + Z_Free(old_gamekeydown); + g_available_gamepad_devices -= 1; + return; + } + } +} + +INT32 G_GetNumAvailableGamepads(void) +{ + return g_available_gamepad_devices; +} + +INT32 G_GetAvailableGamepadDevice(INT32 available_index) +{ + if (available_index < 0 || available_index >= G_GetNumAvailableGamepads()) + { + return -1; + } + + return g_gamepad_device_ids[available_index]; +} + +INT32 G_GetPlayerForDevice(INT32 device_id) { INT32 i; for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) { - if (deviceID == cv_usejoystick[i].value) + if (device_id == g_player_devices[i]) { return i; } @@ -83,6 +152,172 @@ INT32 G_GetDevicePlayer(INT32 deviceID) return -1; } +INT32 G_GetDeviceForPlayer(INT32 player) +{ + int i; + + if (G_GetPlayerForDevice(KEYBOARD_MOUSE_DEVICE) == player) + { + return KEYBOARD_MOUSE_DEVICE; + } + + for (i = 0; i < G_GetNumAvailableGamepads() + 1; i++) + { + INT32 device = G_GetAvailableGamepadDevice(i); + if (G_GetPlayerForDevice(device) == player) + { + return device; + } + } + + return -1; +} + +void G_SetDeviceForPlayer(INT32 player, INT32 device) +{ + int i; + + I_Assert(player >= 0 && player < MAXSPLITSCREENPLAYERS); + I_Assert(device >= -1); + + g_player_devices[player] = device; + + if (device == -1) + { + return; + } + + if (device != KEYBOARD_MOUSE_DEVICE) + { + I_SetGamepadPlayerIndex(device, player); + } + + for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) + { + if (i == player) + { + continue; + } + + if (g_player_devices[i] == device) + { + g_player_devices[i] = -1; + if (device > 0) + { + I_SetGamepadPlayerIndex(device, -1); + } + } + } +} + +INT32* G_GetDeviceGameKeyDownArray(INT32 device) +{ + int i; + + I_Assert(device >= 0); + + if (device == KEYBOARD_MOUSE_DEVICE) + { + return g_gamekeydown_device0; + } + + for (i = 0; i < g_available_gamepad_devices; i++) + { + if (g_gamepad_device_ids[i] == device) + { + return g_gamepad_gamekeydown[i]; + } + } + + return NULL; +} + +boolean G_IsDeviceResponding(INT32 device) +{ + int i; + + I_Assert(device >= 0); + + if (device == KEYBOARD_MOUSE_DEVICE) + { + return g_device0_responding; + } + + for (i = 0; i < g_available_gamepad_devices; i++) + { + INT32 device_id = G_GetAvailableGamepadDevice(i); + if (device_id == device) + { + return g_gamepad_responding[i]; + } + } + + return false; +} + +void G_SetDeviceResponding(INT32 device, boolean responding) +{ + int i; + + I_Assert(device >= 0); + + if (device == KEYBOARD_MOUSE_DEVICE) + { + g_device0_responding = responding; + return; + } + + for (i = 0; i < g_available_gamepad_devices; i++) + { + INT32 device_id = G_GetAvailableGamepadDevice(i); + if (device_id == device) + { + g_gamepad_responding[i] = responding; + return; + } + } +} + +void G_ResetAllDeviceResponding(void) +{ + int i; + int num_gamepads; + + g_device0_responding = false; + + num_gamepads = G_GetNumAvailableGamepads(); + + for (i = 0; i < num_gamepads; i++) + { + g_gamepad_responding[i] = false; + } +} + +static boolean AutomaticControllerReassignmentIsAllowed(INT32 device) +{ + boolean device_is_gamepad = device > 0; + boolean device_is_unassigned = G_GetPlayerForDevice(device) == -1; + boolean gamestate_is_in_level = gamestate == GS_LEVEL; + + return device_is_gamepad && device_is_unassigned && gamestate_is_in_level; +} + +static INT32 AssignDeviceToFirstUnassignedPlayer(INT32 device) +{ + int i; + + for (i = 0; i < splitscreen + 1; i++) + { + if (G_GetDeviceForPlayer(i) == -1) + { + G_SetDeviceForPlayer(i, device); + return i; + } + } + + return -1; +} + // // Remaps the inputs to game controls. // @@ -94,15 +329,15 @@ void G_MapEventsToControls(event_t *ev) { INT32 i; - if (ev->device >= 0 && ev->device < MAXDEVICES) + if (ev->device >= 0) { switch (ev->type) { case ev_keydown: //case ev_keyup: //case ev_mouse: - //case ev_joystick: - deviceResponding[ev->device] = true; + //case ev_gamepad_axis: + G_SetDeviceResponding(ev->device, true); break; default: @@ -119,7 +354,16 @@ void G_MapEventsToControls(event_t *ev) case ev_keydown: if (ev->data1 < NUMINPUTS) { - gamekeydown[ev->device][ev->data1] = JOYAXISRANGE; + G_GetDeviceGameKeyDownArray(ev->device)[ev->data1] = JOYAXISRANGE; + + if (AutomaticControllerReassignmentIsAllowed(ev->device)) + { + INT32 assigned = AssignDeviceToFirstUnassignedPlayer(ev->device); + if (assigned >= 0) + { + CONS_Alert(CONS_NOTICE, "Player %d device was reassigned\n", assigned + 1); + } + } } #ifdef PARANOIA else @@ -132,7 +376,7 @@ void G_MapEventsToControls(event_t *ev) case ev_keyup: if (ev->data1 < NUMINPUTS) { - gamekeydown[ev->device][ev->data1] = 0; + G_GetDeviceGameKeyDownArray(ev->device)[ev->data1] = 0; } #ifdef PARANOIA else @@ -147,32 +391,32 @@ void G_MapEventsToControls(event_t *ev) if (ev->data2 < 0) { // Left - gamekeydown[ev->device][KEY_MOUSEMOVE + 2] = abs(ev->data2); - gamekeydown[ev->device][KEY_MOUSEMOVE + 3] = 0; + G_GetDeviceGameKeyDownArray(ev->device)[KEY_MOUSEMOVE + 2] = abs(ev->data2); + G_GetDeviceGameKeyDownArray(ev->device)[KEY_MOUSEMOVE + 3] = 0; } else { // Right - gamekeydown[ev->device][KEY_MOUSEMOVE + 2] = 0; - gamekeydown[ev->device][KEY_MOUSEMOVE + 3] = abs(ev->data2); + G_GetDeviceGameKeyDownArray(ev->device)[KEY_MOUSEMOVE + 2] = 0; + G_GetDeviceGameKeyDownArray(ev->device)[KEY_MOUSEMOVE + 3] = abs(ev->data2); } // Y axis if (ev->data3 < 0) { // Up - gamekeydown[ev->device][KEY_MOUSEMOVE] = abs(ev->data3); - gamekeydown[ev->device][KEY_MOUSEMOVE + 1] = 0; + G_GetDeviceGameKeyDownArray(ev->device)[KEY_MOUSEMOVE] = abs(ev->data3); + G_GetDeviceGameKeyDownArray(ev->device)[KEY_MOUSEMOVE + 1] = 0; } else { // Down - gamekeydown[ev->device][KEY_MOUSEMOVE] = 0; - gamekeydown[ev->device][KEY_MOUSEMOVE + 1] = abs(ev->data3); + G_GetDeviceGameKeyDownArray(ev->device)[KEY_MOUSEMOVE] = 0; + G_GetDeviceGameKeyDownArray(ev->device)[KEY_MOUSEMOVE + 1] = abs(ev->data3); } break; - case ev_joystick: // buttons are virtual keys + case ev_gamepad_axis: // buttons are virtual keys if (ev->data1 >= JOYAXISSETS) { #ifdef PARANOIA @@ -190,12 +434,12 @@ void G_MapEventsToControls(event_t *ev) if (ev->data2 != INT32_MAX) { - gamekeydown[ev->device][KEY_AXIS1 + (JOYANALOGS * 4) + (i * 2)] = max(0, ev->data2); + G_GetDeviceGameKeyDownArray(ev->device)[KEY_AXIS1 + (JOYANALOGS * 4) + (i * 2)] = max(0, ev->data2); } if (ev->data3 != INT32_MAX) { - gamekeydown[ev->device][KEY_AXIS1 + (JOYANALOGS * 4) + (i * 2) + 1] = max(0, ev->data3); + G_GetDeviceGameKeyDownArray(ev->device)[KEY_AXIS1 + (JOYANALOGS * 4) + (i * 2) + 1] = max(0, ev->data3); } } else @@ -206,14 +450,14 @@ void G_MapEventsToControls(event_t *ev) if (ev->data2 < 0) { // Left - gamekeydown[ev->device][KEY_AXIS1 + (i * 4)] = abs(ev->data2); - gamekeydown[ev->device][KEY_AXIS1 + (i * 4) + 1] = 0; + G_GetDeviceGameKeyDownArray(ev->device)[KEY_AXIS1 + (i * 4)] = abs(ev->data2); + G_GetDeviceGameKeyDownArray(ev->device)[KEY_AXIS1 + (i * 4) + 1] = 0; } else { // Right - gamekeydown[ev->device][KEY_AXIS1 + (i * 4)] = 0; - gamekeydown[ev->device][KEY_AXIS1 + (i * 4) + 1] = abs(ev->data2); + G_GetDeviceGameKeyDownArray(ev->device)[KEY_AXIS1 + (i * 4)] = 0; + G_GetDeviceGameKeyDownArray(ev->device)[KEY_AXIS1 + (i * 4) + 1] = abs(ev->data2); } } @@ -222,14 +466,14 @@ void G_MapEventsToControls(event_t *ev) if (ev->data3 < 0) { // Up - gamekeydown[ev->device][KEY_AXIS1 + (i * 4) + 2] = abs(ev->data3); - gamekeydown[ev->device][KEY_AXIS1 + (i * 4) + 3] = 0; + G_GetDeviceGameKeyDownArray(ev->device)[KEY_AXIS1 + (i * 4) + 2] = abs(ev->data3); + G_GetDeviceGameKeyDownArray(ev->device)[KEY_AXIS1 + (i * 4) + 3] = 0; } else { // Down - gamekeydown[ev->device][KEY_AXIS1 + (i * 4) + 2] = 0; - gamekeydown[ev->device][KEY_AXIS1 + (i * 4) + 3] = abs(ev->data3); + G_GetDeviceGameKeyDownArray(ev->device)[KEY_AXIS1 + (i * 4) + 2] = 0; + G_GetDeviceGameKeyDownArray(ev->device)[KEY_AXIS1 + (i * 4) + 3] = abs(ev->data3); } } } diff --git a/src/g_input.h b/src/g_input.h index c99260ceb..30ed3be27 100644 --- a/src/g_input.h +++ b/src/g_input.h @@ -107,8 +107,8 @@ extern consvar_t cv_controlperkey; // current state of the keys: JOYAXISRANGE or 0 when boolean. // Or anything inbetween for analog values #define MAXDEVICES (MAXGAMEPADS + 1) // Gamepads + keyboard & mouse +#define KEYBOARD_MOUSE_DEVICE 0 extern INT32 gamekeydown[MAXDEVICES][NUMINPUTS]; -extern boolean deviceResponding[MAXDEVICES]; // several key codes (or virtual key) per game control extern INT32 gamecontrol[MAXSPLITSCREENPLAYERS][num_gamecontrols][MAXINPUTMAPPING]; @@ -135,7 +135,31 @@ extern const INT32 gcl_full[num_gcl_full]; // peace to my little coder fingers! // check a gamecontrol being active or not -INT32 G_GetDevicePlayer(INT32 deviceID); +/* +*/ + +/// Register a device index (from ev_gamepad_device_added) as an Available Gamepad +void G_RegisterAvailableGamepad(INT32 device_id); +/// Unregister a device index (from ev_gamepad_device_removed) as an Available Gamepad +void G_UnregisterAvailableGamepad(INT32 device_id); +/// Get the number of Available Gamepads registered. +INT32 G_GetNumAvailableGamepads(void); +/// Get the device ID for a given Available Gamepad Index, or -1. 0 <= available_index < G_GetNumAvailableGamepads() +INT32 G_GetAvailableGamepadDevice(INT32 available_index); + +INT32 G_GetPlayerForDevice(INT32 deviceID); +/// Get gamepad device for given player, or -1. +INT32 G_GetDeviceForPlayer(INT32 player); + +/// Set the given player index's assigned device. If the device is in use by another player, that player is unassigned. +void G_SetDeviceForPlayer(INT32 player, INT32 device); + +/// Get the gamekeydown array (NUMINPUTS values) for the given device, or NULL if the device id is invalid. +INT32* G_GetDeviceGameKeyDownArray(INT32 device); + +boolean G_IsDeviceResponding(INT32 device); +void G_SetDeviceResponding(INT32 device, boolean responding); +void G_ResetAllDeviceResponding(void); // remaps the input event to a game control. void G_MapEventsToControls(event_t *ev); diff --git a/src/i_joy.h b/src/i_joy.h index d75e7bed7..15e3b264f 100644 --- a/src/i_joy.h +++ b/src/i_joy.h @@ -58,6 +58,9 @@ struct JoyType_t extern JoyType_t Joystick[MAXSPLITSCREENPLAYERS]; +void I_SetGamepadPlayerIndex(INT32 device_id, INT32 index); +void I_SetGamepadIndicatorColor(INT32 device_id, UINT8 red, UINT8 green, UINT8 blue); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/i_system.h b/src/i_system.h index 1289b384f..23ee3bec0 100644 --- a/src/i_system.h +++ b/src/i_system.h @@ -205,9 +205,8 @@ void I_JoyScale4(void); // Called by D_SRB2Main. -/** \brief to startup a joystick -*/ -void I_InitJoystick(UINT8 index); +/// Startup input subsystems. +void I_StartupInput(void); /** \brief to startup the first joystick */ diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 547da7ed6..f1c61503c 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -60,13 +60,6 @@ // And just some randomness for the exits. #include "m_random.h" -#if defined(HAVE_SDL) -#include "SDL.h" -#if SDL_VERSION_ATLEAST(2,0,0) -#include "sdl/sdlmain.h" // JOYSTICK_HOTPLUG -#endif -#endif - #ifdef PC_DOS #include // for snprintf int snprintf(char *str, size_t n, const char *fmt, ...); @@ -3385,11 +3378,18 @@ void M_DrawProfileControls(void) // Get userbound controls... for (k = 0; k < MAXINPUTMAPPING; k++) { + int device; keys[k] = optionsmenu.tempcontrols[gc][k]; if (keys[k] == KEY_NULL) continue; set++; - if (!G_KeyIsAvailable(keys[k], cv_usejoystick[0].value)) + + device = G_GetDeviceForPlayer(0); + if (device == -1) + { + device = 0; + } + if (!G_KeyIsAvailable(keys[k], device)) continue; available++; }; diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 51bc3a2a7..4cc0f674e 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -222,6 +222,22 @@ boolean M_Responder(event_t *ev) return false; } + if (gamestate == GS_MENU && ev->type == ev_gamepad_device_removed && G_GetPlayerForDevice(ev->device) != -1) + { + int i; + INT32 player = G_GetPlayerForDevice(ev->device); + + // Unassign all controllers + for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) + { + G_SetDeviceForPlayer(i, -1); + } + + // Return to the title because a controller was removed at the menu. + CONS_Alert(CONS_NOTICE, "Player %d's assigned gamepad was removed. Returning to the title screen.", player); + D_StartTitle(); + } + if (ev->type == ev_keydown && ev->data1 < NUMKEYS) { // Record keyboard presses diff --git a/src/menus/options-profiles-edit-controls.c b/src/menus/options-profiles-edit-controls.c index ca0c2579a..13633deed 100644 --- a/src/menus/options-profiles-edit-controls.c +++ b/src/menus/options-profiles-edit-controls.c @@ -123,7 +123,7 @@ static void SetDeviceOnPress(void) { if (deviceResponding[i]) { - CV_SetValue(&cv_usejoystick[0], i); // Force-set this joystick as the current joystick we're using for P1 (which is the only one controlling menus) + G_SetDeviceForPlayer(0, i); // Force-set this joystick as the current joystick we're using for P1 (which is the only one controlling menus) CONS_Printf("SetDeviceOnPress: Device for %d set to %d\n", 0, i); return; } @@ -307,7 +307,7 @@ void M_MapProfileControl(event_t *ev) UINT8 where = n; // By default, we'll save the bind where we're supposed to map. INT32 i; - //SetDeviceOnPress(); // Update cv_usejoystick + //SetDeviceOnPress(); // Update player gamepad assignments // Only consider keydown and joystick events to make sure we ignore ev_mouse and other events // See also G_MapEventsToControls @@ -325,11 +325,11 @@ void M_MapProfileControl(event_t *ev) } #endif break; - case ev_joystick: + case ev_gamepad_axis: if (ev->data1 >= JOYAXES) { #ifdef PARANOIA - CONS_Debug(DBG_GAMELOGIC, "Bad joystick axis event %d\n", ev->data1); + CONS_Debug(DBG_GAMELOGIC, "Bad gamepad axis event %d\n", ev->data1); #endif return; } diff --git a/src/menus/play-char-select.c b/src/menus/play-char-select.c index a6ac59d1a..693938528 100644 --- a/src/menus/play-char-select.c +++ b/src/menus/play-char-select.c @@ -380,10 +380,10 @@ void M_CharacterSelectInit(void) { for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) { - // Un-set devices for other players. - if (i != 0 || optionsmenu.profile) + // Un-set devices for all players if not editing profile + if (!optionsmenu.profile) { - CV_SetValue(&cv_usejoystick[i], -1); + G_SetDeviceForPlayer(i, -1); CONS_Printf("M_CharacterSelectInit: Device for %d set to %d\n", i, -1); } } @@ -526,7 +526,12 @@ static boolean M_DeviceAvailable(INT32 deviceID, UINT8 numPlayers) for (i = 0; i < numPlayers; i++) { - if (cv_usejoystick[i].value == deviceID) + int player_device = G_GetDeviceForPlayer(i); + if (player_device == -1) + { + continue; + } + if (player_device == deviceID) { // This one's already being used. return false; @@ -540,6 +545,7 @@ static boolean M_DeviceAvailable(INT32 deviceID, UINT8 numPlayers) static boolean M_HandlePressStart(setup_player_t *p, UINT8 num) { INT32 i, j; + INT32 num_gamepads_available; if (optionsmenu.profile) return false; // Don't allow for the possibility of SOMEHOW another player joining in. @@ -568,24 +574,32 @@ static boolean M_HandlePressStart(setup_player_t *p, UINT8 num) } // Now detect new devices trying to join. - for (i = 0; i < MAXDEVICES; i++) + num_gamepads_available = G_GetNumAvailableGamepads(); + for (i = 0; i < num_gamepads_available + 1; i++) { - if (deviceResponding[i] != true) + INT32 device = 0; + + if (i > 0) + { + device = G_GetAvailableGamepadDevice(i - 1); + } + + if (G_IsDeviceResponding(device) != true) { // No buttons are being pushed. continue; } - if (M_DeviceAvailable(i, setup_numplayers) == true) + if (M_DeviceAvailable(device, setup_numplayers) == true) { // Available!! Let's use this one!! // if P1 is setting up using keyboard (device 0), save their last used device. // this is to allow them to retain controller usage when they play alone. // Because let's face it, when you test mods, you're often lazy to grab your controller for menuing :) - if (!i && !num) + if (i == 0 && num == 0) { - setup_player[num].ponedevice = cv_usejoystick[num].value; + setup_player[num].ponedevice = G_GetDeviceForPlayer(num); } else if (num) { @@ -593,14 +607,13 @@ static boolean M_HandlePressStart(setup_player_t *p, UINT8 num) memcpy(&gamecontrol[num], gamecontroldefault, sizeof(gamecontroldefault)); } + G_SetDeviceForPlayer(num, device); + CONS_Printf("M_HandlePressStart: Device for %d set to %d\n", num, device); - CV_SetValue(&cv_usejoystick[num], i); - CONS_Printf("M_HandlePressStart: Device for %d set to %d\n", num, i); - - for (j = num+1; j < MAXSPLITSCREENPLAYERS; j++) + for (j = num + 1; j < MAXSPLITSCREENPLAYERS; j++) { // Un-set devices for other players. - CV_SetValue(&cv_usejoystick[j], -1); + G_SetDeviceForPlayer(j, -1); CONS_Printf("M_HandlePressStart: Device for %d set to %d\n", j, -1); } @@ -616,7 +629,7 @@ static boolean M_HandlePressStart(setup_player_t *p, UINT8 num) menucmd[j].buttonsHeld |= MBT_X; } - memset(deviceResponding, false, sizeof(deviceResponding)); + G_ResetAllDeviceResponding(); return true; } } @@ -668,11 +681,8 @@ static boolean M_HandleCSelectProfile(setup_player_t *p, UINT8 num) menucmd[i].buttonsHeld |= MBT_X; } - if (num > 0) - { - CV_StealthSetValue(&cv_usejoystick[num], -1); - CONS_Printf("M_HandleCSelectProfile: Device for %d set to %d\n", num, -1); - } + G_SetDeviceForPlayer(num, -1); + CONS_Printf("M_HandleCSelectProfile: Device for %d set to %d\n", num, -1); return true; } @@ -1482,18 +1492,12 @@ void M_CharacterSelectTick(void) CV_StealthSetValue(&cv_splitplayers, setup_numplayers); - // P1 is alone, set their old device just in case. - if (setup_numplayers < 2 && setup_player[0].ponedevice) - { - CV_StealthSetValue(&cv_usejoystick[0], setup_player[0].ponedevice); - } - #if defined (TESTERS) M_MPOptSelectInit(0); #else M_SetupNextMenu(&PLAY_MainDef, false); #endif - + } } else // In a game diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index 9362a540b..186d85030 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -194,48 +194,6 @@ static char returnWadPath[256]; #include "../byteptr.h" #endif -void I_StoreExJoystick(SDL_GameController *dev) -{ - // ExJoystick is a massive hack to avoid needing to completely - // rewrite pretty much all of the controller support from scratch... - - // Used in favor of most instances of SDL_GameControllerClose. - // If a joystick would've been discarded, then save it in an array, - // because we want it have it for the joystick input screen. - - int index = 0; - - if (dev == NULL) - { - // No joystick? - return; - } - - index = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(dev)); - - if (index >= MAXGAMEPADS || index < 0) - { - // Not enough space to save this joystick, completely discard. - SDL_GameControllerClose(dev); - return; - } - - if (ExJoystick[index] == dev) - { - // No need to do anything else. - return; - } - - if (ExJoystick[index] != NULL) - { - // Discard joystick in the old slot. - SDL_GameControllerClose(ExJoystick[index]); - } - - // Keep for safe-keeping. - ExJoystick[index] = dev; -} - /** \brief The JoyReset function \param JoySet Joystick info to reset @@ -244,10 +202,6 @@ void I_StoreExJoystick(SDL_GameController *dev) */ static void JoyReset(SDLJoyInfo_t *JoySet) { - if (JoySet->dev) - { - I_StoreExJoystick(JoySet->dev); - } JoySet->dev = NULL; JoySet->oldjoy = -1; JoySet->axises = JoySet->buttons = JoySet->hats = JoySet->balls = 0; @@ -261,7 +215,6 @@ static INT32 joystick_started[MAXSPLITSCREENPLAYERS] = {0,0,0,0}; /** \brief SDL info about joystick 1 */ SDLJoyInfo_t JoyInfo[MAXSPLITSCREENPLAYERS]; -SDL_GameController *ExJoystick[MAXGAMEPADS]; SDL_bool consolevent = SDL_FALSE; SDL_bool framebuffer = SDL_FALSE; @@ -983,316 +936,78 @@ void I_JoyScale4(void) JoyInfo[3].scale = Joystick[3].bGamepadStyle?1:cv_joyscale[1].value; } -// Cheat to get the device index for a game controller handle -INT32 I_GetJoystickDeviceIndex(SDL_GameController *dev) +void I_SetGamepadPlayerIndex(INT32 device_id, INT32 player) { - SDL_Joystick *joystick = NULL; +#if !(SDL_VERSION_ATLEAST(2,0,12)) + (void)device_id; + (void)player; +#else + I_Assert(device_id > 0); // Gamepad devices are always ID 1 or higher + I_Assert(player >= 0 && player < MAXSPLITSCREENPLAYERS); - joystick = SDL_GameControllerGetJoystick(dev); - - if (joystick) + SDL_GameController *controller = SDL_GameControllerFromInstanceID(device_id - 1); + if (controller == NULL) { - return SDL_JoystickInstanceID(joystick); + return; } - return -1; + SDL_GameControllerSetPlayerIndex(controller, player); +#endif } -void I_UpdateJoystickDeviceIndex(UINT8 player) +void I_SetGamepadIndicatorColor(INT32 device_id, UINT8 red, UINT8 green, UINT8 blue) { - /////////////////////////////////////////////// - // update this joystick's device index (wow) // - /////////////////////////////////////////////// +#if !(SDL_VERSION_ATLEAST(2,0,14)) + (void)device_id; + (void)player; +#else + I_Assert(device_id > 0); // Gamepad devices are always ID 1 or higher - if (JoyInfo[player].dev) + SDL_GameController *controller = SDL_GameControllerFromInstanceID(device_id - 1); + if (controller == NULL) { - cv_usejoystick[player].value = I_GetJoystickDeviceIndex(JoyInfo[player].dev) + 1; - CONS_Printf("I_UpdateJoystickDeviceIndex: Device for %d set to %d\n", player, cv_usejoystick[player].value); - } - else - { - UINT8 joystickID, compareJoystick; - - for (joystickID = 0; joystickID < MAXSPLITSCREENPLAYERS; joystickID++) - { - // is this cv_usejoystick used? - const INT32 value = atoi(cv_usejoystick[joystickID].string); - - for (compareJoystick = 0; compareJoystick < MAXSPLITSCREENPLAYERS; compareJoystick++) - { - if (compareJoystick == player) - continue; - - if (value == JoyInfo[compareJoystick].oldjoy || value == cv_usejoystick[compareJoystick].value) - break; - } - - if (compareJoystick == MAXSPLITSCREENPLAYERS) - { - // We DID make it through the whole loop, so we can use this one! - cv_usejoystick[player].value = value; - CONS_Printf("I_UpdateJoystickDeviceIndex: Device for %d set to %d\n", player, cv_usejoystick[player].value); - break; - } - } - - if (joystickID == MAXSPLITSCREENPLAYERS) - { - // We DID NOT make it through the whole loop, so we can't assign this joystick to anything. - // When you try your best, but you don't succeed... - cv_usejoystick[player].value = 0; - CONS_Printf("I_UpdateJoystickDeviceIndex: Device for %d set to %d\n", player, 0); - } - } -} - -// Misleading function: updates device indices for all players BUT the one specified. -// Necessary for SDL_JOYDEVICEADDED events -void I_UpdateJoystickDeviceIndices(UINT8 excludePlayer) -{ - UINT8 player; - - for (player = 0; player < MAXSPLITSCREENPLAYERS; player++) - { - if (player == excludePlayer) - continue; - - I_UpdateJoystickDeviceIndex(player); - } -} - -/** \brief Shuts down joystick - \return void -*/ -void I_ShutdownJoystick(UINT8 index) -{ - INT32 i; - event_t event; - - event.device = I_GetJoystickDeviceIndex(JoyInfo[index].dev); - event.type = ev_keyup; - event.data2 = 0; - event.data3 = 0; - - // emulate the up of all joystick buttons - for (i = 0; i < JOYBUTTONS; i++) - { - event.data1 = KEY_JOY1+i; - D_PostEvent(&event); + return; } - // reset joystick position - event.type = ev_joystick; - for (i = 0; i < JOYAXES; i++) - { - event.data1 = i; - D_PostEvent(&event); - } - - joystick_started[index] = 0; - JoyReset(&JoyInfo[index]); - - // don't shut down the subsystem here, because hotplugging -} - -/** \brief Open joystick handle - - \param fname name of joystick - - \return axises - - -*/ -static int joy_open(int playerIndex, int joyIndex) -{ - SDL_GameController *newdev = NULL; - int num_joy = 0; - - if (SDL_WasInit(SDL_INIT_JOYSTICK) == 0) - { - CONS_Printf(M_GetText("Joystick subsystem not started\n")); - return -1; - } - - if (SDL_WasInit(SDL_INIT_GAMECONTROLLER) == 0) - { - CONS_Printf(M_GetText("Game Controller subsystem not started\n")); - return -1; - } - - if (joyIndex <= 0) - return -1; - - num_joy = SDL_NumJoysticks(); - - if (num_joy == 0) - { - CONS_Printf("%s", M_GetText("Found no joysticks on this system\n")); - return -1; - } - - newdev = SDL_GameControllerOpen(joyIndex-1); - - // Handle the edge case where the device <-> joystick index assignment can change due to hotplugging - // This indexing is SDL's responsibility and there's not much we can do about it. - // - // Example: - // 1. Plug Controller A -> Index 0 opened - // 2. Plug Controller B -> Index 1 opened - // 3. Unplug Controller A -> Index 0 closed, Index 1 active - // 4. Unplug Controller B -> Index 0 inactive, Index 1 closed - // 5. Plug Controller B -> Index 0 opened - // 6. Plug Controller A -> Index 0 REPLACED, opened as Controller A; Index 1 is now Controller B - if (JoyInfo[playerIndex].dev) - { - if (JoyInfo[playerIndex].dev == newdev // same device, nothing to do - || (newdev == NULL && SDL_GameControllerGetAttached(JoyInfo[playerIndex].dev))) // we failed, but already have a working device - { - return SDL_CONTROLLER_AXIS_MAX; - } - - // Else, we're changing devices, so send neutral joy events - CONS_Debug(DBG_GAMELOGIC, "Joystick%d device is changing; resetting events...\n", playerIndex+1); - I_ShutdownJoystick(playerIndex); - } - - JoyInfo[playerIndex].dev = newdev; - - if (JoyInfo[playerIndex].dev == NULL) - { - CONS_Debug(DBG_GAMELOGIC, M_GetText("Joystick%d: Couldn't open device - %s\n"), playerIndex+1, SDL_GetError()); - return -1; - } - else - { - CONS_Debug(DBG_GAMELOGIC, M_GetText("Joystick%d: %s\n"), playerIndex+1, SDL_GameControllerName(JoyInfo[playerIndex].dev)); - - JoyInfo[playerIndex].axises = SDL_CONTROLLER_AXIS_MAX; - JoyInfo[playerIndex].buttons = SDL_CONTROLLER_BUTTON_MAX; - JoyInfo[playerIndex].hats = 1; - JoyInfo[playerIndex].balls = 0; - - //JoyInfo[playerIndex].bGamepadStyle = !stricmp(SDL_JoystickName(JoyInfo[playerIndex].dev), "pad"); - - return JoyInfo[playerIndex].axises; - } + SDL_GameControllerSetLED(controller, red, green, blue); +#endif } // -// I_InitJoystick +// I_StartupInput // -void I_InitJoystick(UINT8 index) +void I_StartupInput(void) { - SDL_GameController *newcontroller = NULL; - UINT8 i; - - //I_ShutdownJoystick(); - //SDL_SetHintWithPriority("SDL_XINPUT_ENABLED", "0", SDL_HINT_OVERRIDE); if (M_CheckParm("-nojoy")) return; + if (SDL_WasInit(SDL_INIT_GAMECONTROLLER)) + { + return; + } + if (M_CheckParm("-noxinput")) SDL_SetHintWithPriority("SDL_XINPUT_ENABLED", "0", SDL_HINT_OVERRIDE); if (M_CheckParm("-nohidapi")) SDL_SetHintWithPriority("SDL_JOYSTICK_HIDAPI", "0", SDL_HINT_OVERRIDE); - if (SDL_WasInit(SDL_INIT_JOYSTICK) == 0) - { - CONS_Printf("I_InitJoystick()...\n"); + CONS_Printf("I_StartupInput()...\n"); - if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) == -1) - { - CONS_Printf(M_GetText("Couldn't initialize joystick: %s\n"), SDL_GetError()); - return; - } + if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) == -1) + { + CONS_Printf(M_GetText("Couldn't initialize game controllers: %s\n"), SDL_GetError()); + return; } - if (SDL_WasInit(SDL_INIT_GAMECONTROLLER) == 0) - { - if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) == -1) - { - CONS_Printf(M_GetText("Couldn't initialize gamepads: %s\n"), SDL_GetError()); - return; - } - } - - if (cv_usejoystick[index].value) - newcontroller = SDL_GameControllerOpen(cv_usejoystick[index].value-1); - - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - { - if (i == index) - continue; - - if (JoyInfo[i].dev == newcontroller) - break; - } - - if (newcontroller && i < MAXSPLITSCREENPLAYERS) // don't override an active device - { - cv_usejoystick[index].value = I_GetJoystickDeviceIndex(JoyInfo[index].dev) + 1; - CONS_Printf("I_InitJoystick: Device for %d set to %d\n", index, cv_usejoystick[index].value); - } - else if (newcontroller && joy_open(index, cv_usejoystick[index].value) != -1) - { - // SDL's device indexes are unstable, so cv_usejoystick may not match - // the actual device index. So let's cheat a bit and find the device's current index. - JoyInfo[index].oldjoy = I_GetJoystickDeviceIndex(JoyInfo[index].dev) + 1; - joystick_started[index] = 1; - } - else - { - if (JoyInfo[index].oldjoy) - I_ShutdownJoystick(index); - cv_usejoystick[index].value = 0; - CONS_Printf("I_InitJoystick: Device for %d set to %d\n", index, cv_usejoystick[index].value); - joystick_started[index] = 0; - } - - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - { - if (JoyInfo[i].dev == newcontroller) - break; - } - - if (i == MAXSPLITSCREENPLAYERS) - { - // Joystick didn't end up being used - I_StoreExJoystick(newcontroller); - } -} - -void I_InitJoystick1(void) -{ - I_InitJoystick(0); -} - -void I_InitJoystick2(void) -{ - I_InitJoystick(1); -} - -void I_InitJoystick3(void) -{ - I_InitJoystick(2); -} - -void I_InitJoystick4(void) -{ - I_InitJoystick(3); + // Upon initialization, the gamecontroller subsystem will automatically dispatch controller device added events + // for controllers connected before initialization. } static void I_ShutdownInput(void) { - UINT8 i; - - // Yes, the name is misleading: these send neutral events to - // clean up the unplugged joystick's input - // Note these methods are internal to this file, not called elsewhere. - - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - I_ShutdownJoystick(i); + // The game code is now responsible for resetting its internal state based on ev_gamepad_device_removed events. + // In practice, Input should never be shutdown and restarted during runtime. if (SDL_WasInit(SDL_INIT_GAMECONTROLLER) == SDL_INIT_GAMECONTROLLER) { @@ -1322,14 +1037,28 @@ static char joyname[255]; // joystick name is straight from the driver const char *I_GetJoyName(INT32 joyindex) { const char *tempname = NULL; + SDL_Joystick* joystick; joyname[0] = 0; joyindex--; //SDL's Joystick System starts at 0, not 1 - if (SDL_WasInit(SDL_INIT_JOYSTICK) == SDL_INIT_JOYSTICK) + + if (!SDL_WasInit(SDL_INIT_JOYSTICK) == SDL_INIT_JOYSTICK) { - tempname = SDL_JoystickNameForIndex(joyindex); - if (tempname) - strncpy(joyname, tempname, 255); + return joyname; } + + // joyindex corresponds to the open joystick *instance* ID, not the joystick number + joystick = SDL_JoystickFromInstanceID(joyindex); + if (joystick == NULL) + { + return joyname; + } + + tempname = SDL_JoystickNameForIndex(joyindex); + if (tempname) + { + strncpy(joyname, tempname, 255); + } + return joyname; } diff --git a/src/sdl/i_video.cpp b/src/sdl/i_video.cpp index d86f4a4b4..9c0c3d3b4 100644 --- a/src/sdl/i_video.cpp +++ b/src/sdl/i_video.cpp @@ -542,7 +542,7 @@ static void Impl_HandleWindowEvent(SDL_WindowEvent evt) SDLforceUngrabMouse(); } memset(gamekeydown, 0, sizeof(gamekeydown)); // TODO this is a scary memset - memset(deviceResponding, false, sizeof (deviceResponding)); + G_ResetAllDeviceResponding(); if (MOUSE_MENU) { @@ -701,7 +701,7 @@ static void Impl_HandleControllerAxisEvent(SDL_ControllerAxisEvent evt) event_t event; INT32 value; - event.type = ev_joystick; + event.type = ev_gamepad_axis; event.device = 1 + evt.which; if (event.device == INT32_MAX) @@ -777,6 +777,40 @@ static void Impl_HandleControllerButtonEvent(SDL_ControllerButtonEvent evt, Uint } } +static void Impl_HandleControllerDeviceAddedEvent(SDL_ControllerDeviceEvent event) +{ + // The game is always interested in controller events, even if they aren't internally assigned to a player. + // Thus, we *always* open SDL controllers as they become available, to begin receiving their events. + + SDL_GameController* controller = SDL_GameControllerOpen(event.which); + if (controller == NULL) + { + return; + } + + SDL_Joystick* joystick = SDL_GameControllerGetJoystick(controller); + SDL_JoystickID joystick_instance_id = SDL_JoystickInstanceID(joystick); + + event_t engine_event {}; + + engine_event.type = ev_gamepad_device_added; + engine_event.device = 1 + joystick_instance_id; + + D_PostEvent(&engine_event); +} + +static void Impl_HandleControllerDeviceRemovedEvent(SDL_ControllerDeviceEvent event) +{ + // SDL only posts Device Removed events for controllers that have actually been opened. + // Thus, we don't need to filter out controllers that may not have opened successfully prior to this event. + event_t engine_event {}; + + engine_event.type = ev_gamepad_device_removed; + engine_event.device = 1 + event.which; + + D_PostEvent(&engine_event); +} + static ImGuiKey ImGui_ImplSDL2_KeycodeToImGuiKey(int keycode) { switch (keycode) @@ -983,8 +1017,6 @@ void I_GetEvent(void) // otherwise we'll end up catching the warp back to center. //int mouseMotionOnce = 0; - UINT8 i; - if (!graphics_started) { return; @@ -1031,147 +1063,14 @@ void I_GetEvent(void) Impl_HandleControllerButtonEvent(evt.cbutton, evt.type); break; - //////////////////////////////////////////////////////////// - case SDL_CONTROLLERDEVICEADDED: - { - // OH BOY are you in for a good time! #abominationstation - - SDL_GameController *newcontroller = SDL_GameControllerOpen(evt.cdevice.which); - - CONS_Debug(DBG_GAMELOGIC, "Controller device index %d added\n", evt.cdevice.which + 1); - - //////////////////////////////////////////////////////////// - // Because SDL's device index is unstable, we're going to cheat here a bit: - // For the first joystick setting that is NOT active: - // - // 1. Set cv_usejoystickX.value to the new device index (this does not change what is written to config.cfg) - // - // 2. Set OTHERS' cv_usejoystickX.value to THEIR new device index, because it likely changed - // * If device doesn't exist, switch cv_usejoystick back to default value (.string) - // * BUT: If that default index is being occupied, use ANOTHER cv_usejoystick's default value! - //////////////////////////////////////////////////////////// - - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - { - if (newcontroller && (!JoyInfo[i].dev || !SDL_GameControllerGetAttached(JoyInfo[i].dev))) - { - UINT8 j; - - for (j = 0; j < MAXSPLITSCREENPLAYERS; j++) - { - if (i == j) - continue; - - if (JoyInfo[j].dev == newcontroller) - break; - } - - if (j == MAXSPLITSCREENPLAYERS) - { - // ensures we aren't overriding a currently active device - cv_usejoystick[i].value = evt.cdevice.which + 1; - I_UpdateJoystickDeviceIndices(0); - } - } - } - - //////////////////////////////////////////////////////////// - // Was cv_usejoystick disabled in settings? - //////////////////////////////////////////////////////////// - - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - { - if (!strcmp(cv_usejoystick[i].string, "0") || !cv_usejoystick[i].value) - cv_usejoystick[i].value = 0; - else if (atoi(cv_usejoystick[i].string) <= I_NumJoys() // don't mess if we intentionally set higher than NumJoys - && cv_usejoystick[i].value) // update the cvar ONLY if a device exists - CV_SetValue(&cv_usejoystick[i], cv_usejoystick[i].value); - } - - //////////////////////////////////////////////////////////// - // Update all joysticks' init states - // This is a little wasteful since cv_usejoystick already calls this, but - // we need to do this in case CV_SetValue did nothing because the string was already same. - // if the device is already active, this should do nothing, effectively. - //////////////////////////////////////////////////////////// - - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - I_InitJoystick(i); - - //////////////////////////////////////////////////////////// - - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - CONS_Debug(DBG_GAMELOGIC, "Joystick%d device index: %d\n", i+1, JoyInfo[i].oldjoy); - -#if 0 - // update the menu - if (currentMenu == &OP_JoystickSetDef) - M_SetupJoystickMenu(0); -#endif - - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - { - if (JoyInfo[i].dev == newcontroller) - break; - } - - if (i == MAXSPLITSCREENPLAYERS) - I_StoreExJoystick(newcontroller); - } + Impl_HandleControllerDeviceAddedEvent(evt.cdevice); break; - //////////////////////////////////////////////////////////// - case SDL_CONTROLLERDEVICEREMOVED: - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - { - if (JoyInfo[i].dev && !SDL_GameControllerGetAttached(JoyInfo[i].dev)) - { - CONS_Debug(DBG_GAMELOGIC, "Joystick%d removed, device index: %d\n", i+1, JoyInfo[i].oldjoy); - I_ShutdownJoystick(i); - } - } - - //////////////////////////////////////////////////////////// - // Update the device indexes, because they likely changed - // * If device doesn't exist, switch cv_usejoystick back to default value (.string) - // * BUT: If that default index is being occupied, use ANOTHER cv_usejoystick's default value! - //////////////////////////////////////////////////////////// - - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - { - I_UpdateJoystickDeviceIndex(i); - } - - //////////////////////////////////////////////////////////// - // Was cv_usejoystick disabled in settings? - //////////////////////////////////////////////////////////// - - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - { - if (!strcmp(cv_usejoystick[i].string, "0")) - { - cv_usejoystick[i].value = 0; - } - else if (atoi(cv_usejoystick[i].string) <= I_NumJoys() // don't mess if we intentionally set higher than NumJoys - && cv_usejoystick[i].value) // update the cvar ONLY if a device exists - { - CV_SetValue(&cv_usejoystick[i], cv_usejoystick[i].value); - } - } - - //////////////////////////////////////////////////////////// - - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - CONS_Debug(DBG_GAMELOGIC, "Joystick%d device index: %d\n", i+1, JoyInfo[i].oldjoy); - -#if 0 - // update the menu - if (currentMenu == &OP_JoystickSetDef) - M_SetupJoystickMenu(0); -#endif + Impl_HandleControllerDeviceRemovedEvent(evt.cdevice); break; + case SDL_QUIT: LUA_HookBool(true, HOOK(GameQuit)); I_Quit(); @@ -1195,10 +1094,7 @@ void I_GetEvent(void) // In order to make wheels act like buttons, we have to set their state to Up. // This is because wheel messages don't have an up/down state. - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - { - gamekeydown[i][KEY_MOUSEWHEELDOWN] = gamekeydown[i][KEY_MOUSEWHEELUP] = 0; - } + G_GetDeviceGameKeyDownArray(0)[KEY_MOUSEWHEELDOWN] = G_GetDeviceGameKeyDownArray(0)[KEY_MOUSEWHEELUP] = 0; } static void half_warp_mouse(uint16_t x, uint16_t y) { diff --git a/src/sdl/sdlmain.h b/src/sdl/sdlmain.h index 8fd360248..9fe9c686a 100644 --- a/src/sdl/sdlmain.h +++ b/src/sdl/sdlmain.h @@ -36,9 +36,6 @@ extern "C" { #define SDL2STUB() CONS_Printf("SDL2: stubbed: %s:%d\n", __func__, __LINE__) #endif -// So m_menu knows whether to store cv_usejoystick value or string -#define JOYSTICK_HOTPLUG - /** \brief The JoyInfo_s struct info about joystick @@ -65,9 +62,6 @@ typedef struct SDLJoyInfo_s /** \brief SDL info about controllers */ extern SDLJoyInfo_t JoyInfo[MAXSPLITSCREENPLAYERS]; -extern SDL_GameController *ExJoystick[MAXGAMEPADS]; - -void I_StoreExJoystick(SDL_GameController *dev); /** \brief joystick axis deadzone */ @@ -76,18 +70,6 @@ void I_StoreExJoystick(SDL_GameController *dev); void I_GetConsoleEvents(void); -// So we can call this from i_video event loop -void I_ShutdownJoystick(UINT8 index); - -// Cheat to get the device index for a game controller handle -INT32 I_GetJoystickDeviceIndex(SDL_GameController *dev); - -// Quick thing to make SDL_JOYDEVICEADDED events less of an abomination -void I_UpdateJoystickDeviceIndex(UINT8 player); -void I_UpdateJoystickDeviceIndices(UINT8 excludePlayer); - -void I_GetConsoleEvents(void); - void SDLforceUngrabMouse(void); // Needed for some WIN32 functions