sourcearena/mp/src/game/server/baseentity.cpp
Joe Ludwig beaae8ac45 Updated the SDK with the latest code from the TF and HL2 branches
* Adds support for Visual Studio 2012 and 2013
* VR Mode:
. Switches from headtrack.dll to sourcevr.dll
. Improved readability of the UI in VR
. Removed the IPD calibration tool. TF2 will now obey the Oculus
configuration file. Use the Oculus calibration tool in your SDK or
install and run "OpenVR" under Tools in Steam to calibrate your IPD.
. Added dropdown to enable VR mode in the Video options. Removed the -vr
command line option.
. Added the ability to switch in and out of VR mode without quitting the
game
. By default VR mode will run full screen. To switch back to a
borderless window set the vr_force_windowed convar.
. Added support for VR mode on Linux
* Many assorted bug fixes and other changes from Team Fortress in
various shared files
2013-12-03 08:54:16 -08:00

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