Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion code/cgame/cg_cvar.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand Down
64 changes: 58 additions & 6 deletions code/cgame/cg_ents.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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( &cent->currentState.pos, cg.time, cent->lerpOrigin );
if (cent->interpolate && cent->currentState.apos.trType == TR_INTERPOLATE )
CG_InterpolateEntityAngle( cent );
else
BG_EvaluateTrajectory( &cent->currentState.apos, cg.time, cent->lerpAngles );
BG_EvaluateTrajectory( &cent->currentState.pos, cg.time + timeshift, cent->lerpOrigin );
BG_EvaluateTrajectory( &cent->currentState.apos, cg.time + timeshift, cent->lerpAngles );
// if there's a time shift
if ( timeshift != 0 ) {
trace_t tr;
vec3_t lastOrigin;

BG_EvaluateTrajectory( &cent->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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions code/cgame/cg_local.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
63 changes: 32 additions & 31 deletions code/cgame/cg_localents.c
Original file line number Diff line number Diff line change
Expand Up @@ -967,60 +967,60 @@ 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() ||
cent->currentState.clientNum != cg.snap->ps.clientNum)
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;
}
}

Expand Down Expand Up @@ -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;
Expand All @@ -1080,15 +1080,16 @@ static void CG_AddPredictedEnt( localEntity_t *le ) {
memset(&cent, 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(&cent);

if (le->pos.trType == TR_LINEAR) {
CG_Missile(&cent);
}

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

Expand Down
2 changes: 2 additions & 0 deletions code/game/bg_public.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions code/game/g_active.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions code/game/g_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 );

Expand Down
4 changes: 3 additions & 1 deletion code/game/g_local.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
54 changes: 51 additions & 3 deletions code/game/g_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 ) {
Expand Down Expand Up @@ -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 ; i<level.num_entities ; i++, ent++) {
if ( !ent->inuse || 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
}

/*
Expand Down
Loading