618 lines
17 KiB
C
618 lines
17 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 2006 Neil Toronto.
|
|
|
|
This file is part of the Unlagged source code.
|
|
|
|
Unlagged source code is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or (at your
|
|
option) any later version.
|
|
|
|
Unlagged source code is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Unlagged source code; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
|
|
//Sago: For some reason the Niels version must use a different char set.
|
|
#include "g_local.h"
|
|
|
|
//#include "g_local.h"
|
|
|
|
/*
|
|
============
|
|
G_ResetHistory
|
|
|
|
Clear out the given client's history (should be called when the teleport bit is flipped)
|
|
============
|
|
*/
|
|
void G_ResetHistory( gentity_t *ent ) {
|
|
int i, time;
|
|
|
|
// fill up the history with data (assume the current position)
|
|
ent->client->historyHead = NUM_CLIENT_HISTORY - 1;
|
|
for ( i = ent->client->historyHead, time = level.time; i >= 0; i--, time -= 50 ) {
|
|
VectorCopy( ent->r.mins, ent->client->history[i].mins );
|
|
VectorCopy( ent->r.maxs, ent->client->history[i].maxs );
|
|
VectorCopy( ent->r.currentOrigin, ent->client->history[i].currentOrigin );
|
|
ent->client->history[i].leveltime = time;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
G_StoreHistory
|
|
|
|
Keep track of where the client's been
|
|
============
|
|
*/
|
|
void G_StoreHistory( gentity_t *ent ) {
|
|
int head, frametime;
|
|
|
|
frametime = level.time - level.previousTime;
|
|
|
|
ent->client->historyHead++;
|
|
if ( ent->client->historyHead >= NUM_CLIENT_HISTORY ) {
|
|
ent->client->historyHead = 0;
|
|
}
|
|
|
|
head = ent->client->historyHead;
|
|
|
|
// store all the collision-detection info and the time
|
|
VectorCopy( ent->r.mins, ent->client->history[head].mins );
|
|
VectorCopy( ent->r.maxs, ent->client->history[head].maxs );
|
|
VectorCopy( ent->s.pos.trBase, ent->client->history[head].currentOrigin );
|
|
SnapVector( ent->client->history[head].currentOrigin );
|
|
ent->client->history[head].leveltime = level.time;
|
|
}
|
|
|
|
|
|
/*
|
|
=============
|
|
TimeShiftLerp
|
|
|
|
Used below to interpolate between two previous vectors
|
|
Returns a vector "frac" times the distance between "start" and "end"
|
|
=============
|
|
*/
|
|
static void TimeShiftLerp( float frac, vec3_t start, vec3_t end, vec3_t result ) {
|
|
// From CG_InterpolateEntityPosition in cg_ents.c:
|
|
/*
|
|
cent->lerpOrigin[0] = current[0] + f * ( next[0] - current[0] );
|
|
cent->lerpOrigin[1] = current[1] + f * ( next[1] - current[1] );
|
|
cent->lerpOrigin[2] = current[2] + f * ( next[2] - current[2] );
|
|
*/
|
|
// Making these exactly the same should avoid floating-point error
|
|
|
|
result[0] = start[0] + frac * ( end[0] - start[0] );
|
|
result[1] = start[1] + frac * ( end[1] - start[1] );
|
|
result[2] = start[2] + frac * ( end[2] - start[2] );
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
G_TimeShiftClient
|
|
|
|
Move a client back to where he was at the specified "time"
|
|
=================
|
|
*/
|
|
void G_TimeShiftClient( gentity_t *ent, int time, qboolean debug, gentity_t *debugger ) {
|
|
int j, k;
|
|
//char msg[2048];
|
|
|
|
// this will dump out the head index, and the time for all the stored positions
|
|
/*
|
|
if ( debug ) {
|
|
char str[MAX_STRING_CHARS];
|
|
|
|
Com_sprintf(str, sizeof(str), "print \"head: %d, %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n\"",
|
|
ent->client->historyHead,
|
|
ent->client->history[0].leveltime,
|
|
ent->client->history[1].leveltime,
|
|
ent->client->history[2].leveltime,
|
|
ent->client->history[3].leveltime,
|
|
ent->client->history[4].leveltime,
|
|
ent->client->history[5].leveltime,
|
|
ent->client->history[6].leveltime,
|
|
ent->client->history[7].leveltime,
|
|
ent->client->history[8].leveltime,
|
|
ent->client->history[9].leveltime,
|
|
ent->client->history[10].leveltime,
|
|
ent->client->history[11].leveltime,
|
|
ent->client->history[12].leveltime,
|
|
ent->client->history[13].leveltime,
|
|
ent->client->history[14].leveltime,
|
|
ent->client->history[15].leveltime,
|
|
ent->client->history[16].leveltime);
|
|
|
|
trap_SendServerCommand( debugger - g_entities, str );
|
|
}
|
|
*/
|
|
|
|
// find two entries in the history whose times sandwich "time"
|
|
// assumes no two adjacent records have the same timestamp
|
|
j = k = ent->client->historyHead;
|
|
do {
|
|
if ( ent->client->history[j].leveltime <= time )
|
|
break;
|
|
|
|
k = j;
|
|
j--;
|
|
if ( j < 0 ) {
|
|
j = NUM_CLIENT_HISTORY - 1;
|
|
}
|
|
}
|
|
while ( j != ent->client->historyHead );
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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->historyHead ) {
|
|
float frac = (float)(time - ent->client->history[j].leveltime) /
|
|
(float)(ent->client->history[k].leveltime - ent->client->history[j].leveltime);
|
|
|
|
// interpolate between the two origins to give position at time index "time"
|
|
TimeShiftLerp( frac,
|
|
ent->client->history[j].currentOrigin, ent->client->history[k].currentOrigin,
|
|
ent->r.currentOrigin );
|
|
|
|
// lerp these too, just for fun (and ducking)
|
|
TimeShiftLerp( frac,
|
|
ent->client->history[j].mins, ent->client->history[k].mins,
|
|
ent->r.mins );
|
|
|
|
TimeShiftLerp( frac,
|
|
ent->client->history[j].maxs, ent->client->history[k].maxs,
|
|
ent->r.maxs );
|
|
|
|
/*if ( debug && debugger != NULL ) {
|
|
// print some debugging stuff exactly like what the client does
|
|
|
|
// it starts with "Rec:" to let you know it backward-reconciled
|
|
Com_sprintf( msg, sizeof(msg),
|
|
"print \"^1Rec: time: %d, j: %d, k: %d, origin: %0.2f %0.2f %0.2f\n"
|
|
"^2frac: %0.4f, origin1: %0.2f %0.2f %0.2f, origin2: %0.2f %0.2f %0.2f\n"
|
|
"^7level.time: %d, est time: %d, level.time delta: %d, est real ping: %d\n\"",
|
|
time, ent->client->history[j].leveltime, ent->client->history[k].leveltime,
|
|
ent->r.currentOrigin[0], ent->r.currentOrigin[1], ent->r.currentOrigin[2],
|
|
frac,
|
|
ent->client->history[j].currentOrigin[0],
|
|
ent->client->history[j].currentOrigin[1],
|
|
ent->client->history[j].currentOrigin[2],
|
|
ent->client->history[k].currentOrigin[0],
|
|
ent->client->history[k].currentOrigin[1],
|
|
ent->client->history[k].currentOrigin[2],
|
|
level.time, level.time + debugger->client->frameOffset,
|
|
level.time - time, level.time + debugger->client->frameOffset - time);
|
|
|
|
trap_SendServerCommand( debugger - g_entities, msg );
|
|
}*/
|
|
|
|
// this will recalculate absmin and absmax
|
|
trap_LinkEntity( ent );
|
|
} else {
|
|
// we wrapped, so grab the earliest
|
|
VectorCopy( ent->client->history[k].currentOrigin, ent->r.currentOrigin );
|
|
VectorCopy( ent->client->history[k].mins, ent->r.mins );
|
|
VectorCopy( ent->client->history[k].maxs, ent->r.maxs );
|
|
|
|
// this will recalculate absmin and absmax
|
|
trap_LinkEntity( ent );
|
|
}
|
|
}
|
|
else {
|
|
// this only happens when the client is using a negative timenudge, because that
|
|
// number is added to the command time
|
|
|
|
// print some debugging stuff exactly like what the client does
|
|
|
|
// it starts with "No rec:" to let you know it didn't backward-reconcile
|
|
//Sago: This code looks wierd
|
|
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=====================
|
|
G_TimeShiftAllClients
|
|
|
|
Move ALL clients back to where they were at the specified "time",
|
|
except for "skip"
|
|
=====================
|
|
*/
|
|
void G_TimeShiftAllClients( int time, gentity_t *skip ) {
|
|
int i;
|
|
gentity_t *ent;
|
|
qboolean debug = ( skip != NULL && skip->client &&
|
|
/*skip->client->pers.debugDelag && */ skip->s.weapon == WP_RAILGUN );
|
|
|
|
// for every client
|
|
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 ) {
|
|
G_TimeShiftClient( ent, time, debug, skip );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
G_DoTimeShiftFor
|
|
|
|
Decide what time to shift everyone back to, and do it
|
|
================
|
|
*/
|
|
void G_DoTimeShiftFor( gentity_t *ent ) {
|
|
int wpflags[WP_NUM_WEAPONS] = { 0, 0, 2, 4, 0, 0, 8, 16, 0, 0, 0, 32, 0, 64 };
|
|
|
|
int wpflag = wpflags[ent->client->ps.weapon];
|
|
int time;
|
|
|
|
// don't time shift for mistakes or bots
|
|
if ( !ent->inuse || !ent->client || (ent->r.svFlags & SVF_BOT) ) {
|
|
return;
|
|
}
|
|
|
|
// if it's enabled server-side and the client wants it or wants it for this weapon
|
|
if ( g_delagHitscan.integer && ( ent->client->pers.delag & 1 || ent->client->pers.delag & wpflag ) ) {
|
|
// do the full lag compensation, except what the client nudges
|
|
time = ent->client->attackTime + ent->client->pers.cmdTimeNudge;
|
|
//Give the lightning gun some handicap (lag was part of weapon balance in VQ3)
|
|
if(ent->client->ps.weapon == WP_LIGHTNING && g_lagLightning.integer)
|
|
time+=50;
|
|
}
|
|
else {
|
|
// do just 50ms
|
|
time = level.previousTime + ent->client->frameOffset;
|
|
}
|
|
|
|
G_TimeShiftAllClients( time, ent );
|
|
}
|
|
|
|
|
|
/*
|
|
===================
|
|
G_UnTimeShiftClient
|
|
|
|
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;
|
|
|
|
// this will recalculate absmin and absmax
|
|
trap_LinkEntity( ent );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=======================
|
|
G_UnTimeShiftAllClients
|
|
|
|
Move ALL the clients back to where they were before the time shift,
|
|
except for "skip"
|
|
=======================
|
|
*/
|
|
void G_UnTimeShiftAllClients( gentity_t *skip ) {
|
|
int i;
|
|
gentity_t *ent;
|
|
|
|
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 ) {
|
|
G_UnTimeShiftClient( ent );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
G_UndoTimeShiftFor
|
|
|
|
Put everyone except for this client back where they were
|
|
==================
|
|
*/
|
|
void G_UndoTimeShiftFor( gentity_t *ent ) {
|
|
|
|
// don't un-time shift for mistakes or bots
|
|
if ( !ent->inuse || !ent->client || (ent->r.svFlags & SVF_BOT) ) {
|
|
return;
|
|
}
|
|
|
|
G_UnTimeShiftAllClients( ent );
|
|
}
|
|
|
|
|
|
/*
|
|
===========================
|
|
G_PredictPlayerClipVelocity
|
|
|
|
Slide on the impacting surface
|
|
===========================
|
|
*/
|
|
|
|
#define OVERCLIP 1.001f
|
|
|
|
void G_PredictPlayerClipVelocity( vec3_t in, vec3_t normal, vec3_t out ) {
|
|
float backoff;
|
|
|
|
// find the magnitude of the vector "in" along "normal"
|
|
backoff = DotProduct (in, normal);
|
|
|
|
// tilt the plane a bit to avoid floating-point error issues
|
|
if ( backoff < 0 ) {
|
|
backoff *= OVERCLIP;
|
|
} else {
|
|
backoff /= OVERCLIP;
|
|
}
|
|
|
|
// slide along
|
|
VectorMA( in, -backoff, normal, out );
|
|
}
|
|
|
|
|
|
/*
|
|
========================
|
|
G_PredictPlayerSlideMove
|
|
|
|
Advance the given entity frametime seconds, sliding as appropriate
|
|
========================
|
|
*/
|
|
#define MAX_CLIP_PLANES 5
|
|
|
|
qboolean G_PredictPlayerSlideMove( gentity_t *ent, float frametime ) {
|
|
int bumpcount, numbumps;
|
|
vec3_t dir;
|
|
float d;
|
|
int numplanes;
|
|
vec3_t planes[MAX_CLIP_PLANES];
|
|
vec3_t primal_velocity, velocity, origin;
|
|
vec3_t clipVelocity;
|
|
int i, j, k;
|
|
trace_t trace;
|
|
vec3_t end;
|
|
float time_left;
|
|
float into;
|
|
vec3_t endVelocity;
|
|
vec3_t endClipVelocity;
|
|
// vec3_t worldUp = { 0.0f, 0.0f, 1.0f };
|
|
|
|
numbumps = 4;
|
|
|
|
VectorCopy( ent->s.pos.trDelta, primal_velocity );
|
|
VectorCopy( primal_velocity, velocity );
|
|
VectorCopy( ent->s.pos.trBase, origin );
|
|
|
|
VectorCopy( velocity, endVelocity );
|
|
|
|
time_left = frametime;
|
|
|
|
numplanes = 0;
|
|
|
|
for ( bumpcount = 0; bumpcount < numbumps; bumpcount++ ) {
|
|
|
|
// calculate position we are trying to move to
|
|
VectorMA( origin, time_left, velocity, end );
|
|
|
|
// see if we can make it there
|
|
trap_Trace( &trace, origin, ent->r.mins, ent->r.maxs, end, ent->s.number, ent->clipmask );
|
|
|
|
if (trace.allsolid) {
|
|
// entity is completely trapped in another solid
|
|
VectorClear( velocity );
|
|
VectorCopy( origin, ent->s.pos.trBase );
|
|
return qtrue;
|
|
}
|
|
|
|
if (trace.fraction > 0) {
|
|
// actually covered some distance
|
|
VectorCopy( trace.endpos, origin );
|
|
}
|
|
|
|
if (trace.fraction == 1) {
|
|
break; // moved the entire distance
|
|
}
|
|
|
|
time_left -= time_left * trace.fraction;
|
|
|
|
if ( numplanes >= MAX_CLIP_PLANES ) {
|
|
// this shouldn't really happen
|
|
VectorClear( velocity );
|
|
VectorCopy( origin, ent->s.pos.trBase );
|
|
return qtrue;
|
|
}
|
|
|
|
//
|
|
// if this is the same plane we hit before, nudge velocity
|
|
// out along it, which fixes some epsilon issues with
|
|
// non-axial planes
|
|
//
|
|
for ( i = 0; i < numplanes; i++ ) {
|
|
if ( DotProduct( trace.plane.normal, planes[i] ) > 0.99 ) {
|
|
VectorAdd( trace.plane.normal, velocity, velocity );
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( i < numplanes ) {
|
|
continue;
|
|
}
|
|
|
|
VectorCopy( trace.plane.normal, planes[numplanes] );
|
|
numplanes++;
|
|
|
|
//
|
|
// modify velocity so it parallels all of the clip planes
|
|
//
|
|
|
|
// find a plane that it enters
|
|
for ( i = 0; i < numplanes; i++ ) {
|
|
into = DotProduct( velocity, planes[i] );
|
|
if ( into >= 0.1 ) {
|
|
continue; // move doesn't interact with the plane
|
|
}
|
|
|
|
// slide along the plane
|
|
G_PredictPlayerClipVelocity( velocity, planes[i], clipVelocity );
|
|
|
|
// slide along the plane
|
|
G_PredictPlayerClipVelocity( endVelocity, planes[i], endClipVelocity );
|
|
|
|
// see if there is a second plane that the new move enters
|
|
for ( j = 0; j < numplanes; j++ ) {
|
|
if ( j == i ) {
|
|
continue;
|
|
}
|
|
|
|
if ( DotProduct( clipVelocity, planes[j] ) >= 0.1 ) {
|
|
continue; // move doesn't interact with the plane
|
|
}
|
|
|
|
// try clipping the move to the plane
|
|
G_PredictPlayerClipVelocity( clipVelocity, planes[j], clipVelocity );
|
|
G_PredictPlayerClipVelocity( endClipVelocity, planes[j], endClipVelocity );
|
|
|
|
// see if it goes back into the first clip plane
|
|
if ( DotProduct( clipVelocity, planes[i] ) >= 0 ) {
|
|
continue;
|
|
}
|
|
|
|
// slide the original velocity along the crease
|
|
CrossProduct( planes[i], planes[j], dir );
|
|
VectorNormalize( dir );
|
|
d = DotProduct( dir, velocity );
|
|
VectorScale( dir, d, clipVelocity );
|
|
|
|
CrossProduct( planes[i], planes[j], dir );
|
|
VectorNormalize( dir );
|
|
d = DotProduct( dir, endVelocity );
|
|
VectorScale( dir, d, endClipVelocity );
|
|
|
|
// see if there is a third plane the the new move enters
|
|
for ( k = 0; k < numplanes; k++ ) {
|
|
if ( k == i || k == j ) {
|
|
continue;
|
|
}
|
|
|
|
if ( DotProduct( clipVelocity, planes[k] ) >= 0.1 ) {
|
|
continue; // move doesn't interact with the plane
|
|
}
|
|
|
|
// stop dead at a tripple plane interaction
|
|
VectorClear( velocity );
|
|
VectorCopy( origin, ent->s.pos.trBase );
|
|
return qtrue;
|
|
}
|
|
}
|
|
|
|
// if we have fixed all interactions, try another move
|
|
VectorCopy( clipVelocity, velocity );
|
|
VectorCopy( endClipVelocity, endVelocity );
|
|
break;
|
|
}
|
|
}
|
|
|
|
VectorCopy( endVelocity, velocity );
|
|
VectorCopy( origin, ent->s.pos.trBase );
|
|
|
|
return (bumpcount != 0);
|
|
}
|
|
|
|
|
|
/*
|
|
============================
|
|
G_PredictPlayerStepSlideMove
|
|
|
|
Advance the given entity frametime seconds, stepping and sliding as appropriate
|
|
============================
|
|
*/
|
|
#define STEPSIZE 18
|
|
|
|
void G_PredictPlayerStepSlideMove( gentity_t *ent, float frametime ) {
|
|
vec3_t start_o, start_v, down_o, down_v;
|
|
vec3_t down, up;
|
|
trace_t trace;
|
|
float stepSize;
|
|
|
|
VectorCopy (ent->s.pos.trBase, start_o);
|
|
VectorCopy (ent->s.pos.trDelta, start_v);
|
|
|
|
if ( !G_PredictPlayerSlideMove( ent, frametime ) ) {
|
|
// not clipped, so forget stepping
|
|
return;
|
|
}
|
|
|
|
VectorCopy( ent->s.pos.trBase, down_o);
|
|
VectorCopy( ent->s.pos.trDelta, down_v);
|
|
|
|
VectorCopy (start_o, up);
|
|
up[2] += STEPSIZE;
|
|
|
|
// test the player position if they were a stepheight higher
|
|
trap_Trace( &trace, start_o, ent->r.mins, ent->r.maxs, up, ent->s.number, ent->clipmask );
|
|
if ( trace.allsolid ) {
|
|
return; // can't step up
|
|
}
|
|
|
|
stepSize = trace.endpos[2] - start_o[2];
|
|
|
|
// try slidemove from this position
|
|
VectorCopy( trace.endpos, ent->s.pos.trBase );
|
|
VectorCopy( start_v, ent->s.pos.trDelta );
|
|
|
|
G_PredictPlayerSlideMove( ent, frametime );
|
|
|
|
// push down the final amount
|
|
VectorCopy( ent->s.pos.trBase, down );
|
|
down[2] -= stepSize;
|
|
trap_Trace( &trace, ent->s.pos.trBase, ent->r.mins, ent->r.maxs, down, ent->s.number, ent->clipmask );
|
|
if ( !trace.allsolid ) {
|
|
VectorCopy( trace.endpos, ent->s.pos.trBase );
|
|
}
|
|
if ( trace.fraction < 1.0 ) {
|
|
G_PredictPlayerClipVelocity( ent->s.pos.trDelta, trace.plane.normal, ent->s.pos.trDelta );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===================
|
|
G_PredictPlayerMove
|
|
|
|
Advance the given entity frametime seconds, stepping and sliding as appropriate
|
|
|
|
This is the entry point to the server-side-only prediction code
|
|
===================
|
|
*/
|
|
void G_PredictPlayerMove( gentity_t *ent, float frametime ) {
|
|
G_PredictPlayerStepSlideMove( ent, frametime );
|
|
}
|