Implemented simple player movement version control to avoid desync

Bump minor API version
This commit is contained in:
s1lentq 2024-08-16 09:53:11 +07:00
parent 4e1cb1091f
commit 8b6d659077
8 changed files with 128 additions and 12 deletions

View File

@ -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)

View File

@ -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 <num>
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<CCSPlayer>((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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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;

View File

@ -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

View File

@ -38,7 +38,7 @@
#include <API/CSInterfaces.h>
#define REGAMEDLL_API_VERSION_MAJOR 5
#define REGAMEDLL_API_VERSION_MINOR 27
#define REGAMEDLL_API_VERSION_MINOR 28
// CBasePlayer::Spawn hook
typedef IHookChainClass<void, class CBasePlayer> IReGameHook_CBasePlayer_Spawn;

View File

@ -6,5 +6,5 @@
#pragma once
#define VERSION_MAJOR 5
#define VERSION_MINOR 27
#define VERSION_MINOR 28
#define VERSION_MAINTENANCE 0