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/audio/gain.cpp b/src/audio/gain.cpp index 8db17d4e8..9f907d961 100644 --- a/src/audio/gain.cpp +++ b/src/audio/gain.cpp @@ -36,7 +36,7 @@ size_t Gain::filter(tcb::span> input_buffer, tcb::span> b template void Gain::gain(float new_gain) { - new_gain_ = std::clamp(new_gain, 0.0f, 1.0f); + new_gain_ = std::max(new_gain, 0.f); } template diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 5e309717c..258730b3a 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 23d4562b5..508b42712 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -172,6 +172,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 @@ -184,11 +232,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 @@ -989,7 +1039,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) @@ -1524,6 +1574,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 8fddc2794..46ff5a92f 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), @@ -1040,7 +1030,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]); @@ -3374,7 +3363,7 @@ static void Got_Respawn(UINT8 **cp, INT32 playernum) if (!P_IsObjectOnGround(players[respawnplayer].mo)) return; - K_DoIngameRespawn(&players[respawnplayer]); + P_DamageMobj(players[respawnplayer].mo, NULL, NULL, 1, DMG_DEATHPIT); demo_extradata[playernum] |= DXD_RESPAWN; } } @@ -6231,6 +6220,7 @@ static void FollowerAny_OnChange(UINT8 pnum) return; // don't send anything there. SendNameAndColor(pnum); + G_SetPlayerGamepadIndicatorToPlayerColor(pnum); } // sends the follower change for players @@ -6382,6 +6372,8 @@ static void Color_OnChange(void) } } lastgoodcolor[0] = cv_playercolor[0].value; + + G_SetPlayerGamepadIndicatorToPlayerColor(0); } /** Sends a color change for the secondary splitscreen player, unless that @@ -6410,6 +6402,8 @@ static void Color2_OnChange(void) } } lastgoodcolor[1] = cv_playercolor[1].value; + + G_SetPlayerGamepadIndicatorToPlayerColor(1); } static void Color3_OnChange(void) @@ -6433,6 +6427,8 @@ static void Color3_OnChange(void) } } lastgoodcolor[2] = cv_playercolor[2].value; + + G_SetPlayerGamepadIndicatorToPlayerColor(2); } static void Color4_OnChange(void) @@ -6456,6 +6452,8 @@ static void Color4_OnChange(void) } } lastgoodcolor[3] = cv_playercolor[3].value; + + G_SetPlayerGamepadIndicatorToPlayerColor(3); } /** Displays the result of the chat being muted or unmuted. diff --git a/src/d_netcmd.h b/src/d_netcmd.h index e6c10fa11..1a3bbda8b 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/d_player.h b/src/d_player.h index 0bc1d43bc..5fbe6e7f4 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -513,7 +513,7 @@ struct player_t UINT16 spinouttimer; // Spin-out from a banana peel or oil slick (was "pw_bananacam") UINT8 spinouttype; // Determines the mode of spinout/wipeout, see kartspinoutflags_t UINT8 instashield; // Instashield no-damage animation timer - INT32 invulnhitlag; // Numbers of tics of hitlag added this tic for "potential" damage -- not real damage + INT32 nullHitlag; // Numbers of tics of hitlag that will ultimately be ignored by subtracting from hitlag UINT8 wipeoutslow; // Timer before you slowdown when getting wiped out UINT8 justbumped; // Prevent players from endlessly bumping into each other UINT8 tumbleBounces; @@ -625,9 +625,7 @@ struct player_t UINT32 roundscore; // battle score this round UINT8 emeralds; - UINT8 bumpers; INT16 karmadelay; - tic_t overtimekarma; // time to live in overtime comeback INT16 spheres; tic_t spheredigestion; @@ -666,8 +664,10 @@ struct player_t INT16 lastsidehit, lastlinehit; - // These track how many things tried to damage you, not - // whether you actually took damage. + // TimesHit tracks how many times something tried to + // damage you or how many times you tried to damage + // something else. It does not track whether damage was + // actually dealt. UINT8 timeshit; // times hit this tic UINT8 timeshitprev; // times hit before // That's TIMES HIT, not TIME SHIT, you doofus! -- in memoriam diff --git a/src/deh_tables.c b/src/deh_tables.c index 3e72a1900..8c8ebf8ec 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -5730,7 +5730,7 @@ const char *const MOBJFLAG2_LIST[] = { "JUSTATTACKED", // can be pushed by other moving mobjs "FIRING", // turret fire "SUPERFIRE", // Firing something with Super Sonic-stopping properties. Or, if mobj has MF_MISSILE, this is the actual fire from it. - "\x01", // free: 1<<20 (name un-matchable) + "ALREADYHIT", // This object was already damaged THIS tic, resets even during hitlag "STRONGBOX", // Flag used for "strong" random monitors. "OBJECTFLIP", // Flag for objects that always have flipped gravity. "SKULLFLY", // Special handling: skull in flight. diff --git a/src/doomdef.h b/src/doomdef.h index a3c62eeb2..a28a7085e 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -748,10 +748,10 @@ extern int /// Render flats on walls #define WALLFLATS -/// Divide volume of music and sounds by this much (loudest sounds on earth) -#define VOLUME_DIVIDER 4 -#define USER_VOLUME_SCALE 2 -#define MAX_VOLUME ( 100 * VOLUME_DIVIDER / USER_VOLUME_SCALE ) +// Volume scale is 0-100 in new mixer. 100 is treated as -0dB or 100% gain. No more weirdness to work around SDL_mixer +// problems + +#define MAX_VOLUME 100 #ifdef HAVE_CURL #define MASTERSERVER diff --git a/src/g_demo.c b/src/g_demo.c index 4c9a80d94..312d0f3ca 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -96,7 +96,8 @@ static struct { // EZT_ITEMDATA SINT8 itemtype; - UINT8 itemamount, bumpers; + UINT8 itemamount; + INT32 health; // EZT_STATDATA UINT8 skinid, kartspeed, kartweight; @@ -356,7 +357,7 @@ void G_ReadDemoExtraData(void) if (players[p].mo) { // Is this how this should work..? - K_DoIngameRespawn(&players[p]); + P_DamageMobj(players[p].mo, NULL, NULL, 1, DMG_DEATHPIT); } } if (extradata & DXD_WEAPONPREF) @@ -814,13 +815,13 @@ void G_WriteGhostTic(mobj_t *ghost, INT32 playernum) if (ghost->player && ( ghostext[playernum].itemtype != ghost->player->itemtype || ghostext[playernum].itemamount != ghost->player->itemamount || - ghostext[playernum].bumpers != ghost->player->bumpers + ghostext[playernum].health < ghost->health )) { ghostext[playernum].flags |= EZT_ITEMDATA; ghostext[playernum].itemtype = ghost->player->itemtype; ghostext[playernum].itemamount = ghost->player->itemamount; - ghostext[playernum].bumpers = ghost->player->bumpers; + ghostext[playernum].health = ghost->health; } if (ghost->player && ( @@ -882,7 +883,7 @@ void G_WriteGhostTic(mobj_t *ghost, INT32 playernum) { WRITESINT8(demobuf.p, ghostext[playernum].itemtype); WRITEUINT8(demobuf.p, ghostext[playernum].itemamount); - WRITEUINT8(demobuf.p, ghostext[playernum].bumpers); + WRITEINT32(demobuf.p, ghostext[playernum].health); } if (ghostext[playernum].flags & EZT_STATDATA) { @@ -1065,7 +1066,7 @@ void G_ConsGhostTic(INT32 playernum) { ghostext[playernum].itemtype = READSINT8(demobuf.p); ghostext[playernum].itemamount = READUINT8(demobuf.p); - ghostext[playernum].bumpers = READUINT8(demobuf.p); + ghostext[playernum].health = READINT32(demobuf.p); } if (xziptic & EZT_STATDATA) { @@ -1135,7 +1136,7 @@ void G_ConsGhostTic(INT32 playernum) if (players[playernum].itemtype != ghostext[playernum].itemtype || players[playernum].itemamount != ghostext[playernum].itemamount - || players[playernum].bumpers != ghostext[playernum].bumpers) + || testmo->health < ghostext[playernum].health) { if (demosynced) CONS_Alert(CONS_WARNING, M_GetText("Demo playback has desynced (item/bumpers)!\n")); @@ -1143,7 +1144,7 @@ void G_ConsGhostTic(INT32 playernum) players[playernum].itemtype = ghostext[playernum].itemtype; players[playernum].itemamount = ghostext[playernum].itemamount; - players[playernum].bumpers = ghostext[playernum].bumpers; + testmo->health = ghostext[playernum].health; } if (players[playernum].kartspeed != ghostext[playernum].kartspeed diff --git a/src/g_game.c b/src/g_game.c index efa48408a..d0d55d964 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -523,7 +523,7 @@ void G_UpdateTimeStickerMedals(UINT16 map, boolean showownrecord) break; } case ET_MAP: - { + { if (emblem->flags & ME_SPBATTACK && cv_dummyspbattack.value) break; goto bademblem; @@ -873,32 +873,49 @@ INT16 G_SoftwareClipAimingPitch(INT32 *aiming) return (INT16)((*aiming)>>16); } -// Default controls for keyboard. These are hardcoded and cannot be changed. -static INT32 keyboardMenuDefaults[][2] = { - {gc_a, KEY_ENTER}, - {gc_c, KEY_BACKSPACE}, - {gc_x, KEY_ESCAPE}, - {gc_left, KEY_LEFTARROW}, - {gc_right, KEY_RIGHTARROW}, - {gc_up, KEY_UPARROW}, - {gc_down, KEY_DOWNARROW}, +static INT32 G_GetValueFromControlTable(INT32 deviceID, INT32 deadzone, INT32 *controltable) +{ + INT32 i; - // special control - {gc_start, KEY_ESCAPE}, - // 8 total controls* -}; + if (deviceID <= UNASSIGNED_DEVICE) + { + // An invalid device can't have any binds! + return 0; + } -#define KEYBOARDDEFAULTSSPLIT 7 + for (i = 0; i < MAXINPUTMAPPING; i++) + { + INT32 key = controltable[i]; + INT32 value = 0; + // Invalid key number. + if (G_KeyIsAvailable(key, deviceID) == false) + { + continue; + } + + value = G_GetDeviceGameKeyDownArray(deviceID)[key]; + + if (value >= deadzone) + { + return value; + } + } + + // Not pressed. + return 0; +} INT32 G_PlayerInputAnalog(UINT8 p, INT32 gc, UINT8 menuPlayers) { - INT32 deviceID; - INT32 i, j; - INT32 deadzone = 0; - boolean trydefaults = true; - boolean tryingotherID = false; - INT32 *controltable = &(gamecontrol[p][gc][0]); + const INT32 deadzone = (JOYAXISRANGE * cv_deadzone[p].value) / FRACUNIT; + const INT32 keyboard_player = G_GetPlayerForDevice(KEYBOARD_MOUSE_DEVICE); + const boolean in_menu = (menuPlayers > 0); + const boolean main_player = (p == 0); + INT32 deviceID = UNASSIGNED_DEVICE; + INT32 value = -1; + INT32 avail_gamepad_id = 0; + INT32 i; if (p >= MAXSPLITSCREENPLAYERS) { @@ -908,118 +925,96 @@ INT32 G_PlayerInputAnalog(UINT8 p, INT32 gc, UINT8 menuPlayers) return 0; } - deadzone = (JOYAXISRANGE * cv_deadzone[p].value) / FRACUNIT; + deviceID = G_GetDeviceForPlayer(p); - deviceID = cv_usejoystick[p].value; - -retrygetcontrol: - for (i = 0; i < MAXINPUTMAPPING; i++) + if ((in_menu == true && G_KeyBindIsNecessary(gc) == true) // In menu: check for all unoverrideable menu default controls. + || (in_menu == false && gc == gc_start)) // In gameplay: check for the unoverrideable start button to be able to bring up the menu. { - INT32 key = controltable[i]; - INT32 menukey = KEY_NULL; - INT32 value = 0; - boolean processinput = true; - - - // for menus, keyboards have defaults! - if (deviceID == 0) + value = G_GetValueFromControlTable(KEYBOARD_MOUSE_DEVICE, JOYAXISRANGE/4, &(menucontrolreserved[gc][0])); + if (value > 0) // Check for press instead of bound. { - - // In menus, check indexes 0 through 5 (everything besides gc_start) - // Outside of menus, only consider the hardcoded input for gc_start at index 6 - - INT32 maxj = menuactive ? KEYBOARDDEFAULTSSPLIT : KEYBOARDDEFAULTSSPLIT+1; - j = (!menuactive) ? KEYBOARDDEFAULTSSPLIT : 0; - - for (; j < maxj; j++) // check keyboardMenuDefaults - { - // the gc we're looking for - if (gc == keyboardMenuDefaults[j][0]) - { - menukey = keyboardMenuDefaults[j][1]; - break; - } - - // The key is mapped to *something else*...? - // Then don't process that as it would conflict with our hardcoded inputs. - else if (key == keyboardMenuDefaults[j][1]) - { - processinput = false; - break; - } - } - } - - // Invalid key number. - if (!G_KeyIsAvailable(key, deviceID) && !G_KeyIsAvailable(menukey, deviceID)) - { - continue; - } - - if (processinput) - { - // 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]; - - if (value >= deadzone) + // This is only intended for P1. + if (main_player == true) { return value; } + else + { + return 0; + } } } - // 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) + // Player 1 is always allowed to use the keyboard in 1P, even if they got disconnected. + if (main_player == true && keyboard_player == -1 && deviceID == UNASSIGNED_DEVICE) { - deviceID = 0; - goto retrygetcontrol; + deviceID = KEYBOARD_MOUSE_DEVICE; } - if (menuPlayers == 0) + // First, try our actual binds. + value = G_GetValueFromControlTable(deviceID, deadzone, &(gamecontrol[p][gc][0])); + if (value > 0) { - return 0; + return value; } - // We don't want menus to become unnavigable if people unbind - // all of their controls, so we do several things in this scenario. - // First: try other controllers. - - if (!tryingotherID) + // If you're on gamepad in 1P, and you didn't have a gamepad bind for this, then try your keyboard binds. + if (main_player == true && keyboard_player == -1 && deviceID > KEYBOARD_MOUSE_DEVICE) { - deviceID = MAXDEVICES; - tryingotherID = true; - } -loweringid: - deviceID--; - if (deviceID > 0) - { - for (i = 0; i < menuPlayers; i++) + value = G_GetValueFromControlTable(KEYBOARD_MOUSE_DEVICE, deadzone, &(gamecontrol[p][gc][0])); + if (value > 0) { - if (deviceID != cv_usejoystick[i].value) - continue; - // Controller taken? Try again... - goto loweringid; + return value; } - goto retrygetcontrol; } - if (trydefaults && G_KeyBindIsNecessary(gc)) + if (in_menu == true) { - // If we still haven't found anything and the keybind is necessary, - // try it all again but with default binds. - trydefaults = false; - controltable = &(gamecontroldefault[gc][0]); - tryingotherID = false; - deviceID = cv_usejoystick[p].value; - goto retrygetcontrol; + if (main_player == true) + { + // We are P1 controlling menus. We should be able to + // control the menu with any unused gamepads, so + // that gamepads are able to navigate to the player + // setup menu in the first place. + for (avail_gamepad_id = 0; avail_gamepad_id < G_GetNumAvailableGamepads(); avail_gamepad_id++) + { + INT32 tryDevice = G_GetAvailableGamepadDevice(avail_gamepad_id); + if (tryDevice <= KEYBOARD_MOUSE_DEVICE) + { + continue; + } + for (i = 0; i < menuPlayers; i++) + { + if (tryDevice == G_GetDeviceForPlayer(i)) + { + // Don't do this for already taken devices. + break; + } + } + + if (i == menuPlayers) + { + // This gamepad isn't being used, so we can + // use it for P1 menu navigation. + value = G_GetValueFromControlTable(tryDevice, deadzone, &(gamecontrol[p][gc][0])); + if (value > 0) + { + return value; + } + } + } + } + + // Still nothing bound after everything. Try default gamepad controls. + value = G_GetValueFromControlTable(deviceID, deadzone, &(gamecontroldefault[gc][0])); + if (value > 0) + { + return value; + } } + // Literally not bound at all, so it can't be pressed at all. return 0; } @@ -1542,7 +1537,7 @@ void G_DoLoadLevelEx(boolean resetplayer, gamestate_t newstate) // 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(); @@ -1862,7 +1857,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: @@ -1897,13 +1892,6 @@ boolean G_CouldView(INT32 playernum) if (( player->pflags & PF_NOCONTEST )) return false; - // I don't know if we want this actually, but I'll humor the suggestion anyway - if ((gametyperules & GTR_BUMPERS) && !demo.playback) - { - if (player->bumpers <= 0) - return false; - } - // SRB2Kart: we have no team-based modes, YET... if (G_GametypeHasTeams()) { @@ -2145,7 +2133,19 @@ void G_Ticker(boolean run) { if (playeringame[i]) { - K_PlayerLoseLife(&players[i]); + if (players[i].bot == true && grandprixinfo.gp == true && grandprixinfo.masterbots == false) + { + players[i].botvars.difficulty--; + + if (players[i].botvars.difficulty < 1) + { + players[i].botvars.difficulty = 1; + } + } + else + { + K_PlayerLoseLife(&players[i]); + } } } @@ -2421,7 +2421,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) INT32 itemtype; INT32 itemamount; INT32 growshrinktimer; - INT32 bumper; boolean songcredit = false; UINT16 nocontrol; INT32 khudfault; @@ -2496,7 +2495,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) itemtype = 0; itemamount = 0; growshrinktimer = 0; - bumper = ((gametyperules & GTR_BUMPERS) ? K_StartingBumperCount() : 0); if (gametyperules & GTR_SPHERES) { rings = 0; @@ -2543,7 +2541,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) else growshrinktimer = 0; - bumper = players[player].bumpers; rings = players[player].rings; spheres = players[player].spheres; kickstartaccel = players[player].kickstartaccel; @@ -2644,9 +2641,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->itemtype = itemtype; p->itemamount = itemamount; p->growshrinktimer = growshrinktimer; - p->bumpers = bumper; - p->karmadelay = comebacktime; - p->overtimekarma = 0; + p->karmadelay = 0; p->eggmanblame = -1; p->lastdraft = -1; p->karthud[khud_fault] = khudfault; diff --git a/src/g_input.c b/src/g_input.c index 119eaa834..bdb6bbb9a 100644 --- a/src/g_input.c +++ b/src/g_input.c @@ -19,6 +19,9 @@ #include "d_net.h" #include "console.h" #include "i_joy.h" // JOYAXISRANGE +#include "r_draw.h" // GTC_ macros for assigning gamepad indicator colors +#include "v_video.h" // V_GetColor for assigning gamepad indictaor colors +#include "z_zone.h" #define MAXMOUSESENSITIVITY 100 // sensitivity steps @@ -35,11 +38,11 @@ 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]; INT32 gamecontroldefault[num_gamecontrols][MAXINPUTMAPPING]; // default control storage +INT32 menucontrolreserved[num_gamecontrols][MAXINPUTMAPPING]; // lists of GC codes for selective operation /* @@ -68,13 +71,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 +155,203 @@ 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); + } + } + } +} + +void G_SetPlayerGamepadIndicatorToPlayerColor(INT32 player) +{ + INT32 device; + INT32 skin; + UINT16 skincolor; + UINT8 *colormap; + byteColor_t byte_color; + + I_Assert(player >= 0 && player < MAXSPLITSCREENPLAYERS); + + device = G_GetDeviceForPlayer(player); + + if (device <= 0) + { + return; + } + + skin = cv_skin[player].value; + skincolor = cv_playercolor[player].value; + colormap = R_GetTranslationColormap(skin, skincolor, GTC_MENUCACHE); + + if (colormap == NULL) + { + return; + } + + byte_color = V_GetColor(colormap[104]).s; + + I_SetGamepadIndicatorColor(device, byte_color.red, byte_color.green, byte_color.blue); +} + +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 +363,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 +388,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 +410,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 +425,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 +468,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 +484,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 +500,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); } } } @@ -405,12 +683,13 @@ boolean G_KeyBindIsNecessary(INT32 gc) switch (gc) { case gc_a: - case gc_b: + case gc_c: + case gc_x: case gc_up: case gc_down: case gc_left: case gc_right: - case gc_start: + //case gc_start: // Is necessary, but handled special. return true; default: return false; @@ -421,25 +700,31 @@ boolean G_KeyBindIsNecessary(INT32 gc) // Returns false if a key is deemed unreachable for this device. boolean G_KeyIsAvailable(INT32 key, INT32 deviceID) { + boolean gamepad_key = false; + // Invalid key number. if (key <= 0 || key >= NUMINPUTS) { return false; } - // Valid controller-specific virtual key, but no controller attached for player. - if (key >= KEY_JOY1 && key < JOYINPUTEND && deviceID <= 0) + // Only allow gamepad keys for gamepad devices, + // and vice versa. + gamepad_key = (key >= KEY_JOY1 && key < JOYINPUTEND); + if (deviceID == KEYBOARD_MOUSE_DEVICE) { - return false; + if (gamepad_key == true) + { + return false; + } } - - // Valid mouse-specific virtual key, but no mouse attached for player. TODO HOW TO DETECT ACTIVE MOUSE CONNECTION - /* - if (key >= KEY_MOUSE1 && key < MOUSEINPUTEND && ????????) + else { - return false; + if (gamepad_key == false) + { + return false; + } } - */ return true; } @@ -538,7 +823,7 @@ void G_DefineDefaultControls(void) gamecontroldefault[gc_z ][0] = 'd'; gamecontroldefault[gc_l ][0] = 'q'; gamecontroldefault[gc_r ][0] = 'e'; - gamecontroldefault[gc_start ][0] = KEY_ESCAPE; // * + gamecontroldefault[gc_start ][0] = KEY_ESCAPE; gamecontroldefault[gc_rankings][0] = KEY_TAB; // Gamepad controls @@ -560,6 +845,16 @@ void G_DefineDefaultControls(void) gamecontroldefault[gc_down ][2] = KEY_AXIS1+3; // Axis Y+ gamecontroldefault[gc_left ][2] = KEY_AXIS1+0; // Axis X- gamecontroldefault[gc_right][2] = KEY_AXIS1+1; // Axis X+ + + // Menu reserved controls + menucontrolreserved[gc_up ][0] = KEY_UPARROW; + menucontrolreserved[gc_down ][0] = KEY_DOWNARROW; + menucontrolreserved[gc_left ][0] = KEY_LEFTARROW; + menucontrolreserved[gc_right][0] = KEY_RIGHTARROW; + menucontrolreserved[gc_a ][0] = KEY_ENTER; + menucontrolreserved[gc_c ][0] = KEY_BACKSPACE; + menucontrolreserved[gc_x ][0] = KEY_ESCAPE; + menucontrolreserved[gc_start][0] = KEY_ESCAPE; // Handled special } void G_CopyControls(INT32 (*setupcontrols)[MAXINPUTMAPPING], INT32 (*fromcontrols)[MAXINPUTMAPPING], const INT32 *gclist, INT32 gclen) diff --git a/src/g_input.h b/src/g_input.h index c99260ceb..610c3afdc 100644 --- a/src/g_input.h +++ b/src/g_input.h @@ -107,12 +107,14 @@ 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) +#define UNASSIGNED_DEVICE (-1) 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]; extern INT32 gamecontroldefault[num_gamecontrols][MAXINPUTMAPPING]; // default control storage +extern INT32 menucontrolreserved[num_gamecontrols][MAXINPUTMAPPING]; /* #define num_gcl_accelerate 1 @@ -135,7 +137,33 @@ 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); + +void G_SetPlayerGamepadIndicatorToPlayerColor(INT32 player); + +/// 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_sound.h b/src/i_sound.h index 7df4b5346..7bca60cc1 100644 --- a/src/i_sound.h +++ b/src/i_sound.h @@ -214,7 +214,8 @@ void I_PauseSong(void); */ void I_ResumeSong(void); -/** \brief The I_SetMusicVolume function +/** \brief Sets the volume of the Music mixing channel. Distinguished from the song's individual volume. The scale of + the volume is determined by the interface implementation. \param volume volume to set at @@ -222,6 +223,13 @@ void I_ResumeSong(void); */ void I_SetMusicVolume(int volume); +/** \brief Sets the current song's volume, independent of the overall music channel volume. The volume scale is 0-100, + * as a linear gain multiplier. This is distinguished from SetMusicVolume which may or may not be linear. +*/ +void I_SetCurrentSongVolume(int volume); + +// TODO refactor fades to control Song Volume exclusively in tandem with RR musicdef volume multiplier. + boolean I_SetSongTrack(INT32 track); /// ------------------------ 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/info.c b/src/info.c index 60516c822..b462a8ba9 100644 --- a/src/info.c +++ b/src/info.c @@ -23907,7 +23907,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 16, // mass 0, // damage sfx_None, // activesound - MF_SOLID|MF_NOCLIP|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags + MF_SOLID|MF_NOCLIP|MF_NOCLIPTHING|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags S_NULL // raisestate }, diff --git a/src/k_battle.c b/src/k_battle.c index 3f333180d..456d3c6ab 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -39,7 +39,7 @@ UINT8 numtargets = 0; // Capsules busted INT32 K_StartingBumperCount(void) { if (battleprisons) - return 1; // always 1 hit in Break the Capsules + return 0; // always 1 hit in Prison Break return cv_kartbumpers.value; } @@ -95,8 +95,6 @@ void K_CheckBumpers(void) UINT8 nobumpers = 0; UINT8 eliminated = 0; - const boolean singleplayer = (battleprisons || bossinfo.valid); - if (!(gametyperules & GTR_BUMPERS)) return; @@ -113,7 +111,7 @@ void K_CheckBumpers(void) numingame++; - if (players[i].bumpers <= 0) // if you don't have any bumpers, you're probably not a winner + if (!P_MobjWasRemoved(players[i].mo) && players[i].mo->health <= 0) // if you don't have any bumpers, you're probably not a winner { nobumpers++; } @@ -124,7 +122,7 @@ void K_CheckBumpers(void) } } - if (singleplayer + if (K_Cooperative() ? nobumpers > 0 && nobumpers >= numingame : eliminated >= numingame - 1) { @@ -135,7 +133,7 @@ void K_CheckBumpers(void) if (players[i].spectator) continue; - if (singleplayer) + if (K_Cooperative()) players[i].pflags |= PF_NOCONTEST; P_DoPlayerExit(&players[i]); @@ -170,7 +168,7 @@ void K_CheckEmeralds(player_t *player) return; } - player->roundscore++; // lol + player->roundscore = 100; // lmao for (i = 0; i < MAXPLAYERS; i++) { @@ -179,11 +177,6 @@ void K_CheckEmeralds(player_t *player) continue; } - if (&players[i] == player) - { - continue; - } - P_DoPlayerExit(&players[i]); } } @@ -319,6 +312,21 @@ static inline boolean IsOnInterval(tic_t interval) return ((leveltime - starttime) % interval) == 0; } +static UINT32 CountEmeraldsSpawned(const mobj_t *mo) +{ + switch (mo->type) + { + case MT_EMERALD: + return mo->extravalue1; + + case MT_MONITOR: + return Obj_MonitorGetEmerald(mo); + + default: + return 0U; + } +} + void K_RunPaperItemSpawners(void) { const boolean overtime = (battleovertime.enabled >= 10*TICRATE); @@ -362,7 +370,7 @@ void K_RunPaperItemSpawners(void) emeraldsSpawned |= players[i].emeralds; if ((players[i].exiting > 0 || (players[i].pflags & PF_ELIMINATED)) - || ((gametyperules & GTR_BUMPERS) && players[i].bumpers <= 0)) + || ((gametyperules & GTR_BUMPERS) && !P_MobjWasRemoved(players[i].mo) && players[i].mo->health <= 0)) { continue; } @@ -382,10 +390,7 @@ void K_RunPaperItemSpawners(void) mo = (mobj_t *)th; - if (mo->type == MT_EMERALD) - { - emeraldsSpawned |= mo->extravalue1; - } + emeraldsSpawned |= CountEmeraldsSpawned(mo); } if (canmakeemeralds) @@ -444,15 +449,7 @@ void K_RunPaperItemSpawners(void) mo = (mobj_t *)th; - if (mo->type == MT_EMERALD) - { - emeraldsSpawned |= mo->extravalue1; - } - - if (mo->type == MT_MONITOR) - { - emeraldsSpawned |= Obj_MonitorGetEmerald(mo); - } + emeraldsSpawned |= CountEmeraldsSpawned(mo); if (mo->type != MT_PAPERITEMSPOT) continue; @@ -738,16 +735,20 @@ void K_SetupMovingCapsule(mapthing_t *mt, mobj_t *mobj) void K_SpawnPlayerBattleBumpers(player_t *p) { - if (!p->mo || p->bumpers <= 0) + const UINT8 bumpers = K_Bumpers(p); + + if (bumpers <= 0) + { return; + } { INT32 i; - angle_t diff = FixedAngle(360*FRACUNIT/p->bumpers); + angle_t diff = FixedAngle(360*FRACUNIT / bumpers); angle_t newangle = p->mo->angle; mobj_t *bump; - for (i = 0; i < p->bumpers; i++) + for (i = 0; i < bumpers; i++) { bump = P_SpawnMobjFromMobj(p->mo, P_ReturnThrustX(p->mo, newangle + ANGLE_180, 64*FRACUNIT), @@ -784,21 +785,49 @@ void K_BattleInit(boolean singleplayercontext) if (gametyperules & GTR_BUMPERS) { - INT32 maxbumpers = K_StartingBumperCount(); + const INT32 startingHealth = K_BumpersToHealth(K_StartingBumperCount()); for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator) continue; - players[i].bumpers = maxbumpers; - if (players[i].mo) { - players[i].mo->health = maxbumpers; + players[i].mo->health = startingHealth; } K_SpawnPlayerBattleBumpers(players+i); } } } + +UINT8 K_Bumpers(player_t *player) +{ + if ((gametyperules & GTR_BUMPERS) == 0) + { + return 0; + } + + if (P_MobjWasRemoved(player->mo)) + { + return 0; + } + + if (player->mo->health < 1) + { + return 0; + } + + if (player->mo->health > UINT8_MAX) + { + return UINT8_MAX; + } + + return (player->mo->health - 1); +} + +INT32 K_BumpersToHealth(UINT8 bumpers) +{ + return (bumpers + 1); +} diff --git a/src/k_battle.h b/src/k_battle.h index b1957d9b1..4e1dda2c8 100644 --- a/src/k_battle.h +++ b/src/k_battle.h @@ -38,6 +38,8 @@ void K_RunBattleOvertime(void); void K_SetupMovingCapsule(mapthing_t *mt, mobj_t *mobj); void K_SpawnPlayerBattleBumpers(player_t *p); void K_BattleInit(boolean singleplayercontext); +UINT8 K_Bumpers(player_t *player); +INT32 K_BumpersToHealth(UINT8 bumpers); #ifdef __cplusplus } // extern "C" diff --git a/src/k_collide.c b/src/k_collide.c index 0116f0d49..3939ea099 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -43,9 +43,6 @@ boolean K_BananaBallhogCollide(mobj_t *t1, mobj_t *t2) { boolean damageitem = false; - if ((t1->threshold > 0 && t2->hitlag > 0) || (t2->threshold > 0 && t1->hitlag > 0)) - return true; - if (((t1->target == t2) || (!(t2->flags & (MF_ENEMY|MF_BOSS)) && (t1->target == t2->target))) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0))) return true; @@ -133,9 +130,6 @@ boolean K_BananaBallhogCollide(mobj_t *t1, mobj_t *t2) boolean K_EggItemCollide(mobj_t *t1, mobj_t *t2) { - if ((t1->threshold > 0 && t2->hitlag > 0) || (t2->threshold > 0 && t1->hitlag > 0)) - return true; - // Push fakes out of other item boxes if (t2->type == MT_RANDOMITEM || t2->type == MT_EGGMANITEM) { @@ -154,14 +148,7 @@ boolean K_EggItemCollide(mobj_t *t1, mobj_t *t2) if (!P_CanPickupItem(t2->player, 2)) return true; - if ((gametyperules & GTR_BUMPERS) && t2->player->bumpers <= 0) - { - return true; - } - else - { - K_StartEggmanRoulette(t2->player); - } + K_StartEggmanRoulette(t2->player); if (t2->player->flamedash && t2->player->itemtype == KITEM_FLAMESHIELD) { @@ -182,10 +169,7 @@ boolean K_EggItemCollide(mobj_t *t1, mobj_t *t2) if (t1->target && t1->target->player) { - if ((gametyperules & GTR_CIRCUIT) || t1->target->player->bumpers > 0) - t2->player->eggmanblame = t1->target->player-players; - else - t2->player->eggmanblame = t2->player-players; + t2->player->eggmanblame = t1->target->player - players; if (t1->target->hnext == t1) { @@ -323,6 +307,10 @@ tic_t K_MineExplodeAttack(mobj_t *actor, fixed_t size, boolean spin) // Set this flag to ensure that the inital action won't be triggered twice. actor->flags2 |= MF2_DEBRIS; + // Set this flag to ensure the hitbox timer doesn't get extended with every player hit + actor->flags |= MF_NOHITLAGFORME; + actor->hitlag = 0; // same deal + if (!spin) { if (minehitlag == 0) @@ -340,9 +328,6 @@ tic_t K_MineExplodeAttack(mobj_t *actor, fixed_t size, boolean spin) boolean K_MineCollide(mobj_t *t1, mobj_t *t2) { - if ((t1->threshold > 0 && t2->hitlag > 0) || (t2->threshold > 0 && t1->hitlag > 0)) - return true; - if (((t1->target == t2) || (!(t2->flags & (MF_ENEMY|MF_BOSS)) && (t1->target == t2->target))) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0))) return true; @@ -394,9 +379,6 @@ boolean K_MineCollide(mobj_t *t1, mobj_t *t2) boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2) { - if ((t1->threshold > 0 && t2->hitlag > 0) || (t2->threshold > 0 && t1->hitlag > 0)) - return true; - if (((t1->target == t2) || (!(t2->flags & (MF_ENEMY|MF_BOSS)) && (t1->target == t2->target))) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0))) return true; @@ -483,9 +465,6 @@ boolean K_DropTargetCollide(mobj_t *t1, mobj_t *t2) { mobj_t *draggeddroptarget = (t1->type == MT_DROPTARGET_SHIELD) ? t1->target : NULL; - if ((t1->threshold > 0 && t2->hitlag > 0) || (t2->threshold > 0 && t1->hitlag > 0)) - return true; - if (((t1->target == t2) || (t1->target == t2->target)) && ((t1->threshold > 0 && t2->type == MT_PLAYER) || (t2->type != MT_PLAYER && t2->threshold > 0))) return true; @@ -696,6 +675,17 @@ void K_LightningShieldAttack(mobj_t *actor, fixed_t size) boolean K_BubbleShieldCollide(mobj_t *t1, mobj_t *t2) { + if (t1->type == MT_PLAYER) + { + // Bubble Shield already has a hitbox, and it gets + // teleported every tic so the Bubble itself will + // always make contact with other objects. + // + // Therefore, we don't need a second, smaller hitbox + // on the player. It'll just cause unwanted hitlag. + return true; + } + if (t2->type == MT_PLAYER) { // Counter desyncs @@ -715,8 +705,13 @@ boolean K_BubbleShieldCollide(mobj_t *t1, mobj_t *t2) } // Player Damage - P_DamageMobj(t2, ((t1->type == MT_BUBBLESHIELD) ? t1->target : t1), t1, 1, DMG_NORMAL|DMG_WOMBO); - S_StartSound(t1, sfx_s3k44); + P_DamageMobj(t2, t1->target, t1, 1, DMG_NORMAL|DMG_WOMBO); + + if (t2->player->timeshit > t2->player->timeshitprev) + { + // Don't play from t1 else it gets cut out... for some reason. + S_StartSound(t2, sfx_s3k44); + } } else { @@ -746,9 +741,6 @@ boolean K_BubbleShieldCollide(mobj_t *t1, mobj_t *t2) boolean K_KitchenSinkCollide(mobj_t *t1, mobj_t *t2) { - if ((t1->threshold > 0 && t2->hitlag > 0) || (t2->threshold > 0 && t1->hitlag > 0)) - return true; - if (((t1->target == t2) || (!(t2->flags & (MF_ENEMY|MF_BOSS)) && (t1->target == t2->target))) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0))) return true; diff --git a/src/k_hud.c b/src/k_hud.c index 720b205a5..ea47fb44d 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1995,10 +1995,12 @@ static boolean K_drawKartPositionFaces(void) if (LUA_HudEnabled(hud_battlebumpers)) { - if ((gametyperules & GTR_BUMPERS) && players[rankplayer[i]].bumpers > 0) + const UINT8 bumpers = K_Bumpers(&players[rankplayer[i]]); + + if (bumpers > 0) { V_DrawMappedPatch(bumperx-2, Y, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_tinybumper[0], colormap); - for (j = 1; j < players[rankplayer[i]].bumpers; j++) + for (j = 1; j < bumpers; j++) { bumperx += 5; V_DrawMappedPatch(bumperx, Y, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_tinybumper[1], colormap); @@ -2023,7 +2025,7 @@ static boolean K_drawKartPositionFaces(void) if (i == strank) V_DrawScaledPatch(FACE_X, Y, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_facehighlight[(leveltime / 4) % 8]); - if ((gametyperules & GTR_BUMPERS) && players[rankplayer[i]].bumpers <= 0) + if ((gametyperules & GTR_BUMPERS) && (players[rankplayer[i]].pflags & PF_ELIMINATED)) V_DrawScaledPatch(FACE_X-4, Y-3, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_ranknobumpers); else { @@ -2356,7 +2358,7 @@ void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, IN if (tab[i].num == whiteplayer) V_DrawScaledPatch(x, y-4, 0, kp_facehighlight[(leveltime / 4) % 8]); - if ((gametyperules & GTR_BUMPERS) && players[tab[i].num].bumpers <= 0) + if ((gametyperules & GTR_BUMPERS) && (players[tab[i].num].pflags & PF_ELIMINATED)) V_DrawScaledPatch(x-4, y-7, 0, kp_ranknobumpers); else { @@ -2866,14 +2868,16 @@ static void K_drawKartBumpersOrKarma(void) } else { - INT32 maxbumper = K_StartingBumperCount(); + const INT32 maxbumper = K_StartingBumperCount(); + const UINT8 bumpers = K_Bumpers(stplyr); + V_DrawMappedPatch(fx+1, fy-2, V_HUDTRANS|V_SLIDEIN|splitflags, kp_rankbumper, colormap); - if (stplyr->bumpers > 9 || maxbumper > 9) + if (bumpers > 9 || maxbumper > 9) { UINT8 ln[2]; - ln[0] = (stplyr->bumpers / 10 % 10); - ln[1] = (stplyr->bumpers % 10); + ln[0] = (bumpers / 10 % 10); + ln[1] = (bumpers % 10); V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); V_DrawScaledPatch(fx+17, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[ln[1]]); @@ -2886,7 +2890,7 @@ static void K_drawKartBumpersOrKarma(void) } else { - V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[(stplyr->bumpers) % 10]); + V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[(bumpers) % 10]); V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[(maxbumper) % 10]); } } @@ -2903,17 +2907,15 @@ static void K_drawKartBumpersOrKarma(void) } else { - INT32 maxbumper = K_StartingBumperCount(); + const INT32 maxbumper = K_StartingBumperCount(); + const UINT8 bumpers = K_Bumpers(stplyr); - if (stplyr->bumpers > 9 && maxbumper > 9) + if (bumpers > 9 && maxbumper > 9) V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|V_SLIDEIN|splitflags, kp_bumperstickerwide, colormap); else V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|V_SLIDEIN|splitflags, kp_bumpersticker, colormap); - if (gametyperules & GTR_KARMA) // TODO BETTER HUD - V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d %d", stplyr->bumpers, maxbumper, stplyr->overtimekarma / TICRATE)); - else - V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d", stplyr->bumpers, maxbumper)); + V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d", bumpers, maxbumper)); } } } @@ -3366,12 +3368,6 @@ static void K_drawKartNameTags(void) continue; } - if ((gametyperules & GTR_BUMPERS) && (ntplayer->bumpers <= 0)) - { - // Dead in Battle - continue; - } - if (!P_CheckSight(stplyr->mo, ntplayer->mo)) { // Can't see @@ -3644,6 +3640,10 @@ static void K_drawKartMinimap(void) if (!players[i].mo || players[i].spectator || !players[i].mo->skin || players[i].exiting) continue; + // This player is out of the game! + if ((gametyperules & GTR_BUMPERS) && (players[i].pflags & PF_ELIMINATED)) + continue; + if (i == displayplayers[0] || i == displayplayers[1] || i == displayplayers[2] || i == displayplayers[3]) { // Draw display players on top of everything else @@ -3651,10 +3651,6 @@ static void K_drawKartMinimap(void) continue; } - // Now we know it's not a display player, handle non-local player exceptions. - if ((gametyperules & GTR_BUMPERS) && players[i].bumpers <= 0) - continue; - if (players[i].hyudorotimer > 0) { if (!((players[i].hyudorotimer < TICRATE/2 @@ -3866,19 +3862,22 @@ static void K_drawKartFinish(boolean finish) //else -- 1/2p, scrolling FINISH { - INT32 x, xval, ox, interpx; + INT32 x, xval, ox, interpx, pwidth; x = ((vid.width<width)< x ? xval : x))/TICRATE; - ox = ((TICRATE - (timer - 1))*(xval > x ? xval : x))/TICRATE; + + pwidth = max(xval, x); + + x = ((TICRATE - timer) * pwidth) / TICRATE; + ox = ((TICRATE - (timer - 1)) * pwidth) / TICRATE; interpx = R_InterpolateFixed(ox, x); if (r_splitscreen && stplyr == &players[displayplayers[1]]) interpx = -interpx; - V_DrawFixedPatch(interpx + (STCD_X<>1), + V_DrawFixedPatch(interpx + (STCD_X<height)<<(FRACBITS-1)), FRACUNIT, splitflags, kptodraw[pnum], NULL); @@ -4160,7 +4159,7 @@ static void K_drawBattleFullscreen(void) K_drawKartFinish(true); } - else if (stplyr->bumpers <= 0 && stplyr->karmadelay && !stplyr->spectator && drawcomebacktimer) + else if (stplyr->karmadelay && !stplyr->spectator && drawcomebacktimer) { UINT16 t = stplyr->karmadelay/(10*TICRATE); INT32 txoff, adjust = (r_splitscreen > 1) ? 4 : 6; // normal string is 8, kart string is 12, half of that for ease @@ -4894,8 +4893,7 @@ void K_drawKartHUD(void) battlefullscreen = (!(gametyperules & GTR_CIRCUIT) && (stplyr->exiting - || ((gametyperules & GTR_BUMPERS) && (stplyr->bumpers <= 0) - && ((gametyperules & GTR_KARMA) && (stplyr->karmadelay > 0)) + || (((gametyperules & GTR_KARMA) && (stplyr->karmadelay > 0)) && !(stplyr->pflags & PF_ELIMINATED) && stplyr->playerstate == PST_LIVE))); diff --git a/src/k_hud_track.cpp b/src/k_hud_track.cpp index 7ae184c99..e6a7df1ed 100644 --- a/src/k_hud_track.cpp +++ b/src/k_hud_track.cpp @@ -5,8 +5,8 @@ #include "core/static_vec.hpp" #include "k_battle.h" -#include "k_boss.h" #include "k_hud.h" +#include "k_kart.h" #include "k_objects.h" #include "m_fixed.h" #include "p_local.h" @@ -314,7 +314,7 @@ bool is_player_tracking_target(player_t *player = stplyr) return false; } - if (battleprisons || bossinfo.valid) + if (K_Cooperative()) { return false; } @@ -338,6 +338,23 @@ bool is_player_tracking_target(player_t *player = stplyr) return false; } + if (player->emeralds != 0 && K_IsPlayerWanted(stplyr)) + { + // The player who is about to win because of emeralds + // gets a TARGET on them + if (K_NumEmeralds(player) == 6) // 6 out of 7 + { + return true; + } + + // WANTED player sees TARGETs on players holding + // emeralds + if (K_IsPlayerWanted(stplyr)) + { + return true; + } + } + return K_IsPlayerWanted(player); } diff --git a/src/k_kart.c b/src/k_kart.c index 34db8419e..ec83c0098 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -374,9 +374,6 @@ boolean K_IsPlayerLosing(player_t *player) if (battleprisons && numtargets == 0) return true; // Didn't even TRY? - if (battleprisons || (gametyperules & GTR_BOSS)) - return (player->bumpers <= 0); // anything short of DNF is COOL - if (player->position == 1) return false; @@ -3269,11 +3266,6 @@ fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower, boolean dorubberb { finalspeed = K_GetKartSpeedFromStat(player->kartspeed); - if (gametyperules & GTR_BUMPERS && player->bumpers <= 0) - { - finalspeed = 3 * finalspeed / 2; - } - if (player->spheres > 0) { fixed_t sphereAdd = (FRACUNIT/40); // 100% at max @@ -3333,12 +3325,6 @@ fixed_t K_GetKartAccel(player_t *player) return FixedMul(k_accel, FRACUNIT / 4); } - // karma bomb gets 2x acceleration - if ((gametyperules & GTR_BUMPERS) && player->bumpers <= 0) - { - k_accel *= 2; - } - // Marble Garden Top gets 1200% accel if (player->curshield == KSHIELD_TOP) { @@ -3654,7 +3640,7 @@ void K_DoPowerClash(player_t *t1, player_t *t2) { P_SetScale(clash, 3*clash->destscale/2); } -void K_BattleAwardHit(player_t *player, player_t *victim, mobj_t *inflictor, UINT8 bumpersRemoved) +void K_BattleAwardHit(player_t *player, player_t *victim, mobj_t *inflictor, UINT8 damage) { UINT8 points = 1; boolean trapItem = false; @@ -3692,7 +3678,7 @@ void K_BattleAwardHit(player_t *player, player_t *victim, mobj_t *inflictor, UIN } else if (gametyperules & GTR_BUMPERS) { - if ((victim->bumpers > 0) && (victim->bumpers <= bumpersRemoved)) + if ((victim->mo->health > 0) && (victim->mo->health <= damage)) { // +2 points for finishing off a player points = 2; @@ -4071,10 +4057,14 @@ void K_UpdateStumbleIndicator(player_t *player) } } +#define MIN_WAVEDASH_CHARGE (5*TICRATE/8) + static boolean K_IsLosingSliptideZip(player_t *player) { if (player->mo == NULL || P_MobjWasRemoved(player->mo) == true) return true; + if (!K_Sliptiding(player) && player->sliptideZip < MIN_WAVEDASH_CHARGE) + return true; if (!K_Sliptiding(player) && player->drift == 0 && P_IsObjectOnGround(player->mo) && player->sneakertimer == 0 && player->driftboost == 0) @@ -4341,81 +4331,22 @@ void K_DebtStingPlayer(player_t *player, mobj_t *source) P_SetPlayerMobjState(player->mo, S_KART_SPINOUT); } -void K_HandleBumperChanges(player_t *player, UINT8 prevBumpers) +void K_TakeBumpersFromPlayer(player_t *player, player_t *victim, UINT8 amount) { - if (!(gametyperules & GTR_BUMPERS)) - { - // Bumpers aren't being used - return; - } - - // TODO: replace all console text print-outs with a real visual - - if (player->bumpers > 0 && prevBumpers == 0) - { - K_DoInvincibility(player, 8 * TICRATE); - - if (netgame) - { - CONS_Printf(M_GetText("%s is back in the game!\n"), player_names[player-players]); - } - } - else if (player->bumpers == 0 && prevBumpers > 0) - { - if (battleprisons || bossinfo.valid) - { - player->pflags |= (PF_NOCONTEST|PF_ELIMINATED); - } - } - - K_CalculateBattleWanted(); - K_CheckBumpers(); -} - -UINT8 K_DestroyBumpers(player_t *player, UINT8 amount) -{ - UINT8 oldBumpers = player->bumpers; - - if (!(gametyperules & GTR_BUMPERS)) - { - return 0; - } - - amount = min(amount, player->bumpers); - - if (amount == 0) - { - return 0; - } - - player->bumpers -= amount; - K_HandleBumperChanges(player, oldBumpers); - - return amount; -} - -UINT8 K_TakeBumpersFromPlayer(player_t *player, player_t *victim, UINT8 amount) -{ - UINT8 oldPlayerBumpers = player->bumpers; - UINT8 oldVictimBumpers = victim->bumpers; + const UINT8 oldPlayerBumpers = K_Bumpers(player); UINT8 tookBumpers = 0; - if (!(gametyperules & GTR_BUMPERS)) - { - return 0; - } - - amount = min(amount, victim->bumpers); + amount = min(amount, K_Bumpers(victim)); if (amount == 0) { - return 0; + return; } - while ((tookBumpers < amount) && (victim->bumpers > 0)) + while (tookBumpers < amount) { - UINT8 newbumper = player->bumpers; + const UINT8 newbumper = (oldPlayerBumpers + tookBumpers); angle_t newangle, diff; fixed_t newx, newy; @@ -4457,24 +4388,14 @@ UINT8 K_TakeBumpersFromPlayer(player_t *player, player_t *victim, UINT8 amount) P_SetMobjState(newmo, S_BATTLEBUMPER1); } - player->bumpers++; - victim->bumpers--; tookBumpers++; } - if (tookBumpers == 0) - { - // No change occured. - return 0; - } + // :jartcookiedance: + player->mo->health += tookBumpers; // Play steal sound S_StartSound(player->mo, sfx_3db06); - - K_HandleBumperChanges(player, oldPlayerBumpers); - K_HandleBumperChanges(victim, oldVictimBumpers); - - return tookBumpers; } #define MINEQUAKEDIST 4096 @@ -5138,8 +5059,7 @@ void K_SpawnBoostTrail(player_t *player) I_Assert(!P_MobjWasRemoved(player->mo)); if (!P_IsObjectOnGround(player->mo) - || player->hyudorotimer != 0 - || ((gametyperules & GTR_BUMPERS) && player->bumpers <= 0 && player->karmadelay)) + || player->hyudorotimer != 0) return; if (player->mo->eflags & MFE_VERTICALFLIP) @@ -7098,12 +7018,6 @@ mobj_t *K_FindJawzTarget(mobj_t *actor, player_t *source, angle_t range) } else { - if (player->bumpers <= 0) - { - // Don't pay attention to dead players - continue; - } - // Z pos too high/low if (abs(player->mo->z - (actor->z + actor->momz)) > FixedMul(RING_DIST/8, mapobjectscale)) { @@ -7809,13 +7723,6 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->spheres = 40; // where's the < 0 check? see below the following block! - if ((gametyperules & GTR_BUMPERS) && (player->bumpers <= 0)) - { - // Deplete 1 every tic when removed from the game. - player->spheres--; - player->spheredigestion = 0; - } - else { tic_t spheredigestion = TICRATE; // Base rate of 1 every second when playing. tic_t digestionpower = ((10 - player->kartspeed) + (10 - player->kartweight))-1; // 1 to 17 @@ -7860,12 +7767,12 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (!(gametyperules & GTR_KARMA) || (player->pflags & PF_ELIMINATED)) { - player->karmadelay = comebacktime; + player->karmadelay = 0; } else if (player->karmadelay > 0 && !P_PlayerInPain(player)) { player->karmadelay--; - if (P_IsDisplayPlayer(player) && player->bumpers <= 0 && player->karmadelay <= 0) + if (P_IsDisplayPlayer(player) && player->karmadelay <= 0) comebackshowninfo = true; // client has already seen the message } @@ -8029,14 +7936,6 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) K_UpdateTripwire(player); - if (battleovertime.enabled && !(player->pflags & PF_ELIMINATED) && player->bumpers <= 0 && player->karmadelay <= 0) - { - if (player->overtimekarma) - player->overtimekarma--; - else - P_DamageMobj(player->mo, NULL, NULL, 1, DMG_TIMEOVER); - } - if ((battleovertime.enabled >= 10*TICRATE) && !(player->pflags & PF_ELIMINATED) && !player->exiting) { fixed_t distanceToBarrier = 0; @@ -8066,7 +7965,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->eggmanexplode) { - if (player->spectator || ((gametyperules & GTR_BUMPERS) && player->bumpers <= 0)) + if (player->spectator) player->eggmanexplode = 0; else { @@ -8407,30 +8306,14 @@ void K_KartPlayerAfterThink(player_t *player) K_LookForRings(player->mo); } - if (player->invulnhitlag > 0) + if (player->nullHitlag > 0) { - // Hitlag from what would normally be damage but the - // player was invulnerable. - // - // If we're constantly getting hit the same number of - // times, we're probably standing on a damage floor. - // - // Checking if we're hit more than before ensures - // that: - // - // 1) repeating damage doesn't count - // 2) new damage sources still count - - if (player->timeshit <= player->timeshitprev) + if (!P_MobjWasRemoved(player->mo)) { - if (!P_MobjWasRemoved(player->mo)) - { - player->mo->hitlag -= player->invulnhitlag; - player->mo->eflags &= ~(MFE_DAMAGEHITLAG); - } + player->mo->hitlag -= player->nullHitlag; } - player->invulnhitlag = 0; + player->nullHitlag = 0; } } @@ -9405,6 +9288,8 @@ static void K_KartDrift(player_t *player, boolean onground) K_SpawnAIZDust(player); player->sliptideZip++; + if (player->sliptideZip == MIN_WAVEDASH_CHARGE) + S_StartSound(player->mo, sfx_waved5); if (abs(player->aizdrifttilt) < ANGLE_22h) { @@ -9432,35 +9317,38 @@ static void K_KartDrift(player_t *player, boolean onground) player->sliptideZipDelay++; if (player->sliptideZipDelay > TICRATE/2) { - fixed_t maxZipPower = 2*FRACUNIT; - fixed_t minZipPower = 1*FRACUNIT; - fixed_t powerSpread = maxZipPower - minZipPower; + if (player->sliptideZip >= MIN_WAVEDASH_CHARGE) + { + fixed_t maxZipPower = 2*FRACUNIT; + fixed_t minZipPower = 1*FRACUNIT; + fixed_t powerSpread = maxZipPower - minZipPower; - int minPenalty = 2*1 + (9-9); // Kinda doing a similar thing to driftspark stage timers here. - int maxPenalty = 2*9 + (9-1); // 1/9 gets max, 9/1 gets min, everyone else gets something in between. - int penaltySpread = maxPenalty - minPenalty; - int yourPenalty = 2*player->kartspeed + (9 - player->kartweight); // And like driftsparks, speed hurts double. + int minPenalty = 2*1 + (9-9); // Kinda doing a similar thing to driftspark stage timers here. + int maxPenalty = 2*9 + (9-1); // 1/9 gets max, 9/1 gets min, everyone else gets something in between. + int penaltySpread = maxPenalty - minPenalty; + int yourPenalty = 2*player->kartspeed + (9 - player->kartweight); // And like driftsparks, speed hurts double. - yourPenalty -= minPenalty; // Normalize; minimum penalty should take away 0 power. + yourPenalty -= minPenalty; // Normalize; minimum penalty should take away 0 power. - fixed_t yourPowerReduction = FixedDiv(yourPenalty * FRACUNIT, penaltySpread * FRACUNIT); - fixed_t yourPower = maxZipPower - FixedMul(yourPowerReduction, powerSpread); - int yourBoost = FixedInt(FixedMul(yourPower, player->sliptideZip * FRACUNIT)); + fixed_t yourPowerReduction = FixedDiv(yourPenalty * FRACUNIT, penaltySpread * FRACUNIT); + fixed_t yourPower = maxZipPower - FixedMul(yourPowerReduction, powerSpread); + int yourBoost = FixedInt(FixedMul(yourPower, player->sliptideZip * FRACUNIT)); - /* - CONS_Printf("SZ %d MZ %d mZ %d pwS %d mP %d MP %d peS %d yPe %d yPR %d yPw %d yB %d\n", - player->sliptideZip, maxZipPower, minZipPower, powerSpread, minPenalty, maxPenalty, penaltySpread, yourPenalty, yourPowerReduction, yourPower, yourBoost); - */ + /* + CONS_Printf("SZ %d MZ %d mZ %d pwS %d mP %d MP %d peS %d yPe %d yPR %d yPw %d yB %d\n", + player->sliptideZip, maxZipPower, minZipPower, powerSpread, minPenalty, maxPenalty, penaltySpread, yourPenalty, yourPowerReduction, yourPower, yourBoost); + */ - player->sliptideZipBoost += yourBoost; + player->sliptideZipBoost += yourBoost; - K_SpawnDriftBoostExplosion(player, 0); - player->sliptideZip = 0; - player->sliptideZipDelay = 0; + K_SpawnDriftBoostExplosion(player, 0); + S_StartSoundAtVolume(player->mo, sfx_waved3, 2*255/3); // Boost + } S_StopSoundByID(player->mo, sfx_waved1); S_StopSoundByID(player->mo, sfx_waved2); S_StopSoundByID(player->mo, sfx_waved4); - S_StartSoundAtVolume(player->mo, sfx_waved3, 2*255/3); // Boost + player->sliptideZip = 0; + player->sliptideZipDelay = 0; } } else @@ -9586,7 +9474,7 @@ void K_KartUpdatePosition(player_t *player) else if (yourEmeralds == myEmeralds) { // Bumpers are the second tier tie breaker - if (players[i].bumpers > player->bumpers) + if (K_Bumpers(&players[i]) > K_Bumpers(player)) { position++; } @@ -11210,18 +11098,6 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->mo->renderflags &= ~RF_DONTDRAW; } - if (!(gametyperules & GTR_BUMPERS) || player->bumpers > 0) - { - player->mo->renderflags &= ~(RF_TRANSMASK|RF_BRIGHTMASK); - } - else // dead in match? you da bomb - { - K_DropItems(player); //K_StripItems(player); - K_StripOther(player); - player->mo->renderflags |= RF_GHOSTLY; - player->flashing = player->karmadelay; - } - if (player->trickpanel == 1) { const angle_t lr = ANGLE_45; @@ -11469,8 +11345,9 @@ void K_CheckSpectateStatus(void) P_SpectatorJoinGame(&players[respawnlist[i]]); } - // Reset the match if you're in an empty server - if (!mapreset && gamestate == GS_LEVEL && leveltime >= starttime && (numingame < 2 && numingame+i >= 2)) // use previous i value + // Reset the match when 2P joins 1P, DUEL mode + // Reset the match when 3P joins 1P and 2P, DUEL mode must be disabled + if (!mapreset && gamestate == GS_LEVEL && (numingame < 3 && numingame+i >= 2)) // use previous i value { S_ChangeMusicInternal("chalng", false); // COME ON mapreset = 3*TICRATE; // Even though only the server uses this for game logic, set for everyone for HUD @@ -11720,7 +11597,7 @@ UINT32 K_PointLimitForGametype(void) return cv_pointlimit.value; } - if (battleprisons || bossinfo.valid) + if (K_Cooperative()) { return 0; } @@ -11744,4 +11621,19 @@ UINT32 K_PointLimitForGametype(void) return ptsCap; } +boolean K_Cooperative(void) +{ + if (battleprisons) + { + return true; + } + + if (bossinfo.valid) + { + return true; + } + + return false; +} + //} diff --git a/src/k_kart.h b/src/k_kart.h index c789eb98d..b015c000e 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -104,9 +104,7 @@ void K_UpdateStumbleIndicator(player_t *player); void K_UpdateSliptideZipIndicator(player_t *player); INT32 K_ExplodePlayer(player_t *player, mobj_t *inflictor, mobj_t *source); void K_DebtStingPlayer(player_t *player, mobj_t *source); -void K_HandleBumperChanges(player_t *player, UINT8 prevBumpers); -UINT8 K_DestroyBumpers(player_t *player, UINT8 amount); -UINT8 K_TakeBumpersFromPlayer(player_t *player, player_t *victim, UINT8 amount); +void K_TakeBumpersFromPlayer(player_t *player, player_t *victim, UINT8 amount); void K_MineFlashScreen(mobj_t *source); void K_SpawnMineExplosion(mobj_t *source, UINT8 color, tic_t delay); void K_RunFinishLineBeam(void); @@ -210,6 +208,8 @@ void K_EggmanTransfer(player_t *source, player_t *victim); tic_t K_TimeLimitForGametype(void); UINT32 K_PointLimitForGametype(void); +boolean K_Cooperative(void); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 623d97116..826281bc7 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, ...); @@ -3500,11 +3493,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 a571ce37d..6602f74cd 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -240,6 +240,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/lua_playerlib.c b/src/lua_playerlib.c index 98203ed51..327641362 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -232,8 +232,8 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->spinouttimer); else if (fastcmp(field,"instashield")) lua_pushinteger(L, plr->instashield); - else if (fastcmp(field,"invulnhitlag")) - lua_pushinteger(L, plr->invulnhitlag); + else if (fastcmp(field,"nullHitlag")) + lua_pushinteger(L, plr->nullHitlag); else if (fastcmp(field,"wipeoutslow")) lua_pushinteger(L, plr->wipeoutslow); else if (fastcmp(field,"justbumped")) @@ -398,8 +398,6 @@ static int player_get(lua_State *L) plr->roundscore = luaL_checkinteger(L, 3); else if (fastcmp(field,"emeralds")) lua_pushinteger(L, plr->emeralds); - else if (fastcmp(field,"bumpers")) - lua_pushinteger(L, plr->bumpers); else if (fastcmp(field,"karmadelay")) lua_pushinteger(L, plr->karmadelay); else if (fastcmp(field,"spheres")) @@ -616,8 +614,8 @@ static int player_set(lua_State *L) plr->spinouttimer = luaL_checkinteger(L, 3); else if (fastcmp(field,"instashield")) plr->instashield = luaL_checkinteger(L, 3); - else if (fastcmp(field,"invulnhitlag")) - plr->invulnhitlag = luaL_checkinteger(L, 3); + else if (fastcmp(field,"nullHitlag")) + plr->nullHitlag = luaL_checkinteger(L, 3); else if (fastcmp(field,"wipeoutslow")) plr->wipeoutslow = luaL_checkinteger(L, 3); else if (fastcmp(field,"justbumped")) @@ -782,8 +780,6 @@ static int player_set(lua_State *L) lua_pushinteger(L, plr->roundscore); else if (fastcmp(field,"emeralds")) plr->emeralds = luaL_checkinteger(L, 3); - else if (fastcmp(field,"bumpers")) - plr->bumpers = luaL_checkinteger(L, 3); else if (fastcmp(field,"karmadelay")) plr->karmadelay = luaL_checkinteger(L, 3); else if (fastcmp(field,"spheres")) 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 d7d1d4e44..8c0d36601 100644 --- a/src/menus/play-char-select.c +++ b/src/menus/play-char-select.c @@ -381,10 +381,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); } } @@ -527,7 +527,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; @@ -541,6 +546,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. @@ -569,24 +575,38 @@ 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 (device == KEYBOARD_MOUSE_DEVICE && num != 0) + { + // Only player 1 can be assigned to the KBM device. + continue; + } + + 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) { @@ -594,14 +614,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); } @@ -617,7 +636,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; } } @@ -669,11 +688,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; } @@ -1479,22 +1495,18 @@ void M_CharacterSelectTick(void) else CV_StealthSet(&cv_follower[i], followers[setup_player[i].followern].name); CV_StealthSetValue(&cv_followercolor[i], setup_player[i].followercolor); + + G_SetPlayerGamepadIndicatorToPlayerColor(i); } 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_SetupPlayMenu(0); #endif - + } } else // In a game diff --git a/src/objects/orbinaut.c b/src/objects/orbinaut.c index 6f9ba3788..caed22068 100644 --- a/src/objects/orbinaut.c +++ b/src/objects/orbinaut.c @@ -167,12 +167,6 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) boolean tumbleitem = false; boolean sprung = false; - if ((orbinaut_selfdelay(t1) > 0 && t2->hitlag > 0) - || (orbinaut_selfdelay(t2) > 0 && t1->hitlag > 0)) - { - return true; - } - if (t1->health <= 0 || t2->health <= 0) { return true; diff --git a/src/p_enemy.c b/src/p_enemy.c index ccf5ff47a..9c457bdb5 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -13595,7 +13595,7 @@ void A_ReaperThinker(mobj_t *actor) continue; player = &players[i]; - if (player && player->mo && player->bumpers && player->score >= maxscore) + if (player && player->mo && K_Bumpers(player) && player->score >= maxscore) { targetplayermo = player->mo; maxscore = player->score; diff --git a/src/p_inter.c b/src/p_inter.c index 6b4ce2810..3e04b5c31 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -39,6 +39,7 @@ #include "p_spec.h" #include "k_objects.h" #include "k_roulette.h" +#include "k_boss.h" // CTF player names #define CTFTEAMCODE(pl) pl->ctfteam ? (pl->ctfteam == 1 ? "\x85" : "\x84") : "" @@ -114,13 +115,6 @@ boolean P_CanPickupItem(player_t *player, UINT8 weapon) if (player->exiting || mapreset || (player->pflags & PF_ELIMINATED)) return false; - if ((gametyperules & GTR_BUMPERS) // No bumpers in Match -#ifndef OTHERKARMAMODES - && !weapon -#endif - && player->bumpers <= 0) - return false; - if (weapon) { // Item slot already taken up @@ -285,9 +279,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (!P_CanPickupItem(player, 3) || (player->itemamount && player->itemtype != special->threshold)) return; - if ((gametyperules & GTR_BUMPERS) && player->bumpers <= 0) - return; - player->itemtype = special->threshold; if ((UINT16)(player->itemamount) + special->movecount > 255) player->itemamount = 255; @@ -320,9 +311,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) P_KillMobj(special, toucher, toucher, DMG_NORMAL); return; case MT_ITEMCAPSULE: - if ((gametyperules & GTR_BUMPERS) && player->bumpers <= 0) - return; - if (special->scale < special->extravalue1) // don't break it while it's respawning return; @@ -350,8 +338,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) return; if (player == special->target->player) return; - if (player->bumpers <= 0) - return; if (special->target->player->exiting || player->exiting) return; @@ -452,7 +438,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) S_StartSound(special, sfx_s1a2); return; case MT_CDUFO: // SRB2kart - if (special->fuse || !P_CanPickupItem(player, 1) || ((gametyperules & GTR_BUMPERS) && player->bumpers <= 0)) + if (special->fuse || !P_CanPickupItem(player, 1)) return; K_StartItemRoulette(player); @@ -1395,6 +1381,11 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget P_PlayDeathSound(target); } + + if (K_Cooperative()) + { + target->player->pflags |= (PF_NOCONTEST|PF_ELIMINATED); + } break; case MT_METALSONIC_RACE: @@ -1937,18 +1928,30 @@ static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, switch (type) { case DMG_DEATHPIT: - // Respawn kill types + // Fell off the stage if (player->roundconditions.fell_off == true) { player->roundconditions.fell_off = true; player->roundconditions.checkthisframe = true; } - K_DoIngameRespawn(player); - player->mo->health -= K_DestroyBumpers(player, 1); - return false; + + if (gametyperules & GTR_BUMPERS) + { + player->mo->health--; + } + + if (player->mo->health <= 0) + { + return true; + } + + // Quick respawn; does not kill + return K_DoIngameRespawn(player), false; + case DMG_SPECTATOR: // disappearifies, but still gotta put items back in play break; + default: // Everything else REALLY kills if (leveltime < starttime) @@ -2004,11 +2007,46 @@ static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, player->pflags |= PF_ELIMINATED; } - K_DestroyBumpers(player, player->bumpers); - return true; } +static void AddTimesHit(player_t *player) +{ + const INT32 oldtimeshit = player->timeshit; + + player->timeshit++; + + // overflow prevention + if (player->timeshit < oldtimeshit) + { + player->timeshit = oldtimeshit; + } +} + +static void AddNullHitlag(player_t *player, tic_t oldHitlag) +{ + if (player == NULL) + { + return; + } + + // Hitlag from what would normally be damage but the + // player was invulnerable. + // + // If we're constantly getting hit the same number of + // times, we're probably standing on a damage floor. + // + // Checking if we're hit more than before ensures that: + // + // 1) repeating damage doesn't count + // 2) new damage sources still count + + if (player->timeshit <= player->timeshitprev) + { + player->nullHitlag += (player->mo->hitlag - oldHitlag); + } +} + /** Damages an object, which may or may not be a player. * For melee attacks, source and inflictor are the same. * @@ -2029,6 +2067,7 @@ static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype) { player_t *player; + player_t *playerInflictor; boolean force = false; boolean spbpop = false; @@ -2097,9 +2136,6 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da if (!(target->flags & MF_SHOOTABLE)) return false; // shouldn't happen... } - - if (!(damagetype & DMG_DEATHMASK) && (target->eflags & MFE_PAUSED)) - return false; } if (target->flags2 & MF2_SKULLFLY) @@ -2118,20 +2154,16 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da } player = target->player; + playerInflictor = inflictor ? inflictor->player : NULL; + + if (playerInflictor) + { + AddTimesHit(playerInflictor); + } if (player) // Player is the target { - { - const INT32 oldtimeshit = player->timeshit; - - player->timeshit++; - - // overflow prevention - if (player->timeshit < oldtimeshit) - { - player->timeshit = oldtimeshit; - } - } + AddTimesHit(player); if (player->pflags & PF_GODMODE) return false; @@ -2174,9 +2206,6 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da const boolean hardhit = (type == DMG_EXPLODE || type == DMG_KARMA || type == DMG_TUMBLE); // This damage type can do evil stuff like ALWAYS combo INT16 ringburst = 5; - // Do not die from damage outside of bumpers health system - damage = 0; - // Check if the player is allowed to be damaged! // If not, then spawn the instashield effect instead. if (!force) @@ -2184,16 +2213,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da boolean invincible = true; sfxenum_t sfx = sfx_None; - if (gametyperules & GTR_BUMPERS) - { - if (player->bumpers <= 0 && player->karmadelay) - { - // No bumpers & in WAIT, can't be hurt - K_DoInstashield(player); - return false; - } - } - else + if (!(gametyperules & GTR_BUMPERS)) { if (damagetype & DMG_STEAL) { @@ -2220,12 +2240,32 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da if (invincible && type != DMG_STUMBLE) { - const INT32 oldhitlag = target->hitlag; + const INT32 oldHitlag = target->hitlag; + const INT32 oldHitlagInflictor = inflictor ? inflictor->hitlag : 0; + + // Damage during hitlag should be a no-op + // for invincibility states because there + // are no flashing tics. If the damage is + // from a constant source, a deadlock + // would occur. + + if (target->eflags & MFE_PAUSED) + { + player->timeshit--; // doesn't count + + if (playerInflictor) + { + playerInflictor->timeshit--; + } + + return false; + } laglength = max(laglength / 2, 1); K_SetHitLagForObjects(target, inflictor, laglength, false); - player->invulnhitlag += (target->hitlag - oldhitlag); + AddNullHitlag(player, oldHitlag); + AddNullHitlag(playerInflictor, oldHitlagInflictor); if (player->timeshit > player->timeshitprev) { @@ -2255,6 +2295,11 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da allowcombo = false; } + if (allowcombo == false && (target->eflags & MFE_PAUSED)) + { + return false; + } + // DMG_EXPLODE excluded from flashtic checks to prevent dodging eggbox/SPB with weak spinout if ((target->hitlag == 0 || allowcombo == false) && player->flashing > 0 && type != DMG_EXPLODE && type != DMG_STUMBLE) { @@ -2262,31 +2307,35 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da K_DoInstashield(player); return false; } + else if (target->flags2 & MF2_ALREADYHIT) // do not deal extra damage in the same tic + { + K_SetHitLagForObjects(target, inflictor, laglength, true); + return false; + } } } - // We successfully damaged them! Give 'em some bumpers! - if (type != DMG_STING && type != DMG_STUMBLE) + if (gametyperules & GTR_BUMPERS) { - UINT8 takeBumpers = 1; - if (damagetype & DMG_STEAL) { - takeBumpers = 2; + // Steals 2 bumpers + damage = 2; + } + } + else + { + // Do not die from damage outside of bumpers health system + damage = 0; + } - if (type == DMG_KARMA) - { - takeBumpers = player->bumpers; - } - } - else - { - if (type == DMG_KARMA) - { - // Take half of their bumpers for karma comeback damage - takeBumpers = max(1, player->bumpers / 2); - } - } + if (type == DMG_STING || type == DMG_STUMBLE) + { + damage = 0; + } + else + { + // We successfully damaged them! Give 'em some bumpers! if (source && source != player->mo && source->player) { @@ -2305,18 +2354,12 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da K_TryHurtSoundExchange(target, source); - K_BattleAwardHit(source->player, player, inflictor, takeBumpers); - damage = K_TakeBumpersFromPlayer(source->player, player, takeBumpers); + if (K_Cooperative() == false) + { + K_BattleAwardHit(source->player, player, inflictor, damage); + } - if (type == DMG_KARMA) - { - // Destroy any remainder bumpers from the player for karma comeback damage - damage = K_DestroyBumpers(player, player->bumpers); - } - else - { - source->player->overtimekarma += 5*TICRATE; - } + K_TakeBumpersFromPlayer(source->player, player, damage); if (damagetype & DMG_STEAL) { @@ -2332,10 +2375,6 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da Obj_GardenTopDestroy(source->player); } } - else - { - damage = K_DestroyBumpers(player, takeBumpers); - } if (!(damagetype & DMG_STEAL)) { @@ -2453,6 +2492,8 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da K_SetHitLagForObjects(target, inflictor, laglength, true); + target->flags2 |= MF2_ALREADYHIT; + if (target->health <= 0) { P_KillMobj(target, inflictor, source, damagetype); diff --git a/src/p_map.c b/src/p_map.c index be4e0171b..ed83fd441 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -956,6 +956,8 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) || tm.thing->type == MT_BANANA || tm.thing->type == MT_EGGMANITEM || tm.thing->type == MT_BALLHOG || tm.thing->type == MT_SSMINE || tm.thing->type == MT_LANDMINE || tm.thing->type == MT_SINK || tm.thing->type == MT_GARDENTOP + || tm.thing->type == MT_MONITOR + || tm.thing->type == MT_BATTLECAPSULE || (tm.thing->type == MT_PLAYER))) { // see if it went over / under @@ -971,6 +973,8 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) || thing->type == MT_BANANA || thing->type == MT_EGGMANITEM || thing->type == MT_BALLHOG || thing->type == MT_SSMINE || thing->type == MT_LANDMINE || thing->type == MT_SINK || thing->type == MT_GARDENTOP + || thing->type == MT_MONITOR + || thing->type == MT_BATTLECAPSULE || (thing->type == MT_PLAYER))) { // see if it went over / under @@ -1390,13 +1394,6 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) return BMIT_CONTINUE; } - if ((gametyperules & GTR_BUMPERS) - && ((thing->player->bumpers && !tm.thing->player->bumpers) - || (tm.thing->player->bumpers && !thing->player->bumpers))) - { - return BMIT_CONTINUE; - } - if (!P_MobjWasRemoved(thing) && !P_MobjWasRemoved(tm.thing)) { if (thing->player->eggmanexplode) diff --git a/src/p_mobj.c b/src/p_mobj.c index 5a11045c4..c84b64cd3 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -4602,9 +4602,6 @@ boolean P_SupermanLook4Players(mobj_t *actor) if (players[c].mo->health <= 0) continue; // dead - if ((gametyperules & GTR_BUMPERS) && players[c].bumpers <= 0) - continue; // other dead - playersinthegame[stop] = &players[c]; stop++; } @@ -6143,6 +6140,8 @@ static void P_MobjSceneryThink(mobj_t *mobj) if (mobj->target && !P_MobjWasRemoved(mobj->target) && mobj->target->player && mobj->target->health > 0 && !mobj->target->player->spectator) { + const UINT8 bumpers = K_Bumpers(mobj->target->player); + fixed_t rad = 32*mobj->target->scale; fixed_t offz; angle_t ang, diff; @@ -6152,10 +6151,10 @@ static void P_MobjSceneryThink(mobj_t *mobj) else ang = FixedAngle(mobj->info->speed); - if (mobj->target->player->bumpers <= 1) + if (bumpers <= 1) diff = 0; else - diff = FixedAngle(360*FRACUNIT/mobj->target->player->bumpers); + diff = FixedAngle(360*FRACUNIT / bumpers); ang = (ang*leveltime) + (diff * (mobj->threshold-1)); @@ -6192,9 +6191,9 @@ static void P_MobjSceneryThink(mobj_t *mobj) mobj->color = mobj->target->color; } - if (mobj->target->player->bumpers < 2) + if (bumpers < 2) P_SetMobjState(mobj, S_BATTLEBUMPER3); - else if (mobj->target->player->bumpers < 3) + else if (bumpers < 3) P_SetMobjState(mobj, S_BATTLEBUMPER2); else P_SetMobjState(mobj, S_BATTLEBUMPER1); @@ -6211,7 +6210,7 @@ static void P_MobjSceneryThink(mobj_t *mobj) P_SetThingPosition(mobj); } - if (mobj->target->player->bumpers <= mobj->threshold) + if (bumpers <= mobj->threshold) { // Do bumper destruction P_KillMobj(mobj, NULL, NULL, DMG_NORMAL); @@ -6245,7 +6244,7 @@ static void P_MobjSceneryThink(mobj_t *mobj) mobj->color = mobj->target->color; K_MatchGenericExtraFlags(mobj, mobj->target); - if ((!(gametyperules & GTR_BUMPERS) || mobj->target->player->bumpers <= 0) + if (!(gametyperules & GTR_BUMPERS) #if 1 // Set to 0 to test without needing to host || (P_IsDisplayPlayer(mobj->target->player)) #endif @@ -8261,7 +8260,9 @@ static boolean P_MobjRegularThink(mobj_t *mobj) desty = mobj->target->y; } + mobj->flags &= ~(MF_NOCLIPTHING); P_MoveOrigin(mobj, destx, desty, mobj->target->z); + mobj->flags |= MF_NOCLIPTHING; break; } case MT_FLAMESHIELD: @@ -8442,7 +8443,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) statenum_t state = (mobj->state-states); if (!mobj->target || !mobj->target->health || !mobj->target->player || mobj->target->player->spectator - || (!(gametyperules & GTR_BUMPERS) || mobj->target->player->bumpers)) + || !(gametyperules & GTR_BUMPERS)) { P_RemoveMobj(mobj); return false; @@ -9857,8 +9858,10 @@ void P_MobjThinker(mobj_t *mobj) if ((mobj->flags & MF_BOSS) && mobj->spawnpoint && (bossdisabled & (1<spawnpoint->args[0]))) return; + mobj->flags2 &= ~(MF2_ALREADYHIT); + // Don't run any thinker code while in hitlag - if (mobj->hitlag > 0) + if ((mobj->player ? mobj->hitlag - mobj->player->nullHitlag : mobj->hitlag) > 0) { mobj->eflags |= MFE_PAUSED; mobj->hitlag--; @@ -11890,38 +11893,15 @@ void P_SpawnPlayer(INT32 playernum) P_SetScale(overheadarrow, mobj->destscale); } - if (gametyperules & GTR_BUMPERS) + if ((gametyperules & GTR_BUMPERS) && !p->spectator) { - if (p->spectator) + // At leveltime == 2, K_TimerInit will get called and reset + // the bumpers to the initial value for the level. + if (leveltime > 2) // Reset those bumpers! { - // HEY! No being cheap... - p->bumpers = 0; - } - else if ((p->bumpers > 0) || (leveltime < starttime) || (pcount <= 1)) - { - if ((leveltime < starttime) || (pcount <= 1)) // Start of the map? - { - if (leveltime > 2) // Reset those bumpers! - { - p->bumpers = K_StartingBumperCount(); - K_SpawnPlayerBattleBumpers(p); - } - else // temp, will get overwritten in K_BattleInit - { - p->bumpers = 1; - } - } - } - else if (p->bumpers <= 0) - { - p->bumpers = K_StartingBumperCount(); + mobj->health = K_BumpersToHealth(K_StartingBumperCount()); K_SpawnPlayerBattleBumpers(p); } - - if (p->bumpers > 0) - { - mobj->health = p->bumpers; - } } // I'm not refactoring the loop at the top of this file. @@ -11936,9 +11916,13 @@ void P_SpawnPlayer(INT32 playernum) } // Spectating when there is literally any other player in - // the level enables director cam. + // the level enables director cam. Or if the first player + // enters the game, spectate them. // TODO: how do we support splitscreen? - K_ToggleDirector(players[consoleplayer].spectator && pcount > 0); + if (playernum == consoleplayer || pcount == 1) + { + K_ToggleDirector(players[consoleplayer].spectator && pcount > 0); + } } void P_AfterPlayerSpawn(INT32 playernum) diff --git a/src/p_mobj.h b/src/p_mobj.h index d5dc67a3e..429f48812 100644 --- a/src/p_mobj.h +++ b/src/p_mobj.h @@ -191,7 +191,7 @@ typedef enum MF2_JUSTATTACKED = 1<<16, // can be pushed by other moving mobjs MF2_FIRING = 1<<17, // turret fire MF2_SUPERFIRE = 1<<18, // Firing something with Super Sonic-stopping properties. Or, if mobj has MF_MISSILE, this is the actual fire from it. - // free: 1<<19 + MF2_ALREADYHIT = 1<<19, // This object was already damaged THIS tic, resets even during hitlag MF2_STRONGBOX = 1<<20, // Flag used for "strong" random monitors. MF2_OBJECTFLIP = 1<<21, // Flag for objects that always have flipped gravity. MF2_SKULLFLY = 1<<22, // Special handling: skull in flight. diff --git a/src/p_saveg.c b/src/p_saveg.c index 4e4f5a483..544c43624 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -276,7 +276,7 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT16(save->p, players[i].spinouttimer); WRITEUINT8(save->p, players[i].spinouttype); WRITEUINT8(save->p, players[i].instashield); - WRITEINT32(save->p, players[i].invulnhitlag); + WRITEINT32(save->p, players[i].nullHitlag); WRITEUINT8(save->p, players[i].wipeoutslow); WRITEUINT8(save->p, players[i].justbumped); WRITEUINT8(save->p, players[i].tumbleBounces); @@ -381,9 +381,7 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT32(save->p, players[i].roundscore); WRITEUINT8(save->p, players[i].emeralds); - WRITEUINT8(save->p, players[i].bumpers); WRITEINT16(save->p, players[i].karmadelay); - WRITEUINT32(save->p, players[i].overtimekarma); WRITEINT16(save->p, players[i].spheres); WRITEUINT32(save->p, players[i].spheredigestion); @@ -653,7 +651,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].spinouttimer = READUINT16(save->p); players[i].spinouttype = READUINT8(save->p); players[i].instashield = READUINT8(save->p); - players[i].invulnhitlag = READINT32(save->p); + players[i].nullHitlag = READINT32(save->p); players[i].wipeoutslow = READUINT8(save->p); players[i].justbumped = READUINT8(save->p); players[i].tumbleBounces = READUINT8(save->p); @@ -758,9 +756,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].roundscore = READUINT32(save->p); players[i].emeralds = READUINT8(save->p); - players[i].bumpers = READUINT8(save->p); players[i].karmadelay = READINT16(save->p); - players[i].overtimekarma = READUINT32(save->p); players[i].spheres = READINT16(save->p); players[i].spheredigestion = READUINT32(save->p); diff --git a/src/p_setup.c b/src/p_setup.c index e2058b7ae..939e8cd22 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -122,6 +122,7 @@ unsigned char mapmd5[16]; boolean udmf; size_t numvertexes, numsegs, numsectors, numsubsectors, numnodes, numlines, numsides, nummapthings; +size_t num_orig_vertexes; vertex_t *vertexes; seg_t *segs; sector_t *sectors; @@ -1921,6 +1922,7 @@ static void P_WriteTextmap(void) side_t *wsides; mtag_t freetag; sectorspecialthings_t *specialthings; + boolean *wusedvertexes; f = fopen(filepath, "w"); if (!f) @@ -1930,14 +1932,15 @@ static void P_WriteTextmap(void) } wmapthings = Z_Calloc(nummapthings * sizeof(*mapthings), PU_LEVEL, NULL); - wvertexes = Z_Calloc(numvertexes * sizeof(*vertexes), PU_LEVEL, NULL); + wvertexes = Z_Calloc(num_orig_vertexes * sizeof(*vertexes), PU_LEVEL, NULL); wsectors = Z_Calloc(numsectors * sizeof(*sectors), PU_LEVEL, NULL); wlines = Z_Calloc(numlines * sizeof(*lines), PU_LEVEL, NULL); wsides = Z_Calloc(numsides * sizeof(*sides), PU_LEVEL, NULL); specialthings = Z_Calloc(numsectors * sizeof(*sectors), PU_LEVEL, NULL); + wusedvertexes = Z_Calloc(num_orig_vertexes * sizeof(boolean), PU_LEVEL, NULL); memcpy(wmapthings, mapthings, nummapthings * sizeof(*mapthings)); - memcpy(wvertexes, vertexes, numvertexes * sizeof(*vertexes)); + memcpy(wvertexes, vertexes, num_orig_vertexes * sizeof(*vertexes)); memcpy(wsectors, sectors, numsectors * sizeof(*sectors)); memcpy(wlines, lines, numlines * sizeof(*lines)); memcpy(wsides, sides, numsides * sizeof(*sides)); @@ -1951,9 +1954,19 @@ static void P_WriteTextmap(void) wsectors[i].tags.tags = memcpy(Z_Malloc(sectors[i].tags.count*sizeof(mtag_t), PU_LEVEL, NULL), sectors[i].tags.tags, sectors[i].tags.count*sizeof(mtag_t)); for (i = 0; i < numlines; i++) + { + size_t v; + if (lines[i].tags.count) wlines[i].tags.tags = memcpy(Z_Malloc(lines[i].tags.count * sizeof(mtag_t), PU_LEVEL, NULL), lines[i].tags.tags, lines[i].tags.count * sizeof(mtag_t)); + v = lines[i].v1 - vertexes; + wusedvertexes[v] = true; + + v = lines[i].v2 - vertexes; + wusedvertexes[v] = true; + } + freetag = Tag_NextUnused(0); for (i = 0; i < nummapthings; i++) @@ -1961,6 +1974,13 @@ static void P_WriteTextmap(void) subsector_t *ss; INT32 s; + if (wmapthings[i].type == mobjinfo[MT_WAYPOINT].doomednum + || wmapthings[i].type == mobjinfo[MT_WAYPOINT_ANCHOR].doomednum + || wmapthings[i].type == mobjinfo[MT_WAYPOINT_RISER].doomednum) + { + CONS_Alert(CONS_WARNING, M_GetText("Thing %s is a waypoint or waypoint parameter, which cannot be converted fully.\n"), sizeu1(i)); + } + if (wmapthings[i].type != 751 && wmapthings[i].type != 752 && wmapthings[i].type != 758) continue; @@ -2232,18 +2252,26 @@ static void P_WriteTextmap(void) fprintf(f, "\n"); } - for (i = 0; i < numvertexes; i++) + j = 0; + for (i = 0; i < num_orig_vertexes; i++) { - fprintf(f, "vertex // %s\n", sizeu1(i)); + if (wusedvertexes[i] == false) + { + continue; + } + + fprintf(f, "vertex // %s\n", sizeu1(j)); fprintf(f, "{\n"); - fprintf(f, "x = %f;\n", FIXED_TO_FLOAT(wvertexes[i].x)); - fprintf(f, "y = %f;\n", FIXED_TO_FLOAT(wvertexes[i].y)); - if (wvertexes[i].floorzset) - fprintf(f, "zfloor = %f;\n", FIXED_TO_FLOAT(wvertexes[i].floorz)); - if (wvertexes[i].ceilingzset) - fprintf(f, "zceiling = %f;\n", FIXED_TO_FLOAT(wvertexes[i].ceilingz)); + fprintf(f, "x = %f;\n", FIXED_TO_FLOAT(wvertexes[j].x)); + fprintf(f, "y = %f;\n", FIXED_TO_FLOAT(wvertexes[j].y)); + if (wvertexes[j].floorzset) + fprintf(f, "zfloor = %f;\n", FIXED_TO_FLOAT(wvertexes[j].floorz)); + if (wvertexes[j].ceilingzset) + fprintf(f, "zceiling = %f;\n", FIXED_TO_FLOAT(wvertexes[j].ceilingz)); fprintf(f, "}\n"); fprintf(f, "\n"); + + j++; } for (i = 0; i < numlines; i++) @@ -2563,6 +2591,7 @@ static void P_WriteTextmap(void) Z_Free(wlines); Z_Free(wsides); Z_Free(specialthings); + Z_Free(wusedvertexes); } /** Loads the textmap data, after obtaining the elements count and allocating their respective space. @@ -2939,6 +2968,10 @@ static boolean P_LoadMapData(const virtres_t *virt) if (numlines <= 0) I_Error("Level has no linedefs"); + // Copy original vertex count before BSP modifications, + // as it can alter how -writetextmap works. + num_orig_vertexes = numvertexes; + vertexes = Z_Calloc(numvertexes * sizeof (*vertexes), PU_LEVEL, NULL); sectors = Z_Calloc(numsectors * sizeof (*sectors), PU_LEVEL, NULL); sides = Z_Calloc(numsides * sizeof (*sides), PU_LEVEL, NULL); @@ -4400,7 +4433,7 @@ static void P_ConvertBinaryLinedefTypes(void) break; case 80: //Raise tagged things by type to this FOF lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS; - // angle will be converted to tags elsewhere, because they aren't ready yet... + lines[i].args[1] = tag; break; case 81: //Block enemies lines[i].flags |= ML_BLOCKMONSTERS; @@ -6880,10 +6913,8 @@ static void P_ConvertBinaryMap(void) P_ConvertBinaryThingTypes(); P_ConvertBinaryLinedefFlags(); -#if 0 // Don't do this yet... if (M_CheckParm("-writetextmap")) P_WriteTextmap(); -#endif } /** Compute MD5 message digest for bytes read from memory source @@ -7588,6 +7619,8 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) sector_t *ss; virtlump_t *encoreLump = NULL; + K_TimerReset(); + levelloading = true; // This is needed. Don't touch. @@ -7885,10 +7918,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) { // Backwards compatibility for non-UDMF maps K_AdjustWaypointsParameters(); - - // Moved over here... - if (M_CheckParm("-writetextmap")) - P_WriteTextmap(); } if (!fromnetsave) // ugly hack for P_NetUnArchiveMisc (and P_LoadNetGame) @@ -8009,8 +8038,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) P_MapEnd(); // just in case MapLoad modifies tm.thing } - K_TimerReset(); - // No render mode or reloading gamestate, stop here. if (rendermode == render_none || reloadinggamestate) return true; diff --git a/src/p_slopes.c b/src/p_slopes.c index 5175d850a..d71f80813 100644 --- a/src/p_slopes.c +++ b/src/p_slopes.c @@ -1112,13 +1112,18 @@ void P_HandleSlopeLanding(mobj_t *thing, pslope_t *slope) // Handles sliding down slopes, like if they were made of butter :) void P_ButteredSlope(mobj_t *mo) { - fixed_t thrust; + const fixed_t gameSpeed = K_GetKartGameSpeedScalar(gamespeed); + fixed_t thrust = 0; if (mo->flags & (MF_NOCLIPHEIGHT|MF_NOGRAVITY)) + { return; // don't slide down slopes if you can't touch them or you're not affected by gravity + } if (P_CanApplySlopePhysics(mo, mo->standingslope) == false) + { return; // No physics, no butter. + } if (mo->player != NULL) { @@ -1141,17 +1146,23 @@ void P_ButteredSlope(mobj_t *mo) thrust = FINESINE(mo->standingslope->zangle>>ANGLETOFINESHIFT) * 5 / 4 * (mo->eflags & MFE_VERTICALFLIP ? 1 : -1); - if (mo->player) { + if (mo->momx || mo->momy) + { fixed_t mult = FRACUNIT; - if (mo->momx || mo->momy) { - angle_t angle = R_PointToAngle2(0, 0, mo->momx, mo->momy) - mo->standingslope->xydirection; + angle_t angle = R_PointToAngle2(0, 0, mo->momx, mo->momy) - mo->standingslope->xydirection; - if (P_MobjFlip(mo) * mo->standingslope->zdelta < 0) - angle ^= ANGLE_180; - - mult = FRACUNIT + (FRACUNIT + FINECOSINE(angle>>ANGLETOFINESHIFT))*4/3; + if (P_MobjFlip(mo) * mo->standingslope->zdelta < 0) + { + angle ^= ANGLE_180; } + // Make uphill easier to climb, and downhill even faster. + mult = FINECOSINE(angle >> ANGLETOFINESHIFT); + + // Make relative to game speed + mult = FixedMul(mult, gameSpeed); + + mult = FRACUNIT + (FRACUNIT + mult)*4/3; thrust = FixedMul(thrust, mult); } diff --git a/src/p_spec.c b/src/p_spec.c index e9ad66d67..a01300a12 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -6185,7 +6185,7 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, I static void P_RaiseTaggedThingsToFakeFloor ( UINT16 type, - const taglist_t *tags, + mtag_t tag, sector_t *control ){ sector_t *target; @@ -6210,20 +6210,9 @@ P_RaiseTaggedThingsToFakeFloor ( continue; } - if (!udmf) - { - // We have to convert these here, as mobjs, let alone - // sector thing lists, don't exist at the time of the rest - // of the binary map conversion. - const mtag_t convertTag = mthing->angle; - - Tag_Add(&mthing->tags, convertTag); - Taggroup_Add(tags_mapthings, convertTag, (size_t)(mthing - mapthings)); - } - if ( (type == 0 || mthing->type == type) && - (tags->count == 0 || Tag_Share(&mthing->tags, tags)) + (tag == 0 || udmf ? Tag_Find(&mthing->tags, tag) : mthing->angle == tag) ){ if (( mo->flags2 & MF2_OBJECTFLIP )) { @@ -7771,7 +7760,7 @@ void P_SpawnSpecialsThatRequireObjects(boolean fromnetsave) { P_RaiseTaggedThingsToFakeFloor( lines[i].args[0], - &lines[i].tags, + lines[i].args[1], lines[i].frontsector ); } diff --git a/src/p_user.c b/src/p_user.c index 29ffbd94a..0a123792f 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -537,10 +537,6 @@ INT32 P_GivePlayerSpheres(player_t *player, INT32 num_spheres) if (!(gametyperules & GTR_SPHERES)) // No spheres in Race mode) return 0; - // Not alive - if ((gametyperules & GTR_BUMPERS) && (player->bumpers <= 0)) - return 0; - if (num_spheres > 40) // Reached the cap, don't waste 'em! num_spheres = 40; else if (num_spheres < 0) @@ -2256,7 +2252,7 @@ static void P_UpdatePlayerAngle(player_t *player) angle_t leniency = (2*ANG1/3) * min(player->cmd.latency, 6); // Don't force another turning tic, just give them the desired angle! - if (targetDelta == angleChange || player->pflags & PF_DRIFTEND || (maxTurnRight == 0 && maxTurnLeft == 0)) + if (targetDelta == angleChange || player->pflags & PF_DRIFTEND || K_Sliptiding(player) || (maxTurnRight == 0 && maxTurnLeft == 0)) { // We are where we need to be. // ...Or we aren't, but shouldn't be able to steer. @@ -4404,7 +4400,7 @@ void P_PlayerThink(player_t *player) || player->growshrinktimer > 0 // Grow doesn't flash either. || (player->respawn.state != RESPAWNST_NONE && player->respawn.truedeath == true) // Respawn timer (for drop dash effect) || (player->pflags & PF_NOCONTEST) // NO CONTEST explosion - || ((gametyperules & GTR_BUMPERS) && player->bumpers <= 0 && player->karmadelay))) + || player->karmadelay)) { if (player->flashing > 1 && player->flashing < K_GetKartFlashing(player) && (leveltime & 1)) diff --git a/src/s_sound.c b/src/s_sound.c index b45215c73..d3f9f5472 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -72,8 +72,8 @@ consvar_t stereoreverse = CVAR_INIT ("stereoreverse", "Off", CV_SAVE, CV_OnOff, static consvar_t precachesound = CVAR_INIT ("precachesound", "Off", CV_SAVE, CV_OnOff, NULL); // actual general (maximum) sound & music volume, saved into the config -consvar_t cv_soundvolume = CVAR_INIT ("soundvolume", "50", CV_SAVE, soundvolume_cons_t, NULL); -consvar_t cv_digmusicvolume = CVAR_INIT ("musicvolume", "50", CV_SAVE, soundvolume_cons_t, NULL); +consvar_t cv_soundvolume = CVAR_INIT ("soundvolume", "80", CV_SAVE, soundvolume_cons_t, NULL); +consvar_t cv_digmusicvolume = CVAR_INIT ("musicvolume", "80", CV_SAVE, soundvolume_cons_t, NULL); // number of channels available consvar_t cv_numChannels = CVAR_INIT ("snd_channels", "64", CV_SAVE|CV_CALL, CV_Unsigned, SetChannelsNum); @@ -791,9 +791,9 @@ void S_UpdateSounds(void) mobj_t *listenmobj[MAXSPLITSCREENPLAYERS]; // Update sound/music volumes, if changed manually at console - if (actualsfxvolume != cv_soundvolume.value * USER_VOLUME_SCALE) + if (actualsfxvolume != cv_soundvolume.value) S_SetSfxVolume (cv_soundvolume.value); - if (actualdigmusicvolume != cv_digmusicvolume.value * USER_VOLUME_SCALE) + if (actualdigmusicvolume != cv_digmusicvolume.value) S_SetDigMusicVolume (cv_digmusicvolume.value); // We're done now, if we're not in a level. @@ -990,7 +990,7 @@ void S_UpdateClosedCaptions(void) void S_SetSfxVolume(INT32 volume) { //CV_SetValue(&cv_soundvolume, volume); - actualsfxvolume = volume * USER_VOLUME_SCALE; + actualsfxvolume = volume; #ifdef HW3SOUND hws_mode == HWS_DEFAULT_MODE ? I_SetSfxVolume(volume&0x1F) : HW3S_SetSfxVolume(volume&0x1F); @@ -1360,7 +1360,6 @@ static tic_t pause_starttic; musicdef_t *musicdefstart = NULL; struct cursongcredit cursongcredit; // Currently displayed song credit info -int musicdef_volume; // // S_FindMusicDef @@ -2249,14 +2248,12 @@ void S_ChangeMusicEx(const char *mmusic, UINT16 mflags, boolean looping, UINT32 music_flags = mflags; music_looping = looping; - musicdef_volume = DEFAULT_MUSICDEF_VOLUME; - { musicdef_t *def = S_FindMusicDef(music_name); if (def) { - musicdef_volume = def->volume; + I_SetCurrentSongVolume(def->volume); } } @@ -2350,7 +2347,7 @@ void S_SetMusicVolume(INT32 digvolume) digvolume = cv_digmusicvolume.value; //CV_SetValue(&cv_digmusicvolume, digvolume); - actualdigmusicvolume = digvolume * USER_VOLUME_SCALE; + actualdigmusicvolume = digvolume; I_SetMusicVolume(digvolume); } diff --git a/src/s_sound.h b/src/s_sound.h index 5e29e960f..15c1445a3 100644 --- a/src/s_sound.h +++ b/src/s_sound.h @@ -35,10 +35,10 @@ extern openmpt_module *openmpt_mhandle; #define PICKUP_SOUND 0x8000 // -#define SOUND_VOLUME_RANGE 256 -#define MAX_SOUND_VOLUME 255 +#define SOUND_VOLUME_RANGE 100 +#define MAX_SOUND_VOLUME 100 -#define DEFAULT_MUSICDEF_VOLUME ( 100 / VOLUME_DIVIDER ) +#define DEFAULT_MUSICDEF_VOLUME 100 extern consvar_t stereoreverse; extern consvar_t cv_soundvolume, cv_closedcaptioning, cv_digmusicvolume; @@ -197,7 +197,6 @@ extern struct cursongcredit } cursongcredit; extern musicdef_t *musicdefstart; -extern int musicdef_volume; void S_LoadMusicDefs(UINT16 wadnum); void S_InitMusicDefs(void); diff --git a/src/sdl/i_main.cpp b/src/sdl/i_main.cpp index 2efe4126b..664984ab3 100644 --- a/src/sdl/i_main.cpp +++ b/src/sdl/i_main.cpp @@ -205,7 +205,8 @@ static void init_exchndl() if (exchndl_module == NULL) { - I_Error("exchndl.dll or mgwhelp.dll is missing"); + I_Error("exchndl.dll or mgwhelp.dll is missing, RPT files cannot be generated.\n" + "If you NEED to run without them, use -noexchndl."); } using PFN_ExcHndlInit = void(*)(void); @@ -297,6 +298,7 @@ int main(int argc, char **argv) //I_OutputMsg("I_StartupSystem() ...\n"); I_StartupSystem(); #if defined (_WIN32) + if (!M_CheckParm("-noexchndl")) { #if 0 // just load the DLL p_IsDebuggerPresent pfnIsDebuggerPresent = (p_IsDebuggerPresent)GetProcAddress(GetModuleHandleA("kernel32.dll"), "IsDebuggerPresent"); diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index cce361bd6..b60e30269 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -194,74 +194,9 @@ 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 - - \return void -*/ -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; - //JoySet->scale -} - -/** \brief First joystick up and running -*/ -static INT32 joystick_started[MAXSPLITSCREENPLAYERS] = {0,0,0,0}; - -/** \brief SDL info about joystick 1 +/** \brief SDL info about joysticks */ SDLJoyInfo_t JoyInfo[MAXSPLITSCREENPLAYERS]; -SDL_GameController *ExJoystick[MAXGAMEPADS]; SDL_bool consolevent = SDL_FALSE; SDL_bool framebuffer = SDL_FALSE; @@ -983,316 +918,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 +1019,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/new_sound.cpp b/src/sdl/new_sound.cpp index 80f549508..050b30539 100644 --- a/src/sdl/new_sound.cpp +++ b/src/sdl/new_sound.cpp @@ -57,7 +57,8 @@ static shared_ptr> mixer_sound_effects; static shared_ptr> mixer_music; static shared_ptr music_player; static shared_ptr> gain_sound_effects; -static shared_ptr> gain_music; +static shared_ptr> gain_music_player; +static shared_ptr> gain_music_channel; static vector> sound_effect_channels; @@ -187,12 +188,14 @@ void initialize_sound() mixer_music = make_shared>(); music_player = make_shared(); gain_sound_effects = make_shared>(); - gain_music = make_shared>(); + gain_music_player = make_shared>(); + gain_music_channel = make_shared>(); gain_sound_effects->bind(mixer_sound_effects); - gain_music->bind(mixer_music); + gain_music_player->bind(music_player); + gain_music_channel->bind(mixer_music); master->add_source(gain_sound_effects); - master->add_source(gain_music); - mixer_music->add_source(music_player); + master->add_source(gain_music_channel); + mixer_music->add_source(gain_music_player); for (size_t i = 0; i < static_cast(cv_numChannels.value); i++) { shared_ptr player = make_shared(); @@ -356,7 +359,7 @@ void I_SetSfxVolume(int volume) if (gain_sound_effects) { - gain_sound_effects->gain(vol * vol * vol); + gain_sound_effects->gain(std::clamp(vol * vol * vol, 0.f, 1.f)); } } @@ -597,6 +600,12 @@ boolean I_LoadSong(char* data, size_t len) return false; } + if (gain_music_player) + { + // Reset song volume to 1.0 for newly loaded songs. + gain_music_player->gain(1.0); + } + return true; } @@ -663,9 +672,22 @@ void I_SetMusicVolume(int volume) { float vol = static_cast(volume) / 100.f; - if (gain_music) + if (gain_music_channel) { - gain_music->gain(vol * vol * vol); + // Music channel volume is interpreted as logarithmic rather than linear. + // We approximate by cubing the gain level so vol 50 roughly sounds half as loud. + gain_music_channel->gain(std::clamp(vol * vol * vol, 0.f, 1.f)); + } +} + +void I_SetCurrentSongVolume(int volume) +{ + float vol = static_cast(volume) / 100.f; + + if (gain_music_player) + { + // However, different from music channel volume, musicdef volumes are explicitly linear. + gain_music_player->gain(std::max(vol, 0.f)); } } 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 diff --git a/src/sounds.c b/src/sounds.c index 4e369edd7..d3ad7c641 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -1135,6 +1135,7 @@ sfxinfo_t S_sfx[NUMSFX] = {"waved2", false, 32, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, {"waved3", false, 32, 64, -1, NULL, 0, -1, -1, LUMPERROR, ""}, {"waved4", false, 32, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + {"waved5", false, 32, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Passing sounds {"pass01", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, diff --git a/src/sounds.h b/src/sounds.h index 221abd92e..fee219a7e 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -1204,6 +1204,7 @@ typedef enum sfx_waved2, sfx_waved3, sfx_waved4, + sfx_waved5, // Passing sounds sfx_pass01,