diff --git a/code/cgame/cg_cvar.h b/code/cgame/cg_cvar.h index 49a7d16..842dc13 100644 --- a/code/cgame/cg_cvar.h +++ b/code/cgame/cg_cvar.h @@ -224,7 +224,7 @@ CG_CVAR( cg_drawBBox, "cg_drawBBox", "0", CVAR_CHEAT ) CG_CVAR( cg_cmdTimeNudge, "cg_cmdTimeNudge", "0", CVAR_ARCHIVE | CVAR_USERINFO ) 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 ) +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 ) diff --git a/code/cgame/cg_ents.c b/code/cgame/cg_ents.c index dadc7fc..13fdec9 100644 --- a/code/cgame/cg_ents.c +++ b/code/cgame/cg_ents.c @@ -1593,6 +1593,8 @@ CG_CalcEntityLerpPositions =============== */ static void CG_CalcEntityLerpPositions( centity_t *cent ) { + int timeshift = 0; // how far forward projectiles will be extrapolated + if ( cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE ) { CG_InterpolateEntityPosition( cent ); return; @@ -1605,6 +1607,7 @@ static void CG_CalcEntityLerpPositions( centity_t *cent ) { return; } +//unlagged - timenudge extrapolation // interpolating failed (probably no nextSnap), so extrapolate // this can also happen if the teleport bit is flipped, but that // won't be noticeable @@ -1614,13 +1617,44 @@ static void CG_CalcEntityLerpPositions( centity_t *cent ) { cent->currentState.pos.trTime = cg.snap->serverTime; cent->currentState.pos.trDuration = 1000 / cgs.sv_fps; } +//unlagged - timenudge extrapolation + +//unlagged - projectile nudge + if ( cent->currentState.eType == ET_MISSILE ) { + // if it's one of ours + if ( cent->currentState.otherEntityNum == cg.clientNum ) { + // extrapolate one server frame's worth - this will correct for tiny + // visual inconsistencies introduced by backward-reconciling all players + // one server frame before running projectiles + timeshift = 1000 / cgs.sv_fps; + } + // if it's not, and it's not a grenade launcher + else if ( cent->currentState.weapon != WP_GRENADE_LAUNCHER ) { + // extrapolate based on cg_projectileNudge + timeshift = cg_projectileNudge.integer + 1000 / cgs.sv_fps; + } + } // just use the current frame and evaluate as best we can - BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin ); - if (cent->interpolate && cent->currentState.apos.trType == TR_INTERPOLATE ) - CG_InterpolateEntityAngle( cent ); - else - BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); + BG_EvaluateTrajectory( ¢->currentState.pos, cg.time + timeshift, cent->lerpOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, cg.time + timeshift, cent->lerpAngles ); + // if there's a time shift + if ( timeshift != 0 ) { + trace_t tr; + vec3_t lastOrigin; + + BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, lastOrigin ); + + CG_Trace( &tr, lastOrigin, vec3_origin, vec3_origin, cent->lerpOrigin, cent->currentState.number, MASK_SHOT ); + + // don't let the projectile go through the floor + if ( tr.fraction < 1.0f ) { + cent->lerpOrigin[0] = lastOrigin[0] + tr.fraction * ( cent->lerpOrigin[0] - lastOrigin[0] ); + cent->lerpOrigin[1] = lastOrigin[1] + tr.fraction * ( cent->lerpOrigin[1] - lastOrigin[1] ); + cent->lerpOrigin[2] = lastOrigin[2] + tr.fraction * ( cent->lerpOrigin[2] - lastOrigin[2] ); + } + } +//unlagged - projectile nudge // adjust for riding a mover if it wasn't rolled into the predicted // player state @@ -2116,10 +2150,28 @@ void CG_AddPacketEntities( void ) { CG_UpdateLocalPredictedEnts(); + //unlagged - early transitioning + if ( cg.nextSnap ) { + // pre-add some of the entities sent over by the server + // we have data for them and they don't need to interpolate + for ( num = 0 ; num < cg.nextSnap->numEntities ; num++ ) { + cent = &cg_entities[ cg.nextSnap->entities[ num ].number ]; + if (cent->nextState.eType == ET_MISSILE) { + // transition it immediately and add it + CG_TransitionEntity( cent ); + cent->interpolate = qtrue; + cent->currentState.eFlags |= EF_NEXT_SNAP; + CG_AddCEntity( cent ); + } + } + } + //unlagged - early transitioning + // add each entity sent over by the server for ( num = 0 ; num < cg.snap->numEntities ; num++ ) { cent = &cg_entities[ cg.snap->entities[ num ].number ]; - CG_AddCEntity( cent ); + if ((cent->currentState.eFlags & EF_NEXT_SNAP) == 0) + CG_AddCEntity( cent ); } //djbob: reset sentry cam fx check diff --git a/code/cgame/cg_local.h b/code/cgame/cg_local.h index eb8ec4a..7458610 100644 --- a/code/cgame/cg_local.h +++ b/code/cgame/cg_local.h @@ -392,6 +392,7 @@ typedef struct localEntity_s { trajectory_t pos; trajectory_t angles; + trajectory_t lerp; float bounceFactor; // 0.0 = no bounce, 1.0 = perfect diff --git a/code/cgame/cg_localents.c b/code/cgame/cg_localents.c index 5f51ec0..55a266f 100644 --- a/code/cgame/cg_localents.c +++ b/code/cgame/cg_localents.c @@ -967,7 +967,8 @@ static void CG_UntrackPredictedEnt(localEntity_t *le) { void CG_MatchPredictedEnt(centity_t* cent) { localEntity_t* match = NULL; - float best = SQR(64), sgn; + float best = SQR(64); + vec3_t diff; // Can only match entities we own. if (!CG_PredictedMissilesEnabled() || @@ -975,52 +976,51 @@ void CG_MatchPredictedEnt(centity_t* cent) { return; for (int i = 0; i < num_predicted_ents; i++) { - vec3_t diff; localEntity_t* le = predicted_ents[i]; float len; - VectorSubtract(cent->lerpOrigin, le->pos.trBase, diff); - len = VectorLengthSquared(diff); + if (cent->currentState.eType == ET_MISSILE) { + VectorSubtract(cent->lerpOrigin, le->lerp.trBase, diff); + len = VectorLengthSquared(diff); + } + if (len < best) { match = le; best = len; - sgn = DotProduct(diff, le->pos.trDelta) > 0 ? 1 : -1; } } - if (match) { - float dist = sgn * sqrt(best); - if (match->angles.trTime < 0) - match->angles.trTime += cg.time; - - if (cg_predictWeapons.integer & 4) - Com_Printf("BEST MATCH: %0.2f [%0.1fms] [age=%dms] [%d]\n", - dist, 1000 * dist / VectorLength(match->pos.trDelta), - match->angles.trTime, num_predicted_ents); + if (match && (cg_predictWeapons.integer & 4)) { + float dist, sgn; + VectorSubtract(cent->lerpOrigin, match->lerp.trBase, diff); + sgn = DotProduct(diff, match->pos.trDelta) > 0 ? 1 : -1; + dist = sgn * VectorLength(diff); - match->endTime = 0; // Latch expiry. + Com_Printf("BEST MATCH [%d]: %0.2f [%0.1fms] [age=%dms] [%d]\n", + cent->currentState.number, dist, + 1000 * dist / VectorLength(match->pos.trDelta), + cg.time - match->startTime, num_predicted_ents); } + if (match) + match->endTime = 0; // Latch expiry. } void CG_UpdateLocalPredictedEnts(void) { - float now = cg.time; + float now = cg.time + 1000 / cgs.sv_fps; for (int i = 0; i < num_predicted_ents; i++) { localEntity_t* le = predicted_ents[i]; vec3_t next; trace_t trace; - if (le->pos.trTime >= now) - continue; - BG_EvaluateTrajectory(&le->pos, now, next); - CG_Trace(&trace, le->pos.trBase, NULL, NULL, next, -1, CONTENTS_SOLID); + CG_Trace(&trace, le->lerp.trBase, NULL, NULL, next, -1, CONTENTS_SOLID); if (trace.fraction < 1) le->endTime = 0; // Latch for free - VectorCopy(trace.endpos, le->pos.trBase); - le->pos.trTime = now; + VectorCopy(trace.endpos, le->lerp.trBase); + le->lerp.trTime = now; } } @@ -1059,10 +1059,10 @@ void CG_AddPredictedMissile(entityState_t* ent, vec3_t origin, vec3_t forward) { le->pred_weapon = ent->weapon; le->pos.trType = TR_LINEAR; - le->pos.trTime = cg.time - 23 - tmod; - le->angles.trTime = -cg.time; + le->pos.trTime = cg.time - tmod + cl_timeNudge.integer; - VectorCopy(origin, le->pos.trBase ); + 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; @@ -1080,15 +1080,16 @@ static void CG_AddPredictedEnt( localEntity_t *le ) { memset(¢, 0, sizeof(cent)); cent.currentState.weapon = le->pred_weapon; - VectorCopy(le->pos.trBase, cent.lerpOrigin); + VectorCopy(le->lerp.trBase, cent.lerpOrigin); VectorCopy(le->pos.trDelta, cent.currentState.pos.trDelta); // Needed for axis - CG_Missile(¢); + + if (le->pos.trType == TR_LINEAR) { + CG_Missile(¢); + } if (cg_predictWeapons.integer & 4) { - vec3_t mins, maxs; - VectorSet(mins, -5, -5, -5); - VectorScale(mins, -1, maxs); - CG_DrawBoundingBox( le->pos.trBase, mins, maxs, colorYellow ); + vec3_t mins = {-5, -5, -5}, maxs = {5, 5, 5}; + CG_DrawBoundingBox( le->lerp.trBase, mins, maxs, colorYellow ); } } diff --git a/code/game/bg_public.h b/code/game/bg_public.h index 0fc0c28..82c8e1b 100644 --- a/code/game/bg_public.h +++ b/code/game/bg_public.h @@ -372,6 +372,8 @@ typedef enum { #define EF_TEAMVOTED 0x00200000 // already cast a team vote #define EF_AWARD_CAP 0x00800000 // draw the capture sprite +#define EF_NEXT_SNAP 0x01000000 // Preloaded from next snapshot + #define EF_Q3F_FAILDIRECTION EF_Q3F_DISGUISE // Forcefield direction applies to players failing the criteria. #define EF_Q3F_REVERSECRITERIA EF_Q3F_SAVEME // Forcefield has criteriareversed diff --git a/code/game/g_active.c b/code/game/g_active.c index 2fead39..18f012e 100644 --- a/code/game/g_active.c +++ b/code/game/g_active.c @@ -933,6 +933,8 @@ void ClientThink_real( gentity_t *ent ) { // and *after* lag simulation messes with it :) // 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; //unlagged - backward reconciliation #4 //unlagged - smooth clients #1 diff --git a/code/game/g_client.c b/code/game/g_client.c index 766bbf8..27ea5d8 100644 --- a/code/game/g_client.c +++ b/code/game/g_client.c @@ -1047,6 +1047,8 @@ qboolean ClientUserinfoChanged( int clientNum, const char *reason ) { } // see if the player is nudging his shots + s = Info_ValueForKey( userinfo, "cl_timeNudge" ); + client->pers.timeNudge = atoi( s ); s = Info_ValueForKey( userinfo, "cg_cmdTimeNudge" ); client->pers.cmdTimeNudge = atoi( s ); diff --git a/code/game/g_local.h b/code/game/g_local.h index 80add41..6a12782 100644 --- a/code/game/g_local.h +++ b/code/game/g_local.h @@ -367,6 +367,7 @@ typedef struct { //unlagged - these correspond with variables in the userinfo string int unlagged; int debugDelag; + int timeNudge; int cmdTimeNudge; //unlagged - client options int latentSnaps; @@ -530,6 +531,7 @@ struct gclient_s { // the serverTime the button was pressed // (stored before pmove_fixed changes serverTime) int attackTime; + int attackTimeProj; // the head of the history queue int historyHead; // the history queue @@ -858,7 +860,7 @@ void G_Q3F_RegisterTeamKill( gentity_t *attacker, gentity_t *obituary ); // // g_missile.c // -void G_RunMissile( gentity_t *ent ); +void G_RunMissile( gentity_t *ent, int now ); gentity_t *fire_flame (gentity_t *self, vec3_t start, vec3_t aimdir); gentity_t *fire_mapflame (gentity_t *self, vec3_t start, vec3_t aimdir); diff --git a/code/game/g_main.c b/code/game/g_main.c index ac0e29b..f1268bb 100644 --- a/code/game/g_main.c +++ b/code/game/g_main.c @@ -2095,6 +2095,7 @@ void G_RunEntities(void) { gentity_t* ent; int i,e; trace_t trace; + int projectile_now; // // go through all allocated objects @@ -2143,10 +2144,12 @@ void G_RunEntities(void) { continue; } - if ( ent->s.eType == ET_MISSILE ) { - G_RunMissile( ent ); + +//unlagged - backward reconciliation #2 + // we'll run missiles separately to save CPU in backward reconciliation + if ( ent->s.eType == ET_MISSILE ) continue; - } +//unlagged - backward reconciliation #2 // Golliwog: Custom types if ( ent->s.eType == ET_Q3F_GRENADE ) { @@ -2212,6 +2215,51 @@ void G_RunEntities(void) { G_RunThink( ent ); } +//unlagged - backward reconciliation #3 + // NOW run the missiles, with all players backward-reconciled + // to the positions they were at the end of the last server frame + projectile_now = level.previousTime; + G_TimeShiftAllClients( projectile_now, NULL ); + + ent = &g_entities[0]; + for (i=0 ; iinuse || ent->freeAfterEvent) + 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; + + while (steps > 0) { + int now = projectile_now - steps * snap_ms; + G_TimeShiftAllClients(now, ent->parent); + G_RunMissile(ent, now); + + if (!ent->inuse) + break; + + rs = 1; + steps--; + } + ent->antilag_time = 0; + if (rs) + G_TimeShiftAllClients( projectile_now, NULL ); + } + + if (ent->inuse) + G_RunMissile( ent, level.time ); + break; + } + } + } + + G_UnTimeShiftAllClients( NULL ); +//unlagged - backward reconciliation #2 } /* diff --git a/code/game/g_missile.c b/code/game/g_missile.c index bae5aa9..a70a88f 100644 --- a/code/game/g_missile.c +++ b/code/game/g_missile.c @@ -265,7 +265,7 @@ G_RunMissile ================ */ -void G_RunMissileTime( gentity_t *ent, int time ) { +void G_RunMissile( gentity_t *ent, int time ) { // vec3_t origin; // trace_t tr; // trace_t tr2; @@ -474,33 +474,6 @@ void G_RunMissileTime( gentity_t *ent, int time ) { G_RunThink( ent ); } -void G_RunMissile( gentity_t *ent ) { - if (ent->antilag_time) { - const int kStep = 10; - int time = Q_max(level.time - g_antilag_ms.integer, ent->antilag_time); - int end = level.time - kStep; - qboolean ran = qfalse; - - if (end > time) { - ent->s.pos.trTime -= (end - time); - - while (end > time && ent->s.eType == ET_MISSILE) { - time += Q_min(end - time, kStep); - ran = qtrue; - - G_TimeShiftAllClients(time, ent->parent); - G_RunMissileTime(ent, time); - } - if (ran) - G_UnTimeShiftAllClients(ent->parent); - } - - ent->antilag_time = 0; - } - - G_RunMissileTime(ent, level.time); -} - //============================================================================= /* @@ -770,7 +743,7 @@ gentity_t *fire_rocket (gentity_t *self, vec3_t start, vec3_t dir) { // need to check for client being valid, as this can be a shooter_rocket // there is also shooter_grenade if fire_grenade gets this too eventually if (self->client && self->client->pers.unlagged) - bolt->antilag_time = self->client->attackTime; + bolt->antilag_time = self->client->attackTimeProj; return bolt; } @@ -824,7 +797,7 @@ gentity_t *fire_nail (gentity_t *self, vec3_t start, vec3_t dir, int damage, int VectorCopy (start, bolt->r.currentOrigin); if (self->client && self->client->pers.unlagged) - bolt->antilag_time = self->client->attackTime; + bolt->antilag_time = self->client->attackTimeProj; return bolt; }