diff --git a/src/common/global.cpp b/src/common/global.cpp index d18725d6..5c232085 100644 --- a/src/common/global.cpp +++ b/src/common/global.cpp @@ -85,7 +85,13 @@ ConVar* sv_voiceEcho = nullptr; ConVar* sv_voiceenable = nullptr; ConVar* sv_alltalk = nullptr; +ConVar* sv_clampPlayerFrameTime = nullptr; + +ConVar* playerframetimekick_margin = nullptr; +ConVar* playerframetimekick_decayrate = nullptr; + ConVar* player_userCmdsQueueWarning = nullptr; +ConVar* player_disallow_negative_frametime = nullptr; #endif // !CLIENT_DLL ConVar* sv_cheats = nullptr; @@ -208,7 +214,14 @@ void ConVar_InitShipped(void) sv_voiceenable = g_pCVar->FindVar("sv_voiceenable"); sv_voiceEcho = g_pCVar->FindVar("sv_voiceEcho"); sv_alltalk = g_pCVar->FindVar("sv_alltalk"); + + sv_clampPlayerFrameTime = g_pCVar->FindVar("sv_clampPlayerFrameTime"); + + playerframetimekick_margin = g_pCVar->FindVar("playerframetimekick_margin"); + playerframetimekick_decayrate = g_pCVar->FindVar("playerframetimekick_decayrate"); + player_userCmdsQueueWarning = g_pCVar->FindVar("player_userCmdsQueueWarning"); + player_disallow_negative_frametime = g_pCVar->FindVar("player_disallow_negative_frametime"); sv_updaterate_sp->RemoveFlags(FCVAR_DEVELOPMENTONLY); sv_updaterate_mp->RemoveFlags(FCVAR_DEVELOPMENTONLY); diff --git a/src/common/global.h b/src/common/global.h index e958199b..746afbd3 100644 --- a/src/common/global.h +++ b/src/common/global.h @@ -72,7 +72,13 @@ extern ConVar* sv_voiceEcho; extern ConVar* sv_voiceenable; extern ConVar* sv_alltalk; +extern ConVar* sv_clampPlayerFrameTime; + +extern ConVar* playerframetimekick_margin; +extern ConVar* playerframetimekick_decayrate; + extern ConVar* player_userCmdsQueueWarning; +extern ConVar* player_disallow_negative_frametime; #endif // CLIENT_DLL extern ConVar* sv_cheats; diff --git a/src/core/init.cpp b/src/core/init.cpp index 3677676d..0aec44c8 100644 --- a/src/core/init.cpp +++ b/src/core/init.cpp @@ -135,6 +135,8 @@ #include "game/server/detour_impl.h" #include "game/server/gameinterface.h" #include "game/server/movehelper_server.h" +#include "game/server/player.h" +#include "game/server/player_command.h" #include "game/server/physics_main.h" #include "game/server/vscript_server.h" #endif // !CLIENT_DLL @@ -663,6 +665,7 @@ void DetourRegister() // Register detour classes to be searched and hooked. REGISTER(VBaseEntity); REGISTER(VBaseAnimating); REGISTER(VPlayer); + REGISTER(VPlayerMove); #endif // !CLIENT_DLL diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 496e7afe..9f3edd85 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -524,6 +524,48 @@ bool CClient::VProcessSetConVar(CClient* pClient, NET_SetConVar* pMsg) return true; } +//--------------------------------------------------------------------------------- +// Purpose: set UserCmd time buffer +// Input : numUserCmdProcessTicksMax - +// tickInterval - +//--------------------------------------------------------------------------------- +void CClientExtended::InitializeMovementTimeForUserCmdProcessing(const int numUserCmdProcessTicksMax, const float tickInterval) +{ + // Grant the client some time buffer to execute user commands + m_flMovementTimeForUserCmdProcessingRemaining += tickInterval; + + // but never accumulate more than N ticks + if (m_flMovementTimeForUserCmdProcessingRemaining > numUserCmdProcessTicksMax * tickInterval) + m_flMovementTimeForUserCmdProcessingRemaining = numUserCmdProcessTicksMax * tickInterval; +} + +//--------------------------------------------------------------------------------- +// Purpose: consume UserCmd time buffer +// Input : flTimeNeeded - +// Output : max time allowed for processing +//--------------------------------------------------------------------------------- +float CClientExtended::ConsumeMovementTimeForUserCmdProcessing(const float flTimeNeeded) +{ + if (m_flMovementTimeForUserCmdProcessingRemaining <= 0.0f) + return 0.0f; + else if (flTimeNeeded > m_flMovementTimeForUserCmdProcessingRemaining + FLT_EPSILON) + { + const float flResult = m_flMovementTimeForUserCmdProcessingRemaining; + m_flMovementTimeForUserCmdProcessingRemaining = 0.0f; + + return flResult; + } + else + { + m_flMovementTimeForUserCmdProcessingRemaining -= flTimeNeeded; + + if (m_flMovementTimeForUserCmdProcessingRemaining < 0.0f) + m_flMovementTimeForUserCmdProcessingRemaining = 0.0f; + + return flTimeNeeded; + } +} + void VClient::Detour(const bool bAttach) const { #ifndef CLIENT_DLL diff --git a/src/engine/client/client.h b/src/engine/client/client.h index a40456af..d3f9baf5 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -230,6 +230,7 @@ public: m_flNetProcessTimeBase = 0.0; m_flStringCommandQuotaTimeStart = 0.0; m_nStringCommandQuotaCount = NULL; + m_flMovementTimeForUserCmdProcessingRemaining = 0.0f; m_bInitialConVarsSet = false; } @@ -247,6 +248,12 @@ public: // Inlines: inline void SetStringCommandQuotaCount(const int iCount) { m_nStringCommandQuotaCount = iCount; } inline int GetStringCommandQuotaCount(void) const { return m_nStringCommandQuotaCount; } + inline void SetRemainingMovementTimeForUserCmdProcessing(const float flValue) { m_flMovementTimeForUserCmdProcessingRemaining = flValue; } + inline float GetRemainingMovementTimeForUserCmdProcessing() const { return m_flMovementTimeForUserCmdProcessingRemaining; } + + void InitializeMovementTimeForUserCmdProcessing(const int numUserCmdProcessTicksMax, const float tickInterval); + float ConsumeMovementTimeForUserCmdProcessing(const float flTimeNeeded); + private: // Measure how long this client's packets took to process. double m_flNetProcessingTimeMsecs; @@ -256,6 +263,9 @@ private: double m_flStringCommandQuotaTimeStart; int m_nStringCommandQuotaCount; + // How much of a movement time buffer can we process from this user? + float m_flMovementTimeForUserCmdProcessingRemaining; + bool m_bInitialConVarsSet; // Whether or not the initial ConVar KV's are set }; diff --git a/src/game/CMakeLists.txt b/src/game/CMakeLists.txt index b24557d3..6a734004 100644 --- a/src/game/CMakeLists.txt +++ b/src/game/CMakeLists.txt @@ -73,6 +73,8 @@ add_sources( SOURCE_GROUP "Network" add_sources( SOURCE_GROUP "Player" "server/player.cpp" "server/player.h" + "server/player_command.cpp" + "server/player_command.h" "server/playerlocaldata.h" ) diff --git a/src/game/server/gameinterface.cpp b/src/game/server/gameinterface.cpp index 8b16f760..52992b0a 100644 --- a/src/game/server/gameinterface.cpp +++ b/src/game/server/gameinterface.cpp @@ -22,10 +22,10 @@ //----------------------------------------------------------------------------- // This is called when a new game is started. (restart, map) //----------------------------------------------------------------------------- -void CServerGameDLL::GameInit(void) +bool CServerGameDLL::GameInit(void) { const static int index = 1; - CallVFunc(index, this); + return CallVFunc(index, this); } //----------------------------------------------------------------------------- diff --git a/src/game/server/gameinterface.h b/src/game/server/gameinterface.h index 8b92c280..e89084bd 100644 --- a/src/game/server/gameinterface.h +++ b/src/game/server/gameinterface.h @@ -19,7 +19,7 @@ class ServerClass; class CServerGameDLL { public: - void GameInit(void); + bool GameInit(void); void PrecompileScriptsJob(void); void LevelShutdown(void); void GameShutdown(void); @@ -48,11 +48,15 @@ class CServerGameEnts : public IServerGameEnts }; inline void(*CServerGameDLL__OnReceivedSayTextMessage)(void* thisptr, int senderId, const char* text, bool isTeamChat); +inline bool(*CServerGameDLL__GameInit)(void); + inline void(*CServerGameClients__ProcessUserCmds)(CServerGameClients* thisp, edict_t edict, bf_read* buf, int numCmds, int totalCmds, int droppedPackets, bool ignore, bool paused); inline void(*v_RunFrameServer)(double flFrameTime, bool bRunOverlays, bool bUniformUpdate); +inline float* g_pflServerFrameTimeBase = nullptr; + extern CServerGameDLL* g_pServerGameDLL; extern CServerGameClients* g_pServerGameClients; extern CServerGameEnts* g_pServerGameEntities; @@ -65,8 +69,10 @@ class VServerGameDLL : public IDetour virtual void GetAdr(void) const { LogFunAdr("CServerGameDLL::OnReceivedSayTextMessage", CServerGameDLL__OnReceivedSayTextMessage); + LogFunAdr("CServerGameDLL::GameInit", CServerGameDLL__GameInit); LogFunAdr("CServerGameClients::ProcessUserCmds", CServerGameClients__ProcessUserCmds); LogFunAdr("RunFrameServer", v_RunFrameServer); + LogVarAdr("g_flServerFrameTimeBase", g_pflServerFrameTimeBase); LogVarAdr("g_pServerGameDLL", g_pServerGameDLL); LogVarAdr("g_pServerGameClients", g_pServerGameClients); LogVarAdr("g_pServerGameEntities", g_pServerGameEntities); @@ -75,12 +81,14 @@ class VServerGameDLL : public IDetour virtual void GetFun(void) const { g_GameDll.FindPatternSIMD("85 D2 0F 8E ?? ?? ?? ?? 4C 8B DC").GetPtr(CServerGameDLL__OnReceivedSayTextMessage); + g_GameDll.FindPatternSIMD("48 83 EC 28 48 8B 0D ?? ?? ?? ?? 48 8D 15 ?? ?? ?? ?? 48 8B 01 FF 90 ?? ?? ?? ?? 48 8B 0D ?? ?? ?? ?? 48 8B 01").GetPtr(CServerGameDLL__GameInit); g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 55 41 55 41 57").GetPtr(CServerGameClients__ProcessUserCmds); g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 57 48 83 EC 30 0F 29 74 24 ?? 48 8D 0D ?? ?? ?? ??").GetPtr(v_RunFrameServer); } virtual void GetVar(void) const { g_pGlobals = g_GameDll.FindPatternSIMD("4C 8B 0D ?? ?? ?? ?? 48 8B D1").ResolveRelativeAddressSelf(0x3, 0x7).RCast(); + g_pflServerFrameTimeBase = CMemory(CServerGameDLL__GameInit).FindPatternSelf("F3 0F 11 0D").ResolveRelativeAddressSelf(0x4, 0x8).RCast(); } virtual void GetCon(void) const { } virtual void Detour(const bool bAttach) const; diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp index 8bb00679..122f6b98 100644 --- a/src/game/server/player.cpp +++ b/src/game/server/player.cpp @@ -13,6 +13,9 @@ #include "engine/server/server.h" +// NOTE[ AMOS ]: default tick interval (0.05) * default cvar value (10) = total time buffer of 0.5, which is the default of cvar 'sv_maxunlag'. +static ConVar sv_maxUserCmdProcessTicks("sv_maxUserCmdProcessTicks", "10", FCVAR_NONE, "Maximum number of client-issued UserCmd ticks that can be replayed in packet loss conditions, 0 to allow no restrictions."); + //------------------------------------------------------------------------------ // Purpose: executes a null command for this player //------------------------------------------------------------------------------ @@ -64,7 +67,7 @@ inline void CPlayer::SetTimeBase(float flTimeBase) SetLastUCmdSimulationRemainderTime(flTime); - float flSimulationTime = flTimeBase - m_lastUCmdSimulationRemainderTime * (*g_pGlobals)->m_flTickInterval; + float flSimulationTime = flTimeBase - m_lastUCmdSimulationRemainderTime * TICK_INTERVAL; if (flSimulationTime >= 0.0f) { flTime = flSimulationTime; @@ -241,6 +244,25 @@ void CPlayer::SetLastUserCommand(CUserCmd* pUserCmd) m_LastCmd.Copy(pUserCmd); } +//------------------------------------------------------------------------------ +// Purpose: run physics simulation for player +// Input : *player (this) - +// numPerIteration - +// adjustTimeBase - +//------------------------------------------------------------------------------ +bool Player_PhysicsSimulate(CPlayer* player, int numPerIteration, bool adjustTimeBase) +{ + CClientExtended* const cle = g_pServer->GetClientExtended(player->GetEdict() - 1); + const int numUserCmdProcessTicksMax = sv_maxUserCmdProcessTicks.GetInt(); + + if (numUserCmdProcessTicksMax && (*g_pGlobals)->m_nGameMode != GameMode_t::SP_MODE) // don't apply this filter in SP games + cle->InitializeMovementTimeForUserCmdProcessing(numUserCmdProcessTicksMax, TICK_INTERVAL); + else // Otherwise we don't care to track time + cle->SetRemainingMovementTimeForUserCmdProcessing(FLT_MAX); + + return CPlayer__PhysicsSimulate(player, numPerIteration, adjustTimeBase); +} + /* ===================== CC_CreateFakePlayer_f @@ -286,3 +308,8 @@ static void CC_CreateFakePlayer_f(const CCommand& args) } static ConCommand sv_addbot("sv_addbot", CC_CreateFakePlayer_f, "Creates a bot on the server", FCVAR_RELEASE); + +void VPlayer::Detour(const bool bAttach) const +{ + DetourSetup(&CPlayer__PhysicsSimulate, &Player_PhysicsSimulate, bAttach); +} diff --git a/src/game/server/player.h b/src/game/server/player.h index 1b6d05bf..546064fe 100644 --- a/src/game/server/player.h +++ b/src/game/server/player.h @@ -241,6 +241,7 @@ struct SpeedChangeHistoryEntry class CPlayer : public CBaseCombatCharacter { + friend class CPlayerMove; public: void RunNullCommand(void); QAngle* EyeAngles(QAngle* pAngles); @@ -260,6 +261,8 @@ public: inline bool IsConnected() const { return m_iConnected != PlayerDisconnected; } inline bool IsDisconnecting() const { return m_iConnected == PlayerDisconnecting; } + inline bool IsBot() const { return (GetFlags() & FL_FAKECLIENT) != 0; } + private: int m_StuckLast; char gap_5a8c[4]; @@ -796,6 +799,7 @@ static_assert(sizeof(CPlayer) == 0x7EF0); // !TODO: backwards compatibility. inline QAngle*(*CPlayer__EyeAngles)(CPlayer* pPlayer, QAngle* pAngles); inline void(*CPlayer__PlayerRunCommand)(CPlayer* pPlayer, CUserCmd* pUserCmd, IMoveHelper* pMover); +inline bool(*CPlayer__PhysicsSimulate)(CPlayer* pPlayer, int numPerIteration, bool adjustTimeBase); /////////////////////////////////////////////////////////////////////////////// class VPlayer : public IDetour @@ -804,15 +808,17 @@ class VPlayer : public IDetour { LogFunAdr("CPlayer::EyeAngles", CPlayer__EyeAngles); LogFunAdr("CPlayer::PlayerRunCommand", CPlayer__PlayerRunCommand); + LogFunAdr("CPlayer::PhysicsSimulate", CPlayer__PhysicsSimulate); } virtual void GetFun(void) const { g_GameDll.FindPatternSIMD("40 53 48 83 EC 30 F2 0F 10 05 ?? ?? ?? ??").GetPtr(CPlayer__EyeAngles); g_GameDll.FindPatternSIMD("E8 ?? ?? ?? ?? 8B 03 49 81 C6 ?? ?? ?? ??").FollowNearCallSelf().GetPtr(CPlayer__PlayerRunCommand); + g_GameDll.FindPatternSIMD("E8 ?? ?? ?? ?? 48 8B 15 ?? ?? ?? ?? 84 C0 74 06").FollowNearCallSelf().GetPtr(CPlayer__PhysicsSimulate); } virtual void GetVar(void) const { } virtual void GetCon(void) const { } - virtual void Detour(const bool bAttach) const { } + virtual void Detour(const bool bAttach) const; }; /////////////////////////////////////////////////////////////////////////////// diff --git a/src/game/server/player_command.cpp b/src/game/server/player_command.cpp new file mode 100644 index 00000000..b91c34c5 --- /dev/null +++ b/src/game/server/player_command.cpp @@ -0,0 +1,60 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. =======// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "engine/server/server.h" +#include "engine/client/client.h" + +#include "player_command.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CPlayerMove::CPlayerMove(void) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Runs movement commands for the player +// Input : *player - +// *ucmd - +// *moveHelper - +//----------------------------------------------------------------------------- +void CPlayerMove::StaticRunCommand(CPlayerMove* thisp, CPlayer* player, CUserCmd* ucmd, IMoveHelper* moveHelper) +{ + CClientExtended* const cle = g_pServer->GetClientExtended(player->GetEdict() - 1); + + const float playerCurTime = (player->m_lastUCmdSimulationRemainderTime * TICK_INTERVAL) + player->m_totalExtraClientCmdTimeAttempted; + float playerFrameTime; + + // Always default to clamped UserCmd frame time if this cvar is set + if (player_disallow_negative_frametime->GetBool()) + playerFrameTime = fmaxf(ucmd->frametime, 0.0f); + else + { + if (player->m_bGamePaused) + playerFrameTime = 0.0f; + else + playerFrameTime = TICK_INTERVAL; + + if (ucmd->frametime) + playerFrameTime = ucmd->frametime; + } + + if (sv_clampPlayerFrameTime->GetBool() && player->m_joinFrameTime > ((*g_pflServerFrameTimeBase) + playerframetimekick_margin->GetFloat())) + playerFrameTime = 0.0f; + + const float timeAllowedForProcessing = cle->ConsumeMovementTimeForUserCmdProcessing(playerFrameTime); + + if (!player->IsBot() && (timeAllowedForProcessing < playerFrameTime)) + return; // Don't process this command + + CPlayerMove__RunCommand(thisp, player, ucmd, moveHelper); +} + +void VPlayerMove::Detour(const bool bAttach) const +{ + DetourSetup(&CPlayerMove__RunCommand, &CPlayerMove::StaticRunCommand, bAttach); +} diff --git a/src/game/server/player_command.h b/src/game/server/player_command.h new file mode 100644 index 00000000..2c397243 --- /dev/null +++ b/src/game/server/player_command.h @@ -0,0 +1,67 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. =======// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#ifndef PLAYER_COMMAND_H +#define PLAYER_COMMAND_H + +#include "edict.h" +#include "game/shared/usercmd.h" +#include "game/server/player.h" + +class IMoveHelper; +class CMoveData; +class CBasePlayer; + +//----------------------------------------------------------------------------- +// Purpose: Server side player movement +//----------------------------------------------------------------------------- +class CPlayerMove +{ +public: + //DECLARE_CLASS_NOBASE(CPlayerMove); + + // Construction/destruction + CPlayerMove(void); + virtual ~CPlayerMove(void) {} + + // Hook statics: + static void StaticRunCommand(CPlayerMove* thisp, CPlayer* player, CUserCmd* ucmd, IMoveHelper* moveHelper); + + // Public interfaces: + // Run a movement command from the player + virtual void RunCommand(CPlayer* player, CUserCmd* ucmd, IMoveHelper* moveHelper) = 0; + +protected: + // Prepare for running movement + virtual void SetupMove(CPlayer* player, CUserCmd* ucmd, CMoveData* move) = 0; + + // Finish movement + virtual void FinishMove(CPlayer* player, CUserCmd* ucmd, CMoveData* move) = 0; + + // Called before and after any movement processing + virtual void StartCommand(CPlayer* player, IMoveHelper* pHelper, CUserCmd* cmd) = 0; +}; + +inline void (*CPlayerMove__RunCommand)(CPlayerMove* thisp, CPlayer* player, CUserCmd* ucmd, IMoveHelper* moveHelper); + +/////////////////////////////////////////////////////////////////////////////// +class VPlayerMove : public IDetour +{ + virtual void GetAdr(void) const + { + LogFunAdr("CPlayerMove::RunCommand", CPlayerMove__RunCommand); + } + virtual void GetFun(void) const + { + g_GameDll.FindPatternSIMD("48 8B C4 55 53 56 57 41 57 48 8D A8 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 44 0F 29 50 ??").GetPtr(CPlayerMove__RunCommand); + } + virtual void GetVar(void) const { } + virtual void GetCon(void) const { } + virtual void Detour(const bool bAttach) const; +}; +/////////////////////////////////////////////////////////////////////////////// + +#endif // PLAYER_COMMAND_H