Files
illusion-arena/code/game/g_unlagged.c
2025-07-15 15:01:00 -03:00

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