diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 69d400866..0a6757f35 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -2610,15 +2610,6 @@ void CL_RemovePlayer(INT32 playernum, kickreason_t reason) LUA_HookPlayerQuit(&players[playernum], reason); // Lua hook for player quitting - // don't look through someone's view who isn't there - if (playernum == displayplayers[0] && !demo.playback) - { - // Call ViewpointSwitch hooks here. - // The viewpoint was forcibly changed. - LUA_HookViewpointSwitch(&players[consoleplayer], &players[consoleplayer], true); - displayplayers[0] = consoleplayer; - } - G_RemovePartyMember(playernum); // Reset player data @@ -2638,6 +2629,9 @@ void CL_RemovePlayer(INT32 playernum, kickreason_t reason) LUA_InvalidatePlayer(&players[playernum]); + // don't look through someone's view who isn't there + G_ResetViews(); + K_CheckBumpers(); P_CheckRacers(); } diff --git a/src/d_clisrv.h b/src/d_clisrv.h index a648b07ae..0b51846ca 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -359,10 +359,10 @@ struct doomdata_t UINT8 reserved; // Padding union { - clientcmd_pak clientpak; // 145 bytes - client2cmd_pak client2pak; // 202 bytes - client3cmd_pak client3pak; // 258 bytes(?) - client4cmd_pak client4pak; // 316 bytes(?) + clientcmd_pak clientpak; // 147 bytes + client2cmd_pak client2pak; // 206 bytes + client3cmd_pak client3pak; // 264 bytes(?) + client4cmd_pak client4pak; // 324 bytes(?) servertics_pak serverpak; // 132495 bytes (more around 360, no?) serverconfig_pak servercfg; // 773 bytes UINT8 textcmd[MAXTEXTCMD+1]; // 66049 bytes (wut??? 64k??? More like 257 bytes...) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 5e93a4a10..4b02e962f 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -548,8 +548,6 @@ static CV_PossibleValue_t perfstats_cons_t[] = { }; consvar_t cv_perfstats = CVAR_INIT ("perfstats", "Off", 0, perfstats_cons_t, NULL); -consvar_t cv_director = CVAR_INIT ("director", "Off", 0, CV_OnOff, NULL); - consvar_t cv_schedule = CVAR_INIT ("schedule", "On", CV_NETVAR|CV_CALL, CV_OnOff, Schedule_OnChange); consvar_t cv_automate = CVAR_INIT ("automate", "On", CV_NETVAR, CV_OnOff, NULL); @@ -1060,8 +1058,6 @@ void D_RegisterClientCommands(void) CV_RegisterVar(&cv_scr_width); CV_RegisterVar(&cv_scr_height); - CV_RegisterVar(&cv_director); - CV_RegisterVar(&cv_soundtest); CV_RegisterVar(&cv_invincmusicfade); @@ -1596,21 +1592,8 @@ static void FinalisePlaystateChange(INT32 playernum) K_StripItems(&players[playernum]); } - // Reset away view (some code referenced from P_SpectatorJoinGame) - { - UINT8 i = 0; - INT32 *localplayertable = (splitscreen_partied[consoleplayer] ? splitscreen_party[consoleplayer] : g_localplayers); - - for (i = 0; i <= r_splitscreen; i++) - { - if (localplayertable[i] == playernum) - { - LUA_HookViewpointSwitch(players+playernum, players+playernum, true); - displayplayers[i] = playernum; - break; - } - } - } + // Reset away view + G_ResetViews(); K_CheckBumpers(); // SRB2Kart P_CheckRacers(); // also SRB2Kart diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 61171d393..a126de023 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -128,8 +128,6 @@ extern consvar_t cv_sleep; extern consvar_t cv_perfstats; -extern consvar_t cv_director; - extern consvar_t cv_schedule; extern consvar_t cv_livestudioaudience; diff --git a/src/d_ticcmd.h b/src/d_ticcmd.h index c7a37614c..a3cdf4d9b 100644 --- a/src/d_ticcmd.h +++ b/src/d_ticcmd.h @@ -71,6 +71,7 @@ struct ticcmd_t { SINT8 forwardmove; // -MAXPLMOVE to MAXPLMOVE (50) INT16 turning; // Turn speed + INT16 angle; // Predicted angle, use me if you can! INT16 throwdir; // Aiming direction INT16 aiming; // vertical aiming, see G_BuildTicCmd UINT16 buttons; diff --git a/src/doomdef.h b/src/doomdef.h index 95f8df2ac..a3c62eeb2 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -131,6 +131,7 @@ extern char logfilename[1024]; //#define DEVELOP // Disable this for release builds to remove excessive cheat commands and enable MD5 checking and stuff, all in one go. :3 #ifdef DEVELOP +#define PARANOIA // On by default for DEVELOP builds #define VERSIONSTRING "Development EXE" #define VERSIONSTRING_RC "Development EXE" "\0" // most interface strings are ignored in development mode. diff --git a/src/g_demo.c b/src/g_demo.c index 36a6f6c63..174511e4d 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -136,15 +136,16 @@ demoghost *ghosts = NULL; #define DEMO_BOT 0x08 // For demos -#define ZT_FWD 0x01 -#define ZT_SIDE 0x02 -#define ZT_TURNING 0x04 -#define ZT_THROWDIR 0x08 -#define ZT_BUTTONS 0x10 -#define ZT_AIMING 0x20 -#define ZT_LATENCY 0x40 -#define ZT_FLAGS 0x80 -// OUT OF ZIPTICS... +#define ZT_FWD 0x0001 +#define ZT_SIDE 0x0002 +#define ZT_TURNING 0x0004 +#define ZT_ANGLE 0x0008 +#define ZT_THROWDIR 0x0010 +#define ZT_BUTTONS 0x0020 +#define ZT_AIMING 0x0040 +#define ZT_LATENCY 0x0080 +#define ZT_FLAGS 0x0100 +// Ziptics are UINT16 now, go nuts #define DEMOMARKER 0x80 // demobuf.end @@ -530,17 +531,19 @@ void G_WriteDemoExtraData(void) void G_ReadDemoTiccmd(ticcmd_t *cmd, INT32 playernum) { - UINT8 ziptic; + UINT16 ziptic; if (!demobuf.p || !demo.deferstart) return; - ziptic = READUINT8(demobuf.p); + ziptic = READUINT16(demobuf.p); if (ziptic & ZT_FWD) oldcmd[playernum].forwardmove = READSINT8(demobuf.p); if (ziptic & ZT_TURNING) oldcmd[playernum].turning = READINT16(demobuf.p); + if (ziptic & ZT_ANGLE) + oldcmd[playernum].angle = READINT16(demobuf.p); if (ziptic & ZT_THROWDIR) oldcmd[playernum].throwdir = READINT16(demobuf.p); if (ziptic & ZT_BUTTONS) @@ -564,13 +567,14 @@ void G_ReadDemoTiccmd(ticcmd_t *cmd, INT32 playernum) void G_WriteDemoTiccmd(ticcmd_t *cmd, INT32 playernum) { - char ziptic = 0; + UINT16 ziptic = 0; UINT8 *ziptic_p; (void)playernum; if (!demobuf.p) return; - ziptic_p = demobuf.p++; // the ziptic, written at the end of this function + ziptic_p = demobuf.p; // the ziptic, written at the end of this function + demobuf.p += 2; if (cmd->forwardmove != oldcmd[playernum].forwardmove) { @@ -586,6 +590,13 @@ void G_WriteDemoTiccmd(ticcmd_t *cmd, INT32 playernum) ziptic |= ZT_TURNING; } + if (cmd->angle != oldcmd[playernum].angle) + { + WRITEINT16(demobuf.p,cmd->angle); + oldcmd[playernum].angle = cmd->angle; + ziptic |= ZT_ANGLE; + } + if (cmd->throwdir != oldcmd[playernum].throwdir) { WRITEINT16(demobuf.p,cmd->throwdir); @@ -621,7 +632,7 @@ void G_WriteDemoTiccmd(ticcmd_t *cmd, INT32 playernum) ziptic |= ZT_FLAGS; } - *ziptic_p = ziptic; + WRITEUINT16(ziptic_p, ziptic); // attention here for the ticcmd size! // latest demos with mouse aiming byte in ticcmd @@ -1173,7 +1184,7 @@ void G_GhostTicker(void) for(g = ghosts, p = NULL; g; g = g->next) { // Skip normal demo data. - UINT8 ziptic = READUINT8(g->p); + UINT16 ziptic = READUINT8(g->p); UINT8 xziptic = 0; while (ziptic != DW_END) // Get rid of extradata stuff @@ -1214,12 +1225,14 @@ void G_GhostTicker(void) ziptic = READUINT8(g->p); } - ziptic = READUINT8(g->p); + ziptic = READUINT16(g->p); if (ziptic & ZT_FWD) g->p++; if (ziptic & ZT_TURNING) g->p += 2; + if (ziptic & ZT_ANGLE) + g->p += 2; if (ziptic & ZT_THROWDIR) g->p += 2; if (ziptic & ZT_BUTTONS) diff --git a/src/g_game.c b/src/g_game.c index bf2c2761a..d9673f6b6 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -61,6 +61,7 @@ #include "k_specialstage.h" #include "k_bot.h" #include "doomstat.h" +#include "k_director.h" #ifdef HAVE_DISCORDRPC #include "discord.h" @@ -1080,80 +1081,32 @@ INT32 localaiming[MAXSPLITSCREENPLAYERS]; angle_t localangle[MAXSPLITSCREENPLAYERS]; INT32 localsteering[MAXSPLITSCREENPLAYERS]; -INT32 localdelta[MAXSPLITSCREENPLAYERS]; -INT32 localstoredeltas[MAXSPLITSCREENPLAYERS][TICCMD_LATENCYMASK + 1]; -UINT8 locallatency[MAXSPLITSCREENPLAYERS][TICRATE]; -UINT8 localtic; - -void G_ResetAnglePrediction(player_t *player) -{ - UINT16 i, j; - - for (i = 0; i <= r_splitscreen; i++) - { - if (&players[displayplayers[i]] == player) - { - localdelta[i] = 0; - for (j = 0; j < TICCMD_LATENCYMASK; j++) - { - localstoredeltas[i][j] = 0; - } - break; - } - } -} // Turning was removed from G_BuildTiccmd to prevent easy client hacking. // This brings back the camera prediction that was lost. static void G_DoAnglePrediction(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer, player_t *player) { angle_t angleChange = 0; - angle_t destAngle = player->angleturn; - angle_t diff = 0; - localtic = cmd->latency; - - if (player->pflags & PF_DRIFTEND) + while (realtics > 0) { - // Otherwise, your angle slingshots off to the side violently... - G_ResetAnglePrediction(player); + localsteering[ssplayer - 1] = K_UpdateSteeringValue(localsteering[ssplayer - 1], cmd->turning); + angleChange = K_GetKartTurnValue(player, localsteering[ssplayer - 1]) << TICCMD_REDUCE; + + realtics--; + } + +#if 0 + // Left here in case it needs unsealing later. This tried to replicate an old localcam function, but this behavior was unpopular in tests. + //if (player->pflags & PF_DRIFTEND) + { + localangle[ssplayer - 1] = player->mo->angle; } else +#endif { - while (realtics > 0) - { - localsteering[ssplayer - 1] = K_UpdateSteeringValue(localsteering[ssplayer - 1], cmd->turning); - angleChange = K_GetKartTurnValue(player, localsteering[ssplayer - 1]) << TICCMD_REDUCE; - - // Store the angle we applied to this tic, so we can revert it later. - // If we trust the camera to do all of the work, then it can get out of sync fast. - localstoredeltas[ssplayer - 1][cmd->latency] += angleChange; - localdelta[ssplayer - 1] += angleChange; - - realtics--; - } + localangle[ssplayer - 1] += angleChange; } - - // We COULD set it to destAngle directly... - // but this causes incredible jittering when the prediction turns out to be wrong. So we ease into it. - // Slight increased camera lag in all scenarios > Mostly lagless camera but with jittering - destAngle = player->angleturn + localdelta[ssplayer - 1]; - - diff = destAngle - localangle[ssplayer - 1]; - - if (diff > ANGLE_180) - { - diff = InvAngle(InvAngle(diff) / 2); - } - else - { - diff /= 2; - } - - localangle[ssplayer - 1] += diff; - - // In case of angle debugging, break glass - // localangle[ssplayer - 1] = destAngle; } void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) @@ -1189,6 +1142,8 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) break; } + cmd->angle = localangle[forplayer] >> TICCMD_REDUCE; + // why build a ticcmd if we're paused? // Or, for that matter, if we're being reborn. if (paused || P_AutoPause() || (gamestate == GS_LEVEL && player->playerstate == PST_REBORN)) @@ -1210,6 +1165,28 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) goto aftercmdinput; } + if (displayplayers[forplayer] != g_localplayers[forplayer]) + { + if (M_MenuButtonPressed(forplayer, MBT_A)) + { + G_AdjustView(ssplayer, 1, true); + K_ToggleDirector(false); + } + + if (M_MenuButtonPressed(forplayer, MBT_X)) + { + G_AdjustView(ssplayer, -1, true); + K_ToggleDirector(false); + } + + if (M_MenuButtonPressed(forplayer, MBT_R)) + { + K_ToggleDirector(true); + } + + goto aftercmdinput; + } + if (K_PlayerUsesBotMovement(player)) { // Bot ticcmd is generated by K_BuildBotTiccmd @@ -1398,16 +1375,6 @@ aftercmdinput: cmd->throwdir = -KART_FULLTURN; G_DoAnglePrediction(cmd, realtics, ssplayer, player); - - // Reset away view if a command is given. - if ((cmd->forwardmove || cmd->buttons) - && !r_splitscreen && displayplayers[0] != consoleplayer && ssplayer == 1) - { - // Call ViewpointSwitch hooks here. - // The viewpoint was forcibly changed. - LUA_HookViewpointSwitch(player, &players[consoleplayer], true); - displayplayers[0] = consoleplayer; - } } ticcmd_t *G_CopyTiccmd(ticcmd_t* dest, const ticcmd_t* src, const size_t n) @@ -1422,6 +1389,7 @@ ticcmd_t *G_MoveTiccmd(ticcmd_t* dest, const ticcmd_t* src, const size_t n) { dest[i].forwardmove = src[i].forwardmove; dest[i].turning = (INT16)SHORT(src[i].turning); + dest[i].angle = (INT16)SHORT(src[i].angle); dest[i].throwdir = (INT16)SHORT(src[i].throwdir); dest[i].aiming = (INT16)SHORT(src[i].aiming); dest[i].buttons = (UINT16)SHORT(src[i].buttons); @@ -1740,44 +1708,8 @@ boolean G_Responder(event_t *ev) return true; // chat ate the event } - // allow spy mode changes even during the demo - if (gamestate == GS_LEVEL && ev->type == ev_keydown - && (ev->data1 == KEY_F12 /*|| ev->data1 == gamecontrol[0][gc_viewpoint][0] || ev->data1 == gamecontrol[0][gc_viewpoint][1]*/)) - { - if (!demo.playback && (r_splitscreen || !netgame)) - g_localplayers[0] = consoleplayer; - else - { - G_AdjustView(1, 1, true); - - // change statusbar also if playing back demo - if (demo.quitafterplaying) - ST_changeDemoView(); - - return true; - } - } - if (gamestate == GS_LEVEL && ev->type == ev_keydown && multiplayer && demo.playback && !demo.freecam) { - /* - if (ev->data1 == gamecontrol[1][gc_viewpoint][0] || ev->data1 == gamecontrol[1][gc_viewpoint][1]) - { - G_AdjustView(2, 1, true); - return true; - } - else if (ev->data1 == gamecontrol[2][gc_viewpoint][0] || ev->data1 == gamecontrol[2][gc_viewpoint][1]) - { - G_AdjustView(3, 1, true); - return true; - } - else if (ev->data1 == gamecontrol[3][gc_viewpoint][0] || ev->data1 == gamecontrol[3][gc_viewpoint][1]) - { - G_AdjustView(4, 1, true); - return true; - } - */ - // Allow pausing if ( //ev->data1 == gamecontrol[0][gc_pause][0] @@ -1964,7 +1896,7 @@ boolean G_CanView(INT32 playernum, UINT8 viewnum, boolean onlyactive) INT32 G_FindView(INT32 startview, UINT8 viewnum, boolean onlyactive, boolean reverse) { INT32 i, dir = reverse ? -1 : 1; - startview = min(max(startview, 0), MAXPLAYERS); + startview = min(max(startview, -1), MAXPLAYERS); for (i = startview; i < MAXPLAYERS && i >= 0; i += dir) { if (G_CanView(i, viewnum, onlyactive)) @@ -2035,7 +1967,14 @@ void G_ResetView(UINT8 viewnum, INT32 playernum, boolean onlyactive) /* Check if anyone is available to view. */ if (( playernum = G_FindView(playernum, viewnum, onlyactive, playernum < olddisplayplayer) ) == -1) - return; + { + /* Fall back on true self */ + playernum = g_localplayers[viewnum-1]; + } + + // Call ViewpointSwitch hooks here. + // The viewpoint was forcibly changed. + LUA_HookViewpointSwitch(&players[g_localplayers[viewnum - 1]], &players[playernum], true); /* Focus our target view first so that we don't take its player. */ (*displayplayerp) = playernum; @@ -2043,6 +1982,10 @@ void G_ResetView(UINT8 viewnum, INT32 playernum, boolean onlyactive) { camerap = &camera[viewnum-1]; P_ResetCamera(&players[(*displayplayerp)], camerap); + + // Why does it need to be done twice? + R_ResetViewInterpolation(viewnum); + R_ResetViewInterpolation(viewnum); } if (viewnum > splits) @@ -2060,6 +2003,10 @@ void G_ResetView(UINT8 viewnum, INT32 playernum, boolean onlyactive) if (viewnum == 1 && demo.playback) consoleplayer = displayplayers[0]; + + // change statusbar also if playing back demo + if (demo.quitafterplaying) + ST_changeDemoView(); } // @@ -2097,8 +2044,8 @@ void G_ResetViews(void) /* Demote splits */ if (playersviewable < splits) { - splits = playersviewable; - r_splitscreen = max(splits-1, 0); + splits = max(playersviewable, splitscreen + 1); // don't delete local players + r_splitscreen = splits - 1; R_ExecuteSetViewSize(); } diff --git a/src/g_game.h b/src/g_game.h index 78b484a69..fac245c9a 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -101,7 +101,6 @@ extern consvar_t cv_resume; const char *G_BuildMapName(INT32 map); INT32 G_MapNumber(const char *mapname); -void G_ResetAnglePrediction(player_t *player); void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer); // copy ticcmd_t to and fro the normal way @@ -116,10 +115,6 @@ INT16 G_SoftwareClipAimingPitch(INT32 *aiming); extern angle_t localangle[MAXSPLITSCREENPLAYERS]; extern INT32 localaiming[MAXSPLITSCREENPLAYERS]; // should be an angle_t but signed extern INT32 localsteering[MAXSPLITSCREENPLAYERS]; -extern INT32 localdelta[MAXSPLITSCREENPLAYERS]; -extern INT32 localstoredeltas[MAXSPLITSCREENPLAYERS][TICCMD_LATENCYMASK + 1]; -extern UINT8 locallatency[MAXSPLITSCREENPLAYERS][TICRATE]; -extern UINT8 localtic; INT32 G_PlayerInputAnalog(UINT8 p, INT32 gc, UINT8 menuPlayers); boolean G_PlayerInputDown(UINT8 p, INT32 gc, UINT8 menuPlayers); diff --git a/src/k_director.c b/src/k_director.c index a35c3da84..b1513672c 100644 --- a/src/k_director.c +++ b/src/k_director.c @@ -25,6 +25,7 @@ void K_InitDirector(void) { INT32 playernum; + directorinfo.active = false; directorinfo.cooldown = SWITCHTIME; directorinfo.freeze = 0; directorinfo.attacker = 0; @@ -109,6 +110,11 @@ static boolean K_CanSwitchDirector(void) return false; } + if (!directorinfo.active) + { + return false; + } + return true; } @@ -218,11 +224,6 @@ void K_UpdateDirector(void) INT32 *displayplayerp = &displayplayers[0]; INT32 targetposition; - if (!cv_director.value) - { - return; - } - K_UpdateDirectorPositions(); if (directorinfo.cooldown > 0) { @@ -238,6 +239,13 @@ void K_UpdateDirector(void) return; } + // if there's only one player left in the list, just switch to that player + if (directorinfo.sortedplayers[0] != -1 && directorinfo.sortedplayers[1] == -1) + { + K_DirectorSwitch(directorinfo.sortedplayers[0], false); + return; + } + // aaight, time to walk through the standings to find the first interesting pair // NB: targetposition/sortedplayers is 0-indexed, aiming at the "back half" of a given pair by default. // we adjust for this when comparing to player->position or when looking at the leading player, Don't Freak Out @@ -277,18 +285,40 @@ void K_UpdateDirector(void) target = directorinfo.sortedplayers[targetposition]; + // stop here since we're already viewing this player + if (*displayplayerp == target) + { + break; + } + + // if this is a splitscreen player, try next pair + if (P_IsDisplayPlayer(&players[target])) + { + continue; + } + // if we're certain the back half of the pair is actually in this position, try to switch - if (*displayplayerp != target && !players[target].positiondelay) + if (!players[target].positiondelay) { K_DirectorSwitch(target, false); } // even if we're not certain, if we're certain we're watching the WRONG player, try to switch - if (players[*displayplayerp].position != targetposition+1 && !players[target].positiondelay) + if (players[*displayplayerp].position != targetposition+1 && !players[*displayplayerp].positiondelay) { K_DirectorSwitch(target, false); } break; } -} \ No newline at end of file +} + +void K_ToggleDirector(boolean active) +{ + if (directorinfo.active != active) + { + directorinfo.cooldown = 0; // switch immediately + } + + directorinfo.active = active; +} diff --git a/src/k_director.h b/src/k_director.h index 29bb01721..47b4b2265 100644 --- a/src/k_director.h +++ b/src/k_director.h @@ -12,6 +12,7 @@ extern "C" { extern struct directorinfo { + boolean active; // is view point switching enabled? tic_t cooldown; // how long has it been since we last switched? tic_t freeze; // when nonzero, fixed switch pending, freeze logic! INT32 attacker; // who to switch to when freeze delay elapses @@ -26,6 +27,7 @@ void K_InitDirector(void); void K_UpdateDirector(void); void K_DrawDirectorDebugger(void); void K_DirectorFollowAttack(player_t *player, mobj_t *inflictor, mobj_t *source); +void K_ToggleDirector(boolean active); #ifdef __cplusplus } // extern "C" diff --git a/src/k_hud.c b/src/k_hud.c index 2edb1f3d5..d53a39d95 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -4639,6 +4639,33 @@ K_drawMiniPing (void) } } +static void K_DrawDirectorButton(INT32 idx, const char *label, patch_t *kp[2]) +{ + const INT32 flags = V_SNAPTORIGHT | V_SLIDEIN; + const INT32 textflags = flags | V_6WIDTHSPACE | V_ALLOWLOWERCASE; + + const UINT8 anim_duration = 16; + const UINT8 anim = (leveltime % (anim_duration * 2)) < anim_duration; + + const INT32 x = BASEVIDWIDTH - 60; + const INT32 y = BASEVIDHEIGHT - 70 + (idx * 16); + + V_DrawScaledPatch(x, y - 4, flags, kp[anim]); + V_DrawRightAlignedThinString(x - 2, y, textflags, label); +} + +static void K_drawDirectorHUD(void) +{ + if (!LUA_HudEnabled(hud_textspectator)) + { + return; + } + + K_DrawDirectorButton(0, "Next Player", kp_button_a[0]); + K_DrawDirectorButton(1, "Prev Player", kp_button_x[0]); + K_DrawDirectorButton(2, "Director", kp_button_r); +} + static void K_drawDistributionDebugger(void) { itemroulette_t rouletteData = {0}; @@ -4957,6 +4984,11 @@ void K_drawKartHUD(void) K_drawMiniPing(); } + if (displayplayers[viewnum] != g_localplayers[viewnum]) + { + K_drawDirectorHUD(); + } + if (cv_kartdebugdistribution.value) K_drawDistributionDebugger(); diff --git a/src/k_kart.c b/src/k_kart.c index efbe1bc17..5d4c7eb5d 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -7748,12 +7748,12 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) } } - if (player->invincibilitytimer) + if (player->invincibilitytimer && onground == true) player->invincibilitytimer--; if ((player->respawn.state == RESPAWNST_NONE) && player->growshrinktimer != 0) { - if (player->growshrinktimer > 0) + if (player->growshrinktimer > 0 && onground == true) player->growshrinktimer--; if (player->growshrinktimer < 0) player->growshrinktimer++; @@ -7810,7 +7810,8 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->stealingtimer == 0 - && player->rocketsneakertimer) + && player->rocketsneakertimer + && onground == true) player->rocketsneakertimer--; if (player->hyudorotimer) @@ -9728,6 +9729,7 @@ static void K_KartSpindash(player_t *player) player->spindash = 0; S_ReducedVFXSound(player->mo, sfx_s23c, player); + S_StopSoundByID(player->mo, sfx_kc38); } @@ -9779,11 +9781,15 @@ static void K_KartSpindash(player_t *player) UINT8 ringdropframes = 2 + (player->kartspeed + player->kartweight); boolean spawnOldEffect = true; - if (player->rings <= 0) // Use the damn spindash - player->spindash++; // I am no longer asking - INT16 chargetime = MAXCHARGETIME - ++player->spindash; + if (player->rings <= 0 && chargetime >= 0) // Desperation spindash + { + player->spindash++; + if (!S_SoundPlaying(player->mo, sfx_kc38)) + S_StartSound(player->mo, sfx_kc38); + } + if (player->spindash >= SPINDASHTHRUSTTIME) { K_KartSpindashDust(player->mo); @@ -10504,40 +10510,43 @@ void K_MoveKartPlayer(player_t *player, boolean onground) { if (player->growshrinktimer < 0) { - // If you're shrunk, then "grow" will just make you normal again. + // Old v1 behavior was to have Grow counter Shrink, + // but Shrink is now so ephemeral that it should just work. K_RemoveGrowShrink(player); + // So we fall through here. } - else + + K_PlayPowerGloatSound(player->mo); + + player->mo->scalespeed = mapobjectscale/TICRATE; + player->mo->destscale = FixedMul(mapobjectscale, GROW_SCALE); + + if (K_PlayerShrinkCheat(player) == true) { - K_PlayPowerGloatSound(player->mo); - - player->mo->scalespeed = mapobjectscale/TICRATE; - player->mo->destscale = FixedMul(mapobjectscale, GROW_SCALE); - - if (K_PlayerShrinkCheat(player) == true) - { - player->mo->destscale = FixedMul(player->mo->destscale, SHRINK_SCALE); - } - - player->growshrinktimer = max(player->growshrinktimer, ((gametyperules & GTR_CLOSERPLAYERS) ? 8 : 12) * TICRATE); - - if (player->invincibilitytimer > 0) - { - ; // invincibility has priority in P_RestoreMusic, no point in starting here - } - else if (P_IsLocalPlayer(player) == true) - { - S_ChangeMusicSpecial("kgrow"); - } - else //used to be "if (P_IsDisplayPlayer(player) == false)" - { - S_StartSound(player->mo, sfx_alarmg); - } - - P_RestoreMusic(player); - S_StartSound(player->mo, sfx_kc5a); + player->mo->destscale = FixedMul(player->mo->destscale, SHRINK_SCALE); } + if (player->invincibilitytimer > 0) + { + ; // invincibility has priority in P_RestoreMusic, no point in starting here + } + else if (P_IsLocalPlayer(player) == true) + { + if (player->growshrinktimer < 1) + S_ChangeMusicSpecial("kgrow"); + } + else //used to be "if (P_IsDisplayPlayer(player) == false)" + { + S_StartSound(player->mo, sfx_alarmg); + } + + P_RestoreMusic(player); + + player->growshrinktimer = max(0, player->growshrinktimer); + player->growshrinktimer += ((gametyperules & GTR_CLOSERPLAYERS) ? 8 : 12) * TICRATE; + + S_StartSound(player->mo, sfx_kc5a); + player->itemamount--; } break; diff --git a/src/k_menu.h b/src/k_menu.h index fb08ff4ab..3adbf25de 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -650,6 +650,7 @@ struct setup_player_t UINT16 color; UINT8 mdepth; boolean hitlag; + boolean showextra; // Hack, save player 1's original device even if they init charsel with keyboard. // If they play ALONE, allow them to retain that original device, otherwise, ignore this. @@ -1158,6 +1159,8 @@ extern struct challengesmenu_s { boolean pending; boolean requestnew; + boolean requestflip; + UINT8 unlockcount[CC_MAX]; UINT8 fade; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 6207f0e24..9c3fe3233 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -1363,16 +1363,19 @@ static void M_DrawCharSelectPreview(UINT8 num) M_DrawCharSelectCircle(p, x+32, y+64); } - V_DrawScaledPatch(x+9, y+2, 0, W_CachePatchName("FILEBACK", PU_CACHE)); - V_DrawScaledPatch(x, y+2, 0, W_CachePatchName(va("CHARSEL%c", letter), PU_CACHE)); - if (p->mdepth > CSSTEP_PROFILE) + if (p->showextra == false) { - profile_t *pr = PR_GetProfile(p->profilen); - V_DrawCenteredFileString(x+16+18, y+2, 0, pr->profilename); - } - else - { - V_DrawFileString(x+16, y+2, 0, "PLAYER"); + V_DrawScaledPatch(x+9, y+2, 0, W_CachePatchName("FILEBACK", PU_CACHE)); + V_DrawScaledPatch(x, y+2, 0, W_CachePatchName(va("CHARSEL%c", letter), PU_CACHE)); + if (p->mdepth > CSSTEP_PROFILE) + { + profile_t *pr = PR_GetProfile(p->profilen); + V_DrawCenteredFileString(x+16+18, y+2, 0, pr->profilename); + } + else + { + V_DrawFileString(x+16, y+2, 0, "PLAYER"); + } } if (p->mdepth >= CSSTEP_FOLLOWER) @@ -1471,6 +1474,82 @@ static void M_DrawCharSelectPreview(UINT8 num) V_DrawThinString(xpos+16, cy, (p->changeselect == i ? highlightflags : 0)|V_6WIDTHSPACE, choices[i]); } } + + if (p->showextra == true) + { + switch (p->mdepth) + { + case CSSTEP_CHARS: // Character Select grid + V_DrawThinString(x-3, y+2, V_6WIDTHSPACE, va("Speed %u - Weight %u", p->gridx+1, p->gridy+1)); + break; + case CSSTEP_ALTS: // Select clone + case CSSTEP_READY: + if (p->clonenum < setup_chargrid[p->gridx][p->gridy].numskins + && setup_chargrid[p->gridx][p->gridy].skinlist[p->clonenum] < numskins) + { + V_DrawThinString(x-3, y+2, V_6WIDTHSPACE, + skins[setup_chargrid[p->gridx][p->gridy].skinlist[p->clonenum]].name); + } + else + { + V_DrawThinString(x-3, y+2, V_6WIDTHSPACE, va("BAD CLONENUM %u", p->clonenum)); + } + break; + case CSSTEP_COLORS: // Select color + if (p->color < numskincolors) + { + V_DrawThinString(x-3, y+2, V_6WIDTHSPACE, skincolors[p->color].name); + } + else + { + V_DrawThinString(x-3, y+2, V_6WIDTHSPACE, va("BAD COLOR %u", p->color)); + } + break; + case CSSTEP_FOLLOWERCATEGORY: + if (p->followercategory == -1) + { + V_DrawThinString(x-3, y+2, V_6WIDTHSPACE, "None"); + } + else + { + V_DrawThinString(x-3, y+2, V_6WIDTHSPACE, + followercategories[setup_followercategories[p->followercategory][1]].name); + } + break; + case CSSTEP_FOLLOWER: + if (p->followern == -1) + { + V_DrawThinString(x-3, y+2, V_6WIDTHSPACE, "None"); + } + else + { + V_DrawThinString(x-3, y+2, V_6WIDTHSPACE, + followers[p->followern].name); + } + break; + case CSSTEP_FOLLOWERCOLORS: + if (p->followercolor == FOLLOWERCOLOR_MATCH) + { + V_DrawThinString(x-3, y+2, V_6WIDTHSPACE, "Match"); + } + else if (p->followercolor == FOLLOWERCOLOR_OPPOSITE) + { + V_DrawThinString(x-3, y+2, V_6WIDTHSPACE, "Opposite"); + } + else if (p->followercolor < numskincolors) + { + V_DrawThinString(x-3, y+2, V_6WIDTHSPACE, skincolors[p->followercolor].name); + } + else + { + V_DrawThinString(x-3, y+2, V_6WIDTHSPACE, va("BAD FOLLOWERCOLOR %u", p->followercolor)); + } + break; + default: + V_DrawThinString(x-3, y+2, V_6WIDTHSPACE, "[extrainfo mode]"); + break; + } + } } static void M_DrawCharSelectExplosions(boolean charsel, INT16 basex, INT16 basey) diff --git a/src/menus/extras-challenges.c b/src/menus/extras-challenges.c index 790a03376..17ced0502 100644 --- a/src/menus/extras-challenges.c +++ b/src/menus/extras-challenges.c @@ -175,6 +175,7 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) if (challengesmenu.pending || desiredmenu == NULL) { challengesmenu.ticker = 0; + challengesmenu.requestflip = false; challengesmenu.requestnew = false; challengesmenu.currentunlock = MAXUNLOCKABLES; challengesmenu.unlockcondition = NULL; @@ -287,7 +288,7 @@ void M_ChallengesTick(void) if (challengesmenu.extradata != NULL) { UINT16 id = (challengesmenu.hilix * CHALLENGEGRIDHEIGHT) + challengesmenu.hiliy; - boolean seeeveryone = M_MenuButtonHeld(pid, MBT_R); + boolean seeeveryone = challengesmenu.requestflip; boolean allthewaythrough; UINT8 maxflip; for (i = 0; i < (CHALLENGEGRIDHEIGHT * gamedata->challengegridwidth); i++) @@ -479,8 +480,17 @@ boolean M_ChallengesInputs(INT32 ch) return true; } + if (M_MenuButtonPressed(pid, MBT_R)) + { + challengesmenu.requestflip ^= true; + + return true; + } + if (challengesmenu.extradata != NULL && move) { + challengesmenu.requestflip = false; + // Determine movement around the grid // For right/down movement, we can pre-determine the number of steps based on extradata. // For left/up movement, we can't - we have to be ready to iterate twice, and break early if we don't run into a large tile. diff --git a/src/menus/options-data-1.c b/src/menus/options-data-1.c index 8d2b8ef56..632da7977 100644 --- a/src/menus/options-data-1.c +++ b/src/menus/options-data-1.c @@ -7,7 +7,7 @@ menuitem_t OPTIONS_Data[] = { - {IT_STRING | IT_SUBMENU, "Screenshot Options...", "Set options relative to screenshot and GIF capture.", + {IT_STRING | IT_SUBMENU, "Media Options...", "Set options relative to screenshot and movie capture.", NULL, {.submenu = &OPTIONS_DataScreenshotDef}, 0, 0}, {IT_STRING | IT_SUBMENU, "Addon Options...", "Set options relative to the addons menu.", diff --git a/src/menus/options-data-replays.c b/src/menus/options-data-replays.c index 30bf09c23..20e3aa8a3 100644 --- a/src/menus/options-data-replays.c +++ b/src/menus/options-data-replays.c @@ -8,7 +8,7 @@ menuitem_t OPTIONS_DataReplay[] = {IT_STRING | IT_CVAR, "Record Replays", "Select when to save replays.", NULL, {.cvar = &cv_recordmultiplayerdemos}, 0, 0}, - {IT_STRING | IT_CVAR, "Synch. Check Interval", "How often to check for synchronization while playing back a replay.", + {IT_STRING | IT_CVAR, "Net Consistency Quality", "For filesize, how often do we write position data in online replays?", NULL, {.cvar = &cv_netdemosyncquality}, 0, 0}, }; diff --git a/src/menus/options-data-screenshots.c b/src/menus/options-data-screenshots.c index 8f27953f9..809eb5e1b 100644 --- a/src/menus/options-data-screenshots.c +++ b/src/menus/options-data-screenshots.c @@ -3,6 +3,7 @@ #include "../k_menu.h" #include "../m_misc.h" // screenshot cvars +#include "../m_avrecorder.h" menuitem_t OPTIONS_DataScreenshot[] = { @@ -19,13 +20,19 @@ menuitem_t OPTIONS_DataScreenshot[] = {IT_SPACE | IT_NOTHING, NULL, NULL, NULL, {NULL}, 0, 0}, - {IT_HEADER, "GIF RECORDING (F9)", NULL, + {IT_HEADER, "MOVIE RECORDING (F9)", NULL, NULL, {NULL}, 0, 0}, - {IT_STRING | IT_CVAR, "Storage Location", "Sets where to store GIFs", + {IT_STRING | IT_CVAR, "Recording Format", "What file format will movies will be recorded in?", + NULL, {.cvar = &cv_moviemode}, 0, 0}, + + {IT_STRING | IT_CVAR, "Real-Time Data", "If enabled, shows fps, duration and filesize of recording in real-time.", + NULL, {.cvar = &cv_movie_showfps}, 0, 0}, + + {IT_STRING | IT_CVAR, "Storage Location", "Sets where to store movies.", NULL, {.cvar = &cv_movie_option}, 0, 0}, - {IT_STRING | IT_CVAR | IT_CV_STRING, "Custom Folder", "Specify which folder to save GIFs in.", + {IT_STRING | IT_CVAR | IT_CV_STRING, "Custom Folder", "Specify which folder to save videos in.", NULL, {.cvar = &cv_movie_folder}, 24, 0}, }; @@ -56,6 +63,10 @@ void Screenshot_option_Onchange(void) void Moviemode_mode_Onchange(void) { + // opt 7 in a 0 based array, you get the idea... + OPTIONS_DataScreenshot[6].status = + (cv_moviemode.value == MM_AVRECORDER ? IT_CVAR|IT_STRING : IT_DISABLED); + #if 0 INT32 i, cstart, cend; for (i = op_screenshot_gif_start; i <= op_screenshot_apng_end; ++i) @@ -81,7 +92,7 @@ void Moviemode_mode_Onchange(void) void Moviemode_option_Onchange(void) { - // opt 7 in a 0 based array, you get the idea... - OPTIONS_DataScreenshot[6].status = + // opt 9 in a 0 based array, you get the idea... + OPTIONS_DataScreenshot[8].status = (cv_movie_option.value == 3 ? IT_CVAR|IT_STRING|IT_CV_STRING : IT_DISABLED); } diff --git a/src/menus/options-gameplay-item-toggles.c b/src/menus/options-gameplay-item-toggles.c index c64e2b4ab..0a5cf9ba5 100644 --- a/src/menus/options-gameplay-item-toggles.c +++ b/src/menus/options-gameplay-item-toggles.c @@ -7,44 +7,45 @@ menuitem_t OPTIONS_GameplayItems[] = { // Mostly handled by the drawing function. - {IT_KEYHANDLER | IT_NOTHING, NULL, "Super Rings", NULL, {.routine = M_HandleItemToggles}, KITEM_SUPERRING, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Self-Propelled Bombs", NULL, {.routine = M_HandleItemToggles}, KITEM_SPB, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Eggman Marks", NULL, {.routine = M_HandleItemToggles}, KITEM_EGGMAN, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Super Ring", NULL, {.routine = M_HandleItemToggles}, KITEM_SUPERRING, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Self-Propelled Bomb", NULL, {.routine = M_HandleItemToggles}, KITEM_SPB, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, NULL, NULL, {.routine = M_HandleItemToggles}, 255, 0}, // maybe KITEM_PUYO eventually? {IT_KEYHANDLER | IT_NOTHING, NULL, "Toggle All", NULL, {.routine = M_HandleItemToggles}, 0, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Sneakers", NULL, {.routine = M_HandleItemToggles}, KITEM_SNEAKER, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Sneakers x2", NULL, {.routine = M_HandleItemToggles}, KRITEM_DUALSNEAKER, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Sneakers x3", NULL, {.routine = M_HandleItemToggles}, KRITEM_TRIPLESNEAKER, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Rocket Sneakers", NULL, {.routine = M_HandleItemToggles}, KITEM_ROCKETSNEAKER, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Sneaker", NULL, {.routine = M_HandleItemToggles}, KITEM_SNEAKER, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Sneaker x2", NULL, {.routine = M_HandleItemToggles}, KRITEM_DUALSNEAKER, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Sneaker x3", NULL, {.routine = M_HandleItemToggles}, KRITEM_TRIPLESNEAKER, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Rocket Sneaker", NULL, {.routine = M_HandleItemToggles}, KITEM_ROCKETSNEAKER, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Bananas", NULL, {.routine = M_HandleItemToggles}, KITEM_BANANA, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Bananas x3", NULL, {.routine = M_HandleItemToggles}, KRITEM_TRIPLEBANANA, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Proximity Mines", NULL, {.routine = M_HandleItemToggles}, KITEM_MINE, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Banana", NULL, {.routine = M_HandleItemToggles}, KITEM_BANANA, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Banana x3", NULL, {.routine = M_HandleItemToggles}, KRITEM_TRIPLEBANANA, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Eggman Mark", NULL, {.routine = M_HandleItemToggles}, KITEM_EGGMAN, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Gachabom", NULL, {.routine = M_HandleItemToggles}, KITEM_GACHABOM, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Orbinauts", NULL, {.routine = M_HandleItemToggles}, KITEM_ORBINAUT, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Orbinauts x3", NULL, {.routine = M_HandleItemToggles}, KRITEM_TRIPLEORBINAUT, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Orbinauts x4", NULL, {.routine = M_HandleItemToggles}, KRITEM_QUADORBINAUT, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Land Mines", NULL, {.routine = M_HandleItemToggles}, KITEM_LANDMINE, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Orbinaut", NULL, {.routine = M_HandleItemToggles}, KITEM_ORBINAUT, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Orbinaut x3", NULL, {.routine = M_HandleItemToggles}, KRITEM_TRIPLEORBINAUT, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Orbinaut x4", NULL, {.routine = M_HandleItemToggles}, KRITEM_QUADORBINAUT, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Gachabom x3", NULL, {.routine = M_HandleItemToggles}, KRITEM_TRIPLEGACHABOM, 0}, {IT_KEYHANDLER | IT_NOTHING, NULL, "Jawz", NULL, {.routine = M_HandleItemToggles}, KITEM_JAWZ, 0}, {IT_KEYHANDLER | IT_NOTHING, NULL, "Jawz x2", NULL, {.routine = M_HandleItemToggles}, KRITEM_DUALJAWZ, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Ballhogs", NULL, {.routine = M_HandleItemToggles}, KITEM_BALLHOG, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Drop Targets", NULL, {.routine = M_HandleItemToggles}, KITEM_DROPTARGET, sfx_s258}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Proximity Mine", NULL, {.routine = M_HandleItemToggles}, KITEM_MINE, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Ballhog", NULL, {.routine = M_HandleItemToggles}, KITEM_BALLHOG, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Lightning Shields", NULL, {.routine = M_HandleItemToggles}, KITEM_LIGHTNINGSHIELD, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Bubble Shields", NULL, {.routine = M_HandleItemToggles}, KITEM_BUBBLESHIELD, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Flame Shields", NULL, {.routine = M_HandleItemToggles}, KITEM_FLAMESHIELD, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Hyudoros", NULL, {.routine = M_HandleItemToggles}, KITEM_HYUDORO, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Hyudoro", NULL, {.routine = M_HandleItemToggles}, KITEM_HYUDORO, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Drop Target", NULL, {.routine = M_HandleItemToggles}, KITEM_DROPTARGET, sfx_s258}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Land Mine", NULL, {.routine = M_HandleItemToggles}, KITEM_LANDMINE, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Pogo Spring", NULL, {.routine = M_HandleItemToggles}, KITEM_POGOSPRING, 0}, {IT_KEYHANDLER | IT_NOTHING, NULL, "Invinciblity", NULL, {.routine = M_HandleItemToggles}, KITEM_INVINCIBILITY, 0}, {IT_KEYHANDLER | IT_NOTHING, NULL, "Grow", NULL, {.routine = M_HandleItemToggles}, KITEM_GROW, 0}, {IT_KEYHANDLER | IT_NOTHING, NULL, "Shrink", NULL, {.routine = M_HandleItemToggles}, KITEM_SHRINK, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, NULL, NULL, {.routine = M_HandleItemToggles}, 255, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Gardentop", NULL, {.routine = M_HandleItemToggles}, KITEM_GARDENTOP, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Pogo Springs", NULL, {.routine = M_HandleItemToggles}, KITEM_POGOSPRING, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Kitchen Sinks", NULL, {.routine = M_HandleItemToggles}, KITEM_KITCHENSINK, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, NULL, NULL, {.routine = M_HandleItemToggles}, 255, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, NULL, NULL, {.routine = M_HandleItemToggles}, 255, 0} + {IT_KEYHANDLER | IT_NOTHING, NULL, "Lightning Shield", NULL, {.routine = M_HandleItemToggles}, KITEM_LIGHTNINGSHIELD, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Bubble Shield", NULL, {.routine = M_HandleItemToggles}, KITEM_BUBBLESHIELD, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Flame Shield", NULL, {.routine = M_HandleItemToggles}, KITEM_FLAMESHIELD, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Kitchen Sink", NULL, {.routine = M_HandleItemToggles}, KITEM_KITCHENSINK, 0} }; menu_t OPTIONS_GameplayItemsDef = { diff --git a/src/menus/play-char-select.c b/src/menus/play-char-select.c index 19160c5c7..a6ac59d1a 100644 --- a/src/menus/play-char-select.c +++ b/src/menus/play-char-select.c @@ -905,16 +905,6 @@ static boolean M_HandleCharacterGrid(setup_player_t *p, UINT8 num) if (num == 0 && setup_numplayers == 1 && setup_maxpage && !forceskin) // ONLY one player. { if (M_MenuButtonPressed(num, MBT_L)) - { - if (setup_page == 0) - setup_page = setup_maxpage; - else - setup_page--; - - S_StartSound(NULL, sfx_s3k63); - M_SetMenuDelay(num); - } - else if (M_MenuButtonPressed(num, MBT_R)) { if (setup_page == setup_maxpage) setup_page = 0; @@ -1295,6 +1285,11 @@ boolean M_CharacterSelectHandler(INT32 choice) if (i > 0 && setup_player[i-1].mdepth < CSSTEP_READY) continue; } + + if (M_MenuButtonPressed(i, MBT_R)) + { + p->showextra ^= true; + } } switch (p->mdepth) diff --git a/src/menus/play-local-1.c b/src/menus/play-local-1.c index 632ef172d..edcfc2bc0 100644 --- a/src/menus/play-local-1.c +++ b/src/menus/play-local-1.c @@ -59,7 +59,7 @@ void M_SetupGametypeMenu(INT32 choice) if (!anyunlocked) { // Only one non-Back entry, let's skip straight to Race. - M_SetupRaceMenu(-1); + M_SetupRaceMenu(0); return; } } diff --git a/src/objects/monitor.c b/src/objects/monitor.c index 50d13ee3c..8f5be65eb 100644 --- a/src/objects/monitor.c +++ b/src/objects/monitor.c @@ -602,6 +602,12 @@ Obj_MonitorGetDamage damage = HEALTHFACTOR + (FixedMul(weight, HEALTHFACTOR) / 9); + + if (inflictor->player->tiregrease > 0) + { + damage *= 3; // Do 3x the damage if the player is in spring grease state + } + if (inflictor->scale > mapobjectscale) { damage = P_ScaleFromMap(damage, inflictor->scale); diff --git a/src/objects/shrink.c b/src/objects/shrink.c index 7884201c2..debefa955 100644 --- a/src/objects/shrink.c +++ b/src/objects/shrink.c @@ -542,50 +542,51 @@ boolean Obj_ShrinkLaserCollide(mobj_t *gun, mobj_t *victim) // Take away Shrink. K_RemoveGrowShrink(victim->player); } - else + + victim->player->growshrinktimer += 3*TICRATE; + S_StartSound(victim, sfx_kc5a); + + if (prevTimer <= 0) { - victim->player->growshrinktimer += 3*TICRATE; - S_StartSound(victim, sfx_kc5a); + victim->scalespeed = mapobjectscale/TICRATE; + victim->destscale = FixedMul(mapobjectscale, GROW_SCALE); - if (prevTimer <= 0) + if (K_PlayerShrinkCheat(victim->player) == true) { - victim->scalespeed = mapobjectscale/TICRATE; - victim->destscale = FixedMul(mapobjectscale, GROW_SCALE); - - if (K_PlayerShrinkCheat(victim->player) == true) - { - victim->destscale = FixedMul(victim->destscale, SHRINK_SCALE); - } - - if (victim->player->invincibilitytimer > 0) - { - ; // invincibility has priority in P_RestoreMusic, no point in starting here - } - else if (P_IsLocalPlayer(victim->player) == true) - { - S_ChangeMusicSpecial("kgrow"); - } - else //used to be "if (P_IsDisplayPlayer(victim->player) == false)" - { - S_StartSound(victim, sfx_alarmg); - } - - P_RestoreMusic(victim->player); + victim->destscale = FixedMul(victim->destscale, SHRINK_SCALE); } + + if (victim->player->invincibilitytimer > 0) + { + ; // invincibility has priority in P_RestoreMusic, no point in starting here + } + else if (P_IsLocalPlayer(victim->player) == true) + { + S_ChangeMusicSpecial("kgrow"); + } + else //used to be "if (P_IsDisplayPlayer(victim->player) == false)" + { + S_StartSound(victim, sfx_alarmg); + } + + P_RestoreMusic(victim->player); + } } else { if (prevTimer > 0) { - // Take away Grow. - K_RemoveGrowShrink(victim->player); + // Dock some Grow time. + // (Hack-adjacent: Always make sure there's a tic left so standard timer handling can remove the effect properly.) + victim->player->growshrinktimer -= min(3*TICRATE/2, victim->player->growshrinktimer - 1); + S_StartSound(victim, sfx_s3k40); } else { // Start shrinking! victim->player->growshrinktimer -= 5*TICRATE; - S_StartSound(victim, sfx_kc59); + S_StartSound(victim, sfx_kc59); // I don't think you ever get to hear this while the pohbee laser is in your teeth, but best effort. if (prevTimer >= 0) { diff --git a/src/objects/ufo.c b/src/objects/ufo.c index b47e19c5f..a4c46d58c 100644 --- a/src/objects/ufo.c +++ b/src/objects/ufo.c @@ -23,6 +23,7 @@ #include "../z_zone.h" #include "../k_waypoint.h" #include "../k_specialstage.h" +#include "../r_skins.h" #define UFO_BASE_SPEED (42 * FRACUNIT) // UFO's slowest speed. #define UFO_SPEEDUP (FRACUNIT >> 1) // Acceleration @@ -671,6 +672,16 @@ boolean Obj_SpecialUFODamage(mobj_t *ufo, mobj_t *inflictor, mobj_t *source, UIN return false; } + if (source->player) + { + UINT32 skinflags = (demo.playback) + ? demo.skinlist[demo.currentskinid[(source->player-players)]].flags + : skins[source->player->skin].flags; + if (skinflags & SF_IRONMAN) + SetRandomFakePlayerSkin(source->player, true); + } + + // Speed up on damage! ufo_speed(ufo) += addSpeed; @@ -700,6 +711,7 @@ boolean Obj_SpecialUFODamage(mobj_t *ufo, mobj_t *inflictor, mobj_t *source, UIN S_StopSoundByID(ufo, sfx_clawzm); P_StartQuake(64<health -= damage; + return true; } diff --git a/src/p_mobj.c b/src/p_mobj.c index 52e48da51..d9920a928 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -46,6 +46,7 @@ #include "k_collide.h" #include "k_objects.h" #include "k_grandprix.h" +#include "k_director.h" static CV_PossibleValue_t CV_BobSpeed[] = {{0, "MIN"}, {4*FRACUNIT, "MAX"}, {0, NULL}}; consvar_t cv_movebob = CVAR_INIT ("movebob", "1.0", CV_FLOAT|CV_SAVE, CV_BobSpeed, NULL); @@ -11827,6 +11828,22 @@ void P_SpawnPlayer(INT32 playernum) K_SpawnPlayerBattleBumpers(p); } } + + // I'm not refactoring the loop at the top of this file. + pcount = 0; + + for (i = 0; i < MAXPLAYERS; ++i) + { + if (G_CouldView(i)) + { + pcount++; + } + } + + // Spectating when there is literally any other player in + // the level enables director cam. + // TODO: how do we support splitscreen? + K_ToggleDirector(players[consoleplayer].spectator && pcount > 0); } void P_AfterPlayerSpawn(INT32 playernum) diff --git a/src/p_spec.c b/src/p_spec.c index e252bf374..0317599e4 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -9100,15 +9100,17 @@ void T_Pusher(pusher_t *p) thing->player->carry = CR_SLIDING; thing->angle = R_PointToAngle2(0, 0, xspeed, yspeed); + if (!demo.playback) { - angle_t angle = thing->player->angleturn; + angle_t angle = thing->player->boostangle; if (thing->angle - angle > ANGLE_180) - P_SetPlayerAngle(thing->player, angle - (angle - thing->angle) / 8); + thing->player->boostangle = angle - (angle - thing->angle) / 8; else - P_SetPlayerAngle(thing->player, angle + (thing->angle - angle) / 8); + thing->player->boostangle = angle + (thing->angle - angle) / 8; //P_SetPlayerAngle(thing->player, thing->angle); } + } if (p->exclusive) diff --git a/src/p_user.c b/src/p_user.c index bc5d699d8..223af8f47 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2120,7 +2120,6 @@ static void P_3dMovement(player_t *player) static void P_UpdatePlayerAngle(player_t *player) { angle_t angleChange = ANGLE_MAX; - UINT8 maxlatency; UINT8 p = UINT8_MAX; UINT8 i; @@ -2133,8 +2132,58 @@ static void P_UpdatePlayerAngle(player_t *player) } } - player->steering = K_UpdateSteeringValue(player->steering, player->cmd.turning); - angleChange = K_GetKartTurnValue(player, player->steering) << TICCMD_REDUCE; + // Don't apply steering just yet. If we make a correction, we'll need to adjust it. + INT16 targetsteering = K_UpdateSteeringValue(player->steering, player->cmd.turning); + angleChange = K_GetKartTurnValue(player, targetsteering) << TICCMD_REDUCE; + + if (!K_PlayerUsesBotMovement(player)) + { + // With a full slam on the analog stick, how far could we steer in either direction? + INT16 steeringRight = K_UpdateSteeringValue(player->steering, KART_FULLTURN); + angle_t maxTurnRight = K_GetKartTurnValue(player, steeringRight) << TICCMD_REDUCE; + INT16 steeringLeft = K_UpdateSteeringValue(player->steering, -KART_FULLTURN); + angle_t maxTurnLeft = K_GetKartTurnValue(player, steeringLeft) << TICCMD_REDUCE; + + // Grab local camera angle from ticcmd. Where do we actually want to go? + angle_t targetAngle = (player->cmd.angle) << TICCMD_REDUCE; + angle_t targetDelta = targetAngle - (player->mo->angle); + + if (targetDelta == angleChange || player->pflags & PF_DRIFTEND || (maxTurnRight == 0 && maxTurnLeft == 0)) + { + // We are where we need to be. + // ...Or we aren't, but shouldn't be able to steer. + player->steering = targetsteering; + // Alternatively, while in DRIFTEND we want to trust inputs for a bit, not camera. + // The game client doesn't know we're DRIFTEND until after a response gets back, + // so we momentarily ignore the camera angle and let the server trust our inputs instead. + // That way, even if you're steering blind, you get the intended "kick-out" effect. + } + else if (targetDelta >= ANGLE_180 && maxTurnLeft >= targetDelta) // Overshot, so just fudge it. + { + angleChange = targetDelta; + player->steering = targetsteering; + } + else if (targetDelta <= ANGLE_180 && maxTurnRight <= targetDelta) // Overshot, so just fudge it. + { + angleChange = targetDelta; + player->steering = targetsteering; + } + else if (targetDelta >= ANGLE_180 && maxTurnLeft < targetDelta) // Undershot, slam the stick. + { + angleChange = maxTurnLeft; + player->steering = steeringLeft; + } + else if (targetDelta <= ANGLE_180 && maxTurnRight > targetDelta) // Undershot, slam the stick. + { + angleChange = maxTurnRight; + player->steering = steeringRight; + } + } + else + { + // You're a bot. Go where you're supposed to go + player->steering = targetsteering; + } if (p == UINT8_MAX) { @@ -2144,33 +2193,8 @@ static void P_UpdatePlayerAngle(player_t *player) } else { - // During standard play, our latency can vary by up to 1 tic in either direction, even on a stable connection. - // This probably comes from differences in ticcmd dispatch vs consumption rate. Probably. - // Uncorrected, this 2-tic "wobble" causes camera corrections to sometimes be skipped or batched. - // So just use the highest recent value for the furthest possible search. - // We unset the correction after applying, anyway. - locallatency[p][leveltime%TICRATE] = maxlatency = player->cmd.latency; - for (i = 0; i < TICRATE; i++) - { - maxlatency = max(locallatency[p][i], maxlatency); - } - - UINT8 lateTic = ((leveltime - maxlatency) & TICCMD_LATENCYMASK); - UINT8 clearTic = ((localtic + 1) & TICCMD_LATENCYMASK); - player->angleturn += angleChange; player->mo->angle = player->angleturn; - - // Undo the ticcmd's old emulated angle, - // now that we added the actual game logic angle. - - while (lateTic != clearTic) - { - localdelta[p] -= localstoredeltas[p][lateTic]; - localstoredeltas[p][lateTic] = 0; - - lateTic = (lateTic - 1) & TICCMD_LATENCYMASK; - } } if (!cv_allowmlook.value || player->spectator == false) @@ -3162,7 +3186,13 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall if (demo.playback) { - focusangle = mo->angle; + // Hack-adjacent. + // Sometimes stale ticcmds send a weird angle at the start of the race. + // P_UpdatePlayerAngle knows to ignore cmd angle when you literally can't turn, so we do the same here. + if (leveltime > introtime) + focusangle = player->cmd.angle << TICCMD_REDUCE; + else + focusangle = mo->angle; // Just use something known sane. focusaiming = 0; } else @@ -4478,7 +4508,7 @@ void P_ForceLocalAngle(player_t *player, angle_t angle) if (player == &players[displayplayers[i]]) { localangle[i] = angle; - G_ResetAnglePrediction(player); + break; } } diff --git a/src/st_stuff.c b/src/st_stuff.c index 01350c6ce..289a6c3a7 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -1095,6 +1095,8 @@ void ST_preLevelTitleCardDrawer(void) // static void ST_overlayDrawer(void) { + const UINT8 viewnum = R_GetViewNumber(); + // hu_showscores = auto hide score/time/rings when tab rankings are shown if (!(hu_showscores && (netgame || multiplayer))) { @@ -1135,7 +1137,7 @@ static void ST_overlayDrawer(void) if (!hu_showscores && netgame && !mapreset) { - if (stplyr->spectator && LUA_HudEnabled(hud_textspectator)) + if (stplyr->spectator && displayplayers[viewnum] == g_localplayers[viewnum] && LUA_HudEnabled(hud_textspectator)) { const char *itemtxt = M_GetText("Item - Join Game");