xash3d-fwgs/engine/server/sv_pmove.c

1146 lines
30 KiB
C

/*
sv_pmove.c - server-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 "server.h"
#include "const.h"
#include "pm_local.h"
#include "event_flags.h"
#include "studio.h"
static qboolean has_update = false;
void SV_ClearPhysEnts( void )
{
svgame.pmove->numtouch = 0;
svgame.pmove->numvisent = 0;
svgame.pmove->nummoveent = 0;
svgame.pmove->numphysent = 0;
}
qboolean SV_PlayerIsFrozen( edict_t *pClient )
{
if( sv_background_freeze.value && sv.background )
return true;
if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ))
return false;
if( FBitSet( pClient->v.flags, FL_FROZEN ))
return true;
return false;
}
void SV_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( svgame.physFuncs.ClipPMoveToEntity != NULL )
{
// do custom sweep test
svgame.physFuncs.ClipPMoveToEntity( pe, start, mins, maxs, end, tr );
}
else
{
// function is missed, so we didn't hit anything
tr->allsolid = false;
}
}
qboolean SV_CopyEdictToPhysEnt( physent_t *pe, edict_t *ed )
{
model_t *mod = SV_ModelHandle( ed->v.modelindex );
if( !mod ) return false;
pe->player = false;
pe->info = NUM_FOR_EDICT( ed );
VectorCopy( ed->v.origin, pe->origin );
VectorCopy( ed->v.angles, pe->angles );
if( FBitSet( ed->v.flags, FL_CLIENT ))
{
// client
SV_GetTrueOrigin( sv.current_client, pe->info, pe->origin );
if( FBitSet( ed->v.flags, FL_FAKECLIENT )) // fakeclients have client flag too
{
// bot
Q_strncpy( pe->name, "bot", sizeof( pe->name ));
}
else
{
Q_strncpy( pe->name, "player", sizeof( pe->name ));
}
pe->player = pe->info;
}
else
{
// otherwise copy the classname
Q_strncpy( pe->name, STRING( ed->v.classname ), sizeof( pe->name ));
}
pe->model = pe->studiomodel = NULL;
switch( ed->v.solid )
{
case SOLID_NOT:
case SOLID_BSP:
pe->model = mod;
VectorClear( pe->mins );
VectorClear( pe->maxs );
break;
case SOLID_BBOX:
if( mod && mod->type == mod_studio && mod->flags & STUDIO_TRACE_HITBOX )
pe->studiomodel = mod;
VectorCopy( ed->v.mins, pe->mins );
VectorCopy( ed->v.maxs, pe->maxs );
break;
case SOLID_CUSTOM:
pe->model = (mod->type == mod_brush) ? mod : NULL;
pe->studiomodel = (mod->type == mod_studio) ? mod : NULL;
VectorCopy( ed->v.mins, pe->mins );
VectorCopy( ed->v.maxs, pe->maxs );
break;
default:
pe->studiomodel = (mod->type == mod_studio) ? mod : NULL;
VectorCopy( ed->v.mins, pe->mins );
VectorCopy( ed->v.maxs, pe->maxs );
break;
}
pe->solid = ed->v.solid;
pe->rendermode = ed->v.rendermode;
pe->skin = ed->v.skin;
pe->frame = ed->v.frame;
pe->sequence = ed->v.sequence;
memcpy( &pe->controller[0], &ed->v.controller[0], 4 * sizeof( byte ));
memcpy( &pe->blending[0], &ed->v.blending[0], 2 * sizeof( byte ));
pe->movetype = ed->v.movetype;
pe->takedamage = ed->v.takedamage;
pe->team = ed->v.team;
pe->classnumber = ed->v.playerclass;
pe->blooddecal = 0; // unused in GoldSrc
// for mods
pe->iuser1 = ed->v.iuser1;
pe->iuser2 = ed->v.iuser2;
pe->iuser3 = ed->v.iuser3;
pe->iuser4 = ed->v.iuser4;
pe->fuser1 = ed->v.fuser1;
pe->fuser2 = ed->v.fuser2;
pe->fuser3 = ed->v.fuser3;
pe->fuser4 = ed->v.fuser4;
VectorCopy( ed->v.vuser1, pe->vuser1 );
VectorCopy( ed->v.vuser2, pe->vuser2 );
VectorCopy( ed->v.vuser3, pe->vuser3 );
VectorCopy( ed->v.vuser4, pe->vuser4 );
return true;
}
qboolean SV_ShouldUnlagForPlayer( sv_client_t *cl )
{
// can't unlag in singleplayer
if( svs.maxclients <= 1 )
return false;
// unlag disabled globally
if( !svgame.dllFuncs.pfnAllowLagCompensation() || !sv_unlag.value )
return false;
if( !FBitSet( cl->flags, FCL_LAG_COMPENSATION ))
return false;
// player not ready
if( cl->state != cs_spawned )
return false;
return true;
}
void SV_GetTrueOrigin( sv_client_t *cl, int edictnum, vec3_t origin )
{
if( !SV_ShouldUnlagForPlayer( cl ))
return;
if( edictnum < 1 || edictnum > svs.maxclients )
return;
if( svgame.interp[edictnum-1].active && svgame.interp[edictnum-1].moving )
VectorCopy( svgame.interp[edictnum-1].oldpos, origin );
}
void SV_GetTrueMinMax( sv_client_t *cl, int edictnum, vec3_t mins, vec3_t maxs )
{
if( !SV_ShouldUnlagForPlayer( cl ))
return;
if( edictnum < 1 || edictnum > svs.maxclients )
return;
if( svgame.interp[edictnum-1].active && svgame.interp[edictnum-1].moving )
{
VectorCopy( svgame.interp[edictnum-1].mins, mins );
VectorCopy( svgame.interp[edictnum-1].maxs, maxs );
}
}
/*
====================
SV_AddLinksToPmove
collect solid entities
====================
*/
void SV_AddLinksToPmove( areanode_t *node, const vec3_t pmove_mins, const vec3_t pmove_maxs )
{
link_t *l, *next;
edict_t *check, *pl;
vec3_t mins, maxs;
physent_t *pe;
pl = EDICT_NUM( svgame.pmove->player_index + 1 );
Assert( SV_IsValidEdict( pl ));
// touch linked edicts
for( l = node->solid_edicts.next; l != &node->solid_edicts; l = next )
{
next = l->next;
check = EDICT_FROM_AREA( l );
if( check->v.groupinfo != 0 )
{
if( svs.groupop == GROUP_OP_AND && !FBitSet( check->v.groupinfo, pl->v.groupinfo ))
continue;
if( svs.groupop == GROUP_OP_NAND && FBitSet( check->v.groupinfo, pl->v.groupinfo ))
continue;
}
if( check->v.owner == pl || check->v.solid == SOLID_TRIGGER )
continue; // player or player's own missile
if( svgame.pmove->numvisent < MAX_PHYSENTS )
{
pe = &svgame.pmove->visents[svgame.pmove->numvisent];
if( SV_CopyEdictToPhysEnt( pe, check ))
svgame.pmove->numvisent++;
}
if( check->v.solid == SOLID_NOT && ( check->v.skin == CONTENTS_NONE || check->v.modelindex == 0 ))
continue;
// ignore monsterclip brushes
if( FBitSet( check->v.flags, FL_MONSTERCLIP ) && check->v.solid == SOLID_BSP )
continue;
if( check == pl ) continue; // himself
// nehahra collision flags
if( check->v.movetype != MOVETYPE_PUSH )
{
if(( FBitSet( check->v.flags, FL_CLIENT|FL_FAKECLIENT ) && check->v.health <= 0.0f ) || check->v.deadflag == DEAD_DEAD )
continue; // dead body
}
if( VectorIsNull( check->v.size ))
continue;
VectorCopy( check->v.absmin, mins );
VectorCopy( check->v.absmax, maxs );
if( FBitSet( check->v.flags, FL_CLIENT ) && !FBitSet( check->v.flags, FL_FAKECLIENT ))
{
if( sv.current_client )
{
// trying to get interpolated values
SV_GetTrueMinMax( sv.current_client, NUM_FOR_EDICT( check ), mins, maxs );
}
}
if( !BoundsIntersect( pmove_mins, pmove_maxs, mins, maxs ))
continue;
if( svgame.pmove->numphysent < MAX_PHYSENTS )
{
pe = &svgame.pmove->physents[svgame.pmove->numphysent];
if( SV_CopyEdictToPhysEnt( pe, check ))
svgame.pmove->numphysent++;
}
}
// recurse down both sides
if( node->axis == -1 ) return;
if( pmove_maxs[node->axis] > node->dist )
SV_AddLinksToPmove( node->children[0], pmove_mins, pmove_maxs );
if( pmove_mins[node->axis] < node->dist )
SV_AddLinksToPmove( node->children[1], pmove_mins, pmove_maxs );
}
/*
====================
SV_AddLaddersToPmove
====================
*/
void SV_AddLaddersToPmove( areanode_t *node, const vec3_t pmove_mins, const vec3_t pmove_maxs )
{
link_t *l, *next;
edict_t *check;
model_t *mod;
physent_t *pe;
// get ladder edicts
for( l = node->solid_edicts.next; l != &node->solid_edicts; l = next )
{
next = l->next;
check = EDICT_FROM_AREA( l );
if( check->v.solid != SOLID_NOT || check->v.skin != CONTENTS_LADDER )
continue;
mod = SV_ModelHandle( check->v.modelindex );
// only brushes can have special contents
if( !mod || mod->type != mod_brush )
continue;
if( !BoundsIntersect( pmove_mins, pmove_maxs, check->v.absmin, check->v.absmax ))
continue;
if( svgame.pmove->nummoveent == MAX_MOVEENTS )
return;
pe = &svgame.pmove->moveents[svgame.pmove->nummoveent];
if( SV_CopyEdictToPhysEnt( pe, check ))
svgame.pmove->nummoveent++;
}
// recurse down both sides
if( node->axis == -1 ) return;
if( pmove_maxs[node->axis] > node->dist )
SV_AddLaddersToPmove( node->children[0], pmove_mins, pmove_maxs );
if( pmove_mins[node->axis] < node->dist )
SV_AddLaddersToPmove( node->children[1], pmove_mins, pmove_maxs );
}
static void GAME_EXPORT pfnParticle( const float *origin, int color, float life, int zpos, int zvel )
{
int v;
if( !origin )
{
Con_Reportf( S_ERROR "SV_StartParticle: NULL origin. Ignored\n" );
return;
}
MSG_WriteByte( &sv.reliable_datagram, svc_particle );
MSG_WriteVec3Coord( &sv.reliable_datagram, origin );
MSG_WriteChar( &sv.reliable_datagram, 0 ); // no x-vel
MSG_WriteChar( &sv.reliable_datagram, 0 ); // no y-vel
v = bound( -128, (zpos * zvel) * 16.0f, 127 );
MSG_WriteChar( &sv.reliable_datagram, v ); // write z-vel
MSG_WriteByte( &sv.reliable_datagram, 1 );
MSG_WriteByte( &sv.reliable_datagram, color );
MSG_WriteByte( &sv.reliable_datagram, bound( 0, life * 8, 255 ));
}
int SV_TestLine( const vec3_t start, const vec3_t end, int flags )
{
return PM_TestLineExt( svgame.pmove, svgame.pmove->physents, svgame.pmove->numphysent, start, end, flags );
}
static int GAME_EXPORT pfnTestPlayerPosition( float *pos, pmtrace_t *ptrace )
{
return PM_TestPlayerPosition( svgame.pmove, pos, ptrace, NULL );
}
static void GAME_EXPORT pfnStuckTouch( int hitent, pmtrace_t *tr )
{
int i;
for( i = 0; i < svgame.pmove->numtouch; i++ )
{
if( svgame.pmove->touchindex[i].ent == hitent )
return;
}
if( svgame.pmove->numtouch >= MAX_PHYSENTS )
return;
VectorCopy( svgame.pmove->velocity, tr->deltavelocity );
tr->ent = hitent;
svgame.pmove->touchindex[svgame.pmove->numtouch++] = *tr;
}
static int GAME_EXPORT pfnPointContents( float *p, int *truecontents )
{
int cont, truecont;
truecont = cont = PM_PointContents( svgame.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( svgame.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( svgame.pmove, start, end, traceFlags, svgame.pmove->numphysent, svgame.pmove->physents, ignore_pe, NULL );
}
static pmtrace_t *pfnTraceLine( float *start, float *end, int flags, int usehull, int ignore_pe )
{
static pmtrace_t tr;
int old_usehull;
old_usehull = svgame.pmove->usehull;
svgame.pmove->usehull = usehull;
switch( flags )
{
case PM_TRACELINE_PHYSENTSONLY:
tr = PM_PlayerTraceExt( svgame.pmove, start, end, 0, svgame.pmove->numphysent, svgame.pmove->physents, ignore_pe, NULL );
break;
case PM_TRACELINE_ANYVISIBLE:
tr = PM_PlayerTraceExt( svgame.pmove, start, end, 0, svgame.pmove->numvisent, svgame.pmove->visents, ignore_pe, NULL );
break;
}
svgame.pmove->usehull = old_usehull;
return &tr;
}
static hull_t *pfnHullForBsp( physent_t *pe, float *offset )
{
return PM_HullForBsp( pe, svgame.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 = svgame.pmove->usehull;
svgame.pmove->usehull = 2;
hull = PM_HullForBsp( pe, svgame.pmove, offset );
svgame.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 >= svgame.pmove->numphysent )
return NULL; // bad ground
pe = &svgame.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 )
{
edict_t *ent;
ent = EDICT_NUM( svgame.pmove->player_index + 1 );
if( !SV_IsValidEdict( ent )) return;
SV_StartSound( ent, channel, sample, volume, attenuation, fFlags|SND_FILTER_CLIENT, pitch );
}
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 )
{
edict_t *ent;
ent = EDICT_NUM( clientindex + 1 );
if( !SV_IsValidEdict( ent )) return;
if( Host_IsDedicated() )
flags |= FEV_NOTHOST; // no local clients for dedicated server
SV_PlaybackEventFull( flags, ent, 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( svgame.pmove, start, end, traceFlags, svgame.pmove->numphysent, svgame.pmove->physents, -1, pmFilter );
}
static int GAME_EXPORT pfnTestPlayerPositionEx( float *pos, pmtrace_t *ptrace, pfnIgnore pmFilter )
{
return PM_TestPlayerPosition( svgame.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 = svgame.pmove->usehull;
svgame.pmove->usehull = usehull;
switch( flags )
{
case PM_TRACELINE_PHYSENTSONLY:
tr = PM_PlayerTraceExt( svgame.pmove, start, end, 0, svgame.pmove->numphysent, svgame.pmove->physents, -1, pmFilter );
break;
case PM_TRACELINE_ANYVISIBLE:
tr = PM_PlayerTraceExt( svgame.pmove, start, end, 0, svgame.pmove->numvisent, svgame.pmove->visents, -1, pmFilter );
break;
}
svgame.pmove->usehull = old_usehull;
return &tr;
}
static struct msurface_s *pfnTraceSurface( int ground, float *vstart, float *vend )
{
physent_t *pe;
if( ground < 0 || ground >= svgame.pmove->numphysent )
return NULL; // bad ground
pe = &svgame.pmove->physents[ground];
return PM_TraceSurface( pe, vstart, vend );
}
/*
===============
SV_InitClientMove
===============
*/
void SV_InitClientMove( void )
{
int i;
Pmove_Init ();
svgame.pmove->server = true;
svgame.pmove->movevars = &svgame.movevars;
svgame.pmove->runfuncs = false;
// enumerate client hulls
for( i = 0; i < MAX_MAP_HULLS; i++ )
{
if( svgame.dllFuncs.pfnGetHullBounds( i, host.player_mins[i], host.player_maxs[i] ))
Con_Reportf( "SV: 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( svgame.pmove->player_mins, host.player_mins, sizeof( host.player_mins ));
memcpy( svgame.pmove->player_maxs, host.player_maxs, sizeof( host.player_maxs ));
// common utilities
svgame.pmove->PM_Info_ValueForKey = Info_ValueForKey;
svgame.pmove->PM_Particle = pfnParticle;
svgame.pmove->PM_TestPlayerPosition = pfnTestPlayerPosition;
svgame.pmove->Con_NPrintf = Con_NPrintf;
svgame.pmove->Con_DPrintf = Con_DPrintf;
svgame.pmove->Con_Printf = Con_Printf;
svgame.pmove->Sys_FloatTime = Sys_DoubleTime;
svgame.pmove->PM_StuckTouch = pfnStuckTouch;
svgame.pmove->PM_PointContents = pfnPointContents;
svgame.pmove->PM_TruePointContents = pfnTruePointContents;
svgame.pmove->PM_HullPointContents = pfnHullPointContents;
svgame.pmove->PM_PlayerTrace = pfnPlayerTrace;
svgame.pmove->PM_TraceLine = pfnTraceLine;
svgame.pmove->RandomLong = COM_RandomLong;
svgame.pmove->RandomFloat = COM_RandomFloat;
svgame.pmove->PM_GetModelType = pfnGetModelType;
svgame.pmove->PM_GetModelBounds = pfnGetModelBounds;
svgame.pmove->PM_HullForBsp = (void*)pfnHullForBsp;
svgame.pmove->PM_TraceModel = pfnTraceModel;
svgame.pmove->COM_FileSize = COM_FileSize;
svgame.pmove->COM_LoadFile = COM_LoadFile;
svgame.pmove->COM_FreeFile = COM_FreeFile;
svgame.pmove->memfgets = COM_MemFgets;
svgame.pmove->PM_PlaySound = pfnPlaySound;
svgame.pmove->PM_TraceTexture = pfnTraceTexture;
svgame.pmove->PM_PlaybackEventFull = pfnPlaybackEventFull;
svgame.pmove->PM_PlayerTraceEx = pfnPlayerTraceEx;
svgame.pmove->PM_TestPlayerPositionEx = pfnTestPlayerPositionEx;
svgame.pmove->PM_TraceLineEx = pfnTraceLineEx;
svgame.pmove->PM_TraceSurface = pfnTraceSurface;
// initalize pmove
svgame.dllFuncs.pfnPM_Init( svgame.pmove );
}
static void PM_CheckMovingGround( edict_t *ent, float frametime )
{
if( svgame.physFuncs.SV_UpdatePlayerBaseVelocity != NULL )
{
svgame.physFuncs.SV_UpdatePlayerBaseVelocity( ent );
}
else
{
SV_UpdateBaseVelocity( ent );
}
if( !FBitSet( ent->v.flags, FL_BASEVELOCITY ))
{
// apply momentum (add in half of the previous frame of velocity first)
VectorMA( ent->v.velocity, 1.0f + (frametime * 0.5f), ent->v.basevelocity, ent->v.velocity );
VectorClear( ent->v.basevelocity );
}
ClearBits( ent->v.flags, FL_BASEVELOCITY );
}
static void SV_SetupPMove( playermove_t *pmove, sv_client_t *cl, usercmd_t *ucmd, const char *physinfo )
{
vec3_t absmin, absmax;
edict_t *clent = cl->edict;
int i;
svgame.globals->frametime = (ucmd->msec * 0.001f);
pmove->player_index = NUM_FOR_EDICT( clent ) - 1;
pmove->multiplayer = (svs.maxclients > 1) ? true : false;
pmove->time = (float)(cl->timebase * 1000.0);
VectorCopy( clent->v.origin, pmove->origin );
VectorCopy( clent->v.v_angle, pmove->angles );
VectorCopy( clent->v.v_angle, pmove->oldangles );
VectorCopy( clent->v.velocity, pmove->velocity );
VectorCopy( clent->v.basevelocity, pmove->basevelocity );
VectorCopy( clent->v.view_ofs, pmove->view_ofs );
VectorCopy( clent->v.movedir, pmove->movedir );
pmove->flDuckTime = clent->v.flDuckTime;
pmove->bInDuck = clent->v.bInDuck;
pmove->usehull = (clent->v.flags & FL_DUCKING) ? 1 : 0; // reset hull
pmove->flTimeStepSound = clent->v.flTimeStepSound;
pmove->iStepLeft = clent->v.iStepLeft;
pmove->flFallVelocity = clent->v.flFallVelocity;
pmove->flSwimTime = clent->v.flSwimTime;
VectorCopy( clent->v.punchangle, pmove->punchangle );
pmove->flSwimTime = clent->v.flSwimTime;
pmove->flNextPrimaryAttack = 0.0f; // not used by PM_ code
pmove->effects = clent->v.effects;
pmove->flags = clent->v.flags;
pmove->gravity = clent->v.gravity;
pmove->friction = clent->v.friction;
pmove->oldbuttons = clent->v.oldbuttons;
pmove->waterjumptime = clent->v.teleport_time;
pmove->dead = (clent->v.health <= 0.0f ) ? true : false;
pmove->deadflag = clent->v.deadflag;
pmove->spectator = 0; // spectator physic all execute on client
pmove->movetype = clent->v.movetype;
if( pmove->multiplayer ) pmove->onground = -1;
pmove->waterlevel = clent->v.waterlevel;
pmove->watertype = clent->v.watertype;
pmove->maxspeed = svgame.movevars.maxspeed;
pmove->clientmaxspeed = clent->v.maxspeed;
pmove->iuser1 = clent->v.iuser1;
pmove->iuser2 = clent->v.iuser2;
pmove->iuser3 = clent->v.iuser3;
pmove->iuser4 = clent->v.iuser4;
pmove->fuser1 = clent->v.fuser1;
pmove->fuser2 = clent->v.fuser2;
pmove->fuser3 = clent->v.fuser3;
pmove->fuser4 = clent->v.fuser4;
VectorCopy( clent->v.vuser1, pmove->vuser1 );
VectorCopy( clent->v.vuser2, pmove->vuser2 );
VectorCopy( clent->v.vuser3, pmove->vuser3 );
VectorCopy( clent->v.vuser4, pmove->vuser4 );
pmove->cmd = *ucmd; // setup current cmds
pmove->runfuncs = true;
Q_strncpy( pmove->physinfo, physinfo, MAX_INFO_STRING );
// setup physents
pmove->numvisent = 0;
pmove->numphysent = 0;
pmove->nummoveent = 0;
for( i = 0; i < 3; i++ )
{
absmin[i] = clent->v.origin[i] - 256.0f;
absmax[i] = clent->v.origin[i] + 256.0f;
}
SV_CopyEdictToPhysEnt( &svgame.pmove->physents[0], &svgame.edicts[0] );
svgame.pmove->visents[0] = svgame.pmove->physents[0];
svgame.pmove->numphysent = 1; // always have world
svgame.pmove->numvisent = 1;
SV_AddLinksToPmove( sv_areanodes, absmin, absmax );
SV_AddLaddersToPmove( sv_areanodes, absmin, absmax );
}
static void SV_FinishPMove( playermove_t *pmove, sv_client_t *cl )
{
edict_t *clent = cl->edict;
clent->v.teleport_time = pmove->waterjumptime;
VectorCopy( pmove->origin, clent->v.origin );
VectorCopy( pmove->view_ofs, clent->v.view_ofs );
VectorCopy( pmove->velocity, clent->v.velocity );
VectorCopy( pmove->basevelocity, clent->v.basevelocity );
VectorCopy( pmove->punchangle, clent->v.punchangle );
VectorCopy( pmove->movedir, clent->v.movedir );
clent->v.flTimeStepSound = pmove->flTimeStepSound;
clent->v.flFallVelocity = pmove->flFallVelocity;
clent->v.oldbuttons = pmove->cmd.buttons;
clent->v.waterlevel = pmove->waterlevel;
clent->v.watertype = pmove->watertype;
clent->v.maxspeed = pmove->clientmaxspeed;
clent->v.flDuckTime = pmove->flDuckTime;
clent->v.flSwimTime = pmove->flSwimTime;
clent->v.iStepLeft = pmove->iStepLeft;
clent->v.movetype = pmove->movetype;
clent->v.friction = pmove->friction;
clent->v.deadflag = pmove->deadflag;
clent->v.effects = pmove->effects;
clent->v.bInDuck = pmove->bInDuck;
clent->v.flags = pmove->flags;
// copy back user variables
clent->v.iuser1 = pmove->iuser1;
clent->v.iuser2 = pmove->iuser2;
clent->v.iuser3 = pmove->iuser3;
clent->v.iuser4 = pmove->iuser4;
clent->v.fuser1 = pmove->fuser1;
clent->v.fuser2 = pmove->fuser2;
clent->v.fuser3 = pmove->fuser3;
clent->v.fuser4 = pmove->fuser4;
VectorCopy( pmove->vuser1, clent->v.vuser1 );
VectorCopy( pmove->vuser2, clent->v.vuser2 );
VectorCopy( pmove->vuser3, clent->v.vuser3 );
VectorCopy( pmove->vuser4, clent->v.vuser4 );
if( pmove->onground == -1 )
{
ClearBits( clent->v.flags, FL_ONGROUND );
}
else if( pmove->onground >= 0 && pmove->onground < pmove->numphysent )
{
SetBits( clent->v.flags, FL_ONGROUND );
clent->v.groundentity = EDICT_NUM( pmove->physents[pmove->onground].info );
}
// angles
// show 1/3 the pitch angle and all the roll angle
if( !clent->v.fixangle )
{
VectorCopy( pmove->angles, clent->v.v_angle );
clent->v.angles[PITCH] = -( clent->v.v_angle[PITCH] / 3.0f );
clent->v.angles[ROLL] = clent->v.v_angle[ROLL];
clent->v.angles[YAW] = clent->v.v_angle[YAW];
}
SV_SetMinMaxSize( clent, pmove->player_mins[pmove->usehull], pmove->player_maxs[pmove->usehull], false );
// all next calls ignore footstep sounds
pmove->runfuncs = false;
}
entity_state_t *SV_FindEntInPack( int index, client_frame_t *frame )
{
entity_state_t *state;
int i;
for( i = 0; i < frame->num_entities; i++ )
{
state = &svs.packet_entities[(frame->first_entity+i)%svs.num_client_entities];
if( state->number == index )
return state;
}
return NULL;
}
qboolean SV_UnlagCheckTeleport( vec3_t old_pos, vec3_t new_pos )
{
int i;
for( i = 0; i < 3; i++ )
{
if( fabs( old_pos[i] - new_pos[i] ) > 64.0f )
return true;
}
return false;
}
void SV_SetupMoveInterpolant( sv_client_t *cl )
{
int i, j, clientnum;
float finalpush, lerp_msec;
float latency, lerpFrac;
client_frame_t *frame, *frame2;
entity_state_t *state, *lerpstate;
vec3_t curpos, newpos;
sv_client_t *check;
sv_interp_t *lerp;
memset( svgame.interp, 0, sizeof( svgame.interp ));
has_update = false;
if( !SV_ShouldUnlagForPlayer( cl ))
return;
has_update = true;
for( i = 0, check = svs.clients; i < svs.maxclients; i++, check++ )
{
if( check->state != cs_spawned || check == cl )
continue;
lerp = &svgame.interp[i];
VectorCopy( check->edict->v.origin, lerp->oldpos );
VectorCopy( check->edict->v.absmin, lerp->mins );
VectorCopy( check->edict->v.absmax, lerp->maxs );
lerp->active = true;
}
latency = Q_min( cl->latency, 1.5f );
if( sv_maxunlag.value != 0.0f )
{
if (sv_maxunlag.value < 0.0f )
Cvar_SetValue( "sv_maxunlag", 0.0f );
latency = Q_min( latency, sv_maxunlag.value );
}
lerp_msec = cl->lastcmd.lerp_msec * 0.001f;
if( lerp_msec > 0.1f ) lerp_msec = 0.1f;
if( lerp_msec < cl->cl_updaterate )
lerp_msec = cl->cl_updaterate;
finalpush = ( host.realtime - latency - lerp_msec ) + sv_unlagpush.value;
if( finalpush > host.realtime ) finalpush = host.realtime; // pushed too much ?
frame = frame2 = NULL;
for( i = 0; i < SV_UPDATE_BACKUP; i++, frame2 = frame )
{
frame = &cl->frames[(cl->netchan.outgoing_sequence - (i + 1)) & SV_UPDATE_MASK];
for( j = 0; j < frame->num_entities; j++ )
{
state = &svs.packet_entities[(frame->first_entity+j)%svs.num_client_entities];
if( state->number < 1 || state->number > svs.maxclients )
continue;
lerp = &svgame.interp[state->number-1];
if( lerp->nointerp ) continue;
if( state->health <= 0 || FBitSet( state->effects, EF_NOINTERP ))
lerp->nointerp = true;
if( lerp->firstframe )
{
if( SV_UnlagCheckTeleport( state->origin, lerp->finalpos ))
lerp->nointerp = true;
}
else lerp->firstframe = true;
VectorCopy( state->origin, lerp->finalpos );
}
if( finalpush > frame->senttime )
break;
}
if( i == SV_UPDATE_BACKUP || finalpush - frame->senttime > 1.0f )
{
memset( svgame.interp, 0, sizeof( svgame.interp ));
has_update = false;
return;
}
if( !frame2 )
{
frame2 = frame;
lerpFrac = 0;
}
else
{
if( frame2->senttime - frame->senttime == 0.0 )
{
lerpFrac = 0;
}
else
{
lerpFrac = (finalpush - frame->senttime) / (frame2->senttime - frame->senttime);
lerpFrac = bound( 0.0f, lerpFrac, 1.0f );
}
}
for( i = 0; i < frame->num_entities; i++ )
{
state = &svs.packet_entities[(frame->first_entity+i)%svs.num_client_entities];
if( state->number < 1 || state->number > svs.maxclients )
continue;
clientnum = state->number - 1;
check = &svs.clients[clientnum];
if( check->state != cs_spawned || check == cl )
continue;
lerp = &svgame.interp[clientnum];
if( !lerp->active || lerp->nointerp )
continue;
lerpstate = SV_FindEntInPack( state->number, frame2 );
if( !lerpstate )
{
VectorCopy( state->origin, curpos );
}
else
{
VectorSubtract( lerpstate->origin, state->origin, newpos );
VectorMA( state->origin, lerpFrac, newpos, curpos );
}
VectorCopy( curpos, lerp->curpos );
VectorCopy( curpos, lerp->newpos );
if( !VectorCompare( curpos, check->edict->v.origin ))
{
VectorCopy( curpos, check->edict->v.origin );
SV_LinkEdict( check->edict, false );
lerp->moving = true;
}
}
}
void SV_RestoreMoveInterpolant( sv_client_t *cl )
{
sv_client_t *check;
sv_interp_t *oldlerp;
int i;
if( !has_update )
{
has_update = true;
return;
}
if( !SV_ShouldUnlagForPlayer( cl ))
return;
for( i = 0, check = svs.clients; i < svs.maxclients; i++, check++ )
{
if( check->state != cs_spawned || check == cl )
continue;
oldlerp = &svgame.interp[i];
if( VectorCompareEpsilon( oldlerp->oldpos, oldlerp->newpos, ON_EPSILON ))
continue; // they didn't actually move.
if( !oldlerp->moving || !oldlerp->active )
continue;
if( VectorCompare( oldlerp->curpos, check->edict->v.origin ))
{
VectorCopy( oldlerp->oldpos, check->edict->v.origin );
SV_LinkEdict( check->edict, false );
}
}
}
/*
===========
SV_RunCmd
===========
*/
void SV_RunCmd( sv_client_t *cl, usercmd_t *ucmd, int random_seed )
{
edict_t *clent, *touch;
double frametime;
int i, oldmsec;
pmtrace_t *pmtrace;
trace_t trace;
vec3_t oldvel;
usercmd_t cmd;
clent = cl->edict;
cmd = *ucmd;
if( cl->ignorecmdtime > host.realtime )
{
cl->cmdtime += ((double)ucmd->msec / 1000.0 );
return;
}
cl->ignorecmdtime = 0.0;
// chop up very long commands
if( cmd.msec > 50 )
{
oldmsec = ucmd->msec;
cmd.msec = oldmsec / 2;
SV_RunCmd( cl, &cmd, random_seed );
cmd.msec = oldmsec / 2;
cmd.impulse = 0;
SV_RunCmd( cl, &cmd, random_seed );
return;
}
if( !FBitSet( cl->flags, FCL_FAKECLIENT ))
SV_SetupMoveInterpolant( cl );
svgame.dllFuncs.pfnCmdStart( cl->edict, ucmd, random_seed );
frametime = ((double)ucmd->msec / 1000.0 );
cl->timebase += frametime;
cl->cmdtime += frametime;
PM_CheckMovingGround( clent, frametime );
VectorCopy( clent->v.v_angle, svgame.pmove->oldangles ); // save oldangles
if( !clent->v.fixangle ) VectorCopy( ucmd->viewangles, clent->v.v_angle );
VectorClear( clent->v.clbasevelocity );
// copy player buttons
clent->v.button = ucmd->buttons;
clent->v.light_level = ucmd->lightlevel;
if( ucmd->impulse ) clent->v.impulse = ucmd->impulse;
if( ucmd->impulse == 204 )
{
// force client.dll update
SV_RefreshUserinfo();
}
svgame.globals->time = cl->timebase;
svgame.dllFuncs.pfnPlayerPreThink( clent );
SV_PlayerRunThink( clent, frametime, cl->timebase );
// If conveyor, or think, set basevelocity, then send to client asap too.
if( !VectorIsNull( clent->v.basevelocity ))
VectorCopy( clent->v.basevelocity, clent->v.clbasevelocity );
// setup playermove state
SV_SetupPMove( svgame.pmove, cl, ucmd, cl->physinfo );
// motor!
svgame.dllFuncs.pfnPM_Move( svgame.pmove, true );
// copy results back to client
SV_FinishPMove( svgame.pmove, cl );
if( clent->v.solid != SOLID_NOT && !sv.playersonly )
{
if( svgame.physFuncs.PM_PlayerTouch != NULL )
{
// run custom impact function
svgame.physFuncs.PM_PlayerTouch( svgame.pmove, clent );
}
else
{
// link into place and touch triggers
SV_LinkEdict( clent, true );
VectorCopy( clent->v.velocity, oldvel ); // save velocity
// touch other objects
for( i = 0; i < svgame.pmove->numtouch; i++ )
{
pmtrace = &svgame.pmove->touchindex[i];
touch = EDICT_NUM( svgame.pmove->physents[pmtrace->ent].info );
VectorCopy( pmtrace->deltavelocity, clent->v.velocity );
PM_ConvertTrace( &trace, pmtrace, touch );
SV_Impact( touch, clent, &trace );
}
// restore velocity
VectorCopy( oldvel, clent->v.velocity );
}
}
svgame.pmove->numtouch = 0;
svgame.globals->time = cl->timebase;
svgame.globals->frametime = frametime;
// run post-think
svgame.dllFuncs.pfnPlayerPostThink( clent );
svgame.dllFuncs.pfnCmdEnd( clent );
if( !FBitSet( cl->flags, FCL_FAKECLIENT ))
{
SV_RestoreMoveInterpolant( cl );
}
}