diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 320c36996..5ab749814 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -103,6 +103,30 @@ static tic_t freezetimeout[MAXNETNODES]; // Until when can this node freeze the UINT16 pingmeasurecount = 1; UINT32 realpingtable[MAXPLAYERS]; //the base table of ping where an average will be sent to everyone. UINT32 playerpingtable[MAXPLAYERS]; //table of player latency values. + +#define GENTLEMANSMOOTHING (TICRATE) +static tic_t reference_lag; +static UINT8 spike_time; +tic_t lowest_lag; +tic_t simulated_lag; // just for ping readout without netticbuffer added +boolean server_lagless; + +static void Lagless_OnChange(void) +{ + /* don't back out of dishonesty, or go lagless after playing honestly */ + if (cv_lagless.value && gamestate == GS_LEVEL) + server_lagless = true; + + /*if (cv_lagless.value) + HU_AddChatText(M_GetText("\x82*Gentlemans Delay has been disabled for Serverplayer."), false); + else + HU_AddChatText(M_GetText("\x82*Gentlemans Delay will be enabled for Serverplayer."), false);*/ +} + +static CV_PossibleValue_t mindelay_cons_t[] = {{0, "MIN"}, {30, "MAX"}, {0, NULL}}; +consvar_t cv_mindelay = {"mindelay", "0", CV_SAVE, mindelay_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_lagless = {"serverlagless", "On", CV_SAVE|CV_CALL|CV_NOINIT, CV_OnOff, Lagless_OnChange, 0, NULL, NULL, 0, 0, NULL}; // this should be a netvar Zzz... + SINT8 nodetoplayer[MAXNETNODES]; SINT8 nodetoplayer2[MAXNETNODES]; // say the numplayer for this node if any (splitscreen) SINT8 nodetoplayer3[MAXNETNODES]; // say the numplayer for this node if any (splitscreen == 2) @@ -140,7 +164,7 @@ static UINT8 gamestate_resend_counter[MAXNETNODES]; boolean hu_stopped = false; // Client specific -static ticcmd_t localcmds[MAXSPLITSCREENPLAYERS]; +static ticcmd_t localcmds[MAXSPLITSCREENPLAYERS][MAXGENTLEMENDELAY]; static boolean cl_packetmissed; // here it is for the secondary local player (splitscreen) static UINT8 mynode; // my address pointofview server @@ -500,10 +524,15 @@ static void D_Clearticcmd(tic_t tic) void D_ResetTiccmds(void) { - INT32 i; + INT32 i, j; for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - memset(&localcmds[i], 0, sizeof(ticcmd_t)); + { + for (j = 0; j < MAXGENTLEMENDELAY; j++) + { + memset(&localcmds[i][j], 0, sizeof(ticcmd_t)); + } + } // Reset the net command list for (i = 0; i < TEXTCMD_HASH_SIZE; i++) @@ -6069,27 +6098,78 @@ static void CL_SendClientCmd(void) } else if (gamestate != GS_NULL) { + UINT8 lagDelay = 0; + + if (lowest_lag > 0) + { + // Gentlemens' ping. + lagDelay = min(lowest_lag, MAXGENTLEMENDELAY); + + // Is our connection worse than our current gentleman point? + // Make sure it stays that way for a bit before increasing delay levels. + if (lagDelay > reference_lag) + { + spike_time++; + if (spike_time >= GENTLEMANSMOOTHING) + { + // Okay, this is genuinely the new baseline delay. + reference_lag = lagDelay; + spike_time = 0; + } + else + { + // Just a temporary fluctuation, ignore it. + lagDelay = reference_lag; + } + } + else + { + reference_lag = lagDelay; // Adjust quickly if the connection improves. + spike_time = 0; + } + + /* + if (server) // Clients have to wait for the gamestate to make it back. Servers don't! + lagDelay *= 2; // Simulate the HELLFUCK NIGHTMARE of a complete round trip. + */ + + // [deep breath in] + // Plausible, elegant explanation that is WRONG AND SUPER HARMFUL. + // Clients with stable connections were adding their mindelay to network delay, + // even when their mindelay was as high or higher than network delay—which made + // client delay APPEAR slower than host mindelay, by the exact value that made + // "lmao just double it" make sense at the time. + // + // While this fix made client connections match server mindelay in our most common + // test environment, it also masked an issue that seriously affected online handling + // responsiveness, completely ruining our opportunity to further investigate it! + // + // See UpdatePingTable. + // I am taking this shitty code to my grave as an example of "never trust your brain". + // -Tyron 2024-05-15 + } + packetsize = sizeof (clientcmd_pak); - G_MoveTiccmd(&netbuffer->u.clientpak.cmd, &localcmds[0], 1); - netbuffer->u.clientpak.consistancy = SHORT(consistancy[gametic%TICQUEUE]); + G_MoveTiccmd(&netbuffer->u.clientpak.cmd, &localcmds[0][lagDelay], 1); + netbuffer->u.clientpak.consistancy = SHORT(consistancy[gametic % TICQUEUE]); if (splitscreen || botingame) // Send a special packet with 2 cmd for splitscreen { netbuffer->packettype = (mis ? PT_CLIENT2MIS : PT_CLIENT2CMD); packetsize = sizeof (client2cmd_pak); - G_MoveTiccmd(&netbuffer->u.client2pak.cmd2, &localcmds[1], 1); + G_MoveTiccmd(&netbuffer->u.client2pak.cmd2, &localcmds[1][lagDelay], 1); if (splitscreen > 1) { netbuffer->packettype = (mis ? PT_CLIENT3MIS : PT_CLIENT3CMD); packetsize = sizeof (client3cmd_pak); - G_MoveTiccmd(&netbuffer->u.client3pak.cmd3, &localcmds[2], 1); + G_MoveTiccmd(&netbuffer->u.client3pak.cmd3, &localcmds[2][lagDelay], 1); if (splitscreen > 2) { netbuffer->packettype = (mis ? PT_CLIENT4MIS : PT_CLIENT4CMD); packetsize = sizeof (client4cmd_pak); - G_MoveTiccmd(&netbuffer->u.client4pak.cmd4, &localcmds[3], 1); + G_MoveTiccmd(&netbuffer->u.client4pak.cmd4, &localcmds[3][lagDelay], 1); } } } @@ -6265,8 +6345,15 @@ static void SV_SendTics(void) // static inline void CreateNewLocalCMD(UINT8 p, INT32 realtics) { - G_BuildTiccmd(&localcmds[p], realtics, p+1); - localcmds[p].angleturn |= TICCMD_RECEIVED; + INT32 i; + + for (i = MAXGENTLEMENDELAY-1; i > 0; i--) + { + G_MoveTiccmd(&localcmds[p][i], &localcmds[p][i-1], 1); + } + + G_BuildTiccmd(&localcmds[p][0], realtics, p+1); + localcmds[p][0].angleturn |= TICCMD_RECEIVED; } static void Local_Maketic(INT32 realtics) @@ -6278,8 +6365,8 @@ static void Local_Maketic(INT32 realtics) // game responder calls HU_Responder, AM_Responder, F_Responder, // and G_MapEventsToControls if (!dedicated) rendergametic = gametic; - // translate inputs (keyboard/mouse/joystick) into game controls + // translate inputs (keyboard/mouse/joystick) into game controls for (i = 0; i <= splitscreen; i++) { CreateNewLocalCMD(i, realtics); @@ -6568,17 +6655,62 @@ static tic_t gametime = 0; static void UpdatePingTable(void) { + tic_t fastest; + tic_t lag; + INT32 i; + if (server) { - if (netgame && !(gametime % 35)) // update once per second. + if (Playing() && !(gametime % 8)) // Value chosen based on _my vibes man_ PingUpdate(); + + fastest = 0; + // update node latency values so we can take an average later. for (i = 0; i < MAXPLAYERS; i++) - if (playeringame[i] && playernode[i] != UINT8_MAX) + { + if (playeringame[i] && playernode[i] > 0 && playernode[i] != UINT8_MAX) + { realpingtable[i] += GetLag(playernode[i]); + + if (!server_lagless && !players[i].spectator) + { + lag = playerpingtable[i]; + if (! fastest || lag < fastest) + fastest = lag; + } + } + } + + if (server_lagless) + lowest_lag = 0; + else + lowest_lag = fastest; + + // Don't gentleman below your mindelay + if (lowest_lag < (tic_t)cv_mindelay.value) + lowest_lag = (tic_t)cv_mindelay.value; + + simulated_lag = lowest_lag; + pingmeasurecount++; } + else // We're a client, handle mindelay on the way out. + { + // Previously (neededtic - gametic) - WRONG VALUE! + // Pretty sure that's measuring jitter, not RTT. + // Stable connections would be punished by adding their mindelay to network delay! + tic_t mydelay = playerpingtable[consoleplayer]; + + if (mydelay < (tic_t)cv_mindelay.value) + { + lowest_lag = ((tic_t)cv_mindelay.value - mydelay); + simulated_lag = (tic_t)cv_mindelay.value; + } + else + lowest_lag = simulated_lag = 0; + } } #ifdef HOLEPUNCH diff --git a/src/d_clisrv.h b/src/d_clisrv.h index 94c7c6827..d8a1acc22 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -41,6 +41,15 @@ applications may follow different packet versions. #define BACKUPTICS 32 #define TICQUEUE 512 // more than enough for most timeouts.... #define MAXTEXTCMD 256 + +// No. of tics your controls can be delayed by. + +// TODO: Instead of storing a ton of extra cmds for gentlemens' delay, +// keep them in a linked-list, with timestamps to discard everything that's older than already sent. +// That will support any amount of lag, and be less wasteful for clients who don't use it. +// This just works as a quick implementation. +#define MAXGENTLEMENDELAY TICRATE + // // Packet structure // @@ -576,6 +585,11 @@ extern UINT32 realpingtable[MAXPLAYERS]; extern UINT32 playerpingtable[MAXPLAYERS]; extern tic_t servermaxping; +extern boolean server_lagless; +extern tic_t simulated_lag; +extern tic_t lowest_lag; +extern consvar_t cv_mindelay, cv_lagless; + extern consvar_t #ifdef VANILLAJOINNEXTROUND cv_joinnextround, diff --git a/src/d_netcmd.c b/src/d_netcmd.c index a12257ba2..739b47050 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -919,6 +919,8 @@ void D_RegisterClientCommands(void) CV_RegisterVar(&cv_rollingdemos); CV_RegisterVar(&cv_netstat); CV_RegisterVar(&cv_netticbuffer); + CV_RegisterVar(&cv_mindelay); + CV_RegisterVar(&cv_lagless); #ifdef NETGAME_DEVMODE CV_RegisterVar(&cv_fishcake); diff --git a/src/g_game.c b/src/g_game.c index 33e58cb8a..e69cf6428 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1261,6 +1261,8 @@ static void G_DoLoadLevel(boolean resetplayer) // clear hud messages remains (usually from game startup) CON_ClearHUD(); + server_lagless = cv_lagless.value; + G_ResetAllDeviceRumbles(); } diff --git a/src/hu_stuff.c b/src/hu_stuff.c index 934e3ea43..e18f4e872 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -1154,7 +1154,7 @@ void HU_Ticker(void) || hu_redownloadinggamestate #endif ) - resynch_ticker++; //tic tic tic tic tic + resynch_ticker++; //tic tic tic tic tic HU_TickSongCredits(); } @@ -2356,92 +2356,134 @@ Ping_gfx_num (int lag) return 4; } +static int +Ping_gfx_color (int lag) +{ + if (lag < 2) + return SKINCOLOR_JAWZ; + else if (lag < 4) + return SKINCOLOR_MINT; + else if (lag < 7) + return SKINCOLOR_GOLD; + else if (lag < 10) + return SKINCOLOR_RED; + else + return SKINCOLOR_WHITE; // SKINCOLOR_MAGENTA +} + +static const UINT8 * +Ping_gfx_colormap (UINT32 lag, boolean gentleman) +{ + UINT8 *colormap = NULL; + + if (K_UseColorHud()) + colormap = R_GetTranslationColormap(TC_RAINBOW, Ping_gfx_color(lag), GTC_CACHE); + + if (servermaxping && lag > servermaxping && hu_tick < 4) + { + // flash ping red if too high + colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_RASPBERRY, GTC_CACHE); + } + + return colormap; +} + +static UINT32 +Ping_conversion (UINT32 lag) +{ + if (cv_pingmeasurement.value) + { + lag = (INT32)(lag * (1000.00f / TICRATE)); + } + + return lag; +} + // // HU_drawPing // -void HU_drawPing(INT32 x, INT32 y, UINT32 lag, INT32 flags) +void HU_drawPlayerPing(INT32 x, INT32 y, INT32 pnum, INT32 flags) { - UINT8 *colormap = NULL; INT32 measureid = cv_pingmeasurement.value ? 1 : 0; INT32 gfxnum; // gfx to draw - + //SRB2/Kart v1.0 style UINT8 numbars = 0; // how many ping bars do we draw? UINT8 barcolor = 31; // color we use for the bars (green, yellow, red or black) SINT8 i = 0; SINT8 yoffset = 6; - //INT32 dx; - gfxnum = Ping_gfx_num(lag); - - if (!cv_pingstyle.value) + UINT32 lag = playerpingtable[pnum]; + const boolean gentleman = (cv_mindelay.value && (lag < (tic_t)simulated_lag)); + + if (gentleman) { + lag = simulated_lag; + } + + if (cv_pingstyle.value == 0) // kart + { + gfxnum = Ping_gfx_num(lag); + if (measureid == 1) V_DrawScaledPatch(x+11 - pingmeasure[measureid]->width, y+9, flags, pingmeasure[measureid]); if (cv_pingicon.value) V_DrawScaledPatch(x+2, y, flags, pinggfx[gfxnum]); - if (servermaxping && lag > servermaxping && hu_tick < 4) - colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_RASPBERRY, GTC_CACHE); // flash ping red if too high - - if (cv_pingmeasurement.value) - lag = (INT32)(lag * (1000.00f / TICRATE)); - - x = V_DrawPingNum(x + (measureid == 1 ? 11 - pingmeasure[measureid]->width : 10), y+9, flags, lag, colormap); + x = V_DrawPingNum(x + (measureid == 1 ? 11 - pingmeasure[measureid]->width : 10), y+9, flags, Ping_conversion(lag), Ping_gfx_colormap(lag, gentleman)); if (measureid == 0) - V_DrawScaledPatch(x+1 - pingmeasure[measureid]->width, y+9, flags, pingmeasure[measureid]); + V_DrawScaledPatch(x+1 - pingmeasure[measureid]->width, y+9, flags, pingmeasure[measureid]); } - else if (cv_pingstyle.value) // old style ping + else if (cv_pingstyle.value == 1) // old style ping { - if (cv_pingicon.value) - { - if (lag < 4) - { - numbars = 3; - barcolor = 184; - } - else if (lag < 7) - { - numbars = 2; // Apparently ternaries w/ multiple statements don't look good in C so I decided against it. - barcolor = 103; - } - else if (lag < 10) - { - numbars = 1; - barcolor = 155; // need a better red - } - else // brazil - { - numbars = 0; - barcolor = 31; - } - } - - if (cv_pingmeasurement.value) - lag = (INT32)(lag * (1000.00f / TICRATE)); - if (vid.width >= 640) // how sad, we're using a shit resolution. { if (measureid == 1) { - //dx = x+1 - (V_SmallStringWidth(va("%dms", lag), V_ALLOWLOWERCASE|flags)/2); - //V_DrawSmallString(dx, y+4, V_ALLOWLOWERCASE|flags, va("%dms", lag)); - V_DrawRightAlignedSmallString(x+12, y+13, V_ALLOWLOWERCASE|flags, va("%dms", lag)); + V_DrawRightAlignedSmallString(x+12, y+13, V_ALLOWLOWERCASE|flags, va("%dms", Ping_conversion(lag))); } else if (measureid == 0) { - //dx = x+1 - (V_SmallStringWidth(va("d%d", lag), flags)/2); - //V_DrawSmallString(dx, y+4, flags, va("d%d", lag)); - V_DrawRightAlignedSmallString(x+12, y+13, flags, va("d%d", lag)); + V_DrawRightAlignedSmallString(x+12, y+13, flags, va("d%d", Ping_conversion(lag))); } } if (cv_pingicon.value) - { - for (i=0; (i<3); i++) // Draw the ping bar + { + switch (lag) + { + case 0 ... 1: + numbars = 3; + barcolor = 215; // Blue + break; + case 2 ... 3: + numbars = 3; + barcolor = 184; // Green + break; + case 4 ... 6: + numbars = 2; // Apparently ternaries w/ multiple statements don't look good in C so I decided against it. + barcolor = 103; // Yellow + break; + case 7 ... 9: + numbars = 1; + barcolor = 155; // Red + break; + default: // Brazil + numbars = 0; + barcolor = 31; // black + break; + } + + if (gentleman) + { + barcolor = 194; // make it purplish + // bars get indirectly set earlier + } + + for (i = 0; (i < 3); i++) // Draw the ping bar { V_DrawFill(x+2 *(i-1)+7, y+8+yoffset-4, 2, 8-yoffset, 31|flags); if (i < numbars) @@ -2535,8 +2577,10 @@ static inline void HU_DrawSpectatorTicker(void) } } - if (netgame && i != serverplayer) - HU_drawPing((templength - duptweak)+8, height-20, playerpingtable[i], V_TRANSLUCENT); + if ((netgame && i != serverplayer) || (cv_mindelay.value && P_IsLocalPlayer(&players[i]))) + { + HU_drawPlayerPing((templength - duptweak)+8, height-20, i, V_TRANSLUCENT); + } } if ((length += len) >= dupadjust+8) diff --git a/src/hu_stuff.h b/src/hu_stuff.h index acc815617..00ee1a1e7 100644 --- a/src/hu_stuff.h +++ b/src/hu_stuff.h @@ -119,7 +119,7 @@ void HU_Drawer(void); char HU_dequeueChatChar(void); void HU_Erase(void); void HU_clearChatChars(void); -void HU_drawPing(INT32 x, INT32 y, UINT32 ping, INT32 flags); // Lat': Ping drawer for scoreboard. +void HU_drawPlayerPing(INT32 x, INT32 y, INT32 pnum, INT32 flags); // Lat': Ping drawer for scoreboard. //void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer); //void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer); void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer, INT32 hilicol); diff --git a/src/k_kart.c b/src/k_kart.c index 48723365c..ae106f84b 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -9122,9 +9122,10 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I if (players[tab[i].num].spectator || !players[tab[i].num].mo) continue; //ignore them. - if (netgame // don't draw it offline - && tab[i].num != serverplayer) - HU_drawPing(x + ((i < 8) ? -17 : rightoffset + 11), y-4, playerpingtable[tab[i].num], 0); + if ((netgame && tab[i].num != serverplayer) || (cv_mindelay.value && P_IsLocalPlayer(&players[tab[i].num]))) + { + HU_drawPlayerPing(x + ((i < 8) ? -17 : rightoffset + 11), y-4, tab[i].num, 0); + } STRBUFCPY(strtime, tab[i].name); diff --git a/src/screen.c b/src/screen.c index 249c7225c..5ae4f8d72 100644 --- a/src/screen.c +++ b/src/screen.c @@ -515,13 +515,13 @@ void SCR_DisplayTicRate(void) void SCR_DisplayLocalPing(void) { - UINT32 ping = playerpingtable[consoleplayer]; // consoleplayer's ping is everyone's ping in a splitnetgame :P + UINT32 ping = playerpingtable[consoleplayer]; INT32 pingflags = V_LocalTransFlag()|V_SNAPTOBOTTOM|V_SNAPTORIGHT; if (cv_showping.value == 1 || (cv_showping.value == 2 && ping > servermaxping)) // only show 2 (warning) if our ping is at a bad level { INT32 dispy = (cv_ticrate.value == 1) ? 165 : ((cv_ticrate.value == 2 || cv_ticrate.value == 4) ? 172 : ((cv_ticrate.value == 3) ? 163 : 181)); // absolute buttpain - - HU_drawPing(308, dispy, ping, pingflags); + + HU_drawPlayerPing(308, dispy, consoleplayer, pingflags); // consoleplayer's ping is everyone's ping in a splitnetgame :P } } diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c index 83564bfab..1f9987fa6 100644 --- a/src/sdl/i_video.c +++ b/src/sdl/i_video.c @@ -1472,11 +1472,14 @@ void I_FinishUpdate(void) if (I_SkipFrame()) return; - if (cv_ticrate.value && st_overlay) - SCR_DisplayTicRate(); + if (st_overlay) + { + if (cv_ticrate.value) + SCR_DisplayTicRate(); - if (cv_showping.value && netgame && consoleplayer != serverplayer && st_overlay) - SCR_DisplayLocalPing(); + if (cv_showping.value && ((netgame && consoleplayer != serverplayer) || (simulated_lag != 0 && consoleplayer == serverplayer && Playing()))) + SCR_DisplayLocalPing(); + } #ifdef HAVE_DISCORDRPC if (discordRequestList != NULL)