diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/player_lagcompensation.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip | |
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/player_lagcompensation.cpp')
| -rw-r--r-- | mp/src/game/server/player_lagcompensation.cpp | 1662 |
1 files changed, 831 insertions, 831 deletions
diff --git a/mp/src/game/server/player_lagcompensation.cpp b/mp/src/game/server/player_lagcompensation.cpp index 7a1402e4..0fafb2a7 100644 --- a/mp/src/game/server/player_lagcompensation.cpp +++ b/mp/src/game/server/player_lagcompensation.cpp @@ -1,831 +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 );
- }
- }
-}
-
-
+//========= 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 ); + } + } +} + + |