etqw-sdk/source/game/AntiLag.cpp

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