From 44c482418cdbf701b96666f54bf58d197545e356 Mon Sep 17 00:00:00 2001 From: Foo <2673475+br33zy59@users.noreply.github.com> Date: Thu, 2 Jul 2026 12:15:26 +1200 Subject: [PATCH] Import RATOA unlagged enhancements Calculate PMISSILE_WINDOWTIME based on sv_fps (Modified port of ratoa commit 1a683edaf974e30ed162718c45daaec576a13a4b) - Adjusts the lifetime of a predicted missile so that, instead of simply staying alive for a fixed 30msec, it instead aligns to time into a frame when the missile was launched. Simplify and improve projectile delag (Modified port of ratoa commit 2d6912a9b974231e6a9e9439a6fa6d6d7746d034) - Projectiles that don't match specific delag handlers (most significantly: grenades) now get a 1-frame nudge by default. This should resolve a visual glitch where a new grenade appears to warp backwards momentarily when launched. - Removed prevMsec (not needed) (cg_predict.c) - Bumped client frame history from 17 to 22 (g_local.h) - Implement G_PrintDelagMaxTimeshift() (g_unlagged.c) - Substitute out G_ApplyMissileNudge() and replace with G_SetMissileLaunchTime() Prevent missile visuals getting stuck in owner (Modified port of ratoa commit cd5333e7ed3626fdbaf8f868268d2295410b89fe) - Missiles now skip/pass collision checks with their owner (cg_ents.c) --- ratoa_gamecode/code/cgame/cg_ents.c | 18 ++- ratoa_gamecode/code/cgame/cg_predict.c | 14 --- ratoa_gamecode/code/cgame/cg_unlagged.c | 14 ++- ratoa_gamecode/code/game/g_local.h | 4 +- ratoa_gamecode/code/game/g_main.c | 1 + ratoa_gamecode/code/game/g_missile.c | 145 ++++++++++-------------- ratoa_gamecode/code/game/g_unlagged.c | 14 +++ 7 files changed, 99 insertions(+), 111 deletions(-) diff --git a/ratoa_gamecode/code/cgame/cg_ents.c b/ratoa_gamecode/code/cgame/cg_ents.c index c3bc90f..22ffe5f 100644 --- a/ratoa_gamecode/code/cgame/cg_ents.c +++ b/ratoa_gamecode/code/cgame/cg_ents.c @@ -926,8 +926,7 @@ int CG_ProjectileNudgeTimeshift(centity_t *cent) { // same delayed view for player and missile return 1000 / sv_fps.integer; } - // if it's not, and it's not a grenade launcher - else if ( cent->currentState.weapon != WP_GRENADE_LAUNCHER ) { + else if (cent->currentState.weapon != WP_GRENADE_LAUNCHER) { // extrapolate based on cg_projectileNudge switch (cg_projectileNudgeAuto.integer) { case 1: @@ -939,7 +938,13 @@ int CG_ProjectileNudgeTimeshift(centity_t *cent) { } } - return 0; + // don't nudge GL because we can't locally predict the way it bounces (for now) + // we still shift it by 1 frame, to avoid moving it + // backwards when it bounces (or is fired by a bot). + // This would happen in that + // case because trTime > cg.time as the missiles are + // added from nextSnap (see early transitioning in CG_AddPacketEntities()). + return 1000 / sv_fps.integer; } /* @@ -1026,7 +1031,12 @@ static void CG_CalcEntityLerpPositions( centity_t *cent ) { cent->missileStatus.missileFlags |= MF_DISAPPEARED; } - CG_Trace( &tr, lastOrigin, vec3_origin, vec3_origin, cent->lerpOrigin, cent->currentState.number, MASK_SHOT ); + CG_Trace( &tr, lastOrigin, vec3_origin, vec3_origin, cent->lerpOrigin, + // missiles fly through their owners + // they cannot hit themselves because they are not solid + cent->currentState.eType == ET_MISSILE ? + CG_MissileOwner(cent) : cent->currentState.number, + MASK_SHOT ); // don't let the projectile go through the floor if ( tr.fraction < 1.0f ) { diff --git a/ratoa_gamecode/code/cgame/cg_predict.c b/ratoa_gamecode/code/cgame/cg_predict.c index 08c27fb..7e7c0f3 100644 --- a/ratoa_gamecode/code/cgame/cg_predict.c +++ b/ratoa_gamecode/code/cgame/cg_predict.c @@ -782,7 +782,6 @@ void CG_PredictPlayerState( void ) { //unlagged - optimized prediction int stateIndex = 0, predictCmd = 0; //Sago: added initializing int numPredicted = 0, numPlayedBack = 0; // debug code - int prevMsec; //unlagged - optimized prediction cg.hyperspace = qfalse; // will be set if touching a trigger_teleport @@ -964,7 +963,6 @@ void CG_PredictPlayerState( void ) { // run cmds moved = qfalse; - prevMsec = -1; for ( cmdNum = current - CMD_BACKUP + 1 ; cmdNum <= current ; cmdNum++ ) { // get the command trap_GetUserCmd( cmdNum, &cg_pmove.cmd ); @@ -973,11 +971,6 @@ void CG_PredictPlayerState( void ) { PM_UpdateViewAngles( cg_pmove.ps, &cg_pmove.cmd ); } - // save previous time - if (cmdNum < current) { - prevMsec = cg_pmove.cmd.serverTime; - } - // don't do anything if the time is before the snapshot player time if ( cg_pmove.cmd.serverTime <= cg.predictedPlayerState.commandTime ) { continue; @@ -1042,13 +1035,6 @@ void CG_PredictPlayerState( void ) { } } - // store time delta between commands (used for projectile delag) - if (prevMsec != -1 && latestCmd.serverTime - prevMsec > 0) { - cg.cmdMsecDelta = latestCmd.serverTime - prevMsec; - } else { - cg.cmdMsecDelta = 0; - } - // don't predict gauntlet firing, which is only supposed to happen // when it actually inflicts damage cg_pmove.gauntletHit = qfalse; diff --git a/ratoa_gamecode/code/cgame/cg_unlagged.c b/ratoa_gamecode/code/cgame/cg_unlagged.c index 96f61a3..c435dfb 100644 --- a/ratoa_gamecode/code/cgame/cg_unlagged.c +++ b/ratoa_gamecode/code/cgame/cg_unlagged.c @@ -426,7 +426,10 @@ predictedMissile_t *cg_freePMissiles; // single linked list // how much longer than the player's roundtrip time a predicted missile will // stay alive awaiting confirmation from the server -#define PMISSILE_WINDOWTIME 30 +// if the player's firing command arrives right after the start of the frame, +// the missile will be included in the next snapshot (1000/sv_fps later), so it +// should be at least that long +#define PMISSILE_WINDOWTIME(fps) (1.5*1000/fps) /* =================== @@ -557,8 +560,8 @@ void CG_RemovePredictedMissile( centity_t *missile) { continue; } - if (missile->currentState.pos.trTime - PMISSILE_WINDOWTIME > pm->pos.trTime - || missile->currentState.pos.trTime + PMISSILE_WINDOWTIME < pm->pos.trTime) { + if (missile->currentState.pos.trTime - PMISSILE_WINDOWTIME(sv_fps.integer) > pm->pos.trTime + || missile->currentState.pos.trTime + PMISSILE_WINDOWTIME(sv_fps.integer) < pm->pos.trTime) { continue; } @@ -1279,7 +1282,7 @@ void CG_PredictWeaponEffects( centity_t *cent ) { predictedMissile_t *CG_BasePredictMissile( entityState_t *ent, vec3_t muzzlePoint ) { predictedMissile_t *pm; refEntity_t *bolt; - int lifetime = CG_ReliablePing() + PMISSILE_WINDOWTIME; + int lifetime = CG_ReliablePing() + PMISSILE_WINDOWTIME(sv_fps.integer); pm = CG_AllocPMissile(); pm->removeTime = cg.time + lifetime; @@ -1288,7 +1291,8 @@ predictedMissile_t *CG_BasePredictMissile( entityState_t *ent, vec3_t muzzlePoi bolt = &pm->refEntity; VectorCopy(muzzlePoint, pm->pos.trBase); - pm->pos.trTime = cg.time-cgs.predictedMissileNudge-cg.cmdMsecDelta; + // oldTime is our attackTime + pm->pos.trTime = cg.oldTime-cgs.predictedMissileNudge; if (BG_IsElimGT(cgs.gametype) && cg.warmup == 0 && cgs.roundStartTime diff --git a/ratoa_gamecode/code/game/g_local.h b/ratoa_gamecode/code/game/g_local.h index 21b5b99..e494f38 100644 --- a/ratoa_gamecode/code/game/g_local.h +++ b/ratoa_gamecode/code/game/g_local.h @@ -65,7 +65,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define FL_FORCE_GESTURE 0x00008000 // force gesture on client // for delagged projectiles -//#define MISSILE_PRESTEP_MAX_LATENCY 250 #define DELAG_MAX_BACKTRACK (g_delagMissileMaxLatency.integer + 1000/sv_fps.integer * 2) #define PLASMA_THINKTIME 10000 @@ -544,7 +543,7 @@ typedef struct { //unlagged - backward reconciliation #1 // the size of history we'll keep -#define NUM_CLIENT_HISTORY 17 +#define NUM_CLIENT_HISTORY 22 // everything we need to know to backward reconcile typedef struct { @@ -1183,6 +1182,7 @@ void G_UndoTimeShiftFor( gentity_t *ent ); void G_UnTimeShiftClient( gentity_t *client ); void G_TimeShiftClient( gentity_t *ent, int time, qboolean debug, gentity_t *debugger ); void G_PredictPlayerMove( gentity_t *ent, float frametime ); +void G_PrintDelagMaxTimeshift(void); //unlagged - g_unlagged.c // diff --git a/ratoa_gamecode/code/game/g_main.c b/ratoa_gamecode/code/game/g_main.c index 7dbe3a4..b24435c 100644 --- a/ratoa_gamecode/code/game/g_main.c +++ b/ratoa_gamecode/code/game/g_main.c @@ -1298,6 +1298,7 @@ void G_InitGame( int levelTime, int randomSeed, int restart ) { G_UpdateMultiTrnGames(); #endif CalculateRanks(); + G_PrintDelagMaxTimeshift(); } diff --git a/ratoa_gamecode/code/game/g_missile.c b/ratoa_gamecode/code/game/g_missile.c index a5db65c..fca4b16 100644 --- a/ratoa_gamecode/code/game/g_missile.c +++ b/ratoa_gamecode/code/game/g_missile.c @@ -24,63 +24,65 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define MISSILE_PRESTEP_TIME 50 -int G_DelagLatency(gclient_t *client) { - int ping = 0; - switch (g_delagMissileLatencyMode.integer) { - case 2: - ping = client->ps.ping; - break; - case 3: - ping = client->pers.realPing; - break; - case 1: - default: - ping = level.previousTime + client->frameOffset - client->attackTime; - if (ping < 0) { - ping = 0; - } - break; +void G_SetMissileLaunchTime (gentity_t *self, gentity_t *bolt) { + if (!self->client) { + bolt->s.pos.trTime = level.time; + return; } - if (g_delagMissileLimitVariance.integer && g_delagMissileLimitVarianceMs.integer > 0 && g_truePing.integer) { - int maxping = client->pers.realPing + g_delagMissileLimitVarianceMs.integer; - int minping = client->pers.realPing - g_delagMissileLimitVarianceMs.integer; - qboolean limited = qfalse; - int oldping = ping; - - if (minping < 0) { - minping = 0; - } - if (ping > maxping) { - ping = maxping; - limited = qtrue; - } else if (ping < minping) { - ping = minping; - limited = qtrue; - } - if (limited && g_delagMissileDebug.integer) { - Com_Printf("Limited projectile delag ping (c %i): %i -> %i, realPing: %i\n", client->ps.clientNum, oldping, ping, client->pers.realPing); - } + if (!g_delagMissiles.integer) { + bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; + return; } - return MIN(g_delagMissileMaxLatency.integer, ping); -} -int G_MissileLagTime(gclient_t *client) { - int offset = 0; + bolt->s.pos.trTime = self->client->attackTime - g_delagMissileBaseNudge.integer; - if (!g_delagMissiles.integer) { - return MISSILE_PRESTEP_TIME; + if (bolt->s.pos.trTime < level.time - g_delagMissileMaxLatency.integer) { + bolt->s.pos.trTime = level.time - g_delagMissileMaxLatency.integer; + if (g_delagMissileDebug.integer) { + Com_Printf("Limited projectile delag ping (c %i): launchtime %i -> %i, realPing: %i\n", + self->client->ps.clientNum, + bolt->s.pos.trTime + g_delagMissileMaxLatency.integer, + bolt->s.pos.trTime, + self->client->pers.realPing); + } + } else if (bolt->s.pos.trTime > level.time) { + int orig_time = bolt->s.pos.trTime; + + // perhaps we could allow level.time + client->frameOffset, + // but this is such a rare case and I'd rather not deal + // with missiles from the future. If we're not careful + // we might evaluate the trajectory at level.time and + // then the missile would move backwards relative to + // its launch position. + bolt->s.pos.trTime = level.time; + + if (g_delagMissileDebug.integer) { + Com_Printf("Limited future projectile from (c %i) to level.time (orig launchtime was level.time + %i)\n", + self->client->ps.clientNum, + orig_time - level.time + ); + } } - if (g_delagMissileCorrectFrameOffset.integer) { - offset = level.time - (level.previousTime + client->frameOffset); - if (offset < 0) { - offset = 0; - } - if (offset > 1000/sv_fps.integer) { - offset = 1000/sv_fps.integer; + // need to remember the true launch time for delag in case the missile gets bounced/teleported + bolt->launchTime = bolt->s.pos.trTime; + bolt->needsDelag = qtrue; + + if (G_IsElimGT() && level.time > level.roundStartTime - 1000*g_elimination_activewarmup.integer) { + if (bolt->launchTime < level.roundStartTime) { + int prestep = 0; + if (g_delagMissiles.integer) { + prestep = g_delagMissileBaseNudge.integer; + } else { + prestep = MISSILE_PRESTEP_TIME; + } + if (bolt->launchTime < level.roundStartTime-prestep) { + bolt->s.pos.trTime = level.roundStartTime-prestep; + bolt->launchTime = level.roundStartTime-prestep; + } } } - return offset + G_DelagLatency(client) + g_delagMissileBaseNudge.integer; + } void G_MissileRunDelag(gentity_t *ent, int stepmsec) { @@ -1001,31 +1003,6 @@ void G_RunMissile( gentity_t *ent ) { G_RunThink( ent ); } -void G_ApplyMissileNudge (gentity_t *self, gentity_t *bolt) { - if (!self->client) { - return; - } - bolt->s.pos.trTime -= G_MissileLagTime(self->client); - bolt->needsDelag = qtrue; - bolt->launchTime = bolt->s.pos.trTime; - - if (G_IsElimGT() && level.time > level.roundStartTime - 1000*g_elimination_activewarmup.integer) { - if (bolt->launchTime < level.roundStartTime) { - int prestep = 0; - if (g_delagMissiles.integer) { - prestep = g_delagMissileBaseNudge.integer; - } else { - prestep = MISSILE_PRESTEP_TIME; - } - if (bolt->launchTime < level.roundStartTime-prestep) { - bolt->s.pos.trTime = level.roundStartTime-prestep; - bolt->launchTime = level.roundStartTime-prestep; - } - - } - } -} - //============================================================================= /* @@ -1076,9 +1053,8 @@ gentity_t *fire_plasma (gentity_t *self, vec3_t start, vec3_t dir) { bolt->target_ent = NULL; bolt->s.pos.trType = TR_LINEAR; - bolt->s.pos.trTime = level.time; //bolt->s.pos.trTime = level.time; - G_ApplyMissileNudge(self, bolt); + G_SetMissileLaunchTime(self, bolt); VectorCopy( start, bolt->s.pos.trBase ); VectorScale( dir, PLASMA_VELOCITY, bolt->s.pos.trDelta ); //SnapVector( bolt->s.pos.trDelta ); // save net bandwidth //mrd - nah @@ -1149,9 +1125,8 @@ gentity_t *fire_grenade (gentity_t *self, vec3_t start, vec3_t dir) { bolt->target_ent = NULL; bolt->s.pos.trType = TR_GRAVITY; - bolt->s.pos.trTime = level.time; //bolt->s.pos.trTime = level.time; - G_ApplyMissileNudge(self, bolt); + G_SetMissileLaunchTime(self, bolt); VectorCopy( start, bolt->s.pos.trBase ); VectorScale( dir, GRENADE_VELOCITY, bolt->s.pos.trDelta ); //SnapVector( bolt->s.pos.trDelta ); // save net bandwidth //mrd - nah @@ -1211,9 +1186,8 @@ gentity_t *fire_bfg (gentity_t *self, vec3_t start, vec3_t dir) { bolt->target_ent = NULL; bolt->s.pos.trType = TR_LINEAR; - bolt->s.pos.trTime = level.time; //bolt->s.pos.trTime = level.time; - G_ApplyMissileNudge(self, bolt); + G_SetMissileLaunchTime(self, bolt); VectorCopy( start, bolt->s.pos.trBase ); VectorScale( dir, BFG_VELOCITY, bolt->s.pos.trDelta ); //SnapVector( bolt->s.pos.trDelta ); // save net bandwidth //mrd - nah @@ -1272,9 +1246,8 @@ gentity_t *fire_rocket (gentity_t *self, vec3_t start, vec3_t dir) { bolt->target_ent = NULL; bolt->s.pos.trType = TR_LINEAR; - bolt->s.pos.trTime = level.time; //bolt->s.pos.trTime = level.time; - G_ApplyMissileNudge(self, bolt); + G_SetMissileLaunchTime(self, bolt); VectorCopy( start, bolt->s.pos.trBase ); //VectorScale( dir, 900, bolt->s.pos.trDelta ); //VectorScale( dir, 1000, bolt->s.pos.trDelta ); @@ -1367,8 +1340,8 @@ gentity_t *fire_nail( gentity_t *self, vec3_t start, vec3_t forward, vec3_t righ bolt->target_ent = NULL; bolt->s.pos.trType = TR_LINEAR; - bolt->s.pos.trTime = level.time; - G_ApplyMissileNudge(self, bolt); + //bolt->s.pos.trTime = level.time; + G_SetMissileLaunchTime(self, bolt); VectorCopy( start, bolt->s.pos.trBase ); //r = random() * M_PI * 2.0f; @@ -1432,8 +1405,8 @@ gentity_t *fire_prox( gentity_t *self, vec3_t start, vec3_t dir ) { bolt->s.generic1 = self->client->sess.sessionTeam; bolt->s.pos.trType = TR_GRAVITY; - bolt->s.pos.trTime = level.time; - G_ApplyMissileNudge(self, bolt); + //bolt->s.pos.trTime = level.time; + G_SetMissileLaunchTime(self, bolt); VectorCopy( start, bolt->s.pos.trBase ); VectorScale( dir, PROXMINE_VELOCITY, bolt->s.pos.trDelta ); SnapVector( bolt->s.pos.trDelta ); // save net bandwidth diff --git a/ratoa_gamecode/code/game/g_unlagged.c b/ratoa_gamecode/code/game/g_unlagged.c index 94f3e8f..e3b8328 100644 --- a/ratoa_gamecode/code/game/g_unlagged.c +++ b/ratoa_gamecode/code/game/g_unlagged.c @@ -372,6 +372,20 @@ void G_UndoTimeShiftFor( gentity_t *ent ) { G_UnTimeShiftAllClients( ent ); } +void G_PrintDelagMaxTimeshift(void) { + int max_shift; + if (!(g_delagHitscan.integer || g_delagMissiles.integer)) { + return; + } + max_shift = NUM_CLIENT_HISTORY * 1000/sv_fps.integer; + Com_Printf("Delag: max timeshift is %ims\n", max_shift); + if (max_shift < DELAG_MAX_BACKTRACK) { + Com_Printf(S_COLOR_YELLOW "WARNING: max timeshift %i is not large enough for g_delagMissileMaxLatency %i at sv_fps %i\n", + max_shift, + g_delagMissileMaxLatency.integer, + sv_fps.integer); + } +} /* ===========================