aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/player_lagcompensation.cpp
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/server/player_lagcompensation.cpp
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/server/player_lagcompensation.cpp')
-rw-r--r--mp/src/game/server/player_lagcompensation.cpp831
1 files changed, 831 insertions, 0 deletions
diff --git a/mp/src/game/server/player_lagcompensation.cpp b/mp/src/game/server/player_lagcompensation.cpp
new file mode 100644
index 00000000..7a1402e4
--- /dev/null
+++ b/mp/src/game/server/player_lagcompensation.cpp
@@ -0,0 +1,831 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "usercmd.h"
+#include "igamesystem.h"
+#include "ilagcompensationmanager.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)
+
+static ConVar sv_lagcompensation_teleport_dist( "sv_lagcompensation_teleport_dist", "64", FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT, "How far a player got moved by game code before we can't lag compensate their position back" );
+#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_unlag_fixstuck( "sv_unlag_fixstuck", "0", FCVAR_DEVELOPMENTONLY, "Disallow backtracking a player for lag compensation if it will cause them to become stuck" );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+#define MAX_LAYER_RECORDS (CBaseAnimatingOverlay::MAX_OVERLAYS)
+
+struct LayerRecord
+{
+ int m_sequence;
+ float m_cycle;
+ float m_weight;
+ int m_order;
+
+ LayerRecord()
+ {
+ m_sequence = 0;
+ m_cycle = 0;
+ m_weight = 0;
+ m_order = 0;
+ }
+
+ LayerRecord( const LayerRecord& src )
+ {
+ m_sequence = src.m_sequence;
+ m_cycle = src.m_cycle;
+ m_weight = src.m_weight;
+ m_order = src.m_order;
+ }
+};
+
+struct LagRecord
+{
+public:
+ LagRecord()
+ {
+ m_fFlags = 0;
+ m_vecOrigin.Init();
+ m_vecAngles.Init();
+ m_vecMinsPreScaled.Init();
+ m_vecMaxsPreScaled.Init();
+ m_flSimulationTime = -1;
+ m_masterSequence = 0;
+ m_masterCycle = 0;
+ }
+
+ LagRecord( const LagRecord& src )
+ {
+ m_fFlags = src.m_fFlags;
+ m_vecOrigin = src.m_vecOrigin;
+ m_vecAngles = src.m_vecAngles;
+ m_vecMinsPreScaled = src.m_vecMinsPreScaled;
+ m_vecMaxsPreScaled = src.m_vecMaxsPreScaled;
+ m_flSimulationTime = src.m_flSimulationTime;
+ for( int layerIndex = 0; layerIndex < MAX_LAYER_RECORDS; ++layerIndex )
+ {
+ m_layerRecords[layerIndex] = src.m_layerRecords[layerIndex];
+ }
+ m_masterSequence = src.m_masterSequence;
+ m_masterCycle = src.m_masterCycle;
+ }
+
+ // Did player die this frame
+ int m_fFlags;
+
+ // Player position, orientation and bbox
+ Vector m_vecOrigin;
+ QAngle m_vecAngles;
+ Vector m_vecMinsPreScaled;
+ Vector m_vecMaxsPreScaled;
+
+ float m_flSimulationTime;
+
+ // Player animation details, so we can get the legs in the right spot.
+ LayerRecord m_layerRecords[MAX_LAYER_RECORDS];
+ int m_masterSequence;
+ float m_masterCycle;
+};
+
+
+//
+// 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 RestorePlayerTo( CBasePlayer *pPlayer, const Vector &vWantedPos )
+{
+ // Try to move to the wanted position from our current position.
+ trace_t tr;
+ VPROF_BUDGET( "RestorePlayerTo", "CLagCompensationManager" );
+ UTIL_TraceEntity( pPlayer, vWantedPos, vWantedPos, MASK_PLAYERSOLID, pPlayer, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
+ if ( tr.startsolid || tr.allsolid )
+ {
+ if ( sv_unlag_debug.GetBool() )
+ {
+ DevMsg( "RestorePlayerTo() could not restore player position for client \"%s\" ( %.1f %.1f %.1f )\n",
+ pPlayer->GetPlayerName(), vWantedPos.x, vWantedPos.y, vWantedPos.z );
+ }
+
+ UTIL_TraceEntity( pPlayer, pPlayer->GetLocalOrigin(), vWantedPos, MASK_PLAYERSOLID, pPlayer, 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( pPlayer->GetLocalOrigin(), vWantedPos, tr.fraction * g_flFractionScale, vPos );
+ UTIL_SetOrigin( pPlayer, 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.
+ UTIL_SetOrigin( pPlayer, tr.endpos, true );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CLagCompensationManager : public CAutoGameSystemPerFrame, public ILagCompensationManager
+{
+public:
+ CLagCompensationManager( char const *name ) : CAutoGameSystemPerFrame( name ), m_flTeleportDistanceSqr( 64 *64 )
+ {
+ }
+
+ // IServerSystem stuff
+ virtual void Shutdown()
+ {
+ ClearHistory();
+ }
+
+ virtual void LevelShutdownPostEntity()
+ {
+ ClearHistory();
+ }
+
+ // called after entities think
+ virtual void FrameUpdatePostEntityThink();
+
+ // ILagCompensationManager stuff
+
+ // Called during player movement to set up/restore after lag compensation
+ void StartLagCompensation( CBasePlayer *player, CUserCmd *cmd );
+ void FinishLagCompensation( CBasePlayer *player );
+
+private:
+ void BacktrackPlayer( CBasePlayer *player, float flTargetTime );
+
+ void ClearHistory()
+ {
+ for ( int i=0; i<MAX_PLAYERS; i++ )
+ m_PlayerTrack[i].Purge();
+ }
+
+ // keep a list of lag records for each player
+ CUtlFixedLinkedList< LagRecord > m_PlayerTrack[ MAX_PLAYERS ];
+
+ // Scratchpad for determining what needs to be restored
+ CBitVec<MAX_PLAYERS> m_RestorePlayer;
+ bool m_bNeedToRestore;
+
+ LagRecord m_RestoreData[ MAX_PLAYERS ]; // player data before we moved him back
+ LagRecord m_ChangeData[ MAX_PLAYERS ]; // player data where we moved him back
+
+ CBasePlayer *m_pCurrentPlayer; // The player we are doing lag compensation for
+
+ float m_flTeleportDistanceSqr;
+};
+
+static CLagCompensationManager g_LagCompensationManager( "CLagCompensationManager" );
+ILagCompensationManager *lagcompensation = &g_LagCompensationManager;
+
+
+//-----------------------------------------------------------------------------
+// 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;
+ }
+
+ m_flTeleportDistanceSqr = sv_lagcompensation_teleport_dist.GetFloat() * sv_lagcompensation_teleport_dist.GetFloat();
+
+ VPROF_BUDGET( "FrameUpdatePostEntityThink", "CLagCompensationManager" );
+
+ // remove all records before that time:
+ int flDeadtime = gpGlobals->curtime - sv_maxunlag.GetFloat();
+
+ // Iterate all active players
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
+
+ CUtlFixedLinkedList< LagRecord > *track = &m_PlayerTrack[i-1];
+
+ if ( !pPlayer )
+ {
+ if ( track->Count() > 0 )
+ {
+ track->RemoveAll();
+ }
+
+ continue;
+ }
+
+ Assert( track->Count() < 1000 ); // insanity check
+
+ // 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 >= pPlayer->GetSimulationTime() )
+ continue; // don't add new entry for same or older time
+ }
+
+ // add new record to player track
+ LagRecord &record = track->Element( track->AddToHead() );
+
+ record.m_fFlags = 0;
+ if ( pPlayer->IsAlive() )
+ {
+ record.m_fFlags |= LC_ALIVE;
+ }
+
+ record.m_flSimulationTime = pPlayer->GetSimulationTime();
+ record.m_vecAngles = pPlayer->GetLocalAngles();
+ record.m_vecOrigin = pPlayer->GetLocalOrigin();
+ record.m_vecMinsPreScaled = pPlayer->CollisionProp()->OBBMinsPreScaled();
+ record.m_vecMaxsPreScaled = pPlayer->CollisionProp()->OBBMaxsPreScaled();
+
+ int layerCount = pPlayer->GetNumAnimOverlays();
+ for( int layerIndex = 0; layerIndex < layerCount; ++layerIndex )
+ {
+ CAnimationLayer *currentLayer = pPlayer->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 = pPlayer->GetSequence();
+ record.m_masterCycle = pPlayer->GetCycle();
+ }
+
+ //Clear the current player.
+ m_pCurrentPlayer = NULL;
+}
+
+// Called during player movement to set up/restore after lag compensation
+void CLagCompensationManager::StartLagCompensation( CBasePlayer *player, CUserCmd *cmd )
+{
+ //DONT LAG COMP AGAIN THIS FRAME IF THERES ALREADY ONE IN PROGRESS
+ //IF YOU'RE HITTING THIS THEN IT MEANS THERES A CODE BUG
+ if ( m_pCurrentPlayer )
+ {
+ Assert( m_pCurrentPlayer == NULL );
+ Warning( "Trying to start a new lag compensation session while one is already active!\n" );
+ return;
+ }
+
+ // Assume no players need to be restored
+ m_RestorePlayer.ClearAll();
+ 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;
+
+ // NOTE: Put this here so that it won't show up in single player mode.
+ VPROF_BUDGET( "StartLagCompensation", VPROF_BUDGETGROUP_OTHER_NETWORKING );
+ Q_memset( m_RestoreData, 0, sizeof( m_RestoreData ) );
+ Q_memset( m_ChangeData, 0, sizeof( m_ChangeData ) );
+
+ // Get true latency
+
+ // correct is the amout of time we have to correct game time
+ float correct = 0.0f;
+
+ INetChannelInfo *nci = engine->GetPlayerNetInfo( player->entindex() );
+
+ if ( nci )
+ {
+ // add network latency
+ correct+= nci->GetLatency( FLOW_OUTGOING );
+ }
+
+ // calc number of view interpolation ticks - 1
+ int lerpTicks = TIME_TO_TICKS( player->m_fLerpTime );
+
+ // add view interpolation latency see C_BaseEntity::GetInterpolationAmount()
+ correct += TICKS_TO_TIME( lerpTicks );
+
+ // check bouns [0,sv_maxunlag]
+ correct = clamp( correct, 0.0f, sv_maxunlag.GetFloat() );
+
+ // correct tick send by player
+ int targettick = cmd->tick_count - lerpTicks;
+
+ // calc difference between tick send by player and our latency based tick
+ float deltaTime = correct - TICKS_TO_TIME(gpGlobals->tickcount - targettick);
+
+ 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 );
+ targettick = gpGlobals->tickcount - TIME_TO_TICKS( correct );
+ }
+
+ // Iterate all active players
+ const CBitVec<MAX_EDICTS> *pEntityTransmitBits = engine->GetEntityTransmitBitsForClient( player->entindex() - 1 );
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
+
+ if ( !pPlayer )
+ {
+ continue;
+ }
+
+ // Don't lag compensate yourself you loser...
+ if ( player == pPlayer )
+ {
+ continue;
+ }
+
+ // Custom checks for if things should lag compensate (based on things like what team the player is on).
+ if ( !player->WantsLagCompensationOnEntity( pPlayer, cmd, pEntityTransmitBits ) )
+ continue;
+
+ // Move other player back in time
+ BacktrackPlayer( pPlayer, TICKS_TO_TIME( targettick ) );
+ }
+}
+
+void CLagCompensationManager::BacktrackPlayer( CBasePlayer *pPlayer, float flTargetTime )
+{
+ Vector org;
+ Vector minsPreScaled;
+ Vector maxsPreScaled;
+ QAngle ang;
+
+ VPROF_BUDGET( "BacktrackPlayer", "CLagCompensationManager" );
+ int pl_index = pPlayer->entindex() - 1;
+
+ // get track history of this player
+ CUtlFixedLinkedList< LagRecord > *track = &m_PlayerTrack[ pl_index ];
+
+ // check if we have at leat one entry
+ if ( track->Count() <= 0 )
+ return;
+
+ int curr = track->Head();
+
+ LagRecord *prevRecord = NULL;
+ LagRecord *record = NULL;
+
+ Vector prevOrg = pPlayer->GetLocalOrigin();
+
+ // 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) )
+ {
+ // player most be alive, lost track
+ return;
+ }
+
+ Vector delta = record->m_vecOrigin - prevOrg;
+ if ( delta.Length2DSqr() > m_flTeleportDistanceSqr )
+ {
+ // lost track, too much difference
+ return;
+ }
+
+ // 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 ( %s )\n", pPlayer->GetPlayerName() );
+ }
+
+ return; // that should never happen
+ }
+
+ 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 );
+ minsPreScaled = Lerp( frac, record->m_vecMinsPreScaled, prevRecord->m_vecMinsPreScaled );
+ maxsPreScaled = Lerp( frac, record->m_vecMaxsPreScaled, prevRecord->m_vecMaxsPreScaled );
+ }
+ else
+ {
+ // we found the exact record or no other record to interpolate with
+ // just copy these values since they are the best we have
+ org = record->m_vecOrigin;
+ ang = record->m_vecAngles;
+ minsPreScaled = record->m_vecMinsPreScaled;
+ maxsPreScaled = record->m_vecMaxsPreScaled;
+ }
+
+ // 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( pPlayer, 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 %s\n", pPlayer->GetPlayerName() );
+
+ CBasePlayer *pHitPlayer = dynamic_cast<CBasePlayer *>( tr.m_pEnt );
+
+ // don't lag compensate the current player
+ if ( pHitPlayer && ( pHitPlayer != m_pCurrentPlayer ) )
+ {
+ // If we haven't backtracked this player, do it now
+ // this deliberately ignores WantsLagCompensationOnEntity.
+ if ( !m_RestorePlayer.Get( pHitPlayer->entindex() - 1 ) )
+ {
+ // prevent recursion - save a copy of m_RestorePlayer,
+ // pretend that this player is off-limits
+ int pl_index = pPlayer->entindex() - 1;
+
+ // Temp turn this flag on
+ m_RestorePlayer.Set( pl_index );
+
+ BacktrackPlayer( pHitPlayer, flTargetTime );
+
+ // Remove the temp flag
+ m_RestorePlayer.Clear( pl_index );
+ }
+ }
+
+ // now trace us back as far as we can go
+ UTIL_TraceEntity( pPlayer, pPlayer->GetLocalOrigin(), org, MASK_PLAYERSOLID, &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( pPlayer->GetLocalOrigin(), 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 player
+ int flags = 0;
+ LagRecord *restore = &m_RestoreData[ pl_index ];
+ LagRecord *change = &m_ChangeData[ pl_index ];
+
+ QAngle angdiff = pPlayer->GetLocalAngles() - ang;
+ Vector orgdiff = pPlayer->GetLocalOrigin() - org;
+
+ // Always remember the pristine simulation time in case we need to restore it.
+ restore->m_flSimulationTime = pPlayer->GetSimulationTime();
+
+ if ( angdiff.LengthSqr() > LAG_COMPENSATION_EPS_SQR )
+ {
+ flags |= LC_ANGLES_CHANGED;
+ restore->m_vecAngles = pPlayer->GetLocalAngles();
+ pPlayer->SetLocalAngles( ang );
+ change->m_vecAngles = ang;
+ }
+
+ // Use absolute equality here
+ if ( minsPreScaled != pPlayer->CollisionProp()->OBBMinsPreScaled() || maxsPreScaled != pPlayer->CollisionProp()->OBBMaxsPreScaled() )
+ {
+ flags |= LC_SIZE_CHANGED;
+
+ restore->m_vecMinsPreScaled = pPlayer->CollisionProp()->OBBMinsPreScaled();
+ restore->m_vecMaxsPreScaled = pPlayer->CollisionProp()->OBBMaxsPreScaled();
+
+ pPlayer->SetSize( minsPreScaled, maxsPreScaled );
+
+ change->m_vecMinsPreScaled = minsPreScaled;
+ change->m_vecMaxsPreScaled = maxsPreScaled;
+ }
+
+ // 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 = pPlayer->GetLocalOrigin();
+ pPlayer->SetLocalOrigin( org );
+ change->m_vecOrigin = org;
+ }
+
+ // 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 = pPlayer->GetSequence();
+ restore->m_masterCycle = pPlayer->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;
+ pPlayer->SetSequence( Lerp( frac, record->m_masterSequence, prevRecord->m_masterSequence ) );
+ pPlayer->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 );
+ pPlayer->SetCycle(newCycle < 1 ? newCycle : newCycle - 1 );// and make sure .9 to 1.2 does not end up 1.05
+ }
+ else
+ {
+ pPlayer->SetCycle( Lerp( frac, record->m_masterCycle, prevRecord->m_masterCycle ) );
+ }
+ }
+ if( !interpolatedMasters )
+ {
+ pPlayer->SetSequence(record->m_masterSequence);
+ pPlayer->SetCycle(record->m_masterCycle);
+ }
+
+ ////////////////////////
+ // Now do all the layers
+ int layerCount = pPlayer->GetNumAnimOverlays();
+ for( int layerIndex = 0; layerIndex < layerCount; ++layerIndex )
+ {
+ CAnimationLayer *currentLayer = pPlayer->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; // we didn't change anything
+
+ if ( sv_lagflushbonecache.GetBool() )
+ pPlayer->InvalidateBoneCache();
+
+ /*char text[256]; Q_snprintf( text, sizeof(text), "time %.2f", flTargetTime );
+ pPlayer->DrawServerHitboxes( 10 );
+ NDebugOverlay::Text( org, text, false, 10 );
+ NDebugOverlay::EntityBounds( pPlayer, 255, 0, 0, 32, 10 ); */
+
+ m_RestorePlayer.Set( pl_index ); //remember that we changed this player
+ m_bNeedToRestore = true; // we changed at least one player
+ restore->m_fFlags = flags; // we need to restore these flags
+ change->m_fFlags = flags; // we have changed these flags
+
+ if( sv_showlagcompensation.GetInt() == 1 )
+ {
+ pPlayer->DrawServerHitboxes(4, true);
+ }
+}
+
+
+void CLagCompensationManager::FinishLagCompensation( CBasePlayer *player )
+{
+ VPROF_BUDGET_FLAGS( "FinishLagCompensation", VPROF_BUDGETGROUP_OTHER_NETWORKING, BUDGETFLAG_CLIENT|BUDGETFLAG_SERVER );
+
+ m_pCurrentPlayer = NULL;
+
+ if ( !m_bNeedToRestore )
+ return; // no player was changed at all
+
+ // Iterate all active players
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ int pl_index = i - 1;
+
+ if ( !m_RestorePlayer.Get( pl_index ) )
+ {
+ // player wasn't changed by lag compensation
+ continue;
+ }
+
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
+ if ( !pPlayer )
+ {
+ continue;
+ }
+
+ LagRecord *restore = &m_RestoreData[ pl_index ];
+ LagRecord *change = &m_ChangeData[ pl_index ];
+
+ 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 ( pPlayer->CollisionProp()->OBBMinsPreScaled() == change->m_vecMinsPreScaled &&
+ pPlayer->CollisionProp()->OBBMaxsPreScaled() == change->m_vecMaxsPreScaled )
+ {
+ // Restore it
+ pPlayer->SetSize( restore->m_vecMinsPreScaled, restore->m_vecMaxsPreScaled );
+ }
+ else
+ {
+ Warning( "Should we really not restore the size?\n" );
+ }
+ }
+
+ if ( restore->m_fFlags & LC_ANGLES_CHANGED )
+ {
+ restoreSimulationTime = true;
+
+ if ( pPlayer->GetLocalAngles() == change->m_vecAngles )
+ {
+ pPlayer->SetLocalAngles( 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 = pPlayer->GetLocalOrigin() - change->m_vecOrigin;
+
+ // If it moved really far, just leave the player in the new spot!!!
+ if ( delta.Length2DSqr() < m_flTeleportDistanceSqr )
+ {
+ RestorePlayerTo( pPlayer, restore->m_vecOrigin + delta );
+ }
+ }
+
+ if( restore->m_fFlags & LC_ANIMATION_CHANGED )
+ {
+ restoreSimulationTime = true;
+
+ pPlayer->SetSequence(restore->m_masterSequence);
+ pPlayer->SetCycle(restore->m_masterCycle);
+
+ int layerCount = pPlayer->GetNumAnimOverlays();
+ for( int layerIndex = 0; layerIndex < layerCount; ++layerIndex )
+ {
+ CAnimationLayer *currentLayer = pPlayer->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 )
+ {
+ pPlayer->SetSimulationTime( restore->m_flSimulationTime );
+ }
+ }
+}
+
+