From 4e5f42259af0ee4ea19edf2843f7e6174b382b8e Mon Sep 17 00:00:00 2001 From: newby Date: Fri, 4 Jul 2025 03:04:45 -0700 Subject: [PATCH 1/9] cvar: add cg_packetdelay adds access to cl_packetdelay which is normally cheat protected; useful for both testing and ping equalizing. --- code/cgame/cg_cvar.h | 2 ++ code/cgame/cg_local.h | 1 - code/cgame/cg_main.c | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/code/cgame/cg_cvar.h b/code/cgame/cg_cvar.h index 6503781..14fb36b 100644 --- a/code/cgame/cg_cvar.h +++ b/code/cgame/cg_cvar.h @@ -163,6 +163,8 @@ CG_CVAR( cg_timescaleFadeEnd, "cg_timescaleFadeEnd", "1", 0 ) CG_CVAR( cg_timescaleFadeSpeed, "cg_timescaleFadeSpeed", "0", 0 ) CG_CVAR( cg_timescale, "timescale", "1", 0 ) CG_CVAR( r_clear, "r_clear", "0", 0 ) +CG_CVAR( cg_packetdelay, "cg_packetdelay", "0", CVAR_USERINFO ) +CG_CVAR( cl_packetdelay, "cl_packetdelay", "0", CVAR_CHEAT ) CG_CVAR( r_debugSort, "r_debugSort", "0", CVAR_CHEAT ) diff --git a/code/cgame/cg_local.h b/code/cgame/cg_local.h index ee2f34c..5498f66 100644 --- a/code/cgame/cg_local.h +++ b/code/cgame/cg_local.h @@ -1458,7 +1458,6 @@ typedef struct { qboolean synchronousClients; qboolean sv_cheats; - } cgs_t; //============================================================================== diff --git a/code/cgame/cg_main.c b/code/cgame/cg_main.c index 32167d2..aa5cf78 100644 --- a/code/cgame/cg_main.c +++ b/code/cgame/cg_main.c @@ -209,6 +209,8 @@ static cvarLimitTable_t cvarLimitTable[] = { { &cg_cl_yawspeed, "cl_yawspeed", 140, 0, 0, 0, 0, qfalse }, { &cg_cl_pitchspeed, "cl_pitchspeed", 140, 0, 0, 0, 0, qfalse }, { &cg_cl_freelook, "cl_freelook", 1, 1, 1, 0, 0, qfalse }, + + { &cg_packetdelay, "cg_packetdelay", 0, 0, 200, 0, 0, qfalse }, }; static const int cvarLimitTableSize = (int)ARRAY_LEN( cvarLimitTable ); @@ -538,6 +540,10 @@ void CG_UpdateCvars( void ) { // limit cvars CG_LimitCvars(); + + // Handle packet delay + if (cg_packetdelay.integer != cl_packetdelay.integer) + trap_Cvar_Set("cl_packetdelay", cg_packetdelay.string); } From 37d251bb40d07904afa41045ebc8763124f4e4ea Mon Sep 17 00:00:00 2001 From: newby Date: Sat, 5 Jul 2025 16:57:11 -0700 Subject: [PATCH 2/9] cvar: add `cl_maxfps` which replaces `com_maxfps` Add a new client `cl_maxfps` which allows specifying the maximum desired fps value; which may not be the same as the maximum allowed. `com_maxfps` is now derived from `cl_maxfps`. Current values are 250 when `pmove_fixed=1` and 125 when `pmove_fixed=0` The default value of `cl_maxfps -1` should probably be preferred because it just picks the best currently supported max. Future patches will raise this to 500 in the pmove_fixed=1 case. --- code/cgame/cg_cvar.h | 3 ++- code/cgame/cg_local.h | 3 +++ code/cgame/cg_main.c | 7 +++++-- code/cgame/cg_servercmds.c | 21 +++++++++++++++++++++ code/game/g_main.c | 1 - 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/code/cgame/cg_cvar.h b/code/cgame/cg_cvar.h index 14fb36b..b628c5a 100644 --- a/code/cgame/cg_cvar.h +++ b/code/cgame/cg_cvar.h @@ -148,7 +148,8 @@ CG_CVAR( cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE ) CG_CVAR( cl_maxpackets, "cl_maxpackets", "60", CVAR_ARCHIVE ) CG_CVAR( r_maxpolys, "r_maxpolys", "1800", CVAR_ARCHIVE ) CG_CVAR( r_maxpolyverts, "r_maxpolyverts", "9000", CVAR_ARCHIVE ) -CG_CVAR( com_maxfps, "com_maxfps", "85", CVAR_ARCHIVE ) +CG_CVAR( com_maxfps, "com_maxfps", "125", CVAR_CHEAT) +CG_CVAR( cl_maxfps, "cl_maxfps", "-1", CVAR_ARCHIVE ) CG_CVAR( cg_blood, "com_blood", "1", CVAR_ARCHIVE ) CG_CVAR( cg_cameraOrbitDelay, "cg_cameraOrbitDelay", "50", CVAR_ARCHIVE ) CG_CVAR( cg_scorePlum, "cg_scorePlums", "1", CVAR_ARCHIVE ) diff --git a/code/cgame/cg_local.h b/code/cgame/cg_local.h index 5498f66..d100bb3 100644 --- a/code/cgame/cg_local.h +++ b/code/cgame/cg_local.h @@ -1458,6 +1458,8 @@ typedef struct { qboolean synchronousClients; qboolean sv_cheats; + + int active_maxfps; } cgs_t; //============================================================================== @@ -2301,5 +2303,6 @@ extern int cvar_developer; extern int dll_trap_R_AddRefEntityToScene2; extern int dll_trap_R_AddLinearLightToScene; +void CG_Update_MaxFPS(void); #endif //__CG_LOCAL_H diff --git a/code/cgame/cg_main.c b/code/cgame/cg_main.c index aa5cf78..f4cc631 100644 --- a/code/cgame/cg_main.c +++ b/code/cgame/cg_main.c @@ -170,7 +170,6 @@ static cvarLimitTable_t cvarLimitTable[] = { { &cl_maxpackets, "cl_maxpackets", 40, 30, -1, 0, 0, qfalse }, { &r_maxpolys, "r_maxpolys", 1800, 1800, -1, 0, 0, qfalse }, { &r_maxpolyverts, "r_maxpolyverts", 9000, 9000, -1, 0, 0, qfalse }, - { &com_maxfps, "com_maxfps", 85, 30, 130, 0, 0, qfalse }, { &cg_fov, "cg_fov", 90, 5, 135, 0, 0, qfalse }, { &cg_thirdPerson, "cg_thirdPerson", 0, 0, 0, 0, 0, qfalse }, { &r_lodCurveError, "r_lodCurveError", 250, 1, -1, 0, 0, qfalse }, @@ -517,6 +516,8 @@ void CG_UpdateCvars( void ) { } else if (cv->vmCvar == &cg_pipeTrail) { CG_UpdateColorFromCvar(cg_pipeTrail.string, colorPipeTrail, &cg.pipeTrailTeam, cg.pipeTrailColor); + } else if (cv->vmCvar == &cl_maxfps || cv->vmCvar == &com_maxfps) { + CG_Update_MaxFPS(); } } } @@ -542,8 +543,10 @@ void CG_UpdateCvars( void ) { CG_LimitCvars(); // Handle packet delay - if (cg_packetdelay.integer != cl_packetdelay.integer) + if (cg_packetdelay.integer != cl_packetdelay.integer) { trap_Cvar_Set("cl_packetdelay", cg_packetdelay.string); + trap_Cvar_Update(&cl_packetdelay); + } } diff --git a/code/cgame/cg_servercmds.c b/code/cgame/cg_servercmds.c index e9c953b..571a4b1 100644 --- a/code/cgame/cg_servercmds.c +++ b/code/cgame/cg_servercmds.c @@ -217,6 +217,7 @@ void CG_ParseSysteminfo( void ) { } else if ( cgs.pmove_msec > 33 ) { cgs.pmove_msec = 33; } + CG_Update_MaxFPS(); cgs.sv_fps = Q_atoi( Info_ValueForKey( info, "sv_fps" ) ); @@ -899,3 +900,23 @@ void CG_ExecuteNewServerCommands( int latestSequence ) { } } } + + +/* +==================== +CG_Update_MaxFPS + +Update `com_maxfps` to max allowed value by gamesettings and `cl_maxfps`. +==================== +*/ +void CG_Update_MaxFPS(void) { + // Temp: Higher than 250 wants changes later in stack at higher pings. + int fps = cl_maxfps.integer == -1 ? 250 : cl_maxfps.integer; + fps = Q_max(Q_min(fps, cgs.pmove_fixed ? 500 : 125), 60); + + if (com_maxfps.integer == fps) + return; + + trap_Cvar_Set("com_maxfps", va("%d", fps)); + trap_Cvar_Update(&com_maxfps); +} diff --git a/code/game/g_main.c b/code/game/g_main.c index 85c9eaa..97a6fab 100644 --- a/code/game/g_main.c +++ b/code/game/g_main.c @@ -865,7 +865,6 @@ void G_InitGame( int levelTime, int randomSeed, int restart ) { G_LuaHook_InitGame( levelTime, randomSeed, restart ); #endif // BUILD_LUA - #ifdef PERFLOG BG_Q3F_PerformanceMonitor_LogFunctionStop(); #endif From d1149c350e61f3f6654aebca1bcdb8d1f14a16eb Mon Sep 17 00:00:00 2001 From: newby Date: Sun, 6 Jul 2025 16:30:26 -0700 Subject: [PATCH 3/9] bug fixes: fix old nail bugs and refine prediction - remove fudge factors for prediction being a rendered frame ahea and use the time from command frame directly. --> 0 error on prediction. - fix ancient nail bugs where they were not being correctly snapped - fix ancient nail bugs where original position was wrong for collision calcs --- code/cgame/cg_local.h | 1 - code/cgame/cg_localents.c | 19 +++++++++++++------ code/cgame/cg_unlagged.c | 5 ++--- code/game/g_active.c | 2 +- code/game/g_missile.c | 8 ++++---- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/code/cgame/cg_local.h b/code/cgame/cg_local.h index d100bb3..03302e0 100644 --- a/code/cgame/cg_local.h +++ b/code/cgame/cg_local.h @@ -314,7 +314,6 @@ typedef struct centity_s { // exact interpolated position of entity on this frame vec3_t lerpOrigin; vec3_t lerpAngles; - } centity_t; diff --git a/code/cgame/cg_localents.c b/code/cgame/cg_localents.c index 55a266f..adda4e5 100644 --- a/code/cgame/cg_localents.c +++ b/code/cgame/cg_localents.c @@ -967,7 +967,7 @@ static void CG_UntrackPredictedEnt(localEntity_t *le) { void CG_MatchPredictedEnt(centity_t* cent) { localEntity_t* match = NULL; - float best = SQR(64); + float best = SQR(32); vec3_t diff; // Can only match entities we own. @@ -1025,6 +1025,7 @@ void CG_UpdateLocalPredictedEnts(void) { } void CG_AddPredictedMissile(entityState_t* ent, vec3_t origin, vec3_t forward) { + usercmd_t cmd; localEntity_t *le; int vel, tmod = 0; @@ -1058,15 +1059,21 @@ void CG_AddPredictedMissile(entityState_t* ent, vec3_t origin, vec3_t forward) { le->leFlags = LEF_PREDICTED; le->pred_weapon = ent->weapon; + // Technically we could chain this from where we predict the event but this + // should always be correct since command generation is tied to frame + // generation. + trap_GetUserCmd( trap_GetCurrentCmdNumber(), &cmd ); + le->pos.trType = TR_LINEAR; - le->pos.trTime = cg.time - tmod + cl_timeNudge.integer; + le->pos.trTime = cmd.serverTime - tmod + cl_timeNudge.integer; - VectorCopy(origin, le->lerp.trBase); - VectorCopy(origin, le->pos.trBase); if (ent->weapon == WP_SUPERNAILGUN || ent->weapon == WP_NAILGUN) { - VectorMA(le->pos.trBase, -15, forward, le->pos.trBase); - le->pos.trBase[2] -= 6; + VectorMA(origin, -15, forward, origin); + origin[2] -= 6; + SnapVector(origin); } + VectorCopy(origin, le->lerp.trBase); + VectorCopy(origin, le->pos.trBase); VectorScale(forward, vel, le->pos.trDelta ); SnapVector(le->pos.trDelta); diff --git a/code/cgame/cg_unlagged.c b/code/cgame/cg_unlagged.c index 2dd4db3..0446e9e 100644 --- a/code/cgame/cg_unlagged.c +++ b/code/cgame/cg_unlagged.c @@ -64,6 +64,7 @@ void CG_PredictWeaponEffects( centity_t *cent ) { // get the muzzle point VectorCopy( cg.predictedPlayerState.origin, muzzlePoint ); + SnapVector(muzzlePoint); // match remote s.pos muzzlePoint[2] += cg.predictedPlayerState.viewheight; // get forward, right, and up @@ -75,18 +76,17 @@ void CG_PredictWeaponEffects( centity_t *cent ) { AngleVectors( cg.predictedPlayerState.viewangles, forward, NULL, NULL ); } VectorMA( muzzlePoint, 14, forward, muzzlePoint ); + SnapVector(muzzlePoint); CG_AddPredictedMissile(ent, muzzlePoint, forward); if ( ent->weapon == WP_SHOTGUN ) { VectorScale( forward, 4096, endPoint ); SnapVector( endPoint ); - SnapVector( muzzlePoint ); CG_SingleShotgunPattern( muzzlePoint, endPoint , ent->clientNum, cg.oldTime % 256 ); } else if ( ent->weapon == WP_SUPERSHOTGUN ) { VectorScale( forward, 4096, endPoint ); SnapVector( endPoint ); - SnapVector( muzzlePoint ); CG_ShotgunPattern( muzzlePoint, endPoint , ent->clientNum, cg.oldTime % 256 ); } else if ( ent->weapon == WP_MINIGUN ) { int heat = cg.minigunHeat - ((cg.time - cg.minigunLast)*15) / 5000; @@ -94,7 +94,6 @@ void CG_PredictWeaponEffects( centity_t *cent ) { heat = 0; VectorScale( forward, 4096, endPoint ); SnapVector( endPoint ); - SnapVector( muzzlePoint ); CG_MinigunPattern(muzzlePoint, endPoint , ent->clientNum, cg.oldTime % 256, heat ); } else if ( ent->weapon == WP_ASSAULTRIFLE ) { VectorMA (muzzlePoint, 4096, forward, endPoint ); diff --git a/code/game/g_active.c b/code/game/g_active.c index ed5e244..db36cf0 100644 --- a/code/game/g_active.c +++ b/code/game/g_active.c @@ -934,7 +934,7 @@ void ClientThink_real( gentity_t *ent ) { // attackTime will be used for backward reconciliation later (time shift) client->attackTime = ucmd->serverTime; // For projectile attacks we want raw client positions - client->attackTimeProj = ucmd->serverTime + client->pers.timeNudge + 1000/142 + 1; + client->attackTimeProj = ucmd->serverTime + client->pers.timeNudge; //unlagged - backward reconciliation #4 //unlagged - smooth clients #1 diff --git a/code/game/g_missile.c b/code/game/g_missile.c index a70a88f..61190ca 100644 --- a/code/game/g_missile.c +++ b/code/game/g_missile.c @@ -788,12 +788,12 @@ gentity_t *fire_nail (gentity_t *self, vec3_t start, vec3_t dir, int damage, int bolt->r.maxs[2] = 1; bolt->s.pos.trTime = level.time; - //VectorMA( muzzle, -50, dir, bolt->s.pos.trBase ); // Draw a frame previous. - VectorMA( start, -15, dir, bolt->s.pos.trBase ); // Draw a frame previous. - bolt->s.pos.trBase[2] -= 6; -// VectorScale( dir, 1100, bolt->s.pos.trDelta ); + VectorMA( start, -15, dir, start); // Draw a frame previous. + start[2] -= 6; VectorScale( dir, mod == MOD_SUPERNAILGUN ? PROJ_SPEED_SNG : PROJ_SPEED_NG, bolt->s.pos.trDelta ); // djbob: 2.2 value 1.6 1250 for both nailguns SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + SnapVector(start); + VectorCopy (start, bolt->s.pos.trBase); VectorCopy (start, bolt->r.currentOrigin); if (self->client && self->client->pers.unlagged) From aa4059e09ff7cac6d905921d4e27f8f17bc8055b Mon Sep 17 00:00:00 2001 From: newby Date: Tue, 8 Jul 2025 01:02:24 -0700 Subject: [PATCH 4/9] platform: fix mingw cross compile The introduced byteswap.h dependency isn't portable, use supported intrinsics --- code/api/shared/q_endian.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/code/api/shared/q_endian.h b/code/api/shared/q_endian.h index 678de3e..0440aa3 100644 --- a/code/api/shared/q_endian.h +++ b/code/api/shared/q_endian.h @@ -93,6 +93,12 @@ void CopyLongSwap(void *dest, void *src); #define LongLongSwap(x) bswap64(x) #endif +#elif defined(__MINGW32__) + +#define ShortSwap(x) __builtin_bswap16(x) +#define LongSwap(x) __builtin_bswap32(x) +#define LongLongSwap(x) __builtin_bswap64(x) + #else #include From e82038b8e74865c0e1cc1ceca1da032022793c05 Mon Sep 17 00:00:00 2001 From: newby Date: Sat, 5 Jul 2025 17:09:21 -0700 Subject: [PATCH 5/9] pmove: add pmove_float add upstream `pmove_float` (default on) which makes jump height no longer fps dependent. Previously this specific behavior resulted from accumulated errors on gravity truncation; resolve by not truncating gravity except for one case which otherwise results in erratic accel. Note: This doesnt fix other fps dependent accel --- code/cgame/cg_local.h | 3 ++- code/cgame/cg_predict.c | 1 + code/cgame/cg_servercmds.c | 1 + code/game/bg_pmove.c | 5 ++++- code/game/bg_public.h | 1 + code/game/g_active.c | 1 + code/game/g_cvar.h | 1 + 7 files changed, 11 insertions(+), 2 deletions(-) diff --git a/code/cgame/cg_local.h b/code/cgame/cg_local.h index 03302e0..8c5c27a 100644 --- a/code/cgame/cg_local.h +++ b/code/cgame/cg_local.h @@ -1450,7 +1450,8 @@ typedef struct { float oldtimescale; // Timescale value prior to pausing qboolean pmove_fixed; - int pmove_msec; + int pmove_msec; + int pmove_float; int sv_fps; diff --git a/code/cgame/cg_predict.c b/code/cgame/cg_predict.c index fa8749f..72b002e 100644 --- a/code/cgame/cg_predict.c +++ b/code/cgame/cg_predict.c @@ -736,6 +736,7 @@ void CG_PredictPlayerState( void ) { cg_pmove.pmove_fixed = (int)cgs.pmove_fixed; cg_pmove.pmove_msec = cgs.pmove_msec; + cg_pmove.pmove_float = cgs.pmove_float; // Like the comments described above, a player's state is entirely // re-predicted from the last valid snapshot every client frame, which diff --git a/code/cgame/cg_servercmds.c b/code/cgame/cg_servercmds.c index 571a4b1..3d2d2f5 100644 --- a/code/cgame/cg_servercmds.c +++ b/code/cgame/cg_servercmds.c @@ -217,6 +217,7 @@ void CG_ParseSysteminfo( void ) { } else if ( cgs.pmove_msec > 33 ) { cgs.pmove_msec = 33; } + cgs.pmove_float = ( atoi( Info_ValueForKey( info, "pmove_float" ) ) ) ? qtrue : qfalse; CG_Update_MaxFPS(); cgs.sv_fps = Q_atoi( Info_ValueForKey( info, "sv_fps" ) ); diff --git a/code/game/bg_pmove.c b/code/game/bg_pmove.c index f3bf624..252a8b5 100644 --- a/code/game/bg_pmove.c +++ b/code/game/bg_pmove.c @@ -2936,7 +2936,10 @@ void PmoveSingle (pmove_t *pmove) { PM_WaterEvents(); // snap some parts of playerstate to save network bandwidth - trap_SnapVector( pm->ps->velocity ); + // But only if pmove_float is not enabled. We always snap on slick + // surfaces to prevent acceleration. + if (!pm->pmove_float || pml.groundTrace.surfaceFlags & SURF_SLICK) + trap_SnapVector( pm->ps->velocity ); } diff --git a/code/game/bg_public.h b/code/game/bg_public.h index e45e69b..30de240 100644 --- a/code/game/bg_public.h +++ b/code/game/bg_public.h @@ -259,6 +259,7 @@ typedef struct { // for fixed msec Pmove int pmove_fixed; int pmove_msec; + int pmove_float; // callbacks to test the world // these will be different functions during game and cgame diff --git a/code/game/g_active.c b/code/game/g_active.c index db36cf0..e1329f0 100644 --- a/code/game/g_active.c +++ b/code/game/g_active.c @@ -1251,6 +1251,7 @@ void ClientThink_real( gentity_t *ent ) { pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; pm.pmove_msec = pmove_msec.integer; + pm.pmove_float = pmove_float.integer; VectorCopy( client->ps.origin, client->oldOrigin ); diff --git a/code/game/g_cvar.h b/code/game/g_cvar.h index b04dab8..e4a9948 100644 --- a/code/game/g_cvar.h +++ b/code/game/g_cvar.h @@ -126,6 +126,7 @@ G_CVAR( g_listEntity, "g_listEntity", "0", 0, qfalse ) G_CVAR( pmove_fixed, "pmove_fixed", "0", CVAR_SYSTEMINFO, qfalse ) G_CVAR( pmove_msec, "pmove_msec", "8", CVAR_SYSTEMINFO, qfalse ) +G_CVAR( pmove_float, "pmove_float", "1", CVAR_SYSTEMINFO, qfalse ) G_CVAR( g_suicideDelay, "g_suicideDelay", "7", 0, qfalse ) // Golliwog: Delay after suiciding G_CVAR( g_teamChatSounds, "g_teamChatSounds", "4", 0, qfalse ) // Golliwog: Allow sounds/sounddict in team chat From 5fc2002ae3f66cf4f6cc7f7454e23322aeeb9f52 Mon Sep 17 00:00:00 2001 From: newby Date: Thu, 3 Jul 2025 20:31:22 -0700 Subject: [PATCH 6/9] pmove_fixed: refactor ClientEndFrame - not correctly executed under pmove_fixed - no reason to evaluate every _client_ frame - no reason to buffer underruns (client >1s stale has bigger problems) - merge per server frame checks --- code/game/g_active.c | 624 +++++++++++++++++++++---------------------- code/game/g_local.h | 4 +- 2 files changed, 312 insertions(+), 316 deletions(-) diff --git a/code/game/g_active.c b/code/game/g_active.c index e1329f0..a2eb73e 100644 --- a/code/game/g_active.c +++ b/code/game/g_active.c @@ -542,96 +542,340 @@ ClientTimerActions Actions that happen once a second ================== */ -void ClientTimerActions( gentity_t *ent, int msec ) { +void ClientTimerActions( gentity_t *ent ) { gclient_t *client; - bg_q3f_playerclass_t *cls; + bg_q3f_playerclass_t *cls, *agentcls; char userinfo[MAX_INFO_STRING]; const char *dataptr; + gentity_t *other; int data; client = ent->client; - cls = BG_Q3F_GetClass( &client->ps ); - client->timeResidual += msec; - while ( client->timeResidual >= 1000 ) { - client->timeResidual -= 1000; + if ( client->wantsscore ) { + G_SendScore(ent); + client->wantsscore = qfalse; + } - // regenerate - if ( client->ps.powerups[PW_REGEN] ) { - if ( ent->health < cls->maxhealth) { - ent->health += 15; - if ( ent->health > cls->maxhealth * 1.1 ) { - ent->health = cls->maxhealth * 1.1; - } - G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); - } else if ( ent->health < cls->maxhealth * 2) { - ent->health += 5; - if ( ent->health > cls->maxhealth * 2 ) { - ent->health = cls->maxhealth * 2; - } - G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); - } + // clear the rewards if time + if ( level.time > client->rewardTime ) { + client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT ); + } + + // clear drop anim if time + if( !(client->ps.extFlags & EXTF_ANI_THROWING) && + !(client->ps.extFlags & EXTF_ANI_OPERATING)) { + client->torsoanimEndTime = 0; + } else if( client->torsoanimEndTime < level.time ) { + client->ps.extFlags &= ~EXTF_ANI_THROWING; + client->ps.extFlags &= ~EXTF_ANI_OPERATING; + } + + if ( client->noclip ) { + client->ps.pm_type = PM_NOCLIP; + } else if ( client->ps.stats[STAT_HEALTH] <= 0 ) { + client->ps.pm_type = PM_DEAD; + } else if( client->ps.eFlags & EF_Q3F_INVISIBLE ) { + client->ps.pm_type = PM_INVISIBLE; + } else { + client->ps.pm_type = PM_NORMAL; + } + + // JT: Pay attention to STAT_Q3F_FLAGS + + client->ps.gravity = g_gravity.value; + + // set speed + // Golliwog: Altered for class speeds, and max health + if( (cls = BG_Q3F_GetClass( &client->ps )) != NULL ) + client->ps.speed = cls->maxspeed; + else + client->ps.speed = g_speed.value; + // Golliwog. + + // Golliwog: Have agents slow down to apparent class speed + if( client->ps.persistant[PERS_CURRCLASS] == Q3F_CLASS_AGENT && client->agentclass && client->sess.adjustAgentSpeed ) + { + agentcls = bg_q3f_classlist[client->agentclass]; + if( client->ps.speed > agentcls->maxspeed ) + client->ps.speed = agentcls->maxspeed; + } + // Golliwog. + + // Tranquilised + if( client->tranqTime ) { + if( level.time >= client->tranqTime ) { + trap_SendServerCommand( ent->s.number, "print \"You feel better now.\n\"" ); + client->tranqTime = 0; + client->tranqEnt = NULL; + client->ps.stats[STAT_Q3F_FLAGS] &= ~(1<ps.speed /= 2; + client->ps.stats[STAT_Q3F_FLAGS] |= (1<ps.stats[STAT_Q3F_FLAGS] &= ~(1<flames > 0 && ent->health > 0) { + client->ps.extFlags |= EXTF_BURNING; + } else { + client->ps.extFlags &= ~EXTF_BURNING; + } + //Canabis, am i tranqed? + if (client->tranqTime >= level.time && ent->health > 0) { + client->ps.extFlags |= EXTF_TRANQED; + } else { + client->ps.extFlags &= ~EXTF_TRANQED; + } + //Ensiform, am i legshot? + if (client->legwounds && ent->health > 0) { + if( client->legwounds > 6 ) + client->legwounds = 6; // Cap the wounds to something sensible. + client->ps.extFlags |= EXTF_LEGWOUNDS; + } else { + client->ps.extFlags &= ~EXTF_LEGWOUNDS; + } + // Agent invisible + if( client->ps.eFlags & EF_Q3F_INVISIBLE ) + client->ps.speed = 0; // Can't move if invisible. + + // Laying a charge. + if( client->chargeTime ) { + if( client->chargeTime <= level.time ) { + client->ps.ammoclip[3] = client->chargeEntity->soundPos1; + trap_SendServerCommand( ent->s.number, "print \"Charge set.\n\"" ); + if( client->ps.ammo[AMMO_CHARGE] > 0 ) + client->ps.ammo[AMMO_CHARGE]--; + client->chargeTime = 0; // Allow movement again + client->ps.stats[STAT_Q3F_FLAGS] &= ~(1 << FL_Q3F_LAYCHARGE); + client->chargeEntity->s.legsAnim = 1; + #ifdef BUILD_BOTS + Bot_Event_DetpackBuilt(ent, client->chargeEntity); + #endif + ent->client->pers.stats.data[STATS_GREN + Q3F_GREN_CHARGE].shots++; } else { - // count down health when over max - if ( ent->health > cls->maxhealth ) { - ent->health--; + other = client->chargeEntity; + if( Distance( other->r.currentOrigin, ent->r.currentOrigin ) > 100 ) { + // They've moved too far, cancel the lay. + if( other->inuse && (other->s.eType == ET_Q3F_GRENADE && other->s.weapon == Q3F_GREN_CHARGE)) + G_FreeEntity( other ); + else G_Printf( "Attempted to free '%s' as charge.\n", other->classname ); + client->chargeEntity = NULL; + client->chargeTime = 0; + client->ps.stats[STAT_Q3F_FLAGS] &= ~(1 << FL_Q3F_LAYCHARGE); } + else client->ps.speed = 0; // Stop all movement. } + } + // Disarming a charge + if( client->chargeDisarmTime ) + { + if( !client->chargeDisarmEnt || !client->chargeDisarmEnt->inuse ) + { + // Lost our disarm ent? - // count down armor when over max - // FALCON: Modified to suit Q3F's per-class max armour - if ( client->ps.stats[STAT_ARMOR] > cls->maxarmour ) { - client->ps.stats[STAT_ARMOR]--; - // FALCON: END + client->chargeDisarmEnt = NULL; + client->chargeDisarmTime = 0; } + else if( !(client->ps.pm_flags & PMF_DUCKED) || + !trap_EntityContact( ent->r.absmin, ent->r.absmax, client->chargeDisarmEnt ) ) + { + // We've lost contact. + + if( client->chargeDisarmEnt->count >= 5 && !(rand() % 10) ) + { + // They've flumped the disarming process :) - // Golliwog: Agent invisible, consumes cells. This should be handled - // in Pmove, I think. - if( ent->s.eFlags & EF_Q3F_INVISIBLE ) + trap_SendServerCommand( ent->s.number, "print \"HE Charge Triggered! RUN!\n\"" ); + client->chargeDisarmEnt->count = 5; + client->chargeDisarmEnt->nextthink = level.time; + } + client->chargeDisarmEnt = NULL; + client->chargeDisarmTime = 0; + } + else if( client->chargeDisarmTime <= level.time ) { - if( --client->ps.ammo[AMMO_CELLS] <= 0 ) + // We've finished, clean this ent up. + + trap_SendServerCommand( ent->s.number, "print \"HE Charge Disarmed.\n\"" ); + if( client->chargeDisarmEnt->activator && client->chargeDisarmEnt->activator->inuse ) { - client->ps.ammo[AMMO_CELLS] = 0; - G_Q3F_StopAgentInvisible( ent ); - trap_SendServerCommand( ent->s.number, "print \"Invisibility failed for lack of cells!\n\"" ); + client->chargeDisarmEnt->activator->client->chargeEntity = NULL; + client->chargeDisarmEnt->activator->client->chargeTime = 0; + client->chargeDisarmEnt->activator->client->ps.stats[STAT_Q3F_FLAGS] &= ~(1 << FL_Q3F_LAYCHARGE); } - else if( VectorLength( ent->client->ps.velocity ) > 20 ) + G_FreeEntity( client->chargeDisarmEnt ); + client->chargeDisarmEnt = NULL; + client->chargeDisarmTime = 0; + } else client->ps.speed = 0; // Stop all movement. + } + + if( client->buildTime ) { + if( client->buildTime <= level.time ) { + client->buildTime = 0; // Allow movement again + client->ps.stats[STAT_Q3F_FLAGS] &= ~(1 << FL_Q3F_BUILDING); + } else { + if( (client->sentry && !client->sentry->s.legsAnim && + Distance( client->sentry->r.currentOrigin, ent->r.currentOrigin ) > 100) || + (client->supplystation && !client->supplystation->s.legsAnim && + Distance( client->supplystation->r.currentOrigin, ent->r.currentOrigin ) > 100) ) + { + // They've moved too far, cancel the build. + if( client->sentry ) + G_Q3F_SentryCancel( client->sentry ); + if( client->supplystation ) + G_Q3F_SupplyStationCancel( client->supplystation ); + client->buildTime = 0; + client->ps.stats[STAT_Q3F_FLAGS] &= ~(1 << FL_Q3F_BUILDING); + } else + client->ps.speed = 0; // Stop all movement. + } + } + if( client->repairEnt && client->repairEnt->inuse ) { + if( client->repairEnt->s.eType == ET_Q3F_SENTRY && !G_Q3F_CheckSentryUpgradeable( ent, client->repairEnt->s.number, qtrue, qtrue ) ) { + trap_SendServerCommand( ent->s.number, "menu cancel upgradeautosentry" ); + client->repairEnt = NULL; + } else if ( client->repairEnt->s.eType == ET_Q3F_SUPPLYSTATION && !G_Q3F_CheckSupplyStation( ent, client->repairEnt->s.number, qtrue ) ) { + trap_SendServerCommand( ent->s.number, "menu cancel upgradesupplystation" ); + client->repairEnt = NULL; + } + } + + if( client->speedscale ) // Goalitem speed scale + client->ps.speed *= client->speedscale; + + if( client->callTime && client->callTime <= level.time ) { + // Golliwog: Reset call flags (should use a PlayerStateToEntityState call) + client->ps.eFlags &= ~EF_Q3F_MASKME; + ent->s.eFlags &= ~EF_Q3F_MASKME; + client->callTime = 0; + } + if ( client->ps.powerups[PW_HASTE] ) { + client->ps.speed *= 1.3; + } +#ifdef SENTRY_MOVE + if ( client->ps.stats[STAT_Q3F_FLAGS] & (1 << FL_Q3F_MOVING)) { + client->ps.speed *= 0.5; + } +#endif + // Sniper shots to legs. + if(client->legwounds) { + client->ps.speed -= 0.1 * client->ps.speed * client->legwounds; + } + + if( client->ps.persistant[PERS_CURRCLASS] == Q3F_CLASS_GRENADIER ) + G_Q3F_CheckPipesForPlayer(ent); + + G_Q3F_Check_Maladies(ent); + + // turn off any expired powerups + for (int i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( ent->client->ps.powerups[ i ] && ent->client->ps.powerups[ i ] < level.time ) { + switch( i ) { - G_Q3F_StopAgentInvisible( ent ); - trap_SendServerCommand( ent->s.number, "print \"Invisibility failed because of movement!\n\"" ); + case PW_Q3F_CONCUSS: + trap_SendServerCommand( ent->s.number, "print \"You can see straight again.\n\"" ); + break; + case PW_Q3F_FLASH: + trap_SendServerCommand( ent->s.number, "print \"Your blindness has cleared.\n\"" ); + break; + case PW_Q3F_GAS: + trap_SendServerCommand( ent->s.number, "print \"You feel a little less confused now.\n\"" ); + break; } + ent->client->ps.powerups[ i ] = 0; } - // Golliwog. + } - // Golliwog: Do a minimum rate check. - if( g_minRate.integer && g_banRules.value > 1 ) - { - trap_GetUserinfo( ent->s.number, userinfo, sizeof(userinfo) ); - dataptr = Info_ValueForKey( userinfo, "rate" ); - data = 0; - if( !dataptr || (data = Q_atoi( dataptr )) < g_minRate.integer ) { - G_Q3F_AdminTempBan( ent, va( "Rate set to %d (sv_MinRate is %d)", data, g_minRate.integer ), Q3F_ADMIN_TEMPBAN_TIME ); + // canabis: Check for automatic reload + if( client->pers.autoReload == 3 && client->ps.weaponstate == WEAPON_READY ) + { + if( Q3F_GetClipValue( client->ps.weapon, &client->ps ) < BG_Q3F_GetWeapon( client->ps.weapon )->numammo ) + BG_Q3F_Request_Reload( &client->ps ); + } + + // PERIODIC (1hz) ACTIONS BELOW + if (client->periodicNext > level.time) + return; + client->periodicNext = level.time + 1000; + + cls = BG_Q3F_GetClass( &client->ps ); + // regenerate + if ( client->ps.powerups[PW_REGEN] ) { + if ( ent->health < cls->maxhealth) { + ent->health += 15; + if ( ent->health > cls->maxhealth * 1.1 ) { + ent->health = cls->maxhealth * 1.1; } + G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); + } else if ( ent->health < cls->maxhealth * 2) { + ent->health += 5; + if ( ent->health > cls->maxhealth * 2 ) { + ent->health = cls->maxhealth * 2; + } + G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); } - // Golliwog. - // RR2DO2: Do a minimum snaps check. - if( g_minSnaps.integer && g_banRules.value > 1 ) + } else { + // count down health when over max + if ( ent->health > cls->maxhealth ) { + ent->health--; + } + } + + // count down armor when over max + // FALCON: Modified to suit Q3F's per-class max armour + if ( client->ps.stats[STAT_ARMOR] > cls->maxarmour ) { + client->ps.stats[STAT_ARMOR]--; + // FALCON: END + } + + // Golliwog: Agent invisible, consumes cells. This should be handled + // in Pmove, I think. + if( ent->s.eFlags & EF_Q3F_INVISIBLE ) + { + if( --client->ps.ammo[AMMO_CELLS] <= 0 ) + { + client->ps.ammo[AMMO_CELLS] = 0; + G_Q3F_StopAgentInvisible( ent ); + trap_SendServerCommand( ent->s.number, "print \"Invisibility failed for lack of cells!\n\"" ); + } + else if( VectorLength( ent->client->ps.velocity ) > 20 ) { - trap_GetUserinfo( ent->s.number, userinfo, sizeof(userinfo) ); - dataptr = Info_ValueForKey( userinfo, "snaps" ); - data = 0; - if( !dataptr || (data = Q_atoi( dataptr )) < g_minSnaps.integer ) - G_Q3F_AdminTempBan( ent, va( "Snaps set to %d (sv_MinSnaps is %d)", data, g_minSnaps.integer ), Q3F_ADMIN_TEMPBAN_TIME ); + G_Q3F_StopAgentInvisible( ent ); + trap_SendServerCommand( ent->s.number, "print \"Invisibility failed because of movement!\n\"" ); } + } + // Golliwog. - // Check for valid cg_adjustAgentSpeed + // Golliwog: Do a minimum rate check. + if( g_minRate.integer && g_banRules.value > 1 ) + { trap_GetUserinfo( ent->s.number, userinfo, sizeof(userinfo) ); - dataptr = Info_ValueForKey( userinfo, "cg_adjustAgentSpeed" ); + dataptr = Info_ValueForKey( userinfo, "rate" ); data = 0; - if( dataptr && ent->client ) - ent->client->sess.adjustAgentSpeed = Q_atoi( dataptr ); - // RR2DO2 + if( !dataptr || (data = Q_atoi( dataptr )) < g_minRate.integer ) { + G_Q3F_AdminTempBan( ent, va( "Rate set to %d (sv_MinRate is %d)", data, g_minRate.integer ), Q3F_ADMIN_TEMPBAN_TIME ); + } + } + // Golliwog. + // RR2DO2: Do a minimum snaps check. + if( g_minSnaps.integer && g_banRules.value > 1 ) + { + trap_GetUserinfo( ent->s.number, userinfo, sizeof(userinfo) ); + dataptr = Info_ValueForKey( userinfo, "snaps" ); + data = 0; + if( !dataptr || (data = Q_atoi( dataptr )) < g_minSnaps.integer ) + G_Q3F_AdminTempBan( ent, va( "Snaps set to %d (sv_MinSnaps is %d)", data, g_minSnaps.integer ), Q3F_ADMIN_TEMPBAN_TIME ); } + + // Check for valid cg_adjustAgentSpeed + trap_GetUserinfo( ent->s.number, userinfo, sizeof(userinfo) ); + dataptr = Info_ValueForKey( userinfo, "cg_adjustAgentSpeed" ); + data = 0; + if( dataptr && ent->client ) + ent->client->sess.adjustAgentSpeed = atoi( dataptr ); + // RR2DO2 } /* @@ -847,11 +1091,11 @@ void ClientThink_real( gentity_t *ent ) { int oldEventSequence; int msec; usercmd_t *ucmd; - bg_q3f_playerclass_t *cls, *agentcls; - gentity_t *other; client = ent->client; + ucmd = &ent->client->pers.cmd; + // don't think if the client is not yet connected (and thus not yet spawned in) if (client->pers.connected != CON_CONNECTED) { return; @@ -985,11 +1229,6 @@ void ClientThink_real( gentity_t *ent ) { // return; } - if ( client->wantsscore ) { - G_SendScore(ent); - client->wantsscore = qfalse; - } - // // check for exiting intermission // @@ -1009,221 +1248,8 @@ void ClientThink_real( gentity_t *ent ) { } // check for inactivity timer, but never drop the local client of a non-dedicated server - if ( !ClientInactivityTimer( ent ) ) { + if ( !ClientInactivityTimer( ent ) ) return; - } - - // clear the rewards if time - if ( level.time > client->rewardTime ) { - client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT ); - } - - // clear drop anim if time - if( !(client->ps.extFlags & EXTF_ANI_THROWING) && - !(client->ps.extFlags & EXTF_ANI_OPERATING)) { - client->torsoanimEndTime = 0; - } else if( client->torsoanimEndTime < level.time ) { - client->ps.extFlags &= ~EXTF_ANI_THROWING; - client->ps.extFlags &= ~EXTF_ANI_OPERATING; - } - - if ( client->noclip ) { - client->ps.pm_type = PM_NOCLIP; - } else if ( client->ps.stats[STAT_HEALTH] <= 0 ) { - client->ps.pm_type = PM_DEAD; - } else if( client->ps.eFlags & EF_Q3F_INVISIBLE ) { - client->ps.pm_type = PM_INVISIBLE; - } else { - client->ps.pm_type = PM_NORMAL; - } - - // JT: Pay attention to STAT_Q3F_FLAGS - - client->ps.gravity = g_gravity.value; - - // set speed - // Golliwog: Altered for class speeds, and max health - if( (cls = BG_Q3F_GetClass( &client->ps )) != NULL ) - client->ps.speed = cls->maxspeed; - else - client->ps.speed = g_speed.value; - // Golliwog. - - // Golliwog: Have agents slow down to apparent class speed - if( client->ps.persistant[PERS_CURRCLASS] == Q3F_CLASS_AGENT && client->agentclass && client->sess.adjustAgentSpeed ) - { - agentcls = bg_q3f_classlist[client->agentclass]; - if( client->ps.speed > agentcls->maxspeed ) - client->ps.speed = agentcls->maxspeed; - } - // Golliwog. - - // Tranquilised - if( client->tranqTime ) { - if( level.time >= client->tranqTime ) { - trap_SendServerCommand( ent->s.number, "print \"You feel better now.\n\"" ); - client->tranqTime = 0; - client->tranqEnt = NULL; - client->ps.stats[STAT_Q3F_FLAGS] &= ~(1<ps.speed /= 2; - client->ps.stats[STAT_Q3F_FLAGS] |= (1<ps.stats[STAT_Q3F_FLAGS] &= ~(1<flames > 0 && ent->health > 0) { - client->ps.extFlags |= EXTF_BURNING; - } else { - client->ps.extFlags &= ~EXTF_BURNING; - } - //Canabis, am i tranqed? - if (client->tranqTime >= level.time && ent->health > 0) { - client->ps.extFlags |= EXTF_TRANQED; - } else { - client->ps.extFlags &= ~EXTF_TRANQED; - } - //Ensiform, am i legshot? - if (client->legwounds && ent->health > 0) { - if( client->legwounds > 6 ) - client->legwounds = 6; // Cap the wounds to something sensible. - client->ps.extFlags |= EXTF_LEGWOUNDS; - } else { - client->ps.extFlags &= ~EXTF_LEGWOUNDS; - } - // Agent invisible - if( client->ps.eFlags & EF_Q3F_INVISIBLE ) - client->ps.speed = 0; // Can't move if invisible. - - // Laying a charge. - if( client->chargeTime ) { - if( client->chargeTime <= level.time ) { - client->ps.ammoclip[3] = client->chargeEntity->soundPos1; - trap_SendServerCommand( ent->s.number, "print \"Charge set.\n\"" ); - if( client->ps.ammo[AMMO_CHARGE] > 0 ) - client->ps.ammo[AMMO_CHARGE]--; - client->chargeTime = 0; // Allow movement again - client->ps.stats[STAT_Q3F_FLAGS] &= ~(1 << FL_Q3F_LAYCHARGE); - client->chargeEntity->s.legsAnim = 1; - #ifdef BUILD_BOTS - Bot_Event_DetpackBuilt(ent, client->chargeEntity); - #endif - ent->client->pers.stats.data[STATS_GREN + Q3F_GREN_CHARGE].shots++; - } else { - other = client->chargeEntity; - if( Distance( other->r.currentOrigin, ent->r.currentOrigin ) > 100 ) { - // They've moved too far, cancel the lay. - if( other->inuse && (other->s.eType == ET_Q3F_GRENADE && other->s.weapon == Q3F_GREN_CHARGE)) - G_FreeEntity( other ); - else G_Printf( "Attempted to free '%s' as charge.\n", other->classname ); - client->chargeEntity = NULL; - client->chargeTime = 0; - client->ps.stats[STAT_Q3F_FLAGS] &= ~(1 << FL_Q3F_LAYCHARGE); - } - else client->ps.speed = 0; // Stop all movement. - } - } - // Disarming a charge - if( client->chargeDisarmTime ) - { - if( !client->chargeDisarmEnt || !client->chargeDisarmEnt->inuse ) - { - // Lost our disarm ent? - - client->chargeDisarmEnt = NULL; - client->chargeDisarmTime = 0; - } - else if( !(client->ps.pm_flags & PMF_DUCKED) || - !trap_EntityContact( ent->r.absmin, ent->r.absmax, client->chargeDisarmEnt ) ) - { - // We've lost contact. - - if( client->chargeDisarmEnt->count >= 5 && !(rand() % 10) ) - { - // They've flumped the disarming process :) - - trap_SendServerCommand( ent->s.number, "print \"HE Charge Triggered! RUN!\n\"" ); - client->chargeDisarmEnt->count = 5; - client->chargeDisarmEnt->nextthink = level.time; - } - client->chargeDisarmEnt = NULL; - client->chargeDisarmTime = 0; - } - else if( client->chargeDisarmTime <= level.time ) - { - // We've finished, clean this ent up. - - trap_SendServerCommand( ent->s.number, "print \"HE Charge Disarmed.\n\"" ); - if( client->chargeDisarmEnt->activator && client->chargeDisarmEnt->activator->inuse ) - { - client->chargeDisarmEnt->activator->client->chargeEntity = NULL; - client->chargeDisarmEnt->activator->client->chargeTime = 0; - client->chargeDisarmEnt->activator->client->ps.stats[STAT_Q3F_FLAGS] &= ~(1 << FL_Q3F_LAYCHARGE); - } - G_FreeEntity( client->chargeDisarmEnt ); - client->chargeDisarmEnt = NULL; - client->chargeDisarmTime = 0; - } else client->ps.speed = 0; // Stop all movement. - } - - if( client->buildTime ) { - if( client->buildTime <= level.time ) { - client->buildTime = 0; // Allow movement again - client->ps.stats[STAT_Q3F_FLAGS] &= ~(1 << FL_Q3F_BUILDING); - } else { - if( (client->sentry && !client->sentry->s.legsAnim && - Distance( client->sentry->r.currentOrigin, ent->r.currentOrigin ) > 100) || - (client->supplystation && !client->supplystation->s.legsAnim && - Distance( client->supplystation->r.currentOrigin, ent->r.currentOrigin ) > 100) ) - { - // They've moved too far, cancel the build. - if( client->sentry ) - G_Q3F_SentryCancel( client->sentry ); - if( client->supplystation ) - G_Q3F_SupplyStationCancel( client->supplystation ); - client->buildTime = 0; - client->ps.stats[STAT_Q3F_FLAGS] &= ~(1 << FL_Q3F_BUILDING); - } else - client->ps.speed = 0; // Stop all movement. - } - } - if( client->repairEnt && client->repairEnt->inuse ) { - if( client->repairEnt->s.eType == ET_Q3F_SENTRY && !G_Q3F_CheckSentryUpgradeable( ent, client->repairEnt->s.number, qtrue, qtrue ) ) { - trap_SendServerCommand( ent->s.number, "menu cancel upgradeautosentry" ); - client->repairEnt = NULL; - } else if ( client->repairEnt->s.eType == ET_Q3F_SUPPLYSTATION && !G_Q3F_CheckSupplyStation( ent, client->repairEnt->s.number, qtrue ) ) { - trap_SendServerCommand( ent->s.number, "menu cancel upgradesupplystation" ); - client->repairEnt = NULL; - } - } - - if( client->speedscale ) // Goalitem speed scale - client->ps.speed *= client->speedscale; - - if( client->callTime && client->callTime <= level.time ) { - // Golliwog: Reset call flags (should use a PlayerStateToEntityState call) - client->ps.eFlags &= ~EF_Q3F_MASKME; - ent->s.eFlags &= ~EF_Q3F_MASKME; - client->callTime = 0; - } - if ( client->ps.powerups[PW_HASTE] ) { - client->ps.speed *= 1.3; - } -#ifdef SENTRY_MOVE - if ( client->ps.stats[STAT_Q3F_FLAGS] & (1 << FL_Q3F_MOVING)) { - client->ps.speed *= 0.5; - } -#endif - // Sniper shots to legs. - if(client->legwounds) { - client->ps.speed -= 0.1 * client->ps.speed * client->legwounds; - } - - if( client->ps.persistant[PERS_CURRCLASS] == Q3F_CLASS_GRENADIER ) - G_Q3F_CheckPipesForPlayer(ent); - - G_Q3F_Check_Maladies(ent); // set up for pmove oldEventSequence = client->ps.eventSequence; @@ -1378,13 +1404,6 @@ void ClientThink_real( gentity_t *ent ) { if( !(ucmd->buttons & BUTTON_ATTACK) ) client->lastflame = 0; - // canabis: Check for automatic reload - if( client->pers.autoReload == 3 && client->ps.weaponstate == WEAPON_READY ) - { - if( Q3F_GetClipValue( client->ps.weapon, &client->ps ) < BG_Q3F_GetWeapon( client->ps.weapon )->numammo ) - BG_Q3F_Request_Reload( &client->ps ); - } - // RR2DO2: if we carry an item with "revealagent" make sure we stop our disguise as agent, just in case if( client->agentdata && ( (client->agentdata->s.modelindex2 & Q3F_AGENT_DISGUISEMASK) == 1 || (client->agentdata->s.modelindex2 & Q3F_AGENT_INVISMASK) == 4 ) ) { gentity_t *goalitem; @@ -1471,9 +1490,6 @@ void ClientThink_real( gentity_t *ent ) { break; } } - - // perform once-a-second actions - ClientTimerActions( ent, msec ); } /* @@ -1581,9 +1597,7 @@ while a slow client may have multiple ClientEndFrame between ClientThink. ============== */ void ClientEndFrame( gentity_t *ent ) { - int i; - //clientPersistant_t *pers; - int frames; + int frames; if ( !ent->client ) return; @@ -1636,25 +1650,7 @@ void ClientEndFrame( gentity_t *ent ) { } //pers = &ent->client->pers; - - // turn off any expired powerups - for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { - if ( ent->client->ps.powerups[ i ] && ent->client->ps.powerups[ i ] < level.time ) { - switch( i ) - { - case PW_Q3F_CONCUSS: - trap_SendServerCommand( ent->s.number, "print \"You can see straight again.\n\"" ); - break; - case PW_Q3F_FLASH: - trap_SendServerCommand( ent->s.number, "print \"Your blindness has cleared.\n\"" ); - break; - case PW_Q3F_GAS: - trap_SendServerCommand( ent->s.number, "print \"You feel a little less confused now.\n\"" ); - break; - } - ent->client->ps.powerups[ i ] = 0; - } - } + ClientTimerActions(ent); // save network bandwidth #if 0 diff --git a/code/game/g_local.h b/code/game/g_local.h index 8befad8..083ce66 100644 --- a/code/game/g_local.h +++ b/code/game/g_local.h @@ -446,9 +446,9 @@ struct gclient_s { int switchTeamTime; // time the player switched teams - // timeResidual is used to handle events that happen every second + // periodicNext is used to handle events that happen every second // like health / armor countdowns and regeneration - int timeResidual; + int periodicNext; char *areabits; From fe84900f8723ef6783f0322cc563b9322dc0a879 Mon Sep 17 00:00:00 2001 From: newby Date: Fri, 4 Jul 2025 01:47:25 -0700 Subject: [PATCH 7/9] pmove-fixed: rectify and cleanup - remove client toggle (either it works for everyone or it's bugged and shouldn't be used) - remove realping, adds high per-frame overhead, not used - remove latency simulation, packetdelay exists and adds complexity to prediction cache / one less think to think about with pmove_fixed - move pmove_var fixup out of clientthink --- code/cgame/cg_cvar.h | 2 - code/cgame/cg_main.c | 2 - code/cgame/cg_predict.c | 8 +-- code/cgame/cg_snapshot.c | 32 ++--------- code/cgame/cg_view.c | 2 - code/game/g_active.c | 97 ++-------------------------------- code/game/g_client.c | 8 --- code/game/g_cmds.c | 6 +-- code/game/g_local.h | 9 ---- code/game/g_main.c | 23 ++++---- code/game/g_q3f_admin.c | 2 +- code/game/g_q3f_controllable.c | 2 +- 12 files changed, 27 insertions(+), 166 deletions(-) diff --git a/code/cgame/cg_cvar.h b/code/cgame/cg_cvar.h index b628c5a..c0b9c46 100644 --- a/code/cgame/cg_cvar.h +++ b/code/cgame/cg_cvar.h @@ -228,8 +228,6 @@ CG_CVAR( cg_cmdTimeNudge, "cg_cmdTimeNudge", "0", CVAR_ARCHIVE | CVAR_USERI CG_CVAR( cg_projectileNudge, "cg_projectileNudge", "0", CVAR_ARCHIVE ) CG_CVAR( cg_optimizePrediction, "cg_optimizePrediction", "1", CVAR_ARCHIVE ) CG_CVAR( cl_timeNudge, "cl_timeNudge", "0", CVAR_ARCHIVE | CVAR_USERINFO ) -CG_CVAR( cg_latentSnaps, "cg_latentSnaps", "0", CVAR_USERINFO | CVAR_CHEAT ) -CG_CVAR( cg_latentCmds, "cg_latentCmds", "0", CVAR_USERINFO | CVAR_CHEAT ) CG_CVAR( cg_plOut, "cg_plOut", "0", CVAR_USERINFO | CVAR_CHEAT ) CG_CVAR( cg_predictItems, "cg_predictItems", "1", CVAR_ARCHIVE ) CG_CVAR( cg_predictWeapons, "cg_predictWeapons", "0", CVAR_ARCHIVE ) diff --git a/code/cgame/cg_main.c b/code/cgame/cg_main.c index f4cc631..23e4fb8 100644 --- a/code/cgame/cg_main.c +++ b/code/cgame/cg_main.c @@ -197,8 +197,6 @@ static cvarLimitTable_t cvarLimitTable[] = { //Unlagged cvars { &cg_cmdTimeNudge, "cg_cmdTimeNudge", 0, 0, 999, 0, 0, qtrue }, { &cl_timeNudge, "cl_timeNudge", 0, -50, 50, 0, 0, qtrue }, - { &cg_latentSnaps, "cg_latentSnaps", 0, 0, 10, 0, 0, qtrue }, - { &cg_latentCmds, "cg_latentCmds", 0, 0, MAX_LATENT_CMDS - 1 , 0, 0, qtrue }, { &cg_plOut, "cg_plOut", 0, 0, 100 , 0, 0, qtrue }, // hunkmegs { &com_hunkmegs, "com_hunkmegs", 128, 128, -1, 0, 0, qfalse }, diff --git a/code/cgame/cg_predict.c b/code/cgame/cg_predict.c index 72b002e..0822db1 100644 --- a/code/cgame/cg_predict.c +++ b/code/cgame/cg_predict.c @@ -756,10 +756,7 @@ void CG_PredictPlayerState( void ) { // except a frame following a new snapshot in which there was a prediction // error. This yeilds anywhere from a 15% to 40% performance increase, // depending on how much of a bottleneck the CPU is. - - // we check for cg_latentCmds because it'll mess up the optimization - // FIXME: make cg_latentCmds work with cg_optimizePrediction? - if ( cg_optimizePrediction.integer && !cg_latentCmds.integer ) { + if ( cg_optimizePrediction.integer) { if ( cg.nextFrameTeleport || cg.thisFrameTeleport ) { // do a full predict cg.lastPredictedCommand = 0; @@ -934,8 +931,7 @@ void CG_PredictPlayerState( void ) { } else { cg_pmove.agentclass = 0; } - // we check for cg_latentCmds because it'll mess up the optimization - if ( cg_optimizePrediction.integer && !cg_latentCmds.integer ) { + if ( cg_optimizePrediction.integer ) { // if we need to predict this command, or we've run out of space in the saved states queue if ( cmdNum >= predictCmd || (stateIndex + 1) % NUM_SAVED_STATES == cg.stateHead ) { // run the Pmove diff --git a/code/cgame/cg_snapshot.c b/code/cgame/cg_snapshot.c index 939d7cc..37d40cb 100644 --- a/code/cgame/cg_snapshot.c +++ b/code/cgame/cg_snapshot.c @@ -306,26 +306,13 @@ static snapshot_t *CG_ReadNextSnapshot( void ) { // try to read the snapshot from the client system cgs.processedSnapshotNum++; r = trap_GetSnapshot( cgs.processedSnapshotNum, dest ); - // the client wants latent snaps and the just-read snapshot is valid - if ( cg_latentSnaps.integer && r ) { - int i = 0, time = dest->serverTime; - - // keep grabbing one snapshot earlier until we get to the right time - while ( dest->serverTime > time - cg_latentSnaps.integer * (1000 / cgs.sv_fps) ) { - if ( !(r = trap_GetSnapshot( cgs.processedSnapshotNum - i, dest )) ) { - // the snapshot is not valid, so stop here - break; - } - - // go back one more - i++; - } - } +#if 0 // FIXME: why would trap_GetSnapshot return a snapshot with the same server time if ( cg.snap && r && dest->serverTime == cg.snap->serverTime ) { //continue; } +#endif // if it succeeded, return if ( r ) { @@ -349,7 +336,6 @@ static snapshot_t *CG_ReadNextSnapshot( void ) { return NULL; } - /* ============ CG_ProcessSnapshots @@ -377,13 +363,8 @@ void CG_ProcessSnapshots( void ) { trap_GetCurrentSnapshotNumber( &n, &cg.latestSnapshotTime ); if ( n != cg.latestSnapshotNum ) { if ( n < cg.latestSnapshotNum ) { - // this may actually happen with lag simulation going on - if ( cg_latentSnaps.integer ) { - CG_Printf( BOX_PRINT_MODE_CHAT, "WARNING: CG_ProcessSnapshots: n < cg.latestSnapshotNum\n" ); - } else { // this should never happen - CG_Error( "CG_ProcessSnapshots: n < cg.latestSnapshotNum" ); - } + CG_Error( "CG_ProcessSnapshots: n < cg.latestSnapshotNum" ); } cg.latestSnapshotNum = n; } @@ -423,12 +404,7 @@ void CG_ProcessSnapshots( void ) { // if time went backwards, we have a level restart if ( cg.nextSnap->serverTime < cg.snap->serverTime ) { - // this may actually happen with lag simulation going on - if ( cg_latentSnaps.integer ) { - CG_Printf( BOX_PRINT_MODE_CHAT, "WARNING: CG_ProcessSnapshots: Server time went backwards\n" ); - } else { - CG_Error( "CG_ProcessSnapshots: Server time went backwards" ); - } + CG_Error( "CG_ProcessSnapshots: Server time went backwards" ); } } diff --git a/code/cgame/cg_view.c b/code/cgame/cg_view.c index 94b1693..2b8a5db 100644 --- a/code/cgame/cg_view.c +++ b/code/cgame/cg_view.c @@ -1208,8 +1208,6 @@ void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demo } cg.time = serverTime; - // adjust the clock to reflect latent snaps - cg.time -= cg_latentSnaps.integer * (1000 / cgs.sv_fps); cg.demoPlayback = demoPlayback; diff --git a/code/game/g_active.c b/code/game/g_active.c index a2eb73e..ba1a9bd 100644 --- a/code/game/g_active.c +++ b/code/game/g_active.c @@ -1089,7 +1089,6 @@ void ClientThink_real( gentity_t *ent ) { gclient_t *client; pmove_t pm; int oldEventSequence; - int msec; usercmd_t *ucmd; client = ent->client; @@ -1119,59 +1118,6 @@ void ClientThink_real( gentity_t *ent ) { // does a G_RunFrame() client->frameOffset = trap_Milliseconds() - level.frameStartTime; - // save the estimated ping in a queue for averaging later - // we use level.previousTime to account for 50ms lag correction - // besides, this will turn out numbers more like what players are used to - client->pers.pingsamples[client->pers.samplehead] = level.previousTime + client->frameOffset - ucmd->serverTime; - client->pers.samplehead++; - if ( client->pers.samplehead >= NUM_PING_SAMPLES ) { - client->pers.samplehead -= NUM_PING_SAMPLES; - } - - // initialize the real ping - if ( g_truePing.integer ) { - int i, sum = 0; - - // get an average of the samples we saved up - for ( i = 0; i < NUM_PING_SAMPLES; i++ ) { - sum += client->pers.pingsamples[i]; - } - - client->pers.realPing = sum / NUM_PING_SAMPLES; - } - else { - // if g_truePing is off, use the normal ping - client->pers.realPing = client->ps.ping; - } - -//unlagged - lag simulation #2 - // keep a queue of past commands - client->pers.cmdqueue[client->pers.cmdhead] = client->pers.cmd; - client->pers.cmdhead++; - if ( client->pers.cmdhead >= MAX_LATENT_CMDS ) { - client->pers.cmdhead -= MAX_LATENT_CMDS; - } - - // if the client wants latency in commands (client-to-server latency) - if ( client->pers.latentCmds ) { - // save the actual command time - int time = ucmd->serverTime; - - // find out which index in the queue we want - int cmdindex = client->pers.cmdhead - client->pers.latentCmds - 1; - while ( cmdindex < 0 ) { - cmdindex += MAX_LATENT_CMDS; - } - - // read in the old command - client->pers.cmd = client->pers.cmdqueue[cmdindex]; - - // adjust the real ping to reflect the new latency - client->pers.realPing += time - ucmd->serverTime; - } -//unlagged - lag simulation #2 - - //unlagged - backward reconciliation #4 // save the command time *before* pmove_fixed messes with the serverTime, // and *after* lag simulation messes with it :) @@ -1187,44 +1133,9 @@ void ClientThink_real( gentity_t *ent ) { client->lastUpdateFrame = level.framenum; //unlagged - smooth clients #1 - -//unlagged - lag simulation #1 - // if the client is adding latency to received snapshots (server-to-client latency) - if ( client->pers.latentSnaps ) { - // adjust the real ping - client->pers.realPing += client->pers.latentSnaps * (1000 / sv_fps.integer); - // adjust the attack time so backward reconciliation will work - client->attackTime -= client->pers.latentSnaps * (1000 / sv_fps.integer); - } -//unlagged - lag simulation #1 - - -//unlagged - true ping - // make sure the true ping is over 0 - with cl_timenudge it can be less - if ( client->pers.realPing < 0 ) { - client->pers.realPing = 0; - } -//unlagged - true ping - - msec = ucmd->serverTime - client->ps.commandTime; - // following others may result in bad times, but we still want - // to check for follow toggles - if ( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW && client->sess.spectatorState != SPECTATOR_CHASE ) { - return; - } - if ( msec > 200 ) { - msec = 200; - } - - if ( pmove_msec.integer < 8 ) { - trap_Cvar_Set("pmove_msec", "8"); - } - else if (pmove_msec.integer > 33) { - trap_Cvar_Set("pmove_msec", "33"); - } - - if ( pmove_fixed.integer || client->pers.pmoveFixed ) { - ucmd->serverTime = ((ucmd->serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer; + if ( pmove_fixed.integer ) { + int pms = pmove_msec.integer; + ucmd->serverTime = ((ucmd->serverTime + pms - 1) / pms) * pms; //if (ucmd->serverTime - client->ps.commandTime <= 0) // return; } @@ -1275,7 +1186,7 @@ void ClientThink_real( gentity_t *ent ) { pm.debugLevel = g_debugMove.integer; pm.noFootsteps = ( g_dmflags.integer & DF_NO_FOOTSTEPS ) > 0; - pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; + pm.pmove_fixed = pmove_fixed.integer; pm.pmove_msec = pmove_msec.integer; pm.pmove_float = pmove_float.integer; diff --git a/code/game/g_client.c b/code/game/g_client.c index 557f276..9bf9837 100644 --- a/code/game/g_client.c +++ b/code/game/g_client.c @@ -1061,14 +1061,6 @@ qboolean ClientUserinfoChanged( int clientNum, const char *reason ) { client->pers.debugDelag = qtrue; } - // see if the player is simulating incoming latency - s = Info_ValueForKey( userinfo, "cg_latentSnaps" ); - client->pers.latentSnaps = Q_atoi( s ); - - // see if the player is simulating outgoing latency - s = Info_ValueForKey( userinfo, "cg_latentCmds" ); - client->pers.latentCmds = Q_atoi( s ); - // see if the player is simulating outgoing packet loss s = Info_ValueForKey( userinfo, "cg_plOut" ); client->pers.plOut = Q_atoi( s ); diff --git a/code/game/g_cmds.c b/code/game/g_cmds.c index 2ae3715..8a88f61 100644 --- a/code/game/g_cmds.c +++ b/code/game/g_cmds.c @@ -104,9 +104,7 @@ void G_SendScore( gentity_t *ent ) { } else if( cl->pers.initializing ) { ping = -2; } else { -//unlagged - true ping - ping = cl->pers.realPing < 999 ? cl->pers.realPing : 999; -// ping = cl->ps.ping < 999 ? cl->ps.ping : 999; + ping = cl->ps.ping < 999 ? cl->ps.ping : 999; } flags = 0; @@ -3677,7 +3675,7 @@ static void G_Q3F_PlayerStatus( gentity_t *ent ) { trap_SendServerCommand( ent-g_entities, va("print \"%3d %4d %6s %5d %s\n\"", player->s.number, - player->client->pers.realPing,//ps.ping + player->client->ps.ping, (player->client->sess.sessionTeam ? g_q3f_teamlist[player->client->sess.sessionTeam].name : "spec"), player->client->ps.persistant[PERS_SCORE], player->client->pers.netname ) ); diff --git a/code/game/g_local.h b/code/game/g_local.h index 083ce66..22d43ca 100644 --- a/code/game/g_local.h +++ b/code/game/g_local.h @@ -350,7 +350,6 @@ typedef struct { qboolean localClient; // true if "ip" info key is "localhost" qboolean initialSpawn; // the first spawn should be at a cool location qboolean predictItemPickup; // based on cg_predictItems userinfo - qboolean pmoveFixed; // char netname[MAX_NETNAME]; int enterTime; // level.time the client entered the game int connectTime; @@ -368,15 +367,7 @@ typedef struct { int timeNudge; int cmdTimeNudge; //unlagged - client options - int latentSnaps; - int latentCmds; int plOut; - usercmd_t cmdqueue[MAX_LATENT_CMDS]; - int cmdhead; -//unlagged - true ping - int realPing; - int pingsamples[NUM_PING_SAMPLES]; - int samplehead; //unlagged - true ping // Canabis, stats block diff --git a/code/game/g_main.c b/code/game/g_main.c index 97a6fab..9a4955d 100644 --- a/code/game/g_main.c +++ b/code/game/g_main.c @@ -414,6 +414,11 @@ static void G_UpdateCvars( void ) { } } + if (pmove_msec.integer < 8) + trap_Cvar_Set("pmove_msec", "8"); + else if (pmove_msec.integer > 33) + trap_Cvar_Set("pmove_msec", "33"); + // Golliwog: Check the damage type and gravity if( g_friendlyFire.modificationCount != level.friendlyFireCount ) { @@ -1525,7 +1530,7 @@ void LogExit( const char *string ) { continue; } - ping = cl->pers.realPing < 999 ? cl->pers.realPing : 999;//ps.ping + ping = cl->ps.ping < 999 ? cl->ps.ping : 999; G_LogPrintf( "score: %i ping: %i client: %i %s^7\n", cl->ps.persistant[PERS_SCORE], ping, level.sortedClients[i], cl->pers.netname ); } @@ -1980,7 +1985,7 @@ void G_Q3F_CalculateTeamPings(void) if( !scan->inuse || !scan->client ) continue; counts[scan->client->sess.sessionTeam]++; - pings[scan->client->sess.sessionTeam] += scan->client->pers.realPing;// ps.ping; + pings[scan->client->sess.sessionTeam] += scan->client->ps.ping; } for( index = 0; index < Q3F_TEAM_NUM_TEAMS - 1; index++ ) @@ -2226,14 +2231,12 @@ void G_RunEntities(void) { continue; switch (ent->s.eType) { - case ET_MISSILE: - { - if (ent->antilag_time) { - int snap_ms = 1000 / sv_fps.integer; - int prestep = ent->s.pos.trTime - projectile_now; - int steps = (projectile_now - ent->antilag_time) / snap_ms, rs = 0; - //int ot = ent->s.pos.trTime - prestep; - ent->s.pos.trTime = ent->antilag_time + prestep; + case ET_MISSILE: { + if (ent->antilag_time) { + int snap_ms = 1000 / sv_fps.integer; + int prestep = ent->s.pos.trTime - projectile_now; + int steps = (projectile_now - ent->antilag_time) / snap_ms, rs = 0; + ent->s.pos.trTime = ent->antilag_time + prestep; while (steps > 0) { int now = projectile_now - steps * snap_ms; diff --git a/code/game/g_q3f_admin.c b/code/game/g_q3f_admin.c index 8b2b0c4..cf44269 100644 --- a/code/game/g_q3f_admin.c +++ b/code/game/g_q3f_admin.c @@ -411,7 +411,7 @@ static void G_Q3F_AdminStatus( gentity_t *admin ) { G_Q3F_AdminPrint( admin, "%3d %21s %4d %6s %5d %s\n", player->s.number, player->client->pers.ipStr, - player->client->pers.realPing,//ps.ping + player->client->ps.ping, (player->client->sess.sessionTeam ? g_q3f_teamlist[player->client->sess.sessionTeam].name : "spec"), player->client->ps.persistant[PERS_SCORE], player->client->pers.netname ); diff --git a/code/game/g_q3f_controllable.c b/code/game/g_q3f_controllable.c index a95d301..89fe7eb 100644 --- a/code/game/g_q3f_controllable.c +++ b/code/game/g_q3f_controllable.c @@ -122,7 +122,7 @@ qboolean G_Q3F_Control( gentity_t *ent ) pm.pointcontents = trap_PointContents; pm.debugLevel = g_debugMove.integer; pm.noFootsteps = (g_dmflags.integer & DF_NO_FOOTSTEPS) > 0; - pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; + pm.pmove_fixed = pmove_fixed.integer; pm.pmove_msec = pmove_msec.integer; pm.cs = &controllable->s; From 56e6e52cb0b2aba118ce765ebd3eef2a242b8e58 Mon Sep 17 00:00:00 2001 From: newby Date: Wed, 9 Jul 2025 09:55:41 -0700 Subject: [PATCH 8/9] pmove-fixed: add handling for overlapping client frames pmove_fixed would previously incorrectly overlap and not handle certain frames due to treatment of time. Many frames being assigned the same time was resulting in inconsistent processing, caching, etc. pmove also segmented ranges awkwardly, e.g. (pmsec*n, pmsec*(n+1)], change this to [pmsec%n,pmsec*(n+1)) which brings mod range in congruence. Resolve this by making it an internal detail to pmove, allowing external code to behave independently wrt pmove_fixed being enabled. --- code/cgame/cg_predict.c | 10 ++---- code/game/bg_pmove.c | 68 ++++++++++++++++++++++++++++++++--------- code/game/bg_public.h | 2 +- code/game/g_active.c | 28 ++++++----------- 4 files changed, 66 insertions(+), 42 deletions(-) diff --git a/code/cgame/cg_predict.c b/code/cgame/cg_predict.c index 0822db1..c76ddd1 100644 --- a/code/cgame/cg_predict.c +++ b/code/cgame/cg_predict.c @@ -827,10 +827,6 @@ void CG_PredictPlayerState( void ) { // get the command trap_GetUserCmd( cmdNum, &cg_pmove.cmd ); - if ( cg_pmove.pmove_fixed ) { - PM_UpdateViewAngles( cg_pmove.ps, &cg_pmove.cmd ); - } - // don't do anything if the time is before the snapshot player time if ( cg_pmove.cmd.serverTime <= cg.predictedPlayerState.commandTime ) { continue; @@ -915,22 +911,20 @@ void CG_PredictPlayerState( void ) { VectorSet( cg_pmove.groundVelocity, 0, 0, 0 ); } - if ( cg_pmove.pmove_fixed ) { - cg_pmove.cmd.serverTime = ((cg_pmove.cmd.serverTime + cgs.pmove_msec-1) / cgs.pmove_msec) * cgs.pmove_msec; - } - // ydnar: if server respawning, freeze the player if ( cg.serverRespawning ) { cg_pmove.ps->pm_type = PM_FREEZE; } cg_pmove.airleft = (cg.waterundertime - cg.time); + cg_pmove.retflags = 0; if( cg.agentDataEntity && cg.agentDataEntity->currentValid ) { cg_pmove.agentclass = cg.agentDataEntity->currentState.torsoAnim; } else { cg_pmove.agentclass = 0; } + if ( cg_optimizePrediction.integer ) { // if we need to predict this command, or we've run out of space in the saved states queue if ( cmdNum >= predictCmd || (stateIndex + 1) % NUM_SAVED_STATES == cg.stateHead ) { diff --git a/code/game/bg_pmove.c b/code/game/bg_pmove.c index 252a8b5..fa8582c 100644 --- a/code/game/bg_pmove.c +++ b/code/game/bg_pmove.c @@ -2942,6 +2942,30 @@ void PmoveSingle (pmove_t *pmove) { trap_SnapVector( pm->ps->velocity ); } +/* +================ +PmoveFixedPartial + +For overlapping frames with pmove_fixed we must handle still non-movement state +such as buttons and viewangle updates. +================ +*/ +void PmoveFixedPartial (pmove_t *pmove) { + pm = pmove; + memset(&pml, 0, sizeof(pml)); // Paranoia beyond clearing msec + + PM_UpdateViewAngles( pm->ps, &pm->cmd ); + + PM_Animate(); + + PM_Weapon(); + if (pm->ps->weaponstate >= WEAPON_FIRING) + pm->ps->eFlags |= EF_FIRING; + + PM_TorsoAnimation(); + + pm->retflags |= PMRF_PMOVE_PARTIAL; +} /* ================ @@ -2950,38 +2974,52 @@ Pmove Can be called by either the server or the client ================ */ +static inline int RoundUp(int x, int d) { + return ((x/d)*d) + d - 1; +} + void Pmove (pmove_t *pmove) { - int finalTime; + int end, final; - finalTime = pmove->cmd.serverTime; + final = end = pmove->cmd.serverTime; - if ( finalTime < pmove->ps->commandTime ) { + if ( final < pmove->ps->commandTime ) return; // should not happen - } - - if ( finalTime > pmove->ps->commandTime + 1000 ) { - pmove->ps->commandTime = finalTime - 1000; - } + else if ( final > pmove->ps->commandTime + 1000 ) + pmove->ps->commandTime = final - 1000; pmove->ps->pmove_framecount = (pmove->ps->pmove_framecount+1) & ((1<pmove_fixed) { + pmove->ps->commandTime = RoundUp(pmove->ps->commandTime, pmove->pmove_msec); + final = RoundUp(final, pmove->pmove_msec); + + if (pmove->ps->commandTime == final) // Is this a real movement frame? + PmoveFixedPartial(pmove); + } + // chop the move up if it is too long, to prevent framerate // dependent behavior - while ( pmove->ps->commandTime != finalTime ) { + while ( pmove->ps->commandTime != final ) { int msec; - msec = finalTime - pmove->ps->commandTime; + msec = final - pmove->ps->commandTime; if ( pmove->pmove_fixed ) { - if ( msec > pmove->pmove_msec ) { + if ( msec > pmove->pmove_msec ) msec = pmove->pmove_msec; - } } else { - if ( msec > 66 ) { + if ( msec > 66 ) msec = 66; - } } + pmove->cmd.serverTime = pmove->ps->commandTime + msec; PmoveSingle( pmove ); @@ -2990,5 +3028,7 @@ void Pmove (pmove_t *pmove) { } } + pmove->ps->commandTime = end; // Need "true" time //PM_CheckStuck(); } + diff --git a/code/game/bg_public.h b/code/game/bg_public.h index 30de240..69dcb2d 100644 --- a/code/game/bg_public.h +++ b/code/game/bg_public.h @@ -228,6 +228,7 @@ typedef enum { #define PMRF_SKIP_TAUNT 0x00001 // Done a taunt this pmove #define PMRF_DONE_TAUNT 0x00002 // Done a taunt this pmove +#define PMRF_PMOVE_PARTIAL 0x00004 // PMOVE_FIXED frame that didn't update position #define MAXTOUCH 32 typedef struct { @@ -281,7 +282,6 @@ typedef struct { // if a full pmove isn't done on the client, you can just update the angles void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ); void Pmove (pmove_t *pmove); - //=================================================================================== diff --git a/code/game/g_active.c b/code/game/g_active.c index ba1a9bd..0618b1d 100644 --- a/code/game/g_active.c +++ b/code/game/g_active.c @@ -1086,14 +1086,10 @@ once for each server frame, which makes for smooth demo recording. ============== */ void ClientThink_real( gentity_t *ent ) { - gclient_t *client; + gclient_t *client = ent->client; pmove_t pm; int oldEventSequence; - usercmd_t *ucmd; - - client = ent->client; - - ucmd = &ent->client->pers.cmd; + usercmd_t *ucmd = &ent->client->pers.cmd; // don't think if the client is not yet connected (and thus not yet spawned in) if (client->pers.connected != CON_CONNECTED) { @@ -1133,13 +1129,6 @@ void ClientThink_real( gentity_t *ent ) { client->lastUpdateFrame = level.framenum; //unlagged - smooth clients #1 - if ( pmove_fixed.integer ) { - int pms = pmove_msec.integer; - ucmd->serverTime = ((ucmd->serverTime + pms - 1) / pms) * pms; - //if (ucmd->serverTime - client->ps.commandTime <= 0) - // return; - } - // // check for exiting intermission // @@ -1254,11 +1243,13 @@ void ClientThink_real( gentity_t *ent ) { // use the snapped origin for linking so it matches client predicted versions VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); - VectorCopy (pm.mins, ent->r.mins); - VectorCopy (pm.maxs, ent->r.maxs); + if ((pm.retflags & PMRF_PMOVE_PARTIAL) == 0) { + VectorCopy (pm.mins, ent->r.mins); + VectorCopy (pm.maxs, ent->r.maxs); - ent->waterlevel = pm.waterlevel; - ent->watertype = pm.watertype; + ent->waterlevel = pm.waterlevel; + ent->watertype = pm.watertype; + } // execute client events ClientEvents( ent, oldEventSequence ); @@ -1277,9 +1268,8 @@ void ClientThink_real( gentity_t *ent ) { ClientImpacts( ent, &pm ); // save results of triggers and client events - if (ent->client->ps.eventSequence != oldEventSequence) { + if (ent->client->ps.eventSequence != oldEventSequence) ent->eventTime = level.time; - } // swap and latch button actions client->oldbuttons = client->buttons; From 065065eef6027915b0e39922feb8cae51df38849 Mon Sep 17 00:00:00 2001 From: newby Date: Thu, 10 Jul 2025 21:01:54 -0700 Subject: [PATCH 9/9] pmove: interp view pos with pmove_fixed When pmove_fixed is enable we want to generate interpolated subframes between movement frames so that we're updating the viewport at the intended rate (consider for example the case of walking in a straight line without moving mouse). --- code/cgame/cg_cvar.h | 1 + code/cgame/cg_local.h | 1 + code/cgame/cg_predict.c | 29 +++++++++++++++++++++++++---- code/cgame/cg_view.c | 3 +-- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/code/cgame/cg_cvar.h b/code/cgame/cg_cvar.h index c0b9c46..ba5db70 100644 --- a/code/cgame/cg_cvar.h +++ b/code/cgame/cg_cvar.h @@ -166,6 +166,7 @@ CG_CVAR( cg_timescale, "timescale", "1", 0 ) CG_CVAR( r_clear, "r_clear", "0", 0 ) CG_CVAR( cg_packetdelay, "cg_packetdelay", "0", CVAR_USERINFO ) CG_CVAR( cl_packetdelay, "cl_packetdelay", "0", CVAR_CHEAT ) +CG_CVAR( cg_pmovesmooth, "cg_pmovesmooth", "1", CVAR_ARCHIVE ) CG_CVAR( r_debugSort, "r_debugSort", "0", CVAR_CHEAT ) diff --git a/code/cgame/cg_local.h b/code/cgame/cg_local.h index 8c5c27a..3af2787 100644 --- a/code/cgame/cg_local.h +++ b/code/cgame/cg_local.h @@ -763,6 +763,7 @@ typedef struct { vec3_t kick_origin; // temp working variables for player view + vec3_t view_org, last_pmove_fixed; float bobfracsin; int bobcycle; float xyspeed; diff --git a/code/cgame/cg_predict.c b/code/cgame/cg_predict.c index c76ddd1..d65ff03 100644 --- a/code/cgame/cg_predict.c +++ b/code/cgame/cg_predict.c @@ -664,11 +664,11 @@ void CG_PredictPlayerState( void ) { cg.predictedPlayerState = cg.snap->ps; } - // demo playback just copies the moves - if ( cg.demoPlayback || - (cg.snap->ps.pm_flags & PMF_FOLLOW) || - (cg.snap->ps.pm_flags & PMF_CHASE) ) { + // demo playback/spectating just copies the moves + if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) || + (cg.snap->ps.pm_flags & PMF_CHASE) ) { CG_InterpolatePlayerState( qfalse ); + VectorCopy( cg.predictedPlayerState.origin, cg.view_org ); return; } @@ -976,6 +976,8 @@ void CG_PredictPlayerState( void ) { //CG_CheckChangedPredictableEvents(&cg.predictedPlayerState); } + VectorCopy( cg.predictedPlayerState.origin, cg.view_org ); + if ( cg_showmiss.integer > 1 ) { CG_Printf( BOX_PRINT_MODE_CHAT, "[%i : %i] ", cg_pmove.cmd.serverTime, cg.time ); } @@ -1007,4 +1009,23 @@ void CG_PredictPlayerState( void ) { cg.eventSequence = cg.predictedPlayerState.eventSequence; } } + + // Need to interp with pmove_fixed to avoid effective 1/pmove_msec fps. + if (cgs.pmove_fixed) { + if ((cg_pmove.retflags & PMRF_PMOVE_PARTIAL) == 0) // New full frame + VectorCopy(oldPlayerState.origin, cg.last_pmove_fixed); + + // We can be very conservative about when we choose to stitch. + if (cg_pmovesmooth.integer && + cg.predictedPlayerState.commandTime - oldPlayerState.commandTime <= cgs.pmove_msec && + DistanceSquared(cg.predictedPlayerState.origin, cg.last_pmove_fixed) < SQR(16) && + !cg.thisFrameTeleport) { + vec3_t diff; + float dt = 1 + (cg.predictedPlayerState.commandTime % cgs.pmove_msec); + dt /= 1.0 * cgs.pmove_msec; + + VectorSubtract(cg.predictedPlayerState.origin, cg.last_pmove_fixed, diff); + VectorMA(cg.last_pmove_fixed, dt, diff, cg.view_org); + } + } } diff --git a/code/cgame/cg_view.c b/code/cgame/cg_view.c index 2b8a5db..367d3cd 100644 --- a/code/cgame/cg_view.c +++ b/code/cgame/cg_view.c @@ -310,7 +310,6 @@ static void CG_OffsetThirdPersonView( void ) { // this causes a compiler bug on mac MrC compiler static void CG_StepOffset( void ) { int timeDelta; - // smooth out stair climbing timeDelta = cg.time - cg.stepTime; if ( timeDelta < STEP_TIME ) { @@ -998,7 +997,7 @@ static int CG_CalcViewValues( void ) { cg.xyspeed = sqrt( ps->velocity[0] * ps->velocity[0] + ps->velocity[1] * ps->velocity[1] ); - VectorCopy( ps->origin, cg.refdef.vieworg ); + VectorCopy( cg.view_org, cg.refdef.vieworg ); VectorCopy( ps->viewangles, cg.refdefViewAngles ); if (cg_cameraOrbit.integer) {