280 lines
9.2 KiB
C++
280 lines
9.2 KiB
C++
#include "cbase.h"
|
|
#include "asw_lag_compensation.h"
|
|
#include "asw_player.h"
|
|
#include "asw_marine.h"
|
|
#include "inetchannelinfo.h"
|
|
#include "ai_basenpc.h"
|
|
#include "asw_game_resource.h"
|
|
#include "asw_marine_resource.h"
|
|
#include "asw_lag_compensation.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
CUtlVector<CASW_Lag_Compensation*> g_LagCompensatingEntities;
|
|
bool CASW_Lag_Compensation::s_bInLagCompensation = false;
|
|
CBasePlayer* CASW_Lag_Compensation::s_pLagCompensatingPlayer = NULL;
|
|
|
|
ConVar asw_alien_unlag("asw_alien_unlag", "1", 0, "Unlag alien positions by player's ping");
|
|
extern ConVar sv_maxunlag;
|
|
extern ConVar sv_showlagcompensation;
|
|
|
|
CASW_Lag_Compensation::CASW_Lag_Compensation()
|
|
{
|
|
g_LagCompensatingEntities.AddToTail(this);
|
|
|
|
m_iPositionHistoryTail = 0;
|
|
for (int i=0;i<ASW_LAG_NUM_POSITION_HISTORY_SAMPLES;i++)
|
|
{
|
|
m_vecPositionHistory[i] = Vector(0,0,0);
|
|
m_fPositionHistoryTime[i] = 0;
|
|
}
|
|
m_vecRealPosition = Vector(0,0,0);
|
|
m_bSetRealPosition = false;
|
|
m_fRealSimulationTime = 0;
|
|
m_hOwnerEntity = NULL;
|
|
}
|
|
|
|
CASW_Lag_Compensation::~CASW_Lag_Compensation()
|
|
{
|
|
g_LagCompensatingEntities.FindAndRemove(this);
|
|
}
|
|
|
|
void CASW_Lag_Compensation::Init(CBaseAnimating *pOwner)
|
|
{
|
|
m_hOwnerEntity = pOwner;
|
|
}
|
|
|
|
CAI_BaseNPC *CASW_Lag_Compensation::GetOwnerNPC()
|
|
{
|
|
if ( !m_hOwnerEntity.Get() || !m_hOwnerEntity->IsNPC() )
|
|
return NULL;
|
|
|
|
return static_cast<CAI_BaseNPC*>( m_hOwnerEntity.Get() );
|
|
}
|
|
|
|
void CASW_Lag_Compensation::StorePositionHistory()
|
|
{
|
|
if ( !m_hOwnerEntity.Get() || !asw_alien_unlag.GetBool() )
|
|
return;
|
|
if ( GetOwnerNPC() && GetOwnerNPC()->GetSleepState() != AISS_AWAKE )
|
|
return;
|
|
if (gpGlobals->curtime - m_fPositionHistoryTime[m_iPositionHistoryTail] < ASW_LAG_MIN_SAMPLE_TIME_DIFFERENCE)
|
|
return;
|
|
|
|
m_iPositionHistoryTail++;
|
|
if ( m_iPositionHistoryTail >= ASW_LAG_NUM_POSITION_HISTORY_SAMPLES)
|
|
m_iPositionHistoryTail = 0;
|
|
|
|
m_vecPositionHistory[m_iPositionHistoryTail] = m_hOwnerEntity->GetAbsOrigin();
|
|
m_fPositionHistoryTime[m_iPositionHistoryTail] = gpGlobals->curtime;
|
|
}
|
|
|
|
const Vector& CASW_Lag_Compensation::GetLaggedPosition( const float fLaggedTime )
|
|
{
|
|
if ( !m_hOwnerEntity.Get() )
|
|
return vec3_origin;
|
|
if ( GetOwnerNPC() && GetOwnerNPC()->GetSleepState() != AISS_AWAKE )
|
|
return m_hOwnerEntity->GetAbsOrigin();
|
|
|
|
int iCurrentIndex = m_iPositionHistoryTail;
|
|
int iChosenIndex = -1;
|
|
for (int i=0;i<ASW_LAG_NUM_POSITION_HISTORY_SAMPLES;i++) // go through all our history samples and find the oldest one that's just past the lagged time requested
|
|
{
|
|
if (m_fPositionHistoryTime[iCurrentIndex] == 0) // this isn't a real index
|
|
{
|
|
break;
|
|
}
|
|
iChosenIndex = iCurrentIndex; // it's a real sample, so we might use this one
|
|
if (m_fPositionHistoryTime[iCurrentIndex] <= fLaggedTime) // if the sample is behind our lagged time, then stop, we'll use this one
|
|
{
|
|
break;
|
|
}
|
|
iCurrentIndex--; // count the index back
|
|
if (iCurrentIndex == -1) // loop around if we need to
|
|
iCurrentIndex = ASW_LAG_NUM_POSITION_HISTORY_SAMPLES-1;
|
|
}
|
|
|
|
static Vector vecResult;
|
|
vecResult = m_hOwnerEntity->GetAbsOrigin();
|
|
if (iChosenIndex != -1) // if we found an index, then work out what % of the movement we need to do based on the difference between now and the lagged time
|
|
{
|
|
const float base = (gpGlobals->curtime - m_fPositionHistoryTime[iChosenIndex]);
|
|
if (base <= 0) // if our only sample is 'now' then we can't do any lag compensation
|
|
return vecResult;
|
|
const float fFraction = (gpGlobals->curtime - fLaggedTime) / base;
|
|
vecResult = m_hOwnerEntity->GetAbsOrigin() +
|
|
(m_vecPositionHistory[iChosenIndex] - m_hOwnerEntity->GetAbsOrigin()) * fFraction;
|
|
}
|
|
return vecResult;
|
|
}
|
|
|
|
void CASW_Lag_Compensation::MoveToLaggedPosition(const float fLaggedTime)
|
|
{
|
|
if ( !m_hOwnerEntity.Get() )
|
|
return;
|
|
// if entity is attached to a marine, don't do lag compensation (fixes parasites detaching when the marine is shot)
|
|
if ( m_hOwnerEntity->GetMoveParent() && m_hOwnerEntity->GetMoveParent()->Classify() == CLASS_ASW_MARINE )
|
|
return;
|
|
|
|
if ( GetOwnerNPC() && GetOwnerNPC()->GetSleepState() != AISS_AWAKE )
|
|
return;
|
|
|
|
int iCurrentIndex = m_iPositionHistoryTail;
|
|
int iChosenIndex = -1;
|
|
for (int i=0;i<ASW_LAG_NUM_POSITION_HISTORY_SAMPLES;i++) // go through all our history samples and find the oldest one that's just past the lagged time requested
|
|
{
|
|
if (m_fPositionHistoryTime[iCurrentIndex] == 0) // this isn't a real index
|
|
{
|
|
break;
|
|
}
|
|
iChosenIndex = iCurrentIndex; // it's a real sample, so we might use this one
|
|
if (m_fPositionHistoryTime[iCurrentIndex] <= fLaggedTime) // if the sample is behind our lagged time, then stop, we'll use this one
|
|
{
|
|
break;
|
|
}
|
|
iCurrentIndex--; // count the index back
|
|
if (iCurrentIndex == -1) // loop around if we need to
|
|
iCurrentIndex = ASW_LAG_NUM_POSITION_HISTORY_SAMPLES-1;
|
|
}
|
|
|
|
if (iChosenIndex != -1) // if we found an index, then work out what % of the movement we need to do based on the difference between now and the lagged time
|
|
{
|
|
const float base = (gpGlobals->curtime - m_fPositionHistoryTime[iChosenIndex]);
|
|
if (base <= 0) // if our only sample is 'now' then we can't do any lag compensation
|
|
return;
|
|
const float fFraction = (gpGlobals->curtime - fLaggedTime) / base;
|
|
m_vecRealPosition = m_hOwnerEntity->GetAbsOrigin(); // store our real position, so we can restore it once lag compensation is done
|
|
m_fRealSimulationTime = m_hOwnerEntity->GetSimulationTime();
|
|
const Vector vecNewPos = m_vecRealPosition +
|
|
(m_vecPositionHistory[iChosenIndex] - m_vecRealPosition) * fFraction;
|
|
|
|
m_bSetRealPosition = true;
|
|
if (asw_alien_unlag.GetInt() < 2)
|
|
{
|
|
m_hOwnerEntity->SetAbsOrigin(vecNewPos); // move us to the lagged position
|
|
}
|
|
}
|
|
}
|
|
|
|
void CASW_Lag_Compensation::UndoLaggedPosition()
|
|
{
|
|
if ( !m_hOwnerEntity.Get() )
|
|
return;
|
|
// if entity is attached to a marine, don't do lag compensation (fixes parasites detaching when the marine is shot)
|
|
if ( m_hOwnerEntity->GetMoveParent() && m_hOwnerEntity->GetMoveParent()->Classify() == CLASS_ASW_MARINE )
|
|
return;
|
|
|
|
if ( m_bSetRealPosition )
|
|
{
|
|
m_hOwnerEntity->SetAbsOrigin(m_vecRealPosition);
|
|
m_hOwnerEntity->SetSimulationTime(m_fRealSimulationTime);
|
|
m_bSetRealPosition = false;
|
|
}
|
|
}
|
|
|
|
Vector CASW_Lag_Compensation::GetLagCompensationOffset()
|
|
{
|
|
if (!m_bSetRealPosition)
|
|
return vec3_origin;
|
|
|
|
return m_vecRealPosition - m_hOwnerEntity->GetAbsOrigin();
|
|
}
|
|
|
|
void CASW_Lag_Compensation::AllowLagCompensation(CBasePlayer *player)
|
|
{
|
|
s_pLagCompensatingPlayer = player;
|
|
}
|
|
|
|
void CASW_Lag_Compensation::RequestLagCompensation(CASW_Player *player, const CUserCmd *cmd )
|
|
{
|
|
if (player != s_pLagCompensatingPlayer)
|
|
return;
|
|
|
|
if ( !player
|
|
|| !player->m_bLagCompensation // Player not wanting lag compensation
|
|
|| (gpGlobals->maxClients <= 1) // no lag compensation in single player
|
|
|| !asw_alien_unlag.GetBool() // disabled by server admin
|
|
|| player->IsBot() // not for bots
|
|
//|| player->IsObserver() // not for spectators
|
|
)
|
|
return;
|
|
|
|
if (s_bInLagCompensation)
|
|
{
|
|
return;
|
|
}
|
|
|
|
s_bInLagCompensation = true;
|
|
|
|
// 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 );
|
|
}
|
|
|
|
// check the player has enough lag to warrant doing the work of lag compensation
|
|
const float fLaggedTime = TICKS_TO_TIME( targettick );
|
|
if (gpGlobals->curtime - fLaggedTime < ASW_MIN_LAG_TIME)
|
|
return;
|
|
|
|
CASW_Marine *pMarine = player->GetMarine();
|
|
|
|
for (int i=0;i<g_LagCompensatingEntities.Count();i++)
|
|
{
|
|
if ( g_LagCompensatingEntities[i]->m_hOwnerEntity.Get() == pMarine ) // don't lag compensate my own marine
|
|
continue;
|
|
|
|
g_LagCompensatingEntities[i]->MoveToLaggedPosition( fLaggedTime );
|
|
|
|
if( sv_showlagcompensation.GetInt() == 1)
|
|
{
|
|
CBaseAnimating *pAnim = g_LagCompensatingEntities[i]->m_hOwnerEntity.Get();
|
|
if (pAnim)
|
|
pAnim->DrawServerHitboxes(4, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CASW_Lag_Compensation::FinishLagCompensation()
|
|
{
|
|
if (!s_bInLagCompensation)
|
|
{
|
|
return;
|
|
}
|
|
s_bInLagCompensation = false;
|
|
s_pLagCompensatingPlayer = NULL;
|
|
for (int i=0;i<g_LagCompensatingEntities.Count();i++)
|
|
{
|
|
g_LagCompensatingEntities[i]->UndoLaggedPosition();
|
|
}
|
|
} |