From 8e5c8c4bea35d361f81e598dc0fdb39dc0057479 Mon Sep 17 00:00:00 2001 From: Tater Date: Sun, 2 Jun 2024 20:59:19 -0500 Subject: [PATCH] Allow client to re send past inputs to the server --- src/engine/client/client.cpp | 58 +++++++++++++++++++++++++ src/engine/client/client.h | 10 +++++ src/engine/server/server.cpp | 2 +- src/engine/shared/config_variables.h | 1 + src/engine/shared/protocol_ex.h | 3 +- src/game/client/components/controls.cpp | 11 +++++ 6 files changed, 83 insertions(+), 2 deletions(-) diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 4eefceb12cc..44847755773 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -275,6 +275,13 @@ void CClient::SendInput() Msg.AddInt(m_aPredTick[g_Config.m_ClDummy]); Msg.AddInt(Size); + // copy to sent inputs + int SendIndex = m_aPredTick[g_Config.m_ClDummy] % 200; + m_aSentInputs[i][SendIndex].m_AckGameTick = m_aAckGameTick[i]; + m_aSentInputs[i][SendIndex].m_PredTick = m_aPredTick[g_Config.m_ClDummy]; + m_aSentInputs[i][SendIndex].Size = Size; + mem_copy(m_aSentInputs[i][SendIndex].m_aData, m_aInputs[i][m_aCurrentInput[i]].m_aData, sizeof(m_aSentInputs[i][SendIndex].m_aData)); + m_aInputs[i][m_aCurrentInput[i]].m_Tick = m_aPredTick[g_Config.m_ClDummy]; m_aInputs[i][m_aCurrentInput[i]].m_PredictedTime = m_PredictedTime.Get(Now); m_aInputs[i][m_aCurrentInput[i]].m_PredictionMargin = PredictionMargin() * time_freq() / 1000; @@ -295,6 +302,53 @@ void CClient::SendInput() Force = true; } } + if(g_Config.m_ClResendInputs && m_ServerCapabilities.m_SortInputs) + { + // for each 20ms of prediction margin (1 tick duration) we can usefully send 1 extra input to the server + // incase the previous attempt was dropped + int ResendTicks = PredictionMargin() / (1000 / GameTickSpeed()); + for(int Dummy = 0; Dummy < NUM_DUMMIES; Dummy++) + { + if(!m_DummyConnected && Dummy != 0) + { + break; + } + int i = g_Config.m_ClDummy ^ Dummy; + + int FlushTick = 0; + + // figure out which tick will be the last one so we can flush the network + for(int Tick = 0; Tick < ResendTicks; Tick++) + { + int TargetTick = m_aPredTick[g_Config.m_ClDummy] - Tick; + int SendIndex = TargetTick % 200; + if(m_aSentInputs[i][SendIndex].m_PredTick == TargetTick) + FlushTick = Tick; + } + + for(int Tick = 0; Tick < ResendTicks; Tick++) + { + int TargetTick = m_aPredTick[g_Config.m_ClDummy] - Tick; + int SendIndex = TargetTick % 200; + + // we only want to send inputs that we already sent the server normally + if(m_aSentInputs[i][SendIndex].m_PredTick == TargetTick) + { + CMsgPacker Msg(NETMSG_INPUT, true); + Msg.AddInt(m_aSentInputs[i][SendIndex].m_AckGameTick); + Msg.AddInt(m_aSentInputs[i][SendIndex].m_PredTick); + Msg.AddInt(m_aSentInputs[i][SendIndex].Size); + for(int k = 0; k < m_aSentInputs[i][SendIndex].Size / 4; k++) + Msg.AddInt(m_aSentInputs[i][SendIndex].m_aData[k]); + + if(Tick == FlushTick || Tick == ResendTicks - 1) + SendMsg(i, &Msg, MSGFLAG_FLUSH); + else + SendMsg(i, &Msg, 0); + } + } + } + } } const char *CClient::LatestVersion() const @@ -1311,6 +1365,10 @@ static CServerCapabilities GetServerCapabilities(int Version, int Flags) { Result.m_SyncWeaponInput = Flags & SERVERCAPFLAG_SYNCWEAPONINPUT; } + if(Version >= 6) + { + Result.m_SortInputs = Flags & SERVERCAPFLAG_SORTINPUTS; + } return Result; } diff --git a/src/engine/client/client.h b/src/engine/client/client.h index bc587744521..a6759d0ccf2 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -55,6 +55,7 @@ class CServerCapabilities bool m_PingEx = false; bool m_AllowDummy = false; bool m_SyncWeaponInput = false; + bool m_SortInputs = false; }; class CClient : public IClient, public CDemoPlayer::IListener @@ -175,6 +176,15 @@ class CClient : public IClient, public CDemoPlayer::IListener int64_t m_Time; } m_aInputs[NUM_DUMMIES][200]; + struct CSentInput + { + int m_AckGameTick; + int m_PredTick; + int Size; + int m_aData[MAX_INPUT_SIZE]; + }; + CSentInput m_aSentInputs[NUM_DUMMIES][200]; + int m_aCurrentInput[NUM_DUMMIES] = {0, 0}; bool m_LastDummy = false; bool m_DummySendConnInfo = false; diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 6a9fdde9f52..bf93f4563f0 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -1199,7 +1199,7 @@ void CServer::SendCapabilities(int ClientId) { CMsgPacker Msg(NETMSG_CAPABILITIES, true); Msg.AddInt(SERVERCAP_CURVERSION); // version - Msg.AddInt(SERVERCAPFLAG_DDNET | SERVERCAPFLAG_CHATTIMEOUTCODE | SERVERCAPFLAG_ANYPLAYERFLAG | SERVERCAPFLAG_PINGEX | SERVERCAPFLAG_ALLOWDUMMY | SERVERCAPFLAG_SYNCWEAPONINPUT); // flags + Msg.AddInt(SERVERCAPFLAG_DDNET | SERVERCAPFLAG_CHATTIMEOUTCODE | SERVERCAPFLAG_ANYPLAYERFLAG | SERVERCAPFLAG_PINGEX | SERVERCAPFLAG_ALLOWDUMMY | SERVERCAPFLAG_SYNCWEAPONINPUT | SERVERCAPFLAG_SORTINPUTS); // flags SendMsg(&Msg, MSGFLAG_VITAL, ClientId); } diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index c53136b379f..a12a1662287 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -22,6 +22,7 @@ MACRO_CONFIG_INT(ClAntiPingSmooth, cl_antiping_smooth, 0, 0, 1, CFGFLAG_CLIENT | MACRO_CONFIG_INT(ClAntiPingGunfire, cl_antiping_gunfire, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Predict gunfire and show predicted weapon physics (with cl_antiping_grenade 1 and cl_antiping_weapons 1)") MACRO_CONFIG_INT(ClPredictionMargin, cl_prediction_margin, 10, 1, 300, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Prediction margin in ms (adds latency, can reduce lag from ping jumps)") MACRO_CONFIG_INT(ClSubTickAiming, cl_sub_tick_aiming, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Send aiming data at sub-tick accuracy") +MACRO_CONFIG_INT(ClResendInputs, cl_resend_inputs, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Send Inputs to server multiple times when prediction margin is high enough") MACRO_CONFIG_INT(ClNameplates, cl_nameplates, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show name plates") MACRO_CONFIG_INT(ClAfkEmote, cl_afk_emote, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show zzz emote next to afk players") diff --git a/src/engine/shared/protocol_ex.h b/src/engine/shared/protocol_ex.h index 6ef72f163fb..7d18d60cbbf 100644 --- a/src/engine/shared/protocol_ex.h +++ b/src/engine/shared/protocol_ex.h @@ -23,13 +23,14 @@ enum UNPACKMESSAGE_OK, UNPACKMESSAGE_ANSWER, - SERVERCAP_CURVERSION = 5, + SERVERCAP_CURVERSION = 6, SERVERCAPFLAG_DDNET = 1 << 0, SERVERCAPFLAG_CHATTIMEOUTCODE = 1 << 1, SERVERCAPFLAG_ANYPLAYERFLAG = 1 << 2, SERVERCAPFLAG_PINGEX = 1 << 3, SERVERCAPFLAG_ALLOWDUMMY = 1 << 4, SERVERCAPFLAG_SYNCWEAPONINPUT = 1 << 5, + SERVERCAPFLAG_SORTINPUTS = 1 << 6, }; void RegisterUuids(CUuidManager *pManager); diff --git a/src/game/client/components/controls.cpp b/src/game/client/components/controls.cpp index ab7e6ae1a71..6e0e7a4cb37 100644 --- a/src/game/client/components/controls.cpp +++ b/src/game/client/components/controls.cpp @@ -201,6 +201,8 @@ void CControls::OnMessage(int Msg, void *pRawMsg) int CControls::SnapInput(int *pData) { static int64_t LastSendTime = 0; + static int HotSendCount = 0; + bool Send = false; // update player state @@ -339,10 +341,19 @@ int CControls::SnapInput(int *pData) else if(m_aInputData[g_Config.m_ClDummy].m_PrevWeapon != m_aLastData[g_Config.m_ClDummy].m_PrevWeapon) Send = true; + HotSendCount = std::max(0, HotSendCount - 1); + + if(Send) + HotSendCount = 5; + // send at at least 10hz if(time_get() > LastSendTime + time_freq() / 25) Send = true; + // always send for 5 ticks in a row when a new input happens + if(HotSendCount > 0) + Send = true; + if(m_pClient->m_Snap.m_pLocalCharacter && m_pClient->m_Snap.m_pLocalCharacter->m_Weapon == WEAPON_NINJA && (m_aInputData[g_Config.m_ClDummy].m_Direction || m_aInputData[g_Config.m_ClDummy].m_Jump || m_aInputData[g_Config.m_ClDummy].m_Hook)) Send = true; }