diff --git a/r5dev/common/global.cpp b/r5dev/common/global.cpp index 54b8fe26..b0e9ca38 100644 --- a/r5dev/common/global.cpp +++ b/r5dev/common/global.cpp @@ -121,6 +121,7 @@ ConVar* sv_forceChatToTeamOnly = nullptr; ConVar* sv_single_core_dedi = nullptr; ConVar* sv_maxunlag = nullptr; +ConVar* sv_unlag_clamp = nullptr; ConVar* sv_clockcorrection_msecs = nullptr; ConVar* sv_updaterate_sp = nullptr; @@ -364,6 +365,9 @@ void ConVar_StaticInit(void) #endif // !DEDICATED sv_language = ConVar::StaticCreate("sv_language", "english", FCVAR_RELEASE, "Language of the server. Sent to MasterServer for localising error messages.", false, 0.f, false, 0.f, LanguageChanged_f, nullptr); + + sv_unlag_clamp = ConVar::StaticCreate("sv_unlag_clamp", "0", FCVAR_RELEASE, "Clamp the difference between the current time and received command time to sv_maxunlag + sv_clockcorrection_msecs.", false, 0.f, false, 0.f, nullptr, nullptr); + sv_showconnecting = ConVar::StaticCreate("sv_showconnecting" , "1", FCVAR_RELEASE, "Logs information about the connecting client to the console.", false, 0.f, false, 0.f, nullptr, nullptr); sv_globalBanlist = ConVar::StaticCreate("sv_globalBanlist" , "1", FCVAR_RELEASE, "Determines whether or not to use the global banned list.", false, 0.f, false, 0.f, nullptr, "0 = Disable, 1 = Enable."); sv_pylonVisibility = ConVar::StaticCreate("sv_pylonVisibility", "0", FCVAR_RELEASE, "Determines the visibility to the Pylon master server.", false, 0.f, false, 0.f, nullptr, "0 = Offline, 1 = Hidden, 2 = Public."); diff --git a/r5dev/common/global.h b/r5dev/common/global.h index bb957a81..4a2091ea 100644 --- a/r5dev/common/global.h +++ b/r5dev/common/global.h @@ -110,6 +110,7 @@ extern ConVar* sv_forceChatToTeamOnly; extern ConVar* sv_single_core_dedi; extern ConVar* sv_maxunlag; +extern ConVar* sv_unlag_clamp; extern ConVar* sv_clockcorrection_msecs; extern ConVar* sv_updaterate_sp; diff --git a/r5dev/game/server/player.cpp b/r5dev/game/server/player.cpp index 6d236e49..dfeafe77 100644 --- a/r5dev/game/server/player.cpp +++ b/r5dev/game/server/player.cpp @@ -109,6 +109,65 @@ void CPlayer::SetTotalExtraClientCmdTimeAttempted(float flAttemptedTime) } } +//------------------------------------------------------------------------------ +// Purpose: clamps the unlag amount to sv_unlag + clockdrift +// Input : *cmd - +//------------------------------------------------------------------------------ +void CPlayer::ClampUnlag(CUserCmd* cmd) +{ + const CClient* client = g_pServer->GetClient(GetEdict() - 1); + const CNetChan* chan = client->GetNetChan(); + + const float clockDriftMsecs = sv_clockcorrection_msecs->GetFloat() / 1000.0f; + const float maxUnlag = sv_maxunlag->GetFloat(); + const float latencyAmount = Clamp(chan->GetLatency(FLOW_OUTGOING), 0.0f, maxUnlag); + const float serverTime = (*g_pGlobals)->m_flCurTime; + + // Command issue time from client, note that this value can be altered + // from the client, and therefore be used to exploit lag compensation. + const float commandTime = cmd->command_time; + const float lastCommandTime = m_LastCmd.command_time; + const float commandDelta = fabs(commandTime - serverTime); + + bool recomputeUnlag = false; + + // Check delta first, otherwise player could set commandTime to a fixed + // time and circumvent the system, as commandTime < lastCommandTime or + // commandTime > localCurTime will always fail. + if (commandDelta > maxUnlag) + { + // Too much to unlag, clamp to max !!! + recomputeUnlag = true; + DevWarning(eDLL_T::SERVER, "%s: commandDelta( %f ) > maxUnlag( %f ) !!!\n", + __FUNCTION__, commandDelta, maxUnlag); + } + else if (commandTime < (lastCommandTime - clockDriftMsecs)) + { + // Can never be lower than last !!! + recomputeUnlag = true; + DevWarning(eDLL_T::SERVER, "%s: cmd->command_time( %f ) < (m_LastCmd.command_time( %f ) - clockDriftMsecs( %f )) !!!\n", + __FUNCTION__, commandTime, lastCommandTime, clockDriftMsecs); + } + else if (commandTime > (serverTime + clockDriftMsecs)) + { + // Too far in the future, clamp to max !!! + recomputeUnlag = true; + DevWarning(eDLL_T::SERVER, "%s: cmd->command_time( %f ) > (g_pGlobals->m_flCurTime( %f ) + clockDriftMsecs( %f )) !!!\n", + __FUNCTION__, commandTime, serverTime, clockDriftMsecs); + } + + if (recomputeUnlag) + { + // Clamp it to server time minus latency. Note that it could still + // be lower than previous, hence the clamp on the recomputation. + float newCommandTime = Clamp(serverTime - latencyAmount, lastCommandTime, serverTime); + cmd->command_time = newCommandTime; + + DevWarning(eDLL_T::SERVER, "%s: Clamped cmd->command_time( %f ) to %f !!!\n", + __FUNCTION__, commandTime, newCommandTime); + } +} + //------------------------------------------------------------------------------ // Purpose: processes user cmd's for this player // Input : *cmds - @@ -123,16 +182,8 @@ void CPlayer::ProcessUserCmds(CUserCmd* cmds, int numCmds, int totalCmds, if (totalCmds <= 0) return; - const CClient* client = g_pServer->GetClient(GetEdict() - 1); - const CNetChan* chan = client->GetNetChan(); - CUserCmd* lastCmd = &m_Commands[MAX_QUEUED_COMMANDS_PROCESS]; - const float clockDriftMsecs = sv_clockcorrection_msecs->GetFloat() / 1000.0f; - const float maxUnlag = sv_maxunlag->GetFloat(); - const float latencyAmount = Clamp(chan->GetLatency(FLOW_OUTGOING), 0.0f, maxUnlag); - const float serverTime = (*g_pGlobals)->m_flCurTime; - for (int i = totalCmds - 1; i >= 0; i--) { CUserCmd* cmd = &cmds[i]; @@ -147,49 +198,8 @@ void CPlayer::ProcessUserCmds(CUserCmd* cmds, int numCmds, int totalCmds, if (lastCommandNumber == MAX_QUEUED_COMMANDS_PROCESS) return; - // Command issue time from client, note that this value can be altered - // from the client, and therefore be used to exploit lag compensation. - const float commandTime = cmd->command_time; - const float lastCommandTime = m_LastCmd.command_time; - const float commandDelta = fabs(commandTime - serverTime); - - bool recomputeUnlag = false; - - // Check delta first, otherwise player could set commandTime to a fixed - // time and circumvent the system, as commandTime < lastCommandTime or - // commandTime > localCurTime will always fail. - if (commandDelta > maxUnlag) - { - // Too much to unlag, clamp to max !!! - recomputeUnlag = true; - DevWarning(eDLL_T::SERVER, "%s: commandDelta( %f ) > maxUnlag( %f ) !!!\n", - __FUNCTION__, commandDelta, maxUnlag); - } - else if (commandTime < (lastCommandTime - clockDriftMsecs)) - { - // Can never be lower than last !!! - recomputeUnlag = true; - DevWarning(eDLL_T::SERVER, "%s: cmd->command_time( %f ) < (m_LastCmd.command_time( %f ) - clockDriftMsecs( %f )) !!!\n", - __FUNCTION__, commandTime, lastCommandTime, clockDriftMsecs); - } - else if (commandTime > (serverTime + clockDriftMsecs)) - { - // Too far in the future, clamp to max !!! - recomputeUnlag = true; - DevWarning(eDLL_T::SERVER, "%s: cmd->command_time( %f ) > (g_pGlobals->m_flCurTime( %f ) + clockDriftMsecs( %f )) !!!\n", - __FUNCTION__, commandTime, serverTime, clockDriftMsecs); - } - - if (recomputeUnlag) - { - // Clamp it to server time minus latency. Note that it could still - // be lower than previous, hence the clamp on the recomputation. - float newCommandTime = Clamp(serverTime - latencyAmount, lastCommandTime, serverTime); - cmd->command_time = newCommandTime; - - DevWarning(eDLL_T::SERVER, "%s: Clamped cmd->command_time( %f ) to %f !!!\n", - __FUNCTION__, commandTime, newCommandTime); - } + if (sv_unlag_clamp->GetBool()) + ClampUnlag(cmd); CUserCmd* queuedCmd = &m_Commands[lastCommandNumber]; queuedCmd->Copy(cmd); diff --git a/r5dev/game/server/player.h b/r5dev/game/server/player.h index 5e463963..8e896c7c 100644 --- a/r5dev/game/server/player.h +++ b/r5dev/game/server/player.h @@ -251,6 +251,9 @@ public: void ProcessUserCmds(CUserCmd* cmds, int numCmds, int totalCmds, int droppedPackets, bool paused); + + void ClampUnlag(CUserCmd* cmd); + void PlayerRunCommand(CUserCmd* pUserCmd, IMoveHelper* pMover); void SetLastUserCommand(CUserCmd* pUserCmd);