Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Antilag refactor #3

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion src/game/g_active.c
Original file line number Diff line number Diff line change
Expand Up @@ -1771,7 +1771,7 @@ void ClientThink_real( gentity_t *ent ) {
VectorCopy( ent->client->ps.origin, ent->r.currentOrigin );

// L0 - antilag
G_StoreTrail(ent);
G_StoreTrailNode(ent);

// touch other objects
ClientImpacts( ent, &pm );
Expand Down
256 changes: 132 additions & 124 deletions src/game/g_antilag.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,184 +10,189 @@ Created: 24. Mar / 2014
*/
#include "g_local.h"

// OSP
#define IS_ACTIVE( x ) ( \
x->r.linked == qtrue && \
x->client->ps.stats[STAT_HEALTH] > 0 && \
x->client->sess.sessionTeam != TEAM_SPECTATOR && \
(x->client->ps.pm_flags & PMF_LIMBO) == 0 \
)
/*
============
IsActiveClient

Is the entity a client that's currently playing in the world (active)?
============
*/
qboolean IsActiveClient(gentity_t* ent) {
return
ent->r.linked == qtrue &&
ent->client &&
ent->client->ps.stats[STAT_HEALTH] > 0 &&
ent->client->sess.sessionTeam != TEAM_SPECTATOR &&
(ent->client->ps.pm_flags & PMF_LIMBO) == 0;
}

/*
============
G_ResetTrail

Clear out the given client's origin trails (should be called from ClientBegin and when
the teleport bit is toggled)
Resets the given client's trail (should be called from ClientBegin and when the teleport bit is toggled)
Each trail node is populated using the client's current state within the server.
============
*/
void G_ResetTrail(gentity_t *ent) {
int i, time;
int i, time;

// we want to store half a second worth of trails (500ms)
const int trail_time_range_ms = 500;
const int time_interval = round((double)trail_time_range_ms / (double)NUM_CLIENT_TRAIL_NODES);

// fill up the origin trails with data (assume the current position
// for the last 1/2 second or so)
ent->client->trailHead = NUM_CLIENT_TRAILS - 1;
for (i = ent->client->trailHead, time = level.time; i >= 0; i--, time -= 50) {
ent->client->trail_head = NUM_CLIENT_TRAIL_NODES - 1;
for (i = ent->client->trail_head, time = level.time; i >= 0; i--, time -= time_interval) {
VectorCopy(ent->r.mins, ent->client->trail[i].mins);
VectorCopy(ent->r.maxs, ent->client->trail[i].maxs);
VectorCopy(ent->r.currentOrigin, ent->client->trail[i].currentOrigin);
ent->client->trail[i].leveltime = time;
ent->client->trail[i].time = time;
ent->client->trail[i].animInfo = ent->client->animationInfo;
ent->client->trail[i].animationInfo = ent->client->animationInfo;
}
}


/*
============
G_StoreTrail
G_StoreTrailNode

Keep track of where the client's been (usually called every ClientThink)
Store the client's current positional information (usually called every ClientThink)
============
*/
void G_StoreTrail(gentity_t *ent) {
int head, newtime;
void G_StoreTrailNode(gentity_t *ent) {
int newtime;

if (!IS_ACTIVE(ent))
// only store trail nodes for actively playing clients.
// also, don't store if the level time hasn't been set yet (it'll happen next SV_Frame).
if (!IsActiveClient(ent) || !level.time || !level.previousTime) {
return;
}

head = ent->client->trailHead;
// 6ms is the minimum frame time for 125fps clients (average is 8ms).
#define MINIMUM_TIME_BETWEEN_NODES 6

// if we're on a new frame
if (ent->client->trail[head].leveltime < level.time) {
// snap the last head up to the end of frame time
ent->client->trail[head].time = level.previousTime;
// limit how often higher fps clients store trail nodes.
// otherwise we lose too much time-sensitive data that's required for higher ping players.
int time_since_last_store = ent->client->pers.cmd.serverTime - ent->client->last_trail_node_store_time;
ent->client->accum_trail_node_store_time += time_since_last_store;
ent->client->last_trail_node_store_time = ent->client->pers.cmd.serverTime;
if (ent->client->accum_trail_node_store_time < MINIMUM_TIME_BETWEEN_NODES) {
return;
}
ent->client->accum_trail_node_store_time = 0;

// increment the head
ent->client->trailHead++;
if (ent->client->trailHead >= NUM_CLIENT_TRAILS) {
ent->client->trailHead = 0;
}
head = ent->client->trailHead;
// increment the head
ent->client->trail_head++;
if (ent->client->trail_head >= NUM_CLIENT_TRAIL_NODES) {
ent->client->trail_head = 0;
}

if (ent->r.svFlags & SVF_BOT) {
// bots move only once per frame
// bots move only once per server frame (every 1000/sv_fps ms)
newtime = level.time;
}
else {
// calculate the actual server time
// (we set level.frameStartTime every G_RunFrame)
newtime = level.previousTime + trap_Milliseconds() - level.frameStartTime;
if (newtime > level.time) {
newtime = level.time;
}
else if (newtime <= level.previousTime) {
newtime = level.previousTime + 1;
}
// level.frameStartTime is set to trap_Milliseconds() within G_RunFrame.
//
// we want to store where the server thinks the client is after receiving and processing
// one of their usercmd packets (move command). trap_Milliseconds() is used for a more granular timestamp,
// since level.time is only ever incremented every 50-ish milliseconds (depends on sv_fps).
//
// if level.time were used then clients with high fps, high maxpackets, and high rate would have
// many trail nodes with duplicate timestamps.
int realTimeSinceFrameStartTime = trap_Milliseconds() - level.frameStartTime;
newtime = level.previousTime + realTimeSinceFrameStartTime;
}

// store all the collision-detection info and the time
VectorCopy(ent->r.mins, ent->client->trail[head].mins);
VectorCopy(ent->r.maxs, ent->client->trail[head].maxs);
VectorCopy(ent->r.currentOrigin, ent->client->trail[head].currentOrigin);
ent->client->trail[head].leveltime = level.time;
ent->client->trail[head].time = newtime;
ent->client->trail[head].animInfo = ent->client->animationInfo;
clientTrailNode_t* trail_node = &ent->client->trail[ent->client->trail_head];
VectorCopy(ent->r.mins, trail_node->mins);
VectorCopy(ent->r.maxs, trail_node->maxs);
VectorCopy(ent->r.currentOrigin, trail_node->currentOrigin);
trail_node->time = newtime;
trail_node->animationInfo = ent->client->animationInfo;
}

/*
=============
TimeShiftLerp
=================
Interpolate

Used below to interpolate between two previous vectors
Returns a vector "frac" times the distance between "start" and "end"
=============
Interpolates along two vectors (start -> end).
=================
*/
static void TimeShiftLerp(float frac, vec3_t start, vec3_t end, vec3_t result) {
float comp = 1.0f - frac;
void Interpolate(float frac, vec3_t start, vec3_t end, vec3_t out) {
float comp = 1.0f - frac;

result[0] = frac * start[0] + comp * end[0];
result[1] = frac * start[1] + comp * end[1];
result[2] = frac * start[2] + comp * end[2];
out[0] = start[0] * frac + end[0] * comp;
out[1] = start[1] * frac + end[1] * comp;
out[2] = start[2] * frac + end[2] * comp;
}


/*
=================
G_TimeShiftClient

Move a client back to where he was at the specified "time"
Shifts a client back to where he was at the specified "time"
=================
*/
void G_TimeShiftClient(gentity_t *ent, int time) {
int j, k;
int j, k;
qboolean found_trail_nodes_that_sandwich_time;

if (time > level.time) {
time = level.time;
// this prevents looping through every trail node if we know none of them are <= time.
// the trail nodes are "sorted" by time, so if the oldest one isn't <= time, then none of them can be.
if (ent->client->trail[(ent->client->trail_head + 1) & (NUM_CLIENT_TRAIL_NODES - 1)].time > time) {
return;
}

// find two entries in the origin trail whose times sandwich "time"
// assumes no two adjacent trail records have the same timestamp
j = k = ent->client->trailHead;
// find two trail nodes in the trail whose times sandwich "time".
// this will check every trail node, even if the head starts at index 0.. it'll wrap around to NUM_CLIENT_TRAIL_NODES - 1 and decrease from there.
found_trail_nodes_that_sandwich_time = qfalse;
j = k = ent->client->trail_head;
do {
if (ent->client->trail[j].time <= time)
if (ent->client->trail[j].time <= time) {
found_trail_nodes_that_sandwich_time = j != k;
break;
}

k = j;
j--;
if (j < 0) {
j = NUM_CLIENT_TRAILS - 1;
}
} while (j != ent->client->trailHead);

// if we got past the first iteration above, we've sandwiched (or wrapped)
if (j != k) {
// make sure it doesn't get re-saved
if (ent->client->saved.leveltime != level.time) {
// save the current origin and bounding box
VectorCopy(ent->r.mins, ent->client->saved.mins);
VectorCopy(ent->r.maxs, ent->client->saved.maxs);
VectorCopy(ent->r.currentOrigin, ent->client->saved.currentOrigin);
ent->client->saved.leveltime = level.time;
ent->client->saved.animInfo = ent->client->animationInfo;
j = NUM_CLIENT_TRAIL_NODES - 1;
}
} while (j != ent->client->trail_head);

memset(&ent->client->saved_trail_node, 0, sizeof(clientTrailNode_t));

// we've found two trail nodes with times that "sandwich" the passed in "time"
if (found_trail_nodes_that_sandwich_time) {
// save the current origin, bounding box and animation info; used to untimeshift the client once collision detection is complete
VectorCopy(ent->r.mins, ent->client->saved_trail_node.mins);
VectorCopy(ent->r.maxs, ent->client->saved_trail_node.maxs);
VectorCopy(ent->r.currentOrigin, ent->client->saved_trail_node.currentOrigin);
ent->client->saved_trail_node.animationInfo = ent->client->animationInfo;

// calculate a fraction that will be used to shift the client's position back to the trail node that's nearest to "time"
float frac = (float)(time - ent->client->trail[j].time) / (float)(ent->client->trail[k].time - ent->client->trail[j].time);

// find the "best" origin between the sandwiching trail nodes via interpolation
Interpolate(frac, ent->client->trail[j].currentOrigin, ent->client->trail[k].currentOrigin, ent->r.currentOrigin);
// find the "best" mins & maxs (crouching/standing).
// it doesn't make sense to interpolate mins and maxs. the server either thinks the client
// is crouching or not, and updates the mins & maxs immediately. there's no inbetween.
int nearest_trail_node_index = frac < 0.5 ? j : k;
VectorCopy(ent->client->trail[nearest_trail_node_index].mins, ent->r.mins);
VectorCopy(ent->client->trail[nearest_trail_node_index].maxs, ent->r.maxs);
// use the trail node's animation info that's nearest "time" (for head hitbox).
// the current server animation code used for head hitboxes doesn't support interpolating
// between two different animation frames (i.e. crouch -> standing animation), so can't interpolate here either.
ent->client->animationInfo = ent->client->trail[nearest_trail_node_index].animationInfo;

// if we haven't wrapped back to the head, we've sandwiched, so
// we shift the client's position back to where he was at "time"
if (j != ent->client->trailHead) {
float frac = (float)(ent->client->trail[k].time - time) /
(float)(ent->client->trail[k].time - ent->client->trail[j].time);

// interpolate between the two origins to give position at time index "time"
TimeShiftLerp(frac,
ent->client->trail[k].currentOrigin, ent->client->trail[j].currentOrigin,
ent->r.currentOrigin);

// lerp these too, just for fun (and ducking)
TimeShiftLerp(frac,
ent->client->trail[k].mins, ent->client->trail[j].mins,
ent->r.mins);

TimeShiftLerp(frac,
ent->client->trail[k].maxs, ent->client->trail[j].maxs,
ent->r.maxs);

ent->client->animationInfo = ent->client->trail[frac <= 0.5f ? k : j].animInfo;

// this will recalculate absmin and absmax
trap_LinkEntity(ent);
}
else {
// we wrapped, so grab the earliest
VectorCopy(ent->client->trail[k].currentOrigin, ent->r.currentOrigin);
VectorCopy(ent->client->trail[k].mins, ent->r.mins);
VectorCopy(ent->client->trail[k].maxs, ent->r.maxs);
ent->client->animationInfo = ent->client->trail[k].animInfo;

// this will recalculate absmin and absmax
trap_LinkEntity(ent);
}
// this will recalculate absmin and absmax
trap_LinkEntity(ent);
}
}

Expand All @@ -204,11 +209,13 @@ void G_TimeShiftAllClients(int time, gentity_t *skip) {
int i;
gentity_t *ent;

if (time > level.time) {
time = level.time;
// don't shift clients if "skip" (the client that's trying to timeshift everyone) is more than 500ms behind the current server time (very laggy).
if (level.time - time > 500) {
return;
}

// for every client
// shift every client thats:
// not a spectator, not the "timeshifter" (skip), and not in limbo.
ent = &g_entities[0];
for (i = 0; i < MAX_CLIENTS; i++, ent++) {
if (ent->client && ent->inuse && ent->client->sess.sessionTeam < TEAM_SPECTATOR && ent != skip) {
Expand All @@ -228,14 +235,13 @@ Move a client back to where he was before the time shift
===================
*/
void G_UnTimeShiftClient(gentity_t *ent) {
// if it was saved
if (ent->client->saved.leveltime == level.time) {
// move it back
VectorCopy(ent->client->saved.mins, ent->r.mins);
VectorCopy(ent->client->saved.maxs, ent->r.maxs);
VectorCopy(ent->client->saved.currentOrigin, ent->r.currentOrigin);
ent->client->saved.leveltime = 0;
ent->client->animationInfo = ent->client->saved.animInfo;
// if ent was time shifted
if (ent->client->saved_trail_node.mins[0]) {
// revert the time shift
VectorCopy(ent->client->saved_trail_node.mins, ent->r.mins);
VectorCopy(ent->client->saved_trail_node.maxs, ent->r.maxs);
VectorCopy(ent->client->saved_trail_node.currentOrigin, ent->r.currentOrigin);
ent->client->animationInfo = ent->client->saved_trail_node.animationInfo;

// this will recalculate absmin and absmax
trap_LinkEntity(ent);
Expand All @@ -254,6 +260,8 @@ void G_UnTimeShiftAllClients(gentity_t *skip) {
int i;
gentity_t *ent;

// unshift every client thats:
// not a spectator, not the "timeshifter" (skip), and not in limbo.
ent = &g_entities[0];
for (i = 0; i < MAX_CLIENTS; i++, ent++) {
if (ent->client && ent->inuse && ent->client->sess.sessionTeam < TEAM_SPECTATOR && ent != skip) {
Expand Down
2 changes: 0 additions & 2 deletions src/game/g_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,6 @@ void respawn( gentity_t *ent ) {

// L0 - antilag
G_ResetTrail(ent);
ent->client->saved.leveltime = 0;
}

// NERVE - SMF - merge from team arena
Expand Down Expand Up @@ -2004,7 +2003,6 @@ void ClientBegin( int clientNum ) {

// L0 - antilag
G_ResetTrail(ent);
ent->client->saved.leveltime = 0;
// End

// Xian -- Changed below for team independant maxlives
Expand Down
Loading