diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /sp/src/game/server/baseentity.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip | |
Fix line endings. WHAMMY.
Diffstat (limited to 'sp/src/game/server/baseentity.cpp')
| -rw-r--r-- | sp/src/game/server/baseentity.cpp | 14948 |
1 files changed, 7474 insertions, 7474 deletions
diff --git a/sp/src/game/server/baseentity.cpp b/sp/src/game/server/baseentity.cpp index 48f1a4a7..edc00977 100644 --- a/sp/src/game/server/baseentity.cpp +++ b/sp/src/game/server/baseentity.cpp @@ -1,7474 +1,7474 @@ -//========= 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_VISION_MODES ; index++ )
- {
- m_nModelIndexOverrides.Set( index, 0 );
- }
-#endif
-}
-
-void CBaseEntity::SetModelIndexOverride( int index, int nValue )
-{
-#ifdef TF_DLL
- if ( ( index >= VISION_MODE_NONE ) && ( index < MAX_VISION_MODES ) )
- {
- 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_VISION_MODES ),
-#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();
-
- CBasePlayer *pPlayer = UTIL_GetCommandClient();
- if (!pPlayer)
- {
- return;
- }
-
- // Don't allow regular users to create point_servercommand entities for the same reason as blocking ent_fire
- if ( !Q_stricmp( args[1], "point_servercommand" ) )
- {
- 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 create point_servercommand.
- CBasePlayer *pHostPlayer = UTIL_GetListenServerHost();
- if ( pPlayer != pHostPlayer )
- return;
- }
- }
-
- 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
- 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);
+//========= 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_VISION_MODES ; index++ ) + { + m_nModelIndexOverrides.Set( index, 0 ); + } +#endif +} + +void CBaseEntity::SetModelIndexOverride( int index, int nValue ) +{ +#ifdef TF_DLL + if ( ( index >= VISION_MODE_NONE ) && ( index < MAX_VISION_MODES ) ) + { + 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_VISION_MODES ), +#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(); + + CBasePlayer *pPlayer = UTIL_GetCommandClient(); + if (!pPlayer) + { + return; + } + + // Don't allow regular users to create point_servercommand entities for the same reason as blocking ent_fire + if ( !Q_stricmp( args[1], "point_servercommand" ) ) + { + 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 create point_servercommand. + CBasePlayer *pHostPlayer = UTIL_GetListenServerHost(); + if ( pPlayer != pHostPlayer ) + return; + } + } + + 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 + 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); |