mirror of
https://github.com/YGGverse/xash3d-fwgs.git
synced 2025-01-09 14:47:59 +00:00
5e0a0765ce
The `.editorconfig` file in this repo is configured to trim all trailing whitespace regardless of whether the line is modified. Trims all trailing whitespace in the repository to make the codebase easier to work with in editors that respect `.editorconfig`. `git blame` becomes less useful on these lines but it already isn't very useful. Commands: ``` find . -type f -name '*.h' -exec sed --in-place 's/[[:space:]]\+$//' {} \+ find . -type f -name '*.c' -exec sed --in-place 's/[[:space:]]\+$//' {} \+ ```
1352 lines
35 KiB
C
1352 lines
35 KiB
C
/*
|
|
cl_pmove.c - client-side player physic
|
|
Copyright (C) 2010 Uncle Mike
|
|
|
|
This program 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 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program 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.
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include "client.h"
|
|
#include "const.h"
|
|
#include "cl_tent.h"
|
|
#include "pm_local.h"
|
|
#include "particledef.h"
|
|
#include "studio.h"
|
|
|
|
#define MAX_FORWARD 6 // forward probes for set idealpitch
|
|
#define MIN_CORRECTION_DISTANCE 0.25f // use smoothing if error is > this
|
|
#define MIN_PREDICTION_EPSILON 0.5f // complain if error is > this and we have cl_showerror set
|
|
#define MAX_PREDICTION_ERROR 64.0f // above this is assumed to be a teleport, don't smooth, etc.
|
|
|
|
/*
|
|
=============
|
|
CL_ClearPhysEnts
|
|
|
|
=============
|
|
*/
|
|
void CL_ClearPhysEnts( void )
|
|
{
|
|
clgame.pmove->numtouch = 0;
|
|
clgame.pmove->numvisent = 0;
|
|
clgame.pmove->nummoveent = 0;
|
|
clgame.pmove->numphysent = 0;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CL_PushPMStates
|
|
|
|
=============
|
|
*/
|
|
void GAME_EXPORT CL_PushPMStates( void )
|
|
{
|
|
if( clgame.pushed ) return;
|
|
clgame.oldphyscount = clgame.pmove->numphysent;
|
|
clgame.oldviscount = clgame.pmove->numvisent;
|
|
clgame.pushed = true;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CL_PopPMStates
|
|
|
|
=============
|
|
*/
|
|
void GAME_EXPORT CL_PopPMStates( void )
|
|
{
|
|
if( !clgame.pushed ) return;
|
|
clgame.pmove->numphysent = clgame.oldphyscount;
|
|
clgame.pmove->numvisent = clgame.oldviscount;
|
|
clgame.pushed = false;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CL_PushTraceBounds
|
|
|
|
=============
|
|
*/
|
|
void GAME_EXPORT CL_PushTraceBounds( int hullnum, const float *mins, const float *maxs )
|
|
{
|
|
hullnum = bound( 0, hullnum, 3 );
|
|
VectorCopy( mins, clgame.pmove->player_mins[hullnum] );
|
|
VectorCopy( maxs, clgame.pmove->player_maxs[hullnum] );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CL_PopTraceBounds
|
|
|
|
=============
|
|
*/
|
|
void GAME_EXPORT CL_PopTraceBounds( void )
|
|
{
|
|
memcpy( clgame.pmove->player_mins, host.player_mins, sizeof( host.player_mins ));
|
|
memcpy( clgame.pmove->player_maxs, host.player_maxs, sizeof( host.player_maxs ));
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CL_IsPredicted
|
|
===============
|
|
*/
|
|
qboolean CL_IsPredicted( void )
|
|
{
|
|
if( cl_nopred->value || cl.intermission )
|
|
return false;
|
|
|
|
// never predict the quake demos
|
|
if( cls.demoplayback == DEMO_QUAKE1 )
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CL_SetLastUpdate
|
|
===============
|
|
*/
|
|
void CL_SetLastUpdate( void )
|
|
{
|
|
cls.lastupdate_sequence = cls.netchan.incoming_sequence;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CL_RedoPrediction
|
|
===============
|
|
*/
|
|
void CL_RedoPrediction( void )
|
|
{
|
|
if ( cls.netchan.incoming_sequence != cls.lastupdate_sequence )
|
|
{
|
|
CL_PredictMovement( true );
|
|
CL_CheckPredictionError();
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CL_SetIdealPitch
|
|
===============
|
|
*/
|
|
void CL_SetIdealPitch( void )
|
|
{
|
|
float angleval, sinval, cosval;
|
|
int i, j, step, dir, steps;
|
|
float z[MAX_FORWARD];
|
|
vec3_t top, bottom;
|
|
pmtrace_t tr;
|
|
|
|
if( cl.local.onground == -1 )
|
|
return;
|
|
|
|
angleval = cl.viewangles[YAW] * M_PI2 / 360.0f;
|
|
SinCos( angleval, &sinval, &cosval );
|
|
|
|
// Now move forward by 36, 48, 60, etc. units from the eye position and drop lines straight down
|
|
// 160 or so units to see what's below
|
|
for( i = 0; i < MAX_FORWARD; i++ )
|
|
{
|
|
top[0] = cl.simorg[0] + cosval * (i + 3.0f) * 12.0f;
|
|
top[1] = cl.simorg[1] + sinval * (i + 3.0f) * 12.0f;
|
|
top[2] = cl.simorg[2] + cl.viewheight[2];
|
|
|
|
bottom[0] = top[0];
|
|
bottom[1] = top[1];
|
|
bottom[2] = top[2] - 160.0f;
|
|
|
|
// skip any monsters (only world and brush models)
|
|
tr = CL_TraceLine( top, bottom, PM_STUDIO_BOX );
|
|
if( tr.allsolid ) return; // looking at a wall, leave ideal the way is was
|
|
|
|
if( tr.fraction == 1.0f )
|
|
return; // near a dropoff
|
|
|
|
z[i] = top[2] + tr.fraction * (bottom[2] - top[2]);
|
|
}
|
|
|
|
dir = 0;
|
|
steps = 0;
|
|
|
|
for( j = 1; j < i; j++ )
|
|
{
|
|
step = z[j] - z[j-1];
|
|
if( step > -ON_EPSILON && step < ON_EPSILON )
|
|
continue;
|
|
|
|
if( dir && ( step-dir > ON_EPSILON || step-dir < -ON_EPSILON ))
|
|
return; // mixed changes
|
|
|
|
steps++;
|
|
dir = step;
|
|
}
|
|
|
|
if( !dir )
|
|
{
|
|
cl.local.idealpitch = 0.0f;
|
|
return;
|
|
}
|
|
|
|
if( steps < 2 ) return;
|
|
cl.local.idealpitch = -dir * cl_idealpitchscale->value;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_PlayerTeleported
|
|
|
|
check for instant movement in case
|
|
we don't want interpolate this
|
|
==================
|
|
*/
|
|
qboolean CL_PlayerTeleported( local_state_t *from, local_state_t *to )
|
|
{
|
|
int len, maxlen;
|
|
vec3_t delta;
|
|
|
|
VectorSubtract( to->playerstate.origin, from->playerstate.origin, delta );
|
|
|
|
// compute potential max movement in units per frame and compare with entity movement
|
|
maxlen = ( clgame.movevars.maxvelocity * ( 1.0f / GAME_FPS ));
|
|
len = VectorLength( delta );
|
|
|
|
return (len > maxlen);
|
|
}
|
|
|
|
/*
|
|
===================
|
|
CL_CheckPredictionError
|
|
===================
|
|
*/
|
|
void CL_CheckPredictionError( void )
|
|
{
|
|
int frame, cmd;
|
|
static int pos = 0;
|
|
vec3_t delta;
|
|
float dist;
|
|
|
|
if( !CL_IsPredicted( ))
|
|
return;
|
|
|
|
// calculate the last usercmd_t we sent that the server has processed
|
|
frame = ( cls.netchan.incoming_acknowledged ) & CL_UPDATE_MASK;
|
|
cmd = cl.parsecountmod;
|
|
|
|
// compare what the server returned with what we had predicted it to be
|
|
VectorSubtract( cl.frames[cmd].playerstate[cl.playernum].origin, cl.local.predicted_origins[frame], delta );
|
|
dist = VectorLength( delta );
|
|
|
|
// save the prediction error for interpolation
|
|
if( dist > MAX_PREDICTION_ERROR )
|
|
{
|
|
if( cl_showerror->value && host_developer.value )
|
|
Con_NPrintf( 10 + ( ++pos & 3 ), "^3player teleported:^7 %.3f units\n", dist );
|
|
|
|
// a teleport or something or gamepaused
|
|
VectorClear( cl.local.prediction_error );
|
|
}
|
|
else
|
|
{
|
|
if( cl_showerror->value && dist > MIN_PREDICTION_EPSILON && host_developer.value )
|
|
Con_NPrintf( 10 + ( ++pos & 3 ), "^1prediction error:^7 %.3f units\n", dist );
|
|
|
|
VectorCopy( cl.frames[cmd].playerstate[cl.playernum].origin, cl.local.predicted_origins[frame] );
|
|
|
|
// save for error interpolation
|
|
VectorCopy( delta, cl.local.prediction_error );
|
|
|
|
if( dist > MIN_CORRECTION_DISTANCE )
|
|
cls.correction_time = cl_smoothtime->value;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CL_SetUpPlayerPrediction
|
|
|
|
Calculate the new position of players, without other player clipping
|
|
We do this to set up real player prediction.
|
|
Players are predicted twice, first without clipping other players,
|
|
then with clipping against them.
|
|
This sets up the first phase.
|
|
=============
|
|
*/
|
|
void GAME_EXPORT CL_SetUpPlayerPrediction( int dopred, int bIncludeLocalClient )
|
|
{
|
|
entity_state_t *state;
|
|
predicted_player_t *player;
|
|
cl_entity_t *ent;
|
|
int i;
|
|
|
|
for( i = 0; i < MAX_CLIENTS; i++ )
|
|
{
|
|
state = &cl.frames[cl.parsecountmod].playerstate[i];
|
|
player = &cls.predicted_players[i];
|
|
|
|
player->active = false;
|
|
|
|
if( state->messagenum != cl.parsecount )
|
|
continue; // not present this frame
|
|
|
|
if( !state->modelindex )
|
|
continue;
|
|
|
|
player->active = true;
|
|
player->movetype = state->movetype;
|
|
player->solid = state->solid;
|
|
player->usehull = state->usehull;
|
|
|
|
if( FBitSet( state->effects, EF_NODRAW ) && !bIncludeLocalClient && ( cl.playernum == i ))
|
|
continue;
|
|
|
|
// note that the local player is special, since he moves locally
|
|
// we use his last predicted postition
|
|
if( cl.playernum == i )
|
|
{
|
|
VectorCopy( state->origin, player->origin );
|
|
VectorCopy( state->angles, player->angles );
|
|
}
|
|
else
|
|
{
|
|
ent = CL_GetEntityByIndex( i + 1 );
|
|
|
|
CL_ComputePlayerOrigin( ent );
|
|
|
|
VectorCopy( ent->origin, player->origin );
|
|
VectorCopy( ent->angles, player->angles );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CL_ClipPMoveToEntity( physent_t *pe, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, pmtrace_t *tr )
|
|
{
|
|
Assert( tr != NULL );
|
|
|
|
if( clgame.dllFuncs.pfnClipMoveToEntity != NULL )
|
|
{
|
|
// do custom sweep test
|
|
clgame.dllFuncs.pfnClipMoveToEntity( pe, start, mins, maxs, end, tr );
|
|
}
|
|
else
|
|
{
|
|
// function is missed, so we didn't hit anything
|
|
tr->allsolid = false;
|
|
}
|
|
}
|
|
|
|
static void CL_CopyEntityToPhysEnt( physent_t *pe, entity_state_t *state, qboolean visent )
|
|
{
|
|
model_t *mod = CL_ModelHandle( state->modelindex );
|
|
|
|
pe->player = 0;
|
|
|
|
if( state->number >= 1 && state->number <= cl.maxclients )
|
|
pe->player = state->number;
|
|
|
|
if( pe->player )
|
|
{
|
|
// client or bot
|
|
Q_snprintf( pe->name, sizeof( pe->name ), "player %i", pe->player - 1 );
|
|
}
|
|
else
|
|
{
|
|
// otherwise copy the modelname
|
|
Q_strncpy( pe->name, mod->name, sizeof( pe->name ));
|
|
}
|
|
|
|
pe->model = pe->studiomodel = NULL;
|
|
|
|
VectorCopy( state->mins, pe->mins );
|
|
VectorCopy( state->maxs, pe->maxs );
|
|
|
|
if( state->solid == SOLID_BBOX )
|
|
{
|
|
if( FBitSet( mod->flags, STUDIO_TRACE_HITBOX ))
|
|
pe->studiomodel = mod;
|
|
}
|
|
else
|
|
{
|
|
if( pe->solid != SOLID_BSP && ( mod != NULL ) && ( mod->type == mod_studio ))
|
|
pe->studiomodel = mod;
|
|
else pe->model = mod;
|
|
}
|
|
|
|
// rare case: not solid entities in vistrace
|
|
if( visent && VectorIsNull( pe->mins ))
|
|
{
|
|
VectorCopy( mod->mins, pe->mins );
|
|
VectorCopy( mod->maxs, pe->maxs );
|
|
}
|
|
|
|
pe->info = state->number;
|
|
VectorCopy( state->origin, pe->origin );
|
|
VectorCopy( state->angles, pe->angles );
|
|
|
|
pe->solid = state->solid;
|
|
pe->rendermode = state->rendermode;
|
|
pe->skin = state->skin;
|
|
pe->frame = state->frame;
|
|
pe->sequence = state->sequence;
|
|
|
|
memcpy( &pe->controller[0], &state->controller[0], sizeof( pe->controller ));
|
|
memcpy( &pe->blending[0], &state->blending[0], sizeof( pe->blending ));
|
|
|
|
pe->movetype = state->movetype;
|
|
pe->takedamage = (pe->player) ? DAMAGE_YES : DAMAGE_NO;
|
|
pe->team = state->team;
|
|
pe->classnumber = state->playerclass;
|
|
pe->blooddecal = 0; // unused in GoldSrc
|
|
|
|
// for mods
|
|
pe->iuser1 = state->iuser1;
|
|
pe->iuser2 = state->iuser2;
|
|
pe->iuser3 = state->iuser3;
|
|
pe->iuser4 = state->iuser4;
|
|
pe->fuser1 = state->fuser1;
|
|
pe->fuser2 = state->fuser2;
|
|
pe->fuser3 = state->fuser3;
|
|
pe->fuser4 = state->fuser4;
|
|
|
|
VectorCopy( state->vuser1, pe->vuser1 );
|
|
VectorCopy( state->vuser2, pe->vuser2 );
|
|
VectorCopy( state->vuser3, pe->vuser3 );
|
|
VectorCopy( state->vuser4, pe->vuser4 );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CL_AddLinksToPmove
|
|
|
|
collect solid entities
|
|
====================
|
|
*/
|
|
void CL_AddLinksToPmove( frame_t *frame )
|
|
{
|
|
entity_state_t *state;
|
|
model_t *model;
|
|
physent_t *pe;
|
|
int i;
|
|
|
|
if( !frame->valid ) return;
|
|
|
|
for( i = 0; i < frame->num_entities; i++ )
|
|
{
|
|
state = &cls.packet_entities[(frame->first_entity + i) % cls.num_client_entities];
|
|
|
|
if( state->number >= 1 && state->number <= cl.maxclients )
|
|
continue;
|
|
|
|
if( !state->modelindex )
|
|
continue;
|
|
|
|
model = CL_ModelHandle( state->modelindex );
|
|
if( !model ) continue;
|
|
|
|
if(( state->owner != 0 ) && ( state->owner == cl.playernum + 1 ))
|
|
continue;
|
|
|
|
if(( model->hulls[1].lastclipnode || model->type == mod_studio ) && clgame.pmove->numvisent < MAX_PHYSENTS )
|
|
{
|
|
pe = &clgame.pmove->visents[clgame.pmove->numvisent];
|
|
CL_CopyEntityToPhysEnt( pe, state, true );
|
|
clgame.pmove->numvisent++;
|
|
}
|
|
|
|
if( state->solid == SOLID_TRIGGER || ( state->solid == SOLID_NOT && state->skin >= CONTENTS_EMPTY ))
|
|
continue;
|
|
|
|
// dead body
|
|
if( state->mins[2] == 0.0f && state->maxs[2] == 1.0f )
|
|
continue;
|
|
|
|
// can't collide with zeroed hull
|
|
if( VectorIsNull( state->mins ) && VectorIsNull( state->maxs ))
|
|
continue;
|
|
|
|
if( state->solid == SOLID_NOT && state->skin == CONTENTS_LADDER )
|
|
{
|
|
if( clgame.pmove->nummoveent >= MAX_MOVEENTS )
|
|
continue;
|
|
|
|
pe = &clgame.pmove->moveents[clgame.pmove->nummoveent];
|
|
CL_CopyEntityToPhysEnt( pe, state, false );
|
|
clgame.pmove->nummoveent++;
|
|
}
|
|
else
|
|
{
|
|
if( !model->hulls[1].lastclipnode && model->type != mod_studio )
|
|
continue;
|
|
|
|
// reserve slots for all the clients
|
|
if( clgame.pmove->numphysent >= ( MAX_PHYSENTS - cl.maxclients ))
|
|
continue;
|
|
|
|
pe = &clgame.pmove->physents[clgame.pmove->numphysent];
|
|
CL_CopyEntityToPhysEnt( pe, state, false );
|
|
clgame.pmove->numphysent++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CL_SetSolidEntities
|
|
|
|
Builds all the pmove physents for the current frame
|
|
===============
|
|
*/
|
|
void CL_SetSolidEntities( void )
|
|
{
|
|
physent_t *pe = clgame.pmove->physents;
|
|
|
|
// setup physents
|
|
clgame.pmove->numvisent = 1;
|
|
clgame.pmove->numphysent = 1;
|
|
clgame.pmove->nummoveent = 0;
|
|
|
|
memset( clgame.pmove->physents, 0, sizeof( physent_t ));
|
|
memset( clgame.pmove->visents, 0, sizeof( physent_t ));
|
|
|
|
pe->model = cl.worldmodel;
|
|
if( pe->model ) Q_strncpy( pe->name, pe->model->name, sizeof( pe->name ));
|
|
pe->takedamage = DAMAGE_YES;
|
|
pe->solid = SOLID_BSP;
|
|
|
|
// share to visents
|
|
clgame.pmove->visents[0] = clgame.pmove->physents[0];
|
|
|
|
// add all other entities exlucde players
|
|
CL_AddLinksToPmove( &cl.frames[cl.parsecountmod] );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CL_SetSolidPlayers
|
|
|
|
Builds all the pmove physents for the current frame
|
|
Note that CL_SetUpPlayerPrediction() must be called first!
|
|
pmove must be setup with world and solid entity hulls before calling
|
|
(via CL_PredictMove)
|
|
===============
|
|
*/
|
|
void GAME_EXPORT CL_SetSolidPlayers( int playernum )
|
|
{
|
|
entity_state_t *state;
|
|
predicted_player_t *player;
|
|
physent_t *pe;
|
|
int i;
|
|
|
|
if( !cl_solid_players->value )
|
|
return;
|
|
|
|
for( i = 0; i < MAX_CLIENTS; i++ )
|
|
{
|
|
state = &cl.frames[cl.parsecountmod].playerstate[i];
|
|
player = &cls.predicted_players[i];
|
|
|
|
if( playernum == -1 )
|
|
{
|
|
if( i != cl.playernum && !player->active )
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if( !player->active )
|
|
continue; // not present this frame
|
|
|
|
// the player object never gets added
|
|
if( playernum == i )
|
|
continue;
|
|
}
|
|
|
|
if( player->solid == SOLID_NOT )
|
|
continue; // dead body
|
|
|
|
if( clgame.pmove->numphysent >= MAX_PHYSENTS )
|
|
break;
|
|
|
|
pe = &clgame.pmove->physents[clgame.pmove->numphysent];
|
|
CL_CopyEntityToPhysEnt( pe, state, false );
|
|
clgame.pmove->numphysent++;
|
|
|
|
// some fields needs to be override from cls.predicted_players
|
|
VectorCopy( player->origin, pe->origin );
|
|
VectorCopy( player->angles, pe->angles );
|
|
VectorCopy( clgame.pmove->player_mins[player->usehull], pe->mins );
|
|
VectorCopy( clgame.pmove->player_maxs[player->usehull], pe->maxs );
|
|
pe->movetype = player->movetype;
|
|
pe->solid = player->solid;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CL_WaterEntity
|
|
|
|
=============
|
|
*/
|
|
int GAME_EXPORT CL_WaterEntity( const float *rgflPos )
|
|
{
|
|
physent_t *pe;
|
|
hull_t *hull;
|
|
vec3_t test, offset;
|
|
int i, oldhull;
|
|
|
|
if( !rgflPos ) return -1;
|
|
|
|
oldhull = clgame.pmove->usehull;
|
|
|
|
for( i = 0; i < clgame.pmove->numphysent; i++ )
|
|
{
|
|
pe = &clgame.pmove->physents[i];
|
|
|
|
if( pe->solid != SOLID_NOT ) // disabled ?
|
|
continue;
|
|
|
|
// only brushes can have special contents
|
|
if( !pe->model || pe->model->type != mod_brush )
|
|
continue;
|
|
|
|
// check water brushes accuracy
|
|
clgame.pmove->usehull = 2;
|
|
hull = PM_HullForBsp( pe, clgame.pmove, offset );
|
|
clgame.pmove->usehull = oldhull;
|
|
|
|
// offset the test point appropriately for this hull.
|
|
VectorSubtract( rgflPos, offset, test );
|
|
|
|
if( FBitSet( pe->model->flags, MODEL_HAS_ORIGIN ) && !VectorIsNull( pe->angles ))
|
|
{
|
|
matrix4x4 matrix;
|
|
|
|
Matrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f );
|
|
Matrix4x4_VectorITransform( matrix, rgflPos, test );
|
|
}
|
|
|
|
// test hull for intersection with this model
|
|
if( PM_HullPointContents( hull, hull->firstclipnode, test ) == CONTENTS_EMPTY )
|
|
continue;
|
|
|
|
// found water entity
|
|
return pe->info;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CL_TraceLine
|
|
|
|
a simple engine traceline
|
|
=============
|
|
*/
|
|
pmtrace_t CL_TraceLine( vec3_t start, vec3_t end, int flags )
|
|
{
|
|
int old_usehull;
|
|
pmtrace_t tr;
|
|
|
|
old_usehull = clgame.pmove->usehull;
|
|
clgame.pmove->usehull = 2;
|
|
tr = PM_PlayerTraceExt( clgame.pmove, start, end, flags, clgame.pmove->numphysent, clgame.pmove->physents, -1, NULL );
|
|
clgame.pmove->usehull = old_usehull;
|
|
|
|
return tr;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CL_VisTraceLine
|
|
|
|
trace by visible objects (thats can be non-solid)
|
|
=============
|
|
*/
|
|
pmtrace_t *CL_VisTraceLine( vec3_t start, vec3_t end, int flags )
|
|
{
|
|
int old_usehull;
|
|
static pmtrace_t tr;
|
|
|
|
old_usehull = clgame.pmove->usehull;
|
|
clgame.pmove->usehull = 2;
|
|
tr = PM_PlayerTraceExt( clgame.pmove, start, end, flags, clgame.pmove->numvisent, clgame.pmove->visents, -1, NULL );
|
|
clgame.pmove->usehull = old_usehull;
|
|
|
|
return &tr;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CL_GetWaterEntity
|
|
|
|
returns water brush where inside pos
|
|
=============
|
|
*/
|
|
cl_entity_t *CL_GetWaterEntity( const float *rgflPos )
|
|
{
|
|
int entnum;
|
|
|
|
entnum = CL_WaterEntity( rgflPos );
|
|
if( entnum <= 0 ) return NULL; // world or not water
|
|
|
|
return CL_GetEntityByIndex( entnum );
|
|
}
|
|
|
|
int GAME_EXPORT CL_TestLine( const vec3_t start, const vec3_t end, int flags )
|
|
{
|
|
return PM_TestLineExt( clgame.pmove, clgame.pmove->physents, clgame.pmove->numphysent, start, end, flags );
|
|
}
|
|
|
|
static int GAME_EXPORT pfnTestPlayerPosition( float *pos, pmtrace_t *ptrace )
|
|
{
|
|
return PM_TestPlayerPosition( clgame.pmove, pos, ptrace, NULL );
|
|
}
|
|
|
|
static void GAME_EXPORT pfnStuckTouch( int hitent, pmtrace_t *tr )
|
|
{
|
|
int i;
|
|
|
|
for( i = 0; i < clgame.pmove->numtouch; i++ )
|
|
{
|
|
if( clgame.pmove->touchindex[i].ent == hitent )
|
|
return;
|
|
}
|
|
|
|
if( clgame.pmove->numtouch >= MAX_PHYSENTS )
|
|
return;
|
|
|
|
VectorCopy( clgame.pmove->velocity, tr->deltavelocity );
|
|
tr->ent = hitent;
|
|
|
|
clgame.pmove->touchindex[clgame.pmove->numtouch++] = *tr;
|
|
}
|
|
|
|
static int GAME_EXPORT pfnPointContents( float *p, int *truecontents )
|
|
{
|
|
int cont, truecont;
|
|
|
|
truecont = cont = PM_PointContents( clgame.pmove, p );
|
|
if( truecontents ) *truecontents = truecont;
|
|
|
|
if( cont <= CONTENTS_CURRENT_0 && cont >= CONTENTS_CURRENT_DOWN )
|
|
cont = CONTENTS_WATER;
|
|
return cont;
|
|
}
|
|
|
|
static int GAME_EXPORT pfnTruePointContents( float *p )
|
|
{
|
|
return PM_TruePointContents( clgame.pmove, p );
|
|
}
|
|
|
|
static int GAME_EXPORT pfnHullPointContents( struct hull_s *hull, int num, float *p )
|
|
{
|
|
return PM_HullPointContents( hull, num, p );
|
|
}
|
|
|
|
static pmtrace_t GAME_EXPORT pfnPlayerTrace( float *start, float *end, int traceFlags, int ignore_pe )
|
|
{
|
|
return PM_PlayerTraceExt( clgame.pmove, start, end, traceFlags, clgame.pmove->numphysent, clgame.pmove->physents, ignore_pe, NULL );
|
|
}
|
|
|
|
pmtrace_t *PM_TraceLine( float *start, float *end, int flags, int usehull, int ignore_pe )
|
|
{
|
|
static pmtrace_t tr;
|
|
int old_usehull;
|
|
|
|
old_usehull = clgame.pmove->usehull;
|
|
clgame.pmove->usehull = usehull;
|
|
|
|
switch( flags )
|
|
{
|
|
case PM_TRACELINE_PHYSENTSONLY:
|
|
tr = PM_PlayerTraceExt( clgame.pmove, start, end, 0, clgame.pmove->numphysent, clgame.pmove->physents, ignore_pe, NULL );
|
|
break;
|
|
case PM_TRACELINE_ANYVISIBLE:
|
|
tr = PM_PlayerTraceExt( clgame.pmove, start, end, 0, clgame.pmove->numvisent, clgame.pmove->visents, ignore_pe, NULL );
|
|
break;
|
|
}
|
|
|
|
clgame.pmove->usehull = old_usehull;
|
|
|
|
return &tr;
|
|
}
|
|
|
|
static hull_t *pfnHullForBsp( physent_t *pe, float *offset )
|
|
{
|
|
return PM_HullForBsp( pe, clgame.pmove, offset );
|
|
}
|
|
|
|
static float GAME_EXPORT pfnTraceModel( physent_t *pe, float *start, float *end, trace_t *trace )
|
|
{
|
|
int old_usehull;
|
|
vec3_t start_l, end_l;
|
|
vec3_t offset, temp;
|
|
qboolean rotated;
|
|
matrix4x4 matrix;
|
|
hull_t *hull;
|
|
|
|
old_usehull = clgame.pmove->usehull;
|
|
clgame.pmove->usehull = 2;
|
|
|
|
hull = PM_HullForBsp( pe, clgame.pmove, offset );
|
|
|
|
clgame.pmove->usehull = old_usehull;
|
|
|
|
if( pe->solid == SOLID_BSP && !VectorIsNull( pe->angles ))
|
|
rotated = true;
|
|
else rotated = false;
|
|
|
|
if( rotated )
|
|
{
|
|
Matrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f );
|
|
Matrix4x4_VectorITransform( matrix, start, start_l );
|
|
Matrix4x4_VectorITransform( matrix, end, end_l );
|
|
}
|
|
else
|
|
{
|
|
VectorSubtract( start, offset, start_l );
|
|
VectorSubtract( end, offset, end_l );
|
|
}
|
|
|
|
PM_RecursiveHullCheck( hull, hull->firstclipnode, 0, 1, start_l, end_l, (pmtrace_t *)trace );
|
|
trace->ent = NULL;
|
|
|
|
if( rotated )
|
|
{
|
|
VectorCopy( trace->plane.normal, temp );
|
|
Matrix4x4_TransformPositivePlane( matrix, temp, trace->plane.dist, trace->plane.normal, &trace->plane.dist );
|
|
}
|
|
|
|
VectorLerp( start, trace->fraction, end, trace->endpos );
|
|
|
|
return trace->fraction;
|
|
}
|
|
|
|
static const char *pfnTraceTexture( int ground, float *vstart, float *vend )
|
|
{
|
|
physent_t *pe;
|
|
|
|
if( ground < 0 || ground >= clgame.pmove->numphysent )
|
|
return NULL; // bad ground
|
|
|
|
pe = &clgame.pmove->physents[ground];
|
|
return PM_TraceTexture( pe, vstart, vend );
|
|
}
|
|
|
|
static void GAME_EXPORT pfnPlaySound( int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch )
|
|
{
|
|
if( !clgame.pmove->runfuncs )
|
|
return;
|
|
|
|
S_StartSound( NULL, clgame.pmove->player_index + 1, channel, S_RegisterSound( sample ), volume, attenuation, pitch, fFlags );
|
|
}
|
|
|
|
static void GAME_EXPORT pfnPlaybackEventFull( int flags, int clientindex, word eventindex, float delay, float *origin,
|
|
float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 )
|
|
{
|
|
CL_PlaybackEvent( flags, NULL, eventindex, delay, origin, angles, fparam1, fparam2, iparam1, iparam2, bparam1, bparam2 );
|
|
}
|
|
|
|
static pmtrace_t GAME_EXPORT pfnPlayerTraceEx( float *start, float *end, int traceFlags, pfnIgnore pmFilter )
|
|
{
|
|
return PM_PlayerTraceExt( clgame.pmove, start, end, traceFlags, clgame.pmove->numphysent, clgame.pmove->physents, -1, pmFilter );
|
|
}
|
|
|
|
static int GAME_EXPORT pfnTestPlayerPositionEx( float *pos, pmtrace_t *ptrace, pfnIgnore pmFilter )
|
|
{
|
|
return PM_TestPlayerPosition( clgame.pmove, pos, ptrace, pmFilter );
|
|
}
|
|
|
|
static pmtrace_t *pfnTraceLineEx( float *start, float *end, int flags, int usehull, pfnIgnore pmFilter )
|
|
{
|
|
static pmtrace_t tr;
|
|
int old_usehull;
|
|
|
|
old_usehull = clgame.pmove->usehull;
|
|
clgame.pmove->usehull = usehull;
|
|
|
|
switch( flags )
|
|
{
|
|
case PM_TRACELINE_PHYSENTSONLY:
|
|
tr = PM_PlayerTraceExt( clgame.pmove, start, end, 0, clgame.pmove->numphysent, clgame.pmove->physents, -1, pmFilter );
|
|
break;
|
|
case PM_TRACELINE_ANYVISIBLE:
|
|
tr = PM_PlayerTraceExt( clgame.pmove, start, end, 0, clgame.pmove->numvisent, clgame.pmove->visents, -1, pmFilter );
|
|
break;
|
|
}
|
|
|
|
clgame.pmove->usehull = old_usehull;
|
|
|
|
return &tr;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CL_InitClientMove
|
|
|
|
===============
|
|
*/
|
|
void CL_InitClientMove( void )
|
|
{
|
|
int i;
|
|
|
|
Pmove_Init ();
|
|
|
|
clgame.pmove->server = false; // running at client
|
|
clgame.pmove->movevars = &clgame.movevars;
|
|
clgame.pmove->runfuncs = false;
|
|
|
|
// enumerate client hulls
|
|
for( i = 0; i < MAX_MAP_HULLS; i++ )
|
|
{
|
|
if( clgame.dllFuncs.pfnGetHullBounds( i, host.player_mins[i], host.player_maxs[i] ))
|
|
Con_Reportf( "CL: hull%i, player_mins: %g %g %g, player_maxs: %g %g %g\n", i,
|
|
host.player_mins[i][0], host.player_mins[i][1], host.player_mins[i][2],
|
|
host.player_maxs[i][0], host.player_maxs[i][1], host.player_maxs[i][2] );
|
|
}
|
|
|
|
memcpy( clgame.pmove->player_mins, host.player_mins, sizeof( host.player_mins ));
|
|
memcpy( clgame.pmove->player_maxs, host.player_maxs, sizeof( host.player_maxs ));
|
|
|
|
// common utilities
|
|
clgame.pmove->PM_Info_ValueForKey = Info_ValueForKey;
|
|
clgame.pmove->PM_Particle = CL_Particle; // ref should be initialized here already
|
|
clgame.pmove->PM_TestPlayerPosition = pfnTestPlayerPosition;
|
|
clgame.pmove->Con_NPrintf = Con_NPrintf;
|
|
clgame.pmove->Con_DPrintf = Con_DPrintf;
|
|
clgame.pmove->Con_Printf = Con_Printf;
|
|
clgame.pmove->Sys_FloatTime = Sys_DoubleTime;
|
|
clgame.pmove->PM_StuckTouch = pfnStuckTouch;
|
|
clgame.pmove->PM_PointContents = pfnPointContents;
|
|
clgame.pmove->PM_TruePointContents = pfnTruePointContents;
|
|
clgame.pmove->PM_HullPointContents = pfnHullPointContents;
|
|
clgame.pmove->PM_PlayerTrace = pfnPlayerTrace;
|
|
clgame.pmove->PM_TraceLine = PM_TraceLine;
|
|
clgame.pmove->RandomLong = COM_RandomLong;
|
|
clgame.pmove->RandomFloat = COM_RandomFloat;
|
|
clgame.pmove->PM_GetModelType = pfnGetModelType;
|
|
clgame.pmove->PM_GetModelBounds = pfnGetModelBounds;
|
|
clgame.pmove->PM_HullForBsp = (void*)pfnHullForBsp;
|
|
clgame.pmove->PM_TraceModel = pfnTraceModel;
|
|
clgame.pmove->COM_FileSize = COM_FileSize;
|
|
clgame.pmove->COM_LoadFile = COM_LoadFile;
|
|
clgame.pmove->COM_FreeFile = COM_FreeFile;
|
|
clgame.pmove->memfgets = COM_MemFgets;
|
|
clgame.pmove->PM_PlaySound = pfnPlaySound;
|
|
clgame.pmove->PM_TraceTexture = pfnTraceTexture;
|
|
clgame.pmove->PM_PlaybackEventFull = pfnPlaybackEventFull;
|
|
clgame.pmove->PM_PlayerTraceEx = pfnPlayerTraceEx;
|
|
clgame.pmove->PM_TestPlayerPositionEx = pfnTestPlayerPositionEx;
|
|
clgame.pmove->PM_TraceLineEx = pfnTraceLineEx;
|
|
clgame.pmove->PM_TraceSurface = pfnTraceSurface;
|
|
|
|
// initalize pmove
|
|
clgame.dllFuncs.pfnPlayerMoveInit( clgame.pmove );
|
|
}
|
|
|
|
static void PM_CheckMovingGround( clientdata_t *cd, entity_state_t *state, float frametime )
|
|
{
|
|
if(!( cd->flags & FL_BASEVELOCITY ))
|
|
{
|
|
// apply momentum (add in half of the previous frame of velocity first)
|
|
VectorMA( cd->velocity, 1.0f + (frametime * 0.5f), state->basevelocity, cd->velocity );
|
|
VectorClear( state->basevelocity );
|
|
}
|
|
cd->flags &= ~FL_BASEVELOCITY;
|
|
}
|
|
|
|
void CL_SetupPMove( playermove_t *pmove, local_state_t *from, usercmd_t *ucmd, qboolean runfuncs, double time )
|
|
{
|
|
entity_state_t *ps;
|
|
clientdata_t *cd;
|
|
|
|
ps = &from->playerstate;
|
|
cd = &from->client;
|
|
|
|
pmove->player_index = ps->number - 1;
|
|
pmove->multiplayer = (cl.maxclients > 1);
|
|
pmove->runfuncs = runfuncs;
|
|
pmove->time = time * 1000.0f;
|
|
pmove->frametime = ucmd->msec / 1000.0f;
|
|
VectorCopy( ps->origin, pmove->origin );
|
|
VectorCopy( ps->angles, pmove->angles );
|
|
VectorCopy( pmove->angles, pmove->oldangles );
|
|
VectorCopy( cd->velocity, pmove->velocity );
|
|
VectorCopy( ps->basevelocity, pmove->basevelocity );
|
|
VectorCopy( cd->view_ofs, pmove->view_ofs );
|
|
VectorClear( pmove->movedir );
|
|
pmove->flDuckTime = cd->flDuckTime;
|
|
pmove->bInDuck = cd->bInDuck;
|
|
pmove->usehull = ps->usehull;
|
|
pmove->flTimeStepSound = cd->flTimeStepSound;
|
|
pmove->iStepLeft = ps->iStepLeft;
|
|
pmove->flFallVelocity = ps->flFallVelocity;
|
|
pmove->flSwimTime = cd->flSwimTime;
|
|
VectorCopy( cd->punchangle, pmove->punchangle );
|
|
pmove->flSwimTime = cd->flSwimTime;
|
|
pmove->flNextPrimaryAttack = 0.0f; // not used by PM_ code
|
|
pmove->effects = ps->effects;
|
|
pmove->flags = cd->flags;
|
|
pmove->gravity = ps->gravity;
|
|
pmove->friction = ps->friction;
|
|
pmove->oldbuttons = ps->oldbuttons;
|
|
pmove->waterjumptime = cd->waterjumptime;
|
|
pmove->dead = (cl.local.health <= 0);
|
|
pmove->deadflag = cd->deadflag;
|
|
pmove->spectator = (cls.spectator != 0);
|
|
pmove->movetype = ps->movetype;
|
|
pmove->onground = ps->onground;
|
|
pmove->waterlevel = cd->waterlevel;
|
|
pmove->watertype = cd->watertype;
|
|
pmove->maxspeed = clgame.movevars.maxspeed;
|
|
pmove->clientmaxspeed = cd->maxspeed;
|
|
pmove->iuser1 = cd->iuser1;
|
|
pmove->iuser2 = cd->iuser2;
|
|
pmove->iuser3 = cd->iuser3;
|
|
pmove->iuser4 = cd->iuser4;
|
|
pmove->fuser1 = cd->fuser1;
|
|
pmove->fuser2 = cd->fuser2;
|
|
pmove->fuser3 = cd->fuser3;
|
|
pmove->fuser4 = cd->fuser4;
|
|
VectorCopy( cd->vuser1, pmove->vuser1 );
|
|
VectorCopy( cd->vuser2, pmove->vuser2 );
|
|
VectorCopy( cd->vuser3, pmove->vuser3 );
|
|
VectorCopy( cd->vuser4, pmove->vuser4 );
|
|
pmove->cmd = *ucmd; // copy current cmds
|
|
|
|
Q_strncpy( pmove->physinfo, cls.physinfo, MAX_INFO_STRING );
|
|
}
|
|
|
|
void CL_FinishPMove( playermove_t *pmove, local_state_t *to )
|
|
{
|
|
entity_state_t *ps;
|
|
clientdata_t *cd;
|
|
|
|
ps = &to->playerstate;
|
|
cd = &to->client;
|
|
|
|
cd->flags = pmove->flags;
|
|
cd->bInDuck = pmove->bInDuck;
|
|
cd->flTimeStepSound = pmove->flTimeStepSound;
|
|
cd->flDuckTime = pmove->flDuckTime;
|
|
cd->flSwimTime = (int)pmove->flSwimTime;
|
|
cd->waterjumptime = (int)pmove->waterjumptime;
|
|
cd->watertype = pmove->watertype;
|
|
cd->waterlevel = pmove->waterlevel;
|
|
cd->maxspeed = pmove->clientmaxspeed;
|
|
cd->deadflag = pmove->deadflag;
|
|
VectorCopy( pmove->velocity, cd->velocity );
|
|
VectorCopy( pmove->view_ofs, cd->view_ofs );
|
|
VectorCopy( pmove->origin, ps->origin );
|
|
VectorCopy( pmove->angles, ps->angles );
|
|
VectorCopy( pmove->basevelocity, ps->basevelocity );
|
|
VectorCopy( pmove->punchangle, cd->punchangle );
|
|
ps->oldbuttons = pmove->oldbuttons;
|
|
ps->friction = pmove->friction;
|
|
ps->movetype = pmove->movetype;
|
|
ps->onground = pmove->onground;
|
|
ps->effects = pmove->effects;
|
|
ps->usehull = pmove->usehull;
|
|
ps->iStepLeft = pmove->iStepLeft;
|
|
ps->flFallVelocity = pmove->flFallVelocity;
|
|
cd->iuser1 = pmove->iuser1;
|
|
cd->iuser2 = pmove->iuser2;
|
|
cd->iuser3 = pmove->iuser3;
|
|
cd->iuser4 = pmove->iuser4;
|
|
cd->fuser1 = pmove->fuser1;
|
|
cd->fuser2 = pmove->fuser2;
|
|
cd->fuser3 = pmove->fuser3;
|
|
cd->fuser4 = pmove->fuser4;
|
|
VectorCopy( pmove->vuser1, cd->vuser1 );
|
|
VectorCopy( pmove->vuser2, cd->vuser2 );
|
|
VectorCopy( pmove->vuser3, cd->vuser3 );
|
|
VectorCopy( pmove->vuser4, cd->vuser4 );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CL_RunUsercmd
|
|
|
|
Runs prediction code for user cmd
|
|
=================
|
|
*/
|
|
void CL_RunUsercmd( local_state_t *from, local_state_t *to, usercmd_t *u, qboolean runfuncs, double *time, unsigned int random_seed )
|
|
{
|
|
usercmd_t cmd;
|
|
local_state_t temp;
|
|
usercmd_t split;
|
|
|
|
memset( &temp, 0, sizeof( temp ));
|
|
|
|
if( u->msec > 50 )
|
|
{
|
|
split = *u;
|
|
split.msec /= 2;
|
|
CL_RunUsercmd( from, &temp, &split, runfuncs, time, random_seed );
|
|
split.impulse = split.weaponselect = 0;
|
|
CL_RunUsercmd( &temp, to, &split, runfuncs, time, random_seed );
|
|
return;
|
|
}
|
|
|
|
cmd = *u; // deal with local copy
|
|
*to = *from;
|
|
|
|
if( CL_IsPredicted( ))
|
|
{
|
|
// setup playermove state
|
|
CL_SetupPMove( clgame.pmove, from, &cmd, runfuncs, *time );
|
|
|
|
// motor!
|
|
clgame.dllFuncs.pfnPlayerMove( clgame.pmove, false );
|
|
|
|
// copy results back to client
|
|
CL_FinishPMove( clgame.pmove, to );
|
|
|
|
if( clgame.pmove->onground > 0 && clgame.pmove->onground < clgame.pmove->numphysent )
|
|
cl.local.lastground = clgame.pmove->physents[clgame.pmove->onground].info;
|
|
else cl.local.lastground = clgame.pmove->onground; // world(0) or in air(-1)
|
|
}
|
|
|
|
clgame.dllFuncs.pfnPostRunCmd( from, to, &cmd, runfuncs, *time, random_seed );
|
|
|
|
*time += (double)cmd.msec / 1000.0;
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
CL_MoveSpectatorCamera
|
|
|
|
spectator movement code
|
|
=================
|
|
*/
|
|
void CL_MoveSpectatorCamera( void )
|
|
{
|
|
double time = cl.time;
|
|
|
|
if( !cls.spectator )
|
|
return;
|
|
|
|
CL_SetUpPlayerPrediction( false, true );
|
|
CL_SetSolidPlayers( cl.playernum );
|
|
CL_RunUsercmd( &cls.spectator_state, &cls.spectator_state, cl.cmd, true, &time, (uint)( time * 100.0 ));
|
|
|
|
VectorCopy( cls.spectator_state.client.velocity, cl.simvel );
|
|
VectorCopy( cls.spectator_state.client.origin, cl.simorg );
|
|
VectorCopy( cls.spectator_state.client.punchangle, cl.punchangle );
|
|
VectorCopy( cls.spectator_state.client.view_ofs, cl.viewheight );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CL_PredictMovement
|
|
|
|
Sets cl.predicted.origin and cl.predicted.angles
|
|
=================
|
|
*/
|
|
void CL_PredictMovement( qboolean repredicting )
|
|
{
|
|
runcmd_t *to_cmd, *from_cmd;
|
|
local_state_t *from = NULL, *to = NULL;
|
|
int current_command;
|
|
int current_command_mod;
|
|
frame_t *frame = NULL;
|
|
int i, stoppoint;
|
|
qboolean runfuncs;
|
|
double f = 1.0;
|
|
cl_entity_t *ent;
|
|
double time;
|
|
|
|
if( cls.state != ca_active || cls.spectator )
|
|
return;
|
|
|
|
if( cls.demoplayback && !repredicting )
|
|
CL_DemoInterpolateAngles();
|
|
|
|
CL_SetUpPlayerPrediction( false, false );
|
|
|
|
if( cls.state != ca_active || !cl.validsequence )
|
|
return;
|
|
|
|
if(( cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged ) >= CL_UPDATE_MASK )
|
|
return;
|
|
|
|
// this is the last frame received from the server
|
|
frame = &cl.frames[cl.parsecountmod];
|
|
|
|
if( !CL_IsPredicted( ))
|
|
{
|
|
VectorCopy( frame->clientdata.velocity, cl.simvel );
|
|
VectorCopy( frame->clientdata.origin, cl.simorg );
|
|
VectorCopy( frame->clientdata.punchangle, cl.punchangle );
|
|
VectorCopy( frame->clientdata.view_ofs, cl.viewheight );
|
|
cl.local.usehull = frame->playerstate[cl.playernum].usehull;
|
|
cl.local.waterlevel = frame->clientdata.waterlevel;
|
|
|
|
if( FBitSet( frame->clientdata.flags, FL_ONGROUND ))
|
|
cl.local.onground = frame->playerstate[cl.playernum].onground;
|
|
else cl.local.onground = -1;
|
|
}
|
|
|
|
from = &cl.predicted_frames[cl.parsecountmod];
|
|
from_cmd = &cl.commands[cls.netchan.incoming_acknowledged & CL_UPDATE_MASK];
|
|
memcpy( from->weapondata, frame->weapondata, sizeof( from->weapondata ));
|
|
from->playerstate = frame->playerstate[cl.playernum];
|
|
from->client = frame->clientdata;
|
|
if( !frame->valid ) return;
|
|
|
|
time = frame->time;
|
|
stoppoint = ( repredicting ) ? 0 : 1;
|
|
cl.local.repredicting = repredicting;
|
|
cl.local.onground = -1;
|
|
|
|
// predict forward until cl.time <= to->senttime
|
|
CL_PushPMStates();
|
|
CL_SetSolidPlayers( cl.playernum );
|
|
|
|
for( i = 1; i < CL_UPDATE_MASK && cls.netchan.incoming_acknowledged + i < cls.netchan.outgoing_sequence + stoppoint; i++ )
|
|
{
|
|
current_command = cls.netchan.incoming_acknowledged + i;
|
|
current_command_mod = current_command & CL_UPDATE_MASK;
|
|
|
|
to = &cl.predicted_frames[(cl.parsecountmod + i) & CL_UPDATE_MASK];
|
|
to_cmd = &cl.commands[current_command_mod];
|
|
runfuncs = ( !repredicting && !to_cmd->processedfuncs );
|
|
|
|
CL_RunUsercmd( from, to, &to_cmd->cmd, runfuncs, &time, current_command );
|
|
VectorCopy( to->playerstate.origin, cl.local.predicted_origins[current_command_mod] );
|
|
to_cmd->processedfuncs = true;
|
|
|
|
if( to_cmd->senttime >= host.realtime )
|
|
break;
|
|
|
|
// now interpolate some fraction of the final frame
|
|
if( to_cmd->senttime != from_cmd->senttime )
|
|
f = (host.realtime - from_cmd->senttime) / (to_cmd->senttime - from_cmd->senttime) * 0.1;
|
|
|
|
from = to;
|
|
from_cmd = to_cmd;
|
|
}
|
|
|
|
CL_PopPMStates();
|
|
|
|
if(( i == CL_UPDATE_MASK ) || ( !to && !repredicting ))
|
|
{
|
|
cl.local.repredicting = false;
|
|
return; // net hasn't deliver packets in a long time...
|
|
}
|
|
|
|
if( !to )
|
|
{
|
|
to = from;
|
|
to_cmd = from_cmd;
|
|
}
|
|
|
|
if( !CL_IsPredicted( ))
|
|
{
|
|
// keep onground actual
|
|
if( FBitSet( frame->clientdata.flags, FL_ONGROUND ))
|
|
cl.local.onground = frame->playerstate[cl.playernum].onground;
|
|
else cl.local.onground = -1;
|
|
|
|
if( !repredicting || !CVAR_TO_BOOL( cl_lw ))
|
|
cl.local.viewmodel = to->client.viewmodel;
|
|
cl.local.repredicting = false;
|
|
cl.local.moving = false;
|
|
return;
|
|
}
|
|
|
|
f = bound( 0.0f, f, 1.0f );
|
|
f = 0.0; // FIXME: make work, do revision
|
|
|
|
if( CL_PlayerTeleported( from, to ))
|
|
{
|
|
VectorCopy( to->client.velocity, cl.simvel );
|
|
VectorCopy( to->playerstate.origin, cl.simorg );
|
|
VectorCopy( to->client.punchangle, cl.punchangle );
|
|
VectorCopy( to->client.view_ofs, cl.viewheight );
|
|
}
|
|
else
|
|
{
|
|
VectorLerp( from->playerstate.origin, f, to->playerstate.origin, cl.simorg );
|
|
VectorLerp( from->client.velocity, f, to->client.velocity, cl.simvel );
|
|
VectorLerp( from->client.punchangle, f, to->client.punchangle, cl.punchangle );
|
|
|
|
if( from->playerstate.usehull == to->playerstate.usehull )
|
|
VectorLerp( from->client.view_ofs, f, to->client.view_ofs, cl.viewheight );
|
|
else VectorCopy( to->client.view_ofs, cl.viewheight );
|
|
}
|
|
|
|
cl.local.waterlevel = to->client.waterlevel;
|
|
cl.local.usehull = to->playerstate.usehull;
|
|
if( !repredicting || !CVAR_TO_BOOL( cl_lw ))
|
|
cl.local.viewmodel = to->client.viewmodel;
|
|
|
|
if( FBitSet( to->client.flags, FL_ONGROUND ))
|
|
{
|
|
ent = CL_GetEntityByIndex( cl.local.lastground );
|
|
cl.local.onground = cl.local.lastground;
|
|
cl.local.moving = false;
|
|
|
|
if( ent )
|
|
{
|
|
vec3_t delta;
|
|
|
|
delta[0] = ent->curstate.origin[0] - ent->prevstate.origin[0];
|
|
delta[1] = ent->curstate.origin[1] - ent->prevstate.origin[1];
|
|
delta[2] = 0.0f;
|
|
|
|
if( VectorLength( delta ) > 0.0f )
|
|
{
|
|
cls.correction_time = 0;
|
|
cl.local.moving = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cl.local.onground = -1;
|
|
cl.local.moving = 0;
|
|
}
|
|
|
|
if( cls.correction_time > 0 && !cl_nosmooth->value && cl_smoothtime->value )
|
|
{
|
|
vec3_t delta;
|
|
float frac;
|
|
|
|
// only decay timer once per frame
|
|
if( !repredicting )
|
|
cls.correction_time -= host.frametime;
|
|
|
|
// Make sure smoothtime is postive
|
|
if( cl_smoothtime->value <= 0.0f )
|
|
Cvar_DirectSet( cl_smoothtime, "0.1" );
|
|
|
|
// Clamp from 0 to cl_smoothtime.value
|
|
cls.correction_time = bound( 0.0, cls.correction_time, cl_smoothtime->value );
|
|
|
|
// Compute backward interpolation fraction along full correction
|
|
frac = 1.0f - cls.correction_time / cl_smoothtime->value;
|
|
|
|
// Determine how much error we still have to make up for
|
|
VectorSubtract( cl.simorg, cl.local.lastorigin, delta );
|
|
|
|
// Scale the error by the backlerp fraction
|
|
VectorScale( delta, frac, delta );
|
|
|
|
// Go some fraction of the way
|
|
// FIXME, Probably can't do this any more
|
|
VectorAdd( cl.local.lastorigin, delta, cl.simorg );
|
|
}
|
|
|
|
VectorCopy( cl.simorg, cl.local.lastorigin );
|
|
cl.local.repredicting = false;
|
|
}
|