diff options
Diffstat (limited to 'mp/src/game/server/baseentity.cpp')
| -rw-r--r-- | mp/src/game/server/baseentity.cpp | 7451 |
1 files changed, 7451 insertions, 0 deletions
diff --git a/mp/src/game/server/baseentity.cpp b/mp/src/game/server/baseentity.cpp new file mode 100644 index 00000000..689aa020 --- /dev/null +++ b/mp/src/game/server/baseentity.cpp @@ -0,0 +1,7451 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: The base class from which all game entities are derived.
+//
+//===========================================================================//
+
+#include "cbase.h"
+#include "globalstate.h"
+#include "isaverestore.h"
+#include "client.h"
+#include "decals.h"
+#include "gamerules.h"
+#include "entityapi.h"
+#include "entitylist.h"
+#include "eventqueue.h"
+#include "hierarchy.h"
+#include "basecombatweapon.h"
+#include "const.h"
+#include "player.h" // For debug draw sending
+#include "ndebugoverlay.h"
+#include "physics.h"
+#include "model_types.h"
+#include "team.h"
+#include "sendproxy.h"
+#include "IEffects.h"
+#include "vstdlib/random.h"
+#include "baseentity.h"
+#include "collisionutils.h"
+#include "coordsize.h"
+#include "animation.h"
+#include "tier1/strtools.h"
+#include "engine/IEngineSound.h"
+#include "physics_saverestore.h"
+#include "saverestore_utlvector.h"
+#include "bone_setup.h"
+#include "vcollide_parse.h"
+#include "filters.h"
+#include "te_effect_dispatch.h"
+#include "AI_Criteria.h"
+#include "AI_ResponseSystem.h"
+#include "world.h"
+#include "globals.h"
+#include "saverestoretypes.h"
+#include "SkyCamera.h"
+#include "sceneentity.h"
+#include "game.h"
+#include "tier0/vprof.h"
+#include "ai_basenpc.h"
+#include "iservervehicle.h"
+#include "eventlist.h"
+#include "scriptevent.h"
+#include "SoundEmitterSystem/isoundemittersystembase.h"
+#include "UtlCachedFileData.h"
+#include "utlbuffer.h"
+#include "positionwatcher.h"
+#include "movetype_push.h"
+#include "tier0/icommandline.h"
+#include "vphysics/friction.h"
+#include <ctype.h>
+#include "datacache/imdlcache.h"
+#include "ModelSoundsCache.h"
+#include "env_debughistory.h"
+#include "tier1/utlstring.h"
+#include "utlhashtable.h"
+
+#if defined( TF_DLL )
+#include "tf_gamerules.h"
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+extern bool g_bTestMoveTypeStepSimulation;
+extern ConVar sv_vehicle_autoaim_scale;
+
+// Init static class variables
+bool CBaseEntity::m_bInDebugSelect = false; // Used for selection in debug overlays
+int CBaseEntity::m_nDebugPlayer = -1; // Player doing the selection
+
+// This can be set before creating an entity to force it to use a particular edict.
+edict_t *g_pForceAttachEdict = NULL;
+
+bool CBaseEntity::m_bDebugPause = false; // Whether entity i/o is paused.
+int CBaseEntity::m_nDebugSteps = 1; // Number of entity outputs to fire before pausing again.
+bool CBaseEntity::sm_bDisableTouchFuncs = false; // Disables PhysicsTouch and PhysicsStartTouch function calls
+bool CBaseEntity::sm_bAccurateTriggerBboxChecks = true; // set to false for legacy behavior in ep1
+
+int CBaseEntity::m_nPredictionRandomSeed = -1;
+CBasePlayer *CBaseEntity::m_pPredictionPlayer = NULL;
+
+// Used to make sure nobody calls UpdateTransmitState directly.
+int g_nInsideDispatchUpdateTransmitState = 0;
+
+// When this is false, throw an assert in debug when GetAbsAnything is called. Used when hierachy is incomplete/invalid.
+bool CBaseEntity::s_bAbsQueriesValid = true;
+
+
+ConVar sv_netvisdist( "sv_netvisdist", "10000", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Test networking visibility distance" );
+
+// This table encodes edict data.
+void SendProxy_AnimTime( const SendProp *pProp, const void *pStruct, const void *pVarData, DVariant *pOut, int iElement, int objectID )
+{
+ CBaseEntity *pEntity = (CBaseEntity *)pStruct;
+
+#if defined( _DEBUG )
+ CBaseAnimating *pAnimating = pEntity->GetBaseAnimating();
+ Assert( pAnimating );
+
+ if ( pAnimating )
+ {
+ Assert( !pAnimating->IsUsingClientSideAnimation() );
+ }
+#endif
+
+ int ticknumber = TIME_TO_TICKS( pEntity->m_flAnimTime );
+ // Tickbase is current tick rounded down to closes 100 ticks
+ int tickbase = gpGlobals->GetNetworkBase( gpGlobals->tickcount, pEntity->entindex() );
+ int addt = 0;
+ // If it's within the last tick interval through the current one, then we can encode it
+ if ( ticknumber >= ( tickbase - 100 ) )
+ {
+ addt = ( ticknumber - tickbase ) & 0xFF;
+ }
+
+ pOut->m_Int = addt;
+}
+
+// This table encodes edict data.
+void SendProxy_SimulationTime( const SendProp *pProp, const void *pStruct, const void *pVarData, DVariant *pOut, int iElement, int objectID )
+{
+ CBaseEntity *pEntity = (CBaseEntity *)pStruct;
+
+ int ticknumber = TIME_TO_TICKS( pEntity->m_flSimulationTime );
+ // tickbase is current tick rounded down to closest 100 ticks
+ int tickbase = gpGlobals->GetNetworkBase( gpGlobals->tickcount, pEntity->entindex() );
+ int addt = 0;
+ if ( ticknumber >= tickbase )
+ {
+ addt = ( ticknumber - tickbase ) & 0xff;
+ }
+
+ pOut->m_Int = addt;
+}
+
+void* SendProxy_ClientSideAnimation( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID )
+{
+ CBaseEntity *pEntity = (CBaseEntity *)pStruct;
+ CBaseAnimating *pAnimating = pEntity->GetBaseAnimating();
+
+ if ( pAnimating && !pAnimating->IsUsingClientSideAnimation() )
+ return (void*)pVarData;
+ else
+ return NULL; // Don't send animtime unless the client needs it.
+}
+REGISTER_SEND_PROXY_NON_MODIFIED_POINTER( SendProxy_ClientSideAnimation );
+
+
+BEGIN_SEND_TABLE_NOBASE( CBaseEntity, DT_AnimTimeMustBeFirst )
+ // NOTE: Animtime must be sent before origin and angles ( from pev ) because it has a
+ // proxy on the client that stores off the old values before writing in the new values and
+ // if it is sent after the new values, then it will only have the new origin and studio model, etc.
+ // interpolation will be busted
+ SendPropInt (SENDINFO(m_flAnimTime), 8, SPROP_UNSIGNED|SPROP_CHANGES_OFTEN|SPROP_ENCODED_AGAINST_TICKCOUNT, SendProxy_AnimTime),
+END_SEND_TABLE()
+
+#if !defined( NO_ENTITY_PREDICTION )
+BEGIN_SEND_TABLE_NOBASE( CBaseEntity, DT_PredictableId )
+ SendPropPredictableId( SENDINFO( m_PredictableID ) ),
+ SendPropInt( SENDINFO( m_bIsPlayerSimulated ), 1, SPROP_UNSIGNED ),
+END_SEND_TABLE()
+
+
+static void* SendProxy_SendPredictableId( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID )
+{
+ CBaseEntity *pEntity = (CBaseEntity *)pStruct;
+ if ( !pEntity || !pEntity->m_PredictableID->IsActive() )
+ return NULL;
+
+ int id_player_index = pEntity->m_PredictableID->GetPlayer();
+ pRecipients->SetOnly( id_player_index );
+
+ return ( void * )pVarData;
+}
+REGISTER_SEND_PROXY_NON_MODIFIED_POINTER( SendProxy_SendPredictableId );
+#endif
+
+void SendProxy_Origin( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
+{
+ CBaseEntity *entity = (CBaseEntity*)pStruct;
+ Assert( entity );
+
+ const Vector *v;
+
+ if ( !entity->UseStepSimulationNetworkOrigin( &v ) )
+ {
+ v = &entity->GetLocalOrigin();
+ }
+
+ pOut->m_Vector[ 0 ] = v->x;
+ pOut->m_Vector[ 1 ] = v->y;
+ pOut->m_Vector[ 2 ] = v->z;
+}
+
+//--------------------------------------------------------------------------------------------------------
+// Used when breaking up origin, note we still have to deal with StepSimulation
+//--------------------------------------------------------------------------------------------------------
+void SendProxy_OriginXY( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
+{
+ CBaseEntity *entity = (CBaseEntity*)pStruct;
+ Assert( entity );
+
+ const Vector *v;
+
+ if ( !entity->UseStepSimulationNetworkOrigin( &v ) )
+ {
+ v = &entity->GetLocalOrigin();
+ }
+
+ pOut->m_Vector[ 0 ] = v->x;
+ pOut->m_Vector[ 1 ] = v->y;
+}
+
+//--------------------------------------------------------------------------------------------------------
+// Used when breaking up origin, note we still have to deal with StepSimulation
+//--------------------------------------------------------------------------------------------------------
+void SendProxy_OriginZ( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
+{
+ CBaseEntity *entity = (CBaseEntity*)pStruct;
+ Assert( entity );
+
+ const Vector *v;
+
+ if ( !entity->UseStepSimulationNetworkOrigin( &v ) )
+ {
+ v = &entity->GetLocalOrigin();
+ }
+
+ pOut->m_Float = v->z;
+}
+
+
+void SendProxy_Angles( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
+{
+ CBaseEntity *entity = (CBaseEntity*)pStruct;
+ Assert( entity );
+
+ const QAngle *a;
+
+ if ( !entity->UseStepSimulationNetworkAngles( &a ) )
+ {
+ a = &entity->GetLocalAngles();
+ }
+
+ pOut->m_Vector[ 0 ] = anglemod( a->x );
+ pOut->m_Vector[ 1 ] = anglemod( a->y );
+ pOut->m_Vector[ 2 ] = anglemod( a->z );
+}
+
+// This table encodes the CBaseEntity data.
+IMPLEMENT_SERVERCLASS_ST_NOBASE( CBaseEntity, DT_BaseEntity )
+ SendPropDataTable( "AnimTimeMustBeFirst", 0, &REFERENCE_SEND_TABLE(DT_AnimTimeMustBeFirst), SendProxy_ClientSideAnimation ),
+ SendPropInt (SENDINFO(m_flSimulationTime), SIMULATION_TIME_WINDOW_BITS, SPROP_UNSIGNED|SPROP_CHANGES_OFTEN|SPROP_ENCODED_AGAINST_TICKCOUNT, SendProxy_SimulationTime),
+
+#if PREDICTION_ERROR_CHECK_LEVEL > 1
+ SendPropVector (SENDINFO(m_vecOrigin), -1, SPROP_NOSCALE|SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_Origin ),
+#else
+ SendPropVector (SENDINFO(m_vecOrigin), -1, SPROP_COORD|SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_Origin ),
+#endif
+
+ SendPropInt (SENDINFO( m_ubInterpolationFrame ), NOINTERP_PARITY_MAX_BITS, SPROP_UNSIGNED ),
+ SendPropModelIndex(SENDINFO(m_nModelIndex)),
+ SendPropDataTable( SENDINFO_DT( m_Collision ), &REFERENCE_SEND_TABLE(DT_CollisionProperty) ),
+ SendPropInt (SENDINFO(m_nRenderFX), 8, SPROP_UNSIGNED ),
+ SendPropInt (SENDINFO(m_nRenderMode), 8, SPROP_UNSIGNED ),
+ SendPropInt (SENDINFO(m_fEffects), EF_MAX_BITS, SPROP_UNSIGNED),
+ SendPropInt (SENDINFO(m_clrRender), 32, SPROP_UNSIGNED),
+ SendPropInt (SENDINFO(m_iTeamNum), TEAMNUM_NUM_BITS, 0),
+ SendPropInt (SENDINFO(m_CollisionGroup), 5, SPROP_UNSIGNED),
+ SendPropFloat (SENDINFO(m_flElasticity), 0, SPROP_COORD),
+ SendPropFloat (SENDINFO(m_flShadowCastDistance), 12, SPROP_UNSIGNED ),
+ SendPropEHandle (SENDINFO(m_hOwnerEntity)),
+ SendPropEHandle (SENDINFO(m_hEffectEntity)),
+ SendPropEHandle (SENDINFO_NAME(m_hMoveParent, moveparent)),
+ SendPropInt (SENDINFO(m_iParentAttachment), NUM_PARENTATTACHMENT_BITS, SPROP_UNSIGNED),
+
+ SendPropInt (SENDINFO_NAME( m_MoveType, movetype ), MOVETYPE_MAX_BITS, SPROP_UNSIGNED ),
+ SendPropInt (SENDINFO_NAME( m_MoveCollide, movecollide ), MOVECOLLIDE_MAX_BITS, SPROP_UNSIGNED ),
+#if PREDICTION_ERROR_CHECK_LEVEL > 1
+ SendPropVector (SENDINFO(m_angRotation), -1, SPROP_NOSCALE|SPROP_CHANGES_OFTEN, 0, HIGH_DEFAULT, SendProxy_Angles ),
+#else
+ SendPropQAngles (SENDINFO(m_angRotation), 13, SPROP_CHANGES_OFTEN, SendProxy_Angles ),
+#endif
+
+ SendPropInt ( SENDINFO( m_iTextureFrameIndex ), 8, SPROP_UNSIGNED ),
+
+#if !defined( NO_ENTITY_PREDICTION )
+ SendPropDataTable( "predictable_id", 0, &REFERENCE_SEND_TABLE( DT_PredictableId ), SendProxy_SendPredictableId ),
+#endif
+
+ // FIXME: Collapse into another flag field?
+ SendPropInt (SENDINFO(m_bSimulatedEveryTick), 1, SPROP_UNSIGNED ),
+ SendPropInt (SENDINFO(m_bAnimatedEveryTick), 1, SPROP_UNSIGNED ),
+ SendPropBool( SENDINFO( m_bAlternateSorting )),
+
+#ifdef TF_DLL
+ SendPropArray3( SENDINFO_ARRAY3(m_nModelIndexOverrides), SendPropInt( SENDINFO_ARRAY(m_nModelIndexOverrides), SP_MODEL_INDEX_BITS, SPROP_UNSIGNED ) ),
+#endif
+
+END_SEND_TABLE()
+
+
+// dynamic models
+class CBaseEntityModelLoadProxy
+{
+protected:
+ class Handler : public IModelLoadCallback
+ {
+ public:
+ explicit Handler( CBaseEntity *pEntity ) : m_pEntity(pEntity) { }
+ virtual void OnModelLoadComplete( const model_t *pModel );
+ CBaseEntity* m_pEntity;
+ };
+ Handler* m_pHandler;
+
+public:
+ explicit CBaseEntityModelLoadProxy( CBaseEntity *pEntity ) : m_pHandler( new Handler( pEntity ) ) { }
+ ~CBaseEntityModelLoadProxy() { delete m_pHandler; }
+ void Register( int nModelIndex ) const { modelinfo->RegisterModelLoadCallback( nModelIndex, m_pHandler ); }
+ operator CBaseEntity * () const { return m_pHandler->m_pEntity; }
+
+private:
+ CBaseEntityModelLoadProxy( const CBaseEntityModelLoadProxy& );
+ CBaseEntityModelLoadProxy& operator=( const CBaseEntityModelLoadProxy& );
+};
+
+static CUtlHashtable< CBaseEntityModelLoadProxy, empty_t, PointerHashFunctor, PointerEqualFunctor, CBaseEntity * > sg_DynamicLoadHandlers;
+
+void CBaseEntityModelLoadProxy::Handler::OnModelLoadComplete( const model_t *pModel )
+{
+ m_pEntity->OnModelLoadComplete( pModel );
+ sg_DynamicLoadHandlers.Remove( m_pEntity ); // NOTE: destroys *this!
+}
+
+
+CBaseEntity::CBaseEntity( bool bServerOnly )
+{
+ COMPILE_TIME_ASSERT( MOVETYPE_LAST < (1 << MOVETYPE_MAX_BITS) );
+ COMPILE_TIME_ASSERT( MOVECOLLIDE_COUNT < (1 << MOVECOLLIDE_MAX_BITS) );
+
+#ifdef _DEBUG
+ // necessary since in debug, we initialize vectors to NAN for debugging
+ m_vecAngVelocity.Init();
+// m_vecAbsAngVelocity.Init();
+ m_vecViewOffset.Init();
+ m_vecBaseVelocity.GetForModify().Init();
+ m_vecVelocity.Init();
+ m_vecAbsVelocity.Init();
+#endif
+
+ m_bAlternateSorting = false;
+ m_CollisionGroup = COLLISION_GROUP_NONE;
+ m_iParentAttachment = 0;
+ CollisionProp()->Init( this );
+ NetworkProp()->Init( this );
+
+ // NOTE: THIS MUST APPEAR BEFORE ANY SetMoveType() or SetNextThink() calls
+ AddEFlags( EFL_NO_THINK_FUNCTION | EFL_NO_GAME_PHYSICS_SIMULATION | EFL_USE_PARTITION_WHEN_NOT_SOLID );
+
+ // clear debug overlays
+ m_debugOverlays = 0;
+ m_pTimedOverlay = NULL;
+ m_pPhysicsObject = NULL;
+ m_flElasticity = 1.0f;
+ m_flShadowCastDistance = m_flDesiredShadowCastDistance = 0;
+ SetRenderColor( 255, 255, 255, 255 );
+ m_iTeamNum = m_iInitialTeamNum = TEAM_UNASSIGNED;
+ m_nLastThinkTick = gpGlobals->tickcount;
+ m_nSimulationTick = -1;
+ SetIdentityMatrix( m_rgflCoordinateFrame );
+ m_pBlocker = NULL;
+#if _DEBUG
+ m_iCurrentThinkContext = NO_THINK_CONTEXT;
+#endif
+ m_nWaterTouch = m_nSlimeTouch = 0;
+
+ SetSolid( SOLID_NONE );
+ ClearSolidFlags();
+
+ m_nModelIndex = 0;
+ m_bDynamicModelAllowed = false;
+ m_bDynamicModelPending = false;
+ m_bDynamicModelSetBounds = false;
+
+ SetMoveType( MOVETYPE_NONE );
+ SetOwnerEntity( NULL );
+ SetCheckUntouch( false );
+ SetModelIndex( 0 );
+ SetModelName( NULL_STRING );
+ m_nTransmitStateOwnedCounter = 0;
+
+ SetCollisionBounds( vec3_origin, vec3_origin );
+ ClearFlags();
+
+ SetFriction( 1.0f );
+
+ if ( bServerOnly )
+ {
+ AddEFlags( EFL_SERVER_ONLY );
+ }
+ NetworkProp()->MarkPVSInformationDirty();
+
+#ifndef _XBOX
+ AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID );
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Scale up our physics hull and test against the new one
+// Input : *pNewCollide - New collision hull
+//-----------------------------------------------------------------------------
+void CBaseEntity::SetScaledPhysics( IPhysicsObject *pNewObject )
+{
+ if ( pNewObject )
+ {
+ AddSolidFlags( FSOLID_CUSTOMBOXTEST | FSOLID_CUSTOMRAYTEST );
+ }
+ else
+ {
+ RemoveSolidFlags( FSOLID_CUSTOMBOXTEST | FSOLID_CUSTOMRAYTEST );
+ }
+}
+
+extern bool g_bDisableEhandleAccess;
+
+//-----------------------------------------------------------------------------
+// Purpose: See note below
+//-----------------------------------------------------------------------------
+CBaseEntity::~CBaseEntity( )
+{
+ // FIXME: This can't be called from UpdateOnRemove! There's at least one
+ // case where friction sounds are added between the call to UpdateOnRemove + ~CBaseEntity
+ PhysCleanupFrictionSounds( this );
+
+ Assert( !IsDynamicModelIndex( m_nModelIndex ) );
+ Verify( !sg_DynamicLoadHandlers.Remove( this ) );
+
+ // In debug make sure that we don't call delete on an entity without setting
+ // the disable flag first!
+ // EHANDLE accessors will check, in debug, for access to entities during destruction of
+ // another entity.
+ // That kind of operation should only occur in UpdateOnRemove calls
+ // Deletion should only occur via UTIL_Remove(Immediate) calls, not via naked delete calls
+ Assert( g_bDisableEhandleAccess );
+
+ VPhysicsDestroyObject();
+
+ // Need to remove references to this entity before EHANDLES go null
+ {
+ g_bDisableEhandleAccess = false;
+ CBaseEntity::PhysicsRemoveTouchedList( this );
+ CBaseEntity::PhysicsRemoveGroundList( this );
+ SetGroundEntity( NULL ); // remove us from the ground entity if we are on it
+ DestroyAllDataObjects();
+ g_bDisableEhandleAccess = true;
+
+ // Remove this entity from the ent list (NOTE: This Makes EHANDLES go NULL)
+ gEntList.RemoveEntity( GetRefEHandle() );
+ }
+}
+
+void CBaseEntity::PostConstructor( const char *szClassname )
+{
+ if ( szClassname )
+ {
+ SetClassname(szClassname);
+ }
+
+ Assert( m_iClassname != NULL_STRING && STRING(m_iClassname) != NULL );
+
+ // Possibly get an edict, and add self to global list of entites.
+ if ( IsEFlagSet( EFL_SERVER_ONLY ) )
+ {
+ gEntList.AddNonNetworkableEntity( this );
+ }
+ else
+ {
+ // Certain entities set up their edicts in the constructor
+ if ( !IsEFlagSet( EFL_NO_AUTO_EDICT_ATTACH ) )
+ {
+ NetworkProp()->AttachEdict( g_pForceAttachEdict );
+ g_pForceAttachEdict = NULL;
+ }
+
+ // Some ents like the player override the AttachEdict function and do it at a different time.
+ // While precaching, they don't ever have an edict, so we don't need to add them to
+ // the entity list in that case.
+ if ( edict() )
+ {
+ gEntList.AddNetworkableEntity( this, entindex() );
+
+ // Cache our IServerNetworkable pointer for the engine for fast access.
+ if ( edict() )
+ edict()->m_pNetworkable = NetworkProp();
+ }
+ }
+
+ CheckHasThinkFunction( false );
+ CheckHasGamePhysicsSimulation();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called after player becomes active in the game
+//-----------------------------------------------------------------------------
+void CBaseEntity::PostClientActive( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Verifies that this entity's data description is valid in debug builds.
+//-----------------------------------------------------------------------------
+#ifdef _DEBUG
+typedef CUtlVector< const char * > KeyValueNameList_t;
+
+static void AddDataMapFieldNamesToList( KeyValueNameList_t &list, datamap_t *pDataMap )
+{
+ while (pDataMap != NULL)
+ {
+ for (int i = 0; i < pDataMap->dataNumFields; i++)
+ {
+ typedescription_t *pField = &pDataMap->dataDesc[i];
+
+ if (pField->fieldType == FIELD_EMBEDDED)
+ {
+ AddDataMapFieldNamesToList( list, pField->td );
+ continue;
+ }
+
+ if (pField->flags & FTYPEDESC_KEY)
+ {
+ list.AddToTail( pField->externalName );
+ }
+ }
+
+ pDataMap = pDataMap->baseMap;
+ }
+}
+
+void CBaseEntity::ValidateDataDescription(void)
+{
+ // Multiple key fields that have the same name are not allowed - it creates an
+ // ambiguity when trying to parse keyvalues and outputs.
+ datamap_t *pDataMap = GetDataDescMap();
+ if ((pDataMap == NULL) || pDataMap->bValidityChecked)
+ return;
+
+ pDataMap->bValidityChecked = true;
+
+ // Let's generate a list of all keyvalue strings in the entire hierarchy...
+ KeyValueNameList_t names(128);
+ AddDataMapFieldNamesToList( names, pDataMap );
+
+ for (int i = names.Count(); --i > 0; )
+ {
+ for (int j = i - 1; --j >= 0; )
+ {
+ if (!Q_stricmp(names[i], names[j]))
+ {
+ DevMsg( "%s has multiple data description entries for \"%s\"\n", STRING(m_iClassname), names[i]);
+ break;
+ }
+ }
+ }
+}
+#endif // _DEBUG
+
+
+//-----------------------------------------------------------------------------
+// Sets the collision bounds + the size
+//-----------------------------------------------------------------------------
+void CBaseEntity::SetCollisionBounds( const Vector& mins, const Vector &maxs )
+{
+ m_Collision.SetCollisionBounds( mins, maxs );
+}
+
+
+void CBaseEntity::StopFollowingEntity( )
+{
+ if( !IsFollowingEntity() )
+ {
+// Assert( IsEffectActive( EF_BONEMERGE ) == 0 );
+ return;
+ }
+
+ SetParent( NULL );
+ RemoveEffects( EF_BONEMERGE );
+ RemoveSolidFlags( FSOLID_NOT_SOLID );
+ SetMoveType( MOVETYPE_NONE );
+ CollisionRulesChanged();
+}
+
+bool CBaseEntity::IsFollowingEntity()
+{
+ return IsEffectActive( EF_BONEMERGE ) && (GetMoveType() == MOVETYPE_NONE) && GetMoveParent();
+}
+
+CBaseEntity *CBaseEntity::GetFollowedEntity()
+{
+ if (!IsFollowingEntity())
+ return NULL;
+ return GetMoveParent();
+}
+
+void CBaseEntity::SetClassname( const char *className )
+{
+ m_iClassname = AllocPooledString( className );
+}
+
+void CBaseEntity::SetModelIndex( int index )
+{
+ if ( IsDynamicModelIndex( index ) && !(GetBaseAnimating() && m_bDynamicModelAllowed) )
+ {
+ AssertMsg( false, "dynamic model support not enabled on server entity" );
+ index = -1;
+ }
+
+ if ( index != m_nModelIndex )
+ {
+ if ( m_bDynamicModelPending )
+ {
+ sg_DynamicLoadHandlers.Remove( this );
+ }
+
+ modelinfo->ReleaseDynamicModel( m_nModelIndex );
+ modelinfo->AddRefDynamicModel( index );
+ m_nModelIndex = index;
+
+ m_bDynamicModelSetBounds = false;
+
+ if ( IsDynamicModelIndex( index ) )
+ {
+ m_bDynamicModelPending = true;
+ sg_DynamicLoadHandlers[ sg_DynamicLoadHandlers.Insert( this ) ].Register( index );
+ }
+ else
+ {
+ m_bDynamicModelPending = false;
+ OnNewModel();
+ }
+ }
+ DispatchUpdateTransmitState();
+}
+
+void CBaseEntity::ClearModelIndexOverrides( void )
+{
+#ifdef TF_DLL
+ for ( int index = 0 ; index < MAX_MODEL_INDEX_OVERRIDES ; index++ )
+ {
+ m_nModelIndexOverrides.Set( index, 0 );
+ }
+#endif
+}
+
+void CBaseEntity::SetModelIndexOverride( int index, int nValue )
+{
+#ifdef TF_DLL
+ if ( ( index >= MODEL_INDEX_OVERRIDE_DEFAULT ) && ( index < MAX_MODEL_INDEX_OVERRIDES ) )
+ {
+ if ( nValue != m_nModelIndexOverrides[index] )
+ {
+ m_nModelIndexOverrides.Set( index, nValue );
+ }
+ }
+#endif
+}
+
+// position to shoot at
+Vector CBaseEntity::BodyTarget( const Vector &posSrc, bool bNoisy)
+{
+ return WorldSpaceCenter( );
+}
+
+// return the position of my head. someone's trying to attack it.
+Vector CBaseEntity::HeadTarget( const Vector &posSrc )
+{
+ return EyePosition();
+}
+
+
+struct TimedOverlay_t
+{
+ char *msg;
+ int msgEndTime;
+ int msgStartTime;
+ TimedOverlay_t *pNextTimedOverlay;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Display an error message on the entity
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CBaseEntity::AddTimedOverlay( const char *msg, int endTime )
+{
+ TimedOverlay_t *pNewTO = new TimedOverlay_t;
+ int len = strlen(msg);
+ pNewTO->msg = new char[len + 1];
+ Q_strncpy(pNewTO->msg,msg, len+1);
+ pNewTO->msgEndTime = gpGlobals->curtime + endTime;
+ pNewTO->msgStartTime = gpGlobals->curtime;
+ pNewTO->pNextTimedOverlay = m_pTimedOverlay;
+ m_pTimedOverlay = pNewTO;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Send debug overlay box to the client
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CBaseEntity::DrawBBoxOverlay( float flDuration )
+{
+ if (edict())
+ {
+ NDebugOverlay::EntityBounds(this, 255, 100, 0, 0, flDuration );
+
+ if ( CollisionProp()->IsSolidFlagSet( FSOLID_USE_TRIGGER_BOUNDS ) )
+ {
+ Vector vecTriggerMins, vecTriggerMaxs;
+ CollisionProp()->WorldSpaceTriggerBounds( &vecTriggerMins, &vecTriggerMaxs );
+ Vector center = 0.5f * (vecTriggerMins + vecTriggerMaxs);
+ Vector extents = vecTriggerMaxs - center;
+ NDebugOverlay::Box(center, -extents, extents, 0, 255, 255, 0, flDuration );
+ }
+ }
+}
+
+
+void CBaseEntity::DrawAbsBoxOverlay()
+{
+ int red = 0;
+ int green = 200;
+
+ if ( VPhysicsGetObject() && VPhysicsGetObject()->IsAsleep() )
+ {
+ red = 90;
+ green = 120;
+ }
+
+ if (edict())
+ {
+ // Surrounding boxes are axially aligned, so ignore angles
+ Vector vecSurroundMins, vecSurroundMaxs;
+ CollisionProp()->WorldSpaceSurroundingBounds( &vecSurroundMins, &vecSurroundMaxs );
+ Vector center = 0.5f * (vecSurroundMins + vecSurroundMaxs);
+ Vector extents = vecSurroundMaxs - center;
+ NDebugOverlay::Box(center, -extents, extents, red, green, 0, 0 ,0);
+ }
+}
+
+void CBaseEntity::DrawRBoxOverlay()
+{
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Draws an axis overlay at the origin and angles of the entity
+//-----------------------------------------------------------------------------
+void CBaseEntity::SendDebugPivotOverlay( void )
+{
+ if ( edict() )
+ {
+ NDebugOverlay::Axis( GetAbsOrigin(), GetAbsAngles(), 20, true, 0 );
+ }
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Add new entity positioned overlay text
+// Input : How many lines to offset text from origin
+// The text to print
+// How long to display text
+// The color of the text
+// Output :
+//------------------------------------------------------------------------------
+void CBaseEntity::EntityText( int text_offset, const char *text, float duration, int r, int g, int b, int a )
+{
+ Vector origin;
+ Vector vecLocalCenter;
+
+ VectorAdd( m_Collision.OBBMins(), m_Collision.OBBMaxs(), vecLocalCenter );
+ vecLocalCenter *= 0.5f;
+
+ if ( ( m_Collision.GetCollisionAngles() == vec3_angle ) || ( vecLocalCenter == vec3_origin ) )
+ {
+ VectorAdd( vecLocalCenter, m_Collision.GetCollisionOrigin(), origin );
+ }
+ else
+ {
+ VectorTransform( vecLocalCenter, m_Collision.CollisionToWorldTransform(), origin );
+ }
+
+ NDebugOverlay::EntityTextAtPosition( origin, text_offset, text, duration, r, g, b, a );
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CBaseEntity::DrawTimedOverlays(void)
+{
+ // Draw name first if I have an overlay or am in message mode
+ if ((m_debugOverlays & OVERLAY_MESSAGE_BIT))
+ {
+ char tempstr[512];
+ Q_snprintf( tempstr, sizeof( tempstr ), "[%s]", GetDebugName() );
+ EntityText(0,tempstr, 0);
+ }
+
+ // Now draw overlays
+ TimedOverlay_t* pTO = m_pTimedOverlay;
+ TimedOverlay_t* pNextTO = NULL;
+ TimedOverlay_t* pLastTO = NULL;
+ int nCount = 1; // Offset by one
+ while (pTO)
+ {
+ pNextTO = pTO->pNextTimedOverlay;
+
+ // Remove old messages unless messages are paused
+ if ((!CBaseEntity::Debug_IsPaused() && gpGlobals->curtime > pTO->msgEndTime) ||
+ (nCount > 10))
+ {
+ if (pLastTO)
+ {
+ pLastTO->pNextTimedOverlay = pNextTO;
+ }
+ else
+ {
+ m_pTimedOverlay = pNextTO;
+ }
+
+ delete pTO->msg;
+ delete pTO;
+ }
+ else
+ {
+ int nAlpha = 0;
+
+ // If messages aren't paused fade out
+ if (!CBaseEntity::Debug_IsPaused())
+ {
+ nAlpha = 255*((gpGlobals->curtime - pTO->msgStartTime)/(pTO->msgEndTime - pTO->msgStartTime));
+ }
+ int r = 185;
+ int g = 145;
+ int b = 145;
+
+ // Brighter when new message
+ if (nAlpha < 50)
+ {
+ r = 255;
+ g = 205;
+ b = 205;
+ }
+ if (nAlpha < 0) nAlpha = 0;
+ EntityText(nCount,pTO->msg, 0.0, r, g, b, 255-nAlpha);
+ nCount++;
+
+ pLastTO = pTO;
+ }
+ pTO = pNextTO;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Draw all overlays (should be implemented by subclass to add
+// any additional non-text overlays)
+// Input :
+// Output : Current text offset from the top
+//-----------------------------------------------------------------------------
+void CBaseEntity::DrawDebugGeometryOverlays(void)
+{
+ DrawTimedOverlays();
+ DrawDebugTextOverlays();
+
+ if (m_debugOverlays & OVERLAY_NAME_BIT)
+ {
+ EntityText(0,GetDebugName(), 0);
+ }
+ if (m_debugOverlays & OVERLAY_BBOX_BIT)
+ {
+ DrawBBoxOverlay();
+ }
+ if (m_debugOverlays & OVERLAY_ABSBOX_BIT )
+ {
+ DrawAbsBoxOverlay();
+ }
+ if (m_debugOverlays & OVERLAY_PIVOT_BIT)
+ {
+ SendDebugPivotOverlay();
+ }
+ if( m_debugOverlays & OVERLAY_RBOX_BIT )
+ {
+ DrawRBoxOverlay();
+ }
+ if ( m_debugOverlays & (OVERLAY_BBOX_BIT|OVERLAY_PIVOT_BIT) )
+ {
+ // draw mass center
+ if ( VPhysicsGetObject() )
+ {
+ Vector massCenter = VPhysicsGetObject()->GetMassCenterLocalSpace();
+ Vector worldPos;
+ VPhysicsGetObject()->LocalToWorld( &worldPos, massCenter );
+ NDebugOverlay::Cross3D( worldPos, 12, 255, 0, 0, false, 0 );
+ DebugDrawContactPoints(VPhysicsGetObject());
+ if ( GetMoveType() != MOVETYPE_VPHYSICS )
+ {
+ Vector pos;
+ QAngle angles;
+ VPhysicsGetObject()->GetPosition( &pos, &angles );
+ float dist = (pos - GetAbsOrigin()).Length();
+
+ Vector axis;
+ float deltaAngle;
+ RotationDeltaAxisAngle( angles, GetAbsAngles(), axis, deltaAngle );
+ if ( dist > 2 || fabsf(deltaAngle) > 2 )
+ {
+ Vector mins, maxs;
+ physcollision->CollideGetAABB( &mins, &maxs, VPhysicsGetObject()->GetCollide(), vec3_origin, vec3_angle );
+ NDebugOverlay::BoxAngles( pos, mins, maxs, angles, 255, 255, 0, 16, 0 );
+ }
+ }
+ }
+ }
+ if ( m_debugOverlays & OVERLAY_SHOW_BLOCKSLOS )
+ {
+ if ( BlocksLOS() )
+ {
+ NDebugOverlay::EntityBounds(this, 255, 255, 255, 0, 0 );
+ }
+ }
+ if ( m_debugOverlays & OVERLAY_AUTOAIM_BIT && (GetFlags()&FL_AIMTARGET) && AI_GetSinglePlayer() != NULL )
+ {
+ // Crude, but it gets the point across.
+ Vector vecCenter = GetAutoAimCenter();
+ Vector vecRight, vecUp, vecDiag;
+ CBasePlayer *pPlayer = AI_GetSinglePlayer();
+ float radius = GetAutoAimRadius();
+
+ QAngle angles = pPlayer->EyeAngles();
+ AngleVectors( angles, NULL, &vecRight, &vecUp );
+
+ int r,g,b;
+
+ if( ((int)gpGlobals->curtime) % 2 == 1 )
+ {
+ r = 255;
+ g = 255;
+ b = 255;
+
+ if( pPlayer->GetActiveWeapon() != NULL )
+ radius *= pPlayer->GetActiveWeapon()->WeaponAutoAimScale();
+
+ }
+ else
+ {
+ r = 255;g=0;b=0;
+
+ if( !ShouldAttractAutoAim(pPlayer) )
+ {
+ g = 255;
+ }
+ }
+
+ if( pPlayer->IsInAVehicle() )
+ {
+ radius *= sv_vehicle_autoaim_scale.GetFloat();
+ }
+
+ NDebugOverlay::Line( vecCenter, vecCenter + vecRight * radius, r, g, b, true, 0.1 );
+ NDebugOverlay::Line( vecCenter, vecCenter - vecRight * radius, r, g, b, true, 0.1 );
+ NDebugOverlay::Line( vecCenter, vecCenter + vecUp * radius, r, g, b, true, 0.1 );
+ NDebugOverlay::Line( vecCenter, vecCenter - vecUp * radius, r, g, b, true, 0.1 );
+
+ vecDiag = vecRight + vecUp;
+ VectorNormalize( vecDiag );
+ NDebugOverlay::Line( vecCenter - vecDiag * radius, vecCenter + vecDiag * radius, r, g, b, true, 0.1 );
+
+ vecDiag = vecRight - vecUp;
+ VectorNormalize( vecDiag );
+ NDebugOverlay::Line( vecCenter - vecDiag * radius, vecCenter + vecDiag * radius, r, g, b, true, 0.1 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Draw any text overlays (override in subclass to add additional text)
+// Output : Current text offset from the top
+//-----------------------------------------------------------------------------
+int CBaseEntity::DrawDebugTextOverlays(void)
+{
+ int offset = 1;
+ if (m_debugOverlays & OVERLAY_TEXT_BIT)
+ {
+ char tempstr[512];
+ Q_snprintf( tempstr, sizeof(tempstr), "(%d) Name: %s (%s)", entindex(), GetDebugName(), GetClassname() );
+ EntityText(offset,tempstr, 0);
+ offset++;
+
+ if( m_iGlobalname != NULL_STRING )
+ {
+ Q_snprintf( tempstr, sizeof(tempstr), "GLOBALNAME: %s", STRING(m_iGlobalname) );
+ EntityText(offset,tempstr, 0);
+ offset++;
+ }
+
+ Vector vecOrigin = GetAbsOrigin();
+ Q_snprintf( tempstr, sizeof(tempstr), "Position: %0.1f, %0.1f, %0.1f\n", vecOrigin.x, vecOrigin.y, vecOrigin.z );
+ EntityText( offset, tempstr, 0 );
+ offset++;
+
+ if( GetModelName() != NULL_STRING || GetBaseAnimating() )
+ {
+ Q_snprintf(tempstr, sizeof(tempstr), "Model:%s", STRING(GetModelName()) );
+ EntityText(offset,tempstr,0);
+ offset++;
+ }
+
+ if( m_hDamageFilter.Get() != NULL )
+ {
+ Q_snprintf( tempstr, sizeof(tempstr), "DAMAGE FILTER:%s", m_hDamageFilter->GetDebugName() );
+ EntityText( offset,tempstr,0 );
+ offset++;
+ }
+ }
+
+ if (m_debugOverlays & OVERLAY_VIEWOFFSET)
+ {
+ NDebugOverlay::Cross3D( EyePosition(), 16, 255, 0, 0, true, 0.05f );
+ }
+
+ return offset;
+}
+
+
+void CBaseEntity::SetParent( string_t newParent, CBaseEntity *pActivator, int iAttachment )
+{
+ // find and notify the new parent
+ CBaseEntity *pParent = gEntList.FindEntityByName( NULL, newParent, NULL, pActivator );
+
+ // debug check
+ if ( newParent != NULL_STRING && pParent == NULL )
+ {
+ Msg( "Entity %s(%s) has bad parent %s\n", STRING(m_iClassname), GetDebugName(), STRING(newParent) );
+ }
+ else
+ {
+ // make sure there isn't any ambiguity
+ if ( gEntList.FindEntityByName( pParent, newParent, NULL, pActivator ) )
+ {
+ Msg( "Entity %s(%s) has ambigious parent %s\n", STRING(m_iClassname), GetDebugName(), STRING(newParent) );
+ }
+ SetParent( pParent, iAttachment );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Move our points from parent to worldspace
+// Input : *pParent - Parent to use as reference
+//-----------------------------------------------------------------------------
+void CBaseEntity::TransformStepData_ParentToWorld( CBaseEntity *pParent )
+{
+ // Fix up our step simulation points to be in the proper local space
+ StepSimulationData *step = (StepSimulationData *) GetDataObject( STEPSIMULATION );
+ if ( step != NULL )
+ {
+ // Convert our positions
+ UTIL_ParentToWorldSpace( pParent, step->m_Previous2.vecOrigin, step->m_Previous2.qRotation );
+ UTIL_ParentToWorldSpace( pParent, step->m_Previous.vecOrigin, step->m_Previous.qRotation );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Move step data between two parent-spaces
+// Input : *pOldParent - parent we were attached to
+// *pNewParent - parent we're now attached to
+//-----------------------------------------------------------------------------
+void CBaseEntity::TransformStepData_ParentToParent( CBaseEntity *pOldParent, CBaseEntity *pNewParent )
+{
+ // Fix up our step simulation points to be in the proper local space
+ StepSimulationData *step = (StepSimulationData *) GetDataObject( STEPSIMULATION );
+ if ( step != NULL )
+ {
+ // Convert our positions
+ UTIL_ParentToWorldSpace( pOldParent, step->m_Previous2.vecOrigin, step->m_Previous2.qRotation );
+ UTIL_WorldToParentSpace( pNewParent, step->m_Previous2.vecOrigin, step->m_Previous2.qRotation );
+
+ UTIL_ParentToWorldSpace( pOldParent, step->m_Previous.vecOrigin, step->m_Previous.qRotation );
+ UTIL_WorldToParentSpace( pNewParent, step->m_Previous.vecOrigin, step->m_Previous.qRotation );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: After parenting to an object, we need to also correctly translate our
+// step stimulation positions and angles into that parent space. Otherwise
+// we end up splining between two different world spaces.
+//-----------------------------------------------------------------------------
+void CBaseEntity::TransformStepData_WorldToParent( CBaseEntity *pParent )
+{
+ // Fix up our step simulation points to be in the proper local space
+ StepSimulationData *step = (StepSimulationData *) GetDataObject( STEPSIMULATION );
+ if ( step != NULL )
+ {
+ // Convert our positions
+ UTIL_WorldToParentSpace( pParent, step->m_Previous2.vecOrigin, step->m_Previous2.qRotation );
+ UTIL_WorldToParentSpace( pParent, step->m_Previous.vecOrigin, step->m_Previous.qRotation );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the movement parent of this entity. This entity will be moved
+// to a local coordinate calculated from its current absolute offset
+// from the parent entity and will then follow the parent entity.
+// Input : pParentEntity - This entity's new parent in the movement hierarchy.
+//-----------------------------------------------------------------------------
+void CBaseEntity::SetParent( CBaseEntity *pParentEntity, int iAttachment )
+{
+ // If they didn't specify an attachment, use our current
+ if ( iAttachment == -1 )
+ {
+ iAttachment = m_iParentAttachment;
+ }
+
+ bool bWasNotParented = ( GetParent() == NULL );
+ CBaseEntity *pOldParent = m_pParent;
+
+ // notify the old parent of the loss
+ UnlinkFromParent( this );
+
+ // set the new name
+ m_pParent = pParentEntity;
+
+ if ( m_pParent == this )
+ {
+ // should never set parent to 'this' - makes no sense
+ Assert(0);
+ m_pParent = NULL;
+ }
+
+ if ( m_pParent == NULL )
+ {
+ m_iParent = NULL_STRING;
+
+ // Transform step data from parent to worldspace
+ TransformStepData_ParentToWorld( pOldParent );
+ return;
+ }
+
+ m_iParent = m_pParent->m_iName;
+
+ RemoveSolidFlags( FSOLID_ROOT_PARENT_ALIGNED );
+ if ( pParentEntity )
+ {
+ if ( const_cast<CBaseEntity *>(pParentEntity)->GetRootMoveParent()->GetSolid() == SOLID_BSP )
+ {
+ AddSolidFlags( FSOLID_ROOT_PARENT_ALIGNED );
+ }
+ else
+ {
+ if ( GetSolid() == SOLID_BSP )
+ {
+ // Must be SOLID_VPHYSICS because parent might rotate
+ SetSolid( SOLID_VPHYSICS );
+ }
+ }
+ }
+ // set the move parent if we have one
+ if ( edict() )
+ {
+ // add ourselves to the list
+ LinkChild( m_pParent, this );
+
+ m_iParentAttachment = (char)iAttachment;
+
+ EntityMatrix matrix, childMatrix;
+ matrix.InitFromEntity( const_cast<CBaseEntity *>(pParentEntity), m_iParentAttachment ); // parent->world
+ childMatrix.InitFromEntityLocal( this ); // child->world
+ Vector localOrigin = matrix.WorldToLocal( GetLocalOrigin() );
+
+ // I have the axes of local space in world space. (childMatrix)
+ // I want to compute those world space axes in the parent's local space
+ // and set that transform (as angles) on the child's object so the net
+ // result is that the child is now in parent space, but still oriented the same way
+ VMatrix tmp = matrix.Transpose(); // world->parent
+ tmp.MatrixMul( childMatrix, matrix ); // child->parent
+ QAngle angles;
+ MatrixToAngles( matrix, angles );
+ SetLocalAngles( angles );
+ UTIL_SetOrigin( this, localOrigin );
+
+ // Move our step data into the correct space
+ if ( bWasNotParented )
+ {
+ // Transform step data from world to parent-space
+ TransformStepData_WorldToParent( this );
+ }
+ else
+ {
+ // Transform step data between parent-spaces
+ TransformStepData_ParentToParent( pOldParent, this );
+ }
+ }
+ if ( VPhysicsGetObject() )
+ {
+ if ( VPhysicsGetObject()->IsStatic())
+ {
+ if ( VPhysicsGetObject()->IsAttachedToConstraint(false) )
+ {
+ Warning("SetParent on static object, all constraints attached to %s (%s)will now be broken!\n", GetDebugName(), GetClassname() );
+ }
+ VPhysicsDestroyObject();
+ VPhysicsInitShadow(false, false);
+ }
+ }
+ CollisionRulesChanged();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseEntity::ValidateEntityConnections()
+{
+ if ( m_target == NULL_STRING )
+ return;
+
+ if ( ClassMatches( "scripted_*" ) ||
+ ClassMatches( "trigger_relay" ) ||
+ ClassMatches( "trigger_auto" ) ||
+ ClassMatches( "path_*" ) ||
+ ClassMatches( "monster_*" ) ||
+ ClassMatches( "trigger_teleport" ) ||
+ ClassMatches( "func_train" ) ||
+ ClassMatches( "func_tracktrain" ) ||
+ ClassMatches( "func_plat*" ) ||
+ ClassMatches( "npc_*" ) ||
+ ClassMatches( "info_big*" ) ||
+ ClassMatches( "env_texturetoggle" ) ||
+ ClassMatches( "env_render" ) ||
+ ClassMatches( "func_areaportalwindow") ||
+ ClassMatches( "point_view*") ||
+ ClassMatches( "func_traincontrols" ) ||
+ ClassMatches( "multisource" ) ||
+ ClassMatches( "xen_plant*" ) )
+ return;
+
+ datamap_t *dmap = GetDataDescMap();
+ while ( dmap )
+ {
+ int fields = dmap->dataNumFields;
+ for ( int i = 0; i < fields; i++ )
+ {
+ typedescription_t *dataDesc = &dmap->dataDesc[i];
+ if ( ( dataDesc->fieldType == FIELD_CUSTOM ) && ( dataDesc->flags & FTYPEDESC_OUTPUT ) )
+ {
+ CBaseEntityOutput *pOutput = (CBaseEntityOutput *)((int)this + (int)dataDesc->fieldOffset[0]);
+ if ( pOutput->NumberOfElements() )
+ return;
+ }
+ }
+
+ dmap = dmap->baseMap;
+ }
+
+ Vector vecLoc = WorldSpaceCenter();
+ Warning("---------------------------------\n");
+ Warning( "Entity %s - (%s) has a target and NO OUTPUTS\n", GetDebugName(), GetClassname() );
+ Warning( "Location %f %f %f\n", vecLoc.x, vecLoc.y, vecLoc.z );
+ Warning("---------------------------------\n");
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseEntity::FireNamedOutput( const char *pszOutput, variant_t variant, CBaseEntity *pActivator, CBaseEntity *pCaller, float flDelay )
+{
+ if ( pszOutput == NULL )
+ return;
+
+ datamap_t *dmap = GetDataDescMap();
+ while ( dmap )
+ {
+ int fields = dmap->dataNumFields;
+ for ( int i = 0; i < fields; i++ )
+ {
+ typedescription_t *dataDesc = &dmap->dataDesc[i];
+ if ( ( dataDesc->fieldType == FIELD_CUSTOM ) && ( dataDesc->flags & FTYPEDESC_OUTPUT ) )
+ {
+ CBaseEntityOutput *pOutput = ( CBaseEntityOutput * )( ( int )this + ( int )dataDesc->fieldOffset[0] );
+ if ( !Q_stricmp( dataDesc->externalName, pszOutput ) )
+ {
+ pOutput->FireOutput( variant, pActivator, pCaller, flDelay );
+ return;
+ }
+ }
+ }
+
+ dmap = dmap->baseMap;
+ }
+}
+
+void CBaseEntity::Activate( void )
+{
+#ifdef DEBUG
+ extern bool g_bCheckForChainedActivate;
+ extern bool g_bReceivedChainedActivate;
+
+ if ( g_bCheckForChainedActivate && g_bReceivedChainedActivate )
+ {
+ Assert( !"Multiple calls to base class Activate()\n" );
+ }
+ g_bReceivedChainedActivate = true;
+#endif
+
+ // NOTE: This forces a team change so that stuff in the level
+ // that starts out on a team correctly changes team
+ if (m_iInitialTeamNum)
+ {
+ ChangeTeam( m_iInitialTeamNum );
+ }
+
+ // Get a handle to my damage filter entity if there is one.
+ if ( m_iszDamageFilterName != NULL_STRING )
+ {
+ m_hDamageFilter = gEntList.FindEntityByName( NULL, m_iszDamageFilterName );
+ }
+
+ // Add any non-null context strings to our context vector
+ if ( m_iszResponseContext != NULL_STRING )
+ {
+ AddContext( m_iszResponseContext.ToCStr() );
+ }
+
+#ifdef HL1_DLL
+ ValidateEntityConnections();
+#endif //HL1_DLL
+}
+
+//////////////////////////// old CBaseEntity stuff ///////////////////////////////////
+
+
+// give health.
+// Returns the amount of health actually taken.
+int CBaseEntity::TakeHealth( float flHealth, int bitsDamageType )
+{
+ if ( !edict() || m_takedamage < DAMAGE_YES )
+ return 0;
+
+ int iMax = GetMaxHealth();
+
+// heal
+ if ( m_iHealth >= iMax )
+ return 0;
+
+ const int oldHealth = m_iHealth;
+
+ m_iHealth += flHealth;
+
+ if (m_iHealth > iMax)
+ m_iHealth = iMax;
+
+ return m_iHealth - oldHealth;
+}
+
+// inflict damage on this entity. bitsDamageType indicates type of damage inflicted, ie: DMG_CRUSH
+
+int CBaseEntity::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ Vector vecTemp;
+
+ if ( !edict() || !m_takedamage )
+ return 0;
+
+ if ( info.GetInflictor() )
+ {
+ vecTemp = info.GetInflictor()->WorldSpaceCenter() - ( WorldSpaceCenter() );
+ }
+ else
+ {
+ vecTemp.Init( 1, 0, 0 );
+ }
+
+ // this global is still used for glass and other non-NPC killables, along with decals.
+ g_vecAttackDir = vecTemp;
+ VectorNormalize(g_vecAttackDir);
+
+ // save damage based on the target's armor level
+
+ // figure momentum add (don't let hurt brushes or other triggers move player)
+
+ // physics objects have their own calcs for this: (don't let fire move things around!)
+ if ( !IsEFlagSet( EFL_NO_DAMAGE_FORCES ) )
+ {
+ if ( ( GetMoveType() == MOVETYPE_VPHYSICS ) )
+ {
+ VPhysicsTakeDamage( info );
+ }
+ else
+ {
+ if ( info.GetInflictor() && (GetMoveType() == MOVETYPE_WALK || GetMoveType() == MOVETYPE_STEP) &&
+ !info.GetAttacker()->IsSolidFlagSet(FSOLID_TRIGGER) )
+ {
+ Vector vecDir, vecInflictorCentroid;
+ vecDir = WorldSpaceCenter( );
+ vecInflictorCentroid = info.GetInflictor()->WorldSpaceCenter( );
+ vecDir -= vecInflictorCentroid;
+ VectorNormalize( vecDir );
+
+ float flForce = info.GetDamage() * ((32 * 32 * 72.0) / (WorldAlignSize().x * WorldAlignSize().y * WorldAlignSize().z)) * 5;
+
+ if (flForce > 1000.0)
+ flForce = 1000.0;
+ ApplyAbsVelocityImpulse( vecDir * flForce );
+ }
+ }
+ }
+
+ if ( m_takedamage != DAMAGE_EVENTS_ONLY )
+ {
+ // do the damage
+ m_iHealth -= info.GetDamage();
+ if (m_iHealth <= 0)
+ {
+ Event_Killed( info );
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Scale damage done and call OnTakeDamage
+//-----------------------------------------------------------------------------
+void CBaseEntity::TakeDamage( const CTakeDamageInfo &inputInfo )
+{
+ if ( !g_pGameRules )
+ return;
+
+ bool bHasPhysicsForceDamage = !g_pGameRules->Damage_NoPhysicsForce( inputInfo.GetDamageType() );
+ if ( bHasPhysicsForceDamage && inputInfo.GetDamageType() != DMG_GENERIC )
+ {
+ // If you hit this assert, you've called TakeDamage with a damage type that requires a physics damage
+ // force & position without specifying one or both of them. Decide whether your damage that's causing
+ // this is something you believe should impart physics force on the receiver. If it is, you need to
+ // setup the damage force & position inside the CTakeDamageInfo (Utility functions for this are in
+ // takedamageinfo.cpp. If you think the damage shouldn't cause force (unlikely!) then you can set the
+ // damage type to DMG_GENERIC, or | DMG_CRUSH if you need to preserve the damage type for purposes of HUD display.
+
+ if ( inputInfo.GetDamageForce() == vec3_origin || inputInfo.GetDamagePosition() == vec3_origin )
+ {
+ static int warningCount = 0;
+ if ( ++warningCount < 10 )
+ {
+ if ( inputInfo.GetDamageForce() == vec3_origin )
+ {
+ DevWarning( "CBaseEntity::TakeDamage: with inputInfo.GetDamageForce() == vec3_origin\n" );
+ }
+ if ( inputInfo.GetDamagePosition() == vec3_origin )
+ {
+ DevWarning( "CBaseEntity::TakeDamage: with inputInfo.GetDamagePosition() == vec3_origin\n" );
+ }
+ }
+ }
+ }
+
+ // Make sure our damage filter allows the damage.
+ if ( !PassesDamageFilter( inputInfo ))
+ {
+ return;
+ }
+
+ if( !g_pGameRules->AllowDamage(this, inputInfo) )
+ {
+ return;
+ }
+
+ if ( PhysIsInCallback() )
+ {
+ PhysCallbackDamage( this, inputInfo );
+ }
+ else
+ {
+ CTakeDamageInfo info = inputInfo;
+
+ // Scale the damage by the attacker's modifier.
+ if ( info.GetAttacker() )
+ {
+ info.ScaleDamage( info.GetAttacker()->GetAttackDamageScale( this ) );
+ }
+
+ // Scale the damage by my own modifiers
+ info.ScaleDamage( GetReceivedDamageScale( info.GetAttacker() ) );
+
+ //Msg("%s took %.2f Damage, at %.2f\n", GetClassname(), info.GetDamage(), gpGlobals->curtime );
+
+ OnTakeDamage( info );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns a value that scales all damage done by this entity.
+//-----------------------------------------------------------------------------
+float CBaseEntity::GetAttackDamageScale( CBaseEntity *pVictim )
+{
+ float flScale = 1;
+ FOR_EACH_LL( m_DamageModifiers, i )
+ {
+ if ( !m_DamageModifiers[i]->IsDamageDoneToMe() )
+ {
+ flScale *= m_DamageModifiers[i]->GetModifier();
+ }
+ }
+ return flScale;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns a value that scales all damage done to this entity
+//-----------------------------------------------------------------------------
+float CBaseEntity::GetReceivedDamageScale( CBaseEntity *pAttacker )
+{
+ float flScale = 1;
+ FOR_EACH_LL( m_DamageModifiers, i )
+ {
+ if ( m_DamageModifiers[i]->IsDamageDoneToMe() )
+ {
+ flScale *= m_DamageModifiers[i]->GetModifier();
+ }
+ }
+ return flScale;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Applies forces to our physics object in response to damage.
+//-----------------------------------------------------------------------------
+int CBaseEntity::VPhysicsTakeDamage( const CTakeDamageInfo &info )
+{
+ // don't let physics impacts or fire cause objects to move (again)
+ bool bNoPhysicsForceDamage = g_pGameRules->Damage_NoPhysicsForce( info.GetDamageType() );
+ if ( bNoPhysicsForceDamage || info.GetDamageType() == DMG_GENERIC )
+ return 1;
+
+ Assert(VPhysicsGetObject() != NULL);
+ if ( VPhysicsGetObject() )
+ {
+ Vector force = info.GetDamageForce();
+ Vector offset = info.GetDamagePosition();
+
+ // If you hit this assert, you've called TakeDamage with a damage type that requires a physics damage
+ // force & position without specifying one or both of them. Decide whether your damage that's causing
+ // this is something you believe should impart physics force on the receiver. If it is, you need to
+ // setup the damage force & position inside the CTakeDamageInfo (Utility functions for this are in
+ // takedamageinfo.cpp. If you think the damage shouldn't cause force (unlikely!) then you can set the
+ // damage type to DMG_GENERIC, or | DMG_CRUSH if you need to preserve the damage type for purposes of HUD display.
+#if !defined( TF_DLL )
+ Assert( force != vec3_origin && offset != vec3_origin );
+#else
+ // this was spamming the console for Payload maps in TF (trigger_hurt entity on the front of the cart)
+ if ( !TFGameRules() || TFGameRules()->GetGameType() != TF_GAMETYPE_ESCORT )
+ {
+ Assert( force != vec3_origin && offset != vec3_origin );
+ }
+#endif
+
+ unsigned short gameFlags = VPhysicsGetObject()->GetGameFlags();
+ if ( gameFlags & FVPHYSICS_PLAYER_HELD )
+ {
+ // if the player is holding the object, use it's real mass (player holding reduced the mass)
+ CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
+ if ( pPlayer )
+ {
+ float mass = pPlayer->GetHeldObjectMass( VPhysicsGetObject() );
+ if ( mass != 0.0f )
+ {
+ float ratio = VPhysicsGetObject()->GetMass() / mass;
+ force *= ratio;
+ }
+ }
+ }
+ else if ( (gameFlags & FVPHYSICS_PART_OF_RAGDOLL) && (gameFlags & FVPHYSICS_CONSTRAINT_STATIC) )
+ {
+ IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
+ int count = VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
+ for ( int i = 0; i < count; i++ )
+ {
+ if ( !(pList[i]->GetGameFlags() & FVPHYSICS_CONSTRAINT_STATIC) )
+ {
+ pList[i]->ApplyForceOffset( force, offset );
+ return 1;
+ }
+ }
+
+ }
+ VPhysicsGetObject()->ApplyForceOffset( force, offset );
+ }
+
+ return 1;
+}
+
+ // Character killed (only fired once)
+void CBaseEntity::Event_Killed( const CTakeDamageInfo &info )
+{
+ if( info.GetAttacker() )
+ {
+ info.GetAttacker()->Event_KilledOther(this, info);
+ }
+
+ m_takedamage = DAMAGE_NO;
+ m_lifeState = LIFE_DEAD;
+ UTIL_Remove( this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: helper method to send a game event when this entity is killed. Note:
+// gets called specifically for particular entities (mostly NPC), this
+// does not get called for every entity
+//-----------------------------------------------------------------------------
+void CBaseEntity::SendOnKilledGameEvent( const CTakeDamageInfo &info )
+{
+ IGameEvent *event = gameeventmanager->CreateEvent( "entity_killed" );
+ if ( event )
+ {
+ event->SetInt( "entindex_killed", entindex() );
+ if ( info.GetAttacker())
+ {
+ event->SetInt( "entindex_attacker", info.GetAttacker()->entindex() );
+ }
+ if ( info.GetInflictor())
+ {
+ event->SetInt( "entindex_inflictor", info.GetInflictor()->entindex() );
+ }
+ event->SetInt( "damagebits", info.GetDamageType() );
+ gameeventmanager->FireEvent( event );
+ }
+}
+
+
+bool CBaseEntity::HasTarget( string_t targetname )
+{
+ if( targetname != NULL_STRING && m_target != NULL_STRING )
+ return FStrEq(STRING(targetname), STRING(m_target) );
+ else
+ return false;
+}
+
+
+CBaseEntity *CBaseEntity::GetNextTarget( void )
+{
+ if ( !m_target )
+ return NULL;
+ return gEntList.FindEntityByName( NULL, m_target );
+}
+
+class CThinkContextsSaveDataOps : public CDefSaveRestoreOps
+{
+ virtual void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave )
+ {
+ AssertMsg( fieldInfo.pTypeDesc->fieldSize == 1, "CThinkContextsSaveDataOps does not support arrays");
+
+ // Write out the vector
+ CUtlVector< thinkfunc_t > *pUtlVector = (CUtlVector< thinkfunc_t > *)fieldInfo.pField;
+ SaveUtlVector( pSave, pUtlVector, FIELD_EMBEDDED );
+
+ // Get our owner
+ CBaseEntity *pOwner = (CBaseEntity*)fieldInfo.pOwner;
+
+ pSave->StartBlock();
+ // Now write out all the functions
+ for ( int i = 0; i < pUtlVector->Size(); i++ )
+ {
+#ifdef WIN32
+ void **ppV = (void**)&((*pUtlVector)[i].m_pfnThink);
+#else
+ BASEPTR *ppV = &((*pUtlVector)[i].m_pfnThink);
+#endif
+ bool bHasFunc = (*ppV != NULL);
+ pSave->WriteBool( &bHasFunc, 1 );
+ if ( bHasFunc )
+ {
+ pSave->WriteFunction( pOwner->GetDataDescMap(), "m_pfnThink", (inputfunc_t **)ppV, 1 );
+ }
+ }
+ pSave->EndBlock();
+ }
+
+ virtual void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore )
+ {
+ AssertMsg( fieldInfo.pTypeDesc->fieldSize == 1, "CThinkContextsSaveDataOps does not support arrays");
+
+ // Read in the vector
+ CUtlVector< thinkfunc_t > *pUtlVector = (CUtlVector< thinkfunc_t > *)fieldInfo.pField;
+ RestoreUtlVector( pRestore, pUtlVector, FIELD_EMBEDDED );
+
+ // Get our owner
+ CBaseEntity *pOwner = (CBaseEntity*)fieldInfo.pOwner;
+
+ pRestore->StartBlock();
+ // Now read in all the functions
+ for ( int i = 0; i < pUtlVector->Size(); i++ )
+ {
+ bool bHasFunc;
+ pRestore->ReadBool( &bHasFunc, 1 );
+#ifdef WIN32
+ void **ppV = (void**)&((*pUtlVector)[i].m_pfnThink);
+#else
+ BASEPTR *ppV = &((*pUtlVector)[i].m_pfnThink);
+ Q_memset( (void *)ppV, 0x0, sizeof(inputfunc_t) );
+#endif
+ if ( bHasFunc )
+ {
+ SaveRestoreRecordHeader_t header;
+ pRestore->ReadHeader( &header );
+ pRestore->ReadFunction( pOwner->GetDataDescMap(), (inputfunc_t **)ppV, 1, header.size );
+ }
+ else
+ {
+ *ppV = NULL;
+ }
+ }
+ pRestore->EndBlock();
+ }
+
+ virtual bool IsEmpty( const SaveRestoreFieldInfo_t &fieldInfo )
+ {
+ CUtlVector< thinkfunc_t > *pUtlVector = (CUtlVector< thinkfunc_t > *)fieldInfo.pField;
+ return ( pUtlVector->Count() == 0 );
+ }
+
+ virtual void MakeEmpty( const SaveRestoreFieldInfo_t &fieldInfo )
+ {
+ BASEPTR pFunc = *((BASEPTR*)fieldInfo.pField);
+ pFunc = NULL;
+ }
+};
+CThinkContextsSaveDataOps g_ThinkContextsSaveDataOps;
+ISaveRestoreOps *thinkcontextFuncs = &g_ThinkContextsSaveDataOps;
+
+BEGIN_SIMPLE_DATADESC( thinkfunc_t )
+
+ DEFINE_FIELD( m_iszContext, FIELD_STRING ),
+ // DEFINE_FIELD( m_pfnThink, FIELD_FUNCTION ), // Manually written
+ DEFINE_FIELD( m_nNextThinkTick, FIELD_TICK ),
+ DEFINE_FIELD( m_nLastThinkTick, FIELD_TICK ),
+
+END_DATADESC()
+
+BEGIN_SIMPLE_DATADESC( ResponseContext_t )
+
+ DEFINE_FIELD( m_iszName, FIELD_STRING ),
+ DEFINE_FIELD( m_iszValue, FIELD_STRING ),
+ DEFINE_FIELD( m_fExpirationTime, FIELD_TIME ),
+
+END_DATADESC()
+
+BEGIN_DATADESC_NO_BASE( CBaseEntity )
+
+ DEFINE_KEYFIELD( m_iClassname, FIELD_STRING, "classname" ),
+ DEFINE_GLOBAL_KEYFIELD( m_iGlobalname, FIELD_STRING, "globalname" ),
+ DEFINE_KEYFIELD( m_iParent, FIELD_STRING, "parentname" ),
+
+ DEFINE_KEYFIELD( m_iHammerID, FIELD_INTEGER, "hammerid" ), // save ID numbers so that entities can be tracked between save/restore and vmf
+
+ DEFINE_KEYFIELD( m_flSpeed, FIELD_FLOAT, "speed" ),
+ DEFINE_KEYFIELD( m_nRenderFX, FIELD_CHARACTER, "renderfx" ),
+ DEFINE_KEYFIELD( m_nRenderMode, FIELD_CHARACTER, "rendermode" ),
+
+ // Consider moving to CBaseAnimating?
+ DEFINE_FIELD( m_flPrevAnimTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flAnimTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flSimulationTime, FIELD_TIME ),
+ DEFINE_FIELD( m_nLastThinkTick, FIELD_TICK ),
+
+ DEFINE_KEYFIELD( m_nNextThinkTick, FIELD_TICK, "nextthink" ),
+ DEFINE_KEYFIELD( m_fEffects, FIELD_INTEGER, "effects" ),
+ DEFINE_KEYFIELD( m_clrRender, FIELD_COLOR32, "rendercolor" ),
+ DEFINE_GLOBAL_KEYFIELD( m_nModelIndex, FIELD_SHORT, "modelindex" ),
+#if !defined( NO_ENTITY_PREDICTION )
+ // DEFINE_FIELD( m_PredictableID, CPredictableId ),
+#endif
+ DEFINE_FIELD( touchStamp, FIELD_INTEGER ),
+ DEFINE_CUSTOM_FIELD( m_aThinkFunctions, thinkcontextFuncs ),
+ // m_iCurrentThinkContext (not saved, debug field only, and think transient to boot)
+
+ DEFINE_UTLVECTOR(m_ResponseContexts, FIELD_EMBEDDED),
+ DEFINE_KEYFIELD( m_iszResponseContext, FIELD_STRING, "ResponseContext" ),
+
+ DEFINE_FIELD( m_pfnThink, FIELD_FUNCTION ),
+ DEFINE_FIELD( m_pfnTouch, FIELD_FUNCTION ),
+ DEFINE_FIELD( m_pfnUse, FIELD_FUNCTION ),
+ DEFINE_FIELD( m_pfnBlocked, FIELD_FUNCTION ),
+ DEFINE_FIELD( m_pfnMoveDone, FIELD_FUNCTION ),
+
+ DEFINE_FIELD( m_lifeState, FIELD_CHARACTER ),
+ DEFINE_FIELD( m_takedamage, FIELD_CHARACTER ),
+ DEFINE_KEYFIELD( m_iMaxHealth, FIELD_INTEGER, "max_health" ),
+ DEFINE_KEYFIELD( m_iHealth, FIELD_INTEGER, "health" ),
+ // DEFINE_FIELD( m_pLink, FIELD_CLASSPTR ),
+ DEFINE_KEYFIELD( m_target, FIELD_STRING, "target" ),
+
+ DEFINE_KEYFIELD( m_iszDamageFilterName, FIELD_STRING, "damagefilter" ),
+ DEFINE_FIELD( m_hDamageFilter, FIELD_EHANDLE ),
+
+ DEFINE_FIELD( m_debugOverlays, FIELD_INTEGER ),
+
+ DEFINE_GLOBAL_FIELD( m_pParent, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_iParentAttachment, FIELD_CHARACTER ),
+ DEFINE_GLOBAL_FIELD( m_hMoveParent, FIELD_EHANDLE ),
+ DEFINE_GLOBAL_FIELD( m_hMoveChild, FIELD_EHANDLE ),
+ DEFINE_GLOBAL_FIELD( m_hMovePeer, FIELD_EHANDLE ),
+
+ DEFINE_FIELD( m_iEFlags, FIELD_INTEGER ),
+
+ DEFINE_FIELD( m_iName, FIELD_STRING ),
+ DEFINE_EMBEDDED( m_Collision ),
+ DEFINE_EMBEDDED( m_Network ),
+
+ DEFINE_FIELD( m_MoveType, FIELD_CHARACTER ),
+ DEFINE_FIELD( m_MoveCollide, FIELD_CHARACTER ),
+ DEFINE_FIELD( m_hOwnerEntity, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_CollisionGroup, FIELD_INTEGER ),
+ DEFINE_PHYSPTR( m_pPhysicsObject),
+ DEFINE_FIELD( m_flElasticity, FIELD_FLOAT ),
+ DEFINE_KEYFIELD( m_flShadowCastDistance, FIELD_FLOAT, "shadowcastdist" ),
+ DEFINE_FIELD( m_flDesiredShadowCastDistance, FIELD_FLOAT ),
+
+ DEFINE_INPUT( m_iInitialTeamNum, FIELD_INTEGER, "TeamNum" ),
+ DEFINE_FIELD( m_iTeamNum, FIELD_INTEGER ),
+
+// DEFINE_FIELD( m_bSentLastFrame, FIELD_INTEGER ),
+
+ DEFINE_FIELD( m_hGroundEntity, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_flGroundChangeTime, FIELD_TIME ),
+ DEFINE_GLOBAL_KEYFIELD( m_ModelName, FIELD_MODELNAME, "model" ),
+
+ DEFINE_KEYFIELD( m_vecBaseVelocity, FIELD_VECTOR, "basevelocity" ),
+ DEFINE_FIELD( m_vecAbsVelocity, FIELD_VECTOR ),
+ DEFINE_KEYFIELD( m_vecAngVelocity, FIELD_VECTOR, "avelocity" ),
+// DEFINE_FIELD( m_vecAbsAngVelocity, FIELD_VECTOR ),
+ DEFINE_ARRAY( m_rgflCoordinateFrame, FIELD_FLOAT, 12 ), // NOTE: MUST BE IN LOCAL SPACE, NOT POSITION_VECTOR!!! (see CBaseEntity::Restore)
+
+ DEFINE_KEYFIELD( m_nWaterLevel, FIELD_CHARACTER, "waterlevel" ),
+ DEFINE_FIELD( m_nWaterType, FIELD_CHARACTER ),
+ DEFINE_FIELD( m_pBlocker, FIELD_EHANDLE ),
+
+ DEFINE_KEYFIELD( m_flGravity, FIELD_FLOAT, "gravity" ),
+ DEFINE_KEYFIELD( m_flFriction, FIELD_FLOAT, "friction" ),
+
+ // Local time is local to each object. It doesn't need to be re-based if the clock
+ // changes. Therefore it is saved as a FIELD_FLOAT, not a FIELD_TIME
+ DEFINE_KEYFIELD( m_flLocalTime, FIELD_FLOAT, "ltime" ),
+ DEFINE_FIELD( m_flVPhysicsUpdateLocalTime, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flMoveDoneTime, FIELD_FLOAT ),
+
+// DEFINE_FIELD( m_nPushEnumCount, FIELD_INTEGER ),
+
+ DEFINE_FIELD( m_vecAbsOrigin, FIELD_POSITION_VECTOR ),
+ DEFINE_KEYFIELD( m_vecVelocity, FIELD_VECTOR, "velocity" ),
+ DEFINE_KEYFIELD( m_iTextureFrameIndex, FIELD_CHARACTER, "texframeindex" ),
+ DEFINE_FIELD( m_bSimulatedEveryTick, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bAnimatedEveryTick, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bAlternateSorting, FIELD_BOOLEAN ),
+ DEFINE_KEYFIELD( m_spawnflags, FIELD_INTEGER, "spawnflags" ),
+ DEFINE_FIELD( m_nTransmitStateOwnedCounter, FIELD_CHARACTER ),
+ DEFINE_FIELD( m_angAbsRotation, FIELD_VECTOR ),
+ DEFINE_FIELD( m_vecOrigin, FIELD_VECTOR ), // NOTE: MUST BE IN LOCAL SPACE, NOT POSITION_VECTOR!!! (see CBaseEntity::Restore)
+ DEFINE_FIELD( m_angRotation, FIELD_VECTOR ),
+
+ DEFINE_KEYFIELD( m_vecViewOffset, FIELD_VECTOR, "view_ofs" ),
+
+ DEFINE_FIELD( m_fFlags, FIELD_INTEGER ),
+#if !defined( NO_ENTITY_PREDICTION )
+// DEFINE_FIELD( m_bIsPlayerSimulated, FIELD_INTEGER ),
+// DEFINE_FIELD( m_hPlayerSimulationOwner, FIELD_EHANDLE ),
+#endif
+ // DEFINE_FIELD( m_pTimedOverlay, TimedOverlay_t* ),
+ DEFINE_FIELD( m_nSimulationTick, FIELD_TICK ),
+ // DEFINE_FIELD( m_RefEHandle, CBaseHandle ),
+
+// DEFINE_FIELD( m_nWaterTouch, FIELD_INTEGER ),
+// DEFINE_FIELD( m_nSlimeTouch, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flNavIgnoreUntilTime, FIELD_TIME ),
+
+// DEFINE_FIELD( m_bToolRecording, FIELD_BOOLEAN ),
+// DEFINE_FIELD( m_ToolHandle, FIELD_INTEGER ),
+
+ // NOTE: This is tricky. TeamNum must be saved, but we can't directly
+ // read it in, because we can only set it after the team entity has been read in,
+ // which may or may not actually occur before the entity is parsed.
+ // Therefore, we set the TeamNum from the InitialTeamNum in Activate
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "SetTeam", InputSetTeam ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "Kill", InputKill ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "KillHierarchy", InputKillHierarchy ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Use", InputUse ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "Alpha", InputAlpha ),
+ DEFINE_INPUTFUNC( FIELD_BOOLEAN, "AlternativeSorting", InputAlternativeSorting ),
+ DEFINE_INPUTFUNC( FIELD_COLOR32, "Color", InputColor ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "SetParent", InputSetParent ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "SetParentAttachment", InputSetParentAttachment ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "SetParentAttachmentMaintainOffset", InputSetParentAttachmentMaintainOffset ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "ClearParent", InputClearParent ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "SetDamageFilter", InputSetDamageFilter ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "EnableDamageForces", InputEnableDamageForces ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DisableDamageForces", InputDisableDamageForces ),
+
+ DEFINE_INPUTFUNC( FIELD_STRING, "DispatchEffect", InputDispatchEffect ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "DispatchResponse", InputDispatchResponse ),
+
+ // Entity I/O methods to alter context
+ DEFINE_INPUTFUNC( FIELD_STRING, "AddContext", InputAddContext ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "RemoveContext", InputRemoveContext ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "ClearContext", InputClearContext ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "DisableShadow", InputDisableShadow ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "EnableShadow", InputEnableShadow ),
+
+ DEFINE_INPUTFUNC( FIELD_STRING, "AddOutput", InputAddOutput ),
+
+ DEFINE_INPUTFUNC( FIELD_STRING, "FireUser1", InputFireUser1 ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "FireUser2", InputFireUser2 ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "FireUser3", InputFireUser3 ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "FireUser4", InputFireUser4 ),
+
+ DEFINE_OUTPUT( m_OnUser1, "OnUser1" ),
+ DEFINE_OUTPUT( m_OnUser2, "OnUser2" ),
+ DEFINE_OUTPUT( m_OnUser3, "OnUser3" ),
+ DEFINE_OUTPUT( m_OnUser4, "OnUser4" ),
+
+ // Function Pointers
+ DEFINE_FUNCTION( SUB_Remove ),
+ DEFINE_FUNCTION( SUB_DoNothing ),
+ DEFINE_FUNCTION( SUB_StartFadeOut ),
+ DEFINE_FUNCTION( SUB_StartFadeOutInstant ),
+ DEFINE_FUNCTION( SUB_FadeOut ),
+ DEFINE_FUNCTION( SUB_Vanish ),
+ DEFINE_FUNCTION( SUB_CallUseToggle ),
+ DEFINE_THINKFUNC( ShadowCastDistThink ),
+
+ DEFINE_FIELD( m_hEffectEntity, FIELD_EHANDLE ),
+
+ //DEFINE_FIELD( m_DamageModifiers, FIELD_?? ), // can't save?
+ // DEFINE_FIELD( m_fDataObjectTypes, FIELD_INTEGER ),
+
+#ifdef TF_DLL
+ DEFINE_ARRAY( m_nModelIndexOverrides, FIELD_INTEGER, MAX_MODEL_INDEX_OVERRIDES ),
+#endif
+
+END_DATADESC()
+
+// For code error checking
+extern bool g_bReceivedChainedUpdateOnRemove;
+
+//-----------------------------------------------------------------------------
+// Purpose: Called just prior to object destruction
+// Entities that need to unlink themselves from other entities should do the unlinking
+// here rather than in their destructor. The reason why is that when the global entity list
+// is told to Clear(), it first takes a pass through all active entities and calls UTIL_Remove
+// on each such entity. Then it calls the delete function on each deleted entity in the list.
+// In the old code, the objects were simply destroyed in order and there was no guarantee that the
+// destructor of one object would not try to access another object that might already have been
+// destructed (especially since the entity list order is more or less random!).
+// NOTE: You should never call delete directly on an entity (there's an assert now), see note
+// at CBaseEntity::~CBaseEntity for more information.
+//
+// NOTE: You should chain to BaseClass::UpdateOnRemove after doing your own cleanup code, e.g.:
+//
+// void CDerived::UpdateOnRemove( void )
+// {
+// ... cleanup code
+// ...
+//
+// BaseClass::UpdateOnRemove();
+// }
+//
+// In general, this function updates global tables that need to know about entities being removed
+//-----------------------------------------------------------------------------
+void CBaseEntity::UpdateOnRemove( void )
+{
+ g_bReceivedChainedUpdateOnRemove = true;
+
+ // Virtual call to shut down any looping sounds.
+ StopLoopingSounds();
+
+ // Notifies entity listeners, etc
+ gEntList.NotifyRemoveEntity( GetRefEHandle() );
+
+ if ( edict() )
+ {
+ AddFlag( FL_KILLME );
+ if ( GetFlags() & FL_GRAPHED )
+ {
+ /* <<TODO>>
+ // this entity was a LinkEnt in the world node graph, so we must remove it from
+ // the graph since we are removing it from the world.
+ for ( int i = 0 ; i < WorldGraph.m_cLinks ; i++ )
+ {
+ if ( WorldGraph.m_pLinkPool [ i ].m_pLinkEnt == pev )
+ {
+ // if this link has a link ent which is the same ent that is removing itself, remove it!
+ WorldGraph.m_pLinkPool [ i ].m_pLinkEnt = NULL;
+ }
+ }
+ */
+ }
+ }
+
+ if ( m_iGlobalname != NULL_STRING )
+ {
+ // NOTE: During level shutdown the global list will suppress this
+ // it assumes your changing levels or the game will end
+ // causing the whole list to be flushed
+ GlobalEntity_SetState( m_iGlobalname, GLOBAL_DEAD );
+ }
+
+ VPhysicsDestroyObject();
+
+ // This is only here to allow the MOVETYPE_NONE to be set without the
+ // assertion triggering. Why do we bother setting the MOVETYPE to none here?
+ RemoveEffects( EF_BONEMERGE );
+ SetMoveType(MOVETYPE_NONE);
+
+ // If we have a parent, unlink from it.
+ UnlinkFromParent( this );
+
+ // Any children still connected are orphans, mark all for delete
+ CUtlVector<CBaseEntity *> childrenList;
+ GetAllChildren( this, childrenList );
+ if ( childrenList.Count() )
+ {
+ DevMsg( 2, "Warning: Deleting orphaned children of %s\n", GetClassname() );
+ for ( int i = childrenList.Count()-1; i >= 0; --i )
+ {
+ UTIL_Remove( childrenList[i] );
+ }
+ }
+
+ SetGroundEntity( NULL );
+
+ if ( m_bDynamicModelPending )
+ {
+ sg_DynamicLoadHandlers.Remove( this );
+ }
+
+ if ( IsDynamicModelIndex( m_nModelIndex ) )
+ {
+ modelinfo->ReleaseDynamicModel( m_nModelIndex ); // no-op if not dynamic
+ m_nModelIndex = -1;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// capabilities
+//-----------------------------------------------------------------------------
+int CBaseEntity::ObjectCaps( void )
+{
+#if 1
+ model_t *pModel = GetModel();
+ bool bIsBrush = ( pModel && modelinfo->GetModelType( pModel ) == mod_brush );
+
+ // We inherit our parent's use capabilities so that we can forward use commands
+ // to our parent.
+ CBaseEntity *pParent = GetParent();
+ if ( pParent )
+ {
+ int caps = pParent->ObjectCaps();
+
+ if ( !bIsBrush )
+ caps &= ( FCAP_ACROSS_TRANSITION | FCAP_IMPULSE_USE | FCAP_CONTINUOUS_USE | FCAP_ONOFF_USE | FCAP_DIRECTIONAL_USE );
+ else
+ caps &= ( FCAP_IMPULSE_USE | FCAP_CONTINUOUS_USE | FCAP_ONOFF_USE | FCAP_DIRECTIONAL_USE );
+
+ if ( pParent->IsPlayer() )
+ caps |= FCAP_ACROSS_TRANSITION;
+
+ return caps;
+ }
+ else if ( !bIsBrush )
+ {
+ return FCAP_ACROSS_TRANSITION;
+ }
+
+ return 0;
+#else
+ // We inherit our parent's use capabilities so that we can forward use commands
+ // to our parent.
+ int parentCaps = 0;
+ if (GetParent())
+ {
+ parentCaps = GetParent()->ObjectCaps();
+ parentCaps &= ( FCAP_IMPULSE_USE | FCAP_CONTINUOUS_USE | FCAP_ONOFF_USE | FCAP_DIRECTIONAL_USE );
+ }
+
+ model_t *pModel = GetModel();
+ if ( pModel && modelinfo->GetModelType( pModel ) == mod_brush )
+ return parentCaps;
+
+ return FCAP_ACROSS_TRANSITION | parentCaps;
+#endif
+}
+
+void CBaseEntity::StartTouch( CBaseEntity *pOther )
+{
+ // notify parent
+ if ( m_pParent != NULL )
+ m_pParent->StartTouch( pOther );
+}
+
+void CBaseEntity::Touch( CBaseEntity *pOther )
+{
+ if ( m_pfnTouch )
+ (this->*m_pfnTouch)( pOther );
+
+ // notify parent of touch
+ if ( m_pParent != NULL )
+ m_pParent->Touch( pOther );
+}
+
+void CBaseEntity::EndTouch( CBaseEntity *pOther )
+{
+ // notify parent
+ if ( m_pParent != NULL )
+ {
+ m_pParent->EndTouch( pOther );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Dispatches blocked events to this entity's blocked handler, set via SetBlocked.
+// Input : pOther - The entity that is blocking us.
+//-----------------------------------------------------------------------------
+void CBaseEntity::Blocked( CBaseEntity *pOther )
+{
+ if ( m_pfnBlocked )
+ {
+ (this->*m_pfnBlocked)( pOther );
+ }
+
+ //
+ // Forward the blocked event to our parent, if any.
+ //
+ if ( m_pParent != NULL )
+ {
+ m_pParent->Blocked( pOther );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Dispatches use events to this entity's use handler, set via SetUse.
+// Input : pActivator -
+// pCaller -
+// useType -
+// value -
+//-----------------------------------------------------------------------------
+void CBaseEntity::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ if ( m_pfnUse != NULL )
+ {
+ (this->*m_pfnUse)( pActivator, pCaller, useType, value );
+ }
+ else
+ {
+ //
+ // We don't handle use events. Forward to our parent, if any.
+ //
+ if ( m_pParent != NULL )
+ {
+ m_pParent->Use( pActivator, pCaller, useType, value );
+ }
+ }
+}
+
+static CBaseEntity *FindPhysicsBlocker( IPhysicsObject *pPhysics, physicspushlist_t &list, const Vector &pushVel )
+{
+ IPhysicsFrictionSnapshot *pSnapshot = pPhysics->CreateFrictionSnapshot();
+ CBaseEntity *pBlocker = NULL;
+ float maxForce = 0;
+ while ( pSnapshot->IsValid() )
+ {
+ IPhysicsObject *pOther = pSnapshot->GetObject(1);
+ CBaseEntity *pOtherEntity = static_cast<CBaseEntity *>(pOther->GetGameData());
+ bool inList = false;
+ for ( int i = 0; i < list.pushedCount; i++ )
+ {
+ if ( pOtherEntity == list.pushedEnts[i] )
+ {
+ inList = true;
+ break;
+ }
+ }
+
+ Vector normal;
+ pSnapshot->GetSurfaceNormal(normal);
+ float dot = DotProduct( pushVel, pSnapshot->GetNormalForce() * normal );
+ if ( !pBlocker || (!inList && dot > maxForce) )
+ {
+ pBlocker = pOtherEntity;
+ if ( !inList )
+ {
+ maxForce = dot;
+ }
+ }
+
+ pSnapshot->NextFrictionData();
+ }
+ pPhysics->DestroyFrictionSnapshot( pSnapshot );
+
+ return pBlocker;
+}
+
+
+struct pushblock_t
+{
+ physicspushlist_t *pList;
+ CBaseEntity *pRootParent;
+ CBaseEntity *pBlockedEntity;
+ float moveBackFraction;
+ float movetime;
+};
+
+static void ComputePushStartMatrix( matrix3x4_t &start, CBaseEntity *pEntity, const pushblock_t ¶ms )
+{
+ Vector localOrigin;
+ QAngle localAngles;
+ if ( params.pList )
+ {
+ localOrigin = params.pList->localOrigin;
+ localAngles = params.pList->localAngles;
+ }
+ else
+ {
+ localOrigin = params.pRootParent->GetAbsOrigin() - params.pRootParent->GetAbsVelocity() * params.movetime;
+ localAngles = params.pRootParent->GetAbsAngles() - params.pRootParent->GetLocalAngularVelocity() * params.movetime;
+ }
+ matrix3x4_t xform, delta;
+ AngleMatrix( localAngles, localOrigin, xform );
+
+ matrix3x4_t srcInv;
+ // xform = src(-1) * dest
+ MatrixInvert( params.pRootParent->EntityToWorldTransform(), srcInv );
+ ConcatTransforms( xform, srcInv, delta );
+ ConcatTransforms( delta, pEntity->EntityToWorldTransform(), start );
+}
+
+#define DEBUG_PUSH_MESSAGES 0
+static void CheckPushedEntity( CBaseEntity *pEntity, pushblock_t ¶ms )
+{
+ IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject();
+ if ( !pPhysics )
+ return;
+ // somehow we've got a static or motion disabled physics object in hierarchy!
+ // This is not allowed! Don't test blocking in that case.
+ Assert(pPhysics->IsMoveable());
+ if ( !pPhysics->IsMoveable() || !pPhysics->GetShadowController() )
+ {
+#if DEBUG_PUSH_MESSAGES
+ Msg("Blocking %s, not moveable!\n", pEntity->GetClassname());
+#endif
+ return;
+ }
+
+ bool checkrot = true;
+ bool checkmove = true;
+ Vector origin;
+ QAngle angles;
+ pPhysics->GetShadowPosition( &origin, &angles );
+ float fraction = -1.0f;
+
+ matrix3x4_t parentDelta;
+ if ( pEntity == params.pRootParent )
+ {
+ if ( pEntity->GetLocalAngularVelocity() == vec3_angle )
+ checkrot = false;
+ if ( pEntity->GetLocalVelocity() == vec3_origin)
+ checkmove = false;
+ }
+ else
+ {
+#if DEBUG_PUSH_MESSAGES
+ if ( pPhysics->IsAttachedToConstraint(false))
+ {
+ Msg("Warning, hierarchical entity is attached to a constraint %s\n", pEntity->GetClassname());
+ }
+#endif
+ }
+
+ if ( checkmove )
+ {
+ // project error onto the axis of movement
+ Vector dir = pEntity->GetAbsVelocity();
+ float speed = VectorNormalize(dir);
+ Vector targetPos;
+ pPhysics->GetShadowController()->GetTargetPosition( &targetPos, NULL );
+ float targetAmount = DotProduct(targetPos, dir);
+ float currentAmount = DotProduct(origin, dir);
+ float entityAmount = DotProduct(pEntity->GetAbsOrigin(), dir);
+
+ // if target and entity origin are not in sync, then the position of the entity was updated
+ // by something outside of push physics
+ if ( (targetAmount - entityAmount) > 1 )
+ {
+ pEntity->UpdatePhysicsShadowToCurrentPosition(0);
+#if DEBUG_PUSH_MESSAGES
+ Warning("Someone slammed the position of a %s\n", pEntity->GetClassname() );
+#endif
+ }
+ else
+ {
+ float dist = targetAmount - currentAmount;
+ if ( dist > 1 )
+ {
+ #if DEBUG_PUSH_MESSAGES
+ const char *pName = pEntity->GetClassname();
+ Msg( "%s blocked by %.2f units\n", pName, dist );
+ #endif
+ float movementAmount = targetAmount - (speed * params.movetime);
+ if ( pEntity == params.pRootParent )
+ {
+ if ( params.pList )
+ {
+ Vector localVel = pEntity->GetLocalVelocity();
+ VectorNormalize(localVel);
+ float localTargetAmt = DotProduct(pEntity->GetLocalOrigin(), localVel);
+ movementAmount = targetAmount + DotProduct(params.pList->localOrigin, localVel) - localTargetAmt;
+ }
+ }
+ else
+ {
+ matrix3x4_t start;
+ ComputePushStartMatrix( start, pEntity, params );
+ Vector startPos;
+ MatrixPosition( start, startPos );
+ movementAmount = DotProduct(startPos, dir);
+ }
+ float expectedDist = targetAmount - movementAmount;
+ // compute the fraction to move back the AI to match the physics
+ if ( expectedDist <= 0 )
+ {
+ fraction = 1;
+ }
+ else
+ {
+ fraction = dist / expectedDist;
+ fraction = clamp(fraction, 0.f, 1.f);
+ }
+ }
+ }
+ }
+
+ if ( checkrot )
+ {
+ Vector axis;
+ float deltaAngle;
+ RotationDeltaAxisAngle( angles, pEntity->GetAbsAngles(), axis, deltaAngle );
+ if ( fabsf(deltaAngle) > 0.5f )
+ {
+ Vector targetAxis;
+ QAngle targetRot;
+ float deltaTargetAngle;
+ pPhysics->GetShadowController()->GetTargetPosition( NULL, &targetRot );
+ RotationDeltaAxisAngle( angles, targetRot, targetAxis, deltaTargetAngle );
+ if ( fabsf(deltaTargetAngle) > 0.01f )
+ {
+ float expectedDist = deltaAngle;
+#if DEBUG_PUSH_MESSAGES
+ const char *pName = pEntity->GetClassname();
+ Msg( "%s blocked by %.2f degrees\n", pName, deltaAngle );
+ if ( pPhysics->IsAsleep() )
+ {
+ Msg("Asleep while blocked?\n");
+ }
+ if ( pPhysics->GetGameFlags() & FVPHYSICS_PENETRATING )
+ {
+ Msg("Blocking for penetration!\n");
+ }
+#endif
+ if ( pEntity == params.pRootParent )
+ {
+ expectedDist = pEntity->GetLocalAngularVelocity().Length() * params.movetime;
+ }
+ else
+ {
+ matrix3x4_t start;
+ ComputePushStartMatrix( start, pEntity, params );
+ Vector startAxis;
+ float startAngle;
+ Vector startPos;
+ QAngle startAngles;
+ MatrixAngles( start, startAngles, startPos );
+ RotationDeltaAxisAngle( startAngles, pEntity->GetAbsAngles(), startAxis, startAngle );
+ expectedDist = startAngle * DotProduct( startAxis, axis );
+ }
+
+ float t = expectedDist != 0.0f ? fabsf(deltaAngle / expectedDist) : 1.0f;
+ t = clamp(t,0.f,1.f);
+ fraction = MAX(fraction, t);
+ }
+ else
+ {
+ pEntity->UpdatePhysicsShadowToCurrentPosition(0);
+#if DEBUG_PUSH_MESSAGES
+ Warning("Someone slammed the position of a %s\n", pEntity->GetClassname() );
+#endif
+ }
+ }
+ }
+ if ( fraction >= params.moveBackFraction )
+ {
+ params.moveBackFraction = fraction;
+ params.pBlockedEntity = pEntity;
+ }
+}
+
+void CBaseEntity::VPhysicsUpdatePusher( IPhysicsObject *pPhysics )
+{
+ float movetime = m_flLocalTime - m_flVPhysicsUpdateLocalTime;
+ if (movetime <= 0)
+ return;
+
+ // only reconcile pushers on the final vphysics tick
+ if ( !PhysIsFinalTick() )
+ return;
+
+ Vector origin;
+ QAngle angles;
+
+ // physics updated the shadow, so check to see if I got blocked
+ // NOTE: SOLID_BSP cannont compute consistent collisions wrt vphysics, so
+ // don't allow vphysics to block. Assume game physics has handled it.
+ if ( GetSolid() != SOLID_BSP && pPhysics->GetShadowPosition( &origin, &angles ) )
+ {
+ CUtlVector<CBaseEntity *> list;
+ GetAllInHierarchy( this, list );
+ //NDebugOverlay::BoxAngles( origin, CollisionProp()->OBBMins(), CollisionProp()->OBBMaxs(), angles, 255,0,0,0, gpGlobals->frametime);
+
+ physicspushlist_t *pList = NULL;
+ if ( HasDataObjectType(PHYSICSPUSHLIST) )
+ {
+ pList = (physicspushlist_t *)GetDataObject( PHYSICSPUSHLIST );
+ Assert(pList);
+ }
+ bool checkrot = (GetLocalAngularVelocity() != vec3_angle) ? true : false;
+ bool checkmove = (GetLocalVelocity() != vec3_origin) ? true : false;
+
+ pushblock_t params;
+ params.pRootParent = this;
+ params.pList = pList;
+ params.pBlockedEntity = NULL;
+ params.moveBackFraction = 0.0f;
+ params.movetime = movetime;
+ for ( int i = 0; i < list.Count(); i++ )
+ {
+ if ( list[i]->IsSolid() )
+ {
+ CheckPushedEntity( list[i], params );
+ }
+ }
+
+ float physLocalTime = m_flLocalTime;
+ if ( params.pBlockedEntity )
+ {
+ float moveback = movetime * params.moveBackFraction;
+ if ( moveback > 0 )
+ {
+ physLocalTime = m_flLocalTime - moveback;
+ // add 1% noise for bouncing in collision.
+ if ( physLocalTime <= (m_flVPhysicsUpdateLocalTime + movetime * 0.99f) )
+ {
+ CBaseEntity *pBlocked = NULL;
+ IPhysicsObject *pOther;
+ if ( params.pBlockedEntity->VPhysicsGetObject()->GetContactPoint( NULL, &pOther ) )
+ {
+ pBlocked = static_cast<CBaseEntity *>(pOther->GetGameData());
+ }
+ // UNDONE: Need to traverse hierarchy here? Shouldn't.
+ if ( pList )
+ {
+ SetLocalOrigin( pList->localOrigin );
+ SetLocalAngles( pList->localAngles );
+ physLocalTime = pList->localMoveTime;
+ for ( int i = 0; i < pList->pushedCount; i++ )
+ {
+ CBaseEntity *pEntity = pList->pushedEnts[i];
+ if ( !pEntity )
+ continue;
+
+ pEntity->SetAbsOrigin( pEntity->GetAbsOrigin() - pList->pushVec[i] );
+ }
+ CBaseEntity *pPhysicsBlocker = FindPhysicsBlocker( VPhysicsGetObject(), *pList, pList->pushVec[0] );
+ if ( pPhysicsBlocker )
+ {
+ pBlocked = pPhysicsBlocker;
+ }
+ }
+ else
+ {
+ Vector origin = GetLocalOrigin();
+ QAngle angles = GetLocalAngles();
+
+ if ( checkmove )
+ {
+ origin -= GetLocalVelocity() * moveback;
+ }
+ if ( checkrot )
+ {
+ // BUGBUG: This is pretty hack-tastic!
+ angles -= GetLocalAngularVelocity() * moveback;
+ }
+
+ SetLocalOrigin( origin );
+ SetLocalAngles( angles );
+ }
+
+ if ( pBlocked )
+ {
+ Blocked( pBlocked );
+ }
+ m_flLocalTime = physLocalTime;
+ }
+ }
+ }
+ }
+
+ // this data is no longer useful, free the memory
+ if ( HasDataObjectType(PHYSICSPUSHLIST) )
+ {
+ DestroyDataObject( PHYSICSPUSHLIST );
+ }
+
+ m_flVPhysicsUpdateLocalTime = m_flLocalTime;
+ if ( m_flMoveDoneTime <= m_flLocalTime && m_flMoveDoneTime > 0 )
+ {
+ SetMoveDoneTime( -1 );
+ MoveDone();
+ }
+}
+
+
+void CBaseEntity::SetMoveDoneTime( float flDelay )
+{
+ if (flDelay >= 0)
+ {
+ m_flMoveDoneTime = GetLocalTime() + flDelay;
+ }
+ else
+ {
+ m_flMoveDoneTime = -1;
+ }
+ CheckHasGamePhysicsSimulation();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Relinks all of a parents children into the collision tree
+//-----------------------------------------------------------------------------
+void CBaseEntity::PhysicsRelinkChildren( float dt )
+{
+ CBaseEntity *child;
+
+ // iterate through all children
+ for ( child = FirstMoveChild(); child != NULL; child = child->NextMovePeer() )
+ {
+ if ( child->IsSolid() || child->IsSolidFlagSet(FSOLID_TRIGGER) )
+ {
+ child->PhysicsTouchTriggers();
+ }
+
+ //
+ // Update their physics shadows. We should never have any children of
+ // movetype VPHYSICS.
+ //
+ if ( child->GetMoveType() != MOVETYPE_VPHYSICS )
+ {
+ child->UpdatePhysicsShadowToCurrentPosition( dt );
+ }
+ else if ( child->GetOwnerEntity() != this )
+ {
+ // the only case where this is valid is if this entity is an attached ragdoll.
+ // So assert here to catch the non-ragdoll case.
+ Assert( 0 );
+ }
+
+ if ( child->FirstMoveChild() )
+ {
+ child->PhysicsRelinkChildren(dt);
+ }
+ }
+}
+
+void CBaseEntity::PhysicsTouchTriggers( const Vector *pPrevAbsOrigin )
+{
+ edict_t *pEdict = edict();
+ if ( pEdict && !IsWorld() )
+ {
+ Assert(CollisionProp());
+ bool isTriggerCheckSolids = IsSolidFlagSet( FSOLID_TRIGGER );
+ bool isSolidCheckTriggers = IsSolid() && !isTriggerCheckSolids; // NOTE: Moving triggers (items, ammo etc) are not
+ // checked against other triggers to reduce the number of touchlinks created
+ if ( !(isSolidCheckTriggers || isTriggerCheckSolids) )
+ return;
+
+ if ( GetSolid() == SOLID_BSP )
+ {
+ if ( !GetModel() && Q_strlen( STRING( GetModelName() ) ) == 0 )
+ {
+ Warning( "Inserted %s with no model\n", GetClassname() );
+ return;
+ }
+ }
+
+ SetCheckUntouch( true );
+ if ( isSolidCheckTriggers )
+ {
+ engine->SolidMoved( pEdict, CollisionProp(), pPrevAbsOrigin, sm_bAccurateTriggerBboxChecks );
+ }
+ if ( isTriggerCheckSolids )
+ {
+ engine->TriggerMoved( pEdict, sm_bAccurateTriggerBboxChecks );
+ }
+ }
+}
+
+void CBaseEntity::VPhysicsShadowCollision( int index, gamevcollisionevent_t *pEvent )
+{
+}
+
+
+
+void CBaseEntity::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
+{
+ // filter out ragdoll props hitting other parts of itself too often
+ // UNDONE: Store a sound time for this entity (not just this pair of objects)
+ // and filter repeats on that?
+ int otherIndex = !index;
+ CBaseEntity *pHitEntity = pEvent->pEntities[otherIndex];
+
+ // Don't make sounds / effects if neither entity is MOVETYPE_VPHYSICS. The game
+ // physics should have done so.
+ if ( GetMoveType() != MOVETYPE_VPHYSICS && pHitEntity->GetMoveType() != MOVETYPE_VPHYSICS )
+ return;
+
+ if ( pEvent->deltaCollisionTime < 0.5 && (pHitEntity == this) )
+ return;
+
+ // don't make noise for hidden/invisible/sky materials
+ surfacedata_t *phit = physprops->GetSurfaceData( pEvent->surfaceProps[otherIndex] );
+ const surfacedata_t *pprops = physprops->GetSurfaceData( pEvent->surfaceProps[index] );
+ if ( phit->game.material == 'X' || pprops->game.material == 'X' )
+ return;
+
+ if ( pHitEntity == this )
+ {
+ PhysCollisionSound( this, pEvent->pObjects[index], CHAN_BODY, pEvent->surfaceProps[index], pEvent->surfaceProps[otherIndex], pEvent->deltaCollisionTime, pEvent->collisionSpeed );
+ }
+ else
+ {
+ PhysCollisionSound( this, pEvent->pObjects[index], CHAN_STATIC, pEvent->surfaceProps[index], pEvent->surfaceProps[otherIndex], pEvent->deltaCollisionTime, pEvent->collisionSpeed );
+ }
+ PhysCollisionScreenShake( pEvent, index );
+
+#if HL2_EPISODIC
+ // episodic does something different for when advisor shields are struck
+ if ( phit->game.material == 'Z' || pprops->game.material == 'Z')
+ {
+ PhysCollisionWarpEffect( pEvent, phit );
+ }
+ else
+ {
+ PhysCollisionDust( pEvent, phit );
+ }
+#else
+ PhysCollisionDust( pEvent, phit );
+#endif
+}
+
+void CBaseEntity::VPhysicsFriction( IPhysicsObject *pObject, float energy, int surfaceProps, int surfacePropsHit )
+{
+ PhysFrictionSound( this, pObject, energy, surfaceProps, surfacePropsHit );
+}
+
+
+void CBaseEntity::VPhysicsSwapObject( IPhysicsObject *pSwap )
+{
+ if ( !pSwap )
+ {
+ PhysRemoveShadow(this);
+ }
+
+ if ( !m_pPhysicsObject )
+ {
+ Warning( "Bad vphysics swap for %s\n", STRING(m_iClassname) );
+ }
+ m_pPhysicsObject = pSwap;
+}
+
+
+// Tells the physics shadow to update it's target to the current position
+void CBaseEntity::UpdatePhysicsShadowToCurrentPosition( float deltaTime )
+{
+ if ( GetMoveType() != MOVETYPE_VPHYSICS )
+ {
+ IPhysicsObject *pPhys = VPhysicsGetObject();
+ if ( pPhys )
+ {
+ pPhys->UpdateShadow( GetAbsOrigin(), GetAbsAngles(), false, deltaTime );
+ }
+ }
+}
+
+int CBaseEntity::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 CBaseEntity::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;
+}
+
+bool CBaseEntity::Intersects( CBaseEntity *pOther )
+{
+ if ( !edict() || !pOther->edict() )
+ return false;
+
+ CCollisionProperty *pMyProp = CollisionProp();
+ CCollisionProperty *pOtherProp = pOther->CollisionProp();
+
+ return IsOBBIntersectingOBB(
+ pMyProp->GetCollisionOrigin(), pMyProp->GetCollisionAngles(), pMyProp->OBBMins(), pMyProp->OBBMaxs(),
+ pOtherProp->GetCollisionOrigin(), pOtherProp->GetCollisionAngles(), pOtherProp->OBBMins(), pOtherProp->OBBMaxs() );
+}
+
+extern ConVar ai_LOS_mode;
+
+//=========================================================
+// FVisible - returns true if a line can be traced from
+// the caller's eyes to the target
+//=========================================================
+bool CBaseEntity::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
+{
+ VPROF( "CBaseEntity::FVisible" );
+
+ if ( pEntity->GetFlags() & FL_NOTARGET )
+ return false;
+
+#if HL1_DLL
+ // FIXME: only block LOS through opaque water
+ // don't look through water
+ if ((m_nWaterLevel != 3 && pEntity->m_nWaterLevel == 3)
+ || (m_nWaterLevel == 3 && pEntity->m_nWaterLevel == 0))
+ return false;
+#endif
+
+ Vector vecLookerOrigin = EyePosition();//look through the caller's 'eyes'
+ Vector vecTargetOrigin = pEntity->EyePosition();
+
+ trace_t tr;
+ if ( !IsXbox() && ai_LOS_mode.GetBool() )
+ {
+ UTIL_TraceLine(vecLookerOrigin, vecTargetOrigin, traceMask, this, COLLISION_GROUP_NONE, &tr);
+ }
+ else
+ {
+ // If we're doing an LOS search, include NPCs.
+ if ( traceMask == MASK_BLOCKLOS )
+ {
+ traceMask = MASK_BLOCKLOS_AND_NPCS;
+ }
+
+ // Player sees through nodraw
+ if ( IsPlayer() )
+ {
+ traceMask &= ~CONTENTS_BLOCKLOS;
+ }
+
+ // Use the custom LOS trace filter
+ CTraceFilterLOS traceFilter( this, COLLISION_GROUP_NONE, pEntity );
+ UTIL_TraceLine( vecLookerOrigin, vecTargetOrigin, traceMask, &traceFilter, &tr );
+ }
+
+ if (tr.fraction != 1.0 || tr.startsolid )
+ {
+ // If we hit the entity we're looking for, it's visible
+ if ( tr.m_pEnt == pEntity )
+ return true;
+
+ // Got line of sight on the vehicle the player is driving!
+ if ( pEntity && pEntity->IsPlayer() )
+ {
+ CBasePlayer *pPlayer = assert_cast<CBasePlayer*>( pEntity );
+ if ( tr.m_pEnt == pPlayer->GetVehicleEntity() )
+ return true;
+ }
+
+ if (ppBlocker)
+ {
+ *ppBlocker = tr.m_pEnt;
+ }
+
+ return false;// Line of sight is not established
+ }
+
+ return true;// line of sight is valid.
+}
+
+//=========================================================
+// FVisible - returns true if a line can be traced from
+// the caller's eyes to the wished position.
+//=========================================================
+bool CBaseEntity::FVisible( const Vector &vecTarget, int traceMask, CBaseEntity **ppBlocker )
+{
+#if HL1_DLL
+
+ // don't look through water
+ // FIXME: only block LOS through opaque water
+ bool inWater = ( UTIL_PointContents( vecTarget ) & (CONTENTS_SLIME|CONTENTS_WATER) ) ? true : false;
+
+ // Don't allow it if we're straddling two areas
+ if ( ( m_nWaterLevel == 3 && !inWater ) || ( m_nWaterLevel != 3 && inWater ) )
+ return false;
+
+#endif
+
+ trace_t tr;
+ Vector vecLookerOrigin = EyePosition();// look through the caller's 'eyes'
+
+ if ( ai_LOS_mode.GetBool() )
+ {
+ UTIL_TraceLine( vecLookerOrigin, vecTarget, traceMask, this, COLLISION_GROUP_NONE, &tr);
+ }
+ else
+ {
+ // If we're doing an LOS search, include NPCs.
+ if ( traceMask == MASK_BLOCKLOS )
+ {
+ traceMask = MASK_BLOCKLOS_AND_NPCS;
+ }
+
+ // Player sees through nodraw and blocklos
+ if ( IsPlayer() )
+ {
+ traceMask |= CONTENTS_IGNORE_NODRAW_OPAQUE;
+ traceMask &= ~CONTENTS_BLOCKLOS;
+ }
+
+ // Use the custom LOS trace filter
+ CTraceFilterLOS traceFilter( this, COLLISION_GROUP_NONE );
+ UTIL_TraceLine( vecLookerOrigin, vecTarget, traceMask, &traceFilter, &tr );
+ }
+
+ if (tr.fraction != 1.0)
+ {
+ if (ppBlocker)
+ {
+ *ppBlocker = tr.m_pEnt;
+ }
+ return false;// Line of sight is not established
+ }
+
+ return true;// line of sight is valid.
+}
+
+extern ConVar ai_debug_los;
+//-----------------------------------------------------------------------------
+// Purpose: Turn on prop LOS debugging mode
+//-----------------------------------------------------------------------------
+void CC_AI_LOS_Debug( IConVar *var, const char *pOldString, float flOldValue )
+{
+ int iLOSMode = ai_debug_los.GetInt();
+ for ( CBaseEntity *pEntity = gEntList.FirstEnt(); pEntity != NULL; pEntity = gEntList.NextEnt(pEntity) )
+ {
+ if ( iLOSMode == 1 && pEntity->IsSolid() )
+ {
+ pEntity->m_debugOverlays |= OVERLAY_SHOW_BLOCKSLOS;
+ }
+ else if ( iLOSMode == 2 )
+ {
+ pEntity->m_debugOverlays |= OVERLAY_SHOW_BLOCKSLOS;
+ }
+ else
+ {
+ pEntity->m_debugOverlays &= ~OVERLAY_SHOW_BLOCKSLOS;
+ }
+ }
+}
+ConVar ai_debug_los("ai_debug_los", "0", FCVAR_CHEAT, "NPC Line-Of-Sight debug mode. If 1, solid entities that block NPC LOC will be highlighted with white bounding boxes. If 2, it'll show non-solid entities that would do it if they were solid.", CC_AI_LOS_Debug );
+
+
+Class_T CBaseEntity::Classify ( void )
+{
+ return CLASS_NONE;
+}
+
+float CBaseEntity::GetAutoAimRadius()
+{
+ if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE )
+ return 48.0f;
+ else
+ return 24.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Changes the shadow cast distance over time
+//-----------------------------------------------------------------------------
+void CBaseEntity::ShadowCastDistThink( )
+{
+ SetShadowCastDistance( m_flDesiredShadowCastDistance );
+ SetContextThink( NULL, gpGlobals->curtime, "ShadowCastDistThink" );
+}
+
+void CBaseEntity::SetShadowCastDistance( float flDesiredDistance, float flDelay )
+{
+ m_flDesiredShadowCastDistance = flDesiredDistance;
+ if ( m_flDesiredShadowCastDistance != m_flShadowCastDistance )
+ {
+ SetContextThink( &CBaseEntity::ShadowCastDistThink, gpGlobals->curtime + flDelay, "ShadowCastDistThink" );
+ }
+}
+
+
+/*
+================
+TraceAttack
+================
+*/
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns whether a damage info can damage this entity.
+//-----------------------------------------------------------------------------
+bool CBaseEntity::PassesDamageFilter( const CTakeDamageInfo &info )
+{
+ if (m_hDamageFilter)
+ {
+ CBaseFilter *pFilter = (CBaseFilter *)(m_hDamageFilter.Get());
+ return pFilter->PassesDamageFilter(info);
+ }
+
+ return true;
+}
+
+FORCEINLINE bool NamesMatch( const char *pszQuery, string_t nameToMatch )
+{
+ if ( nameToMatch == NULL_STRING )
+ return (!pszQuery || *pszQuery == 0 || *pszQuery == '*');
+
+ const char *pszNameToMatch = STRING(nameToMatch);
+
+ // If the pointers are identical, we're identical
+ if ( pszNameToMatch == pszQuery )
+ return true;
+
+ while ( *pszNameToMatch && *pszQuery )
+ {
+ unsigned char cName = *pszNameToMatch;
+ unsigned char cQuery = *pszQuery;
+ // simple ascii case conversion
+ if ( cName == cQuery )
+ ;
+ else if ( cName - 'A' <= (unsigned char)'Z' - 'A' && cName - 'A' + 'a' == cQuery )
+ ;
+ else if ( cName - 'a' <= (unsigned char)'z' - 'a' && cName - 'a' + 'A' == cQuery )
+ ;
+ else
+ break;
+ ++pszNameToMatch;
+ ++pszQuery;
+ }
+
+ if ( *pszQuery == 0 && *pszNameToMatch == 0 )
+ return true;
+
+ // @TODO (toml 03-18-03): Perhaps support real wildcards. Right now, only thing supported is trailing *
+ if ( *pszQuery == '*' )
+ return true;
+
+ return false;
+}
+
+bool CBaseEntity::NameMatchesComplex( const char *pszNameOrWildcard )
+{
+ if ( !Q_stricmp( "!player", pszNameOrWildcard) )
+ return IsPlayer();
+
+ return NamesMatch( pszNameOrWildcard, m_iName );
+}
+
+bool CBaseEntity::ClassMatchesComplex( const char *pszClassOrWildcard )
+{
+ return NamesMatch( pszClassOrWildcard, m_iClassname );
+}
+
+void CBaseEntity::MakeDormant( void )
+{
+ AddEFlags( EFL_DORMANT );
+
+ // disable thinking for dormant entities
+ SetThink( NULL );
+
+ if ( !edict() )
+ return;
+
+ SETBITS( m_iEFlags, EFL_DORMANT );
+
+ // Don't touch
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ // Don't move
+ SetMoveType( MOVETYPE_NONE );
+ // Don't draw
+ AddEffects( EF_NODRAW );
+ // Don't think
+ SetNextThink( TICK_NEVER_THINK );
+}
+
+int CBaseEntity::IsDormant( void )
+{
+ return IsEFlagSet( EFL_DORMANT );
+}
+
+
+bool CBaseEntity::IsInWorld( void ) const
+{
+ if ( !edict() )
+ return true;
+
+ // position
+ if (GetAbsOrigin().x >= MAX_COORD_INTEGER) return false;
+ if (GetAbsOrigin().y >= MAX_COORD_INTEGER) return false;
+ if (GetAbsOrigin().z >= MAX_COORD_INTEGER) return false;
+ if (GetAbsOrigin().x <= MIN_COORD_INTEGER) return false;
+ if (GetAbsOrigin().y <= MIN_COORD_INTEGER) return false;
+ if (GetAbsOrigin().z <= MIN_COORD_INTEGER) return false;
+ // speed
+ if (GetAbsVelocity().x >= 2000) return false;
+ if (GetAbsVelocity().y >= 2000) return false;
+ if (GetAbsVelocity().z >= 2000) return false;
+ if (GetAbsVelocity().x <= -2000) return false;
+ if (GetAbsVelocity().y <= -2000) return false;
+ if (GetAbsVelocity().z <= -2000) return false;
+
+ return true;
+}
+
+
+bool CBaseEntity::IsViewable( void )
+{
+ if ( IsEffectActive( EF_NODRAW ) )
+ {
+ return false;
+ }
+
+ if (IsBSPModel())
+ {
+ if (GetMoveType() != MOVETYPE_NONE)
+ {
+ return true;
+ }
+ }
+ else if (GetModelIndex() != 0)
+ {
+ // check for total transparency???
+ return true;
+ }
+ return false;
+}
+
+
+int CBaseEntity::ShouldToggle( USE_TYPE useType, int currentState )
+{
+ if ( useType != USE_TOGGLE && useType != USE_SET )
+ {
+ if ( (currentState && useType == USE_ON) || (!currentState && useType == USE_OFF) )
+ return 0;
+ }
+ return 1;
+}
+
+
+// NOTE: szName must be a pointer to constant memory, e.g. "NPC_class" because the entity
+// will keep a pointer to it after this call.
+CBaseEntity *CBaseEntity::Create( const char *szName, const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity *pOwner )
+{
+ CBaseEntity *pEntity = CreateNoSpawn( szName, vecOrigin, vecAngles, pOwner );
+
+ DispatchSpawn( pEntity );
+ return pEntity;
+}
+
+
+
+// NOTE: szName must be a pointer to constant memory, e.g. "NPC_class" because the entity
+// will keep a pointer to it after this call.
+CBaseEntity * CBaseEntity::CreateNoSpawn( const char *szName, const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity *pOwner )
+{
+ CBaseEntity *pEntity = CreateEntityByName( szName );
+ if ( !pEntity )
+ {
+ Assert( !"CreateNoSpawn: only works for CBaseEntities" );
+ return NULL;
+ }
+
+ pEntity->SetLocalOrigin( vecOrigin );
+ pEntity->SetLocalAngles( vecAngles );
+ pEntity->SetOwnerEntity( pOwner );
+
+ gEntList.NotifyCreateEntity( pEntity );
+
+ return pEntity;
+}
+
+Vector CBaseEntity::GetSoundEmissionOrigin() const
+{
+ return WorldSpaceCenter();
+}
+
+
+//-----------------------------------------------------------------------------
+// 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 CBaseEntity::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 CBaseEntity::SaveDataDescBlock( ISave &save, datamap_t *dmap )
+{
+ return save.WriteAll( this, dmap );
+}
+
+//-----------------------------------------------------------------------------
+// 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 CBaseEntity::Restore( IRestore &restore )
+{
+ // This is essential to getting the spatial partition info correct
+ CollisionProp()->DestroyPartitionHandle();
+
+ // loops through the data description list, restoring each data desc block in order
+ int status = RestoreDataDescBlock( restore, GetDataDescMap() );
+
+ // ---------------------------------------------------------------
+ // HACKHACK: We don't know the space of these vectors until now
+ // if they are worldspace, fix them up.
+ // ---------------------------------------------------------------
+ {
+ CGameSaveRestoreInfo *pGameInfo = restore.GetGameSaveRestoreInfo();
+ Vector parentSpaceOffset = pGameInfo->modelSpaceOffset;
+ if ( !GetParent() )
+ {
+ // parent is the world, so parent space is worldspace
+ // so update with the worldspace leveltransition transform
+ parentSpaceOffset += pGameInfo->GetLandmark();
+ }
+
+ // NOTE: Do *not* use GetAbsOrigin() here because it will
+ // try to recompute m_rgflCoordinateFrame!
+ MatrixSetColumn( m_vecAbsOrigin, 3, m_rgflCoordinateFrame );
+
+ m_vecOrigin += parentSpaceOffset;
+ }
+
+ // Gotta do this after the coordframe is set up as it depends on it.
+
+ // By definition, the surrounding bounds are dirty
+ // Also, twiddling with the flags here ensures it gets added to the KD tree dirty list
+ // (We don't want to use the saved version of this flag)
+ RemoveEFlags( EFL_DIRTY_SPATIAL_PARTITION );
+ CollisionProp()->MarkSurroundingBoundsDirty();
+
+ if ( edict() && GetModelIndex() != 0 && GetModelName() != NULL_STRING && restore.GetPrecacheMode() )
+ {
+ PrecacheModel( STRING( GetModelName() ) );
+
+ //Adrian: We should only need to do this after we precache. No point in setting the model again.
+ SetModelIndex( modelinfo->GetModelIndex( STRING(GetModelName() ) ) );
+ }
+
+ // Restablish ground entity
+ if ( m_hGroundEntity != NULL )
+ {
+ m_hGroundEntity->AddEntityToGroundList( this );
+ }
+
+ return status;
+}
+
+
+//-----------------------------------------------------------------------------
+// handler to do stuff before you are saved
+//-----------------------------------------------------------------------------
+void CBaseEntity::OnSave( IEntitySaveUtils *pUtils )
+{
+ // 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 CBaseEntity::OnRestore()
+{
+#if defined( PORTAL ) || defined( HL2_EPISODIC ) || defined ( HL2_DLL ) || defined( HL2_LOSTCOAST )
+ // We had a short period during the 2013 beta where the FL_* flags had a bogus value near the top, so detect
+ // these bad saves and just give up. Only saves from the short beta period should have been effected.
+ if ( GetFlags() & FL_FAKECLIENT )
+ {
+ char szMsg[256];
+ V_snprintf( szMsg, sizeof(szMsg), "\nInvalid save, unable to load. Please run \"map %s\" to restart this level manually\n\n", gpGlobals->mapname.ToCStr() );
+ Msg( "%s", szMsg );
+
+ engine->ServerCommand("wait;wait;disconnect;showconsole\n");
+ }
+#endif
+
+ SimThink_EntityChanged( this );
+
+ // touchlinks get recomputed
+ if ( IsEFlagSet( EFL_CHECK_UNTOUCH ) )
+ {
+ RemoveEFlags( EFL_CHECK_UNTOUCH );
+ SetCheckUntouch( true );
+ }
+
+ // disable touch functions while we recreate the touch links between entities
+ // NOTE: We don't do this on transitions, because we'd miss the OnStartTouch call!
+#if !defined(HL2_DLL) || ( defined(HL2_DLL) && defined(HL2_EPISODIC) )
+ CBaseEntity::sm_bDisableTouchFuncs = ( gpGlobals->eLoadType != MapLoad_Transition );
+ PhysicsTouchTriggers();
+ CBaseEntity::sm_bDisableTouchFuncs = false;
+#endif // HL2_EPISODIC
+
+ //Adrian: If I'm restoring with these fields it means I've become a client side ragdoll.
+ //Don't create another one, just wait until is my time of being removed.
+ if ( GetFlags() & FL_TRANSRAGDOLL )
+ {
+ m_nRenderFX = kRenderFxNone;
+ AddEffects( EF_NODRAW );
+ RemoveFlag( FL_DISSOLVING | FL_ONFIRE );
+ }
+
+ if ( m_pParent )
+ {
+ CBaseEntity *pChild = m_pParent->FirstMoveChild();
+ while ( pChild )
+ {
+ if ( pChild == this )
+ break;
+ pChild = pChild->NextMovePeer();
+ }
+ if ( pChild != this )
+ {
+#if _DEBUG
+ // generally this means you've got something marked FCAP_DONT_SAVE
+ // in a hierarchy. That's probably ok given this fixup, but the hierarhcy
+ // linked list is just saved/loaded in-place
+ Warning("Fixing up parent on %s\n", GetClassname() );
+#endif
+ // We only need to be back in the parent's list because we're already in the right place and with the right data
+ LinkChild( m_pParent, this );
+ }
+ }
+
+ // We're not save/loading the PVS dirty state. Assume everything is dirty after a restore
+ NetworkProp()->MarkPVSInformationDirty();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Recursively restores all the classes in an object, in reverse order (top down)
+// Output : int 0 on failure, 1 on success
+//-----------------------------------------------------------------------------
+int CBaseEntity::RestoreDataDescBlock( IRestore &restore, datamap_t *dmap )
+{
+ return restore.ReadAll( this, dmap );
+}
+
+//-----------------------------------------------------------------------------
+
+bool CBaseEntity::ShouldSavePhysics()
+{
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+
+#include "tier0/memdbgoff.h"
+
+//-----------------------------------------------------------------------------
+// CBaseEntity new/delete
+// allocates and frees memory for itself from the engine->
+// All fields in the object are all initialized to 0.
+//-----------------------------------------------------------------------------
+void *CBaseEntity::operator new( size_t stAllocateBlock )
+{
+ // call into engine to get memory
+ Assert( stAllocateBlock != 0 );
+ return engine->PvAllocEntPrivateData(stAllocateBlock);
+};
+
+void *CBaseEntity::operator new( size_t stAllocateBlock, int nBlockUse, const char *pFileName, int nLine )
+{
+ // call into engine to get memory
+ Assert( stAllocateBlock != 0 );
+ return engine->PvAllocEntPrivateData(stAllocateBlock);
+}
+
+void CBaseEntity::operator delete( void *pMem )
+{
+ // get the engine to free the memory
+ engine->FreeEntPrivateData( pMem );
+}
+
+#include "tier0/memdbgon.h"
+
+
+#ifdef _DEBUG
+void CBaseEntity::FunctionCheck( void *pFunction, const char *name )
+{
+#ifdef USES_SAVERESTORE
+ // Note, if you crash here and your class is using multiple inheritance, it is
+ // probably the case that CBaseEntity (or a descendant) is not the first
+ // class in your list of ancestors, which it must be.
+ if (pFunction && !UTIL_FunctionToName( GetDataDescMap(), (inputfunc_t *)pFunction ) )
+ {
+ Warning( "FUNCTION NOT IN TABLE!: %s:%s (%08lx)\n", STRING(m_iClassname), name, (unsigned long)pFunction );
+ Assert(0);
+ }
+#endif
+}
+#endif
+
+
+bool CBaseEntity::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace )
+{
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Perform hitbox test, returns true *if hitboxes were tested at all*!!
+//-----------------------------------------------------------------------------
+bool CBaseEntity::TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr )
+{
+ return false;
+}
+
+
+void CBaseEntity::SetOwnerEntity( CBaseEntity* pOwner )
+{
+ if ( m_hOwnerEntity.Get() != pOwner )
+ {
+ m_hOwnerEntity = pOwner;
+
+ CollisionRulesChanged();
+ }
+}
+
+void CBaseEntity::SetMoveType( MoveType_t val, MoveCollide_t moveCollide )
+{
+#ifdef _DEBUG
+ // Make sure the move type + move collide are compatible...
+ if ((val != MOVETYPE_FLY) && (val != MOVETYPE_FLYGRAVITY))
+ {
+ Assert( moveCollide == MOVECOLLIDE_DEFAULT );
+ }
+
+ if ( m_MoveType == MOVETYPE_VPHYSICS && val != m_MoveType )
+ {
+ if ( VPhysicsGetObject() && val != MOVETYPE_NONE )
+ {
+ // What am I supposed to do with the physics object if
+ // you're changing away from MOVETYPE_VPHYSICS without making the object
+ // shadow? This isn't likely to work, assert.
+ // You probably meant to call VPhysicsInitShadow() instead of VPhysicsInitNormal()!
+ Assert( VPhysicsGetObject()->GetShadowController() );
+ }
+ }
+#endif
+
+ if ( m_MoveType == val )
+ {
+ m_MoveCollide = moveCollide;
+ return;
+ }
+
+ // This is needed to the removal of MOVETYPE_FOLLOW:
+ // We can't transition from follow to a different movetype directly
+ // or the leaf code will break.
+ Assert( !IsEffectActive( EF_BONEMERGE ) );
+ m_MoveType = val;
+ m_MoveCollide = moveCollide;
+
+ CollisionRulesChanged();
+
+ switch( m_MoveType )
+ {
+ case MOVETYPE_WALK:
+ {
+ SetSimulatedEveryTick( true );
+ SetAnimatedEveryTick( true );
+ }
+ break;
+ case MOVETYPE_STEP:
+ {
+ // This will probably go away once I remove the cvar that controls the test code
+ SetSimulatedEveryTick( g_bTestMoveTypeStepSimulation ? true : false );
+ SetAnimatedEveryTick( false );
+ }
+ break;
+ case MOVETYPE_FLY:
+ case MOVETYPE_FLYGRAVITY:
+ {
+ // Initialize our water state, because these movetypes care about transitions in/out of water
+ UpdateWaterState();
+ }
+ break;
+ default:
+ {
+ SetSimulatedEveryTick( true );
+ SetAnimatedEveryTick( false );
+ }
+ }
+
+ // This will probably go away or be handled in a better way once I remove the cvar that controls the test code
+ CheckStepSimulationChanged();
+ CheckHasGamePhysicsSimulation();
+}
+
+void CBaseEntity::Spawn( void )
+{
+}
+
+
+CBaseEntity* CBaseEntity::Instance( const CBaseHandle &hEnt )
+{
+ return gEntList.GetBaseEntity( hEnt );
+}
+
+int CBaseEntity::GetTransmitState( void )
+{
+ edict_t *ed = edict();
+
+ if ( !ed )
+ return 0;
+
+ return ed->m_fStateFlags;
+}
+
+int CBaseEntity::SetTransmitState( int nFlag)
+{
+ edict_t *ed = edict();
+
+ if ( !ed )
+ return 0;
+
+ // clear current flags = check ShouldTransmit()
+ ed->ClearTransmitState();
+
+ int oldFlags = ed->m_fStateFlags;
+ ed->m_fStateFlags |= nFlag;
+
+ // Tell the engine (used for a network backdoor optimization).
+ if ( (oldFlags & FL_EDICT_DONTSEND) != (ed->m_fStateFlags & FL_EDICT_DONTSEND) )
+ engine->NotifyEdictFlagsChange( entindex() );
+
+ return ed->m_fStateFlags;
+}
+
+int CBaseEntity::UpdateTransmitState()
+{
+ // If you get this assert, you should be calling DispatchUpdateTransmitState
+ // instead of UpdateTransmitState.
+ Assert( g_nInsideDispatchUpdateTransmitState > 0 );
+
+ // If an object is the moveparent of something else, don't skip it just because it's marked EF_NODRAW or else
+ // the client won't have a proper origin for the child since the hierarchy won't be correctly transmitted down
+ if ( IsEffectActive( EF_NODRAW ) &&
+ !m_hMoveChild.Get() )
+ {
+ return SetTransmitState( FL_EDICT_DONTSEND );
+ }
+
+ if ( !IsEFlagSet( EFL_FORCE_CHECK_TRANSMIT ) )
+ {
+ if ( !GetModelIndex() || !GetModelName() )
+ {
+ return SetTransmitState( FL_EDICT_DONTSEND );
+ }
+ }
+
+ // Always send the world
+ if ( GetModelIndex() == 1 )
+ {
+ return SetTransmitState( FL_EDICT_ALWAYS );
+ }
+
+ if ( IsEFlagSet( EFL_IN_SKYBOX ) )
+ {
+ return SetTransmitState( FL_EDICT_ALWAYS );
+ }
+
+ // by default cull against PVS
+ return SetTransmitState( FL_EDICT_PVSCHECK );
+}
+
+int CBaseEntity::DispatchUpdateTransmitState()
+{
+ edict_t *ed = edict();
+ if ( m_nTransmitStateOwnedCounter != 0 )
+ return ed ? ed->m_fStateFlags : 0;
+
+ g_nInsideDispatchUpdateTransmitState++;
+ int ret = UpdateTransmitState();
+ g_nInsideDispatchUpdateTransmitState--;
+
+ return ret;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Note, an entity can override the send table ( e.g., to send less data or to send minimal data for
+// objects ( prob. players ) that are not in the pvs.
+// Input : **ppSendTable -
+// *recipient -
+// *pvs -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+int CBaseEntity::ShouldTransmit( const CCheckTransmitInfo *pInfo )
+{
+ int fFlags = DispatchUpdateTransmitState();
+
+ if ( fFlags & FL_EDICT_PVSCHECK )
+ {
+ return FL_EDICT_PVSCHECK;
+ }
+ else if ( fFlags & FL_EDICT_ALWAYS )
+ {
+ return FL_EDICT_ALWAYS;
+ }
+ else if ( fFlags & FL_EDICT_DONTSEND )
+ {
+ return FL_EDICT_DONTSEND;
+ }
+
+// if ( IsToolRecording() )
+// {
+// return FL_EDICT_ALWAYS;
+// }
+
+ CBaseEntity *pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt );
+
+ Assert( pRecipientEntity->IsPlayer() );
+
+ CBasePlayer *pRecipientPlayer = static_cast<CBasePlayer*>( pRecipientEntity );
+
+
+ // FIXME: Refactor once notion of "team" is moved into HL2 code
+ // Team rules may tell us that we should
+ if ( pRecipientPlayer->GetTeam() )
+ {
+ if ( pRecipientPlayer->GetTeam()->ShouldTransmitToPlayer( pRecipientPlayer, this ))
+ return FL_EDICT_ALWAYS;
+ }
+
+
+/*#ifdef INVASION_DLL
+ // Check test network vis distance stuff. Eventually network LOD will do this.
+ float flTestDistSqr = pRecipientEntity->GetAbsOrigin().DistToSqr( WorldSpaceCenter() );
+ if ( flTestDistSqr > sv_netvisdist.GetFloat() * sv_netvisdist.GetFloat() )
+ return TRANSMIT_NO; // TODO doesn't work with HLTV
+#endif*/
+
+ // by default do a PVS check
+
+ return FL_EDICT_PVSCHECK;
+}
+
+
+//-----------------------------------------------------------------------------
+// Rules about which entities need to transmit along with me
+//-----------------------------------------------------------------------------
+void CBaseEntity::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways )
+{
+ int index = entindex();
+
+ // Are we already marked for transmission?
+ if ( pInfo->m_pTransmitEdict->Get( index ) )
+ return;
+
+ CServerNetworkProperty *pNetworkParent = NetworkProp()->GetNetworkParent();
+
+ pInfo->m_pTransmitEdict->Set( index );
+
+ // HLTV/Replay need to know if this entity is culled by PVS limits
+ if ( pInfo->m_pTransmitAlways )
+ {
+ // in HLTV/Replay mode always transmit entitys with move-parents
+ // HLTV/Replay can't resolve the mode-parents relationships
+ if ( bAlways || pNetworkParent )
+ {
+ // tell HLTV/Replay that this entity is always transmitted
+ pInfo->m_pTransmitAlways->Set( index );
+ }
+ else
+ {
+ // HLTV/Replay will PVS cull this entity, so update the
+ // node/cluster infos if necessary
+ m_Network.RecomputePVSInformation();
+ }
+ }
+
+ // Force our aiment and move parent to be sent.
+ if ( pNetworkParent )
+ {
+ CBaseEntity *pMoveParent = pNetworkParent->GetBaseEntity();
+ pMoveParent->SetTransmit( pInfo, bAlways );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns which skybox the entity is in
+//-----------------------------------------------------------------------------
+CSkyCamera *CBaseEntity::GetEntitySkybox()
+{
+ int area = engine->GetArea( WorldSpaceCenter() );
+
+ CSkyCamera *pCur = GetSkyCameraList();
+ while ( pCur )
+ {
+ if ( engine->CheckAreasConnected( area, pCur->m_skyboxData.area ) )
+ return pCur;
+
+ pCur = pCur->m_pNext;
+ }
+
+ return NULL;
+}
+
+bool CBaseEntity::DetectInSkybox()
+{
+ if ( GetEntitySkybox() != NULL )
+ {
+ AddEFlags( EFL_IN_SKYBOX );
+ return true;
+ }
+
+ RemoveEFlags( EFL_IN_SKYBOX );
+ return false;
+}
+
+
+//------------------------------------------------------------------------------
+// Computes a world-aligned bounding box that surrounds everything in the entity
+//------------------------------------------------------------------------------
+void CBaseEntity::ComputeWorldSpaceSurroundingBox( Vector *pMins, Vector *pMaxs )
+{
+ // Should never get here.. only use USE_GAME_CODE with bounding boxes
+ // if you have an implementation for this method
+ Assert( 0 );
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose : If name exists returns name, otherwise returns classname
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+const char *CBaseEntity::GetDebugName(void)
+{
+ if ( this == NULL )
+ return "<<null>>";
+
+ if ( m_iName != NULL_STRING )
+ {
+ return STRING(m_iName);
+ }
+ else
+ {
+ return STRING(m_iClassname);
+ }
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CBaseEntity::DrawInputOverlay(const char *szInputName, CBaseEntity *pCaller, variant_t Value)
+{
+ char bigstring[1024];
+ if ( Value.FieldType() == FIELD_INTEGER )
+ {
+ Q_snprintf( bigstring,sizeof(bigstring), "%3.1f (%s,%d) <-- (%s)\n", gpGlobals->curtime, szInputName, Value.Int(), pCaller ? pCaller->GetDebugName() : NULL);
+ }
+ else if ( Value.FieldType() == FIELD_STRING )
+ {
+ Q_snprintf( bigstring,sizeof(bigstring), "%3.1f (%s,%s) <-- (%s)\n", gpGlobals->curtime, szInputName, Value.String(), pCaller ? pCaller->GetDebugName() : NULL);
+ }
+ else
+ {
+ Q_snprintf( bigstring,sizeof(bigstring), "%3.1f (%s) <-- (%s)\n", gpGlobals->curtime, szInputName, pCaller ? pCaller->GetDebugName() : NULL);
+ }
+ AddTimedOverlay(bigstring, 10.0);
+
+ if ( Value.FieldType() == FIELD_INTEGER )
+ {
+ DevMsg( 2, "input: (%s,%d) -> (%s,%s), from (%s)\n", szInputName, Value.Int(), STRING(m_iClassname), GetDebugName(), pCaller ? pCaller->GetDebugName() : NULL);
+ }
+ else if ( Value.FieldType() == FIELD_STRING )
+ {
+ DevMsg( 2, "input: (%s,%s) -> (%s,%s), from (%s)\n", szInputName, Value.String(), STRING(m_iClassname), GetDebugName(), pCaller ? pCaller->GetDebugName() : NULL);
+ }
+ else
+ DevMsg( 2, "input: (%s) -> (%s,%s), from (%s)\n", szInputName, STRING(m_iClassname), GetDebugName(), pCaller ? pCaller->GetDebugName() : NULL);
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CBaseEntity::DrawOutputOverlay(CEventAction *ev)
+{
+ // Print to entity
+ char bigstring[1024];
+ if ( ev->m_flDelay )
+ {
+ Q_snprintf( bigstring,sizeof(bigstring), "%3.1f (%s) --> (%s),%.1f) \n", gpGlobals->curtime, STRING(ev->m_iTargetInput), STRING(ev->m_iTarget), ev->m_flDelay);
+ }
+ else
+ {
+ Q_snprintf( bigstring,sizeof(bigstring), "%3.1f (%s) --> (%s)\n", gpGlobals->curtime, STRING(ev->m_iTargetInput), STRING(ev->m_iTarget));
+ }
+ AddTimedOverlay(bigstring, 10.0);
+
+ // Now print to the console
+ if ( ev->m_flDelay )
+ {
+ DevMsg( 2, "output: (%s,%s) -> (%s,%s,%.1f)\n", STRING(m_iClassname), GetDebugName(), STRING(ev->m_iTarget), STRING(ev->m_iTargetInput), ev->m_flDelay );
+ }
+ else
+ {
+ DevMsg( 2, "output: (%s,%s) -> (%s,%s)\n", STRING(m_iClassname), GetDebugName(), STRING(ev->m_iTarget), STRING(ev->m_iTargetInput) );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Entity events... these are events targetted to a particular entity
+// Each event defines its own well-defined event data structure
+//-----------------------------------------------------------------------------
+void CBaseEntity::OnEntityEvent( EntityEvent_t event, void *pEventData )
+{
+ switch( event )
+ {
+ case ENTITY_EVENT_WATER_TOUCH:
+ {
+ int nContents = (int)pEventData;
+ if ( !nContents || (nContents & CONTENTS_WATER) )
+ {
+ ++m_nWaterTouch;
+ }
+ if ( nContents & CONTENTS_SLIME )
+ {
+ ++m_nSlimeTouch;
+ }
+ }
+ break;
+
+ case ENTITY_EVENT_WATER_UNTOUCH:
+ {
+ int nContents = (int)pEventData;
+ if ( !nContents || (nContents & CONTENTS_WATER) )
+ {
+ --m_nWaterTouch;
+ }
+ if ( nContents & CONTENTS_SLIME )
+ {
+ --m_nSlimeTouch;
+ }
+ }
+ break;
+
+ default:
+ return;
+ }
+
+ // Only do this for vphysics objects
+ if ( GetMoveType() != MOVETYPE_VPHYSICS )
+ return;
+
+ int nNewContents = 0;
+ if ( m_nWaterTouch > 0 )
+ {
+ nNewContents |= CONTENTS_WATER;
+ }
+
+ if ( m_nSlimeTouch > 0 )
+ {
+ nNewContents |= CONTENTS_SLIME;
+ }
+
+ if (( nNewContents & MASK_WATER ) == 0)
+ {
+ SetWaterLevel( 0 );
+ SetWaterType( CONTENTS_EMPTY );
+ return;
+ }
+
+ SetWaterLevel( 1 );
+ SetWaterType( nNewContents );
+}
+
+
+ConVar ent_messages_draw( "ent_messages_draw", "0", FCVAR_CHEAT, "Visualizes all entity input/output activity." );
+
+
+//-----------------------------------------------------------------------------
+// Purpose: calls the appropriate message mapped function in the entity according
+// to the fired action.
+// Input : char *szInputName - input destination
+// *pActivator - entity which initiated this sequence of actions
+// *pCaller - entity from which this event is sent
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CBaseEntity::AcceptInput( const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t Value, int outputID )
+{
+ if ( ent_messages_draw.GetBool() )
+ {
+ if ( pCaller != NULL )
+ {
+ NDebugOverlay::Line( pCaller->GetAbsOrigin(), GetAbsOrigin(), 255, 255, 255, false, 3 );
+ NDebugOverlay::Box( pCaller->GetAbsOrigin(), Vector(-4, -4, -4), Vector(4, 4, 4), 255, 0, 0, 0, 3 );
+ }
+
+ NDebugOverlay::Text( GetAbsOrigin(), szInputName, false, 3 );
+ NDebugOverlay::Box( GetAbsOrigin(), Vector(-4, -4, -4), Vector(4, 4, 4), 0, 255, 0, 0, 3 );
+ }
+
+ // loop through the data description list, restoring each data desc block
+ for ( datamap_t *dmap = GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap )
+ {
+ // search through all the actions in the data description, looking for a match
+ for ( int i = 0; i < dmap->dataNumFields; i++ )
+ {
+ if ( dmap->dataDesc[i].flags & FTYPEDESC_INPUT )
+ {
+ if ( !Q_stricmp(dmap->dataDesc[i].externalName, szInputName) )
+ {
+ // found a match
+
+ char szBuffer[256];
+ // mapper debug message
+ if (pCaller != NULL)
+ {
+ Q_snprintf( szBuffer, sizeof(szBuffer), "(%0.2f) input %s: %s.%s(%s)\n", gpGlobals->curtime, STRING(pCaller->m_iName), GetDebugName(), szInputName, Value.String() );
+ }
+ else
+ {
+ Q_snprintf( szBuffer, sizeof(szBuffer), "(%0.2f) input <NULL>: %s.%s(%s)\n", gpGlobals->curtime, GetDebugName(), szInputName, Value.String() );
+ }
+ DevMsg( 2, "%s", szBuffer );
+ ADD_DEBUG_HISTORY( HISTORY_ENTITY_IO, szBuffer );
+
+ if (m_debugOverlays & OVERLAY_MESSAGE_BIT)
+ {
+ DrawInputOverlay(szInputName,pCaller,Value);
+ }
+
+ // convert the value if necessary
+ if ( Value.FieldType() != dmap->dataDesc[i].fieldType )
+ {
+ if ( !(Value.FieldType() == FIELD_VOID && dmap->dataDesc[i].fieldType == FIELD_STRING) ) // allow empty strings
+ {
+ if ( !Value.Convert( (fieldtype_t)dmap->dataDesc[i].fieldType ) )
+ {
+ // bad conversion
+ Warning( "!! ERROR: bad input/output link:\n!! %s(%s,%s) doesn't match type from %s(%s)\n",
+ STRING(m_iClassname), GetDebugName(), szInputName,
+ ( pCaller != NULL ) ? STRING(pCaller->m_iClassname) : "<null>",
+ ( pCaller != NULL ) ? STRING(pCaller->m_iName) : "<null>" );
+ return false;
+ }
+ }
+ }
+
+ // call the input handler, or if there is none just set the value
+ inputfunc_t pfnInput = dmap->dataDesc[i].inputFunc;
+
+ if ( pfnInput )
+ {
+ // Package the data into a struct for passing to the input handler.
+ inputdata_t data;
+ data.pActivator = pActivator;
+ data.pCaller = pCaller;
+ data.value = Value;
+ data.nOutputID = outputID;
+
+ (this->*pfnInput)( data );
+ }
+ else if ( dmap->dataDesc[i].flags & FTYPEDESC_KEY )
+ {
+ // set the value directly
+ Value.SetOther( ((char*)this) + dmap->dataDesc[i].fieldOffset[ TD_OFFSET_NORMAL ]);
+
+ // TODO: if this becomes evil and causes too many full entity updates, then we should make
+ // a macro like this:
+ //
+ // define MAKE_INPUTVAR(x) void Note##x##Modified() { x.GetForModify(); }
+ //
+ // Then the datadesc points at that function and we call it here. The only pain is to add
+ // that function for all the DEFINE_INPUT calls.
+ NetworkStateChanged();
+ }
+
+ return true;
+ }
+ }
+ }
+ }
+
+ DevMsg( 2, "unhandled input: (%s) -> (%s,%s)\n", szInputName, STRING(m_iClassname), GetDebugName()/*,", from (%s,%s)" STRING(pCaller->m_iClassname), STRING(pCaller->m_iName)*/ );
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Input handler for the entity alpha.
+// Input : nAlpha - Alpha value (0 - 255).
+//-----------------------------------------------------------------------------
+void CBaseEntity::InputAlpha( inputdata_t &inputdata )
+{
+ SetRenderColorA( clamp( inputdata.value.Int(), 0, 255 ) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Activate alternative sorting
+//-----------------------------------------------------------------------------
+void CBaseEntity::InputAlternativeSorting( inputdata_t &inputdata )
+{
+ m_bAlternateSorting = inputdata.value.Bool();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Input handler for the entity color. Ignores alpha since that is handled
+// by a separate input handler.
+// Input : Color32 new value for color (alpha is ignored).
+//-----------------------------------------------------------------------------
+void CBaseEntity::InputColor( inputdata_t &inputdata )
+{
+ color32 clr = inputdata.value.Color32();
+
+ SetRenderColor( clr.r, clr.g, clr.b );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Called whenever the entity is 'Used'. This can be when a player hits
+// use, or when an entity targets it without an output name (legacy entities)
+//-----------------------------------------------------------------------------
+void CBaseEntity::InputUse( inputdata_t &inputdata )
+{
+ Use( inputdata.pActivator, inputdata.pCaller, (USE_TYPE)inputdata.nOutputID, 0 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Reads an output variable, by string name, from an entity
+// Input : char *varName - the string name of the variable
+// variant_t *var - the value is stored here
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CBaseEntity::ReadKeyField( const char *varName, variant_t *var )
+{
+ if ( !varName )
+ return false;
+
+ // loop through the data description list, restoring each data desc block
+ for ( datamap_t *dmap = GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap )
+ {
+ // search through all the readable fields in the data description, looking for a match
+ for ( int i = 0; i < dmap->dataNumFields; i++ )
+ {
+ if ( dmap->dataDesc[i].flags & (FTYPEDESC_OUTPUT | FTYPEDESC_KEY) )
+ {
+ if ( !Q_stricmp(dmap->dataDesc[i].externalName, varName) )
+ {
+ var->Set( dmap->dataDesc[i].fieldType, ((char*)this) + dmap->dataDesc[i].fieldOffset[ TD_OFFSET_NORMAL ] );
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the damage filter on the object
+//-----------------------------------------------------------------------------
+void CBaseEntity::InputEnableDamageForces( inputdata_t &inputdata )
+{
+ RemoveEFlags( EFL_NO_DAMAGE_FORCES );
+}
+
+void CBaseEntity::InputDisableDamageForces( inputdata_t &inputdata )
+{
+ AddEFlags( EFL_NO_DAMAGE_FORCES );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the damage filter on the object
+//-----------------------------------------------------------------------------
+void CBaseEntity::InputSetDamageFilter( inputdata_t &inputdata )
+{
+ // Get a handle to my damage filter entity if there is one.
+ m_iszDamageFilterName = inputdata.value.StringID();
+ if ( m_iszDamageFilterName != NULL_STRING )
+ {
+ m_hDamageFilter = gEntList.FindEntityByName( NULL, m_iszDamageFilterName );
+ }
+ else
+ {
+ m_hDamageFilter = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Dispatch effects on this entity
+//-----------------------------------------------------------------------------
+void CBaseEntity::InputDispatchEffect( inputdata_t &inputdata )
+{
+ const char *sEffect = inputdata.value.String();
+ if ( sEffect && sEffect[0] )
+ {
+ CEffectData data;
+ GetInputDispatchEffectPosition( sEffect, data.m_vOrigin, data.m_vAngles );
+ AngleVectors( data.m_vAngles, &data.m_vNormal );
+ data.m_vStart = data.m_vOrigin;
+ data.m_nEntIndex = entindex();
+
+ // Clip off leading attachment point numbers
+ while ( sEffect[0] >= '0' && sEffect[0] <= '9' )
+ {
+ sEffect++;
+ }
+ DispatchEffect( sEffect, data );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the origin at which to play an inputted dispatcheffect
+//-----------------------------------------------------------------------------
+void CBaseEntity::GetInputDispatchEffectPosition( const char *sInputString, Vector &pOrigin, QAngle &pAngles )
+{
+ pOrigin = GetAbsOrigin();
+ pAngles = GetAbsAngles();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Marks the entity for deletion
+//-----------------------------------------------------------------------------
+void CBaseEntity::InputKill( inputdata_t &inputdata )
+{
+ // tell owner ( if any ) that we're dead.This is mostly for NPCMaker functionality.
+ CBaseEntity *pOwner = GetOwnerEntity();
+ if ( pOwner )
+ {
+ pOwner->DeathNotice( this );
+ SetOwnerEntity( NULL );
+ }
+
+ UTIL_Remove( this );
+}
+
+void CBaseEntity::InputKillHierarchy( inputdata_t &inputdata )
+{
+ CBaseEntity *pChild, *pNext;
+ for ( pChild = FirstMoveChild(); pChild; pChild = pNext )
+ {
+ pNext = pChild->NextMovePeer();
+ pChild->InputKillHierarchy( inputdata );
+ }
+
+ // tell owner ( if any ) that we're dead. This is mostly for NPCMaker functionality.
+ CBaseEntity *pOwner = GetOwnerEntity();
+ if ( pOwner )
+ {
+ pOwner->DeathNotice( this );
+ SetOwnerEntity( NULL );
+ }
+
+ UTIL_Remove( this );
+}
+
+//------------------------------------------------------------------------------
+// Purpose: Input handler for changing this entity's movement parent.
+//------------------------------------------------------------------------------
+void CBaseEntity::InputSetParent( inputdata_t &inputdata )
+{
+ // If we had a parent attachment, clear it, because it's no longer valid.
+ if ( m_iParentAttachment )
+ {
+ m_iParentAttachment = 0;
+ }
+
+ SetParent( inputdata.value.StringID(), inputdata.pActivator );
+}
+
+//------------------------------------------------------------------------------
+// Purpose:
+//------------------------------------------------------------------------------
+void CBaseEntity::SetParentAttachment( const char *szInputName, const char *szAttachment, bool bMaintainOffset )
+{
+ // Must have a parent
+ if ( !m_pParent )
+ {
+ Warning("ERROR: Tried to %s for entity %s (%s), but it has no parent.\n", szInputName, GetClassname(), GetDebugName() );
+ return;
+ }
+
+ // Valid only on CBaseAnimating
+ CBaseAnimating *pAnimating = m_pParent->GetBaseAnimating();
+ if ( !pAnimating )
+ {
+ Warning("ERROR: Tried to %s for entity %s (%s), but its parent has no model.\n", szInputName, GetClassname(), GetDebugName() );
+ return;
+ }
+
+ // Lookup the attachment
+ int iAttachment = pAnimating->LookupAttachment( szAttachment );
+ if ( iAttachment <= 0 )
+ {
+ Warning("ERROR: Tried to %s for entity %s (%s), but it has no attachment named %s.\n", szInputName, GetClassname(), GetDebugName(), szAttachment );
+ return;
+ }
+
+ m_iParentAttachment = iAttachment;
+ SetParent( m_pParent, m_iParentAttachment );
+
+ // Now move myself directly onto the attachment point
+ SetMoveType( MOVETYPE_NONE );
+
+ if ( !bMaintainOffset )
+ {
+ SetLocalOrigin( vec3_origin );
+ SetLocalAngles( vec3_angle );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Input handler for changing this entity's movement parent's attachment point
+//-----------------------------------------------------------------------------
+void CBaseEntity::InputSetParentAttachment( inputdata_t &inputdata )
+{
+ SetParentAttachment( "SetParentAttachment", inputdata.value.String(), false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Input handler for changing this entity's movement parent's attachment point
+//-----------------------------------------------------------------------------
+void CBaseEntity::InputSetParentAttachmentMaintainOffset( inputdata_t &inputdata )
+{
+ SetParentAttachment( "SetParentAttachmentMaintainOffset", inputdata.value.String(), true );
+}
+
+//------------------------------------------------------------------------------
+// Purpose: Input handler for clearing this entity's movement parent.
+//------------------------------------------------------------------------------
+void CBaseEntity::InputClearParent( inputdata_t &inputdata )
+{
+ SetParent( NULL );
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose : Returns velcocity of base entity. If physically simulated gets
+// velocity from physics object
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CBaseEntity::GetVelocity(Vector *vVelocity, AngularImpulse *vAngVelocity)
+{
+ if (GetMoveType()==MOVETYPE_VPHYSICS && m_pPhysicsObject)
+ {
+ m_pPhysicsObject->GetVelocity(vVelocity,vAngVelocity);
+ }
+ else
+ {
+ if (vVelocity != NULL)
+ {
+ *vVelocity = GetAbsVelocity();
+ }
+ if (vAngVelocity != NULL)
+ {
+ QAngle tmp = GetLocalAngularVelocity();
+ QAngleToAngularImpulse( tmp, *vAngVelocity );
+ }
+ }
+}
+
+bool CBaseEntity::IsMoving()
+{
+ Vector velocity;
+ GetVelocity( &velocity, NULL );
+ return velocity != vec3_origin;
+}
+
+//-----------------------------------------------------------------------------
+// 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 CBaseEntity::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 );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the model, validates that it's of the appropriate type
+// Input : *szModelName -
+//-----------------------------------------------------------------------------
+void CBaseEntity::SetModel( const char *szModelName )
+{
+ int modelIndex = modelinfo->GetModelIndex( szModelName );
+ const model_t *model = modelinfo->GetModel( modelIndex );
+ if ( model && modelinfo->GetModelType( model ) != mod_brush )
+ {
+ Msg( "Setting CBaseEntity to non-brush model %s\n", szModelName );
+ }
+ UTIL_SetModel( this, szModelName );
+}
+
+//------------------------------------------------------------------------------
+
+CStudioHdr *CBaseEntity::OnNewModel()
+{
+ // Do nothing.
+ return NULL;
+}
+
+
+//================================================================================
+// TEAM HANDLING
+//================================================================================
+void CBaseEntity::InputSetTeam( inputdata_t &inputdata )
+{
+ ChangeTeam( inputdata.value.Int() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Put the entity in the specified team
+//-----------------------------------------------------------------------------
+void CBaseEntity::ChangeTeam( int iTeamNum )
+{
+ m_iTeamNum = iTeamNum;
+}
+
+//-----------------------------------------------------------------------------
+// Get the Team this entity is on
+//-----------------------------------------------------------------------------
+CTeam *CBaseEntity::GetTeam( void ) const
+{
+ return GetGlobalTeam( m_iTeamNum );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if these players are both in at least one team together
+//-----------------------------------------------------------------------------
+bool CBaseEntity::InSameTeam( CBaseEntity *pEntity ) const
+{
+ if ( !pEntity )
+ return false;
+
+ return ( pEntity->GetTeam() == GetTeam() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the string name of the players team
+//-----------------------------------------------------------------------------
+const char *CBaseEntity::TeamID( void ) const
+{
+ if ( GetTeam() == NULL )
+ return "";
+
+ return GetTeam()->GetName();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if the player is on the same team
+//-----------------------------------------------------------------------------
+bool CBaseEntity::IsInTeam( CTeam *pTeam ) const
+{
+ return ( GetTeam() == pTeam );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CBaseEntity::GetTeamNumber( void ) const
+{
+ return m_iTeamNum;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseEntity::IsInAnyTeam( void ) const
+{
+ return ( GetTeam() != NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the type of damage that this entity inflicts.
+//-----------------------------------------------------------------------------
+int CBaseEntity::GetDamageType() const
+{
+ return DMG_GENERIC;
+}
+
+
+//-----------------------------------------------------------------------------
+// process notification
+//-----------------------------------------------------------------------------
+
+void CBaseEntity::NotifySystemEvent( CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t ¶ms )
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Holds an entity's previous abs origin and angles at the time of
+// teleportation. Used for child & constrained entity fixup to prevent
+// lazy updates of abs origins and angles from messing things up.
+//-----------------------------------------------------------------------------
+struct TeleportListEntry_t
+{
+ CBaseEntity *pEntity;
+ Vector prevAbsOrigin;
+ QAngle prevAbsAngles;
+};
+
+
+static void TeleportEntity( CBaseEntity *pSourceEntity, TeleportListEntry_t &entry, const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity )
+{
+ CBaseEntity *pTeleport = entry.pEntity;
+ Vector prevOrigin = entry.prevAbsOrigin;
+ QAngle prevAngles = entry.prevAbsAngles;
+
+ int nSolidFlags = pTeleport->GetSolidFlags();
+ pTeleport->AddSolidFlags( FSOLID_NOT_SOLID );
+
+ // I'm teleporting myself
+ if ( pSourceEntity == pTeleport )
+ {
+ if ( newAngles )
+ {
+ pTeleport->SetLocalAngles( *newAngles );
+ if ( pTeleport->IsPlayer() )
+ {
+ CBasePlayer *pPlayer = (CBasePlayer *)pTeleport;
+ pPlayer->SnapEyeAngles( *newAngles );
+ }
+ }
+
+ if ( newVelocity )
+ {
+ pTeleport->SetAbsVelocity( *newVelocity );
+ pTeleport->SetBaseVelocity( vec3_origin );
+ }
+
+ if ( newPosition )
+ {
+ pTeleport->IncrementInterpolationFrame();
+ UTIL_SetOrigin( pTeleport, *newPosition );
+ }
+ }
+ else
+ {
+ // My parent is teleporting, just update my position & physics
+ pTeleport->CalcAbsolutePosition();
+ }
+ IPhysicsObject *pPhys = pTeleport->VPhysicsGetObject();
+ bool rotatePhysics = false;
+
+ // handle physics objects / shadows
+ if ( pPhys )
+ {
+ if ( newVelocity )
+ {
+ pPhys->SetVelocity( newVelocity, NULL );
+ }
+ const QAngle *rotAngles = &pTeleport->GetAbsAngles();
+ // don't rotate physics on players or bbox entities
+ if (pTeleport->IsPlayer() || pTeleport->GetSolid() == SOLID_BBOX )
+ {
+ rotAngles = &vec3_angle;
+ }
+ else
+ {
+ rotatePhysics = true;
+ }
+
+ pPhys->SetPosition( pTeleport->GetAbsOrigin(), *rotAngles, true );
+ }
+
+ g_pNotify->ReportTeleportEvent( pTeleport, prevOrigin, prevAngles, rotatePhysics );
+
+ pTeleport->SetSolidFlags( nSolidFlags );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Recurses an entity hierarchy and fills out a list of all entities
+// in the hierarchy with their current origins and angles.
+//
+// This list is necessary to keep lazy updates of abs origins and angles
+// from messing up our child/constrained entity fixup.
+//-----------------------------------------------------------------------------
+static void BuildTeleportList_r( CBaseEntity *pTeleport, CUtlVector<TeleportListEntry_t> &teleportList )
+{
+ TeleportListEntry_t entry;
+
+ entry.pEntity = pTeleport;
+ entry.prevAbsOrigin = pTeleport->GetAbsOrigin();
+ entry.prevAbsAngles = pTeleport->GetAbsAngles();
+
+ teleportList.AddToTail( entry );
+
+ CBaseEntity *pList = pTeleport->FirstMoveChild();
+ while ( pList )
+ {
+ BuildTeleportList_r( pList, teleportList );
+ pList = pList->NextMovePeer();
+ }
+}
+
+
+static CUtlVector<CBaseEntity *> g_TeleportStack;
+void CBaseEntity::Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity )
+{
+ if ( g_TeleportStack.Find( this ) >= 0 )
+ return;
+ int index = g_TeleportStack.AddToTail( this );
+
+ CUtlVector<TeleportListEntry_t> teleportList;
+ BuildTeleportList_r( this, teleportList );
+
+ int i;
+ for ( i = 0; i < teleportList.Count(); i++)
+ {
+ TeleportEntity( this, teleportList[i], newPosition, newAngles, newVelocity );
+ }
+
+ for (i = 0; i < teleportList.Count(); i++)
+ {
+ teleportList[i].pEntity->CollisionRulesChanged();
+ }
+
+ Assert( g_TeleportStack[index] == this );
+ g_TeleportStack.FastRemove( index );
+
+ // FIXME: add an initializer function to StepSimulationData
+ StepSimulationData *step = ( StepSimulationData * )GetDataObject( STEPSIMULATION );
+ if (step)
+ {
+ Q_memset( step, 0, sizeof( *step ) );
+ }
+}
+
+// Stuff implemented for weapon prediction code
+void CBaseEntity::SetSize( const Vector &vecMin, const Vector &vecMax )
+{
+ UTIL_SetSize( this, vecMin, vecMax );
+}
+
+CStudioHdr *ModelSoundsCache_LoadModel( const char *filename )
+{
+ // Load the file
+ int idx = engine->PrecacheModel( filename, true );
+ if ( idx != -1 )
+ {
+ model_t *mdl = (model_t *)modelinfo->GetModel( idx );
+ if ( mdl )
+ {
+ CStudioHdr *studioHdr = new CStudioHdr( modelinfo->GetStudiomodel( mdl ), mdlcache );
+ if ( studioHdr->IsValid() )
+ {
+ return studioHdr;
+ }
+ }
+ }
+ return NULL;
+}
+
+void ModelSoundsCache_FinishModel( CStudioHdr *hdr )
+{
+ Assert( hdr );
+ delete hdr;
+}
+
+void ModelSoundsCache_PrecacheScriptSound( const char *soundname )
+{
+ CBaseEntity::PrecacheScriptSound( soundname );
+}
+
+static CUtlCachedFileData< CModelSoundsCache > g_ModelSoundsCache( "modelsounds.cache", MODELSOUNDSCACHE_VERSION, 0, UTL_CACHED_FILE_USE_FILESIZE, false );
+
+void ClearModelSoundsCache()
+{
+ if ( IsX360() )
+ {
+ return;
+ }
+
+ g_ModelSoundsCache.Reload();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool ModelSoundsCacheInit()
+{
+ if ( IsX360() )
+ {
+ return true;
+ }
+
+ return g_ModelSoundsCache.Init();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void ModelSoundsCacheShutdown()
+{
+ if ( IsX360() )
+ {
+ return;
+ }
+
+ g_ModelSoundsCache.Shutdown();
+}
+
+static CUtlSymbolTable g_ModelSoundsSymbolHelper( 0, 32, true );
+class CModelSoundsCacheSaver: public CAutoGameSystem
+{
+public:
+ CModelSoundsCacheSaver( const char *name ) : CAutoGameSystem( name )
+ {
+ }
+ virtual void LevelInitPostEntity()
+ {
+ if ( IsX360() )
+ {
+ return;
+ }
+
+ if ( g_ModelSoundsCache.IsDirty() )
+ {
+ g_ModelSoundsCache.Save();
+ }
+ }
+ virtual void LevelShutdownPostEntity()
+ {
+ if ( IsX360() )
+ {
+ // Unforunate that this table must persist through duration of level.
+ // It is the common case that PrecacheModel() still gets called (and needs this table),
+ // after LevelInitPostEntity, as PrecacheModel() redundantly precaches.
+ g_ModelSoundsSymbolHelper.RemoveAll();
+ return;
+ }
+
+ if ( g_ModelSoundsCache.IsDirty() )
+ {
+ g_ModelSoundsCache.Save();
+ }
+ }
+};
+
+static CModelSoundsCacheSaver g_ModelSoundsCacheSaver( "CModelSoundsCacheSaver" );
+
+//#define WATCHACCESS
+#if defined( WATCHACCESS )
+
+static bool g_bWatching = true;
+
+void ModelLogFunc( const char *fileName, const char *accessType )
+{
+ if ( g_bWatching && !CBaseEntity::IsPrecacheAllowed() )
+ {
+ if ( Q_stristr( fileName, ".vcd" ) )
+ {
+ Msg( "%s\n", fileName );
+ }
+ }
+}
+
+class CWatchForModelAccess: public CAutoGameSystem
+{
+public:
+ virtual bool Init()
+ {
+ filesystem->AddLoggingFunc(&ModelLogFunc);
+ return true;
+ }
+
+ virtual void Shutdown()
+ {
+ filesystem->RemoveLoggingFunc(&ModelLogFunc);
+ }
+
+};
+
+static CWatchForModelAccess g_WatchForModels;
+
+#endif
+
+// HACK: This must match the #define in cl_animevent.h in the client .dll code!!!
+#define CL_EVENT_SOUND 5004
+#define CL_EVENT_FOOTSTEP_LEFT 6004
+#define CL_EVENT_FOOTSTEP_RIGHT 6005
+#define CL_EVENT_MFOOTSTEP_LEFT 6006
+#define CL_EVENT_MFOOTSTEP_RIGHT 6007
+
+//-----------------------------------------------------------------------------
+// Precache model sound. Requires a local symbol table to prevent
+// a very expensive call to PrecacheScriptSound().
+//-----------------------------------------------------------------------------
+void CBaseEntity::PrecacheSoundHelper( const char *pName )
+{
+ if ( !IsX360() )
+ {
+ // 360 only
+ Assert( 0 );
+ return;
+ }
+
+ if ( !pName || !pName[0] )
+ {
+ return;
+ }
+
+ if ( UTL_INVAL_SYMBOL == g_ModelSoundsSymbolHelper.Find( pName ) )
+ {
+ g_ModelSoundsSymbolHelper.AddString( pName );
+
+ // very expensive, only call when required
+ PrecacheScriptSound( pName );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Precache model components
+//-----------------------------------------------------------------------------
+void CBaseEntity::PrecacheModelComponents( int nModelIndex )
+{
+
+ model_t *pModel = (model_t *)modelinfo->GetModel( nModelIndex );
+ if ( !pModel || modelinfo->GetModelType( pModel ) != mod_studio )
+ {
+ return;
+ }
+
+ // sounds
+ if ( IsPC() )
+ {
+ const char *name = modelinfo->GetModelName( pModel );
+ if ( !g_ModelSoundsCache.EntryExists( name ) )
+ {
+ char extension[ 8 ];
+ Q_ExtractFileExtension( name, extension, sizeof( extension ) );
+
+ if ( Q_stristr( extension, "mdl" ) )
+ {
+ DevMsg( 2, "Late precache of %s, need to rebuild modelsounds.cache\n", name );
+ }
+ else
+ {
+ if ( !extension[ 0 ] )
+ {
+ Warning( "Precache of %s ambigious (no extension specified)\n", name );
+ }
+ else
+ {
+ Warning( "Late precache of %s (file missing?)\n", name );
+ }
+ return;
+ }
+ }
+
+ CModelSoundsCache *entry = g_ModelSoundsCache.Get( name );
+ Assert( entry );
+ if ( entry )
+ {
+ entry->PrecacheSoundList();
+ }
+ }
+
+ // particles
+ {
+ // Check keyvalues for auto-emitting particles
+ KeyValues *pModelKeyValues = new KeyValues("");
+ KeyValues::AutoDelete autodelete_pModelKeyValues( pModelKeyValues );
+ if ( pModelKeyValues->LoadFromBuffer( modelinfo->GetModelName( pModel ), modelinfo->GetModelKeyValueText( pModel ) ) )
+ {
+ KeyValues *pParticleEffects = pModelKeyValues->FindKey("Particles");
+ if ( pParticleEffects )
+ {
+ // Start grabbing the sounds and slotting them in
+ for ( KeyValues *pSingleEffect = pParticleEffects->GetFirstSubKey(); pSingleEffect; pSingleEffect = pSingleEffect->GetNextKey() )
+ {
+ const char *pParticleEffectName = pSingleEffect->GetString( "name", "" );
+ PrecacheParticleSystem( pParticleEffectName );
+ }
+ }
+ }
+ }
+
+ // model anim event owned components
+ {
+ // Check animevents for particle events
+ CStudioHdr studioHdr( modelinfo->GetStudiomodel( pModel ), mdlcache );
+ if ( studioHdr.IsValid() )
+ {
+ // force animation event resolution!!!
+ VerifySequenceIndex( &studioHdr );
+
+ int nSeqCount = studioHdr.GetNumSeq();
+ for ( int i = 0; i < nSeqCount; ++i )
+ {
+ mstudioseqdesc_t &seq = studioHdr.pSeqdesc( i );
+ int nEventCount = seq.numevents;
+ for ( int j = 0; j < nEventCount; ++j )
+ {
+ mstudioevent_t *pEvent = seq.pEvent( j );
+
+ if ( !( pEvent->type & AE_TYPE_NEWEVENTSYSTEM ) || ( pEvent->type & AE_TYPE_CLIENT ) )
+ {
+ if ( pEvent->event == AE_CL_CREATE_PARTICLE_EFFECT )
+ {
+ char token[256];
+ const char *pOptions = pEvent->pszOptions();
+ nexttoken( token, pOptions, ' ' );
+ if ( token )
+ {
+ PrecacheParticleSystem( token );
+ }
+ continue;
+ }
+ }
+
+ // 360 precaches the model sounds now at init time, the cost is now ~250 msecs worst case.
+ // The disk based solution was not needed. Now at runtime partly due to already crawling the sequences
+ // for the particles and the expensive part was redundant PrecacheScriptSound(), which is now prevented
+ // by a local symbol table.
+ if ( IsX360() )
+ {
+ switch ( pEvent->event )
+ {
+ default:
+ {
+ if ( ( pEvent->type & AE_TYPE_NEWEVENTSYSTEM ) && ( pEvent->event == AE_SV_PLAYSOUND ) )
+ {
+ PrecacheSoundHelper( pEvent->pszOptions() );
+ }
+ }
+ break;
+ case CL_EVENT_FOOTSTEP_LEFT:
+ case CL_EVENT_FOOTSTEP_RIGHT:
+ {
+ char soundname[256];
+ char const *options = pEvent->pszOptions();
+ if ( !options || !options[0] )
+ {
+ options = "NPC_CombineS";
+ }
+
+ Q_snprintf( soundname, sizeof( soundname ), "%s.RunFootstepLeft", options );
+ PrecacheSoundHelper( soundname );
+ Q_snprintf( soundname, sizeof( soundname ), "%s.RunFootstepRight", options );
+ PrecacheSoundHelper( soundname );
+ Q_snprintf( soundname, sizeof( soundname ), "%s.FootstepLeft", options );
+ PrecacheSoundHelper( soundname );
+ Q_snprintf( soundname, sizeof( soundname ), "%s.FootstepRight", options );
+ PrecacheSoundHelper( soundname );
+ }
+ break;
+ case AE_CL_PLAYSOUND:
+ {
+ if ( !( pEvent->type & AE_TYPE_CLIENT ) )
+ break;
+
+ if ( pEvent->pszOptions()[0] )
+ {
+ PrecacheSoundHelper( pEvent->pszOptions() );
+ }
+ else
+ {
+ Warning( "-- Error --: empty soundname, .qc error on AE_CL_PLAYSOUND in model %s, sequence %s, animevent # %i\n",
+ studioHdr.GetRenderHdr()->pszName(), seq.pszLabel(), j+1 );
+ }
+ }
+ break;
+ case CL_EVENT_SOUND:
+ case SCRIPT_EVENT_SOUND:
+ case SCRIPT_EVENT_SOUND_VOICE:
+ {
+ PrecacheSoundHelper( pEvent->pszOptions() );
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Add model to level precache list
+// Input : *name - model name
+// Output : int -- model index for model
+//-----------------------------------------------------------------------------
+int CBaseEntity::PrecacheModel( const char *name, bool bPreload )
+{
+ if ( !name || !*name )
+ {
+ Msg( "Attempting to precache model, but model name is NULL\n");
+ return -1;
+ }
+
+ // Warn on out of order precache
+ if ( !CBaseEntity::IsPrecacheAllowed() )
+ {
+ if ( !engine->IsModelPrecached( name ) )
+ {
+ Assert( !"CBaseEntity::PrecacheModel: too late" );
+ Warning( "Late precache of %s\n", name );
+ }
+ }
+#if defined( WATCHACCESS )
+ else
+ {
+ g_bWatching = false;
+ }
+#endif
+
+ int idx = engine->PrecacheModel( name, bPreload );
+ if ( idx != -1 )
+ {
+ PrecacheModelComponents( idx );
+ }
+
+#if defined( WATCHACCESS )
+ g_bWatching = true;
+#endif
+
+ return idx;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseEntity::Remove( )
+{
+ UTIL_Remove( this );
+}
+
+// Entity degugging console commands
+extern CBaseEntity *FindPickerEntity( CBasePlayer *pPlayer );
+extern void SetDebugBits( CBasePlayer* pPlayer, const char *name, int bit );
+extern CBaseEntity *GetNextCommandEntity( CBasePlayer *pPlayer, const char *name, CBaseEntity *ent );
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void ConsoleFireTargets( CBasePlayer *pPlayer, const char *name)
+{
+ // If no name was given use the picker
+ if (FStrEq(name,""))
+ {
+ CBaseEntity *pEntity = FindPickerEntity( pPlayer );
+ if ( pEntity && !pEntity->IsMarkedForDeletion())
+ {
+ Msg( "[%03d] Found: %s, firing\n", gpGlobals->tickcount%1000, pEntity->GetDebugName());
+ pEntity->Use( pPlayer, pPlayer, USE_TOGGLE, 0 );
+ return;
+ }
+ }
+ // Otherwise use name or classname
+ FireTargets( name, pPlayer, pPlayer, USE_TOGGLE, 0 );
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CC_Ent_Name( const CCommand& args )
+{
+ SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_NAME_BIT);
+}
+static ConCommand ent_name("ent_name", CC_Ent_Name, 0, FCVAR_CHEAT);
+
+//------------------------------------------------------------------------------
+void CC_Ent_Text( const CCommand& args )
+{
+ SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_TEXT_BIT);
+}
+static ConCommand ent_text("ent_text", CC_Ent_Text, "Displays text debugging information about the given entity(ies) on top of the entity (See Overlay Text)\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
+
+//------------------------------------------------------------------------------
+void CC_Ent_BBox( const CCommand& args )
+{
+ SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_BBOX_BIT);
+}
+static ConCommand ent_bbox("ent_bbox", CC_Ent_BBox, "Displays the movement bounding box for the given entity(ies) in orange. Some entites will also display entity specific overlays.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
+
+
+//------------------------------------------------------------------------------
+void CC_Ent_AbsBox( const CCommand& args )
+{
+ SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_ABSBOX_BIT);
+}
+static ConCommand ent_absbox("ent_absbox", CC_Ent_AbsBox, "Displays the total bounding box for the given entity(s) in green. Some entites will also display entity specific overlays.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
+
+
+//------------------------------------------------------------------------------
+void CC_Ent_RBox( const CCommand& args )
+{
+ SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_RBOX_BIT);
+}
+static ConCommand ent_rbox("ent_rbox", CC_Ent_RBox, "Displays the total bounding box for the given entity(s) in green. Some entites will also display entity specific overlays.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
+
+//------------------------------------------------------------------------------
+void CC_Ent_AttachmentPoints( const CCommand& args )
+{
+ SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_ATTACHMENTS_BIT);
+}
+static ConCommand ent_attachments("ent_attachments", CC_Ent_AttachmentPoints, "Displays the attachment points on an entity.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
+
+//------------------------------------------------------------------------------
+void CC_Ent_ViewOffset( const CCommand& args )
+{
+ SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_VIEWOFFSET);
+}
+static ConCommand ent_viewoffset("ent_viewoffset", CC_Ent_ViewOffset, "Displays the eye position for the given entity(ies) in red.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
+
+//------------------------------------------------------------------------------
+void CC_Ent_Remove( const CCommand& args )
+{
+ CBaseEntity *pEntity = NULL;
+
+ // If no name was given set bits based on the picked
+ if ( FStrEq( args[1],"") )
+ {
+ pEntity = FindPickerEntity( UTIL_GetCommandClient() );
+ }
+ else
+ {
+ int index = atoi( args[1] );
+ if ( index )
+ {
+ pEntity = CBaseEntity::Instance( index );
+ }
+ else
+ {
+ // Otherwise set bits based on name or classname
+ CBaseEntity *ent = NULL;
+ while ( (ent = gEntList.NextEnt(ent)) != NULL )
+ {
+ if ( (ent->GetEntityName() != NULL_STRING && FStrEq(args[1], STRING(ent->GetEntityName()))) ||
+ (ent->m_iClassname != NULL_STRING && FStrEq(args[1], STRING(ent->m_iClassname))) ||
+ (ent->GetClassname()!=NULL && FStrEq(args[1], ent->GetClassname())))
+ {
+ pEntity = ent;
+ break;
+ }
+ }
+ }
+ }
+
+ // Found one?
+ if ( pEntity )
+ {
+ Msg( "Removed %s(%s)\n", STRING(pEntity->m_iClassname), pEntity->GetDebugName() );
+ UTIL_Remove( pEntity );
+ }
+}
+static ConCommand ent_remove("ent_remove", CC_Ent_Remove, "Removes the given entity(s)\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
+
+//------------------------------------------------------------------------------
+void CC_Ent_RemoveAll( const CCommand& args )
+{
+ // If no name was given remove based on the picked
+ if ( args.ArgC() < 2 )
+ {
+ Msg( "Removes all entities of the specified type\n\tArguments: {entity_name} / {class_name}\n" );
+ }
+ else
+ {
+ // Otherwise remove based on name or classname
+ int iCount = 0;
+ CBaseEntity *ent = NULL;
+ while ( (ent = gEntList.NextEnt(ent)) != NULL )
+ {
+ if ( (ent->GetEntityName() != NULL_STRING && FStrEq(args[1], STRING(ent->GetEntityName()))) ||
+ (ent->m_iClassname != NULL_STRING && FStrEq(args[1], STRING(ent->m_iClassname))) ||
+ (ent->GetClassname()!=NULL && FStrEq(args[1], ent->GetClassname())))
+ {
+ UTIL_Remove( ent );
+ iCount++;
+ }
+ }
+
+ if ( iCount )
+ {
+ Msg( "Removed %d %s's\n", iCount, args[1] );
+ }
+ else
+ {
+ Msg( "No %s found.\n", args[1] );
+ }
+ }
+}
+static ConCommand ent_remove_all("ent_remove_all", CC_Ent_RemoveAll, "Removes all entities of the specified type\n\tArguments: {entity_name} / {class_name} ", FCVAR_CHEAT);
+
+//------------------------------------------------------------------------------
+void CC_Ent_SetName( const CCommand& args )
+{
+ CBaseEntity *pEntity = NULL;
+
+ if ( args.ArgC() < 1 )
+ {
+ CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() );
+ if (!pPlayer)
+ return;
+
+ ClientPrint( pPlayer, HUD_PRINTCONSOLE, "Usage:\n ent_setname <new name> <entity name>\n" );
+ }
+ else
+ {
+ // If no name was given set bits based on the picked
+ if ( FStrEq( args[2],"") )
+ {
+ pEntity = FindPickerEntity( UTIL_GetCommandClient() );
+ }
+ else
+ {
+ // Otherwise set bits based on name or classname
+ CBaseEntity *ent = NULL;
+ while ( (ent = gEntList.NextEnt(ent)) != NULL )
+ {
+ if ( (ent->GetEntityName() != NULL_STRING && FStrEq(args[1], STRING(ent->GetEntityName()))) ||
+ (ent->m_iClassname != NULL_STRING && FStrEq(args[1], STRING(ent->m_iClassname))) ||
+ (ent->GetClassname()!=NULL && FStrEq(args[1], ent->GetClassname())))
+ {
+ pEntity = ent;
+ break;
+ }
+ }
+ }
+
+ // Found one?
+ if ( pEntity )
+ {
+ Msg( "Set the name of %s to %s\n", STRING(pEntity->m_iClassname), args[1] );
+ pEntity->SetName( AllocPooledString( args[1] ) );
+ }
+ }
+}
+static ConCommand ent_setname("ent_setname", CC_Ent_SetName, "Sets the targetname of the given entity(s)\n\tArguments: {new entity name} {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
+
+//------------------------------------------------------------------------------
+void CC_Find_Ent( const CCommand& args )
+{
+ if ( args.ArgC() < 2 )
+ {
+ Msg( "Total entities: %d (%d edicts)\n", gEntList.NumberOfEntities(), gEntList.NumberOfEdicts() );
+ Msg( "Format: find_ent <substring>\n" );
+ return;
+ }
+
+ int iCount = 0;
+ const char *pszSubString = args[1];
+ Msg("Searching for entities with class/target name containing substring: '%s'\n", pszSubString );
+
+ CBaseEntity *ent = NULL;
+ while ( (ent = gEntList.NextEnt(ent)) != NULL )
+ {
+ const char *pszClassname = ent->GetClassname();
+ const char *pszTargetname = STRING(ent->GetEntityName());
+
+ bool bMatches = false;
+ if ( pszClassname && pszClassname[0] )
+ {
+ if ( Q_stristr( pszClassname, pszSubString ) )
+ {
+ bMatches = true;
+ }
+ }
+
+ if ( !bMatches && pszTargetname && pszTargetname[0] )
+ {
+ if ( Q_stristr( pszTargetname, pszSubString ) )
+ {
+ bMatches = true;
+ }
+ }
+
+ if ( bMatches )
+ {
+ iCount++;
+ Msg(" '%s' : '%s' (entindex %d) \n", ent->GetClassname(), ent->GetEntityName().ToCStr(), ent->entindex() );
+ }
+ }
+
+ Msg("Found %d matches.\n", iCount);
+}
+static ConCommand find_ent("find_ent", CC_Find_Ent, "Find and list all entities with classnames or targetnames that contain the specified substring.\nFormat: find_ent <substring>\n", FCVAR_CHEAT);
+
+//------------------------------------------------------------------------------
+void CC_Find_Ent_Index( const CCommand& args )
+{
+ if ( args.ArgC() < 2 )
+ {
+ Msg( "Format: find_ent_index <index>\n" );
+ return;
+ }
+
+ int iIndex = atoi(args[1]);
+ CBaseEntity *pEnt = UTIL_EntityByIndex( iIndex );
+ if ( pEnt )
+ {
+ Msg(" '%s' : '%s' (entindex %d) \n", pEnt->GetClassname(), pEnt->GetEntityName().ToCStr(), iIndex );
+ }
+ else
+ {
+ Msg("Found no entity at %d.\n", iIndex);
+ }
+}
+static ConCommand find_ent_index("find_ent_index", CC_Find_Ent_Index, "Display data for entity matching specified index.\nFormat: find_ent_index <index>\n", FCVAR_CHEAT);
+
+// Purpose :
+//------------------------------------------------------------------------------
+void CC_Ent_Dump( const CCommand& args )
+{
+ CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() );
+ if (!pPlayer)
+ {
+ return;
+ }
+
+ if ( args.ArgC() < 2 )
+ {
+ ClientPrint( pPlayer, HUD_PRINTCONSOLE, "Usage:\n ent_dump <entity name>\n" );
+ }
+ else
+ {
+ // iterate through all the ents of this name, printing out their details
+ CBaseEntity *ent = NULL;
+ bool bFound = false;
+ while ( ( ent = gEntList.FindEntityByName(ent, args[1] ) ) != NULL )
+ {
+ bFound = true;
+ for ( datamap_t *dmap = ent->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap )
+ {
+ // search through all the actions in the data description, printing out details
+ for ( int i = 0; i < dmap->dataNumFields; i++ )
+ {
+ variant_t var;
+ if ( ent->ReadKeyField( dmap->dataDesc[i].externalName, &var) )
+ {
+ char buf[256];
+ buf[0] = 0;
+ switch( var.FieldType() )
+ {
+ case FIELD_STRING:
+ Q_strncpy( buf, var.String() ,sizeof(buf));
+ break;
+ case FIELD_INTEGER:
+ if ( var.Int() )
+ Q_snprintf( buf,sizeof(buf), "%d", var.Int() );
+ break;
+ case FIELD_FLOAT:
+ if ( var.Float() )
+ Q_snprintf( buf,sizeof(buf), "%.2f", var.Float() );
+ break;
+ case FIELD_EHANDLE:
+ {
+ // get the entities name
+ if ( var.Entity() )
+ {
+ Q_snprintf( buf,sizeof(buf), "%s", STRING(var.Entity()->GetEntityName()) );
+ }
+ }
+ break;
+ }
+
+ // don't print out the duplicate keys
+ if ( !Q_stricmp("parentname",dmap->dataDesc[i].externalName) || !Q_stricmp("targetname",dmap->dataDesc[i].externalName) )
+ continue;
+
+ // don't print out empty keys
+ if ( buf[0] )
+ {
+ ClientPrint( pPlayer, HUD_PRINTCONSOLE, UTIL_VarArgs(" %s: %s\n", dmap->dataDesc[i].externalName, buf) );
+ }
+ }
+ }
+ }
+ }
+
+ if ( !bFound )
+ {
+ ClientPrint( pPlayer, HUD_PRINTCONSOLE, "ent_dump: no such entity" );
+ }
+ }
+}
+static ConCommand ent_dump("ent_dump", CC_Ent_Dump, "Usage:\n ent_dump <entity name>\n", FCVAR_CHEAT);
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CC_Ent_FireTarget( const CCommand& args )
+{
+ ConsoleFireTargets(UTIL_GetCommandClient(),args[1]);
+}
+static ConCommand firetarget("firetarget", CC_Ent_FireTarget, 0, FCVAR_CHEAT);
+
+static bool UtlStringLessFunc( const CUtlString &lhs, const CUtlString &rhs )
+{
+ return Q_stricmp( lhs.String(), rhs.String() ) < 0;
+}
+
+class CEntFireAutoCompletionFunctor : public ICommandCallback, public ICommandCompletionCallback
+{
+public:
+ virtual void CommandCallback( const CCommand &command )
+ {
+ CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() );
+ if (!pPlayer)
+ {
+ return;
+ }
+
+ // fires a command from the console
+ if ( command.ArgC() < 2 )
+ {
+ ClientPrint( pPlayer, HUD_PRINTCONSOLE, "Usage:\n ent_fire <target> [action] [value] [delay]\n" );
+ }
+ else
+ {
+ const char *target = "", *action = "Use";
+ variant_t value;
+ int delay = 0;
+
+ target = STRING( AllocPooledString(command.Arg( 1 ) ) );
+
+ // Don't allow them to run anything on a point_servercommand unless they're the host player. Otherwise they can ent_fire
+ // and run any command on the server. Admittedly, they can only do the ent_fire if sv_cheats is on, but
+ // people complained about users resetting the rcon password if the server briefly turned on cheats like this:
+ // give point_servercommand
+ // ent_fire point_servercommand command "rcon_password mynewpassword"
+ //
+ // Robin: Unfortunately, they get around point_servercommand checks with this:
+ // ent_create point_servercommand; ent_setname mine; ent_fire mine command "rcon_password mynewpassword"
+ // So, I'm removing the ability for anyone to execute ent_fires on dedicated servers (we can't check to see if
+ // this command is going to connect with a point_servercommand entity here, because they could delay the event and create it later).
+ if ( engine->IsDedicatedServer() )
+ {
+ // We allow people with disabled autokick to do it, because they already have rcon.
+ if ( pPlayer->IsAutoKickDisabled() == false )
+ return;
+ }
+ else if ( gpGlobals->maxClients > 1 )
+ {
+ // On listen servers with more than 1 player, only allow the host to issue ent_fires.
+ CBasePlayer *pHostPlayer = UTIL_GetListenServerHost();
+ if ( pPlayer != pHostPlayer )
+ return;
+ }
+
+ if ( command.ArgC() >= 3 )
+ {
+ action = STRING( AllocPooledString(command.Arg( 2 )) );
+ }
+ if ( command.ArgC() >= 4 )
+ {
+ value.SetString( AllocPooledString(command.Arg( 3 )) );
+ }
+ if ( command.ArgC() >= 5 )
+ {
+ delay = atoi( command.Arg( 4 ) );
+ }
+
+ g_EventQueue.AddEvent( target, action, value, delay, pPlayer, pPlayer );
+ }
+ }
+
+ virtual int CommandCompletionCallback( const char *partial, CUtlVector< CUtlString > &commands )
+ {
+ if ( !g_pGameRules )
+ {
+ return 0;
+ }
+
+ const char *cmdname = "ent_fire";
+
+ char *substring = (char *)partial;
+ if ( Q_strstr( partial, cmdname ) )
+ {
+ substring = (char *)partial + strlen( cmdname ) + 1;
+ }
+
+ int checklen = 0;
+ char *space = Q_strstr( substring, " " );
+ if ( space )
+ {
+ return EntFire_AutoCompleteInput( partial, commands );;
+ }
+ else
+ {
+ checklen = Q_strlen( substring );
+ }
+
+ CUtlRBTree< CUtlString > symbols( 0, 0, UtlStringLessFunc );
+
+ CBaseEntity *pos = NULL;
+ while ( ( pos = gEntList.NextEnt( pos ) ) != NULL )
+ {
+ // Check target name against partial string
+ if ( pos->GetEntityName() == NULL_STRING )
+ continue;
+
+ if ( Q_strnicmp( STRING( pos->GetEntityName() ), substring, checklen ) )
+ continue;
+
+ CUtlString sym = STRING( pos->GetEntityName() );
+ int idx = symbols.Find( sym );
+ if ( idx == symbols.InvalidIndex() )
+ {
+ symbols.Insert( sym );
+ }
+
+ // Too many
+ if ( symbols.Count() >= COMMAND_COMPLETION_MAXITEMS )
+ break;
+ }
+
+ // Now fill in the results
+ for ( int i = symbols.FirstInorder(); i != symbols.InvalidIndex(); i = symbols.NextInorder( i ) )
+ {
+ const char *name = symbols[ i ].String();
+
+ char buf[ 512 ];
+ Q_strncpy( buf, name, sizeof( buf ) );
+ Q_strlower( buf );
+
+ CUtlString command;
+ command = CFmtStr( "%s %s", cmdname, buf );
+ commands.AddToTail( command );
+ }
+
+ return symbols.Count();
+ }
+private:
+ int EntFire_AutoCompleteInput( const char *partial, CUtlVector< CUtlString > &commands )
+ {
+ const char *cmdname = "ent_fire";
+
+ char *substring = (char *)partial;
+ if ( Q_strstr( partial, cmdname ) )
+ {
+ substring = (char *)partial + strlen( cmdname ) + 1;
+ }
+
+ int checklen = 0;
+ char *space = Q_strstr( substring, " " );
+ if ( !space )
+ {
+ Assert( !"CC_EntFireAutoCompleteInputFunc is broken\n" );
+ return 0;
+ }
+
+ checklen = Q_strlen( substring );
+
+ char targetEntity[ 256 ];
+ targetEntity[0] = 0;
+ int nEntityNameLength = (space-substring);
+ Q_strncat( targetEntity, substring, sizeof( targetEntity ), nEntityNameLength );
+
+ // Find the target entity by name
+ CBaseEntity *target = gEntList.FindEntityByName( NULL, targetEntity );
+ if ( target == NULL )
+ return 0;
+
+ CUtlRBTree< CUtlString > symbols( 0, 0, UtlStringLessFunc );
+
+ // Find the next portion of the text chain, if any (removing space)
+ int nInputNameLength = (checklen-nEntityNameLength-1);
+
+ // Starting past the last space, this is the remainder of the string
+ char *inputPartial = ( checklen > nEntityNameLength ) ? (space+1) : NULL;
+
+ for ( datamap_t *dmap = target->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap )
+ {
+ // Make sure we don't keep adding things in if the satisfied the limit
+ if ( symbols.Count() >= COMMAND_COMPLETION_MAXITEMS )
+ break;
+
+ int c = dmap->dataNumFields;
+ for ( int i = 0; i < c; i++ )
+ {
+ typedescription_t *field = &dmap->dataDesc[ i ];
+
+ // Only want inputs
+ if ( !( field->flags & FTYPEDESC_INPUT ) )
+ continue;
+
+ // Only want input functions
+ if ( field->flags & FTYPEDESC_SAVE )
+ continue;
+
+ // See if we've got a partial string for the input name already
+ if ( inputPartial != NULL )
+ {
+ if ( Q_strnicmp( inputPartial, field->externalName, nInputNameLength ) )
+ continue;
+ }
+
+ CUtlString sym = field->externalName;
+
+ int idx = symbols.Find( sym );
+ if ( idx == symbols.InvalidIndex() )
+ {
+ symbols.Insert( sym );
+ }
+
+ // Too many items have been added
+ if ( symbols.Count() >= COMMAND_COMPLETION_MAXITEMS )
+ break;
+ }
+ }
+
+ // Now fill in the results
+ for ( int i = symbols.FirstInorder(); i != symbols.InvalidIndex(); i = symbols.NextInorder( i ) )
+ {
+ const char *name = symbols[ i ].String();
+
+ char buf[ 512 ];
+ Q_strncpy( buf, name, sizeof( buf ) );
+ Q_strlower( buf );
+
+ CUtlString command;
+ command = CFmtStr( "%s %s %s", cmdname, targetEntity, buf );
+ commands.AddToTail( command );
+ }
+
+ return symbols.Count();
+ }
+};
+
+static CEntFireAutoCompletionFunctor g_EntFireAutoComplete;
+static ConCommand ent_fire("ent_fire", &g_EntFireAutoComplete, "Usage:\n ent_fire <target> [action] [value] [delay]\n", FCVAR_CHEAT, &g_EntFireAutoComplete );
+
+void CC_Ent_CancelPendingEntFires( const CCommand& args )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() );
+ if (!pPlayer)
+ return;
+
+ g_EventQueue.CancelEvents( pPlayer );
+}
+static ConCommand ent_cancelpendingentfires("ent_cancelpendingentfires", CC_Ent_CancelPendingEntFires, "Cancels all ent_fire created outputs that are currently waiting for their delay to expire." );
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CC_Ent_Info( const CCommand& args )
+{
+ CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() );
+ if (!pPlayer)
+ {
+ return;
+ }
+
+ if ( args.ArgC() < 2 )
+ {
+ ClientPrint( pPlayer, HUD_PRINTCONSOLE, "Usage:\n ent_info <class name>\n" );
+ }
+ else
+ {
+ // iterate through all the ents printing out their details
+ CBaseEntity *ent = CreateEntityByName( args[1] );
+
+ if ( ent )
+ {
+ datamap_t *dmap;
+ for ( dmap = ent->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap )
+ {
+ // search through all the actions in the data description, printing out details
+ for ( int i = 0; i < dmap->dataNumFields; i++ )
+ {
+ if ( dmap->dataDesc[i].flags & FTYPEDESC_OUTPUT )
+ {
+ ClientPrint( pPlayer, HUD_PRINTCONSOLE, UTIL_VarArgs(" output: %s\n", dmap->dataDesc[i].externalName) );
+ }
+ }
+ }
+
+ for ( dmap = ent->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap )
+ {
+ // search through all the actions in the data description, printing out details
+ for ( int i = 0; i < dmap->dataNumFields; i++ )
+ {
+ if ( dmap->dataDesc[i].flags & FTYPEDESC_INPUT )
+ {
+ ClientPrint( pPlayer, HUD_PRINTCONSOLE, UTIL_VarArgs(" input: %s\n", dmap->dataDesc[i].externalName) );
+ }
+ }
+ }
+
+ delete ent;
+ }
+ else
+ {
+ ClientPrint( pPlayer, HUD_PRINTCONSOLE, UTIL_VarArgs("no such entity %s\n", args[1]) );
+ }
+ }
+}
+static ConCommand ent_info("ent_info", CC_Ent_Info, "Usage:\n ent_info <class name>\n", FCVAR_CHEAT);
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CC_Ent_Messages( const CCommand& args )
+{
+ SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_MESSAGE_BIT);
+}
+static ConCommand ent_messages("ent_messages", CC_Ent_Messages ,"Toggles input/output message display for the selected entity(ies). The name of the entity will be displayed as well as any messages that it sends or receives.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at", FCVAR_CHEAT);
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CC_Ent_Pause( void )
+{
+ if (CBaseEntity::Debug_IsPaused())
+ {
+ Msg( "Resuming entity I/O events\n" );
+ CBaseEntity::Debug_Pause(false);
+ }
+ else
+ {
+ Msg( "Pausing entity I/O events\n" );
+ CBaseEntity::Debug_Pause(true);
+ }
+}
+static ConCommand ent_pause("ent_pause", CC_Ent_Pause, "Toggles pausing of input/output message processing for entities. When turned on processing of all message will stop. Any messages displayed with 'ent_messages' will stop fading and be displayed indefinitely. To step through the messages one by one use 'ent_step'.", FCVAR_CHEAT);
+
+
+//------------------------------------------------------------------------------
+// Purpose : Enables the entity picker, revelaing debug information about the
+// entity under the crosshair.
+// Input : an optional command line argument "full" enables all debug info.
+// Output :
+//------------------------------------------------------------------------------
+void CC_Ent_Picker( void )
+{
+ CBaseEntity::m_bInDebugSelect = CBaseEntity::m_bInDebugSelect ? false : true;
+
+ // Remember the player that's making this request
+ CBaseEntity::m_nDebugPlayer = UTIL_GetCommandClientIndex();
+}
+static ConCommand picker("picker", CC_Ent_Picker, "Toggles 'picker' mode. When picker is on, the bounding box, pivot and debugging text is displayed for whatever entity the player is looking at.\n\tArguments: full - enables all debug information", FCVAR_CHEAT);
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CC_Ent_Pivot( const CCommand& args )
+{
+ SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_PIVOT_BIT);
+}
+static ConCommand ent_pivot("ent_pivot", CC_Ent_Pivot, "Displays the pivot for the given entity(ies).\n\t(y=up=green, z=forward=blue, x=left=red). \n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CC_Ent_Step( const CCommand& args )
+{
+ int nSteps = atoi(args[1]);
+ if (nSteps <= 0)
+ {
+ nSteps = 1;
+ }
+ CBaseEntity::Debug_SetSteps(nSteps);
+}
+static ConCommand ent_step("ent_step", CC_Ent_Step, "When 'ent_pause' is set this will step through one waiting input / output message at a time.", FCVAR_CHEAT);
+
+void CBaseEntity::SetCheckUntouch( bool check )
+{
+ // Invalidate touchstamp
+ if ( check )
+ {
+ touchStamp++;
+ if ( !IsEFlagSet( EFL_CHECK_UNTOUCH ) )
+ {
+ AddEFlags( EFL_CHECK_UNTOUCH );
+ EntityTouch_Add( this );
+ }
+ }
+ else
+ {
+ RemoveEFlags( EFL_CHECK_UNTOUCH );
+ }
+}
+
+model_t *CBaseEntity::GetModel( void )
+{
+ return (model_t *)modelinfo->GetModel( GetModelIndex() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Calculates the absolute position of an edict in the world
+// assumes the parent's absolute origin has already been calculated
+//-----------------------------------------------------------------------------
+void CBaseEntity::CalcAbsolutePosition( void )
+{
+ if (!IsEFlagSet( EFL_DIRTY_ABSTRANSFORM ))
+ return;
+
+ RemoveEFlags( EFL_DIRTY_ABSTRANSFORM );
+
+ // Plop the entity->parent matrix into m_rgflCoordinateFrame
+ AngleMatrix( m_angRotation, m_vecOrigin, m_rgflCoordinateFrame );
+
+ CBaseEntity *pMoveParent = GetMoveParent();
+ if ( !pMoveParent )
+ {
+ // no move parent, so just copy existing values
+ m_vecAbsOrigin = m_vecOrigin;
+ m_angAbsRotation = m_angRotation;
+ if ( HasDataObjectType( POSITIONWATCHER ) )
+ {
+ ReportPositionChanged( this );
+ }
+ return;
+ }
+
+ // concatenate with our parent's transform
+ matrix3x4_t tmpMatrix, scratchSpace;
+ ConcatTransforms( GetParentToWorldTransform( scratchSpace ), m_rgflCoordinateFrame, tmpMatrix );
+ MatrixCopy( tmpMatrix, 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( pMoveParent->GetAbsAngles(), m_angAbsRotation );
+ }
+ else
+ {
+ MatrixAngles( m_rgflCoordinateFrame, m_angAbsRotation );
+ }
+ if ( HasDataObjectType( POSITIONWATCHER ) )
+ {
+ ReportPositionChanged( this );
+ }
+}
+
+void CBaseEntity::CalcAbsoluteVelocity()
+{
+ if (!IsEFlagSet( EFL_DIRTY_ABSVELOCITY ))
+ return;
+
+ RemoveEFlags( EFL_DIRTY_ABSVELOCITY );
+
+ CBaseEntity *pMoveParent = GetMoveParent();
+ if ( !pMoveParent )
+ {
+ m_vecAbsVelocity = m_vecVelocity;
+ return;
+ }
+
+ // This transforms the local velocity into world space
+ VectorRotate( m_vecVelocity, pMoveParent->EntityToWorldTransform(), m_vecAbsVelocity );
+
+ // Now add in the parent abs velocity
+ m_vecAbsVelocity += pMoveParent->GetAbsVelocity();
+}
+
+// FIXME: While we're using (dPitch, dYaw, dRoll) as our local angular velocity
+// representation, we can't actually solve this problem
+/*
+void CBaseEntity::CalcAbsoluteAngularVelocity()
+{
+ if (!IsEFlagSet( EFL_DIRTY_ABSANGVELOCITY ))
+ return;
+
+ RemoveEFlags( EFL_DIRTY_ABSANGVELOCITY );
+
+ CBaseEntity *pMoveParent = GetMoveParent();
+ if ( !pMoveParent )
+ {
+ m_vecAbsAngVelocity = m_vecAngVelocity;
+ return;
+ }
+
+ // This transforms the local ang velocity into world space
+ matrix3x4_t angVelToParent, angVelToWorld;
+ AngleMatrix( m_vecAngVelocity, angVelToParent );
+ ConcatTransforms( pMoveParent->EntityToWorldTransform(), angVelToParent, angVelToWorld );
+ MatrixAngles( angVelToWorld, m_vecAbsAngVelocity );
+}
+*/
+
+//-----------------------------------------------------------------------------
+// Computes the abs position of a point specified in local space
+//-----------------------------------------------------------------------------
+void CBaseEntity::ComputeAbsPosition( const Vector &vecLocalPosition, Vector *pAbsPosition )
+{
+ CBaseEntity *pMoveParent = GetMoveParent();
+ if ( !pMoveParent )
+ {
+ *pAbsPosition = vecLocalPosition;
+ }
+ else
+ {
+ VectorTransform( vecLocalPosition, pMoveParent->EntityToWorldTransform(), *pAbsPosition );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes the abs position of a point specified in local space
+//-----------------------------------------------------------------------------
+void CBaseEntity::ComputeAbsDirection( const Vector &vecLocalDirection, Vector *pAbsDirection )
+{
+ CBaseEntity *pMoveParent = GetMoveParent();
+ if ( !pMoveParent )
+ {
+ *pAbsDirection = vecLocalDirection;
+ }
+ else
+ {
+ VectorRotate( vecLocalDirection, pMoveParent->EntityToWorldTransform(), *pAbsDirection );
+ }
+}
+
+
+matrix3x4_t& CBaseEntity::GetParentToWorldTransform( matrix3x4_t &tempMatrix )
+{
+ CBaseEntity *pMoveParent = GetMoveParent();
+ if ( !pMoveParent )
+ {
+ Assert( false );
+ SetIdentityMatrix( tempMatrix );
+ return tempMatrix;
+ }
+
+ if ( m_iParentAttachment != 0 )
+ {
+ MDLCACHE_CRITICAL_SECTION();
+
+ CBaseAnimating *pAnimating = pMoveParent->GetBaseAnimating();
+ if ( pAnimating && pAnimating->GetAttachment( m_iParentAttachment, tempMatrix ) )
+ {
+ return tempMatrix;
+ }
+ }
+
+ // If we fall through to here, then just use the move parent's abs origin and angles.
+ return pMoveParent->EntityToWorldTransform();
+}
+
+
+//-----------------------------------------------------------------------------
+// These methods recompute local versions as well as set abs versions
+//-----------------------------------------------------------------------------
+void CBaseEntity::SetAbsOrigin( const Vector& absOrigin )
+{
+ AssertMsg( absOrigin.IsValid(), "Invalid origin set" );
+
+ // 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 );
+
+ Vector vecNewOrigin;
+ CBaseEntity *pMoveParent = GetMoveParent();
+ if (!pMoveParent)
+ {
+ vecNewOrigin = absOrigin;
+ }
+ else
+ {
+ matrix3x4_t tempMat;
+ matrix3x4_t &parentTransform = GetParentToWorldTransform( tempMat );
+
+ // Moveparent case: transform the abs position into local space
+ VectorITransform( absOrigin, parentTransform, vecNewOrigin );
+ }
+
+ if (m_vecOrigin != vecNewOrigin)
+ {
+ m_vecOrigin = vecNewOrigin;
+ SetSimulationTime( gpGlobals->curtime );
+ }
+}
+
+void CBaseEntity::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;
+
+ // All children are invalid, but we are not
+ InvalidatePhysicsRecursive( ANGLES_CHANGED );
+ RemoveEFlags( EFL_DIRTY_ABSTRANSFORM );
+
+ m_angAbsRotation = absAngles;
+ AngleMatrix( absAngles, m_rgflCoordinateFrame );
+ MatrixSetColumn( m_vecAbsOrigin, 3, m_rgflCoordinateFrame );
+
+ QAngle angNewRotation;
+ CBaseEntity *pMoveParent = GetMoveParent();
+ if (!pMoveParent)
+ {
+ angNewRotation = absAngles;
+ }
+ else
+ {
+ if ( m_angAbsRotation == pMoveParent->GetAbsAngles() )
+ {
+ angNewRotation.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, angNewRotation );
+ }
+ }
+
+ if (m_angRotation != angNewRotation)
+ {
+ m_angRotation = angNewRotation;
+ SetSimulationTime( gpGlobals->curtime );
+ }
+}
+
+void CBaseEntity::SetAbsVelocity( const Vector &vecAbsVelocity )
+{
+ if ( m_vecAbsVelocity == vecAbsVelocity )
+ return;
+
+ // The abs velocity won't be dirty since we're setting it here
+ // All children are invalid, but we are not
+ InvalidatePhysicsRecursive( VELOCITY_CHANGED );
+ RemoveEFlags( EFL_DIRTY_ABSVELOCITY );
+
+ m_vecAbsVelocity = vecAbsVelocity;
+
+ // NOTE: Do *not* do a network state change in this case.
+ // m_vecVelocity is only networked for the player, which is not manual mode
+ CBaseEntity *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 relative velocity into parent space
+ Vector vNew;
+ VectorIRotate( relVelocity, pMoveParent->EntityToWorldTransform(), vNew );
+ m_vecVelocity = vNew;
+}
+
+// FIXME: While we're using (dPitch, dYaw, dRoll) as our local angular velocity
+// representation, we can't actually solve this problem
+/*
+void CBaseEntity::SetAbsAngularVelocity( const QAngle &vecAbsAngVelocity )
+{
+ // The abs velocity won't be dirty since we're setting it here
+ // All children are invalid, but we are not
+ InvalidatePhysicsRecursive( EFL_DIRTY_ABSANGVELOCITY );
+ RemoveEFlags( EFL_DIRTY_ABSANGVELOCITY );
+
+ m_vecAbsAngVelocity = vecAbsAngVelocity;
+
+ CBaseEntity *pMoveParent = GetMoveParent();
+ if (!pMoveParent)
+ {
+ m_vecAngVelocity = vecAbsAngVelocity;
+ return;
+ }
+
+ // NOTE: We *can't* subtract out parent ang velocity, it's nonsensical
+ matrix3x4_t entityToWorld;
+ AngleMatrix( vecAbsAngVelocity, entityToWorld );
+
+ // Moveparent case: transform the abs relative angular vel into local space
+ matrix3x4_t worldToParent, localMatrix;
+ MatrixInvert( pMoveParent->EntityToWorldTransform(), worldToParent );
+ ConcatTransforms( worldToParent, entityToWorld, localMatrix );
+ MatrixAngles( localMatrix, m_vecAngVelocity );
+}
+*/
+
+//-----------------------------------------------------------------------------
+// Methods that modify local physics state, and let us know to compute abs state later
+//-----------------------------------------------------------------------------
+void CBaseEntity::SetLocalOrigin( const Vector& origin )
+{
+ // Safety check against NaN's or really huge numbers
+ if ( !IsEntityPositionReasonable( origin ) )
+ {
+ if ( CheckEmitReasonablePhysicsSpew() )
+ {
+ Warning( "Bad SetLocalOrigin(%f,%f,%f) on %s\n", origin.x, origin.y, origin.z, GetDebugName() );
+ }
+ Assert( false );
+ return;
+ }
+
+// if ( !origin.IsValid() )
+// {
+// AssertMsg( 0, "Bad origin set" );
+// return;
+// }
+
+ if (m_vecOrigin != origin)
+ {
+ // Sanity check to make sure the origin is valid.
+#ifdef _DEBUG
+ float largeVal = 1024 * 128;
+ Assert( origin.x >= -largeVal && origin.x <= largeVal );
+ Assert( origin.y >= -largeVal && origin.y <= largeVal );
+ Assert( origin.z >= -largeVal && origin.z <= largeVal );
+#endif
+
+ InvalidatePhysicsRecursive( POSITION_CHANGED );
+ m_vecOrigin = origin;
+ SetSimulationTime( gpGlobals->curtime );
+ }
+}
+
+void CBaseEntity::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 ) );
+
+ // Safety check against NaN's or really huge numbers
+ if ( !IsEntityQAngleReasonable( angles ) )
+ {
+ if ( CheckEmitReasonablePhysicsSpew() )
+ {
+ Warning( "Bad SetLocalAngles(%f,%f,%f) on %s\n", angles.x, angles.y, angles.z, GetDebugName() );
+ }
+ Assert( false );
+ return;
+ }
+
+ if (m_angRotation != angles)
+ {
+ InvalidatePhysicsRecursive( ANGLES_CHANGED );
+ m_angRotation = angles;
+ SetSimulationTime( gpGlobals->curtime );
+ }
+}
+
+void CBaseEntity::SetLocalVelocity( const Vector &inVecVelocity )
+{
+ Vector vecVelocity = inVecVelocity;
+
+ // Safety check against receive a huge impulse, which can explode physics
+ switch ( CheckEntityVelocity( vecVelocity ) )
+ {
+ case -1:
+ Warning( "Discarding SetLocalVelocity(%f,%f,%f) on %s\n", vecVelocity.x, vecVelocity.y, vecVelocity.z, GetDebugName() );
+ Assert( false );
+ return;
+ case 0:
+ if ( CheckEmitReasonablePhysicsSpew() )
+ {
+ Warning( "Clamping SetLocalVelocity(%f,%f,%f) on %s\n", inVecVelocity.x, inVecVelocity.y, inVecVelocity.z, GetDebugName() );
+ }
+ break;
+ }
+
+ if (m_vecVelocity != vecVelocity)
+ {
+ InvalidatePhysicsRecursive( VELOCITY_CHANGED );
+ m_vecVelocity = vecVelocity;
+ }
+}
+
+void CBaseEntity::SetLocalAngularVelocity( const QAngle &vecAngVelocity )
+{
+ // Safety check against NaN's or really huge numbers
+ if ( !IsEntityQAngleVelReasonable( vecAngVelocity ) )
+ {
+ if ( CheckEmitReasonablePhysicsSpew() )
+ {
+ Warning( "Bad SetLocalAngularVelocity(%f,%f,%f) on %s\n", vecAngVelocity.x, vecAngVelocity.y, vecAngVelocity.z, GetDebugName() );
+ }
+ Assert( false );
+ return;
+ }
+
+ if (m_vecAngVelocity != vecAngVelocity)
+ {
+// InvalidatePhysicsRecursive( EFL_DIRTY_ABSANGVELOCITY );
+ m_vecAngVelocity = vecAngVelocity;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Sets the local position from a transform
+//-----------------------------------------------------------------------------
+void CBaseEntity::SetLocalTransform( const matrix3x4_t &localTransform )
+{
+ // FIXME: Should angles go away? Should we just use transforms?
+ Vector vecLocalOrigin;
+ QAngle vecLocalAngles;
+ MatrixGetColumn( localTransform, 3, vecLocalOrigin );
+ MatrixAngles( localTransform, vecLocalAngles );
+ SetLocalOrigin( vecLocalOrigin );
+ SetLocalAngles( vecLocalAngles );
+}
+
+
+//-----------------------------------------------------------------------------
+// Is the entity floating?
+//-----------------------------------------------------------------------------
+bool CBaseEntity::IsFloating()
+{
+ if ( !IsEFlagSet(EFL_TOUCHING_FLUID) )
+ return false;
+
+ IPhysicsObject *pObject = VPhysicsGetObject();
+ if ( !pObject )
+ return false;
+
+ int nMaterialIndex = pObject->GetMaterialIndex();
+
+ float flDensity;
+ float flThickness;
+ float flFriction;
+ float flElasticity;
+ physprops->GetPhysicsProperties( nMaterialIndex, &flDensity,
+ &flThickness, &flFriction, &flElasticity );
+
+ // FIXME: This really only works for water at the moment..
+ // Owing the check for density == 1000
+ return (flDensity < 1000.0f);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Created predictable and sets up Id. Note that persist is ignored on the server.
+// Input : *classname -
+// *module -
+// line -
+// persist -
+// Output : CBaseEntity
+//-----------------------------------------------------------------------------
+CBaseEntity *CBaseEntity::CreatePredictedEntityByName( const char *classname, const char *module, int line, bool persist /* = false */ )
+{
+#if !defined( NO_ENTITY_PREDICTION )
+ CBasePlayer *player = CBaseEntity::GetPredictionPlayer();
+ Assert( player );
+
+ CBaseEntity *ent = NULL;
+
+ int command_number = player->CurrentCommandNumber();
+ int player_index = player->entindex() - 1;
+
+ CPredictableId testId;
+ testId.Init( player_index, command_number, classname, module, line );
+
+ ent = CreateEntityByName( classname );
+ // No factory???
+ if ( !ent )
+ return NULL;
+
+ ent->SetPredictionEligible( true );
+
+ // Set up "shared" id number
+ ent->m_PredictableID.GetForModify().SetRaw( testId.GetRaw() );
+
+ return ent;
+#else
+ return NULL;
+#endif
+
+}
+
+void CBaseEntity::SetPredictionEligible( bool canpredict )
+{
+// Nothing in game code m_bPredictionEligible = canpredict;
+}
+
+//-----------------------------------------------------------------------------
+// These could be virtual, but only the player is overriding them
+// NOTE: If you make any of these virtual, remove this implementation!!!
+//-----------------------------------------------------------------------------
+void CBaseEntity::AddPoints( int score, bool bAllowNegativeScore )
+{
+ CBasePlayer *pPlayer = ToBasePlayer(this);
+ if ( pPlayer )
+ {
+ pPlayer->CBasePlayer::AddPoints( score, bAllowNegativeScore );
+ }
+}
+
+void CBaseEntity::AddPointsToTeam( int score, bool bAllowNegativeScore )
+{
+ CBasePlayer *pPlayer = ToBasePlayer(this);
+ if ( pPlayer )
+ {
+ pPlayer->CBasePlayer::AddPointsToTeam( score, bAllowNegativeScore );
+ }
+}
+
+void CBaseEntity::ViewPunch( const QAngle &angleOffset )
+{
+ CBasePlayer *pPlayer = ToBasePlayer(this);
+ if ( pPlayer )
+ {
+ pPlayer->CBasePlayer::ViewPunch( angleOffset );
+ }
+}
+
+void CBaseEntity::VelocityPunch( const Vector &vecForce )
+{
+ CBasePlayer *pPlayer = ToBasePlayer(this);
+ if ( pPlayer )
+ {
+ pPlayer->CBasePlayer::VelocityPunch( vecForce );
+ }
+}
+//-----------------------------------------------------------------------------
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Tell clients to remove all decals from this entity
+//-----------------------------------------------------------------------------
+void CBaseEntity::RemoveAllDecals( void )
+{
+ EntityMessageBegin( this );
+ WRITE_BYTE( BASEENTITY_MSG_REMOVE_DECALS );
+ MessageEnd();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : set -
+//-----------------------------------------------------------------------------
+void CBaseEntity::ModifyOrAppendCriteria( AI_CriteriaSet& set )
+{
+ // TODO
+ // Append chapter/day?
+
+ set.AppendCriteria( "randomnum", UTIL_VarArgs("%d", RandomInt(0,100)) );
+ // Append map name
+ set.AppendCriteria( "map", gpGlobals->mapname.ToCStr() );
+ // Append our classname and game name
+ set.AppendCriteria( "classname", GetClassname() );
+ set.AppendCriteria( "name", GetEntityName().ToCStr() );
+
+ // Append our health
+ set.AppendCriteria( "health", UTIL_VarArgs( "%i", GetHealth() ) );
+
+ float healthfrac = 0.0f;
+ if ( GetMaxHealth() > 0 )
+ {
+ healthfrac = (float)GetHealth() / (float)GetMaxHealth();
+ }
+
+ set.AppendCriteria( "healthfrac", UTIL_VarArgs( "%.3f", healthfrac ) );
+
+ // Go through all the global states and append them
+
+ for ( int i = 0; i < GlobalEntity_GetNumGlobals(); i++ )
+ {
+ const char *szGlobalName = GlobalEntity_GetName(i);
+ int iGlobalState = (int)GlobalEntity_GetStateByIndex(i);
+ set.AppendCriteria( szGlobalName, UTIL_VarArgs( "%i", iGlobalState ) );
+ }
+
+ // Append anything from I/O or keyvalues pairs
+ AppendContextToCriteria( set );
+
+ if( hl2_episodic.GetBool() )
+ {
+ set.AppendCriteria( "episodic", "1" );
+ }
+
+ // Append anything from world I/O/keyvalues with "world" as prefix
+ CWorld *world = dynamic_cast< CWorld * >( CBaseEntity::Instance( engine->PEntityOfEntIndex( 0 ) ) );
+ if ( world )
+ {
+ world->AppendContextToCriteria( set, "world" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : set -
+// "" -
+//-----------------------------------------------------------------------------
+void CBaseEntity::AppendContextToCriteria( AI_CriteriaSet& set, const char *prefix /*= ""*/ )
+{
+ RemoveExpiredConcepts();
+
+ int c = GetContextCount();
+ int i;
+
+ char sz[ 128 ];
+ for ( i = 0; i < c; i++ )
+ {
+ const char *name = GetContextName( i );
+ const char *value = GetContextValue( i );
+
+ Q_snprintf( sz, sizeof( sz ), "%s%s", prefix, name );
+
+ set.AppendCriteria( sz, value );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Removes expired concepts from list
+// Output :
+//-----------------------------------------------------------------------------
+void CBaseEntity::RemoveExpiredConcepts( void )
+{
+ int c = GetContextCount();
+ int i;
+
+ for ( i = 0; i < c; i++ )
+ {
+ if ( ContextExpired( i ) )
+ {
+ m_ResponseContexts.Remove( i );
+ c--;
+ i--;
+ continue;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get current context count
+// Output : int
+//-----------------------------------------------------------------------------
+int CBaseEntity::GetContextCount() const
+{
+ return m_ResponseContexts.Count();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : index -
+// Output : const char
+//-----------------------------------------------------------------------------
+const char *CBaseEntity::GetContextName( int index ) const
+{
+ if ( index < 0 || index >= m_ResponseContexts.Count() )
+ {
+ Assert( 0 );
+ return "";
+ }
+
+ return m_ResponseContexts[ index ].m_iszName.ToCStr();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : index -
+// Output : const char
+//-----------------------------------------------------------------------------
+const char *CBaseEntity::GetContextValue( int index ) const
+{
+ if ( index < 0 || index >= m_ResponseContexts.Count() )
+ {
+ Assert( 0 );
+ return "";
+ }
+
+ return m_ResponseContexts[ index ].m_iszValue.ToCStr();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Check if context has expired
+// Input : index -
+// Output : bool
+//-----------------------------------------------------------------------------
+bool CBaseEntity::ContextExpired( int index ) const
+{
+ if ( index < 0 || index >= m_ResponseContexts.Count() )
+ {
+ Assert( 0 );
+ return true;
+ }
+
+ if ( !m_ResponseContexts[ index ].m_fExpirationTime )
+ {
+ return false;
+ }
+
+ return ( m_ResponseContexts[ index ].m_fExpirationTime <= gpGlobals->curtime );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Search for index of named context string
+// Input : *name -
+// Output : int
+//-----------------------------------------------------------------------------
+int CBaseEntity::FindContextByName( const char *name ) const
+{
+ int c = m_ResponseContexts.Count();
+ for ( int i = 0; i < c; i++ )
+ {
+ if ( FStrEq( name, GetContextName( i ) ) )
+ return i;
+ }
+
+ return -1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : inputdata -
+//-----------------------------------------------------------------------------
+void CBaseEntity::InputAddContext( inputdata_t& inputdata )
+{
+ const char *contextName = inputdata.value.String();
+ AddContext( contextName );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: User inputs. These fire the corresponding user outputs, and are
+// a means of forwarding messages through !activator to a target known
+// known by !activator but not by the targetting entity.
+//
+// For example, say you have three identical trains, following the same
+// path. Each train has a sprite in hierarchy with it that needs to
+// toggle on/off as it passes each path_track. You would hook each train's
+// OnUser1 output to it's sprite's Toggle input, then connect each path_track's
+// OnPass output to !activator's FireUser1 input.
+//-----------------------------------------------------------------------------
+void CBaseEntity::InputFireUser1( inputdata_t& inputdata )
+{
+ m_OnUser1.FireOutput( inputdata.pActivator, this );
+}
+
+
+void CBaseEntity::InputFireUser2( inputdata_t& inputdata )
+{
+ m_OnUser2.FireOutput( inputdata.pActivator, this );
+}
+
+
+void CBaseEntity::InputFireUser3( inputdata_t& inputdata )
+{
+ m_OnUser3.FireOutput( inputdata.pActivator, this );
+}
+
+
+void CBaseEntity::InputFireUser4( inputdata_t& inputdata )
+{
+ m_OnUser4.FireOutput( inputdata.pActivator, this );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *contextName -
+//-----------------------------------------------------------------------------
+void CBaseEntity::AddContext( const char *contextName )
+{
+ char key[ 128 ];
+ char value[ 128 ];
+ float duration;
+
+ const char *p = contextName;
+ while ( p )
+ {
+ duration = 0.0f;
+ p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), &duration );
+ if ( duration )
+ {
+ duration += gpGlobals->curtime;
+ }
+
+ int iIndex = FindContextByName( key );
+ if ( iIndex != -1 )
+ {
+ // Set the existing context to the new value
+ m_ResponseContexts[iIndex].m_iszValue = AllocPooledString( value );
+ m_ResponseContexts[iIndex].m_fExpirationTime = duration;
+ continue;
+ }
+
+ ResponseContext_t newContext;
+ newContext.m_iszName = AllocPooledString( key );
+ newContext.m_iszValue = AllocPooledString( value );
+ newContext.m_fExpirationTime = duration;
+
+ m_ResponseContexts.AddToTail( newContext );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : inputdata -
+//-----------------------------------------------------------------------------
+void CBaseEntity::InputRemoveContext( inputdata_t& inputdata )
+{
+ const char *contextName = inputdata.value.String();
+ int idx = FindContextByName( contextName );
+ if ( idx == -1 )
+ return;
+
+ m_ResponseContexts.Remove( idx );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : inputdata -
+//-----------------------------------------------------------------------------
+void CBaseEntity::InputClearContext( inputdata_t& inputdata )
+{
+ m_ResponseContexts.RemoveAll();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : IResponseSystem
+//-----------------------------------------------------------------------------
+IResponseSystem *CBaseEntity::GetResponseSystem()
+{
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : inputdata -
+//-----------------------------------------------------------------------------
+void CBaseEntity::InputDispatchResponse( inputdata_t& inputdata )
+{
+ DispatchResponse( inputdata.value.String() );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CBaseEntity::InputDisableShadow( inputdata_t &inputdata )
+{
+ AddEffects( EF_NOSHADOW );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CBaseEntity::InputEnableShadow( inputdata_t &inputdata )
+{
+ RemoveEffects( EF_NOSHADOW );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: An input to add a new connection from this entity
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CBaseEntity::InputAddOutput( inputdata_t &inputdata )
+{
+ char sOutputName[MAX_PATH];
+ Q_strncpy( sOutputName, inputdata.value.String(), sizeof(sOutputName) );
+ char *sChar = strchr( sOutputName, ' ' );
+ if ( sChar )
+ {
+ *sChar = '\0';
+ // Now replace all the :'s in the string with ,'s.
+ // Has to be done this way because Hammer doesn't allow ,'s inside parameters.
+ char *sColon = strchr( sChar+1, ':' );
+ while ( sColon )
+ {
+ *sColon = ',';
+ sColon = strchr( sChar+1, ':' );
+ }
+ KeyValue( sOutputName, sChar+1 );
+ }
+ else
+ {
+ Warning("AddOutput input fired with bad string. Format: <output name> <targetname>,<inputname>,<parameter>,<delay>,<max times to fire (-1 == infinite)>\n");
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *conceptName -
+//-----------------------------------------------------------------------------
+void CBaseEntity::DispatchResponse( const char *conceptName )
+{
+ IResponseSystem *rs = GetResponseSystem();
+ if ( !rs )
+ return;
+
+ AI_CriteriaSet set;
+ // Always include the concept name
+ set.AppendCriteria( "concept", conceptName, CONCEPT_WEIGHT );
+ // Let NPC fill in most match criteria
+ ModifyOrAppendCriteria( set );
+
+ // Append local player criteria to set,too
+ CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
+ if( pPlayer )
+ pPlayer->ModifyOrAppendPlayerCriteria( set );
+
+ // Now that we have a criteria set, ask for a suitable response
+ AI_Response result;
+ bool found = rs->FindBestResponse( set, result );
+ if ( !found )
+ {
+ return;
+ }
+
+ // Handle the response here...
+ char response[ 256 ];
+ result.GetResponse( response, sizeof( response ) );
+ switch ( result.GetType() )
+ {
+ case RESPONSE_SPEAK:
+ {
+ EmitSound( response );
+ }
+ break;
+ case RESPONSE_SENTENCE:
+ {
+ int sentenceIndex = SENTENCEG_Lookup( response );
+ if( sentenceIndex == -1 )
+ {
+ // sentence not found
+ break;
+ }
+
+ // FIXME: Get pitch from npc?
+ CPASAttenuationFilter filter( this );
+ CBaseEntity::EmitSentenceByIndex( filter, entindex(), CHAN_VOICE, sentenceIndex, 1, result.GetSoundLevel(), 0, PITCH_NORM );
+ }
+ break;
+ case RESPONSE_SCENE:
+ {
+ // Try to fire scene w/o an actor
+ InstancedScriptedScene( NULL, response );
+ }
+ break;
+ case RESPONSE_PRINT:
+ {
+
+ }
+ break;
+ default:
+ // Don't know how to handle .vcds!!!
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseEntity::DumpResponseCriteria( void )
+{
+ Msg("----------------------------------------------\n");
+ Msg("RESPONSE CRITERIA FOR: %s (%s)\n", GetClassname(), GetDebugName() );
+
+ AI_CriteriaSet set;
+ // Let NPC fill in most match criteria
+ ModifyOrAppendCriteria( set );
+
+ // Append local player criteria to set,too
+ CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
+ if ( pPlayer )
+ {
+ pPlayer->ModifyOrAppendPlayerCriteria( set );
+ }
+
+ // Now dump it all to console
+ set.Describe();
+}
+
+//------------------------------------------------------------------------------
+void CC_Ent_Show_Response_Criteria( const CCommand& args )
+{
+ CBaseEntity *pEntity = NULL;
+ while ( (pEntity = GetNextCommandEntity( UTIL_GetCommandClient(), args[1], pEntity )) != NULL )
+ {
+ pEntity->DumpResponseCriteria();
+ }
+}
+static ConCommand ent_show_response_criteria("ent_show_response_criteria", CC_Ent_Show_Response_Criteria, "Print, to the console, an entity's current criteria set used to select responses.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
+
+//------------------------------------------------------------------------------
+// Purpose: Show an entity's autoaim radius
+//------------------------------------------------------------------------------
+void CC_Ent_Autoaim( const CCommand& args )
+{
+ SetDebugBits( UTIL_GetCommandClient(),args[1], OVERLAY_AUTOAIM_BIT );
+}
+static ConCommand ent_autoaim("ent_autoaim", CC_Ent_Autoaim, "Displays the entity's autoaim radius.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at", FCVAR_CHEAT );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CAI_BaseNPC *CBaseEntity::MyNPCPointer( void )
+{
+ if ( IsNPC() )
+ return assert_cast<CAI_BaseNPC *>(this);
+
+ return NULL;
+}
+
+ConVar step_spline( "step_spline", "0" );
+
+//-----------------------------------------------------------------------------
+// Purpose: Run one tick's worth of faked simulation
+// Input : *step -
+//-----------------------------------------------------------------------------
+void CBaseEntity::ComputeStepSimulationNetwork( StepSimulationData *step )
+{
+ if ( !step )
+ {
+ Assert( !"ComputeStepSimulationNetworkOriginAndAngles with NULL step\n" );
+ return;
+ }
+
+ // Don't run again if we've already calculated this tick
+ if ( step->m_nLastProcessTickCount == gpGlobals->tickcount )
+ {
+ return;
+ }
+
+ step->m_nLastProcessTickCount = gpGlobals->tickcount;
+
+ // Origin
+ // It's inactive
+ if ( step->m_bOriginActive )
+ {
+ // First see if any external code moved the entity
+ if ( GetStepOrigin() != step->m_Next.vecOrigin )
+ {
+ step->m_bOriginActive = false;
+ }
+ else
+ {
+ // Compute interpolated info based on tick interval
+ float frac = 1.0f;
+ int tickdelta = step->m_Next.nTickCount - step->m_Previous.nTickCount;
+ if ( tickdelta > 0 )
+ {
+ frac = (float)( gpGlobals->tickcount - step->m_Previous.nTickCount ) / (float) tickdelta;
+ frac = clamp( frac, 0.0f, 1.0f );
+ }
+
+ if (step->m_Previous2.nTickCount == 0 || step->m_Previous2.nTickCount >= step->m_Previous.nTickCount)
+ {
+ Vector delta = step->m_Next.vecOrigin - step->m_Previous.vecOrigin;
+ VectorMA( step->m_Previous.vecOrigin, frac, delta, step->m_vecNetworkOrigin );
+ }
+ else if (!step_spline.GetBool())
+ {
+ StepSimulationStep *pOlder = &step->m_Previous;
+ StepSimulationStep *pNewer = &step->m_Next;
+
+ if (step->m_Discontinuity.nTickCount > step->m_Previous.nTickCount)
+ {
+ if (gpGlobals->tickcount > step->m_Discontinuity.nTickCount)
+ {
+ pOlder = &step->m_Discontinuity;
+ }
+ else
+ {
+ pNewer = &step->m_Discontinuity;
+ }
+
+ tickdelta = pNewer->nTickCount - pOlder->nTickCount;
+ if ( tickdelta > 0 )
+ {
+ frac = (float)( gpGlobals->tickcount - pOlder->nTickCount ) / (float) tickdelta;
+ frac = clamp( frac, 0.0f, 1.0f );
+ }
+ }
+
+ Vector delta = pNewer->vecOrigin - pOlder->vecOrigin;
+ VectorMA( pOlder->vecOrigin, frac, delta, step->m_vecNetworkOrigin );
+ }
+ else
+ {
+ Hermite_Spline( step->m_Previous2.vecOrigin, step->m_Previous.vecOrigin, step->m_Next.vecOrigin, frac, step->m_vecNetworkOrigin );
+ }
+ }
+ }
+
+ // Angles
+ if ( step->m_bAnglesActive )
+ {
+ // See if external code changed the orientation of the entity
+ if ( GetStepAngles() != step->m_angNextRotation )
+ {
+ step->m_bAnglesActive = false;
+ }
+ else
+ {
+ // Compute interpolated info based on tick interval
+ float frac = 1.0f;
+ int tickdelta = step->m_Next.nTickCount - step->m_Previous.nTickCount;
+ if ( tickdelta > 0 )
+ {
+ frac = (float)( gpGlobals->tickcount - step->m_Previous.nTickCount ) / (float) tickdelta;
+ frac = clamp( frac, 0.0f, 1.0f );
+ }
+
+ if (step->m_Previous2.nTickCount == 0 || step->m_Previous2.nTickCount >= step->m_Previous.nTickCount)
+ {
+ // Pure blend between start/end orientations
+ Quaternion outangles;
+ QuaternionBlend( step->m_Previous.qRotation, step->m_Next.qRotation, frac, outangles );
+ QuaternionAngles( outangles, step->m_angNetworkAngles );
+ }
+ else if (!step_spline.GetBool())
+ {
+ StepSimulationStep *pOlder = &step->m_Previous;
+ StepSimulationStep *pNewer = &step->m_Next;
+
+ if (step->m_Discontinuity.nTickCount > step->m_Previous.nTickCount)
+ {
+ if (gpGlobals->tickcount > step->m_Discontinuity.nTickCount)
+ {
+ pOlder = &step->m_Discontinuity;
+ }
+ else
+ {
+ pNewer = &step->m_Discontinuity;
+ }
+
+ tickdelta = pNewer->nTickCount - pOlder->nTickCount;
+ if ( tickdelta > 0 )
+ {
+ frac = (float)( gpGlobals->tickcount - pOlder->nTickCount ) / (float) tickdelta;
+ frac = clamp( frac, 0.0f, 1.0f );
+ }
+ }
+
+ // Pure blend between start/end orientations
+ Quaternion outangles;
+ QuaternionBlend( pOlder->qRotation, pNewer->qRotation, frac, outangles );
+ QuaternionAngles( outangles, step->m_angNetworkAngles );
+ }
+ else
+ {
+ // FIXME: enable spline interpolation when turning is debounced.
+ Quaternion outangles;
+ Hermite_Spline( step->m_Previous2.qRotation, step->m_Previous.qRotation, step->m_Next.qRotation, frac, outangles );
+ QuaternionAngles( outangles, step->m_angNetworkAngles );
+ }
+ }
+ }
+
+}
+
+
+//-----------------------------------------------------------------------------
+bool CBaseEntity::UseStepSimulationNetworkOrigin( const Vector **out_v )
+{
+ Assert( out_v );
+
+
+ if ( g_bTestMoveTypeStepSimulation &&
+ GetMoveType() == MOVETYPE_STEP &&
+ HasDataObjectType( STEPSIMULATION ) )
+ {
+ StepSimulationData *step = ( StepSimulationData * )GetDataObject( STEPSIMULATION );
+ ComputeStepSimulationNetwork( step );
+ *out_v = &step->m_vecNetworkOrigin;
+
+ return step->m_bOriginActive;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+bool CBaseEntity::UseStepSimulationNetworkAngles( const QAngle **out_a )
+{
+ Assert( out_a );
+
+ if ( g_bTestMoveTypeStepSimulation &&
+ GetMoveType() == MOVETYPE_STEP &&
+ HasDataObjectType( STEPSIMULATION ) )
+ {
+ StepSimulationData *step = ( StepSimulationData * )GetDataObject( STEPSIMULATION );
+ ComputeStepSimulationNetwork( step );
+ *out_a = &step->m_angNetworkAngles;
+ return step->m_bAnglesActive;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+bool CBaseEntity::AddStepDiscontinuity( float flTime, const Vector &vecOrigin, const QAngle &vecAngles )
+{
+ if ((GetMoveType() != MOVETYPE_STEP ) || !HasDataObjectType( STEPSIMULATION ) )
+ {
+ return false;
+ }
+
+ StepSimulationData *step = ( StepSimulationData * )GetDataObject( STEPSIMULATION );
+
+ if (!step)
+ {
+ Assert( 0 );
+ return false;
+ }
+
+ step->m_Discontinuity.nTickCount = TIME_TO_TICKS( flTime );
+ step->m_Discontinuity.vecOrigin = vecOrigin;
+ AngleQuaternion( vecAngles, step->m_Discontinuity.qRotation );
+
+ return true;
+}
+
+
+Vector CBaseEntity::GetStepOrigin( void ) const
+{
+ return GetLocalOrigin();
+}
+
+QAngle CBaseEntity::GetStepAngles( void ) const
+{
+ return GetLocalAngles();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: For each client who appears to be a valid recipient, checks the client has disabled CC and if so, removes them from
+// the recipient list.
+// Input : filter -
+//-----------------------------------------------------------------------------
+void CBaseEntity::RemoveRecipientsIfNotCloseCaptioning( CRecipientFilter& filter )
+{
+ int c = filter.GetRecipientCount();
+ for ( int i = c - 1; i >= 0; --i )
+ {
+ int playerIndex = filter.GetRecipientIndex( i );
+
+ CBasePlayer *player = static_cast< CBasePlayer * >( CBaseEntity::Instance( playerIndex ) );
+ if ( !player )
+ continue;
+#if !defined( _XBOX )
+ const char *cvarvalue = engine->GetClientConVarValue( playerIndex, "closecaption" );
+ Assert( cvarvalue );
+ if ( !cvarvalue[ 0 ] )
+ continue;
+
+ int value = atoi( cvarvalue );
+#else
+ static ConVar *s_pCloseCaption = NULL;
+ if ( !s_pCloseCaption )
+ {
+ s_pCloseCaption = cvar->FindVar( "closecaption" );
+ if ( !s_pCloseCaption )
+ {
+ Error( "XBOX couldn't find closecaption convar!!!" );
+ }
+ }
+
+ int value = s_pCloseCaption->GetInt();
+#endif
+ // No close captions?
+ if ( value == 0 )
+ {
+ filter.RemoveRecipient( player );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Wrapper to emit a sentence and also a close caption token for the sentence as appropriate.
+// Input : filter -
+// iEntIndex -
+// iChannel -
+// iSentenceIndex -
+// flVolume -
+// iSoundlevel -
+// iFlags -
+// iPitch -
+// bUpdatePositions -
+// soundtime -
+//-----------------------------------------------------------------------------
+void CBaseEntity::EmitSentenceByIndex( IRecipientFilter& filter, int iEntIndex, int iChannel, int iSentenceIndex,
+ float flVolume, soundlevel_t iSoundlevel, int iFlags /*= 0*/, int iPitch /*=PITCH_NORM*/,
+ const Vector *pOrigin /*=NULL*/, const Vector *pDirection /*=NULL*/,
+ bool bUpdatePositions /*=true*/, float soundtime /*=0.0f*/ )
+{
+ CUtlVector< Vector > dummy;
+ enginesound->EmitSentenceByIndex( filter, iEntIndex, iChannel, iSentenceIndex,
+ flVolume, iSoundlevel, iFlags, iPitch, 0, pOrigin, pDirection, &dummy, bUpdatePositions, soundtime );
+}
+
+
+void CBaseEntity::SetRefEHandle( const CBaseHandle &handle )
+{
+ m_RefEHandle = handle;
+ if ( edict() )
+ {
+ COMPILE_TIME_ASSERT( NUM_NETWORKED_EHANDLE_SERIAL_NUMBER_BITS <= 8*sizeof( edict()->m_NetworkSerialNumber ) );
+ edict()->m_NetworkSerialNumber = (m_RefEHandle.GetSerialNumber() & (1 << NUM_NETWORKED_EHANDLE_SERIAL_NUMBER_BITS) - 1);
+ }
+}
+
+
+bool CPointEntity::KeyValue( const char *szKeyName, const char *szValue )
+{
+ if ( FStrEq( szKeyName, "mins" ) || FStrEq( szKeyName, "maxs" ) )
+ {
+ Warning("Warning! Can't specify mins/maxs for point entities! (%s)\n", GetClassname() );
+ return true;
+ }
+
+ return BaseClass::KeyValue( szKeyName, szValue );
+}
+
+bool CServerOnlyPointEntity::KeyValue( const char *szKeyName, const char *szValue )
+{
+ if ( FStrEq( szKeyName, "mins" ) || FStrEq( szKeyName, "maxs" ) )
+ {
+ Warning("Warning! Can't specify mins/maxs for point entities! (%s)\n", GetClassname() );
+ return true;
+ }
+
+ return BaseClass::KeyValue( szKeyName, szValue );
+}
+
+bool CLogicalEntity::KeyValue( const char *szKeyName, const char *szValue )
+{
+ if ( FStrEq( szKeyName, "mins" ) || FStrEq( szKeyName, "maxs" ) )
+ {
+ Warning("Warning! Can't specify mins/maxs for point entities! (%s)\n", GetClassname() );
+ return true;
+ }
+
+ return BaseClass::KeyValue( szKeyName, szValue );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the entity invisible, and makes it remove itself on the next frame
+//-----------------------------------------------------------------------------
+void CBaseEntity::RemoveDeferred( void )
+{
+ // Set our next think to remove us
+ SetThink( &CBaseEntity::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ // Hide us completely
+ AddEffects( EF_NODRAW );
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ SetMoveType( MOVETYPE_NONE );
+}
+
+#define MIN_CORPSE_FADE_TIME 10.0
+#define MIN_CORPSE_FADE_DIST 256.0
+#define MAX_CORPSE_FADE_DIST 1500.0
+
+//
+// fade out - slowly fades a entity out, then removes it.
+//
+// DON'T USE ME FOR GIBS AND STUFF IN MULTIPLAYER!
+// SET A FUTURE THINK AND A RENDERMODE!!
+void CBaseEntity::SUB_StartFadeOut( float delay, bool notSolid )
+{
+ SetThink( &CBaseEntity::SUB_FadeOut );
+ SetNextThink( gpGlobals->curtime + delay );
+ SetRenderColorA( 255 );
+ m_nRenderMode = kRenderNormal;
+
+ if ( notSolid )
+ {
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ SetLocalAngularVelocity( vec3_angle );
+ }
+}
+
+void CBaseEntity::SUB_StartFadeOutInstant()
+{
+ SUB_StartFadeOut( 0, true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Vanish when players aren't looking
+//-----------------------------------------------------------------------------
+void CBaseEntity::SUB_Vanish( void )
+{
+ //Always think again next frame
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ CBasePlayer *pPlayer;
+
+ //Get all players
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ //Get the next client
+ if ( ( pPlayer = UTIL_PlayerByIndex( i ) ) != NULL )
+ {
+ Vector corpseDir = (GetAbsOrigin() - pPlayer->WorldSpaceCenter() );
+
+ float flDistSqr = corpseDir.LengthSqr();
+ //If the player is close enough, don't fade out
+ if ( flDistSqr < (MIN_CORPSE_FADE_DIST*MIN_CORPSE_FADE_DIST) )
+ return;
+
+ // If the player's far enough away, we don't care about looking at it
+ if ( flDistSqr < (MAX_CORPSE_FADE_DIST*MAX_CORPSE_FADE_DIST) )
+ {
+ VectorNormalize( corpseDir );
+
+ Vector plForward;
+ pPlayer->EyeVectors( &plForward );
+
+ float dot = plForward.Dot( corpseDir );
+
+ if ( dot > 0.0f )
+ return;
+ }
+ }
+ }
+
+ //If we're here, then we can vanish safely
+ m_iHealth = 0;
+ SetThink( &CBaseEntity::SUB_Remove );
+}
+
+void CBaseEntity::SUB_PerformFadeOut( void )
+{
+ float dt = gpGlobals->frametime;
+ if ( dt > 0.1f )
+ {
+ dt = 0.1f;
+ }
+ m_nRenderMode = kRenderTransTexture;
+ int speed = MAX(1,256*dt); // fade out over 1 second
+ SetRenderColorA( UTIL_Approach( 0, m_clrRender->a, speed ) );
+}
+
+bool CBaseEntity::SUB_AllowedToFade( void )
+{
+ if( VPhysicsGetObject() )
+ {
+ if( VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD || GetEFlags() & EFL_IS_BEING_LIFTED_BY_BARNACLE )
+ return false;
+ }
+
+ // on Xbox, allow these to fade out
+#ifndef _XBOX
+ CBasePlayer *pPlayer = ( AI_IsSinglePlayer() ) ? UTIL_GetLocalPlayer() : NULL;
+
+ if ( pPlayer && pPlayer->FInViewCone( this ) )
+ return false;
+#endif
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Fade out slowly
+//-----------------------------------------------------------------------------
+void CBaseEntity::SUB_FadeOut( void )
+{
+ if ( SUB_AllowedToFade() == false )
+ {
+ SetNextThink( gpGlobals->curtime + 1 );
+ SetRenderColorA( 255 );
+ return;
+ }
+
+ SUB_PerformFadeOut();
+
+ if ( m_clrRender->a == 0 )
+ {
+ UTIL_Remove(this);
+ }
+ else
+ {
+ SetNextThink( gpGlobals->curtime );
+ }
+}
+
+
+inline bool AnyPlayersInHierarchy_R( CBaseEntity *pEnt )
+{
+ if ( pEnt->IsPlayer() )
+ return true;
+
+ for ( CBaseEntity *pCur = pEnt->FirstMoveChild(); pCur; pCur=pCur->NextMovePeer() )
+ {
+ if ( AnyPlayersInHierarchy_R( pCur ) )
+ return true;
+ }
+
+ return false;
+}
+
+
+void CBaseEntity::RecalcHasPlayerChildBit()
+{
+ if ( AnyPlayersInHierarchy_R( this ) )
+ AddEFlags( EFL_HAS_PLAYER_CHILD );
+ else
+ RemoveEFlags( EFL_HAS_PLAYER_CHILD );
+}
+
+
+bool CBaseEntity::DoesHavePlayerChild()
+{
+ return IsEFlagSet( EFL_HAS_PLAYER_CHILD );
+}
+
+
+//------------------------------------------------------------------------------
+void CBaseEntity::IncrementInterpolationFrame()
+{
+ m_ubInterpolationFrame = (m_ubInterpolationFrame + 1) % NOINTERP_PARITY_MAX;
+}
+
+//------------------------------------------------------------------------------
+
+void CBaseEntity::OnModelLoadComplete( const model_t* model )
+{
+ Assert( m_bDynamicModelPending && IsDynamicModelIndex( m_nModelIndex ) );
+ Assert( model == modelinfo->GetModel( m_nModelIndex ) );
+
+ m_bDynamicModelPending = false;
+
+ if ( m_bDynamicModelSetBounds )
+ {
+ m_bDynamicModelSetBounds = false;
+ SetCollisionBoundsFromModel();
+ }
+
+ OnNewModel();
+}
+
+//------------------------------------------------------------------------------
+
+void CBaseEntity::SetCollisionBoundsFromModel()
+{
+ if ( IsDynamicModelLoading() )
+ {
+ m_bDynamicModelSetBounds = true;
+ return;
+ }
+
+ if ( const model_t *pModel = GetModel() )
+ {
+ Vector mns, mxs;
+ modelinfo->GetModelBounds( pModel, mns, mxs );
+ UTIL_SetSize( this, mns, mxs );
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose: Create an NPC of the given type
+//------------------------------------------------------------------------------
+void CC_Ent_Create( const CCommand& args )
+{
+ MDLCACHE_CRITICAL_SECTION();
+
+ bool allowPrecache = CBaseEntity::IsPrecacheAllowed();
+ CBaseEntity::SetAllowPrecache( true );
+
+ // Try to create entity
+ CBaseEntity *entity = dynamic_cast< CBaseEntity * >( CreateEntityByName(args[1]) );
+ if (entity)
+ {
+ entity->Precache();
+
+ // Pass in any additional parameters.
+ for ( int i = 2; i + 1 < args.ArgC(); i += 2 )
+ {
+ const char *pKeyName = args[i];
+ const char *pValue = args[i+1];
+ entity->KeyValue( pKeyName, pValue );
+ }
+
+ DispatchSpawn(entity);
+
+ // Now attempt to drop into the world
+ CBasePlayer* pPlayer = UTIL_GetCommandClient();
+ trace_t tr;
+ Vector forward;
+ pPlayer->EyeVectors( &forward );
+ UTIL_TraceLine(pPlayer->EyePosition(),
+ pPlayer->EyePosition() + forward * MAX_TRACE_LENGTH,MASK_SOLID,
+ pPlayer, COLLISION_GROUP_NONE, &tr );
+ if ( tr.fraction != 1.0 )
+ {
+ // Raise the end position a little up off the floor, place the npc and drop him down
+ tr.endpos.z += 12;
+ entity->Teleport( &tr.endpos, NULL, NULL );
+ UTIL_DropToFloor( entity, MASK_SOLID );
+ }
+
+ entity->Activate();
+ }
+ CBaseEntity::SetAllowPrecache( allowPrecache );
+}
+static ConCommand ent_create("ent_create", CC_Ent_Create, "Creates an entity of the given type where the player is looking. Additional parameters can be passed in in the form: ent_create <entity name> <param 1 name> <param 1> <param 2 name> <param 2>...<param N name> <param N>", FCVAR_GAMEDLL | FCVAR_CHEAT);
+
+//------------------------------------------------------------------------------
+// Purpose: Teleport a specified entity to where the player is looking
+//------------------------------------------------------------------------------
+bool CC_GetCommandEnt( const CCommand& args, CBaseEntity **ent, Vector *vecTargetPoint, QAngle *vecPlayerAngle )
+{
+ // Find the entity
+ *ent = NULL;
+ // First try using it as an entindex
+ int iEntIndex = atoi( args[1] );
+ if ( iEntIndex )
+ {
+ *ent = CBaseEntity::Instance( iEntIndex );
+ }
+ else
+ {
+ // Try finding it by name
+ *ent = gEntList.FindEntityByName( NULL, args[1] );
+
+ if ( !*ent )
+ {
+ // Finally, try finding it by classname
+ *ent = gEntList.FindEntityByClassname( NULL, args[1] );
+ }
+ }
+
+ if ( !*ent )
+ {
+ Msg( "Couldn't find any entity named '%s'\n", args[1] );
+ return false;
+ }
+
+ CBasePlayer *pPlayer = UTIL_GetCommandClient();
+ if ( vecTargetPoint )
+ {
+ trace_t tr;
+ Vector forward;
+ pPlayer->EyeVectors( &forward );
+ UTIL_TraceLine(pPlayer->EyePosition(),
+ pPlayer->EyePosition() + forward * MAX_TRACE_LENGTH,MASK_NPCSOLID,
+ pPlayer, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.fraction != 1.0 )
+ {
+ *vecTargetPoint = tr.endpos;
+ }
+ }
+
+ if ( vecPlayerAngle )
+ {
+ *vecPlayerAngle = pPlayer->EyeAngles();
+ }
+
+ return true;
+}
+
+//------------------------------------------------------------------------------
+// Purpose: Teleport a specified entity to where the player is looking
+//------------------------------------------------------------------------------
+void CC_Ent_Teleport( const CCommand& args )
+{
+ if ( args.ArgC() < 2 )
+ {
+ Msg( "Format: ent_teleport <entity name>\n" );
+ return;
+ }
+
+ CBaseEntity *pEnt;
+ Vector vecTargetPoint;
+ if ( CC_GetCommandEnt( args, &pEnt, &vecTargetPoint, NULL ) )
+ {
+ pEnt->Teleport( &vecTargetPoint, NULL, NULL );
+ }
+}
+
+static ConCommand ent_teleport("ent_teleport", CC_Ent_Teleport, "Teleport the specified entity to where the player is looking.\n\tFormat: ent_teleport <entity name>", FCVAR_CHEAT);
+
+//------------------------------------------------------------------------------
+// Purpose: Orient a specified entity to match the player's angles
+//------------------------------------------------------------------------------
+void CC_Ent_Orient( const CCommand& args )
+{
+ if ( args.ArgC() < 2 )
+ {
+ Msg( "Format: ent_orient <entity name> <optional: allangles>\n" );
+ return;
+ }
+
+ CBaseEntity *pEnt;
+ QAngle vecPlayerAngles;
+ if ( CC_GetCommandEnt( args, &pEnt, NULL, &vecPlayerAngles ) )
+ {
+ QAngle vecEntAngles = pEnt->GetAbsAngles();
+ if ( args.ArgC() == 3 && !Q_strncmp( args[2], "allangles", 9 ) )
+ {
+ vecEntAngles = vecPlayerAngles;
+ }
+ else
+ {
+ vecEntAngles[YAW] = vecPlayerAngles[YAW];
+ }
+
+ pEnt->SetAbsAngles( vecEntAngles );
+ }
+}
+
+static ConCommand ent_orient("ent_orient", CC_Ent_Orient, "Orient the specified entity to match the player's angles. By default, only orients target entity's YAW. Use the 'allangles' option to orient on all axis.\n\tFormat: ent_orient <entity name> <optional: allangles>", FCVAR_CHEAT);
|