1827 lines
51 KiB
C++
1827 lines
51 KiB
C++
// Copyright (C) 2007 Id Software, Inc.
|
|
//
|
|
|
|
#include "precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#if defined( _DEBUG ) && !defined( ID_REDIRECT_NEWDELETE )
|
|
#define new DEBUG_NEW
|
|
#undef THIS_FILE
|
|
static char THIS_FILE[] = __FILE__;
|
|
#endif
|
|
|
|
#include "AntiLag.h"
|
|
#include "Player.h"
|
|
|
|
idCVar g_drawAntiLag( "g_drawAntiLag", "0", CVAR_BOOL | CVAR_GAME, "Visualizes the anti-lag point generation" );
|
|
idCVar g_drawAntiLagHits( "g_drawAntiLagHits", "0", CVAR_BOOL | CVAR_GAME, "Draws information when anti-lag generates a hit" );
|
|
|
|
#if defined( SD_PUBLIC_BUILD )
|
|
#define ANTILAG_CVAR_FLAGS ( CVAR_GAME | CVAR_SERVERINFO | CVAR_ROM )
|
|
#else
|
|
#define ANTILAG_CVAR_FLAGS ( CVAR_GAME | CVAR_SERVERINFO )
|
|
#endif
|
|
|
|
idCVar si_antiLag( "si_antiLag", "1", CVAR_BOOL | ANTILAG_CVAR_FLAGS, "Server does antilag on players" );
|
|
idCVar si_antiLagOnly( "si_antiLagOnly", "0", CVAR_BOOL | ANTILAG_CVAR_FLAGS, "ONLY use antilag" );
|
|
idCVar si_antiLagForgiving( "si_antiLagForgiving", "0", CVAR_INTEGER | ANTILAG_CVAR_FLAGS, "How forgiving the antilag is - the higher, the more forgiving" );
|
|
|
|
#ifdef ANTILAG_LOGGING
|
|
idCVar si_antiLagLog( "si_antiLagLog", "0", CVAR_BOOL | CVAR_GAME, "Server logs antilag debugging information" );
|
|
#endif
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
sdAntiLagSet
|
|
base class for antilag sets
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
/*
|
|
================
|
|
sdAntiLagSet::sdAntiLagSet
|
|
================
|
|
*/
|
|
sdAntiLagSet::sdAntiLagSet() {
|
|
Reset();
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagSet::Reset
|
|
================
|
|
*/
|
|
void sdAntiLagSet::Reset() {
|
|
branchTime = -1;
|
|
bounds.Clear();
|
|
entityBounds.Clear();
|
|
clientsValidFor.Clear();
|
|
antiLagEntity = NULL;
|
|
userCommandOnly = false;
|
|
baseBranchTime = -1;
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagSet::Setup
|
|
================
|
|
*/
|
|
void sdAntiLagSet::Setup( sdAntiLagEntity& _antiLagEntity ) {
|
|
antiLagEntity = &_antiLagEntity;
|
|
branchTime = gameLocal.time;
|
|
|
|
idEntity* entity = antiLagEntity->GetSelf();
|
|
points[ 0 ] = entity->GetPhysics()->GetOrigin();
|
|
velocity = entity->GetPhysics()->GetLinearVelocity();
|
|
entityBounds = entity->GetPhysics()->GetBounds();
|
|
bounds = entityBounds.Translate( points[ 0 ] );
|
|
|
|
clientsValidFor.Clear();
|
|
|
|
onGround = entity->GetPhysics()->HasGroundContacts();
|
|
|
|
timeUpto = branchTime;
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagSet::Setup
|
|
================
|
|
*/
|
|
void sdAntiLagSet::Setup( sdAntiLagEntity& _antiLagEntity, sdAntiLagSet& baseSet ) {
|
|
antiLagEntity = &_antiLagEntity;
|
|
branchTime = gameLocal.time;
|
|
|
|
points[ 0 ] = *baseSet.GetPointForTime( gameLocal.time );
|
|
velocity = baseSet.GetVelocity();
|
|
entityBounds = baseSet.GetEntityBounds();
|
|
bounds = entityBounds.Translate( points[ 0 ] );
|
|
|
|
clientsValidFor.Clear();
|
|
|
|
onGround = baseSet.OnGround();
|
|
|
|
timeUpto = branchTime;
|
|
|
|
baseBranchTime = baseSet.GetBranchTime();
|
|
userCommandOnly = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagSet::DebugDraw
|
|
================
|
|
*/
|
|
void sdAntiLagSet::DebugDraw() {
|
|
if ( !IsValid() ) {
|
|
return;
|
|
}
|
|
if ( gameLocal.msec == 0 ) {
|
|
return;
|
|
}
|
|
|
|
int maxIndex = ( timeUpto - branchTime ) / gameLocal.msec;
|
|
for ( int i = 1; i < maxIndex; i++ ) {
|
|
gameRenderWorld->DebugBounds( colorRed, entityBounds, points[ i ] );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagSet::GetPointForTime
|
|
================
|
|
*/
|
|
const idVec3* sdAntiLagSet::GetPointForTime( int time ) {
|
|
if ( gameLocal.msec == 0 ) {
|
|
return NULL;
|
|
}
|
|
|
|
int index = ( time - branchTime ) / gameLocal.msec;
|
|
if ( index < 0 || index >= MAX_ANTILAG_POINTS ) {
|
|
return NULL;
|
|
}
|
|
|
|
// make sure its updated all the way
|
|
int lastTimeUpto = timeUpto;
|
|
while ( time > timeUpto ) {
|
|
Update();
|
|
|
|
// HACK - if Update didn't change the time then something is badly wrong
|
|
// (eg using timescale on a server) so abort, otherwise will get
|
|
// stuck in an infinite loop!
|
|
if ( timeUpto == lastTimeUpto ) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return &points[ index ];
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
sdAntiLagSetGeneric
|
|
generic antilag set
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
/*
|
|
================
|
|
sdAntiLagSetGeneric::Setup
|
|
================
|
|
*/
|
|
void sdAntiLagSetGeneric::Setup( sdAntiLagEntity& _antiLagEntity ) {
|
|
sdAntiLagSet::Setup( _antiLagEntity );
|
|
|
|
idEntity* entity = antiLagEntity->GetSelf();
|
|
|
|
if ( !entity->GetPhysics()->HasGroundContacts() ) {
|
|
acceleration = entity->GetPhysics()->GetGravity();
|
|
} else {
|
|
acceleration.Zero();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagSetGeneric::Setup
|
|
================
|
|
*/
|
|
void sdAntiLagSetGeneric::Setup( sdAntiLagEntity& _antiLagEntity, sdAntiLagSet& baseSet ) {
|
|
sdAntiLagSet::Setup( _antiLagEntity, baseSet );
|
|
|
|
// HACK - totally shouldn't assume this.
|
|
sdAntiLagSetGeneric& genericSet = static_cast< sdAntiLagSetGeneric& >( baseSet );
|
|
|
|
acceleration = genericSet.GetAcceleration();
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagSetGeneric::Update
|
|
================
|
|
*/
|
|
void sdAntiLagSetGeneric::Update() {
|
|
if ( !IsValid() ) {
|
|
return;
|
|
}
|
|
if ( gameLocal.msec == 0 ) {
|
|
return;
|
|
}
|
|
|
|
int newTime = timeUpto + gameLocal.msec;
|
|
int index = ( newTime - branchTime ) / gameLocal.msec;
|
|
if ( index <= 0 || index >= MAX_ANTILAG_POINTS ) {
|
|
return;
|
|
}
|
|
|
|
timeUpto = newTime;
|
|
|
|
// carry on the "prediction"
|
|
float timeDelta = MS2SEC( gameLocal.msec );
|
|
|
|
velocity += acceleration * timeDelta;
|
|
points[ index ] = points[ index - 1 ] + velocity * timeDelta;
|
|
bounds.AddBounds( entityBounds.Translate( points[ index ] ) );
|
|
}
|
|
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
sdAntiLagSetPlayer
|
|
player antilag set
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
/*
|
|
================
|
|
sdAntiLagSetPlayer::Setup
|
|
================
|
|
*/
|
|
void sdAntiLagSetPlayer::Setup( sdAntiLagEntity& _antiLagEntity ) {
|
|
sdAntiLagSet::Setup( _antiLagEntity );
|
|
|
|
idEntity* entity = antiLagEntity->GetSelf();
|
|
assert( entity->IsType( idPlayer::Type ) );
|
|
|
|
idPlayer* playerSelf = entity->Cast< idPlayer >();
|
|
gravity = playerSelf->GetPlayerPhysics().GetGravity();
|
|
idVec3 gravityNormal = gravity;
|
|
gravityNormal.Normalize();
|
|
waterLevel = playerSelf->GetPlayerPhysics().GetWaterLevel();
|
|
|
|
walkSpeedFwd = playerSelf->GetPlayerPhysics().GetWalkSpeedFwd();
|
|
walkSpeedBack = playerSelf->GetPlayerPhysics().GetWalkSpeedBack();
|
|
walkSpeedSide = playerSelf->GetPlayerPhysics().GetWalkSpeedSide();
|
|
|
|
playerSpeed = playerSelf->GetPlayerPhysics().GetCurrentAimSpeed();
|
|
|
|
idAngles viewAngles = playerSelf->GetPlayerPhysics().GetViewAngles();
|
|
viewAngles.ToVectors( &viewForward, NULL, NULL );
|
|
viewRight = gravityNormal.Cross( viewForward );
|
|
viewRight.Normalize();
|
|
|
|
cmdForwardMove = playerSelf->usercmd.forwardmove;
|
|
cmdRightMove = playerSelf->usercmd.rightmove;
|
|
|
|
|
|
if ( OnGround() ) {
|
|
groundNormal.Set( 0.0f, 0.0f, 1.0f );
|
|
} else {
|
|
groundNormal = playerSelf->GetPlayerPhysics().GetGroundNormal();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagSetPlayer::Setup
|
|
================
|
|
*/
|
|
void sdAntiLagSetPlayer::Setup( sdAntiLagEntity& _antiLagEntity, sdAntiLagSet& baseSet ) {
|
|
sdAntiLagSet::Setup( _antiLagEntity, baseSet );
|
|
|
|
// HACK: Totally shouldn't assume this.
|
|
sdAntiLagSetPlayer& playerSet = static_cast< sdAntiLagSetPlayer& >( baseSet );
|
|
|
|
idEntity* entity = antiLagEntity->GetSelf();
|
|
assert( entity->IsType( idPlayer::Type ) );
|
|
|
|
idPlayer* playerSelf = entity->Cast< idPlayer >();
|
|
|
|
gravity = playerSet.GetGravity();
|
|
waterLevel = playerSet.GetWaterLevel();
|
|
|
|
walkSpeedFwd = playerSelf->GetPlayerPhysics().GetWalkSpeedFwd();
|
|
walkSpeedBack = playerSelf->GetPlayerPhysics().GetWalkSpeedBack();
|
|
walkSpeedSide = playerSelf->GetPlayerPhysics().GetWalkSpeedSide();
|
|
|
|
playerSpeed = playerSelf->GetPlayerPhysics().GetCurrentAimSpeed();
|
|
|
|
idVec3 gravityNormal = gravity;
|
|
gravityNormal.Normalize();
|
|
idAngles viewAngles = playerSelf->GetPlayerPhysics().GetViewAngles();
|
|
viewAngles.ToVectors( &viewForward, NULL, NULL );
|
|
viewRight = gravityNormal.Cross( viewForward );
|
|
viewRight.Normalize();
|
|
|
|
cmdForwardMove = playerSelf->usercmd.forwardmove;
|
|
cmdRightMove = playerSelf->usercmd.rightmove;
|
|
|
|
groundNormal = playerSet.GetGroundNormal();
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagSetPlayer::CmdScale
|
|
================
|
|
*/
|
|
float sdAntiLagSetPlayer::CmdScale( int forwardmove, int rightmove, int upmove, bool noVertical ) const {
|
|
int max;
|
|
float total;
|
|
float scale;
|
|
|
|
// since the crouch key doubles as downward movement, ignore downward movement when we're on the ground
|
|
// otherwise crouch speed will be lower than specified
|
|
if ( noVertical ) {
|
|
upmove = 0;
|
|
}
|
|
|
|
max = abs( forwardmove );
|
|
if ( abs( rightmove ) > max ) {
|
|
max = abs( rightmove );
|
|
}
|
|
if ( abs( upmove ) > max ) {
|
|
max = abs( upmove );
|
|
}
|
|
|
|
if ( !max ) {
|
|
return 0.0f;
|
|
}
|
|
|
|
total = idMath::Sqrt( (float) forwardmove * forwardmove + rightmove * rightmove + upmove * upmove );
|
|
scale = (float) playerSpeed * max / ( 127.0f * total );
|
|
|
|
return scale;
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagSetPlayer::CalcDesiredWalkMove
|
|
================
|
|
*/
|
|
idVec2 sdAntiLagSetPlayer::CalcDesiredWalkMove( int forwardmove, int rightmove ) const {
|
|
idVec2 adjustedMove( 0.0f, 0.0f );
|
|
float forwardMoveAdjusted = 0.0f;
|
|
float rightMoveAdjusted = 0.0f;
|
|
|
|
// divide the desired movement into "max" and "min" axes
|
|
// to smoothly (ish) calculate how we're going to be moving
|
|
float maxMoveSpeedLimit;
|
|
idVec2 maxMoveAxis;
|
|
float maxMoveStrength = idMath::Fabs( ( float )forwardmove );
|
|
if ( forwardmove >= 0 ) {
|
|
maxMoveSpeedLimit = walkSpeedFwd;
|
|
maxMoveAxis.Set( 1.0f, 0.0f );
|
|
} else {
|
|
maxMoveSpeedLimit = walkSpeedBack;
|
|
maxMoveAxis.Set( -1.0f, 0.0f );
|
|
}
|
|
|
|
float minMoveSpeedLimit = walkSpeedSide;
|
|
idVec2 minMoveAxis;
|
|
float minMoveStrength = idMath::Fabs( ( float )rightmove );
|
|
if ( rightmove >= 0 ) {
|
|
minMoveAxis.Set( 0.0f, 1.0f );
|
|
} else {
|
|
minMoveAxis.Set( 0.0f, -1.0f );
|
|
}
|
|
|
|
if ( minMoveSpeedLimit > maxMoveSpeedLimit ) {
|
|
Swap( minMoveSpeedLimit, maxMoveSpeedLimit );
|
|
Swap( minMoveAxis, maxMoveAxis );
|
|
Swap( minMoveStrength, maxMoveStrength );
|
|
}
|
|
|
|
float maxMoveSpeed = maxMoveSpeedLimit * maxMoveStrength / 127.0f;
|
|
float minMoveSpeed = minMoveSpeedLimit * minMoveStrength / 127.0f;
|
|
|
|
if ( maxMoveSpeedLimit < idMath::FLT_EPSILON || minMoveSpeedLimit < idMath::FLT_EPSILON ) {
|
|
// do nothing! - no move
|
|
} else {
|
|
float minMoveAdjusted = 0.0f;
|
|
float maxMoveAdjusted = 0.0f;
|
|
|
|
if ( maxMoveSpeed < idMath::FLT_EPSILON && minMoveSpeed < idMath::FLT_EPSILON ) {
|
|
// do nothing! - no move
|
|
} else if ( maxMoveStrength >= minMoveStrength ) {
|
|
// calculate the length of the full-strength move in this direction
|
|
float fullLengthScale = maxMoveSpeedLimit / maxMoveSpeed;
|
|
float fullLength = fullLengthScale * idMath::Sqrt( maxMoveSpeed * maxMoveSpeed + minMoveSpeed * minMoveSpeed );
|
|
|
|
// scale-back to fit the maximum speed we're allowed
|
|
maxMoveAdjusted = maxMoveSpeed * maxMoveSpeedLimit / fullLength;
|
|
minMoveAdjusted = minMoveSpeed * maxMoveSpeedLimit / fullLength;
|
|
} else {
|
|
// calculate the length of the full-strength move in this direction
|
|
float fullLengthScale = minMoveSpeedLimit / minMoveSpeed;
|
|
float fullLength = fullLengthScale * idMath::Sqrt( maxMoveSpeed * maxMoveSpeed + minMoveSpeed * minMoveSpeed );
|
|
|
|
// linearly blend the max total strength based on how close it is to the junction point between
|
|
// this case and the previous case (maxMoveStrength >= minMoveStrength)
|
|
float maxLength = Lerp( minMoveSpeedLimit, maxMoveSpeedLimit, ( maxMoveSpeed * fullLengthScale ) / maxMoveSpeedLimit );
|
|
|
|
// scale-back to fit the maximum speed we're allowed
|
|
maxMoveAdjusted = maxMoveSpeed * maxLength / fullLength;
|
|
minMoveAdjusted = minMoveSpeed * maxLength / fullLength;
|
|
}
|
|
|
|
adjustedMove = maxMoveAdjusted * maxMoveAxis + minMoveAdjusted * minMoveAxis;
|
|
|
|
// scale everything so that the max possible speed equals the playerspeed
|
|
float absoluteMaxSpeed = walkSpeedFwd;
|
|
if ( walkSpeedBack > absoluteMaxSpeed ) {
|
|
absoluteMaxSpeed = walkSpeedBack;
|
|
}
|
|
if ( walkSpeedSide > absoluteMaxSpeed ) {
|
|
absoluteMaxSpeed = walkSpeedSide;
|
|
}
|
|
if ( absoluteMaxSpeed > idMath::FLT_EPSILON ) {
|
|
adjustedMove *= playerSpeed / absoluteMaxSpeed;
|
|
}
|
|
}
|
|
|
|
return adjustedMove;
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagSetPlayer::Accelerate
|
|
================
|
|
*/
|
|
void sdAntiLagSetPlayer::Accelerate( const idVec3 &wishdir, const float wishspeed, const float accel ) {
|
|
// q2 style
|
|
float addspeed, accelspeed, currentspeed;
|
|
|
|
currentspeed = velocity * wishdir;
|
|
addspeed = wishspeed - currentspeed;
|
|
if ( addspeed <= 0.0f ) {
|
|
return;
|
|
}
|
|
accelspeed = accel * MS2SEC( gameLocal.msec ) * wishspeed;
|
|
if ( accelspeed > addspeed ) {
|
|
accelspeed = addspeed;
|
|
}
|
|
|
|
velocity += accelspeed * wishdir;
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagSetPlayer::WalkMove
|
|
================
|
|
*/
|
|
void sdAntiLagSetPlayer::WalkMove() {
|
|
idVec3 wishvel;
|
|
idVec3 wishdir;
|
|
float wishspeed;
|
|
float accelerate;
|
|
|
|
idPlayer* playerSelf = antiLagEntity->GetSelf()->Cast< idPlayer >();
|
|
assert( playerSelf != NULL );
|
|
|
|
Friction();
|
|
|
|
idVec3 gravityNormal = gravity;
|
|
gravityNormal.Normalize();
|
|
|
|
// project moves down to flat plane
|
|
viewForward -= (viewForward * gravityNormal) * gravityNormal;
|
|
viewRight -= (viewRight * gravityNormal) * gravityNormal;
|
|
|
|
// assert( groundNormal.z >= MIN_WALK_NORMAL );
|
|
viewForward = idPhysics_Player::AdjustVertically( groundNormal, viewForward );
|
|
viewRight = idPhysics_Player::AdjustVertically( groundNormal, viewRight );
|
|
|
|
viewForward.Normalize();
|
|
viewRight.Normalize();
|
|
|
|
// find how we want to move
|
|
idVec2 adjustedMove = CalcDesiredWalkMove( cmdForwardMove, cmdRightMove );
|
|
wishdir = viewForward * adjustedMove.x + viewRight * adjustedMove.y;
|
|
wishspeed = wishdir.Normalize();
|
|
|
|
// clamp the speed lower if wading or walking on the bottom
|
|
if ( waterLevel ) {
|
|
float waterScale;
|
|
|
|
waterScale = waterLevel / 3.0f;
|
|
waterScale = 1.0f - ( 1.0f - playerSelf->GetPlayerPhysics().GetSwimScale() ) * waterScale;
|
|
if ( wishspeed > playerSpeed * waterScale ) {
|
|
wishspeed = playerSpeed * waterScale;
|
|
}
|
|
}
|
|
|
|
// when a player gets hit, they temporarily lose full control, which allows them to be moved a bit
|
|
// bool fLowControl = ( groundMaterial && groundMaterial->GetSurfaceFlags() & SURF_SLICK ) || current.movementFlags & PMF_TIME_KNOCKBACK;
|
|
// accelerate = fLowControl ? pm_airAccelerate : pm_accelerate;
|
|
accelerate = playerSelf->GetPlayerPhysics().GetGroundAccel();
|
|
Accelerate( wishdir, wishspeed, accelerate );
|
|
// if ( fLowControl ) {
|
|
// current.velocity += gravityVector * frametime;
|
|
// }
|
|
|
|
velocity = idPhysics_Player::AdjustVertically( groundNormal, velocity );
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagSetPlayer::Friction
|
|
================
|
|
*/
|
|
void sdAntiLagSetPlayer::Friction() {
|
|
idVec3 vel;
|
|
float speed, newspeed, control;
|
|
float drop;
|
|
|
|
idVec3 gravityNormal = gravity;
|
|
gravityNormal.Normalize();
|
|
|
|
vel = velocity;
|
|
if ( onGround ) {
|
|
// ignore slope movement, remove all velocity in gravity direction
|
|
vel += ( vel * gravityNormal ) * gravityNormal;
|
|
}
|
|
|
|
speed = vel.Length();
|
|
if ( speed < 1.0f ) {
|
|
// remove all movement orthogonal to gravity, allows for sinking underwater
|
|
if ( fabs( velocity * gravityNormal ) < 1e-5f ) {
|
|
velocity.Zero();
|
|
} else {
|
|
velocity = ( velocity * gravityNormal ) * gravityNormal;
|
|
}
|
|
// FIXME: still have z friction underwater?
|
|
return;
|
|
}
|
|
|
|
drop = 0;
|
|
|
|
idPlayer* playerSelf = antiLagEntity->GetSelf()->Cast< idPlayer >();
|
|
assert( playerSelf != NULL );
|
|
|
|
float pm_stopSpeed = playerSelf->GetPlayerPhysics().GetStopSpeed();
|
|
float pm_friction = playerSelf->GetPlayerPhysics().GetGroundFriction();
|
|
float pm_waterFriction = playerSelf->GetPlayerPhysics().GetWaterFriction();
|
|
float pm_airFriction = playerSelf->GetPlayerPhysics().GetAirFriction();
|
|
float frametime = MS2SEC( gameLocal.msec );
|
|
|
|
// spectator friction
|
|
// if ( current.movementType == PM_SPECTATOR ) {
|
|
// drop += speed * pm_flyFriction * frametime;
|
|
// } else if ( walking && waterLevel <= WATERLEVEL_FEET ) {
|
|
// apply ground friction
|
|
// no friction on slick surfaces
|
|
// if ( !(groundMaterial && groundMaterial->GetSurfaceFlags() & SURF_SLICK) ) {
|
|
|
|
// if getting knocked back, no friction
|
|
// if ( !(current.movementFlags & PMF_TIME_KNOCKBACK) ) {
|
|
if ( onGround ) {
|
|
control = speed < pm_stopSpeed ? pm_stopSpeed : speed;
|
|
float friction;
|
|
if ( pm_friction < 0 ) {
|
|
friction = ::pm_friction.GetFloat();
|
|
} else {
|
|
friction = pm_friction;
|
|
}
|
|
drop += control * friction * frametime;
|
|
// }
|
|
// }
|
|
} else if ( waterLevel > WATERLEVEL_NONE ) {
|
|
// apply water friction even if just wading
|
|
drop += speed * pm_waterFriction * waterLevel * frametime;
|
|
} else {
|
|
// apply air friction
|
|
drop += speed * pm_airFriction * frametime;
|
|
}
|
|
|
|
// scale the velocity
|
|
newspeed = speed - drop;
|
|
if (newspeed < 0) {
|
|
newspeed = 0;
|
|
}
|
|
velocity *= ( newspeed / speed );
|
|
|
|
// snap to avoid denormals
|
|
velocity.FixDenormals( 1.0e-5f );
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagSetPlayer::Update
|
|
================
|
|
*/
|
|
void sdAntiLagSetPlayer::Update() {
|
|
if ( !IsValid() ) {
|
|
return;
|
|
}
|
|
if ( gameLocal.msec == 0 ) {
|
|
return;
|
|
}
|
|
|
|
int newTime = timeUpto + gameLocal.msec;
|
|
int index = ( newTime - branchTime ) / gameLocal.msec;
|
|
if ( index <= 0 || index >= MAX_ANTILAG_POINTS ) {
|
|
return;
|
|
}
|
|
|
|
timeUpto = newTime;
|
|
|
|
// carry on the "prediction"
|
|
float timeDelta = MS2SEC( gameLocal.msec );
|
|
|
|
// friction on-ground for players
|
|
idPlayer* playerSelf = static_cast< idPlayer* >( antiLagEntity->GetSelf() );
|
|
if ( OnGround() ) {
|
|
WalkMove();
|
|
} else {
|
|
velocity += gravity * timeDelta;
|
|
}
|
|
|
|
points[ index ] = points[ index - 1 ] + velocity * timeDelta;
|
|
// handle landing
|
|
if ( !OnGround() ) {
|
|
if ( playerSelf->GetPlayerPhysics().HasGroundContacts() ) {
|
|
if ( points[ index ].z <= playerSelf->GetPlayerPhysics().GetOrigin().z ) {
|
|
points[ index ].z = playerSelf->GetPlayerPhysics().GetOrigin().z;
|
|
onGround = true;
|
|
groundNormal = playerSelf->GetPlayerPhysics().GetGroundNormal();
|
|
|
|
velocity -= ( velocity * groundNormal ) * groundNormal;
|
|
}
|
|
}
|
|
}
|
|
|
|
bounds.AddBounds( entityBounds.Translate( points[ index ] ) );
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
sdAntiLagEntity
|
|
One entity's antilag state
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
/*
|
|
================
|
|
sdAntiLagEntity::Create
|
|
================
|
|
*/
|
|
void sdAntiLagEntity::Create() {
|
|
for ( int i = 0; i < MAX_ANTILAG_SETS; i++ ) {
|
|
antiLagSets[ i ] = new sdAntiLagSetGeneric;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagEntity::Destroy
|
|
================
|
|
*/
|
|
void sdAntiLagEntity::Destroy() {
|
|
for ( int i = 0; i < MAX_ANTILAG_SETS; i++ ) {
|
|
delete antiLagSets[ i ];
|
|
antiLagSets[ i ] = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
sdAntiLagEntity::Reset
|
|
================
|
|
*/
|
|
void sdAntiLagEntity::Reset() {
|
|
self = NULL;
|
|
|
|
for ( int i = 0; i < MAX_ANTILAG_SETS; i++ ) {
|
|
antiLagSets[ i ]->Reset();
|
|
}
|
|
|
|
antiLagUpto = 0;
|
|
|
|
for ( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
clientEstimates[ i ].Reset( vec3_origin );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagEntity::Init
|
|
================
|
|
*/
|
|
void sdAntiLagEntity::Init( const idEntity* _self ) {
|
|
Reset();
|
|
|
|
self = _self;
|
|
|
|
for ( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
clientEstimates[ i ].Init( self );
|
|
clientEstimates[ i ].Reset( self->GetPhysics()->GetOrigin() );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
sdAntiLagEntity::Update
|
|
================
|
|
*/
|
|
void sdAntiLagEntity::Update() {
|
|
for ( int i = 0; i < MAX_ANTILAG_SETS; i++ ) {
|
|
if ( !antiLagSets[ i ]->IsValid() ) {
|
|
continue;
|
|
}
|
|
|
|
antiLagSets[ i ]->Update();
|
|
if ( g_drawAntiLag.GetBool() ) {
|
|
antiLagSets[ i ]->DebugDraw();
|
|
}
|
|
}
|
|
|
|
//
|
|
// Update where the other clients think this entity is
|
|
//
|
|
idPlayer* playerSelf = self->Cast< idPlayer >();
|
|
idVec3 trueOrigin = self->GetPhysics()->GetOrigin();
|
|
for ( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( i == self->entityNumber ) {
|
|
// self is always correct
|
|
clientEstimates[ i ].Reset( trueOrigin );
|
|
continue;
|
|
}
|
|
|
|
idPlayer* other = gameLocal.GetClient( i );
|
|
if ( other == NULL ) {
|
|
// other does not exist
|
|
clientEstimates[ i ].Reset( trueOrigin );
|
|
continue;
|
|
}
|
|
|
|
if ( self->GetHealth() <= 0 ) {
|
|
// dead or invulnerable - no need for antilag
|
|
clientEstimates[ i ].Reset( trueOrigin );
|
|
continue;
|
|
}
|
|
|
|
if ( playerSelf != NULL && ( playerSelf->IsSpectator() || playerSelf->GetProxyEntity() != NULL ) ) {
|
|
// spectating or in a vehicle - no need for antilag
|
|
clientEstimates[ i ].Reset( trueOrigin );
|
|
continue;
|
|
}
|
|
|
|
// estimate based on the sets & prediction time etc where
|
|
// the other client is predicting this entity to be
|
|
int prediction = networkSystem->ServerGetClientPrediction( i );
|
|
if ( prediction <= 0 ) {
|
|
// not predicting ahead, it can use the exact position
|
|
clientEstimates[ i ].Reset( trueOrigin );
|
|
continue;
|
|
}
|
|
|
|
sdAntiLagSet* set = GetAntiLagSet( i );
|
|
if ( set == NULL ) {
|
|
// this means the client is lagging so bad that it doesn't have any information anymore.
|
|
// stuff them - if they're that badly lagged then they won't be able to hit anyway.
|
|
clientEstimates[ i ].Reset( trueOrigin );
|
|
continue;
|
|
}
|
|
|
|
const idVec3* predictedPosition = set->GetPointForTime( gameLocal.time );
|
|
if ( predictedPosition == NULL ) {
|
|
// out of bounds of the set - again, means they must be hitting an extreme
|
|
// network performance case - they won't be able to hit anyway
|
|
clientEstimates[ i ].Reset( trueOrigin );
|
|
continue;
|
|
}
|
|
|
|
// prevent it dipping below the true ground level
|
|
idVec3 newPos = *predictedPosition;
|
|
if ( newPos.z < trueOrigin.z && self->GetPhysics()->HasGroundContacts() ) {
|
|
newPos.z = trueOrigin.z;
|
|
}
|
|
|
|
// set up the information needed for prediction error decay to simulate what the client is doing
|
|
idVec3 viewOrigin;
|
|
idMat3 viewAxis;
|
|
other->GetAORView( viewOrigin, viewAxis );
|
|
|
|
sdPredictionErrorDecay_Origin::CustomDecayInfo decayInfo;
|
|
if ( self->aorLayout != NULL ) {
|
|
decayInfo.physicsCutoffSqr = self->aorLayout->GetPhysicsCutoffSqr();
|
|
decayInfo.ownerAorDistSqr = ( self->GetPhysics()->GetOrigin() - viewOrigin ).LengthSqr();
|
|
decayInfo.packetSpread = ( int )( self->aorLayout->GetSpreadForDistanceSqr( decayInfo.ownerAorDistSqr ) * 1000.0f );
|
|
} else {
|
|
decayInfo.physicsCutoffSqr = 1.0f;
|
|
decayInfo.ownerAorDistSqr = 0.0f;
|
|
decayInfo.packetSpread = 0;
|
|
}
|
|
|
|
#ifdef ANTILAG_LOGGING
|
|
antiLagDistances[ i ] = decayInfo.ownerAorDistSqr;
|
|
#endif
|
|
|
|
decayInfo.boxDecayClip = decayInfo.heightMapDecayClip = decayInfo.pointDecayClip = false;
|
|
decayInfo.hasLocalPhysics = decayInfo.ownerAorDistSqr < decayInfo.physicsCutoffSqr;
|
|
decayInfo.isPlayer = self->IsType( idPlayer::Type );
|
|
decayInfo.currentPrediction = prediction;
|
|
decayInfo.limitVelocity = set->GetVelocity();
|
|
decayInfo.origin = newPos;
|
|
decayInfo.hasGroundContacts = set->OnGround();
|
|
|
|
clientEstimates[ i ].SetOwner( self );
|
|
int branchTime = set->GetBranchTime();
|
|
if ( clientEstimates[ i ].GetNetworkTime() < branchTime ) {
|
|
clientEstimates[ i ].OnNewInfo( set->GetBranchPoint(), branchTime, &decayInfo );
|
|
}
|
|
|
|
clientEstimates[ i ].Update( &decayInfo );
|
|
clientEstimates[ i ].Decay( newPos, &decayInfo );
|
|
|
|
// if ( other != gameLocal.GetLocalPlayer() ) {
|
|
// gameRenderWorld->DebugBounds( colorBlue, set->GetEntityBounds(), *predictedPosition );
|
|
// }
|
|
}
|
|
|
|
// for ( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
// idPlayer* other = gameLocal.GetClient( i );
|
|
// if ( other != NULL && other != gameLocal.GetLocalPlayer() ) {
|
|
// gameRenderWorld->DebugBounds( colorYellow, self->GetPhysics()->GetBounds(), clientEstimates[ i ].GetLastReturned() );
|
|
// }
|
|
// }
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagEntity::CreateBranch
|
|
================
|
|
*/
|
|
void sdAntiLagEntity::CreateBranch( int clientNum ) {
|
|
assert( antiLagUpto >= 0 && antiLagUpto < MAX_ANTILAG_SETS );
|
|
|
|
// check if any set already branches from now
|
|
int upto = antiLagUpto;
|
|
do {
|
|
upto = ( upto - 1 + MAX_ANTILAG_SETS ) % MAX_ANTILAG_SETS;
|
|
|
|
if ( antiLagSets[ upto ]->GetBranchTime() == gameLocal.time ) {
|
|
// one already exists, make sure its enabled for this client
|
|
antiLagSets[ upto ]->SetValidFor( clientNum );
|
|
return;
|
|
}
|
|
} while ( upto != antiLagUpto );
|
|
|
|
antiLagSets[ antiLagUpto ]->Reset();
|
|
antiLagSets[ antiLagUpto ]->Setup( *this );
|
|
antiLagSets[ antiLagUpto ]->SetValidFor( clientNum );
|
|
|
|
antiLagUpto = ( antiLagUpto + 1 ) % MAX_ANTILAG_SETS;
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagEntity::CreateUserCommandBranch
|
|
================
|
|
*/
|
|
void sdAntiLagEntity::CreateUserCommandBranch( int clientNum ) {
|
|
assert( antiLagUpto >= 0 && antiLagUpto < MAX_ANTILAG_SETS );
|
|
|
|
// find the previous full branch heading for this client
|
|
int upto = antiLagUpto;
|
|
sdAntiLagSet* previousFullBranch = NULL;
|
|
do {
|
|
upto = ( upto - 1 + MAX_ANTILAG_SETS ) % MAX_ANTILAG_SETS;
|
|
if ( upto == antiLagUpto ) {
|
|
break;
|
|
}
|
|
|
|
if ( !antiLagSets[ upto ]->IsUserCommandOnly() ) {
|
|
if ( antiLagSets[ upto ]->IsValidFor( clientNum ) ) {
|
|
previousFullBranch = antiLagSets[ upto ];
|
|
break;
|
|
}
|
|
}
|
|
} while ( upto != antiLagUpto );
|
|
|
|
if ( previousFullBranch == NULL ) {
|
|
// there is no previous full branch valid for this client
|
|
// can't really do anything about that!
|
|
return;
|
|
}
|
|
|
|
if ( previousFullBranch->GetBranchTime() == gameLocal.time ) {
|
|
// its a full branch from *now*
|
|
// no point branching from that
|
|
return;
|
|
}
|
|
|
|
if ( previousFullBranch->GetPointForTime( gameLocal.time ) == NULL ) {
|
|
// its too old to have a point from now!
|
|
// can't really do anything with that!
|
|
return;
|
|
}
|
|
|
|
// check if any set already branches from our previous full branch
|
|
// other clients are likely to be branching similarly
|
|
upto = antiLagUpto;
|
|
do {
|
|
upto = ( upto - 1 + MAX_ANTILAG_SETS ) % MAX_ANTILAG_SETS;
|
|
|
|
if ( !antiLagSets[ upto ]->IsUserCommandOnly() ) {
|
|
continue;
|
|
}
|
|
|
|
if ( antiLagSets[ upto ]->GetBranchTime() == gameLocal.time
|
|
&& antiLagSets[ upto ]->GetBaseBranchTime() == previousFullBranch->GetBranchTime() ) {
|
|
// branches from the appropriate time
|
|
// set it valid for us and move on :)
|
|
antiLagSets[ upto ]->SetValidFor( clientNum );
|
|
return;
|
|
}
|
|
} while ( upto != antiLagUpto );
|
|
|
|
assert( antiLagSets[ antiLagUpto ] != previousFullBranch );
|
|
|
|
antiLagSets[ antiLagUpto ]->Reset();
|
|
antiLagSets[ antiLagUpto ]->Setup( *this, *previousFullBranch );
|
|
antiLagSets[ antiLagUpto ]->SetValidFor( clientNum );
|
|
|
|
antiLagUpto = ( antiLagUpto + 1 ) % MAX_ANTILAG_SETS;
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagEntity::GetBounds
|
|
================
|
|
*/
|
|
idBounds sdAntiLagEntity::GetBounds() {
|
|
idBounds totalBounds;
|
|
totalBounds.Clear();
|
|
for ( int i = 0; i < MAX_ANTILAG_SETS; i++ ) {
|
|
if ( antiLagSets[ i ]->IsValid() ) {
|
|
totalBounds.AddBounds( antiLagSets[ i ]->GetBounds() );
|
|
}
|
|
}
|
|
return totalBounds;
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagEntity::GetAntiLagSet
|
|
================
|
|
*/
|
|
sdAntiLagSet* sdAntiLagEntity::GetAntiLagSet( int clientNum ) {
|
|
int realPrediction = networkSystem->ServerGetClientPrediction( clientNum );
|
|
const clientNetworkInfo_t& info = gameLocal.GetNetworkInfo( clientNum );
|
|
|
|
// use this info to try to estimate when the last received packet will have been
|
|
// see how far in the past we were seeing just from ordinary old prediction
|
|
int fullReceivedTime = gameLocal.time - realPrediction;
|
|
int commandReceivedTime = fullReceivedTime;
|
|
if ( self->entityNumber < MAX_CLIENTS ) {
|
|
int timeSinceLastConfirm = gameLocal.time - info.lastUserCommand[ self->entityNumber ];
|
|
int timeSinceNextConfirm = timeSinceLastConfirm - info.lastUserCommandDelay[ self->entityNumber ];
|
|
if ( timeSinceNextConfirm > 0 ) {
|
|
commandReceivedTime = gameLocal.time - timeSinceNextConfirm;
|
|
}
|
|
}
|
|
|
|
// find the first one branching near here
|
|
int upto = antiLagUpto;
|
|
do {
|
|
upto = ( upto - 1 + MAX_ANTILAG_SETS ) % MAX_ANTILAG_SETS;
|
|
if ( !antiLagSets[ upto ]->IsValid() ) {
|
|
continue;
|
|
}
|
|
if ( !antiLagSets[ upto ]->IsValidFor( clientNum ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( antiLagSets[ upto ]->GetBranchTime() <= commandReceivedTime && antiLagSets[ upto ]->IsUserCommandOnly() ) {
|
|
return antiLagSets[ upto ];
|
|
}
|
|
|
|
if ( antiLagSets[ upto ]->GetBranchTime() <= fullReceivedTime ) {
|
|
return antiLagSets[ upto ];
|
|
}
|
|
} while ( upto != antiLagUpto );
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
sdAntiLagPlayer
|
|
One player's antilag state
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
/*
|
|
================
|
|
sdAntiLagPlayer::Create
|
|
================
|
|
*/
|
|
void sdAntiLagPlayer::Create() {
|
|
for ( int i = 0; i < MAX_ANTILAG_SETS; i++ ) {
|
|
antiLagSets[ i ] = &playerSets[ i ];
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagPlayer::Destroy
|
|
================
|
|
*/
|
|
void sdAntiLagPlayer::Destroy() {
|
|
for ( int i = 0; i < MAX_ANTILAG_SETS; i++ ) {
|
|
antiLagSets[ i ] = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagPlayer::Trace
|
|
================
|
|
*/
|
|
void sdAntiLagPlayer::Trace( trace_t& result, const idVec3& start, const idVec3& end, int shooterIndex ) {
|
|
result.fraction = 1.0f;
|
|
|
|
sdAntiLagSet* set = GetAntiLagSet( shooterIndex );
|
|
idPlayer* player = self->Cast< idPlayer >();
|
|
|
|
if ( player != NULL ) {
|
|
// default the last hit origin to the real origin
|
|
sdAntiLagManager::GetInstance().SetLastTraceHitOrigin( player->entityNumber, player->GetPhysics()->GetOrigin() );
|
|
}
|
|
|
|
bool fallback = false;
|
|
if ( set == NULL ) {
|
|
fallback = true;
|
|
}
|
|
|
|
if ( player != NULL && player->GetProxyEntity() != NULL ) {
|
|
// in a proxy entity - use the real position
|
|
fallback = true;
|
|
}
|
|
|
|
if ( fallback ) {
|
|
if ( player != NULL ) {
|
|
idClipModel* shotClip = player->GetPhysics()->GetClipModel( 1 );
|
|
gameLocal.clip.TranslationModel( result, start, end, NULL, mat3_identity, -1, shotClip, shotClip->GetOrigin(), mat3_identity );
|
|
}
|
|
return;
|
|
}
|
|
|
|
// this one
|
|
idClipModel* model = sdAntiLagManager::GetInstance().GetModelForBounds( set->GetEntityBounds() );
|
|
assert( model != NULL );
|
|
|
|
// try a few times before & after the real time
|
|
int startTime = gameLocal.time - gameLocal.msec * si_antiLagForgiving.GetInteger();
|
|
int endTime = gameLocal.time + gameLocal.msec * si_antiLagForgiving.GetInteger();
|
|
|
|
for ( int testTime = startTime; testTime <= endTime; testTime += gameLocal.msec ) {
|
|
const idVec3* predictedClientPosition = set->GetPointForTime( testTime );
|
|
if ( predictedClientPosition != NULL ) {
|
|
gameLocal.clip.TranslationModel( result, start, end, NULL, mat3_identity, -1, model, *predictedClientPosition, mat3_identity );
|
|
|
|
if ( g_drawAntiLagHits.GetBool() ) {
|
|
if ( result.fraction < 1.0f ) {
|
|
gameRenderWorld->DebugBounds( colorGreen, set->GetEntityBounds(), *predictedClientPosition, mat3_identity, 20000 );
|
|
gameRenderWorld->DebugArrow( colorGreen, start, end, 8, 20000 );
|
|
// gameLocal.Printf( "Hit predicted\n" );
|
|
} else {
|
|
// gameRenderWorld->DebugBounds( colorBlue, set->GetEntityBounds(), *predictedClientPosition, mat3_identity, 20000 );
|
|
// gameRenderWorld->DebugArrow( colorBlue, start, end, 8, 20000 );
|
|
}
|
|
}
|
|
|
|
if ( result.fraction < 1.0f ) {
|
|
sdAntiLagManager::GetInstance().SetLastTraceHitOrigin( player->entityNumber, *predictedClientPosition );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( result.fraction == 1.0f ) {
|
|
// didn't hit - try tracing against the "maybe" position
|
|
const idVec3& maybePosition = clientEstimates[ shooterIndex ].GetLastReturned();
|
|
gameLocal.clip.TranslationModel( result, start, end, NULL, mat3_identity, -1, model, maybePosition, mat3_identity );
|
|
|
|
if ( g_drawAntiLagHits.GetBool() ) {
|
|
if ( result.fraction < 1.0f ) {
|
|
gameRenderWorld->DebugBounds( colorCyan, set->GetEntityBounds(), maybePosition, mat3_identity, 20000 );
|
|
gameRenderWorld->DebugArrow( colorCyan, start, end, 8, 20000 );
|
|
// gameLocal.Printf( "Hit decayed\n" );
|
|
} else {
|
|
// gameRenderWorld->DebugBounds( colorPurple, set->GetEntityBounds(), maybePosition, mat3_identity, 20000 );
|
|
// gameRenderWorld->DebugArrow( colorPurple, start, end, 8, 20000 );
|
|
}
|
|
}
|
|
|
|
if ( result.fraction < 1.0f ) {
|
|
sdAntiLagManager::GetInstance().SetLastTraceHitOrigin( player->entityNumber, maybePosition );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
sdAntiLagManagerLocal
|
|
Manages the antilag
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
#ifdef ANTILAG_LOGGING
|
|
|
|
enum antilagRecordType_t {
|
|
ANTILAG_BOUNDS = 0,
|
|
ANTILAG_TRACE,
|
|
ANTILAG_RESET,
|
|
ANTILAG_CREATEBRANCH,
|
|
ANTILAG_SNAPSHOT,
|
|
};
|
|
|
|
typedef struct {
|
|
int time;
|
|
int clientNum;
|
|
} antilagResetRecord_t;
|
|
|
|
typedef struct {
|
|
int clientNum;
|
|
idVec3 origin;
|
|
idVec3 alOrigin;
|
|
idVec3 velocity;
|
|
float aorDistSqr;
|
|
|
|
int lastMarker;
|
|
int lastUserCommand;
|
|
int lastUserCommandDelay;
|
|
|
|
|
|
// TWTODO: lots more here!!
|
|
// enough to emulate the antilag fully!
|
|
bool isSpectating;
|
|
bool hasProxy;
|
|
int currentPrediction;
|
|
|
|
int stance;
|
|
bool onGround;
|
|
|
|
idVec3 gravity;
|
|
idVec3 groundNormal;
|
|
|
|
waterLevel_t waterLevel;
|
|
|
|
float walkSpeedFwd;
|
|
float walkSpeedBack;
|
|
float walkSpeedSide;
|
|
|
|
idAngles viewAngles;
|
|
float playerSpeed;
|
|
|
|
int cmdForwardMove;
|
|
int cmdRightMove;
|
|
} antilagSnapshotRecord_t;
|
|
|
|
typedef struct {
|
|
int clientNum;
|
|
int lastSnapshot;
|
|
float aorDistSqr;
|
|
idVec3 origin;
|
|
idVec3 physicsOrigin;
|
|
} antilagClientSnapshotRecord_t;
|
|
|
|
typedef struct {
|
|
int time;
|
|
int clientNum;
|
|
int targetNum;
|
|
bool userCommandOnly;
|
|
} antilagCreateRecord_t;
|
|
|
|
typedef struct {
|
|
idVec3 mins;
|
|
idVec3 maxs;
|
|
} antilagBoundsRecord_t;
|
|
|
|
typedef struct {
|
|
int time;
|
|
int shooter;
|
|
idVec3 start;
|
|
idVec3 end;
|
|
} antilagTraceRecord_t;
|
|
|
|
#endif
|
|
|
|
/*
|
|
================
|
|
sdAntiLagManagerLocal::sdAntiLagManagerLocal
|
|
================
|
|
*/
|
|
sdAntiLagManagerLocal::sdAntiLagManagerLocal() {
|
|
#ifdef ANTILAG_LOGGING
|
|
logFile = NULL;
|
|
clientLogFile = NULL;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagManagerLocal::ResetForPlayer
|
|
================
|
|
*/
|
|
void sdAntiLagManagerLocal::ResetForPlayer( const idPlayer* self ) {
|
|
if ( gameLocal.isClient ) {
|
|
return;
|
|
}
|
|
|
|
assert( self != NULL );
|
|
assert( self->entityNumber >= 0 && self->entityNumber < MAX_CLIENTS );
|
|
players[ self->entityNumber ].Init( self );
|
|
|
|
#ifdef ANTILAG_LOGGING
|
|
if ( logFile != NULL ) {
|
|
antilagResetRecord_t reset;
|
|
reset.time = gameLocal.time;
|
|
reset.clientNum = self->entityNumber;
|
|
logFile->WriteInt( ANTILAG_RESET );
|
|
logFile->Write( &reset, sizeof( reset ) );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagManagerLocal::Think
|
|
================
|
|
*/
|
|
void sdAntiLagManagerLocal::Think() {
|
|
if ( gameLocal.msec == 0 ) {
|
|
return;
|
|
}
|
|
|
|
if ( gameLocal.isClient ) {
|
|
#ifdef ANTILAG_LOGGING
|
|
int numToWrite = 0;
|
|
for ( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
idPlayer* player = gameLocal.GetClient( i );
|
|
if ( player != NULL ) {
|
|
numToWrite++;
|
|
}
|
|
}
|
|
|
|
sdReliableClientMessage msg( GAME_RELIABLE_CMESSAGE_ANTILAGDEBUG );
|
|
msg.WriteLong( ANTILAG_SNAPSHOT );
|
|
msg.WriteLong( gameLocal.time );
|
|
msg.WriteLong( numToWrite );
|
|
|
|
for ( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
idPlayer* player = gameLocal.GetClient( i );
|
|
if ( player == NULL ) {
|
|
continue;
|
|
}
|
|
|
|
antilagClientSnapshotRecord_t record;
|
|
record.clientNum = i;
|
|
record.lastSnapshot = player->GetPlayerPhysics().GetLastSnapshotTime();
|
|
record.origin = player->GetLastPredictionErrorDecayOrigin();
|
|
record.aorDistSqr = player->aorDistanceSqr;
|
|
record.physicsOrigin = player->GetPlayerPhysics().GetOrigin();
|
|
|
|
msg.WriteData( &record, sizeof( record ) );
|
|
}
|
|
|
|
msg.Send();
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
#ifdef ANTILAG_LOGGING
|
|
if ( logFile != NULL ) {
|
|
int numToWrite = 0;
|
|
for ( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
idPlayer* player = gameLocal.GetClient( i );
|
|
if ( player != NULL ) {
|
|
numToWrite++;
|
|
}
|
|
}
|
|
|
|
logFile->WriteInt( ANTILAG_SNAPSHOT );
|
|
logFile->WriteInt( gameLocal.time );
|
|
logFile->WriteInt( numToWrite );
|
|
}
|
|
#endif
|
|
|
|
for ( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
idPlayer* player = gameLocal.GetClient( i );
|
|
if ( player == NULL ) {
|
|
continue;
|
|
}
|
|
|
|
players[ i ].Update();
|
|
|
|
lastTraceHitOrigin[ i ] = player->GetPhysics()->GetOrigin();
|
|
|
|
#ifdef ANTILAG_LOGGING
|
|
if ( logFile != NULL ) {
|
|
antilagSnapshotRecord_t record;
|
|
record.clientNum = i;
|
|
record.origin = player->GetPhysics()->GetOrigin();
|
|
record.alOrigin = players[ i ].GetClientEstimate( 1 );
|
|
record.aorDistSqr = players[ i ].antiLagDistances[ 1 ];
|
|
record.velocity = player->GetPhysics()->GetLinearVelocity();
|
|
record.isSpectating = player->IsSpectating();
|
|
record.hasProxy = player->GetProxyEntity() != NULL;
|
|
record.currentPrediction = networkSystem->ServerGetClientPrediction( i );
|
|
record.stance = player->IsProne() ? 2 : ( player->IsCrouching() ? 1 : 0 );
|
|
record.onGround = player->GetPhysics()->HasGroundContacts();
|
|
record.gravity = player->GetPlayerPhysics().GetGravity();
|
|
record.groundNormal = player->GetPlayerPhysics().GetGroundNormal();
|
|
record.waterLevel = player->GetPlayerPhysics().GetWaterLevel();
|
|
|
|
record.walkSpeedFwd = player->GetPlayerPhysics().GetWalkSpeedFwd();
|
|
record.walkSpeedBack = player->GetPlayerPhysics().GetWalkSpeedBack();
|
|
record.walkSpeedSide = player->GetPlayerPhysics().GetWalkSpeedSide();
|
|
|
|
record.viewAngles = player->GetPlayerPhysics().GetViewAngles();
|
|
record.playerSpeed = player->GetPlayerPhysics().GetCurrentAimSpeed();
|
|
|
|
record.cmdForwardMove = player->usercmd.forwardmove;
|
|
record.cmdRightMove = player->usercmd.rightmove;
|
|
|
|
// client's network statistics
|
|
const clientNetworkInfo_t& info = gameLocal.GetNetworkInfo( 1 );
|
|
record.lastMarker = info.lastMarker[ 0 ];
|
|
record.lastUserCommand = info.lastUserCommand[ 0 ];
|
|
record.lastUserCommandDelay = info.lastUserCommandDelay[ 0 ];
|
|
|
|
logFile->Write( &record, sizeof( record ) );
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagManagerLocal::CreateBranch
|
|
================
|
|
*/
|
|
void sdAntiLagManagerLocal::CreateBranch( const idPlayer* self ) {
|
|
if ( gameLocal.isClient ) {
|
|
return;
|
|
}
|
|
|
|
idPlayer* snapShotTarget = gameLocal.GetSnapshotClient();
|
|
if ( snapShotTarget == NULL ) {
|
|
return;
|
|
}
|
|
|
|
assert( self != NULL );
|
|
assert( self->entityNumber >= 0 && self->entityNumber < MAX_CLIENTS );
|
|
players[ self->entityNumber ].CreateBranch( snapShotTarget->entityNumber );
|
|
|
|
#ifdef ANTILAG_LOGGING
|
|
if ( logFile != NULL ) {
|
|
sdAntiLagSet& baseSet = players[ self->entityNumber ].GetMostRecentBranch();
|
|
sdAntiLagSetPlayer& set = static_cast< sdAntiLagSetPlayer& >( baseSet );
|
|
|
|
antilagCreateRecord_t create;
|
|
create.time = gameLocal.time;
|
|
create.clientNum = self->entityNumber;
|
|
create.targetNum = snapShotTarget->entityNumber;
|
|
create.userCommandOnly = false;
|
|
|
|
logFile->WriteInt( ANTILAG_CREATEBRANCH );
|
|
logFile->Write( &create, sizeof( create ) );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
sdAntiLagManagerLocal::CreateUserCommandBranch
|
|
================
|
|
*/
|
|
void sdAntiLagManagerLocal::CreateUserCommandBranch( const idPlayer* self ) {
|
|
if ( gameLocal.isClient ) {
|
|
return;
|
|
}
|
|
|
|
idPlayer* snapShotTarget = gameLocal.GetSnapshotClient();
|
|
if ( snapShotTarget == NULL ) {
|
|
return;
|
|
}
|
|
|
|
assert( self != NULL );
|
|
assert( self->entityNumber >= 0 && self->entityNumber < MAX_CLIENTS );
|
|
players[ self->entityNumber ].CreateUserCommandBranch( snapShotTarget->entityNumber );
|
|
|
|
#ifdef ANTILAG_LOGGING
|
|
if ( logFile != NULL ) {
|
|
sdAntiLagSet& baseSet = players[ self->entityNumber ].GetMostRecentBranch();
|
|
sdAntiLagSetPlayer& set = static_cast< sdAntiLagSetPlayer& >( baseSet );
|
|
|
|
antilagCreateRecord_t create;
|
|
create.time = gameLocal.time;
|
|
create.clientNum = self->entityNumber;
|
|
create.targetNum = snapShotTarget->entityNumber;
|
|
create.userCommandOnly = true;
|
|
|
|
logFile->WriteInt( ANTILAG_CREATEBRANCH );
|
|
logFile->Write( &create, sizeof( create ) );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagManagerLocal::Trace
|
|
================
|
|
*/
|
|
void sdAntiLagManagerLocal::Trace( trace_t& result, const idVec3& start, const idVec3& end, int mask, idPlayer* clientShooting, idEntity* ignore ) {
|
|
result.fraction = 1.0f;
|
|
|
|
#ifdef ANTILAG_LOGGING
|
|
if ( gameLocal.isClient ) {
|
|
if ( clientShooting == gameLocal.GetLocalPlayer() ) {
|
|
SendTrace( start, end );
|
|
}
|
|
} else {
|
|
RecordServerTrace( result, start, end, clientShooting );
|
|
}
|
|
#endif
|
|
|
|
if ( gameLocal.isClient || !si_antiLag.GetBool() || clientShooting == NULL ) {
|
|
// drop through to default
|
|
gameLocal.clip.TracePoint( result, start, end, mask, ignore );
|
|
return;
|
|
}
|
|
|
|
// use client prediction to estimate which branch/es the client may have taken
|
|
int clientPrediction = networkSystem->ServerGetClientPrediction( clientShooting->entityNumber );
|
|
if ( clientPrediction <= gameLocal.msec ) {
|
|
// local client
|
|
gameLocal.clip.TracePoint( result, start, end, mask, ignore );
|
|
return;
|
|
}
|
|
|
|
|
|
// k, really doing antilag.
|
|
if ( si_antiLagOnly.GetBool() ) {
|
|
// ONLY use antilag for shooting at players - disable all the player clipmodels temporarily
|
|
for ( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
idPlayer* player = gameLocal.GetClient( i );
|
|
if ( player == NULL ) {
|
|
continue;
|
|
}
|
|
|
|
idClipModel* hitBox = player->GetPhysics()->GetClipModel( 1 );
|
|
if ( hitBox == NULL ) {
|
|
continue;
|
|
}
|
|
|
|
hitBox->Disable();
|
|
}
|
|
}
|
|
|
|
// do the trace
|
|
gameLocal.clip.TracePoint( result, start, end, mask, ignore );
|
|
|
|
if ( si_antiLagOnly.GetBool() ) {
|
|
// re-enable all the player clipmodels
|
|
for ( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
idPlayer* player = gameLocal.GetClient( i );
|
|
if ( player == NULL ) {
|
|
continue;
|
|
}
|
|
|
|
idClipModel* hitBox = player->GetPhysics()->GetClipModel( 1 );
|
|
if ( hitBox == NULL ) {
|
|
continue;
|
|
}
|
|
|
|
hitBox->Enable();
|
|
}
|
|
}
|
|
|
|
|
|
// check against all the players
|
|
for ( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( ignore != NULL && ignore->entityNumber == i ) {
|
|
// ignoring this player
|
|
continue;
|
|
}
|
|
if ( clientShooting->entityNumber == i ) {
|
|
// ignoring self
|
|
continue;
|
|
}
|
|
|
|
idPlayer* other = gameLocal.GetClient( i );
|
|
if ( other == NULL ) {
|
|
// other isn't in the game
|
|
continue;
|
|
}
|
|
if ( !other->GetPhysics()->GetClipModel( 1 )->GetContents() & mask ) {
|
|
// doesn't touch the mask
|
|
continue;
|
|
}
|
|
|
|
if ( !other->CanCollide( ignore, TM_DEFAULT ) || !ignore->CanCollide( other, TM_DEFAULT ) ) {
|
|
// these two can't collide
|
|
continue;
|
|
}
|
|
|
|
// rough check against the current total bounds
|
|
idBounds moveBounds = players[ i ].GetBounds();
|
|
// make sure to include the current decayed position
|
|
float halfwidth = pm_bboxwidth.GetFloat() * 0.5f;
|
|
float height = pm_normalheight.GetFloat();
|
|
idBounds playerBBox( idVec3( -halfwidth, -halfwidth, 0.0f ), idVec3( halfwidth, halfwidth, height ) );
|
|
playerBBox.TranslateSelf( players[ i ].GetClientEstimate( clientShooting->entityNumber ) );
|
|
moveBounds.AddBounds( playerBBox );
|
|
if ( !moveBounds.LineIntersection( start, end ) ) {
|
|
// doesn't hit the total bounds
|
|
continue;
|
|
}
|
|
|
|
trace_t trace;
|
|
players[ i ].Trace( trace, start, end, clientShooting->entityNumber );
|
|
if ( trace.fraction < result.fraction ) {
|
|
trace.c.entityNum = i;
|
|
trace.c.surfaceColor.Set( 1.0f, 1.0f, 1.0f );
|
|
trace.c.surfaceType = NULL;
|
|
trace.c.material = NULL;
|
|
result = trace;
|
|
|
|
if ( g_drawAntiLagHits.GetBool() ) {
|
|
gameRenderWorld->DebugBounds( colorRed, other->GetPhysics()->GetBounds(), other->GetPhysics()->GetOrigin(), mat3_identity, 20000 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagManagerLocal::GetAntiLagPlayer
|
|
================
|
|
*/
|
|
sdAntiLagPlayer& sdAntiLagManagerLocal::GetAntiLagPlayer( int index ) {
|
|
assert( index >= 0 && index < MAX_CLIENTS );
|
|
return players[ index ];
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagManagerLocal::OnNewMapLoad
|
|
================
|
|
*/
|
|
void sdAntiLagManagerLocal::OnMapLoad() {
|
|
#ifdef ANTILAG_LOGGING
|
|
lastStart.Set( 999999.0f, 999999.0f, 999999.0f );
|
|
lastEnd.Set( 999999.0f, 999999.0f, 999999.0f );
|
|
lastTime = -1;
|
|
#endif
|
|
|
|
if ( gameLocal.isClient ) {
|
|
return;
|
|
}
|
|
|
|
// paranoid:
|
|
OnMapShutdown();
|
|
|
|
for ( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
players[ i ].Create();
|
|
}
|
|
|
|
|
|
float halfwidth = pm_bboxwidth.GetFloat() * 0.5f;
|
|
idBounds bounds( idVec3( -halfwidth, -halfwidth, 0.0f ), idVec3( halfwidth, halfwidth, pm_normalheight.GetFloat() ) );
|
|
|
|
// create the default player clip models - likely it'll never have to create any others
|
|
// unless pm_bboxwidth etc change
|
|
|
|
// normal
|
|
cachedPlayerModels.Append( new idClipModel( idTraceModel( bounds, 8 ), false ) );
|
|
|
|
// crouch
|
|
bounds.GetMaxs().z = pm_crouchheight.GetFloat();
|
|
cachedPlayerModels.Append( new idClipModel( idTraceModel( bounds, 8 ), false ) );
|
|
|
|
// prone
|
|
bounds.GetMaxs().z = pm_proneheight.GetFloat();
|
|
cachedPlayerModels.Append( new idClipModel( idTraceModel( bounds, 8 ), false ) );
|
|
|
|
// dead
|
|
bounds.GetMaxs().z = pm_deadheight.GetFloat();
|
|
cachedPlayerModels.Append( new idClipModel( idTraceModel( bounds, 8 ), false ) );
|
|
|
|
#ifdef ANTILAG_LOGGING
|
|
if ( si_antiLagLog.GetBool() ) {
|
|
// figure out which log file number to use
|
|
int logFileNum = 1;
|
|
while ( 1 ) {
|
|
if ( !fileSystem->FileExists( va( "antiLagLog_%i.log", logFileNum ) ) ) {
|
|
break;
|
|
}
|
|
logFileNum++;
|
|
}
|
|
|
|
logFile = fileSystem->OpenFileWrite( va( "antiLagLog_%i.log", logFileNum ) );
|
|
clientLogFile = fileSystem->OpenFileWrite( va( "antiLagLog_client_%i.log", logFileNum ) );
|
|
} else {
|
|
logFile = NULL;
|
|
clientLogFile = NULL;
|
|
}
|
|
|
|
if ( logFile != NULL ) {
|
|
for ( int i = 0; i < 3; i++ ) {
|
|
idBounds bounds = cachedPlayerModels[ i ]->GetBounds();
|
|
|
|
antilagBoundsRecord_t boundsR;
|
|
boundsR.mins[ 0 ] = bounds[ 0 ][ 0 ];
|
|
boundsR.mins[ 1 ] = bounds[ 0 ][ 1 ];
|
|
boundsR.mins[ 2 ] = bounds[ 0 ][ 2 ];
|
|
|
|
boundsR.maxs[ 0 ] = bounds[ 1 ][ 0 ];
|
|
boundsR.maxs[ 1 ] = bounds[ 1 ][ 1 ];
|
|
boundsR.maxs[ 2 ] = bounds[ 1 ][ 2 ];
|
|
|
|
logFile->WriteInt( ANTILAG_BOUNDS );
|
|
logFile->Write( &boundsR, sizeof( boundsR ) );
|
|
|
|
// textLogFile->Printf( "BOUNDS %i %.2f %.2f %.2f %.2f %.2f %.2f\n", i,
|
|
// bounds[ 0 ][ 0 ], bounds[ 0 ][ 1 ], bounds[ 0 ][ 2 ],
|
|
// bounds[ 1 ][ 0 ], bounds[ 1 ][ 1 ], bounds[ 1 ][ 2 ] );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagManagerLocal::OnMapShutdown
|
|
================
|
|
*/
|
|
void sdAntiLagManagerLocal::OnMapShutdown() {
|
|
if ( gameLocal.isClient ) {
|
|
return;
|
|
}
|
|
|
|
for ( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
players[ i ].Destroy();
|
|
}
|
|
|
|
for ( int i = 0; i < cachedPlayerModels.Num(); i++ ) {
|
|
gameLocal.clip.DeleteClipModel( cachedPlayerModels[ i ] );
|
|
}
|
|
cachedPlayerModels.SetNum( 0, false );
|
|
|
|
#ifdef ANTILAG_LOGGING
|
|
if ( logFile != NULL ) {
|
|
fileSystem->CloseFile( logFile );
|
|
logFile = NULL;
|
|
}
|
|
if ( clientLogFile != NULL ) {
|
|
fileSystem->CloseFile( clientLogFile );
|
|
clientLogFile = NULL;
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagManagerLocal::GetModelForBounds
|
|
================
|
|
*/
|
|
idClipModel* sdAntiLagManagerLocal::GetModelForBounds( const idBounds& bounds ) {
|
|
for ( int i = 0; i < cachedPlayerModels.Num(); i++ ) {
|
|
if ( cachedPlayerModels[ i ]->GetBounds().Compare( bounds, 0.5f ) ) {
|
|
return cachedPlayerModels[ i ];
|
|
}
|
|
}
|
|
|
|
// couldn't find it in the cache - make a new one
|
|
idClipModel* newModel = new idClipModel( idTraceModel( bounds, 8 ), false );
|
|
cachedPlayerModels.Append( newModel );
|
|
return newModel;
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagManagerLocal::OnNetworkEvent
|
|
================
|
|
*/
|
|
void sdAntiLagManagerLocal::OnNetworkEvent( int clientNum, const idBitMsg& msg ) {
|
|
|
|
#ifdef ANTILAG_LOGGING
|
|
antilagRecordType_t type = ( antilagRecordType_t )msg.ReadLong();
|
|
if ( type == ANTILAG_TRACE ) {
|
|
// received info from a client about shootin', yall
|
|
// record it!
|
|
int time = msg.ReadLong();
|
|
idVec3 start = msg.ReadVector();
|
|
idVec3 end = msg.ReadVector();
|
|
|
|
RecordTrace( time, false, start, end, clientNum );
|
|
} else if ( type == ANTILAG_SNAPSHOT ) {
|
|
int time = msg.ReadLong();
|
|
int num = msg.ReadLong();
|
|
|
|
if ( clientLogFile != NULL ) {
|
|
clientLogFile->WriteInt( ANTILAG_SNAPSHOT );
|
|
clientLogFile->WriteInt( time );
|
|
clientLogFile->WriteInt( num );
|
|
}
|
|
|
|
for ( int i = 0; i < num; i++ ) {
|
|
antilagClientSnapshotRecord_t record;
|
|
msg.ReadData( &record, sizeof( record ) );
|
|
|
|
if ( clientLogFile != NULL ) {
|
|
clientLogFile->Write( &record, sizeof( record ) );
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef ANTILAG_LOGGING
|
|
/*
|
|
================
|
|
sdAntiLagManagerLocal::SendTrace
|
|
================
|
|
*/
|
|
void sdAntiLagManagerLocal::SendTrace( const idVec3& start, const idVec3& end ) {
|
|
// if its not different from the last one then don't send it
|
|
if ( lastTime == gameLocal.time && lastStart.Compare( start, idMath::FLT_EPSILON ) && lastEnd.Compare( end, idMath::FLT_EPSILON ) ) {
|
|
return;
|
|
}
|
|
lastTime = gameLocal.time;
|
|
lastStart = start;
|
|
lastEnd = end;
|
|
|
|
sdReliableClientMessage msg( GAME_RELIABLE_CMESSAGE_ANTILAGDEBUG );
|
|
msg.WriteLong( ANTILAG_TRACE );
|
|
msg.WriteLong( gameLocal.time );
|
|
msg.WriteVector( start );
|
|
msg.WriteVector( end );
|
|
msg.Send();
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagManagerLocal::RecordTrace
|
|
================
|
|
*/
|
|
void sdAntiLagManagerLocal::RecordTrace( int time, bool server, const idVec3& start, const idVec3& end, int shooter ) {
|
|
|
|
if ( logFile == NULL || clientLogFile == NULL ) {
|
|
return;
|
|
}
|
|
|
|
idPlayer* player = gameLocal.GetClient( shooter );
|
|
if ( player == NULL ) {
|
|
return;
|
|
}
|
|
|
|
antilagTraceRecord_t preamble;
|
|
preamble.time = time;
|
|
preamble.shooter = shooter;
|
|
preamble.start = start;
|
|
preamble.end = end;
|
|
|
|
if ( server ) {
|
|
logFile->WriteInt( ANTILAG_TRACE );
|
|
logFile->Write( &preamble, sizeof( preamble ) );
|
|
} else {
|
|
clientLogFile->WriteInt( ANTILAG_TRACE );
|
|
clientLogFile->Write( &preamble, sizeof( preamble ) );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
sdAntiLagManagerLocal::RecordServerTrace
|
|
================
|
|
*/
|
|
void sdAntiLagManagerLocal::RecordServerTrace( trace_t& result, const idVec3& start, const idVec3& end, idPlayer* clientShooting ) {
|
|
// if its not different from the last one then don't send it
|
|
if ( lastTime == gameLocal.time && lastStart.Compare( start, idMath::FLT_EPSILON ) && lastEnd.Compare( end, idMath::FLT_EPSILON ) ) {
|
|
return;
|
|
}
|
|
|
|
lastTime = gameLocal.time;
|
|
lastStart = start;
|
|
lastEnd = end;
|
|
|
|
// not passing any hit info from the server in - the tool can figure that out based on all the info given
|
|
RecordTrace( gameLocal.time, true, start, end, clientShooting->entityNumber );
|
|
}
|
|
#endif
|