source-engine/game/client/c_baseentity.cpp
2022-05-01 20:09:55 +03:00

6491 lines
180 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//===========================================================================//
#include "cbase.h"
#include "c_baseentity.h"
#include "prediction.h"
#include "model_types.h"
#include "iviewrender_beams.h"
#include "dlight.h"
#include "iviewrender.h"
#include "view.h"
#include "iefx.h"
#include "c_team.h"
#include "clientmode.h"
#include "usercmd.h"
#include "engine/IEngineSound.h"
#include "engine/IEngineTrace.h"
#include "engine/ivmodelinfo.h"
#include "tier0/vprof.h"
#include "fx_line.h"
#include "interface.h"
#include "materialsystem/imaterialsystem.h"
#include "soundinfo.h"
#include "mathlib/vmatrix.h"
#include "isaverestore.h"
#include "interval.h"
#include "engine/ivdebugoverlay.h"
#include "c_ai_basenpc.h"
#include "apparent_velocity_helper.h"
#include "c_baseanimatingoverlay.h"
#include "tier1/KeyValues.h"
#include "hltvcamera.h"
#include "datacache/imdlcache.h"
#include "toolframework/itoolframework.h"
#include "toolframework_client.h"
#include "decals.h"
#include "cdll_bounded_cvars.h"
#include "inetchannelinfo.h"
#include "proto_version.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#ifdef INTERPOLATEDVAR_PARANOID_MEASUREMENT
int g_nInterpolatedVarsChanged = 0;
bool g_bRestoreInterpolatedVarValues = false;
#endif
static bool g_bWasSkipping = (bool)-1;
static bool g_bWasThreaded =(bool)-1;
static int g_nThreadModeTicks = 0;
void cc_cl_interp_all_changed( IConVar *pConVar, const char *pOldString, float flOldValue )
{
ConVarRef var( pConVar );
if ( var.GetInt() )
{
C_BaseEntityIterator iterator;
C_BaseEntity *pEnt;
while ( (pEnt = iterator.Next()) != NULL )
{
if ( pEnt->ShouldInterpolate() )
{
pEnt->AddToInterpolationList();
}
}
}
}
static ConVar cl_extrapolate( "cl_extrapolate", "1", FCVAR_CHEAT, "Enable/disable extrapolation if interpolation history runs out." );
static ConVar cl_interp_npcs( "cl_interp_npcs", "0.0", FCVAR_USERINFO, "Interpolate NPC positions starting this many seconds in past (or cl_interp, if greater)" );
static ConVar cl_interp_all( "cl_interp_all", "0", 0, "Disable interpolation list optimizations.", 0, 0, 0, 0, cc_cl_interp_all_changed );
ConVar r_drawmodeldecals( "r_drawmodeldecals", "1" );
extern ConVar cl_showerror;
int C_BaseEntity::m_nPredictionRandomSeed = -1;
C_BasePlayer *C_BaseEntity::m_pPredictionPlayer = NULL;
bool C_BaseEntity::s_bAbsQueriesValid = true;
bool C_BaseEntity::s_bAbsRecomputationEnabled = true;
bool C_BaseEntity::s_bInterpolate = true;
bool C_BaseEntity::sm_bDisableTouchFuncs = false; // Disables PhysicsTouch and PhysicsStartTouch function calls
static ConVar r_drawrenderboxes( "r_drawrenderboxes", "0", FCVAR_CHEAT );
static bool g_bAbsRecomputationStack[8];
static unsigned short g_iAbsRecomputationStackPos = 0;
// All the entities that want Interpolate() called on them.
static CUtlLinkedList<C_BaseEntity*, unsigned short> g_InterpolationList;
static CUtlLinkedList<C_BaseEntity*, unsigned short> g_TeleportList;
#if !defined( NO_ENTITY_PREDICTION )
//-----------------------------------------------------------------------------
// Purpose: Maintains a list of predicted or client created entities
//-----------------------------------------------------------------------------
class CPredictableList : public IPredictableList
{
public:
virtual C_BaseEntity *GetPredictable( int slot );
virtual int GetPredictableCount( void );
protected:
void AddToPredictableList( ClientEntityHandle_t add );
void RemoveFromPredictablesList( ClientEntityHandle_t remove );
private:
CUtlVector< ClientEntityHandle_t > m_Predictables;
friend class C_BaseEntity;
};
// Create singleton
static CPredictableList g_Predictables;
IPredictableList *predictables = &g_Predictables;
//-----------------------------------------------------------------------------
// Purpose: Add entity to list
// Input : add -
// Output : int
//-----------------------------------------------------------------------------
void CPredictableList::AddToPredictableList( ClientEntityHandle_t add )
{
// This is a hack to remap slot to index
if ( m_Predictables.Find( add ) != m_Predictables.InvalidIndex() )
{
return;
}
// Add to general list
m_Predictables.AddToTail( add );
// Maintain sort order by entindex
int count = m_Predictables.Size();
if ( count < 2 )
return;
int i, j;
for ( i = 0; i < count; i++ )
{
for ( j = i + 1; j < count; j++ )
{
ClientEntityHandle_t h1 = m_Predictables[ i ];
ClientEntityHandle_t h2 = m_Predictables[ j ];
C_BaseEntity *p1 = cl_entitylist->GetBaseEntityFromHandle( h1 );
C_BaseEntity *p2 = cl_entitylist->GetBaseEntityFromHandle( h2 );
if ( !p1 || !p2 )
{
Assert( 0 );
continue;
}
if ( p1->entindex() != -1 &&
p2->entindex() != -1 )
{
if ( p1->entindex() < p2->entindex() )
continue;
}
if ( p2->entindex() == -1 )
continue;
m_Predictables[ i ] = h2;
m_Predictables[ j ] = h1;
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : remove -
//-----------------------------------------------------------------------------
void CPredictableList::RemoveFromPredictablesList( ClientEntityHandle_t remove )
{
m_Predictables.FindAndRemove( remove );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : slot -
// Output : C_BaseEntity
//-----------------------------------------------------------------------------
C_BaseEntity *CPredictableList::GetPredictable( int slot )
{
return cl_entitylist->GetBaseEntityFromHandle( m_Predictables[ slot ] );
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : int
//-----------------------------------------------------------------------------
int CPredictableList::GetPredictableCount( void )
{
return m_Predictables.Count();
}
//-----------------------------------------------------------------------------
// Purpose: Searc predictables for previously created entity (by testId)
// Input : testId -
// Output : static C_BaseEntity
//-----------------------------------------------------------------------------
static C_BaseEntity *FindPreviouslyCreatedEntity( CPredictableId& testId )
{
int c = predictables->GetPredictableCount();
int i;
for ( i = 0; i < c; i++ )
{
C_BaseEntity *e = predictables->GetPredictable( i );
if ( !e || !e->IsClientCreated() )
continue;
// Found it, note use of operator ==
if ( testId == e->m_PredictableID )
{
return e;
}
}
return NULL;
}
#endif
abstract_class IRecordingList
{
public:
virtual ~IRecordingList() {};
virtual void AddToList( ClientEntityHandle_t add ) = 0;
virtual void RemoveFromList( ClientEntityHandle_t remove ) = 0;
virtual int Count() = 0;
virtual IClientRenderable *Get( int index ) = 0;
};
class CRecordingList : public IRecordingList
{
public:
virtual void AddToList( ClientEntityHandle_t add );
virtual void RemoveFromList( ClientEntityHandle_t remove );
virtual int Count();
IClientRenderable *Get( int index );
private:
CUtlVector< ClientEntityHandle_t > m_Recording;
};
static CRecordingList g_RecordingList;
IRecordingList *recordinglist = &g_RecordingList;
//-----------------------------------------------------------------------------
// Purpose: Add entity to list
// Input : add -
// Output : int
//-----------------------------------------------------------------------------
void CRecordingList::AddToList( ClientEntityHandle_t add )
{
// This is a hack to remap slot to index
if ( m_Recording.Find( add ) != m_Recording.InvalidIndex() )
{
return;
}
// Add to general list
m_Recording.AddToTail( add );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : remove -
//-----------------------------------------------------------------------------
void CRecordingList::RemoveFromList( ClientEntityHandle_t remove )
{
m_Recording.FindAndRemove( remove );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : slot -
// Output : IClientRenderable
//-----------------------------------------------------------------------------
IClientRenderable *CRecordingList::Get( int index )
{
return cl_entitylist->GetClientRenderableFromHandle( m_Recording[ index ] );
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : int
//-----------------------------------------------------------------------------
int CRecordingList::Count()
{
return m_Recording.Count();
}
// Should these be somewhere else?
#define PITCH 0
//-----------------------------------------------------------------------------
// Purpose: Decodes animtime and notes when it changes
// Input : *pStruct - ( C_BaseEntity * ) used to flag animtime is changine
// *pVarData -
// *pIn -
// objectID -
//-----------------------------------------------------------------------------
void RecvProxy_AnimTime( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
C_BaseEntity *pEntity = ( C_BaseEntity * )pStruct;
Assert( pOut == &pEntity->m_flAnimTime );
int t;
int tickbase;
int addt;
// Unpack the data.
addt = pData->m_Value.m_Int;
// Note, this needs to be encoded relative to packet timestamp, not raw client clock
tickbase = gpGlobals->GetNetworkBase( gpGlobals->tickcount, pEntity->entindex() );
t = tickbase;
// and then go back to floating point time.
t += addt; // Add in an additional up to 256 100ths from the server
// center m_flAnimTime around current time.
while (t < gpGlobals->tickcount - 127)
t += 256;
while (t > gpGlobals->tickcount + 127)
t -= 256;
pEntity->m_flAnimTime = ( t * TICK_INTERVAL );
}
void RecvProxy_SimulationTime( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
C_BaseEntity *pEntity = ( C_BaseEntity * )pStruct;
Assert( pOut == &pEntity->m_flSimulationTime );
int t;
int tickbase;
int addt;
// Unpack the data.
addt = pData->m_Value.m_Int;
// Note, this needs to be encoded relative to packet timestamp, not raw client clock
tickbase = gpGlobals->GetNetworkBase( gpGlobals->tickcount, pEntity->entindex() );
t = tickbase;
// and then go back to floating point time.
t += addt; // Add in an additional up to 256 100ths from the server
// center m_flSimulationTime around current time.
while (t < gpGlobals->tickcount - 127)
t += 256;
while (t > gpGlobals->tickcount + 127)
t -= 256;
pEntity->m_flSimulationTime = ( t * TICK_INTERVAL );
}
void RecvProxy_LocalVelocity( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
CBaseEntity *pEnt = (CBaseEntity *)pStruct;
Vector vecVelocity;
vecVelocity.x = pData->m_Value.m_Vector[0];
vecVelocity.y = pData->m_Value.m_Vector[1];
vecVelocity.z = pData->m_Value.m_Vector[2];
// SetLocalVelocity checks to see if the value has changed
pEnt->SetLocalVelocity( vecVelocity );
}
void RecvProxy_ToolRecording( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
if ( !ToolsEnabled() )
return;
CBaseEntity *pEnt = (CBaseEntity *)pStruct;
pEnt->SetToolRecording( pData->m_Value.m_Int != 0 );
}
// Expose it to the engine.
IMPLEMENT_CLIENTCLASS(C_BaseEntity, DT_BaseEntity, CBaseEntity);
static void RecvProxy_MoveType( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
((C_BaseEntity*)pStruct)->SetMoveType( (MoveType_t)(pData->m_Value.m_Int) );
}
static void RecvProxy_MoveCollide( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
((C_BaseEntity*)pStruct)->SetMoveCollide( (MoveCollide_t)(pData->m_Value.m_Int) );
}
static void RecvProxy_Solid( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
((C_BaseEntity*)pStruct)->SetSolid( (SolidType_t)pData->m_Value.m_Int );
}
static void RecvProxy_SolidFlags( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
((C_BaseEntity*)pStruct)->SetSolidFlags( pData->m_Value.m_Int );
}
void RecvProxy_EffectFlags( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
((C_BaseEntity*)pStruct)->SetEffects( pData->m_Value.m_Int );
}
BEGIN_RECV_TABLE_NOBASE( C_BaseEntity, DT_AnimTimeMustBeFirst )
RecvPropInt( RECVINFO(m_flAnimTime), 0, RecvProxy_AnimTime ),
END_RECV_TABLE()
#ifndef NO_ENTITY_PREDICTION
BEGIN_RECV_TABLE_NOBASE( C_BaseEntity, DT_PredictableId )
RecvPropPredictableId( RECVINFO( m_PredictableID ) ),
RecvPropInt( RECVINFO( m_bIsPlayerSimulated ) ),
END_RECV_TABLE()
#endif
BEGIN_RECV_TABLE_NOBASE(C_BaseEntity, DT_BaseEntity)
RecvPropDataTable( "AnimTimeMustBeFirst", 0, 0, &REFERENCE_RECV_TABLE(DT_AnimTimeMustBeFirst) ),
RecvPropInt( RECVINFO(m_flSimulationTime), 0, RecvProxy_SimulationTime ),
RecvPropInt( RECVINFO( m_ubInterpolationFrame ) ),
RecvPropVector( RECVINFO_NAME( m_vecNetworkOrigin, m_vecOrigin ) ),
#if PREDICTION_ERROR_CHECK_LEVEL > 1
RecvPropVector( RECVINFO_NAME( m_angNetworkAngles, m_angRotation ) ),
#else
RecvPropQAngles( RECVINFO_NAME( m_angNetworkAngles, m_angRotation ) ),
#endif
#ifdef DEMO_BACKWARDCOMPATABILITY
RecvPropInt( RECVINFO(m_nModelIndex), 0, RecvProxy_IntToModelIndex16_BackCompatible ),
#else
RecvPropInt( RECVINFO(m_nModelIndex) ),
#endif
RecvPropInt(RECVINFO(m_fEffects), 0, RecvProxy_EffectFlags ),
RecvPropInt(RECVINFO(m_nRenderMode)),
RecvPropInt(RECVINFO(m_nRenderFX)),
RecvPropInt(RECVINFO(m_clrRender)),
RecvPropInt(RECVINFO(m_iTeamNum)),
RecvPropInt(RECVINFO(m_CollisionGroup)),
RecvPropFloat(RECVINFO(m_flElasticity)),
RecvPropFloat(RECVINFO(m_flShadowCastDistance)),
RecvPropEHandle( RECVINFO(m_hOwnerEntity) ),
RecvPropEHandle( RECVINFO(m_hEffectEntity) ),
RecvPropInt( RECVINFO_NAME(m_hNetworkMoveParent, moveparent), 0, RecvProxy_IntToMoveParent ),
RecvPropInt( RECVINFO( m_iParentAttachment ) ),
RecvPropInt( "movetype", 0, SIZEOF_IGNORE, 0, RecvProxy_MoveType ),
RecvPropInt( "movecollide", 0, SIZEOF_IGNORE, 0, RecvProxy_MoveCollide ),
RecvPropDataTable( RECVINFO_DT( m_Collision ), 0, &REFERENCE_RECV_TABLE(DT_CollisionProperty) ),
RecvPropInt( RECVINFO ( m_iTextureFrameIndex ) ),
#if !defined( NO_ENTITY_PREDICTION )
RecvPropDataTable( "predictable_id", 0, 0, &REFERENCE_RECV_TABLE( DT_PredictableId ) ),
#endif
RecvPropInt ( RECVINFO( m_bSimulatedEveryTick ), 0, RecvProxy_InterpolationAmountChanged ),
RecvPropInt ( RECVINFO( m_bAnimatedEveryTick ), 0, RecvProxy_InterpolationAmountChanged ),
RecvPropBool ( RECVINFO( m_bAlternateSorting ) ),
#ifdef TF_CLIENT_DLL
RecvPropArray3( RECVINFO_ARRAY(m_nModelIndexOverrides), RecvPropInt( RECVINFO(m_nModelIndexOverrides[0]) ) ),
#endif
END_RECV_TABLE()
const float coordTolerance = 2.0f / (float)( 1 << COORD_FRACTIONAL_BITS );
BEGIN_PREDICTION_DATA_NO_BASE( C_BaseEntity )
// These have a special proxy to handle send/receive
DEFINE_PRED_TYPEDESCRIPTION( m_Collision, CCollisionProperty ),
DEFINE_PRED_FIELD( m_MoveType, FIELD_CHARACTER, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_MoveCollide, FIELD_CHARACTER, FTYPEDESC_INSENDTABLE ),
DEFINE_FIELD( m_vecAbsVelocity, FIELD_VECTOR ),
DEFINE_PRED_FIELD_TOL( m_vecVelocity, FIELD_VECTOR, FTYPEDESC_INSENDTABLE, 0.5f ),
// DEFINE_PRED_FIELD( m_fEffects, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_nRenderMode, FIELD_CHARACTER, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_nRenderFX, FIELD_CHARACTER, FTYPEDESC_INSENDTABLE ),
// DEFINE_PRED_FIELD( m_flAnimTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
// DEFINE_PRED_FIELD( m_flSimulationTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_fFlags, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD_TOL( m_vecViewOffset, FIELD_VECTOR, FTYPEDESC_INSENDTABLE, 0.25f ),
DEFINE_PRED_FIELD( m_nModelIndex, FIELD_SHORT, FTYPEDESC_INSENDTABLE | FTYPEDESC_MODELINDEX ),
DEFINE_PRED_FIELD( m_flFriction, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_iTeamNum, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_hOwnerEntity, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ),
// DEFINE_FIELD( m_nSimulationTick, FIELD_INTEGER ),
DEFINE_PRED_FIELD( m_hNetworkMoveParent, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ),
// DEFINE_PRED_FIELD( m_pMoveParent, FIELD_EHANDLE ),
// DEFINE_PRED_FIELD( m_pMoveChild, FIELD_EHANDLE ),
// DEFINE_PRED_FIELD( m_pMovePeer, FIELD_EHANDLE ),
// DEFINE_PRED_FIELD( m_pMovePrevPeer, FIELD_EHANDLE ),
DEFINE_PRED_FIELD_TOL( m_vecNetworkOrigin, FIELD_VECTOR, FTYPEDESC_INSENDTABLE, coordTolerance ),
DEFINE_PRED_FIELD( m_angNetworkAngles, FIELD_VECTOR, FTYPEDESC_INSENDTABLE | FTYPEDESC_NOERRORCHECK ),
DEFINE_FIELD( m_vecAbsOrigin, FIELD_VECTOR ),
DEFINE_FIELD( m_angAbsRotation, FIELD_VECTOR ),
DEFINE_FIELD( m_vecOrigin, FIELD_VECTOR ),
DEFINE_FIELD( m_angRotation, FIELD_VECTOR ),
// DEFINE_FIELD( m_hGroundEntity, FIELD_EHANDLE ),
DEFINE_FIELD( m_nWaterLevel, FIELD_CHARACTER ),
DEFINE_FIELD( m_nWaterType, FIELD_CHARACTER ),
DEFINE_FIELD( m_vecAngVelocity, FIELD_VECTOR ),
// DEFINE_FIELD( m_vecAbsAngVelocity, FIELD_VECTOR ),
// DEFINE_FIELD( model, FIELD_INTEGER ), // writing pointer literally
// DEFINE_FIELD( index, FIELD_INTEGER ),
// DEFINE_FIELD( m_ClientHandle, FIELD_SHORT ),
// DEFINE_FIELD( m_Partition, FIELD_SHORT ),
// DEFINE_FIELD( m_hRender, FIELD_SHORT ),
DEFINE_FIELD( m_bDormant, FIELD_BOOLEAN ),
// DEFINE_FIELD( current_position, FIELD_INTEGER ),
// DEFINE_FIELD( m_flLastMessageTime, FIELD_FLOAT ),
DEFINE_FIELD( m_vecBaseVelocity, FIELD_VECTOR ),
DEFINE_FIELD( m_iEFlags, FIELD_INTEGER ),
DEFINE_FIELD( m_flGravity, FIELD_FLOAT ),
// DEFINE_FIELD( m_ModelInstance, FIELD_SHORT ),
DEFINE_FIELD( m_flProxyRandomValue, FIELD_FLOAT ),
// DEFINE_FIELD( m_PredictableID, FIELD_INTEGER ),
// DEFINE_FIELD( m_pPredictionContext, FIELD_POINTER ),
// Stuff specific to rendering and therefore not to be copied back and forth
// DEFINE_PRED_FIELD( m_clrRender, color32, FTYPEDESC_INSENDTABLE ),
// DEFINE_FIELD( m_bReadyToDraw, FIELD_BOOLEAN ),
// DEFINE_FIELD( anim, CLatchedAnim ),
// DEFINE_FIELD( mouth, CMouthInfo ),
// DEFINE_FIELD( GetAbsOrigin(), FIELD_VECTOR ),
// DEFINE_FIELD( GetAbsAngles(), FIELD_VECTOR ),
// DEFINE_FIELD( m_nNumAttachments, FIELD_SHORT ),
// DEFINE_FIELD( m_pAttachmentAngles, FIELD_VECTOR ),
// DEFINE_FIELD( m_pAttachmentOrigin, FIELD_VECTOR ),
// DEFINE_FIELD( m_listentry, CSerialEntity ),
// DEFINE_FIELD( m_ShadowHandle, ClientShadowHandle_t ),
// DEFINE_FIELD( m_hThink, ClientThinkHandle_t ),
// Definitely private and not copied around
// DEFINE_FIELD( m_bPredictable, FIELD_BOOLEAN ),
// DEFINE_FIELD( m_CollisionGroup, FIELD_INTEGER ),
// DEFINE_FIELD( m_DataChangeEventRef, FIELD_INTEGER ),
#if !defined( CLIENT_DLL )
// DEFINE_FIELD( m_bPredictionEligible, FIELD_BOOLEAN ),
#endif
END_PREDICTION_DATA()
//-----------------------------------------------------------------------------
// Helper functions.
//-----------------------------------------------------------------------------
void SpewInterpolatedVar( CInterpolatedVar< Vector > *pVar )
{
Msg( "--------------------------------------------------\n" );
int i = pVar->GetHead();
CApparentVelocity<Vector> apparent;
float prevtime = 0.0f;
while ( 1 )
{
float changetime;
Vector *pVal = pVar->GetHistoryValue( i, changetime );
if ( !pVal )
break;
float vel = apparent.AddSample( changetime, *pVal );
Msg( "%6.6f: (%.2f %.2f %.2f), vel: %.2f [dt %.1f]\n", changetime, VectorExpand( *pVal ), vel, prevtime == 0.0f ? 0.0f : 1000.0f * ( changetime - prevtime ) );
i = pVar->GetNext( i );
prevtime = changetime;
}
Msg( "--------------------------------------------------\n" );
}
void SpewInterpolatedVar( CInterpolatedVar< Vector > *pVar, float flNow, float flInterpAmount, bool bSpewAllEntries = true )
{
float target = flNow - flInterpAmount;
Msg( "--------------------------------------------------\n" );
int i = pVar->GetHead();
CApparentVelocity<Vector> apparent;
float newtime = 999999.0f;
Vector newVec( 0, 0, 0 );
bool bSpew = true;
while ( 1 )
{
float changetime;
Vector *pVal = pVar->GetHistoryValue( i, changetime );
if ( !pVal )
break;
if ( bSpew && target >= changetime )
{
Vector o;
pVar->DebugInterpolate( &o, flNow );
bool bInterp = newtime != 999999.0f;
float frac = 0.0f;
char desc[ 32 ];
if ( bInterp )
{
frac = ( target - changetime ) / ( newtime - changetime );
Q_snprintf( desc, sizeof( desc ), "interpolated [%.2f]", frac );
}
else
{
bSpew = true;
int savei = i;
i = pVar->GetNext( i );
float oldtertime = 0.0f;
pVar->GetHistoryValue( i, oldtertime );
if ( changetime != oldtertime )
{
frac = ( target - changetime ) / ( changetime - oldtertime );
}
Q_snprintf( desc, sizeof( desc ), "extrapolated [%.2f]", frac );
i = savei;
}
if ( bSpew )
{
Msg( " > %6.6f: (%.2f %.2f %.2f) %s for %.1f msec\n",
target,
VectorExpand( o ),
desc,
1000.0f * ( target - changetime ) );
bSpew = false;
}
}
float vel = apparent.AddSample( changetime, *pVal );
if ( bSpewAllEntries )
{
Msg( " %6.6f: (%.2f %.2f %.2f), vel: %.2f [dt %.1f]\n", changetime, VectorExpand( *pVal ), vel, newtime == 999999.0f ? 0.0f : 1000.0f * ( newtime - changetime ) );
}
i = pVar->GetNext( i );
newtime = changetime;
newVec = *pVal;
}
Msg( "--------------------------------------------------\n" );
}
void SpewInterpolatedVar( CInterpolatedVar< float > *pVar )
{
Msg( "--------------------------------------------------\n" );
int i = pVar->GetHead();
CApparentVelocity<float> apparent;
while ( 1 )
{
float changetime;
float *pVal = pVar->GetHistoryValue( i, changetime );
if ( !pVal )
break;
float vel = apparent.AddSample( changetime, *pVal );
Msg( "%6.6f: (%.2f), vel: %.2f\n", changetime, *pVal, vel );
i = pVar->GetNext( i );
}
Msg( "--------------------------------------------------\n" );
}
template<class T>
void GetInterpolatedVarTimeRange( CInterpolatedVar<T> *pVar, float &flMin, float &flMax )
{
flMin = 1e23;
flMax = -1e23;
int i = pVar->GetHead();
CApparentVelocity<Vector> apparent;
while ( 1 )
{
float changetime;
if ( !pVar->GetHistoryValue( i, changetime ) )
return;
flMin = MIN( flMin, changetime );
flMax = MAX( flMax, changetime );
i = pVar->GetNext( i );
}
}
//-----------------------------------------------------------------------------
// Global methods related to when abs data is correct
//-----------------------------------------------------------------------------
void C_BaseEntity::SetAbsQueriesValid( bool bValid )
{
// @MULTICORE: Always allow in worker threads, assume higher level code is handling correctly
if ( !ThreadInMainThread() )
return;
if ( !bValid )
{
s_bAbsQueriesValid = false;
}
else
{
s_bAbsQueriesValid = true;
}
}
bool C_BaseEntity::IsAbsQueriesValid( void )
{
if ( !ThreadInMainThread() )
return true;
return s_bAbsQueriesValid;
}
void C_BaseEntity::PushEnableAbsRecomputations( bool bEnable )
{
if ( !ThreadInMainThread() )
return;
if ( g_iAbsRecomputationStackPos < ARRAYSIZE( g_bAbsRecomputationStack ) )
{
g_bAbsRecomputationStack[g_iAbsRecomputationStackPos] = s_bAbsRecomputationEnabled;
++g_iAbsRecomputationStackPos;
s_bAbsRecomputationEnabled = bEnable;
}
else
{
Assert( false );
}
}
void C_BaseEntity::PopEnableAbsRecomputations()
{
if ( !ThreadInMainThread() )
return;
if ( g_iAbsRecomputationStackPos > 0 )
{
--g_iAbsRecomputationStackPos;
s_bAbsRecomputationEnabled = g_bAbsRecomputationStack[g_iAbsRecomputationStackPos];
}
else
{
Assert( false );
}
}
void C_BaseEntity::EnableAbsRecomputations( bool bEnable )
{
if ( !ThreadInMainThread() )
return;
// This should only be called at the frame level. Use PushEnableAbsRecomputations
// if you're blocking out a section of code.
Assert( g_iAbsRecomputationStackPos == 0 );
s_bAbsRecomputationEnabled = bEnable;
}
bool C_BaseEntity::IsAbsRecomputationsEnabled()
{
if ( !ThreadInMainThread() )
return true;
return s_bAbsRecomputationEnabled;
}
int C_BaseEntity::GetTextureFrameIndex( void )
{
return m_iTextureFrameIndex;
}
void C_BaseEntity::SetTextureFrameIndex( int iIndex )
{
m_iTextureFrameIndex = iIndex;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *map -
//-----------------------------------------------------------------------------
void C_BaseEntity::Interp_SetupMappings( VarMapping_t *map )
{
if( !map )
return;
int c = map->m_Entries.Count();
for ( int i = 0; i < c; i++ )
{
VarMapEntry_t *e = &map->m_Entries[ i ];
IInterpolatedVar *watcher = e->watcher;
void *data = e->data;
int type = e->type;
watcher->Setup( data, type );
watcher->SetInterpolationAmount( GetInterpolationAmount( watcher->GetType() ) );
}
}
void C_BaseEntity::Interp_RestoreToLastNetworked( VarMapping_t *map )
{
VPROF( "C_BaseEntity::Interp_RestoreToLastNetworked" );
PREDICTION_TRACKVALUECHANGESCOPE_ENTITY( this, "restoretolastnetworked" );
Vector oldOrigin = GetLocalOrigin();
QAngle oldAngles = GetLocalAngles();
Vector oldVel = GetLocalVelocity();
int c = map->m_Entries.Count();
for ( int i = 0; i < c; i++ )
{
VarMapEntry_t *e = &map->m_Entries[ i ];
IInterpolatedVar *watcher = e->watcher;
watcher->RestoreToLastNetworked();
}
BaseInterpolatePart2( oldOrigin, oldAngles, oldVel, 0 );
}
void C_BaseEntity::Interp_UpdateInterpolationAmounts( VarMapping_t *map )
{
if( !map )
return;
int c = map->m_Entries.Count();
for ( int i = 0; i < c; i++ )
{
VarMapEntry_t *e = &map->m_Entries[ i ];
IInterpolatedVar *watcher = e->watcher;
watcher->SetInterpolationAmount( GetInterpolationAmount( watcher->GetType() ) );
}
}
void C_BaseEntity::Interp_HierarchyUpdateInterpolationAmounts()
{
Interp_UpdateInterpolationAmounts( GetVarMapping() );
for ( C_BaseEntity *pChild = FirstMoveChild(); pChild; pChild = pChild->NextMovePeer() )
{
pChild->Interp_HierarchyUpdateInterpolationAmounts();
}
}
inline int C_BaseEntity::Interp_Interpolate( VarMapping_t *map, float currentTime )
{
int bNoMoreChanges = 1;
if ( currentTime < map->m_lastInterpolationTime )
{
for ( int i = 0; i < map->m_nInterpolatedEntries; i++ )
{
VarMapEntry_t *e = &map->m_Entries[ i ];
e->m_bNeedsToInterpolate = true;
}
}
map->m_lastInterpolationTime = currentTime;
for ( int i = 0; i < map->m_nInterpolatedEntries; i++ )
{
VarMapEntry_t *e = &map->m_Entries[ i ];
if ( !e->m_bNeedsToInterpolate )
continue;
IInterpolatedVar *watcher = e->watcher;
Assert( !( watcher->GetType() & EXCLUDE_AUTO_INTERPOLATE ) );
if ( watcher->Interpolate( currentTime ) )
e->m_bNeedsToInterpolate = false;
else
bNoMoreChanges = 0;
}
return bNoMoreChanges;
}
//-----------------------------------------------------------------------------
// Functions.
//-----------------------------------------------------------------------------
C_BaseEntity::C_BaseEntity() :
m_iv_vecOrigin( "C_BaseEntity::m_iv_vecOrigin" ),
m_iv_angRotation( "C_BaseEntity::m_iv_angRotation" ),
m_iv_vecVelocity( "C_BaseEntity::m_iv_vecVelocity" )
{
AddVar( &m_vecOrigin, &m_iv_vecOrigin, LATCH_SIMULATION_VAR );
AddVar( &m_angRotation, &m_iv_angRotation, LATCH_SIMULATION_VAR );
// Removing this until we figure out why velocity introduces view hitching.
// One possible fix is removing the player->ResetLatched() call in CGameMovement::FinishDuck(),
// but that re-introduces a third-person hitching bug. One possible cause is the abrupt change
// in player size/position that occurs when ducking, and how prediction tries to work through that.
//
// AddVar( &m_vecVelocity, &m_iv_vecVelocity, LATCH_SIMULATION_VAR );
m_DataChangeEventRef = -1;
m_EntClientFlags = 0;
m_bEnableRenderingClipPlane = false;
m_iParentAttachment = 0;
m_nRenderFXBlend = 255;
SetPredictionEligible( false );
m_bPredictable = false;
m_bSimulatedEveryTick = false;
m_bAnimatedEveryTick = false;
m_pPhysicsObject = NULL;
#ifdef _DEBUG
m_vecAbsOrigin = vec3_origin;
m_angAbsRotation = vec3_angle;
m_vecNetworkOrigin.Init();
m_angNetworkAngles.Init();
m_vecAbsOrigin.Init();
// m_vecAbsAngVelocity.Init();
m_vecVelocity.Init();
m_vecAbsVelocity.Init();
m_vecViewOffset.Init();
m_vecBaseVelocity.Init();
m_iCurrentThinkContext = NO_THINK_CONTEXT;
#endif
m_nSimulationTick = -1;
// Assume drawing everything
m_bReadyToDraw = true;
m_flProxyRandomValue = 0.0f;
m_fBBoxVisFlags = 0;
#if !defined( NO_ENTITY_PREDICTION )
m_pPredictionContext = NULL;
#endif
//NOTE: not virtual! we are in the constructor!
C_BaseEntity::Clear();
SetModelName( NULL_STRING );
m_iClassname = NULL_STRING;
m_InterpolationListEntry = 0xFFFF;
m_TeleportListEntry = 0xFFFF;
#ifndef NO_TOOLFRAMEWORK
m_bEnabledInToolView = true;
m_bToolRecording = false;
m_ToolHandle = 0;
m_nLastRecordedFrame = -1;
m_bRecordInTools = true;
#endif
#ifdef TF_CLIENT_DLL
m_bValidatedOwner = false;
m_bDeemedInvalid = false;
m_bWasDeemedInvalid = false;
#endif
ParticleProp()->Init( this );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
C_BaseEntity::~C_BaseEntity()
{
Term();
ClearDataChangedEvent( m_DataChangeEventRef );
#if !defined( NO_ENTITY_PREDICTION )
delete m_pPredictionContext;
#endif
RemoveFromInterpolationList();
RemoveFromTeleportList();
}
void C_BaseEntity::Clear( void )
{
m_bDormant = true;
m_nCreationTick = -1;
m_RefEHandle.Term();
m_ModelInstance = MODEL_INSTANCE_INVALID;
m_ShadowHandle = CLIENTSHADOW_INVALID_HANDLE;
m_hRender = INVALID_CLIENT_RENDER_HANDLE;
m_hThink = INVALID_THINK_HANDLE;
m_AimEntsListHandle = INVALID_AIMENTS_LIST_HANDLE;
index = -1;
m_Collision.Init( this );
SetLocalOrigin( vec3_origin );
SetLocalAngles( vec3_angle );
model = NULL;
m_pOriginalData = NULL;
m_vecAbsOrigin.Init();
m_angAbsRotation.Init();
m_vecVelocity.Init();
ClearFlags();
m_vecViewOffset.Init();
m_vecBaseVelocity.Init();
m_nModelIndex = 0;
m_flAnimTime = 0;
m_flSimulationTime = 0;
SetSolid( SOLID_NONE );
SetSolidFlags( 0 );
SetMoveCollide( MOVECOLLIDE_DEFAULT );
SetMoveType( MOVETYPE_NONE );
ClearEffects();
m_iEFlags = 0;
m_nRenderMode = 0;
m_nOldRenderMode = 0;
SetRenderColor( 255, 255, 255, 255 );
m_nRenderFX = 0;
m_flFriction = 0.0f;
m_flGravity = 0.0f;
SetCheckUntouch( false );
m_ShadowDirUseOtherEntity = NULL;
m_nLastThinkTick = gpGlobals->tickcount;
#if defined(SIXENSE)
m_vecEyeOffset.Init();
m_EyeAngleOffset.Init();
#endif
// Remove prediction context if it exists
#if !defined( NO_ENTITY_PREDICTION )
delete m_pPredictionContext;
m_pPredictionContext = NULL;
#endif
// Do not enable this on all entities. It forces bone setup for entities that
// don't need it.
//AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID );
UpdateVisibility();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_BaseEntity::Spawn( void )
{
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_BaseEntity::Activate()
{
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_BaseEntity::SpawnClientEntity( void )
{
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_BaseEntity::Precache( void )
{
}
//-----------------------------------------------------------------------------
// Purpose: Attach to entity
// Input : *pEnt -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool C_BaseEntity::Init( int entnum, int iSerialNum )
{
Assert( entnum >= 0 && entnum < NUM_ENT_ENTRIES );
index = entnum;
cl_entitylist->AddNetworkableEntity( GetIClientUnknown(), entnum, iSerialNum );
CollisionProp()->CreatePartitionHandle();
Interp_SetupMappings( GetVarMapping() );
m_nCreationTick = gpGlobals->tickcount;
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_BaseEntity::InitializeAsClientEntity( const char *pszModelName, RenderGroup_t renderGroup )
{
int nModelIndex;
if ( pszModelName != NULL )
{
nModelIndex = modelinfo->GetModelIndex( pszModelName );
if ( nModelIndex == -1 )
{
// Model could not be found
Assert( !"Model could not be found, index is -1" );
return false;
}
}
else
{
nModelIndex = -1;
}
Interp_SetupMappings( GetVarMapping() );
return InitializeAsClientEntityByIndex( nModelIndex, renderGroup );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_BaseEntity::InitializeAsClientEntityByIndex( int iIndex, RenderGroup_t renderGroup )
{
index = -1;
// Setup model data.
SetModelByIndex( iIndex );
// Add the client entity to the master entity list.
cl_entitylist->AddNonNetworkableEntity( GetIClientUnknown() );
Assert( GetClientHandle() != ClientEntityList().InvalidHandle() );
// Add the client entity to the renderable "leaf system." (Renderable)
AddToLeafSystem( renderGroup );
// Add the client entity to the spatial partition. (Collidable)
CollisionProp()->CreatePartitionHandle();
SpawnClientEntity();
return true;
}
void C_BaseEntity::Term()
{
C_BaseEntity::PhysicsRemoveTouchedList( this );
C_BaseEntity::PhysicsRemoveGroundList( this );
DestroyAllDataObjects();
#if !defined( NO_ENTITY_PREDICTION )
// Remove from the predictables list
if ( GetPredictable() || IsClientCreated() )
{
g_Predictables.RemoveFromPredictablesList( GetClientHandle() );
}
// If it's play simulated, remove from simulation list if the player still exists...
if ( IsPlayerSimulated() && C_BasePlayer::GetLocalPlayer() )
{
C_BasePlayer::GetLocalPlayer()->RemoveFromPlayerSimulationList( this );
}
#endif
if ( GetClientHandle() != INVALID_CLIENTENTITY_HANDLE )
{
if ( GetThinkHandle() != INVALID_THINK_HANDLE )
{
ClientThinkList()->RemoveThinkable( GetClientHandle() );
}
// Remove from the client entity list.
ClientEntityList().RemoveEntity( GetClientHandle() );
m_RefEHandle = INVALID_CLIENTENTITY_HANDLE;
}
// Are we in the partition?
CollisionProp()->DestroyPartitionHandle();
// If Client side only entity index will be -1
if ( index != -1 )
{
beams->KillDeadBeams( this );
}
// Clean up the model instance
DestroyModelInstance();
// Clean up drawing
RemoveFromLeafSystem();
RemoveFromAimEntsList();
}
void C_BaseEntity::SetRefEHandle( const CBaseHandle &handle )
{
m_RefEHandle = handle;
}
const CBaseHandle& C_BaseEntity::GetRefEHandle() const
{
return m_RefEHandle;
}
//-----------------------------------------------------------------------------
// Purpose: Free beams and destroy object
//-----------------------------------------------------------------------------
void C_BaseEntity::Release()
{
{
C_BaseAnimating::AutoAllowBoneAccess boneaccess( true, true );
UnlinkFromHierarchy();
}
// Note that this must be called from here, not the destructor, because otherwise the
// vtable is hosed and the derived classes function is not going to get called!!!
if ( IsIntermediateDataAllocated() )
{
DestroyIntermediateData();
}
UpdateOnRemove();
delete this;
}
//-----------------------------------------------------------------------------
// Only meant to be called from subclasses.
// Returns true if instance valid, false otherwise
//-----------------------------------------------------------------------------
void C_BaseEntity::CreateModelInstance()
{
if ( m_ModelInstance == MODEL_INSTANCE_INVALID )
{
m_ModelInstance = modelrender->CreateInstance( this );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_BaseEntity::DestroyModelInstance()
{
if (m_ModelInstance != MODEL_INSTANCE_INVALID)
{
modelrender->DestroyInstance( m_ModelInstance );
m_ModelInstance = MODEL_INSTANCE_INVALID;
}
}
void C_BaseEntity::SetRemovalFlag( bool bRemove )
{
if (bRemove)
m_iEFlags |= EFL_KILLME;
else
m_iEFlags &= ~EFL_KILLME;
}
//-----------------------------------------------------------------------------
// VPhysics objects..
//-----------------------------------------------------------------------------
int C_BaseEntity::VPhysicsGetObjectList( IPhysicsObject **pList, int listMax )
{
IPhysicsObject *pPhys = VPhysicsGetObject();
if ( pPhys )
{
// multi-object entities must implement this function
Assert( !(pPhys->GetGameFlags() & FVPHYSICS_MULTIOBJECT_ENTITY) );
if ( listMax > 0 )
{
pList[0] = pPhys;
return 1;
}
}
return 0;
}
bool C_BaseEntity::VPhysicsIsFlesh( void )
{
IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
int count = VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
for ( int i = 0; i < count; i++ )
{
int material = pList[i]->GetMaterialIndex();
const surfacedata_t *pSurfaceData = physprops->GetSurfaceData( material );
// Is flesh ?, don't allow pickup
if ( pSurfaceData->game.material == CHAR_TEX_ANTLION || pSurfaceData->game.material == CHAR_TEX_FLESH || pSurfaceData->game.material == CHAR_TEX_BLOODYFLESH || pSurfaceData->game.material == CHAR_TEX_ALIENFLESH )
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Returns the health fraction
//-----------------------------------------------------------------------------
float C_BaseEntity::HealthFraction() const
{
if (GetMaxHealth() == 0)
return 1.0f;
float flFraction = (float)GetHealth() / (float)GetMaxHealth();
flFraction = clamp( flFraction, 0.0f, 1.0f );
return flFraction;
}
//-----------------------------------------------------------------------------
// Purpose: Retrieves the coordinate frame for this entity.
// Input : forward - Receives the entity's forward vector.
// right - Receives the entity's right vector.
// up - Receives the entity's up vector.
//-----------------------------------------------------------------------------
void C_BaseEntity::GetVectors(Vector* pForward, Vector* pRight, Vector* pUp) const
{
// This call is necessary to cause m_rgflCoordinateFrame to be recomputed
const matrix3x4_t &entityToWorld = EntityToWorldTransform();
if (pForward != NULL)
{
MatrixGetColumn( entityToWorld, 0, *pForward );
}
if (pRight != NULL)
{
MatrixGetColumn( entityToWorld, 1, *pRight );
*pRight *= -1.0f;
}
if (pUp != NULL)
{
MatrixGetColumn( entityToWorld, 2, *pUp );
}
}
void C_BaseEntity::UpdateVisibility()
{
#ifdef TF_CLIENT_DLL
// TF prevents drawing of any entity attached to players that aren't items in the inventory of the player.
// This is to prevent servers creating fake cosmetic items and attaching them to players.
if ( !engine->IsPlayingDemo() )
{
static bool bIsStaging = ( engine->GetAppID() == 810 );
if ( !m_bValidatedOwner )
{
bool bRetry = false;
// Check it the first time we call update visibility (Source TV doesn't bother doing validation)
m_bDeemedInvalid = engine->IsHLTV() ? false : !ValidateEntityAttachedToPlayer( bRetry );
m_bValidatedOwner = !bRetry;
}
if ( m_bDeemedInvalid )
{
if ( bIsStaging )
{
if ( !m_bWasDeemedInvalid )
{
m_PreviousRenderMode = GetRenderMode();
m_PreviousRenderColor = GetRenderColor();
m_bWasDeemedInvalid = true;
}
SetRenderMode( kRenderTransColor );
SetRenderColor( 255, 0, 0, 200 );
}
else
{
RemoveFromLeafSystem();
return;
}
}
else if ( m_bWasDeemedInvalid )
{
if ( bIsStaging )
{
// We need to fix up the rendering.
SetRenderMode( m_PreviousRenderMode );
SetRenderColor( m_PreviousRenderColor.r, m_PreviousRenderColor.g, m_PreviousRenderColor.b, m_PreviousRenderColor.a );
}
m_bWasDeemedInvalid = false;
}
}
#endif
if ( ShouldDraw() && !IsDormant() && ( !ToolsEnabled() || IsEnabledInToolView() ) )
{
// add/update leafsystem
AddToLeafSystem();
}
else
{
// remove from leaf system
RemoveFromLeafSystem();
}
}
//-----------------------------------------------------------------------------
// Purpose: Returns whether object should render.
//-----------------------------------------------------------------------------
bool C_BaseEntity::ShouldDraw()
{
// Only test this in tf2
#if defined( INVASION_CLIENT_DLL )
// Let the client mode (like commander mode) reject drawing entities.
if (g_pClientMode && !g_pClientMode->ShouldDrawEntity(this) )
return false;
#endif
// Some rendermodes prevent rendering
if ( m_nRenderMode == kRenderNone )
return false;
return (model != 0) && !IsEffectActive(EF_NODRAW) && (index != 0);
}
bool C_BaseEntity::TestCollision( const Ray_t& ray, unsigned int mask, trace_t& trace )
{
return false;
}
bool C_BaseEntity::TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr )
{
return false;
}
//-----------------------------------------------------------------------------
// Used when the collision prop is told to ask game code for the world-space surrounding box
//-----------------------------------------------------------------------------
void C_BaseEntity::ComputeWorldSpaceSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs )
{
// This should only be called if you're using USE_GAME_CODE on the server
// and you forgot to implement the client-side version of this method.
Assert(0);
}
//-----------------------------------------------------------------------------
// Purpose: Derived classes will have to write their own message cracking routines!!!
// Input : length -
// *data -
//-----------------------------------------------------------------------------
void C_BaseEntity::ReceiveMessage( int classID, bf_read &msg )
{
// BaseEntity doesn't have a base class we could relay this message to
Assert( classID == GetClientClass()->m_ClassID );
int messageType = msg.ReadByte();
switch( messageType )
{
case BASEENTITY_MSG_REMOVE_DECALS: RemoveAllDecals();
break;
}
}
void* C_BaseEntity::GetDataTableBasePtr()
{
return this;
}
//-----------------------------------------------------------------------------
// Should this object cast shadows?
//-----------------------------------------------------------------------------
ShadowType_t C_BaseEntity::ShadowCastType()
{
if (IsEffectActive(EF_NODRAW | EF_NOSHADOW))
return SHADOWS_NONE;
int modelType = modelinfo->GetModelType( model );
return (modelType == mod_studio) ? SHADOWS_RENDER_TO_TEXTURE : SHADOWS_NONE;
}
//-----------------------------------------------------------------------------
// Per-entity shadow cast distance + direction
//-----------------------------------------------------------------------------
bool C_BaseEntity::GetShadowCastDistance( float *pDistance, ShadowType_t shadowType ) const
{
if ( m_flShadowCastDistance != 0.0f )
{
*pDistance = m_flShadowCastDistance;
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
C_BaseEntity *C_BaseEntity::GetShadowUseOtherEntity( void ) const
{
return m_ShadowDirUseOtherEntity;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_BaseEntity::SetShadowUseOtherEntity( C_BaseEntity *pEntity )
{
m_ShadowDirUseOtherEntity = pEntity;
}
CInterpolatedVar< QAngle >& C_BaseEntity::GetRotationInterpolator()
{
return m_iv_angRotation;
}
CInterpolatedVar< Vector >& C_BaseEntity::GetOriginInterpolator()
{
return m_iv_vecOrigin;
}
//-----------------------------------------------------------------------------
// Purpose: Return a per-entity shadow cast direction
//-----------------------------------------------------------------------------
bool C_BaseEntity::GetShadowCastDirection( Vector *pDirection, ShadowType_t shadowType ) const
{
if ( m_ShadowDirUseOtherEntity )
return m_ShadowDirUseOtherEntity->GetShadowCastDirection( pDirection, shadowType );
return false;
}
//-----------------------------------------------------------------------------
// Should this object receive shadows?
//-----------------------------------------------------------------------------
bool C_BaseEntity::ShouldReceiveProjectedTextures( int flags )
{
Assert( flags & SHADOW_FLAGS_PROJECTED_TEXTURE_TYPE_MASK );
if ( IsEffectActive( EF_NODRAW ) )
return false;
if( flags & SHADOW_FLAGS_FLASHLIGHT )
{
if ( GetRenderMode() > kRenderNormal && GetRenderColor().a == 0 )
return false;
return true;
}
Assert( flags & SHADOW_FLAGS_SHADOW );
if ( IsEffectActive( EF_NORECEIVESHADOW ) )
return false;
if (modelinfo->GetModelType( model ) == mod_studio)
return false;
return true;
}
//-----------------------------------------------------------------------------
// Shadow-related methods
//-----------------------------------------------------------------------------
bool C_BaseEntity::IsShadowDirty( )
{
return IsEFlagSet( EFL_DIRTY_SHADOWUPDATE );
}
void C_BaseEntity::MarkShadowDirty( bool bDirty )
{
if ( bDirty )
{
AddEFlags( EFL_DIRTY_SHADOWUPDATE );
}
else
{
RemoveEFlags( EFL_DIRTY_SHADOWUPDATE );
}
}
IClientRenderable *C_BaseEntity::GetShadowParent()
{
C_BaseEntity *pParent = GetMoveParent();
return pParent ? pParent->GetClientRenderable() : NULL;
}
IClientRenderable *C_BaseEntity::FirstShadowChild()
{
C_BaseEntity *pChild = FirstMoveChild();
return pChild ? pChild->GetClientRenderable() : NULL;
}
IClientRenderable *C_BaseEntity::NextShadowPeer()
{
C_BaseEntity *pPeer = NextMovePeer();
return pPeer ? pPeer->GetClientRenderable() : NULL;
}
//-----------------------------------------------------------------------------
// Purpose: Returns index into entities list for this entity
// Output : Index
//-----------------------------------------------------------------------------
int C_BaseEntity::entindex( void ) const
{
return index;
}
int C_BaseEntity::GetSoundSourceIndex() const
{
#ifdef _DEBUG
if ( index != -1 )
{
Assert( index == GetRefEHandle().GetEntryIndex() );
}
#endif
return GetRefEHandle().GetEntryIndex();
}
//-----------------------------------------------------------------------------
// Get render origin and angles
//-----------------------------------------------------------------------------
const Vector& C_BaseEntity::GetRenderOrigin( void )
{
return GetAbsOrigin();
}
const QAngle& C_BaseEntity::GetRenderAngles( void )
{
return GetAbsAngles();
}
const matrix3x4_t &C_BaseEntity::RenderableToWorldTransform()
{
return EntityToWorldTransform();
}
IPVSNotify* C_BaseEntity::GetPVSNotifyInterface()
{
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : theMins -
// theMaxs -
//-----------------------------------------------------------------------------
void C_BaseEntity::GetRenderBounds( Vector& theMins, Vector& theMaxs )
{
int nModelType = modelinfo->GetModelType( model );
if (nModelType == mod_studio || nModelType == mod_brush)
{
modelinfo->GetModelRenderBounds( GetModel(), theMins, theMaxs );
}
else
{
// By default, we'll just snack on the collision bounds, transform
// them into entity-space, and call it a day.
if ( GetRenderAngles() == CollisionProp()->GetCollisionAngles() )
{
theMins = CollisionProp()->OBBMins();
theMaxs = CollisionProp()->OBBMaxs();
}
else
{
Assert( CollisionProp()->GetCollisionAngles() == vec3_angle );
if ( IsPointSized() )
{
//theMins = CollisionProp()->GetCollisionOrigin();
//theMaxs = theMins;
theMins = theMaxs = vec3_origin;
}
else
{
// NOTE: This shouldn't happen! Or at least, I haven't run
// into a valid case where it should yet.
// Assert(0);
IRotateAABB( EntityToWorldTransform(), CollisionProp()->OBBMins(), CollisionProp()->OBBMaxs(), theMins, theMaxs );
}
}
}
}
void C_BaseEntity::GetRenderBoundsWorldspace( Vector& mins, Vector& maxs )
{
DefaultRenderBoundsWorldspace( this, mins, maxs );
}
void C_BaseEntity::GetShadowRenderBounds( Vector &mins, Vector &maxs, ShadowType_t shadowType )
{
m_EntClientFlags |= ENTCLIENTFLAG_GETTINGSHADOWRENDERBOUNDS;
GetRenderBounds( mins, maxs );
m_EntClientFlags &= ~ENTCLIENTFLAG_GETTINGSHADOWRENDERBOUNDS;
}
//-----------------------------------------------------------------------------
// Purpose: Last received origin
// Output : const float
//-----------------------------------------------------------------------------
const Vector& C_BaseEntity::GetAbsOrigin( void ) const
{
//Assert( s_bAbsQueriesValid );
const_cast<C_BaseEntity*>(this)->CalcAbsolutePosition();
return m_vecAbsOrigin;
}
//-----------------------------------------------------------------------------
// Purpose: Last received angles
// Output : const
//-----------------------------------------------------------------------------
const QAngle& C_BaseEntity::GetAbsAngles( void ) const
{
//Assert( s_bAbsQueriesValid );
const_cast<C_BaseEntity*>(this)->CalcAbsolutePosition();
return m_angAbsRotation;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : org -
//-----------------------------------------------------------------------------
void C_BaseEntity::SetNetworkOrigin( const Vector& org )
{
m_vecNetworkOrigin = org;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : ang -
//-----------------------------------------------------------------------------
void C_BaseEntity::SetNetworkAngles( const QAngle& ang )
{
m_angNetworkAngles = ang;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : index -
//-----------------------------------------------------------------------------
void C_BaseEntity::SetModelIndex( int index )
{
m_nModelIndex = index;
const model_t *pModel = modelinfo->GetModel( m_nModelIndex );
SetModelPointer( pModel );
}
void C_BaseEntity::SetModelPointer( const model_t *pModel )
{
if ( pModel != model )
{
DestroyModelInstance();
model = pModel;
OnNewModel();
UpdateVisibility();
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : val -
// moveCollide -
//-----------------------------------------------------------------------------
void C_BaseEntity::SetMoveType( MoveType_t val, MoveCollide_t moveCollide /*= MOVECOLLIDE_DEFAULT*/ )
{
// Make sure the move type + move collide are compatible...
#ifdef _DEBUG
if ((val != MOVETYPE_FLY) && (val != MOVETYPE_FLYGRAVITY))
{
Assert( moveCollide == MOVECOLLIDE_DEFAULT );
}
#endif
m_MoveType = val;
SetMoveCollide( moveCollide );
}
void C_BaseEntity::SetMoveCollide( MoveCollide_t val )
{
m_MoveCollide = val;
}
//-----------------------------------------------------------------------------
// Purpose: Get rendermode
// Output : int - the render mode
//-----------------------------------------------------------------------------
bool C_BaseEntity::IsTransparent( void )
{
bool modelIsTransparent = modelinfo->IsTranslucent(model);
return modelIsTransparent || (m_nRenderMode != kRenderNormal);
}
bool C_BaseEntity::IsTwoPass( void )
{
return modelinfo->IsTranslucentTwoPass( GetModel() );
}
bool C_BaseEntity::UsesPowerOfTwoFrameBufferTexture()
{
return false;
}
bool C_BaseEntity::UsesFullFrameBufferTexture()
{
return false;
}
bool C_BaseEntity::IgnoresZBuffer( void ) const
{
return m_nRenderMode == kRenderGlow || m_nRenderMode == kRenderWorldGlow;
}
//-----------------------------------------------------------------------------
// Purpose: Get pointer to CMouthInfo data
// Output : CMouthInfo
//-----------------------------------------------------------------------------
CMouthInfo *C_BaseEntity::GetMouth( void )
{
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose: Retrieve sound spatialization info for the specified sound on this entity
// Input : info -
// Output : Return false to indicate sound is not audible
//-----------------------------------------------------------------------------
bool C_BaseEntity::GetSoundSpatialization( SpatializationInfo_t& info )
{
// World is always audible
if ( entindex() == 0 )
{
return true;
}
// Out of PVS
if ( IsDormant() )
{
return false;
}
// pModel might be NULL, but modelinfo can handle that
const model_t *pModel = GetModel();
if ( info.pflRadius )
{
*info.pflRadius = modelinfo->GetModelRadius( pModel );
}
if ( info.pOrigin )
{
*info.pOrigin = GetAbsOrigin();
// move origin to middle of brush
if ( modelinfo->GetModelType( pModel ) == mod_brush )
{
Vector mins, maxs, center;
modelinfo->GetModelBounds( pModel, mins, maxs );
VectorAdd( mins, maxs, center );
VectorScale( center, 0.5f, center );
(*info.pOrigin) += center;
}
}
if ( info.pAngles )
{
VectorCopy( GetAbsAngles(), *info.pAngles );
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Get attachment point by index
// Input : number - which point
// Output : float * - the attachment point
//-----------------------------------------------------------------------------
bool C_BaseEntity::GetAttachment( int number, Vector &origin, QAngle &angles )
{
origin = GetAbsOrigin();
angles = GetAbsAngles();
return true;
}
bool C_BaseEntity::GetAttachment( int number, Vector &origin )
{
origin = GetAbsOrigin();
return true;
}
bool C_BaseEntity::GetAttachment( int number, matrix3x4_t &matrix )
{
MatrixCopy( EntityToWorldTransform(), matrix );
return true;
}
bool C_BaseEntity::GetAttachmentVelocity( int number, Vector &originVel, Quaternion &angleVel )
{
originVel = GetAbsVelocity();
angleVel.Init();
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Get this entity's rendering clip plane if one is defined
// Output : float * - The clip plane to use, or NULL if no clip plane is defined
//-----------------------------------------------------------------------------
float *C_BaseEntity::GetRenderClipPlane( void )
{
if( m_bEnableRenderingClipPlane )
return m_fRenderingClipPlane;
else
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int C_BaseEntity::DrawBrushModel( bool bDrawingTranslucency, int nFlags, bool bTwoPass )
{
VPROF_BUDGET( "C_BaseEntity::DrawBrushModel", VPROF_BUDGETGROUP_BRUSHMODEL_RENDERING );
// Identity brushes are drawn in view->DrawWorld as an optimization
Assert ( modelinfo->GetModelType( model ) == mod_brush );
ERenderDepthMode DepthMode = DEPTH_MODE_NORMAL;
if ( ( nFlags & STUDIO_SSAODEPTHTEXTURE ) != 0 )
{
DepthMode = DEPTH_MODE_SSA0;
}
else if ( ( nFlags & STUDIO_SHADOWDEPTHTEXTURE ) != 0 )
{
DepthMode = DEPTH_MODE_SHADOW;
}
if ( DepthMode != DEPTH_MODE_NORMAL )
{
render->DrawBrushModelShadowDepth( this, (model_t *)model, GetAbsOrigin(), GetAbsAngles(), DepthMode );
}
else
{
DrawBrushModelMode_t mode = DBM_DRAW_ALL;
if ( bTwoPass )
{
mode = bDrawingTranslucency ? DBM_DRAW_TRANSLUCENT_ONLY : DBM_DRAW_OPAQUE_ONLY;
}
render->DrawBrushModelEx( this, (model_t *)model, GetAbsOrigin(), GetAbsAngles(), mode );
}
return 1;
}
//-----------------------------------------------------------------------------
// Purpose: Draws the object
// Input : flags -
//-----------------------------------------------------------------------------
int C_BaseEntity::DrawModel( int flags )
{
if ( !m_bReadyToDraw )
return 0;
int drawn = 0;
if ( !model )
{
return drawn;
}
int modelType = modelinfo->GetModelType( model );
switch ( modelType )
{
case mod_brush:
drawn = DrawBrushModel( flags & STUDIO_TRANSPARENCY ? true : false, flags, ( flags & STUDIO_TWOPASS ) ? true : false );
break;
case mod_studio:
// All studio models must be derived from C_BaseAnimating. Issue warning.
Warning( "ERROR: Can't draw studio model %s because %s is not derived from C_BaseAnimating\n",
modelinfo->GetModelName( model ), GetClientClass()->m_pNetworkName ? GetClientClass()->m_pNetworkName : "unknown" );
break;
case mod_sprite:
//drawn = DrawSprite();
Warning( "ERROR: Sprite model's not supported any more except in legacy temp ents\n" );
break;
default:
break;
}
// If we're visualizing our bboxes, draw them
DrawBBoxVisualizations();
return drawn;
}
//-----------------------------------------------------------------------------
// Purpose: Setup the bones for drawing
//-----------------------------------------------------------------------------
bool C_BaseEntity::SetupBones( matrix3x4_t *pBoneToWorldOut, int nMaxBones, int boneMask, float currentTime )
{
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Setup vertex weights for drawing
//-----------------------------------------------------------------------------
void C_BaseEntity::SetupWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights )
{
}
//-----------------------------------------------------------------------------
// Purpose: Process any local client-side animation events
//-----------------------------------------------------------------------------
void C_BaseEntity::DoAnimationEvents( )
{
}
void C_BaseEntity::UpdatePartitionListEntry()
{
// Don't add the world entity
CollideType_t shouldCollide = GetCollideType();
// Choose the list based on what kind of collisions we want
int list = PARTITION_CLIENT_NON_STATIC_EDICTS;
if (shouldCollide == ENTITY_SHOULD_COLLIDE)
list |= PARTITION_CLIENT_SOLID_EDICTS;
else if (shouldCollide == ENTITY_SHOULD_RESPOND)
list |= PARTITION_CLIENT_RESPONSIVE_EDICTS;
// add the entity to the KD tree so we will collide against it
partition->RemoveAndInsert( PARTITION_CLIENT_SOLID_EDICTS | PARTITION_CLIENT_RESPONSIVE_EDICTS | PARTITION_CLIENT_NON_STATIC_EDICTS, list, CollisionProp()->GetPartitionHandle() );
}
void C_BaseEntity::NotifyShouldTransmit( ShouldTransmitState_t state )
{
// Init should have been called before we get in here.
Assert( CollisionProp()->GetPartitionHandle() != PARTITION_INVALID_HANDLE );
if ( entindex() < 0 )
return;
switch( state )
{
case SHOULDTRANSMIT_START:
{
// We've just been sent by the server. Become active.
SetDormant( false );
UpdatePartitionListEntry();
#if !defined( NO_ENTITY_PREDICTION )
// Note that predictables get a chance to hook up to their server counterparts here
if ( m_PredictableID.IsActive() )
{
// Find corresponding client side predicted entity and remove it from predictables
m_PredictableID.SetAcknowledged( true );
C_BaseEntity *otherEntity = FindPreviouslyCreatedEntity( m_PredictableID );
if ( otherEntity )
{
Assert( otherEntity->IsClientCreated() );
Assert( otherEntity->m_PredictableID.IsActive() );
Assert( ClientEntityList().IsHandleValid( otherEntity->GetClientHandle() ) );
otherEntity->m_PredictableID.SetAcknowledged( true );
if ( OnPredictedEntityRemove( false, otherEntity ) )
{
// Mark it for delete after receive all network data
otherEntity->Release();
}
}
}
#endif
}
break;
case SHOULDTRANSMIT_END:
{
// Clear out links if we're out of the picture...
UnlinkFromHierarchy();
// We're no longer being sent by the server. Become dormant.
SetDormant( true );
// remove the entity from the KD tree so we won't collide against it
partition->Remove( PARTITION_CLIENT_SOLID_EDICTS | PARTITION_CLIENT_RESPONSIVE_EDICTS | PARTITION_CLIENT_NON_STATIC_EDICTS, CollisionProp()->GetPartitionHandle() );
}
break;
default:
Assert( 0 );
break;
}
}
//-----------------------------------------------------------------------------
// Call this in PostDataUpdate if you don't chain it down!
//-----------------------------------------------------------------------------
void C_BaseEntity::MarkMessageReceived()
{
m_flLastMessageTime = engine->GetLastTimeStamp();
}
//-----------------------------------------------------------------------------
// Purpose: Entity is about to be decoded from the network stream
// Input : bnewentity - is this a new entity this update?
//-----------------------------------------------------------------------------
void C_BaseEntity::PreDataUpdate( DataUpdateType_t updateType )
{
VPROF( "C_BaseEntity::PreDataUpdate" );
// Register for an OnDataChanged call and call OnPreDataChanged().
if ( AddDataChangeEvent( this, updateType, &m_DataChangeEventRef ) )
{
OnPreDataChanged( updateType );
}
// Need to spawn on client before receiving original network data
// in case it overrides any values set up in spawn ( e.g., m_iState )
bool bnewentity = (updateType == DATA_UPDATE_CREATED);
if ( !bnewentity )
{
Interp_RestoreToLastNetworked( GetVarMapping() );
}
if ( bnewentity && !IsClientCreated() )
{
m_flSpawnTime = engine->GetLastTimeStamp();
MDLCACHE_CRITICAL_SECTION();
Spawn();
}
#if 0 // Yahn suggesting commenting this out as a fix to demo recording not working
// If the entity moves itself every FRAME on the server but doesn't update animtime,
// then use the current server time as the time for interpolation.
if ( IsSelfAnimating() )
{
m_flAnimTime = engine->GetLastTimeStamp();
}
#endif
m_vecOldOrigin = GetNetworkOrigin();
m_vecOldAngRotation = GetNetworkAngles();
m_flOldAnimTime = m_flAnimTime;
m_flOldSimulationTime = m_flSimulationTime;
m_nOldRenderMode = m_nRenderMode;
if ( m_hRender != INVALID_CLIENT_RENDER_HANDLE )
{
ClientLeafSystem()->EnableAlternateSorting( m_hRender, m_bAlternateSorting );
}
m_ubOldInterpolationFrame = m_ubInterpolationFrame;
}
const Vector& C_BaseEntity::GetOldOrigin()
{
return m_vecOldOrigin;
}
void C_BaseEntity::UnlinkChild( C_BaseEntity *pParent, C_BaseEntity *pChild )
{
Assert( pChild );
Assert( pParent != pChild );
Assert( pChild->GetMoveParent() == pParent );
// Unlink from parent
// NOTE: pParent *may well be NULL*! This occurs
// when a child has unlinked from a parent, and the child
// remains in the PVS but the parent has not
if (pParent && (pParent->m_pMoveChild == pChild))
{
Assert( !(pChild->m_pMovePrevPeer.IsValid()) );
pParent->m_pMoveChild = pChild->m_pMovePeer;
}
// Unlink from siblings...
if (pChild->m_pMovePrevPeer)
{
pChild->m_pMovePrevPeer->m_pMovePeer = pChild->m_pMovePeer;
}
if (pChild->m_pMovePeer)
{
pChild->m_pMovePeer->m_pMovePrevPeer = pChild->m_pMovePrevPeer;
}
pChild->m_pMovePeer = NULL;
pChild->m_pMovePrevPeer = NULL;
pChild->m_pMoveParent = NULL;
pChild->RemoveFromAimEntsList();
Interp_HierarchyUpdateInterpolationAmounts();
}
void C_BaseEntity::LinkChild( C_BaseEntity *pParent, C_BaseEntity *pChild )
{
Assert( !pChild->m_pMovePeer.IsValid() );
Assert( !pChild->m_pMovePrevPeer.IsValid() );
Assert( !pChild->m_pMoveParent.IsValid() );
Assert( pParent != pChild );
#ifdef _DEBUG
// Make sure the child isn't already in this list
C_BaseEntity *pExistingChild;
for ( pExistingChild = pParent->FirstMoveChild(); pExistingChild; pExistingChild = pExistingChild->NextMovePeer() )
{
Assert( pChild != pExistingChild );
}
#endif
pChild->m_pMovePrevPeer = NULL;
pChild->m_pMovePeer = pParent->m_pMoveChild;
if (pChild->m_pMovePeer)
{
pChild->m_pMovePeer->m_pMovePrevPeer = pChild;
}
pParent->m_pMoveChild = pChild;
pChild->m_pMoveParent = pParent;
pChild->AddToAimEntsList();
Interp_HierarchyUpdateInterpolationAmounts();
}
CUtlVector< C_BaseEntity * > g_AimEntsList;
//-----------------------------------------------------------------------------
// Moves all aiments
//-----------------------------------------------------------------------------
void C_BaseEntity::MarkAimEntsDirty()
{
// FIXME: With the dirty bits hooked into cycle + sequence, it's unclear
// that this is even necessary any more (provided aiments are always accessing
// joints or attachments of the move parent).
//
// NOTE: This is a tricky algorithm. This list does not actually contain
// all aim-ents in its list. It actually contains all hierarchical children,
// of which aim-ents are a part. We can tell if something is an aiment if it has
// the EF_BONEMERGE effect flag set.
//
// We will first iterate over all aiments and clear their DIRTY_ABSTRANSFORM flag,
// which is necessary to cause them to recompute their aim-ent origin
// the next time CalcAbsPosition is called. Because CalcAbsPosition calls MoveToAimEnt
// and MoveToAimEnt calls SetAbsOrigin/SetAbsAngles, that is how CalcAbsPosition
// will cause the aim-ent's (and all its children's) dirty state to be correctly updated.
//
// Then we will iterate over the loop a second time and call CalcAbsPosition on them,
int i;
int c = g_AimEntsList.Count();
for ( i = 0; i < c; ++i )
{
C_BaseEntity *pEnt = g_AimEntsList[ i ];
Assert( pEnt && pEnt->GetMoveParent() );
if ( pEnt->IsEffectActive(EF_BONEMERGE | EF_PARENT_ANIMATES) )
{
pEnt->AddEFlags( EFL_DIRTY_ABSTRANSFORM );
}
}
}
void C_BaseEntity::CalcAimEntPositions()
{
VPROF("CalcAimEntPositions");
int i;
int c = g_AimEntsList.Count();
for ( i = 0; i < c; ++i )
{
C_BaseEntity *pEnt = g_AimEntsList[ i ];
Assert( pEnt );
Assert( pEnt->GetMoveParent() );
if ( pEnt->IsEffectActive(EF_BONEMERGE) )
{
pEnt->CalcAbsolutePosition( );
}
}
}
void C_BaseEntity::AddToAimEntsList()
{
// Already in list
if ( m_AimEntsListHandle != INVALID_AIMENTS_LIST_HANDLE )
return;
m_AimEntsListHandle = g_AimEntsList.AddToTail( this );
}
void C_BaseEntity::RemoveFromAimEntsList()
{
// Not in list yet
if ( INVALID_AIMENTS_LIST_HANDLE == m_AimEntsListHandle )
{
return;
}
unsigned int c = g_AimEntsList.Count();
Assert( m_AimEntsListHandle < c );
unsigned int last = c - 1;
if ( last == m_AimEntsListHandle )
{
// Just wipe the final entry
g_AimEntsList.FastRemove( last );
}
else
{
C_BaseEntity *lastEntity = g_AimEntsList[ last ];
// Remove the last entry
g_AimEntsList.FastRemove( last );
// And update it's handle to point to this slot.
lastEntity->m_AimEntsListHandle = m_AimEntsListHandle;
g_AimEntsList[ m_AimEntsListHandle ] = lastEntity;
}
// Invalidate our handle no matter what.
m_AimEntsListHandle = INVALID_AIMENTS_LIST_HANDLE;
}
//-----------------------------------------------------------------------------
// Update move-parent if needed. For SourceTV.
//-----------------------------------------------------------------------------
void C_BaseEntity::HierarchyUpdateMoveParent()
{
if ( m_hNetworkMoveParent.ToInt() == m_pMoveParent.ToInt() )
return;
HierarchySetParent( m_hNetworkMoveParent );
}
//-----------------------------------------------------------------------------
// Connects us up to hierarchy
//-----------------------------------------------------------------------------
void C_BaseEntity::HierarchySetParent( C_BaseEntity *pNewParent )
{
// NOTE: When this is called, we expect to have a valid
// local origin, etc. that we received from network daa
EHANDLE newParentHandle;
newParentHandle.Set( pNewParent );
if (newParentHandle.ToInt() == m_pMoveParent.ToInt())
return;
if (m_pMoveParent.IsValid())
{
UnlinkChild( m_pMoveParent, this );
}
if (pNewParent)
{
LinkChild( pNewParent, this );
}
InvalidatePhysicsRecursive( POSITION_CHANGED | ANGLES_CHANGED | VELOCITY_CHANGED );
#ifdef TF_CLIENT_DLL
m_bValidatedOwner = false;
#endif
}
//-----------------------------------------------------------------------------
// Unlinks from hierarchy
//-----------------------------------------------------------------------------
void C_BaseEntity::SetParent( C_BaseEntity *pParentEntity, int iParentAttachment )
{
// NOTE: This version is meant to be called *outside* of PostDataUpdate
// as it assumes the moveparent has a valid handle
EHANDLE newParentHandle;
newParentHandle.Set( pParentEntity );
if (newParentHandle.ToInt() == m_pMoveParent.ToInt())
return;
// NOTE: Have to do this before the unlink to ensure local coords are valid
Vector vecAbsOrigin = GetAbsOrigin();
QAngle angAbsRotation = GetAbsAngles();
Vector vecAbsVelocity = GetAbsVelocity();
// First deal with unlinking
if (m_pMoveParent.IsValid())
{
UnlinkChild( m_pMoveParent, this );
}
if (pParentEntity)
{
LinkChild( pParentEntity, this );
}
if ( !IsServerEntity() )
{
m_hNetworkMoveParent = pParentEntity;
}
m_iParentAttachment = iParentAttachment;
m_vecAbsOrigin.Init( FLT_MAX, FLT_MAX, FLT_MAX );
m_angAbsRotation.Init( FLT_MAX, FLT_MAX, FLT_MAX );
m_vecAbsVelocity.Init( FLT_MAX, FLT_MAX, FLT_MAX );
SetAbsOrigin(vecAbsOrigin);
SetAbsAngles(angAbsRotation);
SetAbsVelocity(vecAbsVelocity);
}
//-----------------------------------------------------------------------------
// Unlinks from hierarchy
//-----------------------------------------------------------------------------
void C_BaseEntity::UnlinkFromHierarchy()
{
// Clear out links if we're out of the picture...
if ( m_pMoveParent.IsValid() )
{
UnlinkChild( m_pMoveParent, this );
}
//Adrian: This was causing problems with the local network backdoor with entities coming in and out of the PVS at certain times.
//This would work fine if a full entity update was coming (caused by certain factors like too many entities entering the pvs at once).
//but otherwise it would not detect the change on the client (since the server and client shouldn't be out of sync) and the var would not be updated like it should.
//m_iParentAttachment = 0;
// unlink also all move children
C_BaseEntity *pChild = FirstMoveChild();
while( pChild )
{
if ( pChild->m_pMoveParent != this )
{
Warning( "C_BaseEntity::UnlinkFromHierarchy(): Entity has a child with the wrong parent!\n" );
Assert( 0 );
UnlinkChild( this, pChild );
pChild->UnlinkFromHierarchy();
}
else
pChild->UnlinkFromHierarchy();
pChild = FirstMoveChild();
}
}
//-----------------------------------------------------------------------------
// Purpose: Make sure that the correct model is referenced for this entity
//-----------------------------------------------------------------------------
void C_BaseEntity::ValidateModelIndex( void )
{
#ifdef TF_CLIENT_DLL
if ( m_nModelIndexOverrides[VISION_MODE_NONE] > 0 )
{
if ( IsLocalPlayerUsingVisionFilterFlags( TF_VISION_FILTER_HALLOWEEN ) )
{
if ( m_nModelIndexOverrides[VISION_MODE_HALLOWEEN] > 0 )
{
SetModelByIndex( m_nModelIndexOverrides[VISION_MODE_HALLOWEEN] );
return;
}
}
if ( IsLocalPlayerUsingVisionFilterFlags( TF_VISION_FILTER_PYRO ) )
{
if ( m_nModelIndexOverrides[VISION_MODE_PYRO] > 0 )
{
SetModelByIndex( m_nModelIndexOverrides[VISION_MODE_PYRO] );
return;
}
}
if ( IsLocalPlayerUsingVisionFilterFlags( TF_VISION_FILTER_ROME ) )
{
if ( m_nModelIndexOverrides[VISION_MODE_ROME] > 0 )
{
SetModelByIndex( m_nModelIndexOverrides[VISION_MODE_ROME] );
return;
}
}
SetModelByIndex( m_nModelIndexOverrides[VISION_MODE_NONE] );
return;
}
#endif
SetModelByIndex( m_nModelIndex );
}
//-----------------------------------------------------------------------------
// Purpose: Entity data has been parsed and unpacked. Now do any necessary decoding, munging
// Input : bnewentity - was this entity new in this update packet?
//-----------------------------------------------------------------------------
void C_BaseEntity::PostDataUpdate( DataUpdateType_t updateType )
{
MDLCACHE_CRITICAL_SECTION();
PREDICTION_TRACKVALUECHANGESCOPE_ENTITY( this, "postdataupdate" );
// NOTE: This *has* to happen first. Otherwise, Origin + angles may be wrong
if ( m_nRenderFX == kRenderFxRagdoll && updateType == DATA_UPDATE_CREATED )
{
MoveToLastReceivedPosition( true );
}
else
{
MoveToLastReceivedPosition( false );
}
// If it's the world, force solid flags
if ( index == 0 )
{
m_nModelIndex = 1;
SetSolid( SOLID_BSP );
// FIXME: Should these be assertions?
SetAbsOrigin( vec3_origin );
SetAbsAngles( vec3_angle );
}
if ( m_nOldRenderMode != m_nRenderMode )
{
SetRenderMode( (RenderMode_t)m_nRenderMode, true );
}
bool animTimeChanged = ( m_flAnimTime != m_flOldAnimTime ) ? true : false;
bool originChanged = ( m_vecOldOrigin != GetLocalOrigin() ) ? true : false;
bool anglesChanged = ( m_vecOldAngRotation != GetLocalAngles() ) ? true : false;
bool simTimeChanged = ( m_flSimulationTime != m_flOldSimulationTime ) ? true : false;
// Detect simulation changes
bool simulationChanged = originChanged || anglesChanged || simTimeChanged;
bool bPredictable = GetPredictable();
// For non-predicted and non-client only ents, we need to latch network values into the interpolation histories
if ( !bPredictable && !IsClientCreated() )
{
if ( animTimeChanged )
{
OnLatchInterpolatedVariables( LATCH_ANIMATION_VAR );
}
if ( simulationChanged )
{
OnLatchInterpolatedVariables( LATCH_SIMULATION_VAR );
}
}
// For predictables, we also need to store off the last networked value
else if ( bPredictable )
{
// Just store off last networked value for use in prediction
OnStoreLastNetworkedValue();
}
// Deal with hierarchy. Have to do it here (instead of in a proxy)
// because this is the only point at which all entities are loaded
// If this condition isn't met, then a child was sent without its parent
Assert( m_hNetworkMoveParent.Get() || !m_hNetworkMoveParent.IsValid() );
HierarchySetParent(m_hNetworkMoveParent);
MarkMessageReceived();
// Make sure that the correct model is referenced for this entity
ValidateModelIndex();
// If this entity was new, then latch in various values no matter what.
if ( updateType == DATA_UPDATE_CREATED )
{
// Construct a random value for this instance
m_flProxyRandomValue = random->RandomFloat( 0, 1 );
ResetLatched();
m_nCreationTick = gpGlobals->tickcount;
}
CheckInitPredictable( "PostDataUpdate" );
// It's possible that a new entity will need to be forceably added to the
// player simulation list. If so, do this here
#if !defined( NO_ENTITY_PREDICTION )
C_BasePlayer *local = C_BasePlayer::GetLocalPlayer();
if ( IsPlayerSimulated() &&
( NULL != local ) &&
( local == m_hOwnerEntity ) )
{
// Make sure player is driving simulation (field is only ever sent to local player)
SetPlayerSimulated( local );
}
#endif
UpdatePartitionListEntry();
// Add the entity to the nointerp list.
if ( !IsClientCreated() )
{
if ( Teleported() || IsNoInterpolationFrame() )
AddToTeleportList();
}
// if we changed parents, recalculate visibility
if ( m_hOldMoveParent != m_hNetworkMoveParent )
{
UpdateVisibility();
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *context -
//-----------------------------------------------------------------------------
void C_BaseEntity::CheckInitPredictable( const char *context )
{
#if !defined( NO_ENTITY_PREDICTION )
// Prediction is disabled
if ( !cl_predict->GetInt() )
return;
C_BasePlayer *player = C_BasePlayer::GetLocalPlayer();
if ( !player )
return;
if ( !GetPredictionEligible() )
{
if ( m_PredictableID.IsActive() &&
( player->index - 1 ) == m_PredictableID.GetPlayer() )
{
// If it comes through with an ID, it should be eligible
SetPredictionEligible( true );
}
else
{
return;
}
}
if ( IsClientCreated() )
return;
if ( !ShouldPredict() )
return;
if ( IsIntermediateDataAllocated() )
return;
// Msg( "Predicting init %s at %s\n", GetClassname(), context );
InitPredictable();
#endif
}
bool C_BaseEntity::IsSelfAnimating()
{
return true;
}
//-----------------------------------------------------------------------------
// EFlags..
//-----------------------------------------------------------------------------
int C_BaseEntity::GetEFlags() const
{
return m_iEFlags;
}
void C_BaseEntity::SetEFlags( int iEFlags )
{
m_iEFlags = iEFlags;
}
//-----------------------------------------------------------------------------
// Sets the model...
//-----------------------------------------------------------------------------
void C_BaseEntity::SetModelByIndex( int nModelIndex )
{
SetModelIndex( nModelIndex );
}
//-----------------------------------------------------------------------------
// Set model... (NOTE: Should only be used by client-only entities
//-----------------------------------------------------------------------------
bool C_BaseEntity::SetModel( const char *pModelName )
{
if ( pModelName )
{
int nModelIndex = modelinfo->GetModelIndex( pModelName );
SetModelByIndex( nModelIndex );
return ( nModelIndex != -1 );
}
else
{
SetModelByIndex( -1 );
return false;
}
}
void C_BaseEntity::OnStoreLastNetworkedValue()
{
bool bRestore = false;
Vector savePos;
QAngle saveAng;
// Kind of a hack, but we want to latch the actual networked value for origin/angles, not what's sitting in m_vecOrigin in the
// ragdoll case where we don't copy it over in MoveToLastNetworkOrigin
if ( m_nRenderFX == kRenderFxRagdoll && GetPredictable() )
{
bRestore = true;
savePos = GetLocalOrigin();
saveAng = GetLocalAngles();
MoveToLastReceivedPosition( true );
}
int c = m_VarMap.m_Entries.Count();
for ( int i = 0; i < c; i++ )
{
VarMapEntry_t *e = &m_VarMap.m_Entries[ i ];
IInterpolatedVar *watcher = e->watcher;
int type = watcher->GetType();
if ( type & EXCLUDE_AUTO_LATCH )
continue;
watcher->NoteLastNetworkedValue();
}
if ( bRestore )
{
SetLocalOrigin( savePos );
SetLocalAngles( saveAng );
}
}
//-----------------------------------------------------------------------------
// Purpose: The animtime is about to be changed in a network update, store off various fields so that
// we can use them to do blended sequence transitions, etc.
// Input : *pState - the (mostly) previous state data
//-----------------------------------------------------------------------------
void C_BaseEntity::OnLatchInterpolatedVariables( int flags )
{
float changetime = GetLastChangeTime( flags );
bool bUpdateLastNetworkedValue = !(flags & INTERPOLATE_OMIT_UPDATE_LAST_NETWORKED) ? true : false;
PREDICTION_TRACKVALUECHANGESCOPE_ENTITY( this, bUpdateLastNetworkedValue ? "latch+net" : "latch" );
int c = m_VarMap.m_Entries.Count();
for ( int i = 0; i < c; i++ )
{
VarMapEntry_t *e = &m_VarMap.m_Entries[ i ];
IInterpolatedVar *watcher = e->watcher;
int type = watcher->GetType();
if ( !(type & flags) )
continue;
if ( type & EXCLUDE_AUTO_LATCH )
continue;
if ( watcher->NoteChanged( changetime, bUpdateLastNetworkedValue ) )
e->m_bNeedsToInterpolate = true;
}
if ( ShouldInterpolate() )
{
AddToInterpolationList();
}
}
int CBaseEntity::BaseInterpolatePart1( float &currentTime, Vector &oldOrigin, QAngle &oldAngles, Vector &oldVel, int &bNoMoreChanges )
{
// Don't mess with the world!!!
bNoMoreChanges = 1;
// These get moved to the parent position automatically
if ( IsFollowingEntity() || !IsInterpolationEnabled() )
{
// Assume current origin ( no interpolation )
MoveToLastReceivedPosition();
return INTERPOLATE_STOP;
}
if ( GetPredictable() || IsClientCreated() )
{
C_BasePlayer *localplayer = C_BasePlayer::GetLocalPlayer();
if ( localplayer && currentTime == gpGlobals->curtime )
{
currentTime = localplayer->GetFinalPredictedTime();
currentTime -= TICK_INTERVAL;
currentTime += ( gpGlobals->interpolation_amount * TICK_INTERVAL );
}
}
oldOrigin = m_vecOrigin;
oldAngles = m_angRotation;
oldVel = m_vecVelocity;
bNoMoreChanges = Interp_Interpolate( GetVarMapping(), currentTime );
if ( cl_interp_all.GetInt() || (m_EntClientFlags & ENTCLIENTFLAG_ALWAYS_INTERPOLATE) )
bNoMoreChanges = 0;
return INTERPOLATE_CONTINUE;
}
#if 0
static ConVar cl_watchplayer( "cl_watchplayer", "-1", 0 );
#endif
void C_BaseEntity::BaseInterpolatePart2( Vector &oldOrigin, QAngle &oldAngles, Vector &oldVel, int nChangeFlags )
{
if ( m_vecOrigin != oldOrigin )
{
nChangeFlags |= POSITION_CHANGED;
}
if( m_angRotation != oldAngles )
{
nChangeFlags |= ANGLES_CHANGED;
}
if ( m_vecVelocity != oldVel )
{
nChangeFlags |= VELOCITY_CHANGED;
}
if ( nChangeFlags != 0 )
{
InvalidatePhysicsRecursive( nChangeFlags );
}
#if 0
if ( index == 1 )
{
SpewInterpolatedVar( &m_iv_vecOrigin, gpGlobals->curtime, GetInterpolationAmount( LATCH_SIMULATION_VAR ), true );
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Default interpolation for entities
// Output : true means entity should be drawn, false means probably not
//-----------------------------------------------------------------------------
bool C_BaseEntity::Interpolate( float currentTime )
{
VPROF( "C_BaseEntity::Interpolate" );
Vector oldOrigin;
QAngle oldAngles;
Vector oldVel;
int bNoMoreChanges;
int retVal = BaseInterpolatePart1( currentTime, oldOrigin, oldAngles, oldVel, bNoMoreChanges );
// If all the Interpolate() calls returned that their values aren't going to
// change anymore, then get us out of the interpolation list.
if ( bNoMoreChanges )
RemoveFromInterpolationList();
if ( retVal == INTERPOLATE_STOP )
return true;
int nChangeFlags = 0;
BaseInterpolatePart2( oldOrigin, oldAngles, oldVel, nChangeFlags );
return true;
}
CStudioHdr *C_BaseEntity::OnNewModel()
{
#ifdef TF_CLIENT_DLL
m_bValidatedOwner = false;
#endif
return NULL;
}
void C_BaseEntity::OnNewParticleEffect( const char *pszParticleName, CNewParticleEffect *pNewParticleEffect )
{
return;
}
// Above this velocity and we'll assume a warp/teleport
#define MAX_INTERPOLATE_VELOCITY 4000.0f
#define MAX_INTERPOLATE_VELOCITY_PLAYER 1250.0f
//-----------------------------------------------------------------------------
// Purpose: Determine whether entity was teleported ( so we can disable interpolation )
// Input : *ent -
// Output : bool
//-----------------------------------------------------------------------------
bool C_BaseEntity::Teleported( void )
{
// Disable interpolation when hierarchy changes
if (m_hOldMoveParent != m_hNetworkMoveParent || m_iOldParentAttachment != m_iParentAttachment)
{
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Is this a submodel of the world ( model name starts with * )?
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool C_BaseEntity::IsSubModel( void )
{
if ( model &&
modelinfo->GetModelType( model ) == mod_brush &&
modelinfo->GetModelName( model )[0] == '*' )
{
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Create entity lighting effects
//-----------------------------------------------------------------------------
void C_BaseEntity::CreateLightEffects( void )
{
dlight_t *dl;
// Is this for player flashlights only, if so move to linkplayers?
if ( index == render->GetViewEntity() )
return;
if (IsEffectActive(EF_BRIGHTLIGHT))
{
dl = effects->CL_AllocDlight ( index );
dl->origin = GetAbsOrigin();
dl->origin[2] += 16;
dl->color.r = dl->color.g = dl->color.b = 250;
dl->radius = random->RandomFloat(400,431);
dl->die = gpGlobals->curtime + 0.001;
}
if (IsEffectActive(EF_DIMLIGHT))
{
dl = effects->CL_AllocDlight ( index );
dl->origin = GetAbsOrigin();
dl->color.r = dl->color.g = dl->color.b = 100;
dl->radius = random->RandomFloat(200,231);
dl->die = gpGlobals->curtime + 0.001;
}
}
void C_BaseEntity::MoveToLastReceivedPosition( bool force )
{
if ( force || ( m_nRenderFX != kRenderFxRagdoll ) )
{
SetLocalOrigin( GetNetworkOrigin() );
SetLocalAngles( GetNetworkAngles() );
}
}
bool C_BaseEntity::ShouldInterpolate()
{
if ( render->GetViewEntity() == index )
return true;
if ( index == 0 || !GetModel() )
return false;
// always interpolate if visible
if ( IsVisible() )
return true;
// if any movement child needs interpolation, we have to interpolate too
C_BaseEntity *pChild = FirstMoveChild();
while( pChild )
{
if ( pChild->ShouldInterpolate() )
return true;
pChild = pChild->NextMovePeer();
}
// don't interpolate
return false;
}
void C_BaseEntity::ProcessTeleportList()
{
int iNext;
for ( int iCur=g_TeleportList.Head(); iCur != g_TeleportList.InvalidIndex(); iCur=iNext )
{
iNext = g_TeleportList.Next( iCur );
C_BaseEntity *pCur = g_TeleportList[iCur];
bool teleport = pCur->Teleported();
bool ef_nointerp = pCur->IsNoInterpolationFrame();
if ( teleport || ef_nointerp )
{
// Undo the teleport flag..
pCur->m_hOldMoveParent = pCur->m_hNetworkMoveParent;
pCur->m_iOldParentAttachment = pCur->m_iParentAttachment;
// Zero out all but last update.
pCur->MoveToLastReceivedPosition( true );
pCur->ResetLatched();
}
else
{
// Get it out of the list as soon as we can.
pCur->RemoveFromTeleportList();
}
}
}
void C_BaseEntity::CheckInterpolatedVarParanoidMeasurement()
{
// What we're doing here is to check all the entities that were not in the interpolation
// list and make sure that there's no entity that should be in the list that isn't.
#ifdef INTERPOLATEDVAR_PARANOID_MEASUREMENT
int iHighest = ClientEntityList().GetHighestEntityIndex();
for ( int i=0; i <= iHighest; i++ )
{
C_BaseEntity *pEnt = ClientEntityList().GetBaseEntity( i );
if ( !pEnt || pEnt->m_InterpolationListEntry != 0xFFFF || !pEnt->ShouldInterpolate() )
continue;
// Player angles always generates this error when the console is up.
if ( pEnt->entindex() == 1 && engine->Con_IsVisible() )
continue;
// View models tend to screw up this test unnecesarily because they modify origin,
// angles, and
if ( dynamic_cast<C_BaseViewModel*>( pEnt ) )
continue;
g_bRestoreInterpolatedVarValues = true;
g_nInterpolatedVarsChanged = 0;
pEnt->Interpolate( gpGlobals->curtime );
g_bRestoreInterpolatedVarValues = false;
if ( g_nInterpolatedVarsChanged > 0 )
{
static int iWarningCount = 0;
Warning( "(%d): An entity (%d) should have been in g_InterpolationList.\n", iWarningCount++, pEnt->entindex() );
break;
}
}
#endif
}
void C_BaseEntity::ProcessInterpolatedList()
{
CheckInterpolatedVarParanoidMeasurement();
// Interpolate the minimal set of entities that need it.
int iNext;
for ( int iCur=g_InterpolationList.Head(); iCur != g_InterpolationList.InvalidIndex(); iCur=iNext )
{
iNext = g_InterpolationList.Next( iCur );
C_BaseEntity *pCur = g_InterpolationList[iCur];
pCur->m_bReadyToDraw = pCur->Interpolate( gpGlobals->curtime );
}
}
//-----------------------------------------------------------------------------
// Purpose: Add entity to visibile entities list
//-----------------------------------------------------------------------------
void C_BaseEntity::AddEntity( void )
{
// Don't ever add the world, it's drawn separately
if ( index == 0 )
return;
// Create flashlight effects, etc.
CreateLightEffects();
}
//-----------------------------------------------------------------------------
// Returns the aiment render origin + angles
//-----------------------------------------------------------------------------
void C_BaseEntity::GetAimEntOrigin( IClientEntity *pAttachedTo, Vector *pOrigin, QAngle *pAngles )
{
// Should be overridden for things that attach to attchment points
// Slam origin to the origin of the entity we are attached to...
*pOrigin = pAttachedTo->GetAbsOrigin();
*pAngles = pAttachedTo->GetAbsAngles();
}
void C_BaseEntity::StopFollowingEntity( )
{
Assert( IsFollowingEntity() );
SetParent( NULL );
RemoveEffects( EF_BONEMERGE );
RemoveSolidFlags( FSOLID_NOT_SOLID );
SetMoveType( MOVETYPE_NONE );
}
bool C_BaseEntity::IsFollowingEntity()
{
return IsEffectActive(EF_BONEMERGE) && (GetMoveType() == MOVETYPE_NONE) && GetMoveParent();
}
C_BaseEntity *CBaseEntity::GetFollowedEntity()
{
if (!IsFollowingEntity())
return NULL;
return GetMoveParent();
}
//-----------------------------------------------------------------------------
// Default implementation for GetTextureAnimationStartTime
//-----------------------------------------------------------------------------
float C_BaseEntity::GetTextureAnimationStartTime()
{
return m_flSpawnTime;
}
//-----------------------------------------------------------------------------
// Default implementation, indicates that a texture animation has wrapped
//-----------------------------------------------------------------------------
void C_BaseEntity::TextureAnimationWrapped()
{
}
void C_BaseEntity::ClientThink()
{
}
void C_BaseEntity::Simulate()
{
AddEntity(); // Legacy support. Once-per-frame stuff should go in Simulate().
}
// Defined in engine
static ConVar cl_interpolate( "cl_interpolate", "1.0f", FCVAR_USERINFO | FCVAR_DEVELOPMENTONLY );
// (static function)
void C_BaseEntity::InterpolateServerEntities()
{
VPROF_BUDGET( "C_BaseEntity::InterpolateServerEntities", VPROF_BUDGETGROUP_INTERPOLATION );
s_bInterpolate = cl_interpolate.GetBool();
// Don't interpolate during timedemo playback
if ( engine->IsPlayingTimeDemo() || engine->IsPaused() )
{
s_bInterpolate = false;
}
if ( !engine->IsPlayingDemo() )
{
// Don't interpolate, either, if we are timing out
INetChannelInfo *nci = engine->GetNetChannelInfo();
if ( nci && nci->GetTimeSinceLastReceived() > 0.5f )
{
s_bInterpolate = false;
}
}
if ( IsSimulatingOnAlternateTicks() != g_bWasSkipping || IsEngineThreaded() != g_bWasThreaded )
{
g_bWasSkipping = IsSimulatingOnAlternateTicks();
g_bWasThreaded = IsEngineThreaded();
C_BaseEntityIterator iterator;
C_BaseEntity *pEnt;
while ( (pEnt = iterator.Next()) != NULL )
{
pEnt->Interp_UpdateInterpolationAmounts( pEnt->GetVarMapping() );
}
}
// Enable extrapolation?
CInterpolationContext context;
context.SetLastTimeStamp( engine->GetLastTimeStamp() );
if ( cl_extrapolate.GetBool() && !engine->IsPaused() )
{
context.EnableExtrapolation( true );
}
// Smoothly interpolate position for server entities.
ProcessTeleportList();
ProcessInterpolatedList();
}
// (static function)
void C_BaseEntity::AddVisibleEntities()
{
#if !defined( NO_ENTITY_PREDICTION )
VPROF_BUDGET( "C_BaseEntity::AddVisibleEntities", VPROF_BUDGETGROUP_WORLD_RENDERING );
// Let non-dormant client created predictables get added, too
int c = predictables->GetPredictableCount();
for ( int i = 0 ; i < c ; i++ )
{
C_BaseEntity *pEnt = predictables->GetPredictable( i );
if ( !pEnt )
continue;
if ( !pEnt->IsClientCreated() )
continue;
// Only draw until it's ack'd since that means a real entity has arrived
if ( pEnt->m_PredictableID.GetAcknowledged() )
continue;
// Don't draw if dormant
if ( pEnt->IsDormantPredictable() )
continue;
pEnt->UpdateVisibility();
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : type -
//-----------------------------------------------------------------------------
void C_BaseEntity::OnPreDataChanged( DataUpdateType_t type )
{
m_hOldMoveParent = m_hNetworkMoveParent;
m_iOldParentAttachment = m_iParentAttachment;
}
void C_BaseEntity::OnDataChanged( DataUpdateType_t type )
{
// See if it needs to allocate prediction stuff
CheckInitPredictable( "OnDataChanged" );
// Set up shadows; do it here so that objects can change shadowcasting state
CreateShadow();
if ( type == DATA_UPDATE_CREATED )
{
UpdateVisibility();
}
}
ClientThinkHandle_t C_BaseEntity::GetThinkHandle()
{
return m_hThink;
}
void C_BaseEntity::SetThinkHandle( ClientThinkHandle_t hThink )
{
m_hThink = hThink;
}
//-----------------------------------------------------------------------------
// Purpose: This routine modulates renderamt according to m_nRenderFX's value
// This is a client side effect and will not be in-sync on machines across a
// network game.
// Input : origin -
// alpha -
// Output : int
//-----------------------------------------------------------------------------
void C_BaseEntity::ComputeFxBlend( void )
{
// Don't recompute if we've already computed this frame
if ( m_nFXComputeFrame == gpGlobals->framecount )
return;
MDLCACHE_CRITICAL_SECTION();
int blend=0;
float offset;
offset = ((int)index) * 363.0;// Use ent index to de-sync these fx
switch( m_nRenderFX )
{
case kRenderFxPulseSlowWide:
blend = m_clrRender->a + 0x40 * sin( gpGlobals->curtime * 2 + offset );
break;
case kRenderFxPulseFastWide:
blend = m_clrRender->a + 0x40 * sin( gpGlobals->curtime * 8 + offset );
break;
case kRenderFxPulseFastWider:
blend = ( 0xff * fabs(sin( gpGlobals->curtime * 12 + offset ) ) );
break;
case kRenderFxPulseSlow:
blend = m_clrRender->a + 0x10 * sin( gpGlobals->curtime * 2 + offset );
break;
case kRenderFxPulseFast:
blend = m_clrRender->a + 0x10 * sin( gpGlobals->curtime * 8 + offset );
break;
// JAY: HACK for now -- not time based
case kRenderFxFadeSlow:
if ( m_clrRender->a > 0 )
{
SetRenderColorA( m_clrRender->a - 1 );
}
else
{
SetRenderColorA( 0 );
}
blend = m_clrRender->a;
break;
case kRenderFxFadeFast:
if ( m_clrRender->a > 3 )
{
SetRenderColorA( m_clrRender->a - 4 );
}
else
{
SetRenderColorA( 0 );
}
blend = m_clrRender->a;
break;
case kRenderFxSolidSlow:
if ( m_clrRender->a < 255 )
{
SetRenderColorA( m_clrRender->a + 1 );
}
else
{
SetRenderColorA( 255 );
}
blend = m_clrRender->a;
break;
case kRenderFxSolidFast:
if ( m_clrRender->a < 252 )
{
SetRenderColorA( m_clrRender->a + 4 );
}
else
{
SetRenderColorA( 255 );
}
blend = m_clrRender->a;
break;
case kRenderFxStrobeSlow:
blend = 20 * sin( gpGlobals->curtime * 4 + offset );
if ( blend < 0 )
{
blend = 0;
}
else
{
blend = m_clrRender->a;
}
break;
case kRenderFxStrobeFast:
blend = 20 * sin( gpGlobals->curtime * 16 + offset );
if ( blend < 0 )
{
blend = 0;
}
else
{
blend = m_clrRender->a;
}
break;
case kRenderFxStrobeFaster:
blend = 20 * sin( gpGlobals->curtime * 36 + offset );
if ( blend < 0 )
{
blend = 0;
}
else
{
blend = m_clrRender->a;
}
break;
case kRenderFxFlickerSlow:
blend = 20 * (sin( gpGlobals->curtime * 2 ) + sin( gpGlobals->curtime * 17 + offset ));
if ( blend < 0 )
{
blend = 0;
}
else
{
blend = m_clrRender->a;
}
break;
case kRenderFxFlickerFast:
blend = 20 * (sin( gpGlobals->curtime * 16 ) + sin( gpGlobals->curtime * 23 + offset ));
if ( blend < 0 )
{
blend = 0;
}
else
{
blend = m_clrRender->a;
}
break;
case kRenderFxHologram:
case kRenderFxDistort:
{
Vector tmp;
float dist;
VectorCopy( GetAbsOrigin(), tmp );
VectorSubtract( tmp, CurrentViewOrigin(), tmp );
dist = DotProduct( tmp, CurrentViewForward() );
// Turn off distance fade
if ( m_nRenderFX == kRenderFxDistort )
{
dist = 1;
}
if ( dist <= 0 )
{
blend = 0;
}
else
{
SetRenderColorA( 180 );
if ( dist <= 100 )
blend = m_clrRender->a;
else
blend = (int) ((1.0 - (dist - 100) * (1.0 / 400.0)) * m_clrRender->a);
blend += random->RandomInt(-32,31);
}
}
break;
case kRenderFxNone:
case kRenderFxClampMinScale:
default:
if (m_nRenderMode == kRenderNormal)
blend = 255;
else
blend = m_clrRender->a;
break;
}
blend = clamp( blend, 0, 255 );
// Look for client-side fades
unsigned char nFadeAlpha = GetClientSideFade();
if ( nFadeAlpha != 255 )
{
float flBlend = blend / 255.0f;
float flFade = nFadeAlpha / 255.0f;
blend = (int)( flBlend * flFade * 255.0f + 0.5f );
blend = clamp( blend, 0, 255 );
}
m_nRenderFXBlend = blend;
m_nFXComputeFrame = gpGlobals->framecount;
// Update the render group
if ( GetRenderHandle() != INVALID_CLIENT_RENDER_HANDLE )
{
ClientLeafSystem()->SetRenderGroup( GetRenderHandle(), GetRenderGroup() );
}
// Tell our shadow
if ( m_ShadowHandle != CLIENTSHADOW_INVALID_HANDLE )
{
g_pClientShadowMgr->SetFalloffBias( m_ShadowHandle, (255 - m_nRenderFXBlend) );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int C_BaseEntity::GetFxBlend( void )
{
Assert( m_nFXComputeFrame == gpGlobals->framecount );
return m_nRenderFXBlend;
}
//-----------------------------------------------------------------------------
// Determine the color modulation amount
//-----------------------------------------------------------------------------
void C_BaseEntity::GetColorModulation( float* color )
{
color[0] = m_clrRender->r / 255.0f;
color[1] = m_clrRender->g / 255.0f;
color[2] = m_clrRender->b / 255.0f;
}
//-----------------------------------------------------------------------------
// Returns true if we should add this to the collision list
//-----------------------------------------------------------------------------
CollideType_t C_BaseEntity::GetCollideType( void )
{
if ( !m_nModelIndex || !model )
return ENTITY_SHOULD_NOT_COLLIDE;
if ( !IsSolid( ) )
return ENTITY_SHOULD_NOT_COLLIDE;
// If the model is a bsp or studio (i.e. it can collide with the player
if ( ( modelinfo->GetModelType( model ) != mod_brush ) && ( modelinfo->GetModelType( model ) != mod_studio ) )
return ENTITY_SHOULD_NOT_COLLIDE;
// Don't get stuck on point sized entities ( world doesn't count )
if ( m_nModelIndex != 1 )
{
if ( IsPointSized() )
return ENTITY_SHOULD_NOT_COLLIDE;
}
return ENTITY_SHOULD_COLLIDE;
}
//-----------------------------------------------------------------------------
// Is this a brush model?
//-----------------------------------------------------------------------------
bool C_BaseEntity::IsBrushModel() const
{
int modelType = modelinfo->GetModelType( model );
return (modelType == mod_brush);
}
//-----------------------------------------------------------------------------
// This method works when we've got a studio model
//-----------------------------------------------------------------------------
void C_BaseEntity::AddStudioDecal( const Ray_t& ray, int hitbox, int decalIndex,
bool doTrace, trace_t& tr, int maxLODToDecal )
{
if (doTrace)
{
enginetrace->ClipRayToEntity( ray, MASK_SHOT, this, &tr );
// Trace the ray against the entity
if (tr.fraction == 1.0f)
return;
// Set the trace index appropriately...
tr.m_pEnt = this;
}
// Exit out after doing the trace so any other effects that want to happen can happen.
if ( !r_drawmodeldecals.GetBool() )
return;
// Found the point, now lets apply the decals
CreateModelInstance();
// FIXME: Pass in decal up?
Vector up(0, 0, 1);
if (doTrace && (GetSolid() == SOLID_VPHYSICS) && !tr.startsolid && !tr.allsolid)
{
// Choose a more accurate normal direction
// Also, since we have more accurate info, we can avoid pokethru
Vector temp;
VectorSubtract( tr.endpos, tr.plane.normal, temp );
Ray_t betterRay;
betterRay.Init( tr.endpos, temp );
modelrender->AddDecal( m_ModelInstance, betterRay, up, decalIndex, GetStudioBody(), true, maxLODToDecal );
}
else
{
modelrender->AddDecal( m_ModelInstance, ray, up, decalIndex, GetStudioBody(), false, maxLODToDecal );
}
}
//-----------------------------------------------------------------------------
void C_BaseEntity::AddColoredStudioDecal( const Ray_t& ray, int hitbox, int decalIndex,
bool doTrace, trace_t& tr, Color cColor, int maxLODToDecal )
{
if (doTrace)
{
enginetrace->ClipRayToEntity( ray, MASK_SHOT, this, &tr );
// Trace the ray against the entity
if (tr.fraction == 1.0f)
return;
// Set the trace index appropriately...
tr.m_pEnt = this;
}
// Exit out after doing the trace so any other effects that want to happen can happen.
if ( !r_drawmodeldecals.GetBool() )
return;
// Found the point, now lets apply the decals
CreateModelInstance();
// FIXME: Pass in decal up?
Vector up(0, 0, 1);
if (doTrace && (GetSolid() == SOLID_VPHYSICS) && !tr.startsolid && !tr.allsolid)
{
// Choose a more accurate normal direction
// Also, since we have more accurate info, we can avoid pokethru
Vector temp;
VectorSubtract( tr.endpos, tr.plane.normal, temp );
Ray_t betterRay;
betterRay.Init( tr.endpos, temp );
modelrender->AddColoredDecal( m_ModelInstance, betterRay, up, decalIndex, GetStudioBody(), cColor, true, maxLODToDecal );
}
else
{
modelrender->AddColoredDecal( m_ModelInstance, ray, up, decalIndex, GetStudioBody(), cColor, false, maxLODToDecal );
}
}
//-----------------------------------------------------------------------------
// This method works when we've got a brush model
//-----------------------------------------------------------------------------
void C_BaseEntity::AddBrushModelDecal( const Ray_t& ray, const Vector& decalCenter,
int decalIndex, bool doTrace, trace_t& tr )
{
if ( doTrace )
{
enginetrace->ClipRayToEntity( ray, MASK_SHOT, this, &tr );
if ( tr.fraction == 1.0f )
return;
}
effects->DecalShoot( decalIndex, index,
model, GetAbsOrigin(), GetAbsAngles(), decalCenter, 0, 0 );
}
//-----------------------------------------------------------------------------
// A method to apply a decal to an entity
//-----------------------------------------------------------------------------
void C_BaseEntity::AddDecal( const Vector& rayStart, const Vector& rayEnd,
const Vector& decalCenter, int hitbox, int decalIndex, bool doTrace, trace_t& tr, int maxLODToDecal )
{
Ray_t ray;
ray.Init( rayStart, rayEnd );
// FIXME: Better bloat?
// Bloat a little bit so we get the intersection
ray.m_Delta *= 1.1f;
int modelType = modelinfo->GetModelType( model );
switch ( modelType )
{
case mod_studio:
AddStudioDecal( ray, hitbox, decalIndex, doTrace, tr, maxLODToDecal );
break;
case mod_brush:
AddBrushModelDecal( ray, decalCenter, decalIndex, doTrace, tr );
break;
default:
// By default, no collision
tr.fraction = 1.0f;
break;
}
}
//-----------------------------------------------------------------------------
void C_BaseEntity::AddColoredDecal( const Vector& rayStart, const Vector& rayEnd,
const Vector& decalCenter, int hitbox, int decalIndex, bool doTrace, trace_t& tr, Color cColor, int maxLODToDecal )
{
Ray_t ray;
ray.Init( rayStart, rayEnd );
// FIXME: Better bloat?
// Bloat a little bit so we get the intersection
ray.m_Delta *= 1.1f;
int modelType = modelinfo->GetModelType( model );
if ( doTrace )
{
enginetrace->ClipRayToEntity( ray, MASK_SHOT, this, &tr );
switch ( modelType )
{
case mod_studio:
tr.m_pEnt = this;
break;
case mod_brush:
if ( tr.fraction == 1.0f )
return; // Explicitly end
default:
// By default, no collision
tr.fraction = 1.0f;
break;
}
}
switch ( modelType )
{
case mod_studio:
AddColoredStudioDecal( ray, hitbox, decalIndex, doTrace, tr, cColor, maxLODToDecal );
break;
case mod_brush:
{
color32 cColor32 = { (uint8)cColor.r(), (uint8)cColor.g(), (uint8)cColor.b(), (uint8)cColor.a() };
effects->DecalColorShoot( decalIndex, index, model, GetAbsOrigin(), GetAbsAngles(), decalCenter, 0, 0, cColor32 );
}
break;
default:
// By default, no collision
tr.fraction = 1.0f;
break;
}
}
//-----------------------------------------------------------------------------
// A method to remove all decals from an entity
//-----------------------------------------------------------------------------
void C_BaseEntity::RemoveAllDecals( void )
{
// For now, we only handle removing decals from studiomodels
if ( modelinfo->GetModelType( model ) == mod_studio )
{
CreateModelInstance();
modelrender->RemoveAllDecals( m_ModelInstance );
}
}
bool C_BaseEntity::SnatchModelInstance( C_BaseEntity *pToEntity )
{
if ( !modelrender->ChangeInstance( GetModelInstance(), pToEntity ) )
return false; // engine could move modle handle
// remove old handle from toentity if any
if ( pToEntity->GetModelInstance() != MODEL_INSTANCE_INVALID )
pToEntity->DestroyModelInstance();
// move the handle to other entity
pToEntity->SetModelInstance( GetModelInstance() );
// delete own reference
SetModelInstance( MODEL_INSTANCE_INVALID );
return true;
}
#include "tier0/memdbgoff.h"
//-----------------------------------------------------------------------------
// C_BaseEntity new/delete
// All fields in the object are all initialized to 0.
//-----------------------------------------------------------------------------
void *C_BaseEntity::operator new( size_t stAllocateBlock )
{
Assert( stAllocateBlock != 0 );
MEM_ALLOC_CREDIT();
void *pMem = MemAlloc_Alloc( stAllocateBlock );
memset( pMem, 0, stAllocateBlock );
return pMem;
}
void *C_BaseEntity::operator new[]( size_t stAllocateBlock )
{
Assert( stAllocateBlock != 0 );
MEM_ALLOC_CREDIT();
void *pMem = MemAlloc_Alloc( stAllocateBlock );
memset( pMem, 0, stAllocateBlock );
return pMem;
}
void *C_BaseEntity::operator new( size_t stAllocateBlock, int nBlockUse, const char *pFileName, int nLine )
{
Assert( stAllocateBlock != 0 );
void *pMem = MemAlloc_Alloc( stAllocateBlock, pFileName, nLine );
memset( pMem, 0, stAllocateBlock );
return pMem;
}
void *C_BaseEntity::operator new[]( size_t stAllocateBlock, int nBlockUse, const char *pFileName, int nLine )
{
Assert( stAllocateBlock != 0 );
void *pMem = MemAlloc_Alloc( stAllocateBlock, pFileName, nLine );
memset( pMem, 0, stAllocateBlock );
return pMem;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pMem -
//-----------------------------------------------------------------------------
void C_BaseEntity::operator delete( void *pMem )
{
// get the engine to free the memory
MemAlloc_Free( pMem );
}
#include "tier0/memdbgon.h"
//========================================================================================
// TEAM HANDLING
//========================================================================================
C_Team *C_BaseEntity::GetTeam( void )
{
return GetGlobalTeam( m_iTeamNum );
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : int
//-----------------------------------------------------------------------------
int C_BaseEntity::GetTeamNumber( void ) const
{
return m_iTeamNum;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int C_BaseEntity::GetRenderTeamNumber( void )
{
return GetTeamNumber();
}
//-----------------------------------------------------------------------------
// Purpose: Returns true if these entities are both in at least one team together
//-----------------------------------------------------------------------------
bool C_BaseEntity::InSameTeam( C_BaseEntity *pEntity )
{
if ( !pEntity )
return false;
return ( pEntity->GetTeam() == GetTeam() );
}
//-----------------------------------------------------------------------------
// Purpose: Returns true if the entity's on the same team as the local player
//-----------------------------------------------------------------------------
bool C_BaseEntity::InLocalTeam( void )
{
return ( GetTeam() == GetLocalTeam() );
}
void C_BaseEntity::SetNextClientThink( float nextThinkTime )
{
Assert( GetClientHandle() != INVALID_CLIENTENTITY_HANDLE );
ClientThinkList()->SetNextClientThink( GetClientHandle(), nextThinkTime );
}
void C_BaseEntity::AddToLeafSystem()
{
AddToLeafSystem( GetRenderGroup() );
}
void C_BaseEntity::AddToLeafSystem( RenderGroup_t group )
{
if( m_hRender == INVALID_CLIENT_RENDER_HANDLE )
{
// create new renderer handle
ClientLeafSystem()->AddRenderable( this, group );
ClientLeafSystem()->EnableAlternateSorting( m_hRender, m_bAlternateSorting );
}
else
{
// handle already exists, just update group & origin
ClientLeafSystem()->SetRenderGroup( m_hRender, group );
ClientLeafSystem()->RenderableChanged( m_hRender );
}
}
//-----------------------------------------------------------------------------
// Creates the shadow (if it doesn't already exist) based on shadow cast type
//-----------------------------------------------------------------------------
void C_BaseEntity::CreateShadow()
{
ShadowType_t shadowType = ShadowCastType();
if (shadowType == SHADOWS_NONE)
{
DestroyShadow();
}
else
{
if (m_ShadowHandle == CLIENTSHADOW_INVALID_HANDLE)
{
int flags = SHADOW_FLAGS_SHADOW;
if (shadowType != SHADOWS_SIMPLE)
flags |= SHADOW_FLAGS_USE_RENDER_TO_TEXTURE;
if (shadowType == SHADOWS_RENDER_TO_TEXTURE_DYNAMIC)
flags |= SHADOW_FLAGS_ANIMATING_SOURCE;
m_ShadowHandle = g_pClientShadowMgr->CreateShadow(GetClientHandle(), flags);
}
}
}
//-----------------------------------------------------------------------------
// Removes the shadow
//-----------------------------------------------------------------------------
void C_BaseEntity::DestroyShadow()
{
// NOTE: This will actually cause the shadow type to be recomputed
// if the entity doesn't immediately go away
if (m_ShadowHandle != CLIENTSHADOW_INVALID_HANDLE)
{
g_pClientShadowMgr->DestroyShadow(m_ShadowHandle);
m_ShadowHandle = CLIENTSHADOW_INVALID_HANDLE;
}
}
//-----------------------------------------------------------------------------
// Removes the entity from the leaf system
//-----------------------------------------------------------------------------
void C_BaseEntity::RemoveFromLeafSystem()
{
// Detach from the leaf lists.
if( m_hRender != INVALID_CLIENT_RENDER_HANDLE )
{
ClientLeafSystem()->RemoveRenderable( m_hRender );
m_hRender = INVALID_CLIENT_RENDER_HANDLE;
}
DestroyShadow();
}
//-----------------------------------------------------------------------------
// Purpose: Flags this entity as being inside or outside of this client's PVS
// on the server.
// NOTE: this is meaningless for client-side only entities.
// Input : inside_pvs -
//-----------------------------------------------------------------------------
void C_BaseEntity::SetDormant( bool bDormant )
{
Assert( IsServerEntity() );
m_bDormant = bDormant;
// Kill drawing if we became dormant.
UpdateVisibility();
ParticleProp()->OwnerSetDormantTo( bDormant );
}
//-----------------------------------------------------------------------------
// Purpose: Returns whether this entity is dormant. Client/server entities become
// dormant when they leave the PVS on the server. Client side entities
// can decide for themselves whether to become dormant.
//-----------------------------------------------------------------------------
bool C_BaseEntity::IsDormant( void )
{
if ( IsServerEntity() )
{
return m_bDormant;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Tells the entity that it's about to be destroyed due to the client receiving
// an uncompressed update that's caused it to destroy all entities & recreate them.
//-----------------------------------------------------------------------------
void C_BaseEntity::SetDestroyedOnRecreateEntities( void )
{
// Robin: We need to destroy all our particle systems immediately, because
// we're about to be recreated, and their owner EHANDLEs will match up to
// the new entity, but it won't know anything about them.
ParticleProp()->StopEmissionAndDestroyImmediately();
}
//-----------------------------------------------------------------------------
// These methods recompute local versions as well as set abs versions
//-----------------------------------------------------------------------------
void C_BaseEntity::SetAbsOrigin( const Vector& absOrigin )
{
// This is necessary to get the other fields of m_rgflCoordinateFrame ok
CalcAbsolutePosition();
if ( m_vecAbsOrigin == absOrigin )
return;
// All children are invalid, but we are not
InvalidatePhysicsRecursive( POSITION_CHANGED );
RemoveEFlags( EFL_DIRTY_ABSTRANSFORM );
m_vecAbsOrigin = absOrigin;
MatrixSetColumn( absOrigin, 3, m_rgflCoordinateFrame );
C_BaseEntity *pMoveParent = GetMoveParent();
if (!pMoveParent)
{
m_vecOrigin = absOrigin;
return;
}
// Moveparent case: transform the abs position into local space
VectorITransform( absOrigin, pMoveParent->EntityToWorldTransform(), (Vector&)m_vecOrigin );
}
void C_BaseEntity::SetAbsAngles( const QAngle& absAngles )
{
// This is necessary to get the other fields of m_rgflCoordinateFrame ok
CalcAbsolutePosition();
// FIXME: The normalize caused problems in server code like momentary_rot_button that isn't
// handling things like +/-180 degrees properly. This should be revisited.
//QAngle angleNormalize( AngleNormalize( absAngles.x ), AngleNormalize( absAngles.y ), AngleNormalize( absAngles.z ) );
if ( m_angAbsRotation == absAngles )
return;
InvalidatePhysicsRecursive( ANGLES_CHANGED );
RemoveEFlags( EFL_DIRTY_ABSTRANSFORM );
m_angAbsRotation = absAngles;
AngleMatrix( absAngles, m_rgflCoordinateFrame );
MatrixSetColumn( m_vecAbsOrigin, 3, m_rgflCoordinateFrame );
C_BaseEntity *pMoveParent = GetMoveParent();
if (!pMoveParent)
{
m_angRotation = absAngles;
return;
}
// Moveparent case: we're aligned with the move parent
if ( m_angAbsRotation == pMoveParent->GetAbsAngles() )
{
m_angRotation.Init( );
}
else
{
// Moveparent case: transform the abs transform into local space
matrix3x4_t worldToParent, localMatrix;
MatrixInvert( pMoveParent->EntityToWorldTransform(), worldToParent );
ConcatTransforms( worldToParent, m_rgflCoordinateFrame, localMatrix );
MatrixAngles( localMatrix, (QAngle &)m_angRotation );
}
}
void C_BaseEntity::SetAbsVelocity( const Vector &vecAbsVelocity )
{
if ( m_vecAbsVelocity == vecAbsVelocity )
return;
// The abs velocity won't be dirty since we're setting it here
InvalidatePhysicsRecursive( VELOCITY_CHANGED );
m_iEFlags &= ~EFL_DIRTY_ABSVELOCITY;
m_vecAbsVelocity = vecAbsVelocity;
C_BaseEntity *pMoveParent = GetMoveParent();
if (!pMoveParent)
{
m_vecVelocity = vecAbsVelocity;
return;
}
// First subtract out the parent's abs velocity to get a relative
// velocity measured in world space
Vector relVelocity;
VectorSubtract( vecAbsVelocity, pMoveParent->GetAbsVelocity(), relVelocity );
// Transform velocity into parent space
VectorIRotate( relVelocity, pMoveParent->EntityToWorldTransform(), m_vecVelocity );
}
/*
void C_BaseEntity::SetAbsAngularVelocity( const QAngle &vecAbsAngVelocity )
{
// The abs velocity won't be dirty since we're setting it here
InvalidatePhysicsRecursive( EFL_DIRTY_ABSANGVELOCITY );
m_iEFlags &= ~EFL_DIRTY_ABSANGVELOCITY;
m_vecAbsAngVelocity = vecAbsAngVelocity;
C_BaseEntity *pMoveParent = GetMoveParent();
if (!pMoveParent)
{
m_vecAngVelocity = vecAbsAngVelocity;
return;
}
// First subtract out the parent's abs velocity to get a relative
// angular velocity measured in world space
QAngle relAngVelocity;
relAngVelocity = vecAbsAngVelocity - pMoveParent->GetAbsAngularVelocity();
matrix3x4_t entityToWorld;
AngleMatrix( relAngVelocity, entityToWorld );
// Moveparent case: transform the abs angular vel into local space
matrix3x4_t worldToParent, localMatrix;
MatrixInvert( pMoveParent->EntityToWorldTransform(), worldToParent );
ConcatTransforms( worldToParent, entityToWorld, localMatrix );
MatrixAngles( localMatrix, m_vecAngVelocity );
}
*/
// Prevent these for now until hierarchy is properly networked
const Vector& C_BaseEntity::GetLocalOrigin( void ) const
{
return m_vecOrigin;
}
vec_t C_BaseEntity::GetLocalOriginDim( int iDim ) const
{
return m_vecOrigin[iDim];
}
// Prevent these for now until hierarchy is properly networked
void C_BaseEntity::SetLocalOrigin( const Vector& origin )
{
if (m_vecOrigin != origin)
{
InvalidatePhysicsRecursive( POSITION_CHANGED );
m_vecOrigin = origin;
}
}
void C_BaseEntity::SetLocalOriginDim( int iDim, vec_t flValue )
{
if (m_vecOrigin[iDim] != flValue)
{
InvalidatePhysicsRecursive( POSITION_CHANGED );
m_vecOrigin[iDim] = flValue;
}
}
// Prevent these for now until hierarchy is properly networked
const QAngle& C_BaseEntity::GetLocalAngles( void ) const
{
return m_angRotation;
}
vec_t C_BaseEntity::GetLocalAnglesDim( int iDim ) const
{
return m_angRotation[iDim];
}
// Prevent these for now until hierarchy is properly networked
void C_BaseEntity::SetLocalAngles( const QAngle& angles )
{
// NOTE: The angle normalize is a little expensive, but we can save
// a bunch of time in interpolation if we don't have to invalidate everything
// and sometimes it's off by a normalization amount
// FIXME: The normalize caused problems in server code like momentary_rot_button that isn't
// handling things like +/-180 degrees properly. This should be revisited.
//QAngle angleNormalize( AngleNormalize( angles.x ), AngleNormalize( angles.y ), AngleNormalize( angles.z ) );
if (m_angRotation != angles)
{
// This will cause the velocities of all children to need recomputation
InvalidatePhysicsRecursive( ANGLES_CHANGED );
m_angRotation = angles;
}
}
void C_BaseEntity::SetLocalAnglesDim( int iDim, vec_t flValue )
{
flValue = AngleNormalize( flValue );
if (m_angRotation[iDim] != flValue)
{
// This will cause the velocities of all children to need recomputation
InvalidatePhysicsRecursive( ANGLES_CHANGED );
m_angRotation[iDim] = flValue;
}
}
void C_BaseEntity::SetLocalVelocity( const Vector &vecVelocity )
{
if (m_vecVelocity != vecVelocity)
{
InvalidatePhysicsRecursive( VELOCITY_CHANGED );
m_vecVelocity = vecVelocity;
}
}
void C_BaseEntity::SetLocalAngularVelocity( const QAngle &vecAngVelocity )
{
if (m_vecAngVelocity != vecAngVelocity)
{
// InvalidatePhysicsRecursive( ANG_VELOCITY_CHANGED );
m_vecAngVelocity = vecAngVelocity;
}
}
//-----------------------------------------------------------------------------
// Sets the local position from a transform
//-----------------------------------------------------------------------------
void C_BaseEntity::SetLocalTransform( const matrix3x4_t &localTransform )
{
Vector vecLocalOrigin;
QAngle vecLocalAngles;
MatrixGetColumn( localTransform, 3, vecLocalOrigin );
MatrixAngles( localTransform, vecLocalAngles );
SetLocalOrigin( vecLocalOrigin );
SetLocalAngles( vecLocalAngles );
}
//-----------------------------------------------------------------------------
// FIXME: REMOVE!!!
//-----------------------------------------------------------------------------
void C_BaseEntity::MoveToAimEnt( )
{
Vector vecAimEntOrigin;
QAngle vecAimEntAngles;
GetAimEntOrigin( GetMoveParent(), &vecAimEntOrigin, &vecAimEntAngles );
SetAbsOrigin( vecAimEntOrigin );
SetAbsAngles( vecAimEntAngles );
}
void C_BaseEntity::BoneMergeFastCullBloat( Vector &localMins, Vector &localMaxs, const Vector &thisEntityMins, const Vector &thisEntityMaxs ) const
{
// By default, we bloat the bbox for fastcull ents by the maximum length it could hang out of the parent bbox,
// it one corner were touching the edge of the parent's box, and the whole diagonal stretched out.
float flExpand = (thisEntityMaxs - thisEntityMins).Length();
localMins.x -= flExpand;
localMins.y -= flExpand;
localMins.z -= flExpand;
localMaxs.x += flExpand;
localMaxs.y += flExpand;
localMaxs.z += flExpand;
}
matrix3x4_t& C_BaseEntity::GetParentToWorldTransform( matrix3x4_t &tempMatrix )
{
CBaseEntity *pMoveParent = GetMoveParent();
if ( !pMoveParent )
{
Assert( false );
SetIdentityMatrix( tempMatrix );
return tempMatrix;
}
if ( m_iParentAttachment != 0 )
{
Vector vOrigin;
QAngle vAngles;
if ( pMoveParent->GetAttachment( m_iParentAttachment, vOrigin, vAngles ) )
{
AngleMatrix( vAngles, vOrigin, tempMatrix );
return tempMatrix;
}
}
// If we fall through to here, then just use the move parent's abs origin and angles.
return pMoveParent->EntityToWorldTransform();
}
//-----------------------------------------------------------------------------
// Purpose: Calculates the absolute position of an edict in the world
// assumes the parent's absolute origin has already been calculated
//-----------------------------------------------------------------------------
void C_BaseEntity::CalcAbsolutePosition( )
{
// There are periods of time where we're gonna have to live with the
// fact that we're in an indeterminant state and abs queries (which
// shouldn't be happening at all; I have assertions for those), will
// just have to accept stale data.
if (!s_bAbsRecomputationEnabled)
return;
// FIXME: Recompute absbox!!!
if ((m_iEFlags & EFL_DIRTY_ABSTRANSFORM) == 0)
{
// quick check to make sure we really don't need an update
// Assert( m_pMoveParent || m_vecAbsOrigin == GetLocalOrigin() );
return;
}
AUTO_LOCK( m_CalcAbsolutePositionMutex );
if ((m_iEFlags & EFL_DIRTY_ABSTRANSFORM) == 0) // need second check in event another thread grabbed mutex and did the calculation
{
return;
}
RemoveEFlags( EFL_DIRTY_ABSTRANSFORM );
if (!m_pMoveParent)
{
// Construct the entity-to-world matrix
// Start with making an entity-to-parent matrix
AngleMatrix( GetLocalAngles(), GetLocalOrigin(), m_rgflCoordinateFrame );
m_vecAbsOrigin = GetLocalOrigin();
m_angAbsRotation = GetLocalAngles();
NormalizeAngles( m_angAbsRotation );
return;
}
if ( IsEffectActive(EF_BONEMERGE) )
{
MoveToAimEnt();
return;
}
// Construct the entity-to-world matrix
// Start with making an entity-to-parent matrix
matrix3x4_t matEntityToParent;
AngleMatrix( GetLocalAngles(), matEntityToParent );
MatrixSetColumn( GetLocalOrigin(), 3, matEntityToParent );
// concatenate with our parent's transform
matrix3x4_t scratchMatrix;
ConcatTransforms( GetParentToWorldTransform( scratchMatrix ), matEntityToParent, m_rgflCoordinateFrame );
// pull our absolute position out of the matrix
MatrixGetColumn( m_rgflCoordinateFrame, 3, m_vecAbsOrigin );
// if we have any angles, we have to extract our absolute angles from our matrix
if ( m_angRotation == vec3_angle && m_iParentAttachment == 0 )
{
// just copy our parent's absolute angles
VectorCopy( m_pMoveParent->GetAbsAngles(), m_angAbsRotation );
}
else
{
MatrixAngles( m_rgflCoordinateFrame, m_angAbsRotation );
}
// This is necessary because it's possible that our moveparent's CalculateIKLocks will trigger its move children
// (ie: this entity) to call GetAbsOrigin(), and they'll use the moveparent's OLD bone transforms to get their attachments
// since the moveparent is right in the middle of setting up new transforms.
//
// So here, we keep our absorigin invalidated. It means we're returning an origin that is a frame old to CalculateIKLocks,
// but we'll still render with the right origin.
if ( m_iParentAttachment != 0 && (m_pMoveParent->GetEFlags() & EFL_SETTING_UP_BONES) )
{
m_iEFlags |= EFL_DIRTY_ABSTRANSFORM;
}
}
void C_BaseEntity::CalcAbsoluteVelocity()
{
if ((m_iEFlags & EFL_DIRTY_ABSVELOCITY ) == 0)
return;
AUTO_LOCK( m_CalcAbsoluteVelocityMutex );
if ((m_iEFlags & EFL_DIRTY_ABSVELOCITY) == 0) // need second check in event another thread grabbed mutex and did the calculation
{
return;
}
m_iEFlags &= ~EFL_DIRTY_ABSVELOCITY;
CBaseEntity *pMoveParent = GetMoveParent();
if ( !pMoveParent )
{
m_vecAbsVelocity = m_vecVelocity;
return;
}
VectorRotate( m_vecVelocity, pMoveParent->EntityToWorldTransform(), m_vecAbsVelocity );
// Add in the attachments velocity if it exists
if ( m_iParentAttachment != 0 )
{
Vector vOriginVel;
Quaternion vAngleVel;
if ( pMoveParent->GetAttachmentVelocity( m_iParentAttachment, vOriginVel, vAngleVel ) )
{
m_vecAbsVelocity += vOriginVel;
return;
}
}
// Now add in the parent abs velocity
m_vecAbsVelocity += pMoveParent->GetAbsVelocity();
}
/*
void C_BaseEntity::CalcAbsoluteAngularVelocity()
{
if ((m_iEFlags & EFL_DIRTY_ABSANGVELOCITY ) == 0)
return;
m_iEFlags &= ~EFL_DIRTY_ABSANGVELOCITY;
CBaseEntity *pMoveParent = GetMoveParent();
if ( !pMoveParent )
{
m_vecAbsAngVelocity = m_vecAngVelocity;
return;
}
matrix3x4_t angVelToParent, angVelToWorld;
AngleMatrix( m_vecAngVelocity, angVelToParent );
ConcatTransforms( pMoveParent->EntityToWorldTransform(), angVelToParent, angVelToWorld );
MatrixAngles( angVelToWorld, m_vecAbsAngVelocity );
// Now add in the parent abs angular velocity
m_vecAbsAngVelocity += pMoveParent->GetAbsAngularVelocity();
}
*/
//-----------------------------------------------------------------------------
// Computes the abs position of a point specified in local space
//-----------------------------------------------------------------------------
void C_BaseEntity::ComputeAbsPosition( const Vector &vecLocalPosition, Vector *pAbsPosition )
{
C_BaseEntity *pMoveParent = GetMoveParent();
if ( !pMoveParent )
{
*pAbsPosition = vecLocalPosition;
}
else
{
VectorTransform( vecLocalPosition, pMoveParent->EntityToWorldTransform(), *pAbsPosition );
}
}
//-----------------------------------------------------------------------------
// Computes the abs position of a point specified in local space
//-----------------------------------------------------------------------------
void C_BaseEntity::ComputeAbsDirection( const Vector &vecLocalDirection, Vector *pAbsDirection )
{
C_BaseEntity *pMoveParent = GetMoveParent();
if ( !pMoveParent )
{
*pAbsDirection = vecLocalDirection;
}
else
{
VectorRotate( vecLocalDirection, pMoveParent->EntityToWorldTransform(), *pAbsDirection );
}
}
//-----------------------------------------------------------------------------
// Mark shadow as dirty
//-----------------------------------------------------------------------------
void C_BaseEntity::MarkRenderHandleDirty( )
{
// Invalidate render leaf too
ClientRenderHandle_t handle = GetRenderHandle();
if ( handle != INVALID_CLIENT_RENDER_HANDLE )
{
ClientLeafSystem()->RenderableChanged( handle );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_BaseEntity::ShutdownPredictable( void )
{
#if !defined( NO_ENTITY_PREDICTION )
Assert( GetPredictable() );
g_Predictables.RemoveFromPredictablesList( GetClientHandle() );
DestroyIntermediateData();
SetPredictable( false );
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Turn entity into something the predicts locally
//-----------------------------------------------------------------------------
void C_BaseEntity::InitPredictable( void )
{
#if !defined( NO_ENTITY_PREDICTION )
Assert( !GetPredictable() );
// Mark as predictable
SetPredictable( true );
// Allocate buffers into which we copy data
AllocateIntermediateData();
// Add to list of predictables
g_Predictables.AddToPredictableList( GetClientHandle() );
// Copy everything from "this" into the original_state_data
// object. Don't care about client local stuff, so pull from slot 0 which
// should be empty anyway...
PostNetworkDataReceived( 0 );
// Copy original data into all prediction slots, so we don't get an error saying we "mispredicted" any
// values which are still at their initial values
for ( int i = 0; i < MULTIPLAYER_BACKUP; i++ )
{
SaveData( "InitPredictable", i, PC_EVERYTHING );
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : state -
//-----------------------------------------------------------------------------
void C_BaseEntity::SetPredictable( bool state )
{
m_bPredictable = state;
// update interpolation times
Interp_UpdateInterpolationAmounts( GetVarMapping() );
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool C_BaseEntity::GetPredictable( void ) const
{
return m_bPredictable;
}
//-----------------------------------------------------------------------------
// Purpose: Transfer data for intermediate frame to current entity
// Input : copyintermediate -
// last_predicted -
//-----------------------------------------------------------------------------
void C_BaseEntity::PreEntityPacketReceived( int commands_acknowledged )
{
#if !defined( NO_ENTITY_PREDICTION )
// Don't need to copy intermediate data if server did ack any new commands
bool copyintermediate = ( commands_acknowledged > 0 ) ? true : false;
Assert( GetPredictable() );
Assert( cl_predict->GetInt() );
// First copy in any intermediate predicted data for non-networked fields
if ( copyintermediate )
{
RestoreData( "PreEntityPacketReceived", commands_acknowledged - 1, PC_NON_NETWORKED_ONLY );
RestoreData( "PreEntityPacketReceived", SLOT_ORIGINALDATA, PC_NETWORKED_ONLY );
}
else
{
RestoreData( "PreEntityPacketReceived(no commands ack)", SLOT_ORIGINALDATA, PC_EVERYTHING );
}
// At this point the entity has original network data restored as of the last time the
// networking was updated, and it has any intermediate predicted values properly copied over
// Unpacked and OnDataChanged will fill in any changed, networked fields.
// That networked data will be copied forward into the starting slot for the next prediction round
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Called every time PreEntityPacket received is called
// copy any networked data into original_state
// Input : errorcheck -
// last_predicted -
//-----------------------------------------------------------------------------
void C_BaseEntity::PostEntityPacketReceived( void )
{
#if !defined( NO_ENTITY_PREDICTION )
Assert( GetPredictable() );
Assert( cl_predict->GetInt() );
// Always mark as changed
AddDataChangeEvent( this, DATA_UPDATE_DATATABLE_CHANGED, &m_DataChangeEventRef );
// Save networked fields into "original data" store
SaveData( "PostEntityPacketReceived", SLOT_ORIGINALDATA, PC_NETWORKED_ONLY );
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Called once per frame after all updating is done
// Input : errorcheck -
// last_predicted -
//-----------------------------------------------------------------------------
bool C_BaseEntity::PostNetworkDataReceived( int commands_acknowledged )
{
bool haderrors = false;
#if !defined( NO_ENTITY_PREDICTION )
Assert( GetPredictable() );
bool errorcheck = ( commands_acknowledged > 0 ) ? true : false;
// Store network data into post networking pristine state slot (slot 64)
SaveData( "PostNetworkDataReceived", SLOT_ORIGINALDATA, PC_EVERYTHING );
// Show any networked fields that are different
bool showthis = cl_showerror.GetInt() >= 2;
if ( cl_showerror.GetInt() < 0 )
{
if ( entindex() == -cl_showerror.GetInt() )
{
showthis = true;
}
else
{
showthis = false;
}
}
if ( errorcheck )
{
void *predicted_state_data = GetPredictedFrame( commands_acknowledged - 1 );
Assert( predicted_state_data );
const void *original_state_data = GetOriginalNetworkDataObject();
Assert( original_state_data );
bool counterrors = true;
bool reporterrors = showthis;
bool copydata = false;
CPredictionCopy errorCheckHelper( PC_NETWORKED_ONLY,
predicted_state_data, PC_DATA_PACKED,
original_state_data, PC_DATA_PACKED,
counterrors, reporterrors, copydata );
// Suppress debugging output
int ecount = errorCheckHelper.TransferData( "", -1, GetPredDescMap() );
if ( ecount > 0 )
{
haderrors = true;
// Msg( "%i errors %i on entity %i %s\n", gpGlobals->tickcount, ecount, index, IsClientCreated() ? "true" : "false" );
}
}
#endif
return haderrors;
}
// Stuff implemented for weapon prediction code
void C_BaseEntity::SetSize( const Vector &vecMin, const Vector &vecMax )
{
SetCollisionBounds( vecMin, vecMax );
}
//-----------------------------------------------------------------------------
// Purpose: Just look up index
// Input : *name -
// Output : int
//-----------------------------------------------------------------------------
int C_BaseEntity::PrecacheModel( const char *name )
{
return modelinfo->GetModelIndex( name );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *obj -
//-----------------------------------------------------------------------------
void C_BaseEntity::Remove( )
{
// Nothing for now, if it's a predicted entity, could flag as "delete" or dormant
if ( GetPredictable() || IsClientCreated() )
{
// Make it solid
AddSolidFlags( FSOLID_NOT_SOLID );
SetMoveType( MOVETYPE_NONE );
AddEFlags( EFL_KILLME ); // Make sure to ignore further calls into here or UTIL_Remove.
}
Release();
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool C_BaseEntity::GetPredictionEligible( void ) const
{
#if !defined( NO_ENTITY_PREDICTION )
return m_bPredictionEligible;
#else
return false;
#endif
}
C_BaseEntity* C_BaseEntity::Instance( CBaseHandle hEnt )
{
return ClientEntityList().GetBaseEntityFromHandle( hEnt );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : iEnt -
// Output : C_BaseEntity
//-----------------------------------------------------------------------------
C_BaseEntity *C_BaseEntity::Instance( int iEnt )
{
return ClientEntityList().GetBaseEntity( iEnt );
}
#ifdef WIN32
#pragma warning( push )
#include <typeinfo.h>
#pragma warning( pop )
#endif
//-----------------------------------------------------------------------------
// Purpose:
// Output : char const
//-----------------------------------------------------------------------------
const char *C_BaseEntity::GetClassname( void )
{
static char outstr[ 256 ];
outstr[ 0 ] = 0;
bool gotname = false;
#ifndef NO_ENTITY_PREDICTION
if ( GetPredDescMap() )
{
const char *mapname = GetClassMap().Lookup( GetPredDescMap()->dataClassName );
if ( mapname && mapname[ 0 ] )
{
Q_strncpy( outstr, mapname, sizeof( outstr ) );
gotname = true;
}
}
#endif
if ( !gotname )
{
Q_strncpy( outstr, typeid( *this ).name(), sizeof( outstr ) );
}
return outstr;
}
const char *C_BaseEntity::GetDebugName( void )
{
return GetClassname();
}
//-----------------------------------------------------------------------------
// Purpose: Creates an entity by string name, but does not spawn it
// Input : *className -
// Output : C_BaseEntity
//-----------------------------------------------------------------------------
C_BaseEntity *CreateEntityByName( const char *className )
{
C_BaseEntity *ent = GetClassMap().CreateEntity( className );
if ( ent )
{
return ent;
}
Warning( "Can't find factory for entity: %s\n", className );
return NULL;
}
#ifdef _DEBUG
CON_COMMAND( cl_sizeof, "Determines the size of the specified client class." )
{
if ( args.ArgC() != 2 )
{
Msg( "cl_sizeof <gameclassname>\n" );
return;
}
int size = GetClassMap().GetClassSize( args[ 1 ] );
Msg( "%s is %i bytes\n", args[ 1 ], size );
}
#endif
CON_COMMAND_F( dlight_debug, "Creates a dlight in front of the player", FCVAR_CHEAT )
{
dlight_t *el = effects->CL_AllocDlight( 1 );
C_BasePlayer *player = C_BasePlayer::GetLocalPlayer();
if ( !player )
return;
Vector start = player->EyePosition();
Vector forward;
player->EyeVectors( &forward );
Vector end = start + forward * MAX_TRACE_LENGTH;
trace_t tr;
UTIL_TraceLine( start, end, MASK_SHOT_HULL & (~CONTENTS_GRATE), player, COLLISION_GROUP_NONE, &tr );
el->origin = tr.endpos - forward * 12.0f;
el->radius = 200;
el->decay = el->radius / 5.0f;
el->die = gpGlobals->curtime + 5.0f;
el->color.r = 255;
el->color.g = 192;
el->color.b = 64;
el->color.exponent = 5;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool C_BaseEntity::IsClientCreated( void ) const
{
#ifndef NO_ENTITY_PREDICTION
if ( m_pPredictionContext != NULL )
{
// For now can't be both
Assert( !GetPredictable() );
return true;
}
#endif
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *classname -
// *module -
// line -
// Output : C_BaseEntity
//-----------------------------------------------------------------------------
C_BaseEntity *C_BaseEntity::CreatePredictedEntityByName( const char *classname, const char *module, int line, bool persist /*= false */ )
{
#if !defined( NO_ENTITY_PREDICTION )
C_BasePlayer *player = C_BaseEntity::GetPredictionPlayer();
Assert( player );
Assert( player->m_pCurrentCommand );
Assert( prediction->InPrediction() );
C_BaseEntity *ent = NULL;
// What's my birthday (should match server)
int command_number = player->m_pCurrentCommand->command_number;
// Who's my daddy?
int player_index = player->entindex() - 1;
// Create id/context
CPredictableId testId;
testId.Init( player_index, command_number, classname, module, line );
// If repredicting, should be able to find the entity in the previously created list
if ( !prediction->IsFirstTimePredicted() )
{
// Only find previous instance if entity was created with persist set
if ( persist )
{
ent = FindPreviouslyCreatedEntity( testId );
if ( ent )
{
return ent;
}
}
return NULL;
}
// Try to create it
ent = CreateEntityByName( classname );
if ( !ent )
{
return NULL;
}
// It's predictable
ent->SetPredictionEligible( true );
// Set up "shared" id number
ent->m_PredictableID.SetRaw( testId.GetRaw() );
// Get a context (mostly for debugging purposes)
PredictionContext *context = new PredictionContext;
context->m_bActive = true;
context->m_nCreationCommandNumber = command_number;
context->m_nCreationLineNumber = line;
context->m_pszCreationModule = module;
// Attach to entity
ent->m_pPredictionContext = context;
// Add to client entity list
ClientEntityList().AddNonNetworkableEntity( ent );
// and predictables
g_Predictables.AddToPredictableList( ent->GetClientHandle() );
// Duhhhh..., but might as well be safe
Assert( !ent->GetPredictable() );
Assert( ent->IsClientCreated() );
// Add the client entity to the spatial partition. (Collidable)
ent->CollisionProp()->CreatePartitionHandle();
// CLIENT ONLY FOR NOW!!!
ent->index = -1;
if ( AddDataChangeEvent( ent, DATA_UPDATE_CREATED, &ent->m_DataChangeEventRef ) )
{
ent->OnPreDataChanged( DATA_UPDATE_CREATED );
}
ent->Interp_UpdateInterpolationAmounts( ent->GetVarMapping() );
return ent;
#else
return NULL;
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Called each packet that the entity is created on and finally gets called after the next packet
// that doesn't have a create message for the "parent" entity so that the predicted version
// can be removed. Return true to delete entity right away.
//-----------------------------------------------------------------------------
bool C_BaseEntity::OnPredictedEntityRemove( bool isbeingremoved, C_BaseEntity *predicted )
{
#if !defined( NO_ENTITY_PREDICTION )
// Nothing right now, but in theory you could look at the error in origins and set
// up something to smooth out the error
PredictionContext *ctx = predicted->m_pPredictionContext;
Assert( ctx );
if ( ctx )
{
// Create backlink to actual entity
ctx->m_hServerEntity = this;
/*
Msg( "OnPredictedEntity%s: %s created %s(%i) instance(%i)\n",
isbeingremoved ? "Remove" : "Acknowledge",
predicted->GetClassname(),
ctx->m_pszCreationModule,
ctx->m_nCreationLineNumber,
predicted->m_PredictableID.GetInstanceNumber() );
*/
}
// If it comes through with an ID, it should be eligible
SetPredictionEligible( true );
// Start predicting simulation forward from here
CheckInitPredictable( "OnPredictedEntityRemove" );
// Always mark it dormant since we are the "real" entity now
predicted->SetDormantPredictable( true );
InvalidatePhysicsRecursive( POSITION_CHANGED | ANGLES_CHANGED | VELOCITY_CHANGED );
// By default, signal that it should be deleted right away
// If a derived class implements this method, it might chain to here but return
// false if it wants to keep the dormant predictable around until the chain of
// DATA_UPDATE_CREATED messages passes
#endif
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pOwner -
//-----------------------------------------------------------------------------
void C_BaseEntity::SetOwnerEntity( C_BaseEntity *pOwner )
{
m_hOwnerEntity = pOwner;
}
//-----------------------------------------------------------------------------
// Purpose: Put the entity in the specified team
//-----------------------------------------------------------------------------
void C_BaseEntity::ChangeTeam( int iTeamNum )
{
m_iTeamNum = iTeamNum;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : name -
//-----------------------------------------------------------------------------
void C_BaseEntity::SetModelName( string_t name )
{
m_ModelName = name;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : string_t
//-----------------------------------------------------------------------------
string_t C_BaseEntity::GetModelName( void ) const
{
return m_ModelName;
}
//-----------------------------------------------------------------------------
// Purpose: Nothing yet, could eventually supercede Term()
//-----------------------------------------------------------------------------
void C_BaseEntity::UpdateOnRemove( void )
{
VPhysicsDestroyObject();
Assert( !GetMoveParent() );
UnlinkFromHierarchy();
SetGroundEntity( NULL );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : canpredict -
//-----------------------------------------------------------------------------
void C_BaseEntity::SetPredictionEligible( bool canpredict )
{
#if !defined( NO_ENTITY_PREDICTION )
m_bPredictionEligible = canpredict;
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Returns a value that scales all damage done by this entity.
//-----------------------------------------------------------------------------
float C_BaseEntity::GetAttackDamageScale( void )
{
float flScale = 1;
// Not hooked up to prediction yet
#if 0
FOR_EACH_LL( m_DamageModifiers, i )
{
if ( !m_DamageModifiers[i]->IsDamageDoneToMe() )
{
flScale *= m_DamageModifiers[i]->GetModifier();
}
}
#endif
return flScale;
}
#if !defined( NO_ENTITY_PREDICTION )
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool C_BaseEntity::IsDormantPredictable( void ) const
{
return m_bDormantPredictable;
}
#endif
//-----------------------------------------------------------------------------
// Purpose:
// Input : dormant -
//-----------------------------------------------------------------------------
void C_BaseEntity::SetDormantPredictable( bool dormant )
{
#if !defined( NO_ENTITY_PREDICTION )
Assert( IsClientCreated() );
m_bDormantPredictable = true;
m_nIncomingPacketEntityBecameDormant = prediction->GetIncomingPacketNumber();
// Do we need to do the following kinds of things?
#if 0
// Remove from collisions
SetSolid( SOLID_NOT );
// Don't render
AddEffects( EF_NODRAW );
#endif
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Used to determine when a dorman client predictable can be safely deleted
// Note that it can be deleted earlier than this by OnPredictedEntityRemove returning true
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool C_BaseEntity::BecameDormantThisPacket( void ) const
{
#if !defined( NO_ENTITY_PREDICTION )
Assert( IsDormantPredictable() );
if ( m_nIncomingPacketEntityBecameDormant != prediction->GetIncomingPacketNumber() )
return false;
return true;
#else
return false;
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool C_BaseEntity::IsIntermediateDataAllocated( void ) const
{
#if !defined( NO_ENTITY_PREDICTION )
return m_pOriginalData != NULL ? true : false;
#else
return false;
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_BaseEntity::AllocateIntermediateData( void )
{
#if !defined( NO_ENTITY_PREDICTION )
if ( m_pOriginalData )
return;
size_t allocsize = GetIntermediateDataSize();
Assert( allocsize > 0 );
m_pOriginalData = new unsigned char[ allocsize ];
Q_memset( m_pOriginalData, 0, allocsize );
for ( int i = 0; i < MULTIPLAYER_BACKUP; i++ )
{
m_pIntermediateData[ i ] = new unsigned char[ allocsize ];
Q_memset( m_pIntermediateData[ i ], 0, allocsize );
}
m_nIntermediateDataCount = 0;
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_BaseEntity::DestroyIntermediateData( void )
{
#if !defined( NO_ENTITY_PREDICTION )
if ( !m_pOriginalData )
return;
for ( int i = 0; i < MULTIPLAYER_BACKUP; i++ )
{
delete[] m_pIntermediateData[ i ];
m_pIntermediateData[ i ] = NULL;
}
delete[] m_pOriginalData;
m_pOriginalData = NULL;
m_nIntermediateDataCount = 0;
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : slots_to_remove -
// number_of_commands_run -
//-----------------------------------------------------------------------------
void C_BaseEntity::ShiftIntermediateDataForward( int slots_to_remove, int number_of_commands_run )
{
#if !defined( NO_ENTITY_PREDICTION )
Assert( m_pIntermediateData );
if ( !m_pIntermediateData )
return;
Assert( number_of_commands_run >= slots_to_remove );
// Just moving pointers, yeah
CUtlVector< unsigned char * > saved;
// Remember first slots
int i = 0;
for ( ; i < slots_to_remove; i++ )
{
saved.AddToTail( m_pIntermediateData[ i ] );
}
// Move rest of slots forward up to last slot
for ( ; i < number_of_commands_run; i++ )
{
m_pIntermediateData[ i - slots_to_remove ] = m_pIntermediateData[ i ];
}
// Put remembered slots onto end
for ( i = 0; i < slots_to_remove; i++ )
{
int slot = number_of_commands_run - slots_to_remove + i;
m_pIntermediateData[ slot ] = saved[ i ];
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : framenumber -
//-----------------------------------------------------------------------------
void *C_BaseEntity::GetPredictedFrame( int framenumber )
{
#if !defined( NO_ENTITY_PREDICTION )
Assert( framenumber >= 0 );
if ( !m_pOriginalData )
{
Assert( 0 );
return NULL;
}
return (void *)m_pIntermediateData[ framenumber % MULTIPLAYER_BACKUP ];
#else
return NULL;
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void *C_BaseEntity::GetOriginalNetworkDataObject( void )
{
#if !defined( NO_ENTITY_PREDICTION )
if ( !m_pOriginalData )
{
Assert( 0 );
return NULL;
}
return (void *)m_pOriginalData;
#else
return NULL;
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_BaseEntity::ComputePackedOffsets( void )
{
#if !defined( NO_ENTITY_PREDICTION )
datamap_t *map = GetPredDescMap();
if ( !map )
return;
if ( map->packed_offsets_computed )
return;
ComputePackedSize_R( map );
Assert( map->packed_offsets_computed );
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : int
//-----------------------------------------------------------------------------
int C_BaseEntity::GetIntermediateDataSize( void )
{
#if !defined( NO_ENTITY_PREDICTION )
ComputePackedOffsets();
const datamap_t *map = GetPredDescMap();
Assert( map->packed_offsets_computed );
int size = map->packed_size;
Assert( size > 0 );
// At least 4 bytes to avoid some really bad stuff
return MAX( size, 4 );
#else
return 0;
#endif
}
static int g_FieldSizes[FIELD_TYPECOUNT] =
{
0, // FIELD_VOID
sizeof(float), // FIELD_FLOAT
sizeof(int), // FIELD_STRING
sizeof(Vector), // FIELD_VECTOR
sizeof(Quaternion), // FIELD_QUATERNION
sizeof(int), // FIELD_INTEGER
sizeof(char), // FIELD_BOOLEAN
sizeof(short), // FIELD_SHORT
sizeof(char), // FIELD_CHARACTER
sizeof(color32), // FIELD_COLOR32
sizeof(int), // FIELD_EMBEDDED (handled specially)
sizeof(int), // FIELD_CUSTOM (handled specially)
//---------------------------------
sizeof(int), // FIELD_CLASSPTR
sizeof(EHANDLE), // FIELD_EHANDLE
sizeof(int), // FIELD_EDICT
sizeof(Vector), // FIELD_POSITION_VECTOR
sizeof(float), // FIELD_TIME
sizeof(int), // FIELD_TICK
sizeof(int), // FIELD_MODELNAME
sizeof(int), // FIELD_SOUNDNAME
sizeof(int), // FIELD_INPUT (uses custom type)
#ifdef GNUC
// pointer to members under gnuc are 8bytes if you have a virtual func
sizeof(uint64), // FIELD_FUNCTION
#else
sizeof(int *), // FIELD_FUNCTION
#endif
sizeof(VMatrix), // FIELD_VMATRIX
sizeof(VMatrix), // FIELD_VMATRIX_WORLDSPACE
sizeof(matrix3x4_t),// FIELD_MATRIX3X4_WORLDSPACE // NOTE: Use array(FIELD_FLOAT, 12) for matrix3x4_t NOT in worldspace
sizeof(interval_t), // FIELD_INTERVAL
sizeof(int), // FIELD_MODELINDEX
};
//-----------------------------------------------------------------------------
// Purpose:
// Input : *map -
// Output : int
//-----------------------------------------------------------------------------
int C_BaseEntity::ComputePackedSize_R( datamap_t *map )
{
if ( !map )
{
Assert( 0 );
return 0;
}
// Already computed
if ( map->packed_offsets_computed )
{
return map->packed_size;
}
int current_position = 0;
// Recurse to base classes first...
if ( map->baseMap )
{
current_position += ComputePackedSize_R( map->baseMap );
}
int c = map->dataNumFields;
int i;
typedescription_t *field;
for ( i = 0; i < c; i++ )
{
field = &map->dataDesc[ i ];
// Always descend into embedded types...
if ( field->fieldType != FIELD_EMBEDDED )
{
// Skip all private fields
if ( field->flags & FTYPEDESC_PRIVATE )
continue;
}
switch ( field->fieldType )
{
default:
case FIELD_MODELINDEX:
case FIELD_MODELNAME:
case FIELD_SOUNDNAME:
case FIELD_TIME:
case FIELD_TICK:
case FIELD_CUSTOM:
case FIELD_CLASSPTR:
case FIELD_EDICT:
case FIELD_POSITION_VECTOR:
case FIELD_FUNCTION:
Assert( 0 );
break;
case FIELD_EMBEDDED:
{
Assert( field->td != NULL );
int embeddedsize = ComputePackedSize_R( field->td );
field->fieldOffset[ TD_OFFSET_PACKED ] = current_position;
current_position += embeddedsize;
}
break;
case FIELD_FLOAT:
case FIELD_VECTOR:
case FIELD_QUATERNION:
case FIELD_INTEGER:
case FIELD_EHANDLE:
{
// These should be dword aligned
current_position = (current_position + 3) & ~3;
field->fieldOffset[ TD_OFFSET_PACKED ] = current_position;
Assert( field->fieldSize >= 1 );
current_position += g_FieldSizes[ field->fieldType ] * field->fieldSize;
}
break;
case FIELD_SHORT:
{
// This should be word aligned
current_position = (current_position + 1) & ~1;
field->fieldOffset[ TD_OFFSET_PACKED ] = current_position;
Assert( field->fieldSize >= 1 );
current_position += g_FieldSizes[ field->fieldType ] * field->fieldSize;
}
break;
case FIELD_STRING:
case FIELD_COLOR32:
case FIELD_BOOLEAN:
case FIELD_CHARACTER:
{
field->fieldOffset[ TD_OFFSET_PACKED ] = current_position;
Assert( field->fieldSize >= 1 );
current_position += g_FieldSizes[ field->fieldType ] * field->fieldSize;
}
break;
case FIELD_VOID:
{
// Special case, just skip it
}
break;
}
}
map->packed_size = current_position;
map->packed_offsets_computed = true;
return current_position;
}
// Convenient way to delay removing oneself
void C_BaseEntity::SUB_Remove( void )
{
if (m_iHealth > 0)
{
// this situation can screw up NPCs who can't tell their entity pointers are invalid.
m_iHealth = 0;
DevWarning( 2, "SUB_Remove called on entity with health > 0\n");
}
Remove( );
}
CBaseEntity *FindEntityInFrontOfLocalPlayer()
{
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
if ( pPlayer )
{
// Get the entity under my crosshair
trace_t tr;
Vector forward;
pPlayer->EyeVectors( &forward );
UTIL_TraceLine( pPlayer->EyePosition(), pPlayer->EyePosition() + forward * MAX_COORD_RANGE, MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr );
if ( tr.fraction != 1.0 && tr.DidHitNonWorldEntity() )
{
return tr.m_pEnt;
}
}
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose: Debug command to wipe the decals off an entity
//-----------------------------------------------------------------------------
static void RemoveDecals_f( void )
{
CBaseEntity *pHit = FindEntityInFrontOfLocalPlayer();
if ( pHit )
{
pHit->RemoveAllDecals();
}
}
static ConCommand cl_removedecals( "cl_removedecals", RemoveDecals_f, "Remove the decals from the entity under the crosshair.", FCVAR_CHEAT );
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_BaseEntity::ToggleBBoxVisualization( int fVisFlags )
{
if ( m_fBBoxVisFlags & fVisFlags )
{
m_fBBoxVisFlags &= ~fVisFlags;
}
else
{
m_fBBoxVisFlags |= fVisFlags;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
static void ToggleBBoxVisualization( int fVisFlags, const CCommand &args )
{
CBaseEntity *pHit;
int iEntity = -1;
if ( args.ArgC() >= 2 )
{
iEntity = atoi( args[ 1 ] );
}
if ( iEntity == -1 )
{
pHit = FindEntityInFrontOfLocalPlayer();
}
else
{
pHit = cl_entitylist->GetBaseEntity( iEntity );
}
if ( pHit )
{
pHit->ToggleBBoxVisualization( fVisFlags );
}
}
//-----------------------------------------------------------------------------
// Purpose: Command to toggle visualizations of bboxes on the client
//-----------------------------------------------------------------------------
CON_COMMAND_F( cl_ent_bbox, "Displays the client's bounding box for the entity under the crosshair.", FCVAR_CHEAT )
{
ToggleBBoxVisualization( CBaseEntity::VISUALIZE_COLLISION_BOUNDS, args );
}
//-----------------------------------------------------------------------------
// Purpose: Command to toggle visualizations of bboxes on the client
//-----------------------------------------------------------------------------
CON_COMMAND_F( cl_ent_absbox, "Displays the client's absbox for the entity under the crosshair.", FCVAR_CHEAT )
{
ToggleBBoxVisualization( CBaseEntity::VISUALIZE_SURROUNDING_BOUNDS, args );
}
//-----------------------------------------------------------------------------
// Purpose: Command to toggle visualizations of bboxes on the client
//-----------------------------------------------------------------------------
CON_COMMAND_F( cl_ent_rbox, "Displays the client's render box for the entity under the crosshair.", FCVAR_CHEAT )
{
ToggleBBoxVisualization( CBaseEntity::VISUALIZE_RENDER_BOUNDS, args );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_BaseEntity::DrawBBoxVisualizations( void )
{
if ( m_fBBoxVisFlags & VISUALIZE_COLLISION_BOUNDS )
{
debugoverlay->AddBoxOverlay( CollisionProp()->GetCollisionOrigin(), CollisionProp()->OBBMins(),
CollisionProp()->OBBMaxs(), CollisionProp()->GetCollisionAngles(), 190, 190, 0, 0, 0.01 );
}
if ( m_fBBoxVisFlags & VISUALIZE_SURROUNDING_BOUNDS )
{
Vector vecSurroundMins, vecSurroundMaxs;
CollisionProp()->WorldSpaceSurroundingBounds( &vecSurroundMins, &vecSurroundMaxs );
debugoverlay->AddBoxOverlay( vec3_origin, vecSurroundMins,
vecSurroundMaxs, vec3_angle, 0, 255, 255, 0, 0.01 );
}
if ( m_fBBoxVisFlags & VISUALIZE_RENDER_BOUNDS || r_drawrenderboxes.GetInt() )
{
Vector vecRenderMins, vecRenderMaxs;
GetRenderBounds( vecRenderMins, vecRenderMaxs );
debugoverlay->AddBoxOverlay( GetRenderOrigin(), vecRenderMins, vecRenderMaxs,
GetRenderAngles(), 255, 0, 255, 0, 0.01 );
}
}
//-----------------------------------------------------------------------------
// Sets the render mode
//-----------------------------------------------------------------------------
void C_BaseEntity::SetRenderMode( RenderMode_t nRenderMode, bool bForceUpdate )
{
m_nRenderMode = nRenderMode;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : RenderGroup_t
//-----------------------------------------------------------------------------
RenderGroup_t C_BaseEntity::GetRenderGroup()
{
// Don't sort things that don't need rendering
if ( m_nRenderMode == kRenderNone )
return RENDER_GROUP_OPAQUE_ENTITY;
// When an entity has a material proxy, we have to recompute
// translucency here because the proxy may have changed it.
if (modelinfo->ModelHasMaterialProxy( GetModel() ))
{
modelinfo->RecomputeTranslucency( const_cast<model_t*>(GetModel()), GetSkin(), GetBody(), GetClientRenderable() );
}
// NOTE: Bypassing the GetFXBlend protection logic because we want this to
// be able to be called from AddToLeafSystem.
int nTempComputeFrame = m_nFXComputeFrame;
m_nFXComputeFrame = gpGlobals->framecount;
int nFXBlend = GetFxBlend();
m_nFXComputeFrame = nTempComputeFrame;
// Don't need to sort invisible stuff
if ( nFXBlend == 0 )
return RENDER_GROUP_OPAQUE_ENTITY;
// Figure out its RenderGroup.
int modelType = modelinfo->GetModelType( model );
RenderGroup_t renderGroup = (modelType == mod_brush) ? RENDER_GROUP_OPAQUE_BRUSH : RENDER_GROUP_OPAQUE_ENTITY;
if ( ( nFXBlend != 255 ) || IsTransparent() )
{
if ( m_nRenderMode != kRenderEnvironmental )
{
renderGroup = RENDER_GROUP_TRANSLUCENT_ENTITY;
}
else
{
renderGroup = RENDER_GROUP_OTHER;
}
}
if ( ( renderGroup == RENDER_GROUP_TRANSLUCENT_ENTITY ) &&
( modelinfo->IsTranslucentTwoPass( model ) ) )
{
renderGroup = RENDER_GROUP_TWOPASS;
}
return renderGroup;
}
//-----------------------------------------------------------------------------
// Purpose: Copy from this entity into one of the save slots (original or intermediate)
// Input : slot -
// type -
// false -
// false -
// true -
// false -
// NULL -
// Output : int
//-----------------------------------------------------------------------------
int C_BaseEntity::SaveData( const char *context, int slot, int type )
{
#if !defined( NO_ENTITY_PREDICTION )
VPROF( "C_BaseEntity::SaveData" );
void *dest = ( slot == SLOT_ORIGINALDATA ) ? GetOriginalNetworkDataObject() : GetPredictedFrame( slot );
Assert( dest );
char sz[ 64 ];
sz[0] = 0;
// don't build debug strings per entity per frame, unless we are watching the entity
static ConVarRef pwatchent( "pwatchent" );
if ( pwatchent.GetInt() == entindex() )
{
if ( slot == SLOT_ORIGINALDATA )
{
Q_snprintf( sz, sizeof( sz ), "%s SaveData(original)", context );
}
else
{
Q_snprintf( sz, sizeof( sz ), "%s SaveData(slot %02i)", context, slot );
}
}
if ( slot != SLOT_ORIGINALDATA )
{
// Remember high water mark so that we can detect below if we are reading from a slot not yet predicted into...
m_nIntermediateDataCount = slot;
}
CPredictionCopy copyHelper( type, dest, PC_DATA_PACKED, this, PC_DATA_NORMAL );
int error_count = copyHelper.TransferData( sz, entindex(), GetPredDescMap() );
return error_count;
#else
return 0;
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Restore data from specified slot into current entity
// Input : slot -
// type -
// false -
// false -
// true -
// false -
// NULL -
// Output : int
//-----------------------------------------------------------------------------
int C_BaseEntity::RestoreData( const char *context, int slot, int type )
{
#if !defined( NO_ENTITY_PREDICTION )
VPROF( "C_BaseEntity::RestoreData" );
const void *src = ( slot == SLOT_ORIGINALDATA ) ? GetOriginalNetworkDataObject() : GetPredictedFrame( slot );
Assert( src );
// This assert will fire if the server ack'd a CUserCmd which we hadn't predicted yet...
// In that case, we'd be comparing "old" data from this "unused" slot with the networked data and reporting all kinds of prediction errors possibly.
Assert( slot == SLOT_ORIGINALDATA || slot <= m_nIntermediateDataCount );
char sz[ 64 ];
sz[0] = 0;
// don't build debug strings per entity per frame, unless we are watching the entity
static ConVarRef pwatchent( "pwatchent" );
if ( pwatchent.GetInt() == entindex() )
{
if ( slot == SLOT_ORIGINALDATA )
{
Q_snprintf( sz, sizeof( sz ), "%s RestoreData(original)", context );
}
else
{
Q_snprintf( sz, sizeof( sz ), "%s RestoreData(slot %02i)", context, slot );
}
}
// some flags shouldn't be predicted - as we find them, add them to the savedEFlagsMask
const int savedEFlagsMask = EFL_DIRTY_SHADOWUPDATE;
int savedEFlags = GetEFlags() & savedEFlagsMask;
// model index needs to be set manually for dynamic model refcounting purposes
int oldModelIndex = m_nModelIndex;
CPredictionCopy copyHelper( type, this, PC_DATA_NORMAL, src, PC_DATA_PACKED );
int error_count = copyHelper.TransferData( sz, entindex(), GetPredDescMap() );
// set non-predicting flags back to their prior state
RemoveEFlags( savedEFlagsMask );
AddEFlags( savedEFlags );
// restore original model index and change via SetModelIndex
int newModelIndex = m_nModelIndex;
m_nModelIndex = oldModelIndex;
int overrideModelIndex = CalcOverrideModelIndex();
if( overrideModelIndex != -1 )
newModelIndex = overrideModelIndex;
if ( oldModelIndex != newModelIndex )
{
MDLCACHE_CRITICAL_SECTION(); // ???
SetModelIndex( newModelIndex );
}
OnPostRestoreData();
return error_count;
#else
return 0;
#endif
}
void C_BaseEntity::OnPostRestoreData()
{
// HACK Force recomputation of origin
InvalidatePhysicsRecursive( POSITION_CHANGED | ANGLES_CHANGED | VELOCITY_CHANGED );
if ( GetMoveParent() )
{
AddToAimEntsList();
}
// If our model index has changed, then make sure it's reflected in our model pointer.
// (Mostly superseded by new modelindex delta check in RestoreData, but I'm leaving it
// because it might be band-aiding any other missed calls to SetModelByIndex --henryg)
if ( GetModel() != modelinfo->GetModel( GetModelIndex() ) )
{
MDLCACHE_CRITICAL_SECTION();
SetModelByIndex( GetModelIndex() );
}
}
//-----------------------------------------------------------------------------
// Purpose: Determine approximate velocity based on updates from server
// Input : vel -
//-----------------------------------------------------------------------------
void C_BaseEntity::EstimateAbsVelocity( Vector& vel )
{
if ( this == C_BasePlayer::GetLocalPlayer() )
{
// This is interpolated and networked
vel = GetAbsVelocity();
return;
}
CInterpolationContext context;
context.EnableExtrapolation( true );
m_iv_vecOrigin.GetDerivative_SmoothVelocity( &vel, gpGlobals->curtime );
}
void C_BaseEntity::Interp_Reset( VarMapping_t *map )
{
PREDICTION_TRACKVALUECHANGESCOPE_ENTITY( this, "reset" );
int c = map->m_Entries.Count();
for ( int i = 0; i < c; i++ )
{
VarMapEntry_t *e = &map->m_Entries[ i ];
IInterpolatedVar *watcher = e->watcher;
watcher->Reset();
}
}
void C_BaseEntity::ResetLatched()
{
if ( IsClientCreated() )
return;
Interp_Reset( GetVarMapping() );
}
//-----------------------------------------------------------------------------
// Purpose: Fixme, this needs a better solution
// Input : flags -
// Output : float
//-----------------------------------------------------------------------------
static float AdjustInterpolationAmount( C_BaseEntity *pEntity, float baseInterpolation )
{
if ( cl_interp_npcs.GetFloat() > 0 )
{
const float minNPCInterpolationTime = cl_interp_npcs.GetFloat();
const float minNPCInterpolation = TICK_INTERVAL * ( TIME_TO_TICKS( minNPCInterpolationTime ) + 1 );
if ( minNPCInterpolation > baseInterpolation )
{
while ( pEntity )
{
if ( pEntity->IsNPC() )
return minNPCInterpolation;
pEntity = pEntity->GetMoveParent();
}
}
}
return baseInterpolation;
}
//-------------------------------------
float C_BaseEntity::GetInterpolationAmount( int flags )
{
// If single player server is "skipping ticks" everything needs to interpolate for a bit longer
int serverTickMultiple = 1;
if ( IsSimulatingOnAlternateTicks() )
{
serverTickMultiple = 2;
}
if ( GetPredictable() || IsClientCreated() )
{
return TICK_INTERVAL * serverTickMultiple;
}
// Always fully interpolate during multi-player or during demo playback, if the recorded
// demo was recorded locally.
const bool bPlayingDemo = engine->IsPlayingDemo();
const bool bPlayingMultiplayer = !bPlayingDemo && ( gpGlobals->maxClients > 1 );
const bool bPlayingNonLocallyRecordedDemo = bPlayingDemo && !engine->IsPlayingDemoALocallyRecordedDemo();
if ( bPlayingMultiplayer || bPlayingNonLocallyRecordedDemo )
{
return AdjustInterpolationAmount( this, TICKS_TO_TIME( TIME_TO_TICKS( GetClientInterpAmount() ) + serverTickMultiple ) );
}
int expandedServerTickMultiple = serverTickMultiple;
if ( IsEngineThreaded() )
{
expandedServerTickMultiple += g_nThreadModeTicks;
}
if ( IsAnimatedEveryTick() && IsSimulatedEveryTick() )
{
return TICK_INTERVAL * expandedServerTickMultiple;
}
if ( ( flags & LATCH_ANIMATION_VAR ) && IsAnimatedEveryTick() )
{
return TICK_INTERVAL * expandedServerTickMultiple;
}
if ( ( flags & LATCH_SIMULATION_VAR ) && IsSimulatedEveryTick() )
{
return TICK_INTERVAL * expandedServerTickMultiple;
}
return AdjustInterpolationAmount( this, TICKS_TO_TIME( TIME_TO_TICKS( GetClientInterpAmount() ) + serverTickMultiple ) );
}
float C_BaseEntity::GetLastChangeTime( int flags )
{
if ( GetPredictable() || IsClientCreated() )
{
return gpGlobals->curtime;
}
// make sure not both flags are set, we can't resolve that
Assert( !( (flags & LATCH_ANIMATION_VAR) && (flags & LATCH_SIMULATION_VAR) ) );
if ( flags & LATCH_ANIMATION_VAR )
{
return GetAnimTime();
}
if ( flags & LATCH_SIMULATION_VAR )
{
float st = GetSimulationTime();
if ( st == 0.0f )
{
return gpGlobals->curtime;
}
return st;
}
Assert( 0 );
return gpGlobals->curtime;
}
const Vector& C_BaseEntity::GetPrevLocalOrigin() const
{
return m_iv_vecOrigin.GetPrev();
}
const QAngle& C_BaseEntity::GetPrevLocalAngles() const
{
return m_iv_angRotation.GetPrev();
}
//-----------------------------------------------------------------------------
// Simply here for game shared
//-----------------------------------------------------------------------------
bool C_BaseEntity::IsFloating()
{
// NOTE: This is only here because it's called by game shared.
// The server uses it to lower falling impact damage
return false;
}
BEGIN_DATADESC_NO_BASE( C_BaseEntity )
DEFINE_FIELD( m_ModelName, FIELD_STRING ),
DEFINE_FIELD( m_vecAbsOrigin, FIELD_POSITION_VECTOR ),
DEFINE_FIELD( m_angAbsRotation, FIELD_VECTOR ),
DEFINE_ARRAY( m_rgflCoordinateFrame, FIELD_FLOAT, 12 ), // NOTE: MUST BE IN LOCAL SPACE, NOT POSITION_VECTOR!!! (see CBaseEntity::Restore)
DEFINE_FIELD( m_fFlags, FIELD_INTEGER ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool C_BaseEntity::ShouldSavePhysics()
{
return false;
}
//-----------------------------------------------------------------------------
// handler to do stuff before you are saved
//-----------------------------------------------------------------------------
void C_BaseEntity::OnSave()
{
// Here, we must force recomputation of all abs data so it gets saved correctly
// We can't leave the dirty bits set because the loader can't cope with it.
CalcAbsolutePosition();
CalcAbsoluteVelocity();
}
//-----------------------------------------------------------------------------
// handler to do stuff after you are restored
//-----------------------------------------------------------------------------
void C_BaseEntity::OnRestore()
{
InvalidatePhysicsRecursive( POSITION_CHANGED | ANGLES_CHANGED | VELOCITY_CHANGED );
UpdatePartitionListEntry();
CollisionProp()->UpdatePartition();
UpdateVisibility();
}
//-----------------------------------------------------------------------------
// Purpose: Saves the current object out to disk, by iterating through the objects
// data description hierarchy
// Input : &save - save buffer which the class data is written to
// Output : int - 0 if the save failed, 1 on success
//-----------------------------------------------------------------------------
int C_BaseEntity::Save( ISave &save )
{
// loop through the data description list, saving each data desc block
int status = SaveDataDescBlock( save, GetDataDescMap() );
return status;
}
//-----------------------------------------------------------------------------
// Purpose: Recursively saves all the classes in an object, in reverse order (top down)
// Output : int 0 on failure, 1 on success
//-----------------------------------------------------------------------------
int C_BaseEntity::SaveDataDescBlock( ISave &save, datamap_t *dmap )
{
int nResult = save.WriteAll( this, dmap );
return nResult;
}
void C_BaseEntity::SetClassname( const char *className )
{
m_iClassname = MAKE_STRING( className );
}
//-----------------------------------------------------------------------------
// Purpose: Restores the current object from disk, by iterating through the objects
// data description hierarchy
// Input : &restore - restore buffer which the class data is read from
// Output : int - 0 if the restore failed, 1 on success
//-----------------------------------------------------------------------------
int C_BaseEntity::Restore( IRestore &restore )
{
// loops through the data description list, restoring each data desc block in order
int status = RestoreDataDescBlock( restore, GetDataDescMap() );
// NOTE: Do *not* use GetAbsOrigin() here because it will
// try to recompute m_rgflCoordinateFrame!
MatrixSetColumn( m_vecAbsOrigin, 3, m_rgflCoordinateFrame );
// Restablish ground entity
if ( m_hGroundEntity != NULL )
{
m_hGroundEntity->AddEntityToGroundList( this );
}
return status;
}
//-----------------------------------------------------------------------------
// Purpose: Recursively restores all the classes in an object, in reverse order (top down)
// Output : int 0 on failure, 1 on success
//-----------------------------------------------------------------------------
int C_BaseEntity::RestoreDataDescBlock( IRestore &restore, datamap_t *dmap )
{
return restore.ReadAll( this, dmap );
}
//-----------------------------------------------------------------------------
// capabilities
//-----------------------------------------------------------------------------
int C_BaseEntity::ObjectCaps( void )
{
return 0;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : C_AI_BaseNPC
//-----------------------------------------------------------------------------
C_AI_BaseNPC *C_BaseEntity::MyNPCPointer( void )
{
if ( IsNPC() )
{
return assert_cast<C_AI_BaseNPC *>(this);
}
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose: For each client (only can be local client in client .dll ) checks the client has disabled CC and if so, removes them from
// the recipient list.
// Input : filter -
//-----------------------------------------------------------------------------
void C_BaseEntity::RemoveRecipientsIfNotCloseCaptioning( C_RecipientFilter& filter )
{
extern ConVar closecaption;
if ( !closecaption.GetBool() )
{
filter.Reset();
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : recording -
// Output : inline void
//-----------------------------------------------------------------------------
void C_BaseEntity::EnableInToolView( bool bEnable )
{
#ifndef NO_TOOLFRAMEWORK
m_bEnabledInToolView = bEnable;
UpdateVisibility();
#endif
}
void C_BaseEntity::SetToolRecording( bool recording )
{
#ifndef NO_TOOLFRAMEWORK
m_bToolRecording = recording;
if ( m_bToolRecording )
{
recordinglist->AddToList( GetClientHandle() );
}
else
{
recordinglist->RemoveFromList( GetClientHandle() );
}
#endif
}
bool C_BaseEntity::HasRecordedThisFrame() const
{
#ifndef NO_TOOLFRAMEWORK
Assert( m_nLastRecordedFrame <= gpGlobals->framecount );
return m_nLastRecordedFrame == gpGlobals->framecount;
#else
return false;
#endif
}
void C_BaseEntity::GetToolRecordingState( KeyValues *msg )
{
Assert( ToolsEnabled() );
if ( !ToolsEnabled() )
return;
VPROF_BUDGET( "C_BaseEntity::GetToolRecordingState", VPROF_BUDGETGROUP_TOOLS );
C_BaseEntity *pOwner = m_hOwnerEntity;
static BaseEntityRecordingState_t state;
state.m_flTime = gpGlobals->curtime;
state.m_pModelName = modelinfo->GetModelName( GetModel() );
state.m_nOwner = pOwner ? pOwner->entindex() : -1;
state.m_nEffects = m_fEffects;
state.m_bVisible = ShouldDraw() && !IsDormant();
state.m_bRecordFinalVisibleSample = false;
state.m_vecRenderOrigin = GetRenderOrigin();
state.m_vecRenderAngles = GetRenderAngles();
// use EF_NOINTERP if the owner or a hierarchical parent has NO_INTERP
if ( pOwner && pOwner->IsNoInterpolationFrame() )
{
state.m_nEffects |= EF_NOINTERP;
}
C_BaseEntity *pParent = GetMoveParent();
while ( pParent )
{
if ( pParent->IsNoInterpolationFrame() )
{
state.m_nEffects |= EF_NOINTERP;
break;
}
pParent = pParent->GetMoveParent();
}
msg->SetPtr( "baseentity", &state );
}
void C_BaseEntity::CleanupToolRecordingState( KeyValues *msg )
{
}
void C_BaseEntity::RecordToolMessage()
{
Assert( IsToolRecording() );
if ( !IsToolRecording() )
return;
if ( HasRecordedThisFrame() )
return;
KeyValues *msg = new KeyValues( "entity_state" );
// Post a message back to all IToolSystems
GetToolRecordingState( msg );
Assert( (int)GetToolHandle() != 0 );
ToolFramework_PostToolMessage( GetToolHandle(), msg );
CleanupToolRecordingState( msg );
msg->deleteThis();
m_nLastRecordedFrame = gpGlobals->framecount;
}
// (static function)
void C_BaseEntity::ToolRecordEntities()
{
VPROF_BUDGET( "C_BaseEntity::ToolRecordEnties", VPROF_BUDGETGROUP_TOOLS );
if ( !ToolsEnabled() || !clienttools->IsInRecordingMode() )
return;
// Let non-dormant client created predictables get added, too
int c = recordinglist->Count();
for ( int i = 0 ; i < c ; i++ )
{
IClientRenderable *pRenderable = recordinglist->Get( i );
if ( !pRenderable )
continue;
pRenderable->RecordToolMessage();
}
}
void C_BaseEntity::AddToInterpolationList()
{
if ( m_InterpolationListEntry == 0xFFFF )
m_InterpolationListEntry = g_InterpolationList.AddToTail( this );
}
void C_BaseEntity::RemoveFromInterpolationList()
{
if ( m_InterpolationListEntry != 0xFFFF )
{
g_InterpolationList.Remove( m_InterpolationListEntry );
m_InterpolationListEntry = 0xFFFF;
}
}
void C_BaseEntity::AddToTeleportList()
{
if ( m_TeleportListEntry == 0xFFFF )
m_TeleportListEntry = g_TeleportList.AddToTail( this );
}
void C_BaseEntity::RemoveFromTeleportList()
{
if ( m_TeleportListEntry != 0xFFFF )
{
g_TeleportList.Remove( m_TeleportListEntry );
m_TeleportListEntry = 0xFFFF;
}
}
#ifdef TF_CLIENT_DLL
bool C_BaseEntity::ValidateEntityAttachedToPlayer( bool &bShouldRetry )
{
bShouldRetry = false;
C_BaseEntity *pParent = GetRootMoveParent();
if ( pParent == this )
return true;
// Some wearables parent to the view model
C_BasePlayer *pPlayer = ToBasePlayer( pParent );
if ( pPlayer && pPlayer->GetViewModel() == this )
{
return true;
}
// always allow the briefcase model
const char *pszModel = modelinfo->GetModelName( GetModel() );
if ( pszModel && pszModel[0] )
{
if ( FStrEq( pszModel, "models/flag/briefcase.mdl" ) )
return true;
if ( FStrEq( pszModel, "models/props_doomsday/australium_container.mdl" ) )
return true;
// Temp for MVM testing
if ( FStrEq( pszModel, "models/buildables/sapper_placement_sentry1.mdl" ) )
return true;
if ( FStrEq( pszModel, "models/props_td/atom_bomb.mdl" ) )
return true;
if ( FStrEq( pszModel, "models/props_lakeside_event/bomb_temp_hat.mdl" ) )
return true;
}
// Any entity that's not an item parented to a player is invalid.
// This prevents them creating some other entity to pretend to be a cosmetic item.
return !pParent->IsPlayer();
}
#endif // TF_CLIENT_DLL
void C_BaseEntity::AddVar( void *data, IInterpolatedVar *watcher, int type, bool bSetup )
{
// Only add it if it hasn't been added yet.
bool bAddIt = true;
for ( int i=0; i < m_VarMap.m_Entries.Count(); i++ )
{
if ( m_VarMap.m_Entries[i].watcher == watcher )
{
if ( (type & EXCLUDE_AUTO_INTERPOLATE) != (watcher->GetType() & EXCLUDE_AUTO_INTERPOLATE) )
{
// Its interpolation mode changed, so get rid of it and re-add it.
RemoveVar( m_VarMap.m_Entries[i].data, true );
}
else
{
// They're adding something that's already there. No need to re-add it.
bAddIt = false;
}
break;
}
}
if ( bAddIt )
{
// watchers must have a debug name set
Assert( watcher->GetDebugName() != NULL );
VarMapEntry_t map;
map.data = data;
map.watcher = watcher;
map.type = type;
map.m_bNeedsToInterpolate = true;
if ( type & EXCLUDE_AUTO_INTERPOLATE )
{
m_VarMap.m_Entries.AddToTail( map );
}
else
{
m_VarMap.m_Entries.AddToHead( map );
++m_VarMap.m_nInterpolatedEntries;
}
}
if ( bSetup )
{
watcher->Setup( data, type );
watcher->SetInterpolationAmount( GetInterpolationAmount( watcher->GetType() ) );
}
}
void C_BaseEntity::RemoveVar( void *data, bool bAssert )
{
for ( int i=0; i < m_VarMap.m_Entries.Count(); i++ )
{
if ( m_VarMap.m_Entries[i].data == data )
{
if ( !( m_VarMap.m_Entries[i].type & EXCLUDE_AUTO_INTERPOLATE ) )
--m_VarMap.m_nInterpolatedEntries;
m_VarMap.m_Entries.Remove( i );
return;
}
}
if ( bAssert )
{
Assert( !"RemoveVar" );
}
}
void C_BaseEntity::CheckCLInterpChanged()
{
float flCurValue_Interp = GetClientInterpAmount();
static float flLastValue_Interp = flCurValue_Interp;
float flCurValue_InterpNPCs = cl_interp_npcs.GetFloat();
static float flLastValue_InterpNPCs = flCurValue_InterpNPCs;
if ( flLastValue_Interp != flCurValue_Interp ||
flLastValue_InterpNPCs != flCurValue_InterpNPCs )
{
flLastValue_Interp = flCurValue_Interp;
flLastValue_InterpNPCs = flCurValue_InterpNPCs;
// Tell all the existing entities to update their interpolation amounts to account for the change.
C_BaseEntityIterator iterator;
C_BaseEntity *pEnt;
while ( (pEnt = iterator.Next()) != NULL )
{
pEnt->Interp_UpdateInterpolationAmounts( pEnt->GetVarMapping() );
}
}
}
void C_BaseEntity::DontRecordInTools()
{
#ifndef NO_TOOLFRAMEWORK
m_bRecordInTools = false;
#endif
}
int C_BaseEntity::GetCreationTick() const
{
return m_nCreationTick;
}
//------------------------------------------------------------------------------
void CC_CL_Find_Ent( const CCommand& args )
{
if ( args.ArgC() < 2 )
{
Msg( "Format: cl_find_ent <substring>\n" );
return;
}
int iCount = 0;
const char *pszSubString = args[1];
Msg("Searching for client entities with classname containing substring: '%s'\n", pszSubString );
C_BaseEntity *ent = NULL;
while ( (ent = ClientEntityList().NextBaseEntity(ent)) != NULL )
{
const char *pszClassname = ent->GetClassname();
bool bMatches = false;
if ( pszClassname && pszClassname[0] )
{
if ( Q_stristr( pszClassname, pszSubString ) )
{
bMatches = true;
}
}
if ( bMatches )
{
iCount++;
Msg(" '%s' (entindex %d) %s \n", pszClassname ? pszClassname : "[NO NAME]", ent->entindex(), ent->IsDormant() ? "(DORMANT)" : "" );
}
}
Msg("Found %d matches.\n", iCount);
}
static ConCommand cl_find_ent("cl_find_ent", CC_CL_Find_Ent, "Find and list all client entities with classnames that contain the specified substring.\nFormat: cl_find_ent <substring>\n", FCVAR_CHEAT);
//------------------------------------------------------------------------------
void CC_CL_Find_Ent_Index( const CCommand& args )
{
if ( args.ArgC() < 2 )
{
Msg( "Format: cl_find_ent_index <index>\n" );
return;
}
int iIndex = atoi(args[1]);
C_BaseEntity *ent = ClientEntityList().GetBaseEntity( iIndex );
if ( ent )
{
const char *pszClassname = ent->GetClassname();
Msg(" '%s' (entindex %d) %s \n", pszClassname ? pszClassname : "[NO NAME]", iIndex, ent->IsDormant() ? "(DORMANT)" : "" );
}
else
{
Msg("Found no entity at %d.\n", iIndex);
}
}
static ConCommand cl_find_ent_index("cl_find_ent_index", CC_CL_Find_Ent_Index, "Display data for clientside entity matching specified index.\nFormat: cl_find_ent_index <index>\n", FCVAR_CHEAT);