source-sdk-2013-mapbase/sp/src/game/server/baseentity.cpp
2021-09-19 21:08:02 -05:00

10660 lines
327 KiB
C++

//========= 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"
#ifdef MAPBASE
#include "mapbase/matchers.h"
#include "mapbase/datadesc_mod.h"
#endif
#ifdef NEW_RESPONSE_SYSTEM
#include "ai_speech.h"
#endif
#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" );
ConVar sv_script_think_interval("sv_script_think_interval", "0.1");
#ifdef MAPBASE_VSCRIPT
ConVar ent_text_allow_script( "ent_text_allow_script", "1" );
#endif
// 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),
#ifdef MAPBASE
// Keep consistent with VIEW_ID_COUNT in viewrender.h
SendPropInt (SENDINFO(m_iViewHideFlags), 9, SPROP_UNSIGNED ),
SendPropBool (SENDINFO(m_bDisableFlashlight) ),
#endif
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),
SendPropStringT( SENDINFO( m_iName ) ),
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, 0 ) ),
#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( )
{
#ifdef MAPBASE_VSCRIPT
// HACKHACK: This is needed to fix a crash when an entity removes itself with Destroy() during its own think function.
// (see https://github.com/mapbase-source/source-sdk-2013/issues/138)
FOR_EACH_VEC( m_ScriptThinkFuncs, i )
{
HSCRIPT h = m_ScriptThinkFuncs[i]->m_hfnThink;
if ( h ) g_pScriptVM->ReleaseScript( h );
}
m_ScriptThinkFuncs.PurgeAndDeleteElements();
#endif // MAPBASE_VSCRIPT
// 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++;
}
#ifdef MAPBASE
if (m_ResponseContexts.Count() > 0)
{
const char *contexts = UTIL_VarArgs("%s:%s", STRING(m_ResponseContexts[0].m_iszName), STRING(m_ResponseContexts[0].m_iszValue));
for (int i = 1; i < GetContextCount(); i++)
{
contexts = UTIL_VarArgs("%s,%s:%s", contexts, STRING(m_ResponseContexts[i].m_iszName), STRING(m_ResponseContexts[i].m_iszValue));
}
Q_snprintf(tempstr, sizeof(tempstr), "Response Contexts:%s", contexts);
EntityText(offset, tempstr, 0);
offset++;
}
#endif
#ifdef MAPBASE_VSCRIPT
// 'OnEntText' hook inspired by later source games
if (m_ScriptScope.IsInitialized() && g_Hook_OnEntText.CanRunInScope( m_ScriptScope ))
{
if (ent_text_allow_script.GetBool())
{
ScriptVariant_t functionReturn;
if ( g_Hook_OnEntText.Call( m_ScriptScope, &functionReturn, NULL ) && functionReturn.m_type == FIELD_CSTRING )
{
CUtlStringList outStrings;
V_SplitString( functionReturn.m_pszString, "\n", outStrings );
FOR_EACH_VEC( outStrings, i )
{
EntityText( offset, outStrings[i], 0, 224, 240, 255 );
offset++;
}
}
}
}
#endif
}
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
#ifdef MAPBASE
CBaseEntity *pParent = gEntList.FindEntityByName( NULL, newParent, this, pActivator );
#else
CBaseEntity *pParent = gEntList.FindEntityByName( NULL, newParent, NULL, pActivator );
#endif
// 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
#ifdef MAPBASE
if ( gEntList.FindEntityByName( pParent, newParent, this, pActivator ) )
#else
if ( gEntList.FindEntityByName( pParent, newParent, NULL, pActivator ) )
#endif
{
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;
CBaseEntityOutput *pOutput = FindNamedOutput( pszOutput );
if ( pOutput )
{
pOutput->FireOutput( variant, pActivator, pCaller, flDelay );
return;
}
}
#ifdef MAPBASE_VSCRIPT
void CBaseEntity::ScriptFireOutput( const char *pszOutput, HSCRIPT hActivator, HSCRIPT hCaller, const char *szValue, float flDelay )
{
variant_t value;
value.SetString( MAKE_STRING(szValue) );
FireNamedOutput( pszOutput, value, ToEnt(hActivator), ToEnt(hCaller), flDelay );
}
float CBaseEntity::GetMaxOutputDelay( const char *pszOutput )
{
CBaseEntityOutput *pOutput = FindNamedOutput( pszOutput );
if ( pOutput )
{
return pOutput->GetMaxDelay();
}
return 0;
}
//void CBaseEntity::CancelEventsByInput( const char *szInput )
//{
// g_EventQueue.CancelEventsByInput( this, szInput );
//}
#endif // MAPBASE_VSCRIPT
CBaseEntityOutput *CBaseEntity::FindNamedOutput( const char *pszOutput )
{
if ( pszOutput == NULL )
return NULL;
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 ) )
{
return pOutput;
}
}
}
dmap = dmap->baseMap;
}
return NULL;
}
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" );
}
}
}
}
#ifndef MAPBASE // Moved below the gamerules AllowDamage() check
// Make sure our damage filter allows the damage.
if ( !PassesDamageFilter( inputInfo ))
{
return;
}
#endif
if( !g_pGameRules->AllowDamage(this, inputInfo) )
{
return;
}
#ifdef MAPBASE
// Make sure our damage filter allows the damage.
if ( !PassesFinalDamageFilter( inputInfo ))
{
return;
}
#endif
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 );
#ifdef MAPBASE
// Modify damage if we have a filter that does that
DamageFilterDamageMod(info);
#endif
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 = NULL;
if ( AI_IsSinglePlayer() )
{
pPlayer = UTIL_GetLocalPlayer();
}
else
{
// See which MP player is holding the physics object and then use that player to get the real mass of the object.
// This is ugly but better than having linkage between an object and its "holding" player.
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CBasePlayer *tempPlayer = UTIL_PlayerByIndex( i );
if ( tempPlayer && (tempPlayer->GetHeldObject() == this ) )
{
pPlayer = tempPlayer;
break;
}
}
}
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 )
{
#ifdef MAPBASE_VSCRIPT
// False = Cheat death
if (ScriptDeathHook( const_cast<CTakeDamageInfo*>(&info) ) == false)
return;
#endif
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 );
}
}
void CBaseEntity::Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info )
{
#ifdef MAPBASE_VSCRIPT
if (m_ScriptScope.IsInitialized() && g_Hook_OnKilledOther.CanRunInScope( m_ScriptScope ))
{
HSCRIPT hInfo = g_pScriptVM->RegisterInstance( const_cast<CTakeDamageInfo*>(&info) );
// victim, info
ScriptVariant_t args[] = { ScriptVariant_t( pVictim->GetScriptInstance() ), ScriptVariant_t( hInfo ) };
g_Hook_OnKilledOther.Call( m_ScriptScope, NULL, args );
g_pScriptVM->RemoveInstance( hInfo );
}
#endif
}
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_FIELD(m_iszScriptId, FIELD_STRING),
// m_ScriptScope;
// m_hScriptInstance;
DEFINE_KEYFIELD(m_iszVScripts, FIELD_STRING, "vscripts"),
DEFINE_KEYFIELD(m_iszScriptThinkFunction, FIELD_STRING, "thinkfunction"),
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" ),
#ifdef MAPBASE
DEFINE_KEYFIELD( m_iViewHideFlags, FIELD_INTEGER, "viewhideflags" ),
DEFINE_KEYFIELD( m_bDisableFlashlight, FIELD_BOOLEAN, "disableflashlight" ),
#endif
#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 ),
#ifdef MAPBASE
DEFINE_KEYFIELD( m_hOwnerEntity, FIELD_EHANDLE, "OwnerEntity" ),
#else
DEFINE_FIELD( m_hOwnerEntity, FIELD_EHANDLE ),
#endif
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" ),
#ifdef MAPBASE
// You know, m_fFlags access
DEFINE_KEYFIELD( m_fFlags, FIELD_INTEGER, "m_fFlags" ),
#else
DEFINE_FIELD( m_fFlags, FIELD_INTEGER ),
#endif
#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 ),
#ifdef MAPBASE
DEFINE_INPUTFUNC( FIELD_STRING, "ChangeVariable", InputChangeVariable ),
#endif
#ifdef MAPBASE
DEFINE_INPUTFUNC( FIELD_INPUT, "PassUser1", InputPassUser1 ),
DEFINE_INPUTFUNC( FIELD_INPUT, "PassUser2", InputPassUser2 ),
DEFINE_INPUTFUNC( FIELD_INPUT, "PassUser3", InputPassUser3 ),
DEFINE_INPUTFUNC( FIELD_INPUT, "PassUser4", InputPassUser4 ),
DEFINE_INPUTFUNC( FIELD_VOID, "FireUser1", InputFireUser1 ),
DEFINE_INPUTFUNC( FIELD_VOID, "FireUser2", InputFireUser2 ),
DEFINE_INPUTFUNC( FIELD_VOID, "FireUser3", InputFireUser3 ),
DEFINE_INPUTFUNC( FIELD_VOID, "FireUser4", InputFireUser4 ),
DEFINE_INPUTFUNC( FIELD_VOID, "FireRandomUser", InputFireRandomUser ),
DEFINE_INPUTFUNC( FIELD_INPUT, "PassRandomUser", InputPassRandomUser ),
#else
DEFINE_INPUTFUNC( FIELD_STRING, "FireUser1", InputFireUser1 ),
DEFINE_INPUTFUNC( FIELD_STRING, "FireUser2", InputFireUser2 ),
DEFINE_INPUTFUNC( FIELD_STRING, "FireUser3", InputFireUser3 ),
DEFINE_INPUTFUNC( FIELD_STRING, "FireUser4", InputFireUser4 ),
#endif
DEFINE_INPUTFUNC(FIELD_STRING, "RunScriptFile", InputRunScriptFile),
DEFINE_INPUTFUNC(FIELD_STRING, "RunScriptCode", InputRunScript),
DEFINE_INPUTFUNC(FIELD_STRING, "CallScriptFunction", InputCallScriptFunction),
#ifdef MAPBASE_VSCRIPT
DEFINE_INPUTFUNC(FIELD_STRING, "RunScriptCodeQuotable", InputRunScriptQuotable),
DEFINE_INPUTFUNC(FIELD_VOID, "ClearScriptScope", InputClearScriptScope),
#endif
#ifdef MAPBASE
DEFINE_OUTPUT( m_OutUser1, "OutUser1" ),
DEFINE_OUTPUT( m_OutUser2, "OutUser2" ),
DEFINE_OUTPUT( m_OutUser3, "OutUser3" ),
DEFINE_OUTPUT( m_OutUser4, "OutUser4" ),
#endif
DEFINE_OUTPUT( m_OnUser1, "OnUser1" ),
DEFINE_OUTPUT( m_OnUser2, "OnUser2" ),
DEFINE_OUTPUT( m_OnUser3, "OnUser3" ),
DEFINE_OUTPUT( m_OnUser4, "OnUser4" ),
#ifdef MAPBASE
DEFINE_INPUTFUNC( FIELD_STRING, "SetEntityName", InputSetEntityName ),
DEFINE_INPUTFUNC( FIELD_STRING, "SetTarget", InputSetTarget ),
DEFINE_INPUTFUNC( FIELD_EHANDLE, "SetOwnerEntity", InputSetOwnerEntity ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "AddHealth", InputAddHealth ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveHealth", InputRemoveHealth ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetMaxHealth", InputSetMaxHealth ),
DEFINE_INPUTFUNC( FIELD_STRING, "FireOutput", InputFireOutput ),
DEFINE_INPUTFUNC( FIELD_STRING, "RemoveOutput", InputRemoveOutput ),
//DEFINE_INPUTFUNC( FIELD_STRING, "CancelOutput", InputCancelOutput ), // Find a way to implement this
DEFINE_INPUTFUNC( FIELD_STRING, "ReplaceOutput", InputReplaceOutput ),
DEFINE_INPUTFUNC( FIELD_STRING, "AcceptInput", InputAcceptInput ),
DEFINE_INPUTFUNC( FIELD_VOID, "CancelPending", InputCancelPending ),
DEFINE_INPUTFUNC( FIELD_VOID, "FreeChildren", InputFreeChildren ),
DEFINE_INPUTFUNC( FIELD_VECTOR, "SetLocalOrigin", InputSetLocalOrigin ),
DEFINE_INPUTFUNC( FIELD_VECTOR, "SetLocalAngles", InputSetLocalAngles ),
DEFINE_INPUTFUNC( FIELD_VECTOR, "SetAbsOrigin", InputSetAbsOrigin ),
DEFINE_INPUTFUNC( FIELD_VECTOR, "SetAbsAngles", InputSetAbsAngles ),
DEFINE_INPUTFUNC( FIELD_VECTOR, "SetLocalVelocity", InputSetLocalVelocity ),
DEFINE_INPUTFUNC( FIELD_VECTOR, "SetLocalAngularVelocity", InputSetLocalAngularVelocity ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "AddSpawnFlags", InputAddSpawnFlags ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveSpawnFlags", InputRemoveSpawnFlags ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetRenderMode", InputSetRenderMode ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetRenderFX", InputSetRenderFX ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetViewHideFlags", InputSetViewHideFlags ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "AddEffects", InputAddEffects ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveEffects", InputRemoveEffects ),
DEFINE_INPUTFUNC( FIELD_VOID, "EnableDraw", InputDrawEntity ),
DEFINE_INPUTFUNC( FIELD_VOID, "DisableDraw", InputUndrawEntity ),
DEFINE_INPUTFUNC( FIELD_VOID, "EnableReceivingFlashlight", InputEnableReceivingFlashlight ),
DEFINE_INPUTFUNC( FIELD_VOID, "DisableReceivingFlashlight", InputDisableReceivingFlashlight ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "AddEFlags", InputAddEFlags ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveEFlags", InputRemoveEFlags ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "AddSolidFlags", InputAddSolidFlags ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveSolidFlags", InputRemoveSolidFlags ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetMoveType", InputSetMoveType ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetCollisionGroup", InputSetCollisionGroup ),
DEFINE_INPUTFUNC( FIELD_EHANDLE, "Touch", InputTouch ),
DEFINE_INPUTFUNC( FIELD_INPUT, "KilledNPC", InputKilledNPC ),
DEFINE_INPUTFUNC( FIELD_VOID, "KillIfNotVisible", InputKillIfNotVisible ),
DEFINE_INPUTFUNC( FIELD_FLOAT, "KillWhenNotVisible", InputKillWhenNotVisible ),
DEFINE_INPUTFUNC( FIELD_STRING, "SetThinkNull", InputSetThinkNull ),
DEFINE_OUTPUT( m_OnKilled, "OnKilled" ),
#endif
// 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_THINKFUNC( ScriptThink ),
#ifdef MAPBASE_VSCRIPT
DEFINE_THINKFUNC( ScriptContextThink ),
#endif
#ifdef MAPBASE
DEFINE_FUNCTION( SUB_RemoveWhenNotVisible ),
#endif
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()
#ifdef MAPBASE_VSCRIPT
ScriptHook_t CBaseEntity::g_Hook_UpdateOnRemove;
ScriptHook_t CBaseEntity::g_Hook_OnEntText;
ScriptHook_t CBaseEntity::g_Hook_VPhysicsCollision;
ScriptHook_t CBaseEntity::g_Hook_FireBullets;
ScriptHook_t CBaseEntity::g_Hook_OnDeath;
ScriptHook_t CBaseEntity::g_Hook_OnKilledOther;
ScriptHook_t CBaseEntity::g_Hook_HandleInteraction;
ScriptHook_t CBaseEntity::g_Hook_ModifyEmitSoundParams;
ScriptHook_t CBaseEntity::g_Hook_ModifySentenceParams;
#endif
BEGIN_ENT_SCRIPTDESC_ROOT( CBaseEntity, "Root class of all server-side entities" )
DEFINE_SCRIPT_INSTANCE_HELPER( &g_BaseEntityScriptInstanceHelper )
DEFINE_SCRIPTFUNC_NAMED( ConnectOutputToScript, "ConnectOutput", "Adds an I/O connection that will call the named function when the specified output fires" )
DEFINE_SCRIPTFUNC_NAMED( DisconnectOutputFromScript, "DisconnectOutput", "Removes a connected script function from an I/O event." )
DEFINE_SCRIPTFUNC( GetHealth, "" )
DEFINE_SCRIPTFUNC( SetHealth, "" )
DEFINE_SCRIPTFUNC( GetMaxHealth, "" )
DEFINE_SCRIPTFUNC( SetMaxHealth, "" )
DEFINE_SCRIPTFUNC( SetModel, "" )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetModelName, "GetModelName", "Returns the name of the model" )
DEFINE_SCRIPTFUNC_NAMED( ScriptStopSound, "StopSound", "Stops a sound from this entity." )
DEFINE_SCRIPTFUNC_NAMED( ScriptEmitSound, "EmitSound", "Plays a sound from this entity." )
DEFINE_SCRIPTFUNC_NAMED( VScriptPrecacheScriptSound, "PrecacheSoundScript", "Precache a sound for later playing." )
DEFINE_SCRIPTFUNC_NAMED( ScriptSoundDuration, "GetSoundDuration", "Returns float duration of the sound. Takes soundname and optional actormodelname.")
DEFINE_SCRIPTFUNC( GetClassname, "" )
DEFINE_SCRIPTFUNC_NAMED( GetEntityNameAsCStr, "GetName", "" )
#ifdef MAPBASE_VSCRIPT
DEFINE_SCRIPTFUNC( GetDebugName, "If name exists returns name, otherwise returns classname" )
DEFINE_SCRIPTFUNC_NAMED( SetNameAsCStr, "SetName", "" )
#endif
DEFINE_SCRIPTFUNC( GetPreTemplateName, "Get the entity name stripped of template unique decoration" )
DEFINE_SCRIPTFUNC_NAMED( GetAbsOrigin, "GetOrigin", "" )
DEFINE_SCRIPTFUNC( SetAbsOrigin, "SetAbsOrigin" )
#ifdef MAPBASE_VSCRIPT
DEFINE_SCRIPTFUNC( SetAbsAngles, "SetAbsAngles" )
DEFINE_SCRIPTFUNC( GetLocalOrigin, "GetLocalOrigin" )
DEFINE_SCRIPTFUNC( SetLocalOrigin, "SetLocalOrigin" )
DEFINE_SCRIPTFUNC( GetLocalAngles, "GetLocalAngles" )
DEFINE_SCRIPTFUNC( SetLocalAngles, "SetLocalAngles" )
#endif
DEFINE_SCRIPTFUNC_NAMED( ScriptSetOrigin, "SetOrigin", "" )
DEFINE_SCRIPTFUNC_NAMED( ScriptSetForward, "SetForwardVector", "Set the orientation of the entity to have this forward vector" )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetForward, "GetForwardVector", "Get the forward vector of the entity" )
#ifdef MAPBASE_VSCRIPT
DEFINE_SCRIPTFUNC_NAMED( ScriptGetRight, "GetRightVector", "Get the right vector of the entity" )
#endif
DEFINE_SCRIPTFUNC_NAMED( ScriptGetLeft, "GetLeftVector", SCRIPT_HIDE )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetUp, "GetUpVector", "Get the up vector of the entity" )
#ifdef MAPBASE_VSCRIPT
DEFINE_SCRIPTFUNC_NAMED( ScriptSetOriginAngles, "SetOriginAngles", "Set both the origin and the angles" )
DEFINE_SCRIPTFUNC_NAMED( ScriptSetOriginAnglesVelocity, "SetOriginAnglesVelocity", "Set the origin, the angles, and the velocity" )
DEFINE_SCRIPTFUNC_NAMED( ScriptEntityToWorldTransform, "EntityToWorldTransform", "Get the entity's transform" )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetPhysicsObject, "GetPhysicsObject", "Get the entity's physics object if it has one" )
DEFINE_SCRIPTFUNC( ApplyAbsVelocityImpulse, "" )
DEFINE_SCRIPTFUNC( ApplyLocalAngularVelocityImpulse, "" )
DEFINE_SCRIPTFUNC( BodyTarget, "" )
DEFINE_SCRIPTFUNC( HeadTarget, "" )
#endif
DEFINE_SCRIPTFUNC_NAMED( GetAbsVelocity, "GetVelocity", "" )
DEFINE_SCRIPTFUNC_NAMED( SetAbsVelocity, "SetVelocity", "" )
DEFINE_SCRIPTFUNC_NAMED( ScriptSetLocalAngularVelocity, "SetAngularVelocity", "Set the local angular velocity - takes float pitch,yaw,roll velocities" )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetLocalAngularVelocity, "GetAngularVelocity", "Get the local angular velocity - returns a vector of pitch,yaw,roll" )
DEFINE_SCRIPTFUNC_NAMED( WorldSpaceCenter, "GetCenter", "Get vector to center of object - absolute coords")
DEFINE_SCRIPTFUNC_NAMED( ScriptEyePosition, "EyePosition", "Get vector to eye position - absolute coords")
#ifdef MAPBASE_VSCRIPT
DEFINE_SCRIPTFUNC_NAMED( ScriptEyeAngles, "EyeAngles", "Get eye pitch, yaw, roll as a vector" )
#endif
DEFINE_SCRIPTFUNC_NAMED( ScriptSetAngles, "SetAngles", "Set entity pitch, yaw, roll")
DEFINE_SCRIPTFUNC_NAMED( ScriptGetAngles, "GetAngles", "Get entity pitch, yaw, roll as a vector")
DEFINE_SCRIPTFUNC( SetSize, "" )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetBoundingMins, "GetBoundingMins", "Get a vector containing min bounds, centered on object")
DEFINE_SCRIPTFUNC_NAMED( ScriptGetBoundingMaxs, "GetBoundingMaxs", "Get a vector containing max bounds, centered on object")
DEFINE_SCRIPTFUNC_NAMED( Remove, "Destroy", "" )
DEFINE_SCRIPTFUNC_NAMED( ScriptSetOwner, "SetOwner", "" )
DEFINE_SCRIPTFUNC_NAMED( GetTeamNumber, "GetTeam", "" )
DEFINE_SCRIPTFUNC_NAMED( ChangeTeam, "SetTeam", "" )
#ifdef MAPBASE_VSCRIPT
DEFINE_SCRIPTFUNC_NAMED( ScriptSetParent, "SetParent", "" )
#endif
DEFINE_SCRIPTFUNC_NAMED( ScriptGetMoveParent, "GetMoveParent", "If in hierarchy, retrieves the entity's parent" )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetRootMoveParent, "GetRootMoveParent", "If in hierarchy, walks up the hierarchy to find the root parent" )
DEFINE_SCRIPTFUNC_NAMED( ScriptFirstMoveChild, "FirstMoveChild", "" )
DEFINE_SCRIPTFUNC_NAMED( ScriptNextMovePeer, "NextMovePeer", "" )
DEFINE_SCRIPTFUNC_NAMED( KeyValueFromString, "__KeyValueFromString", SCRIPT_HIDE )
DEFINE_SCRIPTFUNC_NAMED( KeyValueFromFloat, "__KeyValueFromFloat", SCRIPT_HIDE )
DEFINE_SCRIPTFUNC_NAMED( KeyValueFromInt, "__KeyValueFromInt", SCRIPT_HIDE )
DEFINE_SCRIPTFUNC_NAMED( KeyValueFromVector, "__KeyValueFromVector", SCRIPT_HIDE )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetModelKeyValues, "GetModelKeyValues", "Get a KeyValue class instance on this entity's model")
#ifdef MAPBASE_VSCRIPT
DEFINE_SCRIPTFUNC( Activate, "" )
DEFINE_SCRIPTFUNC_NAMED( ScriptIsVisible, "IsVisible", "Check if the specified position can be visible to this entity." )
DEFINE_SCRIPTFUNC_NAMED( ScriptIsEntVisible, "IsEntVisible", "Check if the specified entity can be visible to this entity." )
DEFINE_SCRIPTFUNC_NAMED( ScriptIsVisibleWithMask, "IsVisibleWithMask", "Check if the specified position can be visible to this entity with a specific trace mask." )
DEFINE_SCRIPTFUNC_NAMED( ScriptTakeDamage, "TakeDamage", "Apply damage to this entity with a given info handle" )
DEFINE_SCRIPTFUNC_NAMED( ScriptFireBullets, "FireBullets", "Fire bullets from entity with a given info handle" )
DEFINE_SCRIPTFUNC( TakeHealth, "Give this entity health" )
DEFINE_SCRIPTFUNC( IsAlive, "Return true if this entity is alive" )
DEFINE_SCRIPTFUNC( GetWaterLevel, "Get current level of water submergence" )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetContext, "GetContext", "Get a response context value" )
DEFINE_SCRIPTFUNC_NAMED( ScriptAddContext, "AddContext", "Add a response context value" )
DEFINE_SCRIPTFUNC( GetContextExpireTime, "Get a response context's expiration time" )
DEFINE_SCRIPTFUNC( GetContextCount, "Get the number of response contexts" )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetContextIndex, "GetContextIndex", "Get a response context at a specific index in the form of a table" )
DEFINE_SCRIPTFUNC_NAMED( ScriptFollowEntity, "FollowEntity", "Begin following the specified entity. This makes this entity non-solid, parents it to the target entity, and teleports it to the specified entity's origin. The second parameter is whether or not to use bonemerging while following." )
DEFINE_SCRIPTFUNC( StopFollowingEntity, "Stops following an entity if we're following one." )
DEFINE_SCRIPTFUNC( IsFollowingEntity, "Returns true if this entity is following another entity." )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetFollowedEntity, "GetFollowedEntity", "Get the entity we're following." )
DEFINE_SCRIPTFUNC_NAMED( ScriptClassify, "Classify", "Get Class_T class ID (corresponds to the CLASS_ set of constants)" )
DEFINE_SCRIPTFUNC_NAMED( ScriptAcceptInput, "AcceptInput", "" )
DEFINE_SCRIPTFUNC_NAMED( ScriptFireOutput, "FireOutput", "Fire an entity output" )
DEFINE_SCRIPTFUNC( GetMaxOutputDelay, "Get the longest delay for all events attached to an output" )
//DEFINE_SCRIPTFUNC( CancelEventsByInput, "Cancel all I/O events for this entity, match input" ) // Commented out due to unpredictability and unknown risks
DEFINE_SCRIPTFUNC_NAMED( ScriptAddOutput, "AddOutput", "Add an output" )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetKeyValue, "GetKeyValue", "Get a keyvalue" )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorVector, "GetRenderColorVector", "Get the render color as a vector" )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorR, "GetRenderColorR", "Get the render color's R value" )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorG, "GetRenderColorG", "Get the render color's G value" )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorB, "GetRenderColorB", "Get the render color's B value" )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetAlpha, "GetRenderAlpha", "Get the render color's alpha value" )
DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorVector, "SetRenderColorVector", "Set the render color as a vector" )
DEFINE_SCRIPTFUNC_NAMED( ScriptSetColor, "SetRenderColor", "Set the render color" )
DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorR, "SetRenderColorR", "Set the render color's R value" )
DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorG, "SetRenderColorG", "Set the render color's G value" )
DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorB, "SetRenderColorB", "Set the render color's B value" )
DEFINE_SCRIPTFUNC_NAMED( ScriptSetAlpha, "SetRenderAlpha", "Set the render color's alpha value" )
// LEGACY
DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorVector, "GetColorVector", SCRIPT_HIDE )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorR, "GetColorR", SCRIPT_HIDE )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorG, "GetColorG", SCRIPT_HIDE )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorB, "GetColorB", SCRIPT_HIDE )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetAlpha, "GetAlpha", SCRIPT_HIDE )
DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorVector, "SetColorVector", SCRIPT_HIDE )
DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorR, "SetColorR", SCRIPT_HIDE )
DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorG, "SetColorG", SCRIPT_HIDE )
DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorB, "SetColorB", SCRIPT_HIDE )
DEFINE_SCRIPTFUNC_NAMED( ScriptSetAlpha, "SetAlpha", SCRIPT_HIDE )
// END LEGACY
DEFINE_SCRIPTFUNC_NAMED( ScriptGetRenderMode, "GetRenderMode", "Get render mode" )
DEFINE_SCRIPTFUNC_NAMED( ScriptSetRenderMode, "SetRenderMode", "Set render mode" )
DEFINE_SCRIPTFUNC( GetSpawnFlags, "Get spawnflags" )
DEFINE_SCRIPTFUNC( AddSpawnFlags, "Add spawnflag(s)" )
DEFINE_SCRIPTFUNC( RemoveSpawnFlags, "Remove spawnflag(s)" )
DEFINE_SCRIPTFUNC( ClearSpawnFlags, "Clear spawnflag(s)" )
DEFINE_SCRIPTFUNC( HasSpawnFlags, "Check if the entity has specific spawnflag(s) ticked" )
DEFINE_SCRIPTFUNC( GetEffects, "Get effects" )
DEFINE_SCRIPTFUNC( AddEffects, "Add effect(s)" )
DEFINE_SCRIPTFUNC( RemoveEffects, "Remove effect(s)" )
DEFINE_SCRIPTFUNC( ClearEffects, "Clear effect(s)" )
DEFINE_SCRIPTFUNC( SetEffects, "Set effect(s)" )
DEFINE_SCRIPTFUNC( IsEffectActive, "Check if an effect is active" )
DEFINE_SCRIPTFUNC( GetFlags, "Get flags" )
DEFINE_SCRIPTFUNC( AddFlag, "Add flag" )
DEFINE_SCRIPTFUNC( RemoveFlag, "Remove flag" )
DEFINE_SCRIPTFUNC( GetEFlags, "Get Eflags" )
DEFINE_SCRIPTFUNC( AddEFlags, "Add Eflags" )
DEFINE_SCRIPTFUNC( RemoveEFlags, "Remove Eflags" )
DEFINE_SCRIPTFUNC( GetTransmitState, "" )
DEFINE_SCRIPTFUNC( SetTransmitState, "" )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetMoveType, "GetMoveType", "Get the move type" )
DEFINE_SCRIPTFUNC_NAMED( ScriptSetMoveType, "SetMoveType", "Set the move type" )
DEFINE_SCRIPTFUNC( GetCollisionGroup, "Get the collision group" )
DEFINE_SCRIPTFUNC( SetCollisionGroup, "Set the collision group" )
DEFINE_SCRIPTFUNC( GetGravity, "" )
DEFINE_SCRIPTFUNC( SetGravity, "" )
DEFINE_SCRIPTFUNC( GetFriction, "" )
DEFINE_SCRIPTFUNC( SetFriction, "" )
DEFINE_SCRIPTFUNC( GetMass, "" )
DEFINE_SCRIPTFUNC( SetMass, "" )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetSolid, "GetSolid", "" )
DEFINE_SCRIPTFUNC_NAMED( ScriptSetSolid, "SetSolid", "" )
DEFINE_SCRIPTFUNC( GetSolidFlags, "Get solid flags" )
DEFINE_SCRIPTFUNC( AddSolidFlags, "Add solid flags" )
DEFINE_SCRIPTFUNC( RemoveSolidFlags, "Remove solid flags" )
DEFINE_SCRIPTFUNC( IsPlayer, "Returns true if this entity is a player." )
DEFINE_SCRIPTFUNC( IsNPC, "Returns true if this entity is a NPC." )
DEFINE_SCRIPTFUNC( IsCombatCharacter, "Returns true if this entity is a combat character (player or NPC)." )
DEFINE_SCRIPTFUNC_NAMED( IsBaseCombatWeapon, "IsWeapon", "Returns true if this entity is a weapon." )
DEFINE_SCRIPTFUNC( IsWorld, "Returns true if this entity is the world." )
DEFINE_SCRIPTFUNC_NAMED( ScriptDispatchInteraction, "DispatchInteraction", "Dispatches an interaction on this entity. See the g_interaction set of constants for more information." )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetTakeDamage, "GetTakeDamage", "Gets this entity's m_takedamage value. (DAMAGE_YES, DAMAGE_NO, etc.)" )
DEFINE_SCRIPTFUNC_NAMED( ScriptSetTakeDamage, "SetTakeDamage", "Sets this entity's m_takedamage value. (DAMAGE_YES, DAMAGE_NO, etc.)" )
// DEFINE_SCRIPTFUNC( IsMarkedForDeletion, "Returns true if the entity is valid and marked for deletion." )
#endif
DEFINE_SCRIPTFUNC( ValidateScriptScope, "Ensure that an entity's script scope has been created" )
DEFINE_SCRIPTFUNC( GetScriptScope, "Retrieve the script-side data associated with an entity" )
#ifdef MAPBASE_VSCRIPT
DEFINE_SCRIPTFUNC( GetOrCreatePrivateScriptScope, "Create and retrieve the script-side data associated with an entity" )
#endif
DEFINE_SCRIPTFUNC( GetScriptId, "Retrieve the unique identifier used to refer to the entity within the scripting system" )
DEFINE_SCRIPTFUNC_NAMED( GetScriptOwnerEntity, "GetOwner", "Gets this entity's owner" )
DEFINE_SCRIPTFUNC_NAMED( SetScriptOwnerEntity, "SetOwner", "Sets this entity's owner" )
DEFINE_SCRIPTFUNC( entindex, "" )
#ifdef MAPBASE_VSCRIPT
DEFINE_SCRIPTFUNC_NAMED( ScriptSetThinkFunction, "SetThinkFunction", "" )
DEFINE_SCRIPTFUNC_NAMED( ScriptStopThinkFunction, "StopThinkFunction", "" )
DEFINE_SCRIPTFUNC_NAMED( ScriptSetContextThink, "SetContextThink", "Set a think function on this entity." )
DEFINE_SCRIPTFUNC_NAMED( ScriptSetThink, "SetThink", "" )
DEFINE_SCRIPTFUNC_NAMED( ScriptStopThink, "StopThink", "" )
//
// Hooks
//
DEFINE_SIMPLE_SCRIPTHOOK( CBaseEntity::g_Hook_UpdateOnRemove, "UpdateOnRemove", FIELD_VOID, "Called when the entity is being removed." )
DEFINE_SIMPLE_SCRIPTHOOK( CBaseEntity::g_Hook_OnEntText, "OnEntText", FIELD_CSTRING, "Called every frame when ent_text is enabled on the entity. Return a string to be added to the ent_text printout." )
BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_VPhysicsCollision, "VPhysicsCollision", FIELD_VOID, "Called for every single VPhysics-related collision experienced by this entity." )
DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT )
DEFINE_SCRIPTHOOK_PARAM( "speed", FIELD_FLOAT )
DEFINE_SCRIPTHOOK_PARAM( "point", FIELD_VECTOR )
DEFINE_SCRIPTHOOK_PARAM( "normal", FIELD_VECTOR )
END_SCRIPTHOOK()
BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_FireBullets, "FireBullets", FIELD_VOID, "Called for every single VPhysics-related collision experienced by this entity." )
DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT )
DEFINE_SCRIPTHOOK_PARAM( "speed", FIELD_FLOAT )
DEFINE_SCRIPTHOOK_PARAM( "point", FIELD_VECTOR )
DEFINE_SCRIPTHOOK_PARAM( "normal", FIELD_VECTOR )
END_SCRIPTHOOK()
BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_OnDeath, "OnDeath", FIELD_BOOLEAN, "Called when the entity dies (Event_Killed). Returning false makes the entity cancel death, although this could have unforeseen consequences. For hooking any damage instead of just death, see filter_script and PassesFinalDamageFilter." )
DEFINE_SCRIPTHOOK_PARAM( "info", FIELD_HSCRIPT )
END_SCRIPTHOOK()
BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_OnKilledOther, "OnKilledOther", FIELD_VOID, "Called when the entity kills another entity." )
DEFINE_SCRIPTHOOK_PARAM( "victim", FIELD_HSCRIPT )
DEFINE_SCRIPTHOOK_PARAM( "info", FIELD_HSCRIPT )
END_SCRIPTHOOK()
BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_HandleInteraction, "HandleInteraction", FIELD_BOOLEAN, "Called for internal game interactions. See the g_interaction set of constants for more information. Returning true or false will return that value without falling to any internal handling. Returning nothing will allow the interaction to fall to any internal handling." )
DEFINE_SCRIPTHOOK_PARAM( "interaction", FIELD_INTEGER )
//DEFINE_SCRIPTHOOK_PARAM( "data", FIELD_VARIANT )
DEFINE_SCRIPTHOOK_PARAM( "sourceEnt", FIELD_HSCRIPT )
END_SCRIPTHOOK()
BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_ModifyEmitSoundParams, "ModifyEmitSoundParams", FIELD_VOID, "Called every time a sound is emitted on this entity, allowing for its parameters to be modified." )
DEFINE_SCRIPTHOOK_PARAM( "params", FIELD_HSCRIPT )
END_SCRIPTHOOK()
BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_ModifySentenceParams, "ModifySentenceParams", FIELD_VOID, "Called every time a sentence is emitted on this entity, allowing for its parameters to be modified." )
DEFINE_SCRIPTHOOK_PARAM( "params", FIELD_HSCRIPT )
END_SCRIPTHOOK()
#endif
END_SCRIPTDESC();
// 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;
}
if ( m_hScriptInstance )
{
#ifdef MAPBASE_VSCRIPT
if (m_ScriptScope.IsInitialized())
{
g_Hook_UpdateOnRemove.Call( m_ScriptScope, NULL, NULL );
}
#endif // MAPBASE_VSCRIPT
g_pScriptVM->RemoveInstance( m_hScriptInstance );
m_hScriptInstance = NULL;
}
}
//-----------------------------------------------------------------------------
// 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 &params )
{
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 &params )
{
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];
#ifdef MAPBASE_VSCRIPT
if (m_ScriptScope.IsInitialized() && g_Hook_VPhysicsCollision.CanRunInScope(m_ScriptScope))
{
Vector vecContactPoint;
pEvent->pInternalData->GetContactPoint( vecContactPoint );
Vector vecSurfaceNormal;
pEvent->pInternalData->GetSurfaceNormal( vecSurfaceNormal );
// entity, speed, point, normal
ScriptVariant_t args[] = { ScriptVariant_t( pHitEntity->GetScriptInstance() ), pEvent->collisionSpeed, vecContactPoint, vecSurfaceNormal };
g_Hook_VPhysicsCollision.Call( m_ScriptScope, NULL, args );
}
#endif
// 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());
#ifdef MAPBASE
return pFilter->PassesDamageFilter(this, info);
#else
return pFilter->PassesDamageFilter(info);
#endif
}
return true;
}
#ifdef MAPBASE
//-----------------------------------------------------------------------------
// Purpose: A damage filter pass for when this is most certainly the part where we might actually take damage.
// Made for the "damage" family of filters, including filter_damage_transfer.
//-----------------------------------------------------------------------------
bool CBaseEntity::PassesFinalDamageFilter( const CTakeDamageInfo &info )
{
if (!PassesDamageFilter(info))
return false;
if (m_hDamageFilter)
{
CBaseFilter *pFilter = (CBaseFilter *)(m_hDamageFilter.Get());
if (!pFilter->PassesFinalDamageFilter(this, info))
{
return false;
}
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: A hack for damage transfers.
//-----------------------------------------------------------------------------
bool CBaseEntity::DamageFilterAllowsBlood( const CTakeDamageInfo &info )
{
if (m_hDamageFilter)
{
CBaseFilter *pFilter = (CBaseFilter *)(m_hDamageFilter.Get());
if (!pFilter->BloodAllowed(this, info))
{
return false;
}
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Modifies damage taken. Returns true if damage was successfully modded.
//-----------------------------------------------------------------------------
bool CBaseEntity::DamageFilterDamageMod( CTakeDamageInfo &info )
{
if (m_hDamageFilter)
{
CBaseFilter *pFilter = (CBaseFilter *)(m_hDamageFilter.Get());
if (pFilter->DamageMod(this, info))
{
return true;
}
}
return false;
}
#endif
FORCEINLINE bool NamesMatch( const char *pszQuery, string_t nameToMatch )
{
#ifdef MAPBASE
// NamesMatch has been turned into Matcher_NamesMatch in matchers.h
// for a wider range of accessibility and flexibility.
return Matcher_NamesMatch(pszQuery, STRING(nameToMatch));
#else
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;
#endif
}
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()
{
#ifndef MAPBASE // It's your fault if you're trying to load old, broken saves from a possibly closed 2013 beta in Mapbase.
#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
#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.Get() != NULL_STRING )
{
return STRING(m_iName.Get());
}
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
// mapper debug message
#ifdef MAPBASE
CGMsg( 2, CON_GROUP_IO_SYSTEM, "(%0.2f) input %s: %s.%s(%s)\n", gpGlobals->curtime, pCaller ? STRING(pCaller->m_iName.Get()) : "<NULL>", GetDebugName(), szInputName, Value.String() );
#else
DevMsg( 2, "(%0.2f) input %s: %s.%s(%s)\n", gpGlobals->curtime, pCaller ? STRING(pCaller->m_iName.Get()) : "<NULL>", GetDebugName(), szInputName, Value.String() );
#endif
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
{
#ifdef MAPBASE
// Activator, etc. support for EHANDLE convert
if ( !Value.Convert( (fieldtype_t)dmap->dataDesc[i].fieldType, this, pActivator, pCaller ) )
{
bool bBadConversion = true;
// Attempt to convert to string and back.
// Almost all field types support being converted to a string, and many support being parsed from a string too.
fieldtype_t originalfield = Value.FieldType();
if (Value.Convert(FIELD_STRING))
{
bBadConversion = !(Value.Convert((fieldtype_t)dmap->dataDesc[i].fieldType, this, pActivator, pCaller));
if (!bBadConversion)
{
// Actual support should be added for each field, but if it works, it works.
// Warning against it only matters if you're a programmer and want to add support for each field.
// Only send a warning in dev mode.
DevWarning("!! Had to convert to string and back\n"
"!! Source Field Type: %i, Target Field Type: %i\n",
originalfield, dmap->dataDesc[i].fieldType);
}
}
if (bBadConversion)
{
Warning( "!! ERROR: bad input/output link:\n!! Unable to convert value \"%s\" from %s (%s) to field type %i\n!! Target Entity: %s (%s), Input: %s\n",
Value.GetDebug(),
( pCaller != NULL ) ? STRING(pCaller->m_iClassname) : "<null>",
( pCaller != NULL ) ? STRING(pCaller->m_iName.Get()) : "<null>",
dmap->dataDesc[i].fieldType,
STRING(m_iClassname), GetDebugName(), szInputName );
return false;
}
}
#else
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.Get()) : "<null>" );
return false;
}
#endif
}
}
// 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;
// Now, see if there's a function named Input<Name of Input> in this entity's script file.
// If so, execute it and let it decide whether to allow the default behavior to also execute.
bool bCallInputFunc = true; // Always assume default behavior (do call the input function)
if ( m_ScriptScope.IsInitialized() )
{
ScriptVariant_t functionReturn;
if ( ScriptInputHook( szInputName, pActivator, pCaller, Value, functionReturn ) )
{
bCallInputFunc = functionReturn.m_bool;
}
}
if( bCallInputFunc )
{
(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;
}
}
}
}
#ifdef MAPBASE_VSCRIPT
// Allow VScript to handle unhandled inputs.
if (m_ScriptScope.IsInitialized())
{
ScriptVariant_t functionReturn;
if ( ScriptInputHook( szInputName, pActivator, pCaller, Value, functionReturn ) )
{
if (functionReturn.m_bool)
return true;
}
}
#endif
#ifdef MAPBASE
CGMsg( 2, CON_GROUP_IO_SYSTEM, "unhandled input: (%s) -> (%s,%s)\n", szInputName, STRING(m_iClassname), GetDebugName() );
#else
DevMsg( 2, "unhandled input: (%s) -> (%s,%s)\n", szInputName, STRING(m_iClassname), GetDebugName()/*,", from (%s,%s)" STRING(pCaller->m_iClassname), STRING(pCaller->m_iName.Get())*/ );
#endif
return false;
}
#ifdef MAPBASE_VSCRIPT
bool CBaseEntity::ScriptAcceptInput( const char *szInputName, const char *szValue, HSCRIPT hActivator, HSCRIPT hCaller )
{
variant_t value;
value.SetString( MAKE_STRING( szValue ) );
return AcceptInput( szInputName, ToEnt( hActivator ), ToEnt( hCaller ), value, 0 );
}
#endif
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CBaseEntity::ScriptInputHook( const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t Value, ScriptVariant_t &functionReturn )
{
char szScriptFunctionName[255];
Q_strcpy( szScriptFunctionName, "Input" );
Q_strcat( szScriptFunctionName, szInputName, 255 );
g_pScriptVM->SetValue( "activator", ( pActivator ) ? ScriptVariant_t( pActivator->GetScriptInstance() ) : SCRIPT_VARIANT_NULL );
g_pScriptVM->SetValue( "caller", ( pCaller ) ? ScriptVariant_t( pCaller->GetScriptInstance() ) : SCRIPT_VARIANT_NULL );
#ifdef MAPBASE_VSCRIPT
Value.SetScriptVariant( functionReturn );
g_pScriptVM->SetValue( "parameter", functionReturn );
#endif
bool bHandled = false;
if( CallScriptFunction( szScriptFunctionName, &functionReturn ) )
{
bHandled = true;
}
g_pScriptVM->ClearValue( "activator" );
g_pScriptVM->ClearValue( "caller" );
#ifdef MAPBASE_VSCRIPT
g_pScriptVM->ClearValue( "parameter" );
#endif
return bHandled;
}
#ifdef MAPBASE_VSCRIPT
bool CBaseEntity::ScriptDeathHook( CTakeDamageInfo *info )
{
if (m_ScriptScope.IsInitialized() && g_Hook_OnDeath.CanRunInScope( m_ScriptScope ))
{
HSCRIPT hInfo = g_pScriptVM->RegisterInstance( info );
// info
ScriptVariant_t functionReturn;
ScriptVariant_t args[] = { ScriptVariant_t( hInfo ) };
if ( g_Hook_OnDeath.Call( m_ScriptScope, &functionReturn, args ) && (functionReturn.m_type == FIELD_BOOLEAN && functionReturn.m_bool == false) )
{
// Make this entity cheat death
g_pScriptVM->RemoveInstance( hInfo );
return false;
}
g_pScriptVM->RemoveInstance( hInfo );
}
return true;
}
#endif
//-----------------------------------------------------------------------------
// 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 );
#ifdef MAPBASE
IGameEvent *event = gameeventmanager->CreateEvent( "player_use" );
if ( event )
{
event->SetInt( "userid", inputdata.pActivator && inputdata.pActivator->IsPlayer() ?
((CBasePlayer*)inputdata.pActivator)->GetUserID() : 0 );
event->SetInt( "entity", entindex() );
gameeventmanager->FireEvent( event );
}
#endif // MAPBASE
}
//-----------------------------------------------------------------------------
// 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 );
}
#ifdef MAPBASE
m_OnKilled.FireOutput( inputdata.pActivator, this );
#endif
#ifdef MAPBASE
// Kick players
if ( IsPlayer() )
{
engine->ServerCommand( UTIL_VarArgs( "kickid %d CBaseEntity::InputKill()\n", engine->GetPlayerUserId(edict()) ) );
}
else
{
UTIL_Remove( this );
}
#else
UTIL_Remove( this );
#endif
}
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 );
}
#ifdef MAPBASE
m_OnKilled.FireOutput( inputdata.pActivator, this );
// Kicking players in InputKillHierarchy does not exist in future Valve games
// if ( IsPlayer() )
#endif
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 &params )
{
}
#ifdef MAPBASE
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CBaseEntity::DispatchInteraction( int interactionType, void *data, CBaseCombatCharacter* sourceEnt )
{
if (interactionType <= 0)
return false;
if (m_ScriptScope.IsInitialized() && g_Hook_HandleInteraction.CanRunInScope( m_ScriptScope ))
{
//HSCRIPT hData = g_pScriptVM->RegisterInstance( data );
// interaction, data, sourceEnt
ScriptVariant_t functionReturn;
ScriptVariant_t args[] = { interactionType/*, ScriptVariant_t( hData )*/, ScriptVariant_t( ToHScript( sourceEnt ) ) };
if ( g_Hook_HandleInteraction.Call( m_ScriptScope, &functionReturn, args ) && (functionReturn.m_type == FIELD_BOOLEAN) )
{
// Return the interaction here
//g_pScriptVM->RemoveInstance( hData );
return functionReturn.m_bool;
}
//g_pScriptVM->RemoveInstance( hData );
}
return HandleInteraction( interactionType, data, sourceEnt );
}
#endif
//-----------------------------------------------------------------------------
// 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();
}
if ( IsPlayer() )
{
// Tell the client being teleported
IGameEvent *event = gameeventmanager->CreateEvent( "base_player_teleported" );
if ( event )
{
event->SetInt( "entindex", entindex() );
gameeventmanager->FireEventClientSide( event );
}
}
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, ' ', sizeof( token ) );
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 );
}
#ifdef MAPBASE
inline bool UtlStringLessFunc( const CUtlString &lhs, const CUtlString &rhs )
{
return Q_stricmp( lhs.String(), rhs.String() ) < 0;
}
//------------------------------------------------------------------------------
// Purpose : More concommands needed access to entities, so this has been moved to its own function.
// Input : cmdname - The name of the command.
// &commands - Where the complete autocompletes should be sent to.
// substring - The current search query. (only pool entities that start with this)
// checklen - The number of characters to check.
// Output : A pointer to a cUtlRBTRee containing all of the entities.
//------------------------------------------------------------------------------
static int AutoCompleteEntities(const char *cmdname, CUtlVector< CUtlString > &commands, CUtlRBTree< CUtlString > &symbols, char *substring, int checklen = 0)
{
CBaseEntity *pos = NULL;
while ((pos = gEntList.NextEnt(pos)) != NULL)
{
const char *name = pos->GetClassname();
if (pos->GetEntityName() == NULL_STRING || Q_strnicmp(STRING(pos->GetEntityName()), substring, checklen))
{
if (Q_strnicmp(pos->GetClassname(), substring, checklen))
continue;
}
else
name = STRING(pos->GetEntityName());
CUtlString sym = name;
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();
}
#endif
//------------------------------------------------------------------------------
// 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 DumpScriptScope(CBasePlayer* pPlayer, const char* name)
{
CBaseEntity* pEntity = NULL;
while ((pEntity = GetNextCommandEntity(pPlayer, name, pEntity)) != NULL)
{
if (pEntity->m_ScriptScope.IsInitialized())
{
Msg("----Script Dump for entity %s\n", pEntity->GetDebugName());
HSCRIPT hDumpScopeFunc = g_pScriptVM->LookupFunction("__DumpScope");
g_pScriptVM->Call(hDumpScopeFunc, NULL, true, NULL, 1, (HSCRIPT)pEntity->m_ScriptScope);
Msg("----End Script Dump\n");
}
else
{
DevWarning("ent_script_dump: Entity %s has no script scope!\n", pEntity->GetDebugName());
}
}
}
//------------------------------------------------------------------------------
void CC_Ent_Script_Dump( const CCommand& args )
{
DumpScriptScope(UTIL_GetCommandClient(),args[1]);
}
static ConCommand ent_script_dump("ent_script_dump", CC_Ent_Script_Dump, "Dumps the names and values of this entity's script scope to the console\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
//------------------------------------------------------------------------------
#ifdef MAPBASE
class CEntTextAutoCompletionFunctor : public ICommandCallback, public ICommandCompletionCallback
{
public:
virtual void CommandCallback( const CCommand &command )
{
SetDebugBits(UTIL_GetCommandClient(), command.Arg(1), OVERLAY_TEXT_BIT);
}
virtual int CommandCompletionCallback( const char *partial, CUtlVector< CUtlString > &commands )
{
if ( !g_pGameRules )
{
return 0;
}
const char *cmdname = "ent_text";
char *substring = (char *)partial;
if ( Q_strstr( partial, cmdname ) )
{
substring = (char *)partial + strlen( cmdname ) + 1;
}
int checklen = Q_strlen( substring );
CUtlRBTree< CUtlString > symbols( 0, 0, UtlStringLessFunc );
return AutoCompleteEntities(cmdname, commands, symbols, substring, checklen);
}
};
static CEntTextAutoCompletionFunctor g_EntTextAutoComplete;
static ConCommand ent_text("ent_text", &g_EntTextAutoComplete, "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, &g_EntTextAutoComplete);
#else
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);
#endif
//------------------------------------------------------------------------------
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);
#ifndef MAPBASE
static bool UtlStringLessFunc( const CUtlString &lhs, const CUtlString &rhs )
{
return Q_stricmp( lhs.String(), rhs.String() ) < 0;
}
#endif
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;
#ifdef MAPBASE
float delay = 0;
#else
int delay = 0;
#endif
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 )
{
#ifdef MAPBASE
delay = atof( command.Arg( 4 ) );
#else
delay = atoi( command.Arg( 4 ) );
#endif
}
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 );
}
#ifdef MAPBASE
CUtlRBTree< CUtlString > symbols( 0, 0, UtlStringLessFunc );
return AutoCompleteEntities(cmdname, commands, symbols, substring, checklen);
#else
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();
#endif
}
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
#ifdef MAPBASE
CBasePlayer *pPlayer = UTIL_GetCommandClient();
CBaseEntity *target = gEntList.FindEntityGeneric( NULL, targetEntity, pPlayer, pPlayer, pPlayer );
#else
CBaseEntity *target = gEntList.FindEntityByName( NULL, targetEntity );
#endif
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;
#ifndef MAPBASE // What did input variables ever do to you?
// Only want input functions
if ( field->flags & FTYPEDESC_SAVE )
continue;
#endif
// 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);
#ifdef MAPBASE
//------------------------------------------------------------------------------
// Purpose :
// Input :
// Output :
//------------------------------------------------------------------------------
void CC_Ent_Info_Datatable( const CCommand& args )
{
CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() );
if (!pPlayer)
{
return;
}
if ( args.ArgC() < 2 )
{
ClientPrint( pPlayer, HUD_PRINTCONSOLE, "Usage:\n ent_info_datatable <class name>\n" );
}
else
{
// Each element corresponds to a specific field type.
// Hey, if you've got a better idea, be my guest.
static const char *g_FieldStrings[FIELD_TYPECOUNT] =
{
"VOID",
"FLOAT",
"STRING",
"VECTOR",
"QUATERNION",
"INTEGER",
"BOOLEAN",
"SHORT",
"CHARACTER",
"COLOR32",
"EMBEDDED",
"CUSTOM",
"CLASSPTR",
"EHANDLE",
"EDICT",
"POSITION_VECTOR",
"TIME",
"TICK",
"MODELNAME",
"SOUNDNAME",
"INPUT",
"FUNCTION",
"VMATRIX",
"VMATRIX_WORLDSPACE",
"MATRIX3X4_WORLDSPACE",
"INTERVAL",
"MODELINDEX",
"MATERIALINDEX",
"VECTOR2D",
};
// iterate through all the ents printing out their details
CBaseEntity *ent = CreateEntityByName( args[1] );
if ( ent )
{
#define ENT_INFO_BY_HIERARCHY 1
#ifdef ENT_INFO_BY_HIERARCHY
CUtlVector<const char*> dmap_namelist;
CUtlVector< CUtlVector<const char*> > dmap_fieldlist;
CUtlVector< CUtlVector<int> > dmap_fieldtypelist;
datamap_t *dmap;
int dmapnum = 0;
for ( dmap = ent->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap )
{
dmap_fieldlist.AddToTail();
dmap_fieldtypelist.AddToTail();
// search through all the actions in the data description, printing out details
for ( int i = 0; i < dmap->dataNumFields; i++ )
{
dmap_fieldlist[dmapnum].AddToTail(dmap->dataDesc[i].fieldName);
dmap_fieldtypelist[dmapnum].AddToTail(dmap->dataDesc[i].fieldType);
}
dmapnum++;
dmap_namelist.AddToTail(dmap->dataClassName);
}
char offset[64] = { 0 }; // Needed so garbage isn't spewed at the beginning
for ( int i = 0; i < dmapnum; i++ )
{
Q_strncat(offset, " ", sizeof(offset));
// Header for each class
ClientPrint( pPlayer, HUD_PRINTCONSOLE, UTIL_VarArgs("%s=========| %s |=========\n", offset, dmap_namelist[i]) );
Q_strncat(offset, " ", sizeof(offset));
int iFieldCount = dmap_fieldlist[i].Count();
for ( int index = 0; index < iFieldCount; index++ )
{
int iType = dmap_fieldtypelist[i][index];
ClientPrint( pPlayer, HUD_PRINTCONSOLE, UTIL_VarArgs("%s%s (%i): %s\n", offset, g_FieldStrings[iType], iType, dmap_fieldlist[i][index]) );
}
// Clean up after ourselves
dmap_fieldlist[i].RemoveAll();
dmap_fieldtypelist[i].RemoveAll();
}
#else // This sorts by field type instead
CUtlVector<const char*> fieldlist[FIELD_TYPECOUNT];
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++ )
{
fieldlist[dmap->dataDesc[i].fieldType].AddToTail(dmap->dataDesc[i].fieldName);
}
}
for ( int i = 0; i < FIELD_TYPECOUNT; i++ )
{
const char *typestring = g_FieldStrings[i];
for ( int index = 0; index < fieldlist[i].Count(); index++ )
{
ClientPrint( pPlayer, HUD_PRINTCONSOLE, UTIL_VarArgs(" %s (%i): %s\n", typestring, i, fieldlist[i][index]) );
}
// Clean up after ourselves
fieldlist[i].RemoveAll();
}
#endif
delete ent;
}
else
{
ClientPrint( pPlayer, HUD_PRINTCONSOLE, UTIL_VarArgs("no such entity %s\n", args[1]) );
}
}
}
static ConCommand ent_info_datatable("ent_info_datatable", CC_Ent_Info_Datatable, "Usage:\n ent_info_datatable <class name>\n", FCVAR_CHEAT);
#endif
//------------------------------------------------------------------------------
// 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 ) );
}
#ifndef MAPBASE // We do this later now so contexts can override criteria. I originally didn't want to remove it here in case there would be problems, but I think I have all of the bases covered.
// Append anything from I/O or keyvalues pairs
AppendContextToCriteria( set );
#endif
if( hl2_episodic.GetBool() )
{
set.AppendCriteria( "episodic", "1" );
}
// Append anything from world I/O/keyvalues with "world" as prefix
#ifdef MAPBASE
CWorld *world = GetWorldEntity();
#else
CWorld *world = dynamic_cast< CWorld * >( CBaseEntity::Instance( engine->PEntityOfEntIndex( 0 ) ) );
#endif
if ( world )
{
world->AppendContextToCriteria( set, "world" );
}
#ifdef MAPBASE
// Append base stuff
set.AppendCriteria("spawnflags", UTIL_VarArgs("%i", GetSpawnFlags()));
set.AppendCriteria("flags", UTIL_VarArgs("%i", GetFlags()));
#endif
}
//-----------------------------------------------------------------------------
// 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 );
}
}
#ifdef MAPBASE
//-----------------------------------------------------------------------------
// Purpose:
// Input : set -
// "" -
//-----------------------------------------------------------------------------
void CBaseEntity::ReAppendContextCriteria( AI_CriteriaSet& set )
{
// Append contexts again. This allows it to override standard criteria, including that of derived classes.
CWorld *world = GetWorldEntity();
if ( world )
{
// I didn't know this until recently, but world contexts are actually prefixed by "world".
// I'm changing this here to reduce confusion and allow greater potential.
// (e.g. disabling combine soldier episodic on/off in Episodic binaries with world context "episodic:0", even though that's not happening anymore)
// The old prefixed ones still exist. They're just not appended again.
world->AppendContextToCriteria( set );
}
AppendContextToCriteria( set );
}
#endif
//-----------------------------------------------------------------------------
// 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;
}
#ifdef MAPBASE
//-----------------------------------------------------------------------------
// Purpose: Searches entity for named context string and/or value.
// Intended to be called by entities rather than the conventional response system.
// Input : *name - Context name.
// *value - Context value. (optional)
// Output : bool
//-----------------------------------------------------------------------------
bool CBaseEntity::HasContext( const char *name, const char *value ) const
{
int c = m_ResponseContexts.Count();
for ( int i = 0; i < c; i++ )
{
if ( Matcher_NamesMatch( name, STRING(m_ResponseContexts[i].m_iszName) ) )
{
if (value == NULL)
return true;
else
return Matcher_Match(STRING(m_ResponseContexts[i].m_iszValue), value);
}
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Searches entity for named context string and/or value.
// Intended to be called by entities rather than the conventional response system.
// Input : *name - Context name.
// *value - Context value. (optional)
// Output : bool
//-----------------------------------------------------------------------------
bool CBaseEntity::HasContext( string_t name, string_t value ) const
{
int c = m_ResponseContexts.Count();
for ( int i = 0; i < c; i++ )
{
if ( name == m_ResponseContexts[i].m_iszName )
{
if (value == NULL_STRING)
return true;
else
return value == m_ResponseContexts[i].m_iszValue;
}
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Searches entity for named context string and/or value.
// Intended to be called by entities rather than the conventional response system.
// Input : *nameandvalue - Context name and value.
// Output : bool
//-----------------------------------------------------------------------------
bool CBaseEntity::HasContext( const char *nameandvalue ) const
{
char key[ 128 ];
char value[ 128 ];
const char *p = nameandvalue;
while ( p )
{
#ifdef NEW_RESPONSE_SYSTEM
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), NULL, nameandvalue );
#else
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), NULL );
#endif
return HasContext( key, value );
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : index -
// Output : const char
//-----------------------------------------------------------------------------
const char *CBaseEntity::GetContextValue( const char *contextName ) const
{
int idx = FindContextByName( contextName );
if ( idx == -1 )
return "";
return m_ResponseContexts[ idx ].m_iszValue.ToCStr();
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
float CBaseEntity::GetContextExpireTime( const char *name )
{
int idx = FindContextByName( name );
if ( idx == -1 )
return 0.0f;
return m_ResponseContexts[ idx ].m_fExpirationTime;
}
//-----------------------------------------------------------------------------
// Purpose: Internal method or removing contexts and can remove multiple contexts in one call
// Input : *contextName -
//-----------------------------------------------------------------------------
void CBaseEntity::RemoveContext( const char *contextName )
{
char key[ 128 ];
char value[ 128 ];
float duration;
const char *p = contextName;
while ( p )
{
duration = 0.0f;
#ifdef NEW_RESPONSE_SYSTEM
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), &duration, contextName );
#else
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), &duration );
#endif
if ( duration )
{
duration += gpGlobals->curtime;
}
int iIndex = FindContextByName( key );
if ( iIndex != -1 )
{
m_ResponseContexts.Remove( iIndex );
}
}
}
#endif
//-----------------------------------------------------------------------------
// 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 );
}
#ifdef MAPBASE
void CBaseEntity::InputPassUser1( inputdata_t& inputdata )
{
m_OutUser1.Set( inputdata.value, inputdata.pActivator, this );
}
void CBaseEntity::InputPassUser2( inputdata_t& inputdata )
{
m_OutUser2.Set( inputdata.value, inputdata.pActivator, this );
}
void CBaseEntity::InputPassUser3( inputdata_t& inputdata )
{
m_OutUser3.Set( inputdata.value, inputdata.pActivator, this );
}
void CBaseEntity::InputPassUser4( inputdata_t& inputdata )
{
m_OutUser4.Set( inputdata.value, inputdata.pActivator, this );
}
void CBaseEntity::InputFireRandomUser( inputdata_t& inputdata )
{
switch (RandomInt(1, 4))
{
case 1: m_OnUser1.FireOutput( inputdata.pActivator, this ); break;
case 2: m_OnUser2.FireOutput( inputdata.pActivator, this ); break;
case 3: m_OnUser3.FireOutput( inputdata.pActivator, this ); break;
case 4: m_OnUser4.FireOutput( inputdata.pActivator, this ); break;
}
}
void CBaseEntity::InputPassRandomUser( inputdata_t& inputdata )
{
switch (RandomInt(1, 4))
{
case 1: m_OutUser1.Set( inputdata.value, inputdata.pActivator, this ); break;
case 2: m_OutUser2.Set( inputdata.value, inputdata.pActivator, this ); break;
case 3: m_OutUser3.Set( inputdata.value, inputdata.pActivator, this ); break;
case 4: m_OutUser4.Set( inputdata.value, inputdata.pActivator, this ); break;
}
}
#endif
#ifdef MAPBASE
//-----------------------------------------------------------------------------
// Purpose: Sets the entity's targetname.
//-----------------------------------------------------------------------------
void CBaseEntity::InputSetEntityName( inputdata_t& inputdata )
{
SetName( inputdata.value.StringID() );
}
//-----------------------------------------------------------------------------
// Purpose: Sets the generic target field.
//-----------------------------------------------------------------------------
void CBaseEntity::InputSetTarget( inputdata_t& inputdata )
{
m_target = inputdata.value.StringID();
Activate();
}
//-----------------------------------------------------------------------------
// Purpose: Sets our owner entity.
//-----------------------------------------------------------------------------
void CBaseEntity::InputSetOwnerEntity( inputdata_t& inputdata )
{
SetOwnerEntity(inputdata.value.Entity());
}
//-----------------------------------------------------------------------------
// Purpose: Input handler for adding to the entity's health.
// Input : Integer health points to add.
//-----------------------------------------------------------------------------
void CBaseEntity::InputAddHealth( inputdata_t &inputdata )
{
TakeHealth( abs(inputdata.value.Int()), DMG_GENERIC );
}
//-----------------------------------------------------------------------------
// Purpose: Input handler for removing health from the entity.
// Input : Integer health points to remove.
//-----------------------------------------------------------------------------
void CBaseEntity::InputRemoveHealth( inputdata_t &inputdata )
{
TakeDamage( CTakeDamageInfo( this, this, abs(inputdata.value.Int()), DMG_GENERIC ) );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBaseEntity::InputSetHealth( inputdata_t &inputdata )
{
int iNewHealth = inputdata.value.Int();
int iDelta = abs(GetHealth() - iNewHealth);
if ( iNewHealth > GetHealth() )
{
TakeHealth( iDelta, DMG_GENERIC );
}
else if ( iNewHealth < GetHealth() )
{
TakeDamage( CTakeDamageInfo( this, this, iDelta, DMG_GENERIC ) );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBaseEntity::InputSetMaxHealth( inputdata_t &inputdata )
{
int iNewMaxHealth = inputdata.value.Int();
SetMaxHealth(iNewMaxHealth);
if (GetHealth() > iNewMaxHealth)
{
SetHealth(iNewMaxHealth);
}
}
//-----------------------------------------------------------------------------
// Purpose: Forces the named output to fire.
// In addition to the output itself, parameter may include !activator, !caller, and what to pass.
//-----------------------------------------------------------------------------
void CBaseEntity::InputFireOutput( inputdata_t& inputdata )
{
char sParameter[MAX_PATH];
Q_strncpy( sParameter, inputdata.value.String(), sizeof(sParameter) );
if ( sParameter )
{
int iter = 0;
char *data[5] = {sParameter};
char *sToken = strtok( sParameter, ":" );
while ( sToken && iter < 5 )
{
data[iter] = sToken;
iter++;
sToken = strtok( NULL, ":" );
}
//DevMsg("data[0]: %s\ndata[1]: %s\ndata[2]: %s\ndata[3]: %s\ndata[4]: %s\n", data[0], data[1], data[2], data[3], data[4]);
// Format: <output name>:<activator>:<caller>:<parameter>:<delay>
//
// data[0] = Output Name
// data[1] = Activator
// data[2] = Caller
// data[3] = Parameter
// data[4] = Delay
//
CBaseEntity *pActivator = inputdata.pActivator;
if (data[1])
pActivator = gEntList.FindEntityByName(NULL, data[1], this, inputdata.pActivator, inputdata.pCaller);
CBaseEntity *pCaller = this;
if (data[2])
pCaller = gEntList.FindEntityByName(NULL, data[2], this, inputdata.pActivator, inputdata.pCaller);
variant_t parameter;
if (data[3])
{
parameter.SetString(MAKE_STRING(data[3]));
}
float flDelay = 0.0f;
if (data[4])
flDelay = atof(data[4]);
FireNamedOutput(data[0], parameter, pActivator, pCaller, flDelay);
//Msg("Output Name: %s, Activator: %s, Caller: %s, Data: %s, Delay: %f\n", data[0], pActivator->GetDebugName(), pCaller->GetDebugName(), parameter.String(), flDelay);
}
else
{
Warning("FireOutput input fired with bad parameter. Format: <output name>:<activator>:<caller>:<parameter>:<delay>\n");
}
}
//-----------------------------------------------------------------------------
// Purpose: Removes all outputs of the specified name.
//-----------------------------------------------------------------------------
void CBaseEntity::InputRemoveOutput( inputdata_t& inputdata )
{
const char *szOutput = inputdata.value.String();
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 ) )
{
// If our names match, remove
if (Matcher_NamesMatch(szOutput, dataDesc->externalName))
{
CBaseEntityOutput *pOutput = (CBaseEntityOutput *)((int)this + (int)dataDesc->fieldOffset[0]);
pOutput->DeleteAllElements();
}
}
}
dmap = dmap->baseMap;
}
}
// Find a way to implement this
/*
//------------------------------------------------------------------------------
// Purpose: Cancels all I/O events of a specific output.
//------------------------------------------------------------------------------
void CBaseEntity::InputCancelOutput( inputdata_t &inputdata )
{
}
*/
//-----------------------------------------------------------------------------
// Purpose: Replaces all outputs of the specified name.
//-----------------------------------------------------------------------------
void CBaseEntity::InputReplaceOutput( inputdata_t& inputdata )
{
char sParameter[128];
Q_strncpy( sParameter, inputdata.value.String(), sizeof(sParameter) );
if (!sParameter)
return;
int iter = 0;
char *data[2];
char *sToken = strtok( sParameter, ": " );
while ( sToken && iter < 2 )
{
data[iter] = sToken;
iter++;
sToken = strtok( NULL, ": " );
}
const char *szOutput = data[0];
const char *szNewOutput = data[1];
if (!szOutput || !szNewOutput)
{
Warning("ReplaceOutput input fired with bad parameter. Format: <old output name>:<new output name>\n");
return;
}
int iOutputsReplaced = 0;
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 ) )
{
// If our names match, replace
if (Matcher_NamesMatch(szOutput, dataDesc->externalName))
{
CBaseEntityOutput *pOutput = (CBaseEntityOutput *)((int)this + (int)dataDesc->fieldOffset[0]);
const char *szTarget;
const char *szInputName;
const char *szParam;
float flDelay;
int iNumTimes;
char szData[256];
for ( CEventAction *ev = pOutput->GetActionList(); ev != NULL; ev = ev->m_pNext )
{
// This is the only way I think we could do this. Accomplishes the job more or less anyway
szTarget = STRING(ev->m_iTarget);
szInputName = STRING(ev->m_iTargetInput);
szParam = ev->m_iParameter == NULL_STRING ? "" : STRING(ev->m_iParameter);
flDelay = ev->m_flDelay;
iNumTimes = ev->m_nTimesToFire;
Q_snprintf(szData, sizeof(szData), "%s,%s,%s,%f,%i", szTarget, szInputName, szParam, flDelay, iNumTimes);
KeyValue(szNewOutput, szData);
DevMsg("ReplaceOutput: %s %s\n", szNewOutput, szData);
iOutputsReplaced++;
}
pOutput->DeleteAllElements();
}
}
}
dmap = dmap->baseMap;
}
if (iOutputsReplaced == 0)
{
Warning("ReplaceOutput unable to find %s on %s\n", szOutput, GetDebugName());
}
else
{
DevMsg("Replaced %i instances of %s with %s on %s\n", iOutputsReplaced, szOutput, szNewOutput, GetDebugName());
}
}
//-----------------------------------------------------------------------------
// Purpose: Forces the named input to fire...what?
// Inputception...or is it just "Inception"? Whatever.
// True inception would be using this input to fire AcceptInput.
// (it would probably crash, I haven't tested it)
//
// In addition to the input itself, parameter may include !activator, !caller, and what to pass.
//-----------------------------------------------------------------------------
void CBaseEntity::InputAcceptInput( inputdata_t& inputdata )
{
char sParameter[MAX_PATH];
Q_strncpy( sParameter, inputdata.value.String(), sizeof(sParameter) );
if ( sParameter )
{
int iter = 0;
char *data[5] = {sParameter};
char *sToken = strtok( sParameter, ":" );
while ( sToken && iter < 5 )
{
data[iter] = sToken;
iter++;
sToken = strtok( NULL, ":" );
}
//DevMsg("data[0]: %s\ndata[1]: %s\ndata[2]: %s\ndata[3]: %s\ndata[4]: %s\n", data[0], data[1], data[2], data[3], data[4]);
// Format: <input name>:<parameter>:<activator>:<caller>:<output ID>
//
// data[0] = Input Name
// data[1] = Parameter
// data[2] = Activator
// data[3] = Caller
// data[4] = Output ID
//
variant_t parameter;
if (data[1])
{
parameter.SetString(MAKE_STRING(data[1]));
}
CBaseEntity *pActivator = inputdata.pActivator;
if (data[2])
pActivator = gEntList.FindEntityByName(NULL, data[2], this, inputdata.pActivator, inputdata.pCaller);
CBaseEntity *pCaller = this;
if (data[3])
pCaller = gEntList.FindEntityByName(NULL, data[3], this, inputdata.pActivator, inputdata.pCaller);
int iOutputID = -1;
if (data[4])
iOutputID = atoi(data[4]);
AcceptInput(data[0], pActivator, pCaller, parameter, iOutputID);
Msg("Input Name: %s, Activator: %s, Caller: %s, Data: %s, Output ID: %i\n", data[0], pActivator ? pActivator->GetDebugName() : "None", pCaller ? pCaller->GetDebugName() : "None", parameter.String(), iOutputID);
}
else
{
Warning("AcceptInput input fired with bad parameter. Format: <input name>:<parameter>:<activator>:<caller>:<output ID>\n");
}
}
//------------------------------------------------------------------------------
// Purpose: Cancels any I/O events in the queue that were fired by this entity.
//------------------------------------------------------------------------------
void CBaseEntity::InputCancelPending( inputdata_t &inputdata )
{
g_EventQueue.CancelEvents( this );
}
//-----------------------------------------------------------------------------
// Purpose: Frees all of our children, entities parented to this entity.
//-----------------------------------------------------------------------------
void CBaseEntity::InputFreeChildren( inputdata_t& inputdata )
{
UnlinkAllChildren( this );
}
//-----------------------------------------------------------------------------
// Purpose: Sets our origin.
//-----------------------------------------------------------------------------
void CBaseEntity::InputSetLocalOrigin( inputdata_t& inputdata )
{
Vector vec;
inputdata.value.Vector3D(vec);
SetLocalOrigin(vec);
}
//-----------------------------------------------------------------------------
// Purpose: Sets our angles.
//-----------------------------------------------------------------------------
void CBaseEntity::InputSetLocalAngles( inputdata_t& inputdata )
{
QAngle ang;
inputdata.value.Angle3D(ang);
SetLocalAngles(ang);
}
//-----------------------------------------------------------------------------
// Purpose: Sets our origin.
//-----------------------------------------------------------------------------
void CBaseEntity::InputSetAbsOrigin( inputdata_t& inputdata )
{
Vector vec;
inputdata.value.Vector3D(vec);
SetAbsOrigin(vec);
}
//-----------------------------------------------------------------------------
// Purpose: Sets our angles.
//-----------------------------------------------------------------------------
void CBaseEntity::InputSetAbsAngles( inputdata_t& inputdata )
{
QAngle ang;
inputdata.value.Angle3D(ang);
SetAbsAngles(ang);
}
//-----------------------------------------------------------------------------
// Purpose: Sets our velocity.
//-----------------------------------------------------------------------------
void CBaseEntity::InputSetLocalVelocity( inputdata_t& inputdata )
{
Vector vec;
inputdata.value.Vector3D(vec);
SetLocalVelocity(vec);
}
//-----------------------------------------------------------------------------
// Purpose: Sets our angular velocity.
//-----------------------------------------------------------------------------
void CBaseEntity::InputSetLocalAngularVelocity( inputdata_t& inputdata )
{
Vector vec;
inputdata.value.Vector3D(vec);
SetLocalAngularVelocity(QAngle(vec.x, vec.y, vec.z));
}
//-----------------------------------------------------------------------------
// Purpose: Adds spawn flags.
//-----------------------------------------------------------------------------
void CBaseEntity::InputAddSpawnFlags( inputdata_t& inputdata )
{
AddSpawnFlags(inputdata.value.Int());
}
//-----------------------------------------------------------------------------
// Purpose: Removes spawn flags.
//-----------------------------------------------------------------------------
void CBaseEntity::InputRemoveSpawnFlags( inputdata_t& inputdata )
{
RemoveSpawnFlags(inputdata.value.Int());
}
//-----------------------------------------------------------------------------
// Purpose: Sets our render mode.
//-----------------------------------------------------------------------------
void CBaseEntity::InputSetRenderMode( inputdata_t& inputdata )
{
SetRenderMode((RenderMode_t)inputdata.value.Int());
}
//-----------------------------------------------------------------------------
// Purpose: Sets our render FX.
//-----------------------------------------------------------------------------
void CBaseEntity::InputSetRenderFX( inputdata_t& inputdata )
{
m_nRenderFX = inputdata.value.Int();
}
//-----------------------------------------------------------------------------
// Purpose: Sets our view hide flags.
//-----------------------------------------------------------------------------
void CBaseEntity::InputSetViewHideFlags( inputdata_t& inputdata )
{
m_iViewHideFlags = inputdata.value.Int();
}
//-----------------------------------------------------------------------------
// Purpose: Adds effects.
//-----------------------------------------------------------------------------
void CBaseEntity::InputAddEffects( inputdata_t& inputdata )
{
AddEffects(inputdata.value.Int());
}
//-----------------------------------------------------------------------------
// Purpose: Removes effects.
//-----------------------------------------------------------------------------
void CBaseEntity::InputRemoveEffects( inputdata_t& inputdata )
{
RemoveEffects(inputdata.value.Int());
}
//-----------------------------------------------------------------------------
// Purpose: Shortcut for removing nodraw.
//-----------------------------------------------------------------------------
void CBaseEntity::InputDrawEntity( inputdata_t& inputdata )
{
RemoveEffects(EF_NODRAW);
}
//-----------------------------------------------------------------------------
// Purpose: Shortcut to adding nodraw.
//-----------------------------------------------------------------------------
void CBaseEntity::InputUndrawEntity( inputdata_t& inputdata )
{
AddEffects(EF_NODRAW);
}
//-----------------------------------------------------------------------------
// Purpose: Inspired by the Portal 2 input of the same name.
//-----------------------------------------------------------------------------
void CBaseEntity::InputEnableReceivingFlashlight( inputdata_t& inputdata )
{
m_bDisableFlashlight = false;
}
//-----------------------------------------------------------------------------
// Purpose: Inspired by the Portal 2 input of the same name.
//-----------------------------------------------------------------------------
void CBaseEntity::InputDisableReceivingFlashlight( inputdata_t& inputdata )
{
m_bDisableFlashlight = true;
}
//-----------------------------------------------------------------------------
// Purpose: Adds eflags.
//-----------------------------------------------------------------------------
void CBaseEntity::InputAddEFlags( inputdata_t& inputdata )
{
AddEFlags(inputdata.value.Int());
}
//-----------------------------------------------------------------------------
// Purpose: Removes eflags.
//-----------------------------------------------------------------------------
void CBaseEntity::InputRemoveEFlags( inputdata_t& inputdata )
{
RemoveEFlags(inputdata.value.Int());
}
//-----------------------------------------------------------------------------
// Purpose: Adds solid flags.
//-----------------------------------------------------------------------------
void CBaseEntity::InputAddSolidFlags( inputdata_t& inputdata )
{
AddSolidFlags(inputdata.value.Int());
}
//-----------------------------------------------------------------------------
// Purpose: Removes solid flags.
//-----------------------------------------------------------------------------
void CBaseEntity::InputRemoveSolidFlags( inputdata_t& inputdata )
{
RemoveSolidFlags(inputdata.value.Int());
}
//-----------------------------------------------------------------------------
// Purpose: Sets the movetype.
//-----------------------------------------------------------------------------
void CBaseEntity::InputSetMoveType( inputdata_t& inputdata )
{
SetMoveType((MoveType_t)inputdata.value.Int());
}
//-----------------------------------------------------------------------------
// Purpose: Sets the collision group.
//-----------------------------------------------------------------------------
void CBaseEntity::InputSetCollisionGroup( inputdata_t& inputdata )
{
SetCollisionGroup(inputdata.value.Int());
}
//-----------------------------------------------------------------------------
// Purpose: Touch touch :)
//-----------------------------------------------------------------------------
void CBaseEntity::InputTouch( inputdata_t& inputdata )
{
if (inputdata.value.Entity())
Touch( inputdata.value.Entity() );
else
Warning( "%s InputTouch: Can't touch null entity", GetDebugName() );
}
//-----------------------------------------------------------------------------
// Purpose: Passes KilledNPC to our possibly more capable parents.
//-----------------------------------------------------------------------------
void CBaseEntity::InputKilledNPC( inputdata_t &inputdata )
{
// Don't get stuck in an endless loop
if (inputdata.value.Int() > 16)
return;
else
inputdata.value.SetInt(inputdata.value.Int() + 1);
if (GetOwnerEntity())
{
GetOwnerEntity()->AcceptInput("KilledNPC", inputdata.pActivator, inputdata.pCaller, inputdata.value, inputdata.nOutputID);
}
else if (HasPhysicsAttacker(4.0f))
{
HasPhysicsAttacker(4.0f)->AcceptInput("KilledNPC", inputdata.pActivator, inputdata.pCaller, inputdata.value, inputdata.nOutputID);
}
else if (GetMoveParent())
{
GetMoveParent()->AcceptInput("KilledNPC", inputdata.pActivator, inputdata.pCaller, inputdata.value, inputdata.nOutputID);
}
}
//-----------------------------------------------------------------------------
// Purpose: Remove if not visible by any players
//-----------------------------------------------------------------------------
void CBaseEntity::InputKillIfNotVisible( inputdata_t& inputdata )
{
#ifdef MAPBASE_MP
// Go through each client and check if we're in their viewcone.
// If we're in someone's viewcone, return immediately.
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
if ( pPlayer && pPlayer->FInViewCone( this ) )
return;
}
#else
CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
if ( !pPlayer || !pPlayer->FInViewCone( this ) )
#endif
{
m_OnKilled.FireOutput(inputdata.pActivator, this);
UTIL_Remove(this);
}
}
//-----------------------------------------------------------------------------
// Purpose: Remove when not visible by any players
//-----------------------------------------------------------------------------
void CBaseEntity::InputKillWhenNotVisible( inputdata_t& inputdata )
{
SetContextThink( &CBaseEntity::SUB_RemoveWhenNotVisible, gpGlobals->curtime + inputdata.value.Float(), "SUB_RemoveWhenNotVisible" );
//SetRenderColorA( 255 );
//m_nRenderMode = kRenderNormal;
}
//-----------------------------------------------------------------------------
// Purpose: Stop thinking
//-----------------------------------------------------------------------------
void CBaseEntity::InputSetThinkNull( inputdata_t& inputdata )
{
const char *szContext = inputdata.value.String();
if (szContext && szContext[0] != '\0')
{
SetContextThink( NULL, TICK_NEVER_THINK, szContext );
}
else
{
SetThink( NULL );
SetNextThink( TICK_NEVER_THINK );
}
}
#endif
//---------------------------------------------------------
// Use the string as the filename of a script file
// that should be loaded from disk, compiled, and run.
//---------------------------------------------------------
void CBaseEntity::InputRunScriptFile(inputdata_t& inputdata)
{
RunScriptFile(inputdata.value.String());
}
//---------------------------------------------------------
// Send the string to the VM as source code and execute it
//---------------------------------------------------------
void CBaseEntity::InputRunScript(inputdata_t& inputdata)
{
RunScript(inputdata.value.String(), "InputRunScript");
}
//---------------------------------------------------------
// Make an explicit function call.
//---------------------------------------------------------
void CBaseEntity::InputCallScriptFunction(inputdata_t& inputdata)
{
CallScriptFunction(inputdata.value.String(), NULL);
}
#ifdef MAPBASE_VSCRIPT
//---------------------------------------------------------
// Send the string to the VM as source code and execute it
//---------------------------------------------------------
void CBaseEntity::InputRunScriptQuotable(inputdata_t& inputdata)
{
char szQuotableCode[1024];
if (V_StrSubst( inputdata.value.String(), "''", "\"", szQuotableCode, sizeof( szQuotableCode ), false ))
{
RunScript( szQuotableCode, "InputRunScriptQuotable" );
}
else
{
RunScript( inputdata.value.String(), "InputRunScriptQuotable" );
}
}
//---------------------------------------------------------
// Clear this entity's script scope
//---------------------------------------------------------
void CBaseEntity::InputClearScriptScope(inputdata_t& inputdata)
{
m_ScriptScope.Term();
}
#endif
// #define VMPROFILE // define to profile vscript calls
#ifdef VMPROFILE
float g_debugCumulativeTime = 0.0;
float g_debugCounter = 0;
#define START_VMPROFILE float debugStartTime = Plat_FloatTime();
#define UPDATE_VMPROFILE \
g_debugCumulativeTime += Plat_FloatTime() - debugStartTime; \
g_debugCounter++; \
if ( g_debugCounter >= 500 ) \
{ \
DevMsg("***VSCRIPT PROFILE***: %s %s: %6.4f milliseconds\n", "500 vscript function calls", "", g_debugCumulativeTime*1000.0 ); \
g_debugCounter = 0; \
g_debugCumulativeTime = 0.0; \
} \
#else
#define START_VMPROFILE
#define UPDATE_VMPROFILE
#endif // VMPROFILE
//-----------------------------------------------------------------------------
// Returns true if the function was located and called. false otherwise.
// NOTE: Assumes the function takes no parameters at the moment.
//-----------------------------------------------------------------------------
bool CBaseEntity::CallScriptFunction(const char* pFunctionName, ScriptVariant_t* pFunctionReturn)
{
START_VMPROFILE
if (!ValidateScriptScope())
{
DevMsg("\n***\nFAILED to create private ScriptScope. ABORTING script\n***\n");
return false;
}
HSCRIPT hFunc = m_ScriptScope.LookupFunction(pFunctionName);
if (hFunc)
{
m_ScriptScope.Call(hFunc, pFunctionReturn);
m_ScriptScope.ReleaseFunction(hFunc);
UPDATE_VMPROFILE
return true;
}
return false;
}
#ifdef MAPBASE_VSCRIPT
//-----------------------------------------------------------------------------
// Gets a function handle
//-----------------------------------------------------------------------------
HSCRIPT CBaseEntity::LookupScriptFunction(const char* pFunctionName)
{
START_VMPROFILE
if (!m_ScriptScope.IsInitialized())
{
return NULL;
}
return m_ScriptScope.LookupFunction(pFunctionName);
}
//-----------------------------------------------------------------------------
// Calls and releases a function handle (ASSUMES SCRIPT SCOPE AND FUNCTION ARE VALID!)
//-----------------------------------------------------------------------------
bool CBaseEntity::CallScriptFunctionHandle(HSCRIPT hFunc, ScriptVariant_t* pFunctionReturn)
{
m_ScriptScope.Call(hFunc, pFunctionReturn);
m_ScriptScope.ReleaseFunction(hFunc);
UPDATE_VMPROFILE
return true;
}
#endif
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CBaseEntity::ConnectOutputToScript(const char* pszOutput, const char* pszScriptFunc)
{
CBaseEntityOutput* pOutput = FindNamedOutput(pszOutput);
if (!pOutput)
{
DevMsg(2, "Script failed to find output \"%s\"\n", pszOutput);
return;
}
string_t iszSelf = AllocPooledString("!self"); // @TODO: cache this [4/25/2008 tom]
CEventAction* pAction = pOutput->GetActionList();
while (pAction)
{
if (pAction->m_iTarget == iszSelf &&
pAction->m_flDelay == 0 &&
pAction->m_nTimesToFire == EVENT_FIRE_ALWAYS &&
V_strcmp(STRING(pAction->m_iTargetInput), "CallScriptFunction") == 0 &&
V_strcmp(STRING(pAction->m_iParameter), pszScriptFunc) == 0)
{
return;
}
pAction = pAction->m_pNext;
}
pAction = new CEventAction(NULL);
pAction->m_iTarget = iszSelf;
pAction->m_iTargetInput = AllocPooledString("CallScriptFunction");
pAction->m_iParameter = AllocPooledString(pszScriptFunc);
pOutput->AddEventAction(pAction);
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CBaseEntity::DisconnectOutputFromScript(const char* pszOutput, const char* pszScriptFunc)
{
CBaseEntityOutput* pOutput = FindNamedOutput(pszOutput);
if (!pOutput)
{
DevMsg(2, "Script failed to find output \"%s\"\n", pszOutput);
return;
}
string_t iszSelf = AllocPooledString("!self"); // @TODO: cache this [4/25/2008 tom]
CEventAction* pAction = pOutput->GetActionList();
while (pAction)
{
if (pAction->m_iTarget == iszSelf &&
pAction->m_flDelay == 0 &&
pAction->m_nTimesToFire == EVENT_FIRE_ALWAYS &&
V_strcmp(STRING(pAction->m_iTargetInput), "CallScriptFunction") == 0 &&
V_strcmp(STRING(pAction->m_iParameter), pszScriptFunc) == 0)
{
pOutput->RemoveEventAction(pAction);
delete pAction;
return;
}
pAction = pAction->m_pNext;
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CBaseEntity::ScriptThink(void)
{
ScriptVariant_t varThinkRetVal;
if (CallScriptFunction(m_iszScriptThinkFunction.ToCStr(), &varThinkRetVal))
{
float flThinkFrequency = 0.0f;
if (!varThinkRetVal.AssignTo(&flThinkFrequency))
{
// use default think interval if script think function doesn't provide one
flThinkFrequency = sv_script_think_interval.GetFloat();
}
SetNextThink( gpGlobals->curtime + flThinkFrequency, "ScriptThink" );
}
else
{
DevWarning("%s FAILED to call script think function %s!\n", GetDebugName(), STRING(m_iszScriptThinkFunction));
}
}
#ifdef MAPBASE_VSCRIPT
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CBaseEntity::ScriptSetThinkFunction( const char *szFunc, float flTime )
{
// Empty string stops thinking
if (!szFunc || szFunc[0] == '\0')
{
ScriptStopThinkFunction();
}
else
{
m_iszScriptThinkFunction = AllocPooledString(szFunc);
flTime = max( 0, flTime );
SetContextThink( &CBaseEntity::ScriptThink, gpGlobals->curtime + flTime, "ScriptThink" );
}
}
void CBaseEntity::ScriptStopThinkFunction()
{
m_iszScriptThinkFunction = NULL_STRING;
SetContextThink( NULL, TICK_NEVER_THINK, "ScriptThink" );
}
#endif // MAPBASE_VSCRIPT
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
const char* CBaseEntity::GetScriptId()
{
#ifdef MAPBASE_VSCRIPT
return STRING(m_iszScriptId);
#else
return STRING(m_iszScriptThinkFunction);
#endif
}
//-----------------------------------------------------------------------------
// Recreate the old behaviour of GetScriptId under a new function
//-----------------------------------------------------------------------------
// const char* CBaseEntity::GetScriptThinkFunction()
// {
// return STRING(m_iszScriptThinkFunction);
// }
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
HSCRIPT CBaseEntity::GetScriptScope()
{
return m_ScriptScope;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
HSCRIPT CBaseEntity::ScriptGetMoveParent(void)
{
return ToHScript(GetMoveParent());
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
HSCRIPT CBaseEntity::ScriptGetRootMoveParent()
{
return ToHScript(GetRootMoveParent());
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
HSCRIPT CBaseEntity::ScriptFirstMoveChild(void)
{
return ToHScript(FirstMoveChild());
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
HSCRIPT CBaseEntity::ScriptNextMovePeer(void)
{
return ToHScript(NextMovePeer());
}
//-----------------------------------------------------------------------------
// Purpose: Load, compile, and run a script file from disk.
// Input : *pScriptFile - The filename of the script file.
// bUseRootScope - If true, runs this script in the root scope, not
// in this entity's private scope.
//-----------------------------------------------------------------------------
bool CBaseEntity::RunScriptFile(const char* pScriptFile, bool bUseRootScope)
{
if (!ValidateScriptScope())
{
DevMsg("\n***\nFAILED to create private ScriptScope. ABORTING script\n***\n");
return false;
}
if (bUseRootScope)
{
return VScriptRunScript(pScriptFile);
}
else
{
return VScriptRunScript(pScriptFile, m_ScriptScope, true);
}
}
//-----------------------------------------------------------------------------
// Purpose: Compile and execute a discrete string of script source code
// Input : *pScriptText - A string containing script code to compile and run
//-----------------------------------------------------------------------------
bool CBaseEntity::RunScript(const char* pScriptText, const char* pDebugFilename)
{
if (!ValidateScriptScope())
{
DevMsg("\n***\nFAILED to create private ScriptScope. ABORTING script\n***\n");
return false;
}
if (m_ScriptScope.Run(pScriptText, pDebugFilename) == SCRIPT_ERROR)
{
DevWarning(" Entity %s encountered an error in RunScript()\n", GetDebugName());
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *contextName -
//-----------------------------------------------------------------------------
void CBaseEntity::AddContext( const char *contextName )
{
char key[ 128 ];
char value[ 128 ];
float duration = 0.0f;
const char *p = contextName;
while ( p )
{
duration = 0.0f;
#ifdef NEW_RESPONSE_SYSTEM
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), &duration, contextName );
#else
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), &duration );
#endif
if ( duration )
{
duration += gpGlobals->curtime;
}
AddContext( key, value, duration );
}
}
void CBaseEntity::AddContext( const char *name, const char *value, float duration )
{
int iIndex = FindContextByName( name );
if ( iIndex != -1 )
{
// Set the existing context to the new value
#ifdef NEW_RESPONSE_SYSTEM
char buf[64];
if ( RR::CApplyContextOperator::FindOperator( value )->Apply(
m_ResponseContexts[iIndex].m_iszValue.ToCStr(), value, buf, sizeof(buf) ) )
{
m_ResponseContexts[iIndex].m_iszValue = AllocPooledString( buf );
}
else
{
Warning( "RR: could not apply operator %s to prior value %s\n",
value, m_ResponseContexts[iIndex].m_iszValue.ToCStr() );
m_ResponseContexts[iIndex].m_iszValue = AllocPooledString( value );
}
#else
m_ResponseContexts[iIndex].m_iszValue = AllocPooledString( value );
#endif
m_ResponseContexts[iIndex].m_fExpirationTime = duration;
}
else
{
ResponseContext_t newContext;
newContext.m_iszName = AllocPooledString( name );
#ifdef NEW_RESPONSE_SYSTEM
char buf[64];
if ( RR::CApplyContextOperator::FindOperator( value )->Apply(
NULL, value, buf, sizeof(buf) ) )
{
newContext.m_iszValue = AllocPooledString( buf );
}
else
{
newContext.m_iszValue = AllocPooledString( value );
}
#else
newContext.m_iszValue = AllocPooledString( value );
#endif
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");
}
}
#ifdef MAPBASE
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CBaseEntity::InputChangeVariable( inputdata_t &inputdata )
{
const char *szKeyName = NULL;
const char *szValue = NULL;
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, ':' );
}
szKeyName = sOutputName;
szValue = sChar + 1;
}
else
{
Warning("ChangeVariable input fired with bad string. Format: <output name> <targetname>,<inputname>,<parameter>,<delay>,<max times to fire (-1 == infinite)>\n");
return;
}
if (szKeyName == NULL)
return;
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_SAVE | FTYPEDESC_KEY) )
{
if ( Matcher_NamesMatch(szKeyName, dmap->dataDesc[i].fieldName) )
{
// Copied from ::ParseKeyvalue...or technically logic_datadesc_accessor...
typedescription_t *pField = &dmap->dataDesc[i];
char *data = Datadesc_SetFieldString( szValue, this, pField, NULL );
if (!data)
{
Warning( "%s cannot set field of type %i.\n", GetDebugName(), dmap->dataDesc[i].fieldType );
}
}
}
}
}
}
#endif
//-----------------------------------------------------------------------------
// Purpose:
// Input : *conceptName -
//-----------------------------------------------------------------------------
void CBaseEntity::DispatchResponse( const char *conceptName )
{
#ifdef NEW_RESPONSE_SYSTEM
#undef IResponseSystem
using namespace ResponseRules;
#endif
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 );
#ifdef MAPBASE
ReAppendContextCriteria( set );
#endif
// 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 ) );
#ifdef NEW_RESPONSE_SYSTEM
switch (result.GetType())
{
case ResponseRules::RESPONSE_SPEAK:
{
EmitSound(response);
}
break;
case ResponseRules::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 ResponseRules::RESPONSE_SCENE:
{
// Try to fire scene w/o an actor
InstancedScriptedScene(NULL, response);
}
break;
case ResponseRules::RESPONSE_PRINT:
{
}
break;
case ResponseRules::RESPONSE_ENTITYIO:
{
CAI_Expresser::FireEntIOFromResponse(response, this);
break;
}
#ifdef MAPBASE_VSCRIPT
case ResponseRules::RESPONSE_VSCRIPT:
{
CAI_Expresser::RunScriptResponse( this, response, &set, false );
break;
}
case ResponseRules::RESPONSE_VSCRIPT_FILE:
{
CAI_Expresser::RunScriptResponse( this, response, &set, true );
break;
}
#endif
default:
// Don't know how to handle .vcds!!!
break;
}
#else
#ifdef MAPBASE
if (response[0] == '$')
{
const char *context = response + 1;
const char *replace = GetContextValue(context);
if (replace)
{
DevMsg("Replacing %s with %s...\n", response, replace);
Q_strncpy(response, replace, sizeof(response));
// Precache it now because it may not have been precached before
switch ( result.GetType() )
{
case RESPONSE_SPEAK:
{
PrecacheScriptSound( response );
}
break;
case RESPONSE_SCENE:
{
// TODO: Gender handling?
PrecacheInstancedScene( response );
}
break;
}
}
}
#endif
switch ( result.GetType() )
{
case RESPONSE_SPEAK:
{
EmitSound( response );
}
break;
case RESPONSE_SENTENCE:
{
#ifdef MAPBASE
if (response[0] != '!')
{
SENTENCEG_PlayRndSz( edict(), response, 1, result.GetSoundLevel(), 0, PITCH_NORM );
break;
}
#endif
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:
{
#ifdef MAPBASE
// Most flexing actors that use scenes override DispatchResponse via CAI_Expresser in ai_speech.
// So, in order for non-actors to use scenes by themselves, they actually don't really use them at all.
// Most scenes that would be used as responses only have one sound, so we take the first sound in a scene and emit it manually.
//
// Of course, env_speaker uses scenes without using itself as an actor, but that overrides DispatchResponse in Mapbase
// with the original code intact. Hopefully no other entity uses this like that...
//if (!ClassMatches("env_speaker"))
{
// Expand gender string
GenderExpandString( response, response, sizeof( response ) );
// Trust that it's been precached
const char *pszSound = GetFirstSoundInScene(response);
EmitSound(pszSound);
}
//else
// InstancedScriptedScene(NULL, response);
#else
// Try to fire scene w/o an actor
InstancedScriptedScene( NULL, response );
#endif
}
break;
case RESPONSE_PRINT:
{
}
break;
default:
// Don't know how to handle .vcds!!!
break;
}
#endif
}
//-----------------------------------------------------------------------------
// 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 );
}
#ifdef MAPBASE
ReAppendContextCriteria( set );
#endif
// 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 );
}
}
}
#ifndef MAPBASE // Moved to SoundEmitterSystem.cpp
//-----------------------------------------------------------------------------
// 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 );
}
#endif
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 )
{
#ifdef MAPBASE
// This was meant for KillWhenNotVisible before it used its own function,
// but there's not really any harm for keeping this here.
m_OnKilled.FireOutput(this, this);
#endif
UTIL_Remove(this);
}
else
{
SetNextThink( gpGlobals->curtime );
}
}
#ifdef MAPBASE
//-----------------------------------------------------------------------------
// Purpose: For KillWhenNotVisible, based off of SUB_FadeOut
//-----------------------------------------------------------------------------
void CBaseEntity::SUB_RemoveWhenNotVisible( void )
{
if ( SUB_AllowedToFade() == false )
{
SetNextThink( gpGlobals->curtime + 1, "SUB_RemoveWhenNotVisible" );
SetRenderColorA( 255 );
return;
}
SetRenderColorA( m_clrRender->a - 1 );
if ( m_clrRender->a == 0 )
{
m_OnKilled.FireOutput(this, this);
UTIL_Remove(this);
}
else
{
SetNextThink( gpGlobals->curtime, "SUB_RemoveWhenNotVisible" );
}
}
#endif
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 );
}
}
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
HSCRIPT CBaseEntity::GetScriptInstance()
{
if (!m_hScriptInstance)
{
if (m_iszScriptId == NULL_STRING)
{
char* szName = (char*)stackalloc(1024);
g_pScriptVM->GenerateUniqueKey((m_iName.Get() != NULL_STRING) ? STRING(GetEntityName()) : GetClassname(), szName, 1024);
m_iszScriptId = AllocPooledString(szName);
}
m_hScriptInstance = g_pScriptVM->RegisterInstance(GetScriptDesc(), this);
g_pScriptVM->SetInstanceUniqeId(m_hScriptInstance, STRING(m_iszScriptId));
}
return m_hScriptInstance;
}
//-----------------------------------------------------------------------------
// Using my edict, cook up a unique VScript scope that's private to me, and
// persistent.
//-----------------------------------------------------------------------------
bool CBaseEntity::ValidateScriptScope()
{
if (!m_ScriptScope.IsInitialized())
{
if (scriptmanager == NULL)
{
ExecuteOnce(DevMsg("Cannot execute script because scripting is disabled (-scripting)\n"));
return false;
}
if (g_pScriptVM == NULL)
{
ExecuteOnce(DevMsg(" Cannot execute script because there is no available VM\n"));
return false;
}
// Force instance creation
GetScriptInstance();
EHANDLE hThis;
hThis.Set(this);
bool bResult = m_ScriptScope.Init(STRING(m_iszScriptId));
if (!bResult)
{
DevMsg("%s couldn't create ScriptScope!\n", GetDebugName());
return false;
}
g_pScriptVM->SetValue(m_ScriptScope, "self", GetScriptInstance());
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Run all of the vscript files that are set in this entity's VSCRIPTS
// field in Hammer. The list is space-delimited.
//-----------------------------------------------------------------------------
void CBaseEntity::RunVScripts()
{
if (m_iszVScripts == NULL_STRING)
{
return;
}
#ifdef MAPBASE_VSCRIPT
if (g_pScriptVM == NULL)
{
return;
}
#endif
ValidateScriptScope();
// All functions we want to have call chained instead of overwritten
// by other scripts in this entities list.
static const char* sCallChainFunctions[] =
{
"OnPostSpawn",
"Precache"
};
ScriptLanguage_t language = g_pScriptVM->GetLanguage();
// Make a call chainer for each in this entities scope
for (int j = 0; j < ARRAYSIZE(sCallChainFunctions); ++j)
{
if (language == SL_PYTHON)
{
// UNDONE - handle call chaining in python
;
}
else if (language == SL_SQUIRREL)
{
//TODO: For perf, this should be precompiled and the %s should be passed as a parameter
HSCRIPT hCreateChainScript = g_pScriptVM->CompileScript(CFmtStr("%sCallChain <- CSimpleCallChainer(\"%s\", self.GetScriptScope(), true)", sCallChainFunctions[j], sCallChainFunctions[j]));
g_pScriptVM->Run(hCreateChainScript, (HSCRIPT)m_ScriptScope);
}
}
char szScriptsList[255];
Q_strcpy(szScriptsList, STRING(m_iszVScripts));
CUtlStringList szScripts;
V_SplitString(szScriptsList, " ", szScripts);
for (int i = 0; i < szScripts.Count(); i++)
{
#ifdef MAPBASE
CGMsg( 0, CON_GROUP_VSCRIPT, "%s executing script: %s\n", GetDebugName(), szScripts[i] );
#else
Log( "%s executing script: %s\n", GetDebugName(), szScripts[i]);
#endif
RunScriptFile(szScripts[i], IsWorld());
for (int j = 0; j < ARRAYSIZE(sCallChainFunctions); ++j)
{
if (language == SL_PYTHON)
{
// UNDONE - handle call chaining in python
;
}
else if (language == SL_SQUIRREL)
{
//TODO: For perf, this should be precompiled and the %s should be passed as a parameter.
HSCRIPT hRunPostScriptExecute = g_pScriptVM->CompileScript(CFmtStr("%sCallChain.PostScriptExecute()", sCallChainFunctions[j]));
g_pScriptVM->Run(hRunPostScriptExecute, (HSCRIPT)m_ScriptScope);
}
}
}
if (m_iszScriptThinkFunction != NULL_STRING)
{
SetContextThink(&CBaseEntity::ScriptThink, gpGlobals->curtime + sv_script_think_interval.GetFloat(), "ScriptThink");
}
}
//--------------------------------------------------------------------------------------------------
// This is called during entity spawning and after restore to allow scripts to precache any
// resources they need.
//--------------------------------------------------------------------------------------------------
void CBaseEntity::RunPrecacheScripts(void)
{
if (m_iszVScripts == NULL_STRING)
{
return;
}
#ifdef MAPBASE_VSCRIPT
if (g_pScriptVM == NULL)
{
return;
}
#endif
HSCRIPT hScriptPrecache = m_ScriptScope.LookupFunction("DispatchPrecache");
if (hScriptPrecache)
{
g_pScriptVM->Call(hScriptPrecache, m_ScriptScope);
m_ScriptScope.ReleaseFunction(hScriptPrecache);
}
}
void CBaseEntity::RunOnPostSpawnScripts(void)
{
if (m_iszVScripts == NULL_STRING)
{
return;
}
#ifdef MAPBASE_VSCRIPT
if (g_pScriptVM == NULL)
{
return;
}
#endif
HSCRIPT hFuncConnect = g_pScriptVM->LookupFunction("ConnectOutputs");
if (hFuncConnect)
{
g_pScriptVM->Call(hFuncConnect, NULL, true, NULL, (HSCRIPT)m_ScriptScope);
g_pScriptVM->ReleaseFunction(hFuncConnect);
}
HSCRIPT hFuncDisp = m_ScriptScope.LookupFunction("DispatchOnPostSpawn");
if (hFuncDisp)
{
variant_t variant;
variant.SetString(MAKE_STRING("DispatchOnPostSpawn"));
g_EventQueue.AddEvent(this, "CallScriptFunction", variant, 0, this, this);
m_ScriptScope.ReleaseFunction(hFuncDisp);
}
}
#ifndef MAPBASE_VSCRIPT // This is shared now
HSCRIPT CBaseEntity::GetScriptOwnerEntity()
{
return ToHScript(GetOwnerEntity());
}
void CBaseEntity::SetScriptOwnerEntity(HSCRIPT pOwner)
{
SetOwnerEntity(ToEnt(pOwner));
}
#endif
//-----------------------------------------------------------------------------
// VScript access to model's key values
// for iteration and value access, use:
// ScriptFindKey, ScriptGetFirstSubKey, ScriptGetString,
// ScriptGetInt, ScriptGetFloat, ScriptGetNextKey
//-----------------------------------------------------------------------------
HSCRIPT CBaseEntity::ScriptGetModelKeyValues( void )
{
KeyValues *pModelKeyValues = new KeyValues("");
HSCRIPT hScript = NULL;
const char *pszModelName = modelinfo->GetModelName( GetModel() );
const char *pBuffer = modelinfo->GetModelKeyValueText( GetModel() ) ;
if ( pModelKeyValues->LoadFromBuffer( pszModelName, pBuffer ) )
{
// UNDONE: how does destructor get called on this
#ifdef MAPBASE_VSCRIPT
m_pScriptModelKeyValues = hScript = scriptmanager->CreateScriptKeyValues( g_pScriptVM, pModelKeyValues, true ); // Allow VScript to delete this when the instance is removed.
#else
m_pScriptModelKeyValues = new CScriptKeyValues( pModelKeyValues );
#endif
// UNDONE: who calls ReleaseInstance on this??? Does name need to be unique???
#ifndef MAPBASE_VSCRIPT
hScript = g_pScriptVM->RegisterInstance( m_pScriptModelKeyValues );
#endif
/*
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 );
}
}
*/
}
return hScript;
}
void CBaseEntity::ScriptSetLocalAngularVelocity(float pitchVel, float yawVel, float rollVel)
{
QAngle qa;
qa.Init(pitchVel, yawVel, rollVel);
SetLocalAngularVelocity(qa);
}
const Vector& CBaseEntity::ScriptGetLocalAngularVelocity(void)
{
QAngle qa = GetLocalAngularVelocity();
static Vector v;
v.x = qa.x;
v.y = qa.y;
v.z = qa.z;
return v;
}
//-----------------------------------------------------------------------------
// Vscript: Gets the min collision bounds, centered on object
//-----------------------------------------------------------------------------
const Vector& CBaseEntity::ScriptGetBoundingMins(void)
{
return m_Collision.OBBMins();
}
//-----------------------------------------------------------------------------
// Vscript: Gets the max collision bounds, centered on object
//-----------------------------------------------------------------------------
const Vector& CBaseEntity::ScriptGetBoundingMaxs(void)
{
return m_Collision.OBBMaxs();
}
#ifdef MAPBASE_VSCRIPT
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CBaseEntity::ScriptTakeDamage( HSCRIPT pInfo )
{
if (pInfo)
{
CTakeDamageInfo *info = HScriptToClass<CTakeDamageInfo>( pInfo ); //ToDamageInfo( pInfo );
if (info)
{
return OnTakeDamage( *info );
}
}
return 0;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CBaseEntity::ScriptFireBullets( HSCRIPT pInfo )
{
if (pInfo)
{
extern FireBulletsInfo_t *GetFireBulletsInfoFromInfo( HSCRIPT hBulletsInfo );
FireBulletsInfo_t *info = GetFireBulletsInfoFromInfo( pInfo );
if (info)
{
FireBullets( *info );
}
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CBaseEntity::ScriptAddContext( const char *name, const char *value, float duration )
{
AddContext( name, value, duration );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
const char *CBaseEntity::ScriptGetContext( const char *name )
{
return GetContextValue( name );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
HSCRIPT CBaseEntity::ScriptGetContextIndex( int index )
{
if (index >= m_ResponseContexts.Count())
return NULL;
ScriptVariant_t varTable;
g_pScriptVM->CreateTable( varTable );
g_pScriptVM->SetValue( varTable, "name", STRING( m_ResponseContexts[index].m_iszName ) );
g_pScriptVM->SetValue( varTable, "value", STRING( m_ResponseContexts[index].m_iszValue ) );
g_pScriptVM->SetValue( varTable, "expiration_time", m_ResponseContexts[index].m_fExpirationTime );
return varTable.m_hScript;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CBaseEntity::ScriptClassify( void )
{
return (int)Classify();
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CBaseEntity::ScriptAddOutput( const char *pszOutputName, const char *pszTarget, const char *pszAction, const char *pszParameter, float flDelay, int iMaxTimes )
{
const char *pszValue = UTIL_VarArgs("%s,%s,%s,%f,%i", pszTarget, pszAction, pszParameter, flDelay, iMaxTimes);
return KeyValue( pszOutputName, pszValue );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
const char *CBaseEntity::ScriptGetKeyValue( const char *pszKeyName )
{
static char szValue[128];
GetKeyValue( pszKeyName, szValue, sizeof(szValue) );
return szValue;
}
//-----------------------------------------------------------------------------
// Vscript: Dispatch an interaction to the entity
//-----------------------------------------------------------------------------
bool CBaseEntity::ScriptDispatchInteraction( int interactionType, HSCRIPT data, HSCRIPT sourceEnt )
{
return DispatchInteraction( interactionType, data, ToEnt( sourceEnt ) ? ToEnt( sourceEnt )->MyCombatCharacterPointer() : NULL );
}
#endif
#ifdef MAPBASE
extern int EntityFactory_AutoComplete( const char *cmdname, CUtlVector< CUtlString > &commands, CUtlRBTree< CUtlString > &symbols, char *substring, int checklen = 0 );
//------------------------------------------------------------------------------
// Purpose: Create an entity of the given type
//------------------------------------------------------------------------------
class CEntCreateAutoCompletionFunctor : public ICommandCallback, public ICommandCompletionCallback
{
public:
virtual bool CreateAimed() { return false; }
virtual void CommandCallback( 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)
{
// 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 );
// Pass through the player's vehicle
CTraceFilterSkipTwoEntities filter( pPlayer, pPlayer->GetVehicleEntity(), COLLISION_GROUP_NONE );
UTIL_TraceLine(pPlayer->EyePosition(),
pPlayer->EyePosition() + forward * MAX_TRACE_LENGTH,MASK_SOLID,
&filter, &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;
if (CreateAimed())
{
QAngle angles;
VectorAngles( forward, angles );
angles.x = 0;
angles.z = 0;
entity->Teleport( &tr.endpos, &angles, NULL );
}
else
{
entity->Teleport( &tr.endpos, NULL, NULL );
}
UTIL_DropToFloor( entity, MASK_SOLID );
}
entity->Activate();
}
CBaseEntity::SetAllowPrecache( allowPrecache );
}
virtual int CommandCompletionCallback( const char *partial, CUtlVector< CUtlString > &commands )
{
if ( !g_pGameRules )
{
return 0;
}
const char *cmdname = CreateAimed() ? "ent_create_aimed" : "ent_create";
char *substring = (char *)partial;
if ( Q_strstr( partial, cmdname ) )
{
substring = (char *)partial + strlen( cmdname ) + 1;
}
int checklen = Q_strlen( substring );
CUtlRBTree< CUtlString > symbols( 0, 0, UtlStringLessFunc );
return EntityFactory_AutoComplete( cmdname, commands, symbols, substring, checklen );
}
};
static CEntCreateAutoCompletionFunctor g_EntCreateAutoComplete;
static ConCommand ent_create("ent_create", &g_EntCreateAutoComplete, "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, &g_EntCreateAutoComplete);
class CEntCreateAimedAutoCompletionFunctor : public CEntCreateAutoCompletionFunctor
{
public:
virtual bool CreateAimed() { return true; }
};
static CEntCreateAimedAutoCompletionFunctor g_EntCreateAimedAutoComplete;
static ConCommand ent_create_aimed("ent_create_aimed", &g_EntCreateAimedAutoComplete, "Creates an entity of the given type where the player is looking. Additional parameters can be passed in in the form: ent_create_aimed <entity name> <param 1 name> <param 1> <param 2 name> <param 2>...<param N name> <param N>", FCVAR_CHEAT, &g_EntCreateAimedAutoComplete);
#else
//------------------------------------------------------------------------------
// 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);
#endif
//------------------------------------------------------------------------------
// 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;
}
#ifdef MAPBASE
class CEntTeleportAutoCompletionFunctor : public ICommandCallback, public ICommandCompletionCallback
{
public:
virtual void CommandCallback( const CCommand &command )
{
if ( command.ArgC() < 2 )
{
Msg( "Format: ent_teleport <entity name>\n" );
return;
}
CBaseEntity *pEnt;
Vector vecTargetPoint;
if ( CC_GetCommandEnt( command, &pEnt, &vecTargetPoint, NULL ) )
{
pEnt->Teleport( &vecTargetPoint, NULL, NULL );
}
}
virtual int CommandCompletionCallback( const char *partial, CUtlVector< CUtlString > &commands )
{
if ( !g_pGameRules )
{
return 0;
}
const char *cmdname = "ent_teleport";
char *substring = (char *)partial;
if ( Q_strstr( partial, cmdname ) )
{
substring = (char *)partial + strlen( cmdname ) + 1;
}
int checklen = Q_strlen( substring );
CUtlRBTree< CUtlString > symbols( 0, 0, UtlStringLessFunc );
return AutoCompleteEntities(cmdname, commands, symbols, substring, checklen);
}
};
static CEntTeleportAutoCompletionFunctor g_EntTeleportAutoComplete;
static ConCommand ent_teleport("ent_teleport", &g_EntTeleportAutoComplete, "Teleport the specified entity to where the player is looking.\n\tFormat: ent_teleport <entity name>", FCVAR_CHEAT, &g_EntTeleportAutoComplete);
#else
//------------------------------------------------------------------------------
// 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);
#endif
//------------------------------------------------------------------------------
// 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);