From 8b6d659077ee40ea7f082c5ec64b08b795e297fa Mon Sep 17 00:00:00 2001 From: s1lentq Date: Fri, 16 Aug 2024 09:53:11 +0700 Subject: [PATCH] Implemented simple player movement version control to avoid desync Bump minor API version --- regamedll/dlls/cdll_dll.h | 1 + regamedll/dlls/client.cpp | 34 +++++++++++ regamedll/pm_shared/pm_defs.h | 70 +++++++++++++++++++++- regamedll/pm_shared/pm_shared.cpp | 26 +++++--- regamedll/pm_shared/pm_shared.h | 2 + regamedll/public/regamedll/API/CSPlayer.h | 3 + regamedll/public/regamedll/regamedll_api.h | 2 +- regamedll/version/version.h | 2 +- 8 files changed, 128 insertions(+), 12 deletions(-) diff --git a/regamedll/dlls/cdll_dll.h b/regamedll/dlls/cdll_dll.h index da6cd36d..6e495368 100644 --- a/regamedll/dlls/cdll_dll.h +++ b/regamedll/dlls/cdll_dll.h @@ -74,6 +74,7 @@ const int DEFAULT_FOV = 90; // the default field of view #define PLAYER_PREVENT_DUCK BIT(4) #define PLAYER_PREVENT_CLIMB BIT(5) // The player can't climb ladder #define PLAYER_PREVENT_JUMP BIT(6) +#define PLAYER_PREVENT_DDUCK BIT(7) #define MENU_KEY_1 BIT(0) #define MENU_KEY_2 BIT(1) diff --git a/regamedll/dlls/client.cpp b/regamedll/dlls/client.cpp index 9708fa2c..e1b4d322 100644 --- a/regamedll/dlls/client.cpp +++ b/regamedll/dlls/client.cpp @@ -3299,6 +3299,26 @@ void EXT_FUNC InternalCommand(edict_t *pEntity, const char *pcmd, const char *pa } } #endif + +#ifdef REGAMEDLL_ADD + // Request from client for the given version of player movement control, if any + else if (FStrEq(pcmd, "cl_pmove_version")) + { + // cl_pmove_version + if (CMD_ARGC_() < 2) + return; // invalid + + PlayerMovementVersion &playerMovementVersion = pPlayer->CSPlayer()->m_MovementVersion; + playerMovementVersion.Set(parg1); + + // If the client's requested movement version is newer, enforce it to the available one + if (playerMovementVersion.IsGreaterThan(PM_VERSION)) + { + playerMovementVersion.Set(PM_VERSION); // reset to available version + CLIENT_COMMAND(pEntity, "cl_pmove_version %s\n", playerMovementVersion.ToString()); + } + } +#endif else { if (g_pGameRules->ClientCommand_DeadOrAlive(GetClassPtr((CBasePlayer *)pev), pcmd)) @@ -3727,6 +3747,20 @@ void EXT_FUNC ServerDeactivate() void EXT_FUNC ServerActivate(edict_t *pEdictList, int edictCount, int clientMax) { +#ifdef REGAMEDLL_ADD + // + // Tells clients which version of player movement (pmove) the server is using + // + // In GoldSrc, both the server and clients handle player movement using shared code. + // If the server changes how movement works, due to improvements or bugfixes, it can mess up + // the client's movement prediction, causing desync. To avoid this, the server can tell clients what + // version of the movement code it's using. Clients that don't recognize or respond to this version + // will be treated as using the previous behavior, and the server will handle them accordingly. + // Clients that do recognize it will let the server know, so everything stays in sync. + // + SET_KEY_VALUE(GET_INFO_BUFFER(pEdictList), "pmove", PM_ServerVersion()); +#endif + int i; CBaseEntity *pClass; diff --git a/regamedll/pm_shared/pm_defs.h b/regamedll/pm_shared/pm_defs.h index 8606c587..75fba507 100644 --- a/regamedll/pm_shared/pm_defs.h +++ b/regamedll/pm_shared/pm_defs.h @@ -91,6 +91,74 @@ typedef struct physent_s } physent_t; +#define PM_VERSION_MAJOR 1 +#define PM_VERSION_MINOR 0 +#define PM_VERSION_PATCH 0 +#define PM_VERSION PM_VERSION_MAJOR, PM_VERSION_MINOR, PM_VERSION_PATCH + +#define PM_VERSION_STRINGIZE(x) #x +#define PM_VERSION_STRING(major,minor,patch) \ + (patch == 0 ?\ + PM_VERSION_STRINGIZE(major) "." PM_VERSION_STRINGIZE(minor) :\ + PM_VERSION_STRINGIZE(major) "." PM_VERSION_STRINGIZE(minor) "." PM_VERSION_STRINGIZE(patch)) + +// Control version of the player movement system +struct PlayerMovementVersion { + uint8_t major, minor, patch; + uint32_t u32; + + PlayerMovementVersion() { + Set(0, 0, 0); + } + + PlayerMovementVersion(uint32_t majorVersion, uint32_t minorVersion, uint32_t patchVersion = 0) { + Set(majorVersion, minorVersion, patchVersion); + } + + inline void Set(uint32_t majorVersion, uint32_t minorVersion, uint32_t patchVersion = 0) + { + major = majorVersion; + minor = minorVersion; + patch = patchVersion; + u32 = (major << 16) | (minor << 8) | patch; + } + + inline void Set(const char *version) + { + if (!version) + return; + + major = minor = patch = u32 = 0; + int result = sscanf(version, "%hhu.%hhu.%hhu", &major, &minor, &patch); + if (result < 1) + return; // major invalid + + u32 = (major << 16) | (minor << 8) | patch; + } + + // Compares if the current version is less than the given version + inline bool IsLessThan(uint32_t major, uint32_t minor, uint32_t patch = 0) const { + return u32 < ((major << 16) | (minor << 8) | patch); + } + + // Compares if the current version is greater than the given version + inline bool IsGreaterThan(uint32_t major, uint32_t minor, uint32_t patch = 0) const { + return u32 > ((major << 16) | (minor << 8) | patch); + } + + // Compares if the current version is greater than or equal to the given version + inline bool IsAtLeast(uint32_t major, uint32_t minor, uint32_t patch = 0) const { + return !IsLessThan(major, minor, patch); + } + + const char *ToString() const { + static char string[14]; + int len = Q_snprintf(string, sizeof(string), "%u.%u.%u", major, minor, patch); + if (patch == 0 && len > 2) string[len - 2] = '\0'; + return string; + } +}; + typedef struct playermove_s { int player_index; // So we don't try to run the PM_CheckStuck nudging too quickly. @@ -111,7 +179,7 @@ typedef struct playermove_s qboolean bInDuck; // In process of ducking or ducked already? int flTimeStepSound; // For walking/falling // Next time we can play a step sound - int iStepLeft; + qboolean iStepLeft; float flFallVelocity; vec3_t punchangle; float flSwimTime; diff --git a/regamedll/pm_shared/pm_shared.cpp b/regamedll/pm_shared/pm_shared.cpp index d90206bc..6ab25397 100644 --- a/regamedll/pm_shared/pm_shared.cpp +++ b/regamedll/pm_shared/pm_shared.cpp @@ -898,7 +898,7 @@ void PM_WalkMove() #ifdef REGAMEDLL_ADD // Player can speed up the run if '+speed' button is pressed - if ((pmove->cmd.buttons & IN_RUN) && pmove->fuser3 > 0) + if (pmoveplayer->m_MovementVersion.IsAtLeast(1, 0) && (pmove->cmd.buttons & IN_RUN) && pmove->fuser3 > 0) { fmove *= pmove->fuser3; smove *= pmove->fuser3; @@ -1450,7 +1450,7 @@ void PM_CategorizePosition() // Do not stick to the ground of an OBSERVER or NOCLIP mode #ifdef REGAMEDLL_FIXES - if (pmove->movetype == MOVETYPE_NOCLIP || pmove->movetype == MOVETYPE_NONE) + if (pmoveplayer->m_MovementVersion.IsAtLeast(1, 0) && (pmove->movetype == MOVETYPE_NOCLIP || pmove->movetype == MOVETYPE_NONE)) { pmove->onground = -1; return; @@ -1704,7 +1704,7 @@ void PM_SpectatorMove() #ifdef REGAMEDLL_ADD // Observer can accelerate in air if '+speed' button is pressed - if (pmove->cmd.buttons & IN_RUN) + if (pmoveplayer->m_MovementVersion.IsAtLeast(1, 0) && pmove->cmd.buttons & IN_RUN) { float flAirAccelerate = (pmove->fuser3 > 0.0f) ? pmove->fuser3 : max(pmove->movevars->airaccelerate / 100.0f, 7.0f); fmove *= flAirAccelerate; @@ -1737,12 +1737,15 @@ void PM_SpectatorMove() addspeed = wishspeed - currentspeed; -#ifndef REGAMEDLL_FIXES if (addspeed <= 0) - return; -#else - if (addspeed > 0) + { +#ifdef REGAMEDLL_FIXES + if (pmoveplayer->m_MovementVersion.IsLessThan(1, 0)) #endif + return; + } + + if (addspeed > 0) { accelspeed = pmove->movevars->accelerate * pmove->frametime * wishspeed; if (accelspeed > addspeed) @@ -1844,7 +1847,7 @@ LINK_HOOK_VOID_CHAIN2(PM_UnDuck) void EXT_FUNC __API_HOOK(PM_UnDuck)() { #ifdef REGAMEDLL_ADD - if (unduck_method.value) + if (unduck_method.value || (pmove->iuser3 & PLAYER_PREVENT_DDUCK)) #endif { #ifdef REGAMEDLL_FIXES @@ -2365,7 +2368,7 @@ void PM_NoClip() #ifdef REGAMEDLL_ADD // Player with noclip can accelerate in air if '+speed' button is pressed - if ((pmove->cmd.buttons & IN_RUN) && pmove->fuser3 > 0) + if (pmoveplayer->m_MovementVersion.IsAtLeast(1, 0) && (pmove->cmd.buttons & IN_RUN) && pmove->fuser3 > 0) { float flAirAccelerate = pmove->fuser3; fmove *= flAirAccelerate; @@ -3379,3 +3382,8 @@ void EXT_FUNC __API_HOOK(PM_Init)(struct playermove_s *ppmove) pm_shared_initialized = TRUE; } + +const char *PM_ServerVersion() +{ + return PM_VERSION_STRING(PM_VERSION_MAJOR, PM_VERSION_MINOR, PM_VERSION_PATCH); +} diff --git a/regamedll/pm_shared/pm_shared.h b/regamedll/pm_shared/pm_shared.h index 2c0385f4..50550c69 100644 --- a/regamedll/pm_shared/pm_shared.h +++ b/regamedll/pm_shared/pm_shared.h @@ -96,4 +96,6 @@ void PM_AirAccelerate_OrigFunc(vec_t *wishdir, float wishspeed, float accel); void PM_AirMove(int playerIndex = 0); #endif +const char *PM_ServerVersion(); + extern struct playermove_s *pmove; diff --git a/regamedll/public/regamedll/API/CSPlayer.h b/regamedll/public/regamedll/API/CSPlayer.h index f2bfe028..05b81b70 100644 --- a/regamedll/public/regamedll/API/CSPlayer.h +++ b/regamedll/public/regamedll/API/CSPlayer.h @@ -186,6 +186,9 @@ public: int m_iGibDamageThreshold; // negative health to reach to gib player usercmd_t m_LastCmd; + + // Player movement version control + PlayerMovementVersion m_MovementVersion; }; // Inlines diff --git a/regamedll/public/regamedll/regamedll_api.h b/regamedll/public/regamedll/regamedll_api.h index d655ee65..74879aa4 100644 --- a/regamedll/public/regamedll/regamedll_api.h +++ b/regamedll/public/regamedll/regamedll_api.h @@ -38,7 +38,7 @@ #include #define REGAMEDLL_API_VERSION_MAJOR 5 -#define REGAMEDLL_API_VERSION_MINOR 27 +#define REGAMEDLL_API_VERSION_MINOR 28 // CBasePlayer::Spawn hook typedef IHookChainClass IReGameHook_CBasePlayer_Spawn; diff --git a/regamedll/version/version.h b/regamedll/version/version.h index fc64686b..62c33001 100644 --- a/regamedll/version/version.h +++ b/regamedll/version/version.h @@ -6,5 +6,5 @@ #pragma once #define VERSION_MAJOR 5 -#define VERSION_MINOR 27 +#define VERSION_MINOR 28 #define VERSION_MAINTENANCE 0