860 lines
28 KiB
C++
860 lines
28 KiB
C++
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
|
|
#include "cbase.h"
|
|
|
|
#include "player_lagcompensation.h"
|
|
#include "usercmd.h"
|
|
#include "inetchannelinfo.h"
|
|
#include "utllinkedlist.h"
|
|
#include "BaseAnimatingOverlay.h"
|
|
#include "tier0/vprof.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
#define LC_NONE 0
|
|
#define LC_ALIVE (1<<0)
|
|
|
|
#define LC_ORIGIN_CHANGED (1<<8)
|
|
#define LC_ANGLES_CHANGED (1<<9)
|
|
#define LC_SIZE_CHANGED (1<<10)
|
|
#define LC_ANIMATION_CHANGED (1<<11)
|
|
|
|
#define LAG_COMPENSATION_TELEPORTED_DISTANCE_SQR ( 64.0f * 64.0f )
|
|
#define LAG_COMPENSATION_EPS_SQR ( 0.1f * 0.1f )
|
|
// Allow 4 units of error ( about 1 / 8 bbox width )
|
|
#define LAG_COMPENSATION_ERROR_EPS_SQR ( 4.0f * 4.0f )
|
|
|
|
ConVar sv_unlag( "sv_unlag", "1", FCVAR_DEVELOPMENTONLY, "Enables player lag compensation" );
|
|
ConVar sv_maxunlag( "sv_maxunlag", "1.0", FCVAR_DEVELOPMENTONLY, "Maximum lag compensation in seconds", true, 0.0f, true, 1.0f );
|
|
ConVar sv_lagflushbonecache( "sv_lagflushbonecache", "1", FCVAR_DEVELOPMENTONLY, "Flushes entity bone cache on lag compensation" );
|
|
ConVar sv_showlagcompensation( "sv_showlagcompensation", "0", FCVAR_CHEAT, "Show lag compensated hitboxes whenever a player is lag compensated." );
|
|
ConVar sv_lagcompensationforcerestore( "sv_lagcompensationforcerestore", "1", FCVAR_CHEAT, "Don't test validity of a lag comp restore, just do it.");
|
|
|
|
ConVar sv_unlag_fixstuck( "sv_unlag_fixstuck", "0", FCVAR_DEVELOPMENTONLY, "Disallow backtracking a player for lag compensation if it will cause them to become stuck" );
|
|
|
|
ConVar sv_lagpushticks( "sv_lagpushticks", "0", FCVAR_DEVELOPMENTONLY, "Push computed lag compensation amount by this many ticks." );
|
|
|
|
static CLagCompensationManager g_LagCompensationManager( "CLagCompensationManager" );
|
|
ILagCompensationManager *lagcompensation = &g_LagCompensationManager;
|
|
|
|
//
|
|
// Try to take the player from his current origin to vWantedPos.
|
|
// If it can't get there, leave the player where he is.
|
|
//
|
|
|
|
ConVar sv_unlag_debug( "sv_unlag_debug", "0", FCVAR_GAMEDLL | FCVAR_DEVELOPMENTONLY );
|
|
|
|
float g_flFractionScale = 0.95;
|
|
|
|
|
|
static void LC_SetAbsOrigin( CBaseEntity *entity, const Vector &vecAbsOrigin, bool bFireTriggers )
|
|
{
|
|
entity->SetAbsOrigin( vecAbsOrigin );
|
|
if ( bFireTriggers )
|
|
{
|
|
entity->PhysicsTouchTriggers();
|
|
}
|
|
}
|
|
|
|
static void RestoreEntityTo( CBaseEntity *pEntity, const Vector &vWantedPos )
|
|
{
|
|
// Try to move to the wanted position from our current position.
|
|
trace_t tr;
|
|
VPROF_BUDGET( "RestoreEntityTo", "CLagCompensationManager" );
|
|
if( sv_lagcompensationforcerestore.GetBool() )
|
|
{
|
|
// We don't have to test for validity. Just put them back where you found them.
|
|
LC_SetAbsOrigin( pEntity, vWantedPos, true );
|
|
}
|
|
|
|
unsigned int mask = MASK_PLAYERSOLID;
|
|
UTIL_TraceEntity( pEntity, vWantedPos, vWantedPos, mask, pEntity, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
|
|
if ( tr.startsolid || tr.allsolid )
|
|
{
|
|
if ( sv_unlag_debug.GetBool() )
|
|
{
|
|
DevMsg( "RestoreEntityTo could not restore player position for client \"%d\" ( %.1f %.1f %.1f )\n",
|
|
pEntity->entindex(), vWantedPos.x, vWantedPos.y, vWantedPos.z );
|
|
}
|
|
|
|
UTIL_TraceEntity( pEntity, pEntity->GetAbsOrigin(), vWantedPos, mask, pEntity, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
|
|
if ( tr.startsolid || tr.allsolid )
|
|
{
|
|
// In this case, the guy got stuck back wherever we lag compensated him to. Nasty.
|
|
|
|
if ( sv_unlag_debug.GetBool() )
|
|
DevMsg( " restore failed entirely\n" );
|
|
}
|
|
else
|
|
{
|
|
// We can get to a valid place, but not all the way back to where we were.
|
|
Vector vPos;
|
|
VectorLerp( pEntity->GetAbsOrigin(), vWantedPos, tr.fraction * g_flFractionScale, vPos );
|
|
LC_SetAbsOrigin( pEntity, vPos, true );
|
|
|
|
if ( sv_unlag_debug.GetBool() )
|
|
DevMsg( " restore got most of the way\n" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Cool, the player can go back to whence he came.
|
|
LC_SetAbsOrigin( pEntity, tr.endpos, true );
|
|
}
|
|
}
|
|
|
|
// Mappers can flag certain additional entities to lag compensate, this handles them
|
|
void CLagCompensationManager::AddAdditionalEntity( CBaseEntity *pEntity )
|
|
{
|
|
EHANDLE eh;
|
|
eh = pEntity;
|
|
if ( m_AdditionalEntities.Find( eh ) == m_AdditionalEntities.InvalidIndex() )
|
|
{
|
|
m_AdditionalEntities.Insert( eh );
|
|
}
|
|
}
|
|
|
|
void CLagCompensationManager::RemoveAdditionalEntity( CBaseEntity *pEntity )
|
|
{
|
|
EHANDLE eh;
|
|
eh = pEntity;
|
|
m_AdditionalEntities.Remove( eh );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Called once per frame after all entities have had a chance to think
|
|
//-----------------------------------------------------------------------------
|
|
void CLagCompensationManager::FrameUpdatePostEntityThink()
|
|
{
|
|
if ( (gpGlobals->maxClients <= 1) || !sv_unlag.GetBool() )
|
|
{
|
|
ClearHistory();
|
|
return;
|
|
}
|
|
|
|
VPROF_BUDGET( "FrameUpdatePostEntityThink", "CLagCompensationManager" );
|
|
|
|
CUtlRBTree< CBaseEntity * > rbEntityList( 0, 0, DefLessFunc( CBaseEntity * ) );
|
|
|
|
// Add active players
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
|
|
if ( !pPlayer )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( rbEntityList.Find( pPlayer ) == rbEntityList.InvalidIndex() )
|
|
{
|
|
rbEntityList.Insert( pPlayer );
|
|
}
|
|
}
|
|
|
|
// Add any additional entities
|
|
for ( int i = m_AdditionalEntities.FirstInorder(); i != m_AdditionalEntities.InvalidIndex(); i = m_AdditionalEntities.NextInorder( i ) )
|
|
{
|
|
CBaseEntity *pAddEntity = m_AdditionalEntities[ i ].Get();
|
|
if ( !pAddEntity )
|
|
continue;
|
|
|
|
if ( rbEntityList.Find( pAddEntity ) == rbEntityList.InvalidIndex() )
|
|
{
|
|
rbEntityList.Insert( pAddEntity );
|
|
}
|
|
}
|
|
|
|
// Now record the actual history information
|
|
for ( int i = rbEntityList.FirstInorder(); i != rbEntityList.InvalidIndex(); i = rbEntityList.NextInorder( i ) )
|
|
{
|
|
CBaseEntity *pEntity = rbEntityList[ i ];
|
|
|
|
EHANDLE eh;
|
|
eh = pEntity;
|
|
|
|
int slot = m_CompensatedEntities.Find( eh );
|
|
if ( slot == m_CompensatedEntities.InvalidIndex() )
|
|
{
|
|
EntityLagData *pNewEntry = new EntityLagData();
|
|
slot = m_CompensatedEntities.Insert( eh, pNewEntry );
|
|
}
|
|
|
|
EntityLagData *ld = m_CompensatedEntities[ slot ];
|
|
|
|
RecordDataIntoTrack( pEntity, &ld->m_LagRecords, true );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Called during gamemovment weapon firing to set up/restore after lag compensation around the bullet traces
|
|
//-----------------------------------------------------------------------------
|
|
void CLagCompensationManager::StartLagCompensation( CBasePlayer *player, LagCompensationType lagCompensationType, const Vector& weaponPos, const QAngle &weaponAngles, float weaponRange )
|
|
{
|
|
Assert(!m_isCurrentlyDoingCompensation);
|
|
|
|
// Assume no entities need to be restored
|
|
CUtlVector< EHANDLE > invalidList;
|
|
FOR_EACH_MAP( m_CompensatedEntities, i )
|
|
{
|
|
EntityLagData *ld = m_CompensatedEntities[ i ];
|
|
EHANDLE key;
|
|
key = m_CompensatedEntities.Key( i );
|
|
if ( !key.Get() )
|
|
{
|
|
// Note that the EHANDLE is NULL now
|
|
invalidList.AddToTail( key );
|
|
continue;
|
|
}
|
|
|
|
// Clear state
|
|
ld->m_bRestoreEntity = false;
|
|
ld->m_RestoreData.Clear();
|
|
ld->m_ChangeData.Clear();
|
|
}
|
|
|
|
// Wipe any deleted entities from the list
|
|
for ( int i = 0; i < invalidList.Count(); ++i )
|
|
{
|
|
int slot = m_CompensatedEntities.Find( invalidList[ i ] );
|
|
Assert( slot != m_CompensatedEntities.InvalidIndex() );
|
|
if ( slot == m_CompensatedEntities.InvalidIndex() )
|
|
continue;
|
|
|
|
EntityLagData *ld = m_CompensatedEntities[ slot ];
|
|
delete ld;
|
|
m_CompensatedEntities.RemoveAt( slot );
|
|
}
|
|
|
|
m_bNeedToRestore = false;
|
|
|
|
m_pCurrentPlayer = player;
|
|
|
|
if ( !player->m_bLagCompensation // Player not wanting lag compensation
|
|
|| (gpGlobals->maxClients <= 1) // no lag compensation in single player
|
|
|| !sv_unlag.GetBool() // disabled by server admin
|
|
|| player->IsBot() // not for bots
|
|
|| player->IsObserver() // not for spectators
|
|
)
|
|
return;
|
|
|
|
const CUserCmd *cmd = player->GetCurrentUserCommand();
|
|
if ( !cmd )
|
|
{
|
|
// This can hit if m_hActiveWeapon incorrectly gets set to a weapon not actually owned by the local player
|
|
// (since lag comp asks for the GetPlayerOwner() which will have m_pCurrentCommand == NULL,
|
|
// not the player currently executing a CUserCmd).
|
|
Error( "CLagCompensationManager::StartLagCompensation with NULL CUserCmd!!!\n" );
|
|
}
|
|
|
|
// NOTE: Put this here so that it won't show up in single player mode.
|
|
VPROF_BUDGET( "StartLagCompensation", VPROF_BUDGETGROUP_OTHER_NETWORKING );
|
|
|
|
m_isCurrentlyDoingCompensation = true;
|
|
|
|
// correct is the amount of time we have to correct game time
|
|
float correct = 0.0f;
|
|
|
|
// Get true latency
|
|
INetChannelInfo *nci = engine->GetPlayerNetInfo( player->entindex() );
|
|
if ( nci )
|
|
{
|
|
// add network latency
|
|
correct+= nci->GetLatency( FLOW_OUTGOING );
|
|
}
|
|
|
|
// NOTE: do these computations in float time, not ticks, to avoid big roundoff error accumulations in the math
|
|
// add view interpolation latency see C_BaseEntity::GetInterpolationAmount()
|
|
correct += player->m_fLerpTime;
|
|
|
|
// check bounds [0,sv_maxunlag]
|
|
correct = clamp( correct, 0.0f, sv_maxunlag.GetFloat() );
|
|
|
|
// correct tick send by player
|
|
float flTargetTime = TICKS_TO_TIME( cmd->tick_count ) - player->m_fLerpTime;
|
|
|
|
// calculate difference between tick sent by player and our latency based tick
|
|
float deltaTime = correct - ( gpGlobals->curtime - flTargetTime );
|
|
|
|
if ( fabs( deltaTime ) > 0.2f )
|
|
{
|
|
// difference between cmd time and latency is too big > 200ms, use time correction based on latency
|
|
// DevMsg("StartLagCompensation: delta too big (%.3f)\n", deltaTime );
|
|
flTargetTime = gpGlobals->curtime - correct;
|
|
}
|
|
|
|
flTargetTime += TICKS_TO_TIME( sv_lagpushticks.GetInt() );
|
|
|
|
m_lagCompensationType = lagCompensationType;
|
|
m_weaponPos = weaponPos;
|
|
m_weaponAngles = weaponAngles;
|
|
m_weaponRange = weaponRange;
|
|
|
|
// Iterate all lag compensatable entities
|
|
const CBitVec<MAX_EDICTS> *pEntityTransmitBits = engine->GetEntityTransmitBitsForClient( player->entindex() - 1 );
|
|
|
|
FOR_EACH_MAP( m_CompensatedEntities, i )
|
|
{
|
|
EntityLagData *ld = m_CompensatedEntities[ i ];
|
|
EHANDLE key = m_CompensatedEntities.Key( i );
|
|
CBaseEntity *pEntity = key.Get();
|
|
if ( !pEntity )
|
|
{
|
|
// Stale entity in list, fixed up on next time through loop
|
|
continue;
|
|
}
|
|
|
|
// Don't lag compensate self
|
|
if ( player == pEntity )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Custom checks for if things should lag compensate (based on things like what team the entity is associated with).
|
|
if ( !player->WantsLagCompensationOnEntity( pEntity, cmd, pEntityTransmitBits ) )
|
|
continue;
|
|
|
|
// Move entity back in time and remember that fact
|
|
ld->m_bRestoreEntity = BacktrackEntity( pEntity, flTargetTime, &ld->m_LagRecords, &ld->m_RestoreData, &ld->m_ChangeData, true );
|
|
}
|
|
}
|
|
|
|
bool CLagCompensationManager::BacktrackEntity( CBaseEntity *entity, float flTargetTime, LagRecordList *track, LagRecord *restore, LagRecord *change, bool wantsAnims )
|
|
{
|
|
Vector org, mins, maxs;
|
|
QAngle ang;
|
|
|
|
VPROF_BUDGET( "BacktrackEntity", "CLagCompensationManager" );
|
|
|
|
// check if we have at least one entry
|
|
if ( track->Count() <= 0 )
|
|
return false;
|
|
|
|
int curr = track->Head();
|
|
|
|
LagRecord *prevRecord = NULL;
|
|
LagRecord *record = NULL;
|
|
|
|
Vector prevOrg = entity->GetAbsOrigin();
|
|
|
|
// Walk context looking for any invalidating event
|
|
while( track->IsValidIndex(curr) )
|
|
{
|
|
// remember last record
|
|
prevRecord = record;
|
|
|
|
// get next record
|
|
record = &track->Element( curr );
|
|
|
|
if ( !(record->m_fFlags & LC_ALIVE) )
|
|
{
|
|
// entity must be alive, lost track
|
|
return false;
|
|
}
|
|
|
|
Vector delta = record->m_vecOrigin - prevOrg;
|
|
if ( delta.LengthSqr() > LAG_COMPENSATION_TELEPORTED_DISTANCE_SQR )
|
|
{
|
|
// lost track, too much difference
|
|
return false;
|
|
}
|
|
|
|
// did we find a context smaller than target time ?
|
|
if ( record->m_flSimulationTime <= flTargetTime )
|
|
break; // hurra, stop
|
|
|
|
prevOrg = record->m_vecOrigin;
|
|
|
|
// go one step back
|
|
curr = track->Next( curr );
|
|
}
|
|
|
|
Assert( record );
|
|
|
|
if ( !record )
|
|
{
|
|
if ( sv_unlag_debug.GetBool() )
|
|
{
|
|
DevMsg( "No valid positions in history for BacktrackPlayer client ( %d )\n", entity->entindex() );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
float frac = 0.0f;
|
|
if ( prevRecord &&
|
|
(record->m_flSimulationTime < flTargetTime) &&
|
|
(record->m_flSimulationTime < prevRecord->m_flSimulationTime) )
|
|
{
|
|
// we didn't find the exact time but have a valid previous record
|
|
// so interpolate between these two records;
|
|
|
|
Assert( prevRecord->m_flSimulationTime > record->m_flSimulationTime );
|
|
Assert( flTargetTime < prevRecord->m_flSimulationTime );
|
|
|
|
// calc fraction between both records
|
|
frac = ( flTargetTime - record->m_flSimulationTime ) /
|
|
( prevRecord->m_flSimulationTime - record->m_flSimulationTime );
|
|
|
|
Assert( frac > 0 && frac < 1 ); // should never extrapolate
|
|
|
|
ang = Lerp( frac, record->m_vecAngles, prevRecord->m_vecAngles );
|
|
org = Lerp( frac, record->m_vecOrigin, prevRecord->m_vecOrigin );
|
|
mins = Lerp( frac, record->m_vecMins, prevRecord->m_vecMins );
|
|
maxs = Lerp( frac, record->m_vecMaxs, prevRecord->m_vecMaxs );
|
|
}
|
|
else
|
|
{
|
|
// we found the exact record or no other record to interpolate with
|
|
// just copy these values since they are the best we have
|
|
ang = record->m_vecAngles;
|
|
org = record->m_vecOrigin;
|
|
mins = record->m_vecMins;
|
|
maxs = record->m_vecMaxs;
|
|
}
|
|
|
|
// See if this is still a valid position for us to teleport to
|
|
if ( sv_unlag_fixstuck.GetBool() )
|
|
{
|
|
// Try to move to the wanted position from our current position.
|
|
trace_t tr;
|
|
UTIL_TraceEntity( entity, org, org, MASK_PLAYERSOLID, &tr );
|
|
if ( tr.startsolid || tr.allsolid )
|
|
{
|
|
if ( sv_unlag_debug.GetBool() )
|
|
DevMsg( "WARNING: BackupPlayer trying to back player into a bad position - client %d\n", entity->entindex() );
|
|
|
|
CBaseEntity *pHitEntity = tr.m_pEnt;
|
|
// don't lag compensate the current player
|
|
if ( pHitEntity && ( pHitEntity != m_pCurrentPlayer ) )
|
|
{
|
|
// Find it
|
|
EHANDLE eh;
|
|
eh = pHitEntity;
|
|
|
|
int slot = m_CompensatedEntities.Find( eh );
|
|
if ( slot != m_CompensatedEntities.InvalidIndex() )
|
|
{
|
|
EntityLagData *ld = m_CompensatedEntities[ slot ];
|
|
|
|
// If we haven't backtracked this player, do it now
|
|
// this deliberately ignores WantsLagCompensationOnEntity.
|
|
if ( !ld->m_bRestoreEntity )
|
|
{
|
|
// Temp turn this flag on
|
|
ld->m_bRestoreEntity = true;
|
|
|
|
BacktrackEntity( pHitEntity, flTargetTime, &ld->m_LagRecords, &ld->m_RestoreData, &ld->m_ChangeData, true );
|
|
|
|
// Remove the temp flag
|
|
ld->m_bRestoreEntity = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// now trace us back as far as we can go
|
|
unsigned int mask = MASK_PLAYERSOLID;
|
|
UTIL_TraceEntity( entity, entity->GetAbsOrigin(), org, mask, &tr );
|
|
|
|
if ( tr.startsolid || tr.allsolid )
|
|
{
|
|
// Our starting position is bogus
|
|
|
|
if ( sv_unlag_debug.GetBool() )
|
|
DevMsg( "Backtrack failed completely, bad starting position\n" );
|
|
}
|
|
else
|
|
{
|
|
// We can get to a valid place, but not all the way to the target
|
|
Vector vPos;
|
|
VectorLerp( entity->GetAbsOrigin(), org, tr.fraction * g_flFractionScale, vPos );
|
|
|
|
// This is as close as we're going to get
|
|
org = vPos;
|
|
|
|
if ( sv_unlag_debug.GetBool() )
|
|
DevMsg( "Backtrack got most of the way\n" );
|
|
}
|
|
}
|
|
}
|
|
|
|
// See if this represents a change for the entity
|
|
int flags = 0;
|
|
|
|
QAngle angdiff = entity->GetAbsAngles() - ang;
|
|
Vector orgdiff = entity->GetAbsOrigin() - org;
|
|
|
|
// Always remember the pristine simulation time in case we need to restore it.
|
|
restore->m_flSimulationTime = entity->GetSimulationTime();
|
|
|
|
if ( angdiff.LengthSqr() > LAG_COMPENSATION_EPS_SQR )
|
|
{
|
|
flags |= LC_ANGLES_CHANGED;
|
|
restore->m_vecAngles = entity->GetAbsAngles();
|
|
entity->SetAbsAngles( ang );
|
|
change->m_vecAngles = ang;
|
|
}
|
|
|
|
// Use absolute equality here
|
|
if ( ( mins != entity->WorldAlignMins() ) ||
|
|
( maxs != entity->WorldAlignMaxs() ) )
|
|
{
|
|
flags |= LC_SIZE_CHANGED;
|
|
restore->m_vecMins = entity->WorldAlignMins() ;
|
|
restore->m_vecMaxs = entity->WorldAlignMaxs();
|
|
entity->SetSize( mins, maxs );
|
|
change->m_vecMins = mins;
|
|
change->m_vecMaxs = maxs;
|
|
}
|
|
|
|
// Note, do origin at end since it causes a relink into the k/d tree
|
|
if ( orgdiff.LengthSqr() > LAG_COMPENSATION_EPS_SQR )
|
|
{
|
|
flags |= LC_ORIGIN_CHANGED;
|
|
restore->m_vecOrigin = entity->GetAbsOrigin();
|
|
entity->SetAbsOrigin( org );
|
|
change->m_vecOrigin = org;
|
|
}
|
|
|
|
bool skipAnims = false;
|
|
switch (m_lagCompensationType)
|
|
{
|
|
case LAG_COMPENSATE_BOUNDS:
|
|
skipAnims = true;
|
|
break;
|
|
case LAG_COMPENSATE_HITBOXES:
|
|
skipAnims = false;
|
|
break;
|
|
case LAG_COMPENSATE_HITBOXES_ALONG_RAY:
|
|
skipAnims = false;
|
|
{
|
|
Vector forward;
|
|
AngleVectors( m_weaponAngles, &forward );
|
|
|
|
const float maxRange = 100.0f;
|
|
float range = DistanceToRay( entity->WorldSpaceCenter(), m_weaponPos, m_weaponPos + forward * m_weaponRange );
|
|
if ( range < 0.0f || range > maxRange )
|
|
{
|
|
skipAnims = true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if( !skipAnims && wantsAnims && entity->GetBaseAnimating() )
|
|
{
|
|
CBaseAnimating *pAnimating = entity->GetBaseAnimating();
|
|
|
|
// Sorry for the loss of the optimization for the case of people
|
|
// standing still, but you breathe even on the server.
|
|
// This is quicker than actually comparing all bazillion floats.
|
|
flags |= LC_ANIMATION_CHANGED;
|
|
restore->m_masterSequence = pAnimating->GetSequence();
|
|
restore->m_masterCycle = pAnimating->GetCycle();
|
|
|
|
bool interpolationAllowed = false;
|
|
if( prevRecord && (record->m_masterSequence == prevRecord->m_masterSequence) )
|
|
{
|
|
// If the master state changes, all layers will be invalid too, so don't interp (ya know, interp barely ever happens anyway)
|
|
interpolationAllowed = true;
|
|
}
|
|
|
|
////////////////////////
|
|
// First do the master settings
|
|
bool interpolatedMasters = false;
|
|
if( frac > 0.0f && interpolationAllowed )
|
|
{
|
|
interpolatedMasters = true;
|
|
pAnimating->SetSequence( Lerp( frac, record->m_masterSequence, prevRecord->m_masterSequence ) );
|
|
pAnimating->SetCycle( Lerp( frac, record->m_masterCycle, prevRecord->m_masterCycle ) );
|
|
|
|
if( record->m_masterCycle > prevRecord->m_masterCycle )
|
|
{
|
|
// the older record is higher in frame than the newer, it must have wrapped around from 1 back to 0
|
|
// add one to the newer so it is lerping from .9 to 1.1 instead of .9 to .1, for example.
|
|
float newCycle = Lerp( frac, record->m_masterCycle, prevRecord->m_masterCycle + 1 );
|
|
pAnimating->SetCycle(newCycle < 1 ? newCycle : newCycle - 1 );// and make sure .9 to 1.2 does not end up 1.05
|
|
}
|
|
else
|
|
{
|
|
pAnimating->SetCycle( Lerp( frac, record->m_masterCycle, prevRecord->m_masterCycle ) );
|
|
}
|
|
}
|
|
if( !interpolatedMasters )
|
|
{
|
|
pAnimating->SetSequence(record->m_masterSequence);
|
|
pAnimating->SetCycle(record->m_masterCycle);
|
|
}
|
|
|
|
////////////////////////
|
|
// Now do all the layers
|
|
//
|
|
CBaseAnimatingOverlay *pAnimatingOverlay = entity->GetBaseAnimatingOverlay();
|
|
if ( pAnimatingOverlay )
|
|
{
|
|
int layerCount = pAnimatingOverlay->GetNumAnimOverlays();
|
|
for( int layerIndex = 0; layerIndex < layerCount; ++layerIndex )
|
|
{
|
|
CAnimationLayer *currentLayer = pAnimatingOverlay->GetAnimOverlay(layerIndex);
|
|
if( currentLayer )
|
|
{
|
|
restore->m_layerRecords[layerIndex].m_cycle = currentLayer->m_flCycle;
|
|
restore->m_layerRecords[layerIndex].m_order = currentLayer->m_nOrder;
|
|
restore->m_layerRecords[layerIndex].m_sequence = currentLayer->m_nSequence;
|
|
restore->m_layerRecords[layerIndex].m_weight = currentLayer->m_flWeight;
|
|
|
|
bool interpolated = false;
|
|
if( (frac > 0.0f) && interpolationAllowed )
|
|
{
|
|
LayerRecord &recordsLayerRecord = record->m_layerRecords[layerIndex];
|
|
LayerRecord &prevRecordsLayerRecord = prevRecord->m_layerRecords[layerIndex];
|
|
if( (recordsLayerRecord.m_order == prevRecordsLayerRecord.m_order)
|
|
&& (recordsLayerRecord.m_sequence == prevRecordsLayerRecord.m_sequence)
|
|
)
|
|
{
|
|
// We can't interpolate across a sequence or order change
|
|
interpolated = true;
|
|
if( recordsLayerRecord.m_cycle > prevRecordsLayerRecord.m_cycle )
|
|
{
|
|
// the older record is higher in frame than the newer, it must have wrapped around from 1 back to 0
|
|
// add one to the newer so it is lerping from .9 to 1.1 instead of .9 to .1, for example.
|
|
float newCycle = Lerp( frac, recordsLayerRecord.m_cycle, prevRecordsLayerRecord.m_cycle + 1 );
|
|
currentLayer->m_flCycle = newCycle < 1 ? newCycle : newCycle - 1;// and make sure .9 to 1.2 does not end up 1.05
|
|
}
|
|
else
|
|
{
|
|
currentLayer->m_flCycle = Lerp( frac, recordsLayerRecord.m_cycle, prevRecordsLayerRecord.m_cycle );
|
|
}
|
|
currentLayer->m_nOrder = recordsLayerRecord.m_order;
|
|
currentLayer->m_nSequence = recordsLayerRecord.m_sequence;
|
|
currentLayer->m_flWeight = Lerp( frac, recordsLayerRecord.m_weight, prevRecordsLayerRecord.m_weight );
|
|
}
|
|
}
|
|
if( !interpolated )
|
|
{
|
|
//Either no interp, or interp failed. Just use record.
|
|
currentLayer->m_flCycle = record->m_layerRecords[layerIndex].m_cycle;
|
|
currentLayer->m_nOrder = record->m_layerRecords[layerIndex].m_order;
|
|
currentLayer->m_nSequence = record->m_layerRecords[layerIndex].m_sequence;
|
|
currentLayer->m_flWeight = record->m_layerRecords[layerIndex].m_weight;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !flags )
|
|
return false; // we didn't change anything
|
|
|
|
if ( sv_lagflushbonecache.GetBool() && (flags & LC_ANIMATION_CHANGED) && entity->GetBaseAnimating() )
|
|
entity->GetBaseAnimating()->InvalidateBoneCache();
|
|
|
|
/*
|
|
char text[256]; Q_snprintf( text, sizeof(text), "time %.2f", flTargetTime );
|
|
pPlayer->DrawServerHitboxes( 10 );
|
|
NDebugOverlay::Text( org, text, false, 10 );
|
|
if ( skipAnims )
|
|
{
|
|
NDebugOverlay::EntityBounds( pPlayer, 0, 255, 0, 32, 10 );
|
|
}
|
|
else
|
|
{
|
|
NDebugOverlay::EntityBounds( pPlayer, 255, 0, 0, 32, 10 );
|
|
}
|
|
*/
|
|
|
|
m_bNeedToRestore = true; // we changed at least one entity
|
|
restore->m_fFlags = flags; // we need to restore these flags
|
|
change->m_fFlags = flags; // we have changed these flags
|
|
|
|
if( sv_showlagcompensation.GetInt() == 1 && entity->GetBaseAnimating() )
|
|
{
|
|
entity->GetBaseAnimating()->DrawServerHitboxes(4, true);
|
|
}
|
|
|
|
return true;// Yes, this guy has been backtracked
|
|
}
|
|
|
|
|
|
void CLagCompensationManager::FinishLagCompensation( CBasePlayer *player )
|
|
{
|
|
VPROF_BUDGET_FLAGS( "FinishLagCompensation", VPROF_BUDGETGROUP_OTHER_NETWORKING, BUDGETFLAG_CLIENT|BUDGETFLAG_SERVER );
|
|
|
|
m_isCurrentlyDoingCompensation = false;
|
|
|
|
if ( !m_bNeedToRestore )
|
|
return; // no entity was changed at all
|
|
|
|
// Iterate all active entities
|
|
FOR_EACH_MAP( m_CompensatedEntities, i )
|
|
{
|
|
EntityLagData *ld = m_CompensatedEntities[ i ];
|
|
// entity wasn't changed by lag compensation
|
|
if ( !ld->m_bRestoreEntity )
|
|
continue;
|
|
|
|
CBaseEntity *pEntity = m_CompensatedEntities.Key( i ).Get();
|
|
if ( !pEntity )
|
|
continue;
|
|
|
|
LagRecord *restore = &ld->m_RestoreData;
|
|
LagRecord *change = &ld->m_ChangeData;
|
|
|
|
RestoreEntityFromRecords( pEntity, restore, change, true );
|
|
}
|
|
}
|
|
|
|
void CLagCompensationManager::RecordDataIntoTrack( CBaseEntity *entity, LagRecordList *track, bool wantsAnims )
|
|
{
|
|
Assert( track->Count() < 1000 ); // insanity check
|
|
|
|
// remove all records before that time:
|
|
int flDeadtime = gpGlobals->curtime - sv_maxunlag.GetFloat();
|
|
|
|
// remove tail records that are too old
|
|
int tailIndex = track->Tail();
|
|
while ( track->IsValidIndex( tailIndex ) )
|
|
{
|
|
LagRecord &tail = track->Element( tailIndex );
|
|
|
|
// if tail is within limits, stop
|
|
if ( tail.m_flSimulationTime >= flDeadtime )
|
|
break;
|
|
|
|
// remove tail, get new tail
|
|
track->Remove( tailIndex );
|
|
tailIndex = track->Tail();
|
|
}
|
|
|
|
// check if head has same simulation time
|
|
if ( track->Count() > 0 )
|
|
{
|
|
LagRecord &head = track->Element( track->Head() );
|
|
|
|
// check if player changed simulation time since last time updated
|
|
if ( head.m_flSimulationTime >= entity->GetSimulationTime() )
|
|
return; // don't add new entry for same or older time
|
|
}
|
|
|
|
// add new record to entity track
|
|
LagRecord &record = track->Element( track->AddToHead() );
|
|
|
|
record.m_fFlags = 0;
|
|
if ( entity->IsAlive() )
|
|
{
|
|
record.m_fFlags |= LC_ALIVE;
|
|
}
|
|
|
|
record.m_flSimulationTime = entity->GetSimulationTime();
|
|
record.m_vecAngles = entity->GetAbsAngles();
|
|
record.m_vecOrigin = entity->GetAbsOrigin();
|
|
record.m_vecMaxs = entity->WorldAlignMaxs();
|
|
record.m_vecMins = entity->WorldAlignMins();
|
|
|
|
CBaseAnimating *pAnimating = entity->GetBaseAnimating();
|
|
|
|
if( wantsAnims && pAnimating )
|
|
{
|
|
CBaseAnimatingOverlay *pAnimatingOverlay = entity->GetBaseAnimatingOverlay();
|
|
|
|
if ( pAnimatingOverlay )
|
|
{
|
|
int layerCount = pAnimatingOverlay->GetNumAnimOverlays();
|
|
for( int layerIndex = 0; layerIndex < layerCount; ++layerIndex )
|
|
{
|
|
CAnimationLayer *currentLayer = pAnimatingOverlay->GetAnimOverlay(layerIndex);
|
|
if( currentLayer )
|
|
{
|
|
record.m_layerRecords[layerIndex].m_cycle = currentLayer->m_flCycle;
|
|
record.m_layerRecords[layerIndex].m_order = currentLayer->m_nOrder;
|
|
record.m_layerRecords[layerIndex].m_sequence = currentLayer->m_nSequence;
|
|
record.m_layerRecords[layerIndex].m_weight = currentLayer->m_flWeight;
|
|
}
|
|
}
|
|
}
|
|
record.m_masterSequence = pAnimating->GetSequence();
|
|
record.m_masterCycle = pAnimating->GetCycle();
|
|
}
|
|
}
|
|
void CLagCompensationManager::RestoreEntityFromRecords( CBaseEntity *entity, LagRecord *restore, LagRecord *change, bool wantsAnims )
|
|
{
|
|
bool restoreSimulationTime = false;
|
|
|
|
if ( restore->m_fFlags & LC_SIZE_CHANGED )
|
|
{
|
|
restoreSimulationTime = true;
|
|
|
|
// see if simulation made any changes, if no, then do the restore, otherwise,
|
|
// leave new values in
|
|
if ( entity->WorldAlignMins() == change->m_vecMins &&
|
|
entity->WorldAlignMaxs() == change->m_vecMaxs )
|
|
{
|
|
// Restore it
|
|
entity->SetSize( restore->m_vecMins, restore->m_vecMaxs );
|
|
}
|
|
}
|
|
|
|
if ( restore->m_fFlags & LC_ANGLES_CHANGED )
|
|
{
|
|
restoreSimulationTime = true;
|
|
|
|
if ( entity->GetAbsAngles() == change->m_vecAngles )
|
|
{
|
|
entity->SetAbsAngles( restore->m_vecAngles );
|
|
}
|
|
}
|
|
|
|
if ( restore->m_fFlags & LC_ORIGIN_CHANGED )
|
|
{
|
|
restoreSimulationTime = true;
|
|
|
|
// Okay, let's see if we can do something reasonable with the change
|
|
Vector delta = entity->GetAbsOrigin() - change->m_vecOrigin;
|
|
|
|
// If it moved really far, just leave the entity in the new spot!!!
|
|
if ( delta.LengthSqr() < LAG_COMPENSATION_TELEPORTED_DISTANCE_SQR )
|
|
{
|
|
RestoreEntityTo( entity, restore->m_vecOrigin + delta );
|
|
}
|
|
}
|
|
|
|
CBaseAnimating *pAnimating = entity->GetBaseAnimating();
|
|
|
|
if( wantsAnims && pAnimating )
|
|
{
|
|
if( restore->m_fFlags & LC_ANIMATION_CHANGED )
|
|
{
|
|
restoreSimulationTime = true;
|
|
|
|
pAnimating->SetSequence(restore->m_masterSequence);
|
|
pAnimating->SetCycle(restore->m_masterCycle);
|
|
|
|
CBaseAnimatingOverlay *pAnimatingOverlay = entity->GetBaseAnimatingOverlay();
|
|
if ( pAnimatingOverlay )
|
|
{
|
|
int layerCount = pAnimatingOverlay->GetNumAnimOverlays();
|
|
for( int layerIndex = 0; layerIndex < layerCount; ++layerIndex )
|
|
{
|
|
CAnimationLayer *currentLayer = pAnimatingOverlay->GetAnimOverlay(layerIndex);
|
|
if( currentLayer )
|
|
{
|
|
currentLayer->m_flCycle = restore->m_layerRecords[layerIndex].m_cycle;
|
|
currentLayer->m_nOrder = restore->m_layerRecords[layerIndex].m_order;
|
|
currentLayer->m_nSequence = restore->m_layerRecords[layerIndex].m_sequence;
|
|
currentLayer->m_flWeight = restore->m_layerRecords[layerIndex].m_weight;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( restoreSimulationTime )
|
|
{
|
|
entity->SetSimulationTime( restore->m_flSimulationTime );
|
|
}
|
|
}
|