mirror of
https://github.com/mapbase-source/source-sdk-2013.git
synced 2025-01-08 13:15:31 +03:00
10660 lines
327 KiB
C++
10660 lines
327 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose: The base class from which all game entities are derived.
|
|
//
|
|
//===========================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "globalstate.h"
|
|
#include "isaverestore.h"
|
|
#include "client.h"
|
|
#include "decals.h"
|
|
#include "gamerules.h"
|
|
#include "entityapi.h"
|
|
#include "entitylist.h"
|
|
#include "eventqueue.h"
|
|
#include "hierarchy.h"
|
|
#include "basecombatweapon.h"
|
|
#include "const.h"
|
|
#include "player.h" // For debug draw sending
|
|
#include "ndebugoverlay.h"
|
|
#include "physics.h"
|
|
#include "model_types.h"
|
|
#include "team.h"
|
|
#include "sendproxy.h"
|
|
#include "IEffects.h"
|
|
#include "vstdlib/random.h"
|
|
#include "baseentity.h"
|
|
#include "collisionutils.h"
|
|
#include "coordsize.h"
|
|
#include "animation.h"
|
|
#include "tier1/strtools.h"
|
|
#include "engine/IEngineSound.h"
|
|
#include "physics_saverestore.h"
|
|
#include "saverestore_utlvector.h"
|
|
#include "bone_setup.h"
|
|
#include "vcollide_parse.h"
|
|
#include "filters.h"
|
|
#include "te_effect_dispatch.h"
|
|
#include "AI_Criteria.h"
|
|
#include "AI_ResponseSystem.h"
|
|
#include "world.h"
|
|
#include "globals.h"
|
|
#include "saverestoretypes.h"
|
|
#include "SkyCamera.h"
|
|
#include "sceneentity.h"
|
|
#include "game.h"
|
|
#include "tier0/vprof.h"
|
|
#include "ai_basenpc.h"
|
|
#include "iservervehicle.h"
|
|
#include "eventlist.h"
|
|
#include "scriptevent.h"
|
|
#include "SoundEmitterSystem/isoundemittersystembase.h"
|
|
#include "UtlCachedFileData.h"
|
|
#include "utlbuffer.h"
|
|
#include "positionwatcher.h"
|
|
#include "movetype_push.h"
|
|
#include "tier0/icommandline.h"
|
|
#include "vphysics/friction.h"
|
|
#include <ctype.h>
|
|
#include "datacache/imdlcache.h"
|
|
#include "ModelSoundsCache.h"
|
|
#include "env_debughistory.h"
|
|
#include "tier1/utlstring.h"
|
|
#include "utlhashtable.h"
|
|
#ifdef MAPBASE
|
|
#include "mapbase/matchers.h"
|
|
#include "mapbase/datadesc_mod.h"
|
|
#endif
|
|
#ifdef NEW_RESPONSE_SYSTEM
|
|
#include "ai_speech.h"
|
|
#endif
|
|
|
|
#if defined( TF_DLL )
|
|
#include "tf_gamerules.h"
|
|
#endif
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
extern bool g_bTestMoveTypeStepSimulation;
|
|
extern ConVar sv_vehicle_autoaim_scale;
|
|
|
|
// Init static class variables
|
|
bool CBaseEntity::m_bInDebugSelect = false; // Used for selection in debug overlays
|
|
int CBaseEntity::m_nDebugPlayer = -1; // Player doing the selection
|
|
|
|
// This can be set before creating an entity to force it to use a particular edict.
|
|
edict_t *g_pForceAttachEdict = NULL;
|
|
|
|
bool CBaseEntity::m_bDebugPause = false; // Whether entity i/o is paused.
|
|
int CBaseEntity::m_nDebugSteps = 1; // Number of entity outputs to fire before pausing again.
|
|
bool CBaseEntity::sm_bDisableTouchFuncs = false; // Disables PhysicsTouch and PhysicsStartTouch function calls
|
|
bool CBaseEntity::sm_bAccurateTriggerBboxChecks = true; // set to false for legacy behavior in ep1
|
|
|
|
int CBaseEntity::m_nPredictionRandomSeed = -1;
|
|
CBasePlayer *CBaseEntity::m_pPredictionPlayer = NULL;
|
|
|
|
// Used to make sure nobody calls UpdateTransmitState directly.
|
|
int g_nInsideDispatchUpdateTransmitState = 0;
|
|
|
|
// When this is false, throw an assert in debug when GetAbsAnything is called. Used when hierachy is incomplete/invalid.
|
|
bool CBaseEntity::s_bAbsQueriesValid = true;
|
|
|
|
|
|
ConVar sv_netvisdist( "sv_netvisdist", "10000", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Test networking visibility distance" );
|
|
|
|
ConVar sv_script_think_interval("sv_script_think_interval", "0.1");
|
|
|
|
#ifdef MAPBASE_VSCRIPT
|
|
ConVar ent_text_allow_script( "ent_text_allow_script", "1" );
|
|
#endif
|
|
|
|
|
|
// This table encodes edict data.
|
|
void SendProxy_AnimTime( const SendProp *pProp, const void *pStruct, const void *pVarData, DVariant *pOut, int iElement, int objectID )
|
|
{
|
|
CBaseEntity *pEntity = (CBaseEntity *)pStruct;
|
|
|
|
#if defined( _DEBUG )
|
|
CBaseAnimating *pAnimating = pEntity->GetBaseAnimating();
|
|
Assert( pAnimating );
|
|
|
|
if ( pAnimating )
|
|
{
|
|
Assert( !pAnimating->IsUsingClientSideAnimation() );
|
|
}
|
|
#endif
|
|
|
|
int ticknumber = TIME_TO_TICKS( pEntity->m_flAnimTime );
|
|
// Tickbase is current tick rounded down to closes 100 ticks
|
|
int tickbase = gpGlobals->GetNetworkBase( gpGlobals->tickcount, pEntity->entindex() );
|
|
int addt = 0;
|
|
// If it's within the last tick interval through the current one, then we can encode it
|
|
if ( ticknumber >= ( tickbase - 100 ) )
|
|
{
|
|
addt = ( ticknumber - tickbase ) & 0xFF;
|
|
}
|
|
|
|
pOut->m_Int = addt;
|
|
}
|
|
|
|
// This table encodes edict data.
|
|
void SendProxy_SimulationTime( const SendProp *pProp, const void *pStruct, const void *pVarData, DVariant *pOut, int iElement, int objectID )
|
|
{
|
|
CBaseEntity *pEntity = (CBaseEntity *)pStruct;
|
|
|
|
int ticknumber = TIME_TO_TICKS( pEntity->m_flSimulationTime );
|
|
// tickbase is current tick rounded down to closest 100 ticks
|
|
int tickbase = gpGlobals->GetNetworkBase( gpGlobals->tickcount, pEntity->entindex() );
|
|
int addt = 0;
|
|
if ( ticknumber >= tickbase )
|
|
{
|
|
addt = ( ticknumber - tickbase ) & 0xff;
|
|
}
|
|
|
|
pOut->m_Int = addt;
|
|
}
|
|
|
|
void* SendProxy_ClientSideAnimation( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID )
|
|
{
|
|
CBaseEntity *pEntity = (CBaseEntity *)pStruct;
|
|
CBaseAnimating *pAnimating = pEntity->GetBaseAnimating();
|
|
|
|
if ( pAnimating && !pAnimating->IsUsingClientSideAnimation() )
|
|
return (void*)pVarData;
|
|
else
|
|
return NULL; // Don't send animtime unless the client needs it.
|
|
}
|
|
REGISTER_SEND_PROXY_NON_MODIFIED_POINTER( SendProxy_ClientSideAnimation );
|
|
|
|
|
|
BEGIN_SEND_TABLE_NOBASE( CBaseEntity, DT_AnimTimeMustBeFirst )
|
|
// NOTE: Animtime must be sent before origin and angles ( from pev ) because it has a
|
|
// proxy on the client that stores off the old values before writing in the new values and
|
|
// if it is sent after the new values, then it will only have the new origin and studio model, etc.
|
|
// interpolation will be busted
|
|
SendPropInt (SENDINFO(m_flAnimTime), 8, SPROP_UNSIGNED|SPROP_CHANGES_OFTEN|SPROP_ENCODED_AGAINST_TICKCOUNT, SendProxy_AnimTime),
|
|
END_SEND_TABLE()
|
|
|
|
#if !defined( NO_ENTITY_PREDICTION )
|
|
BEGIN_SEND_TABLE_NOBASE( CBaseEntity, DT_PredictableId )
|
|
SendPropPredictableId( SENDINFO( m_PredictableID ) ),
|
|
SendPropInt( SENDINFO( m_bIsPlayerSimulated ), 1, SPROP_UNSIGNED ),
|
|
END_SEND_TABLE()
|
|
|
|
|
|
static void* SendProxy_SendPredictableId( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID )
|
|
{
|
|
CBaseEntity *pEntity = (CBaseEntity *)pStruct;
|
|
if ( !pEntity || !pEntity->m_PredictableID->IsActive() )
|
|
return NULL;
|
|
|
|
int id_player_index = pEntity->m_PredictableID->GetPlayer();
|
|
pRecipients->SetOnly( id_player_index );
|
|
|
|
return ( void * )pVarData;
|
|
}
|
|
REGISTER_SEND_PROXY_NON_MODIFIED_POINTER( SendProxy_SendPredictableId );
|
|
#endif
|
|
|
|
void SendProxy_Origin( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
|
|
{
|
|
CBaseEntity *entity = (CBaseEntity*)pStruct;
|
|
Assert( entity );
|
|
|
|
const Vector *v;
|
|
|
|
if ( !entity->UseStepSimulationNetworkOrigin( &v ) )
|
|
{
|
|
v = &entity->GetLocalOrigin();
|
|
}
|
|
|
|
pOut->m_Vector[ 0 ] = v->x;
|
|
pOut->m_Vector[ 1 ] = v->y;
|
|
pOut->m_Vector[ 2 ] = v->z;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------
|
|
// Used when breaking up origin, note we still have to deal with StepSimulation
|
|
//--------------------------------------------------------------------------------------------------------
|
|
void SendProxy_OriginXY( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
|
|
{
|
|
CBaseEntity *entity = (CBaseEntity*)pStruct;
|
|
Assert( entity );
|
|
|
|
const Vector *v;
|
|
|
|
if ( !entity->UseStepSimulationNetworkOrigin( &v ) )
|
|
{
|
|
v = &entity->GetLocalOrigin();
|
|
}
|
|
|
|
pOut->m_Vector[ 0 ] = v->x;
|
|
pOut->m_Vector[ 1 ] = v->y;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------
|
|
// Used when breaking up origin, note we still have to deal with StepSimulation
|
|
//--------------------------------------------------------------------------------------------------------
|
|
void SendProxy_OriginZ( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
|
|
{
|
|
CBaseEntity *entity = (CBaseEntity*)pStruct;
|
|
Assert( entity );
|
|
|
|
const Vector *v;
|
|
|
|
if ( !entity->UseStepSimulationNetworkOrigin( &v ) )
|
|
{
|
|
v = &entity->GetLocalOrigin();
|
|
}
|
|
|
|
pOut->m_Float = v->z;
|
|
}
|
|
|
|
|
|
void SendProxy_Angles( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
|
|
{
|
|
CBaseEntity *entity = (CBaseEntity*)pStruct;
|
|
Assert( entity );
|
|
|
|
const QAngle *a;
|
|
|
|
if ( !entity->UseStepSimulationNetworkAngles( &a ) )
|
|
{
|
|
a = &entity->GetLocalAngles();
|
|
}
|
|
|
|
pOut->m_Vector[ 0 ] = anglemod( a->x );
|
|
pOut->m_Vector[ 1 ] = anglemod( a->y );
|
|
pOut->m_Vector[ 2 ] = anglemod( a->z );
|
|
}
|
|
|
|
// This table encodes the CBaseEntity data.
|
|
IMPLEMENT_SERVERCLASS_ST_NOBASE( CBaseEntity, DT_BaseEntity )
|
|
SendPropDataTable( "AnimTimeMustBeFirst", 0, &REFERENCE_SEND_TABLE(DT_AnimTimeMustBeFirst), SendProxy_ClientSideAnimation ),
|
|
SendPropInt (SENDINFO(m_flSimulationTime), SIMULATION_TIME_WINDOW_BITS, SPROP_UNSIGNED|SPROP_CHANGES_OFTEN|SPROP_ENCODED_AGAINST_TICKCOUNT, SendProxy_SimulationTime),
|
|
|
|
#if PREDICTION_ERROR_CHECK_LEVEL > 1
|
|
SendPropVector (SENDINFO(m_vecOrigin), -1, SPROP_NOSCALE|SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_Origin ),
|
|
#else
|
|
SendPropVector (SENDINFO(m_vecOrigin), -1, SPROP_COORD|SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_Origin ),
|
|
#endif
|
|
|
|
SendPropInt (SENDINFO( m_ubInterpolationFrame ), NOINTERP_PARITY_MAX_BITS, SPROP_UNSIGNED ),
|
|
SendPropModelIndex(SENDINFO(m_nModelIndex)),
|
|
SendPropDataTable( SENDINFO_DT( m_Collision ), &REFERENCE_SEND_TABLE(DT_CollisionProperty) ),
|
|
SendPropInt (SENDINFO(m_nRenderFX), 8, SPROP_UNSIGNED ),
|
|
SendPropInt (SENDINFO(m_nRenderMode), 8, SPROP_UNSIGNED ),
|
|
SendPropInt (SENDINFO(m_fEffects), EF_MAX_BITS, SPROP_UNSIGNED),
|
|
SendPropInt (SENDINFO(m_clrRender), 32, SPROP_UNSIGNED),
|
|
#ifdef MAPBASE
|
|
// Keep consistent with VIEW_ID_COUNT in viewrender.h
|
|
SendPropInt (SENDINFO(m_iViewHideFlags), 9, SPROP_UNSIGNED ),
|
|
SendPropBool (SENDINFO(m_bDisableFlashlight) ),
|
|
#endif
|
|
SendPropInt (SENDINFO(m_iTeamNum), TEAMNUM_NUM_BITS, 0),
|
|
SendPropInt (SENDINFO(m_CollisionGroup), 5, SPROP_UNSIGNED),
|
|
SendPropFloat (SENDINFO(m_flElasticity), 0, SPROP_COORD),
|
|
SendPropFloat (SENDINFO(m_flShadowCastDistance), 12, SPROP_UNSIGNED ),
|
|
SendPropEHandle (SENDINFO(m_hOwnerEntity)),
|
|
SendPropEHandle (SENDINFO(m_hEffectEntity)),
|
|
SendPropEHandle (SENDINFO_NAME(m_hMoveParent, moveparent)),
|
|
SendPropInt (SENDINFO(m_iParentAttachment), NUM_PARENTATTACHMENT_BITS, SPROP_UNSIGNED),
|
|
|
|
SendPropStringT( SENDINFO( m_iName ) ),
|
|
|
|
SendPropInt (SENDINFO_NAME( m_MoveType, movetype ), MOVETYPE_MAX_BITS, SPROP_UNSIGNED ),
|
|
SendPropInt (SENDINFO_NAME( m_MoveCollide, movecollide ), MOVECOLLIDE_MAX_BITS, SPROP_UNSIGNED ),
|
|
#if PREDICTION_ERROR_CHECK_LEVEL > 1
|
|
SendPropVector (SENDINFO(m_angRotation), -1, SPROP_NOSCALE|SPROP_CHANGES_OFTEN, 0, HIGH_DEFAULT, SendProxy_Angles ),
|
|
#else
|
|
SendPropQAngles (SENDINFO(m_angRotation), 13, SPROP_CHANGES_OFTEN, SendProxy_Angles ),
|
|
#endif
|
|
|
|
SendPropInt ( SENDINFO( m_iTextureFrameIndex ), 8, SPROP_UNSIGNED ),
|
|
|
|
#if !defined( NO_ENTITY_PREDICTION )
|
|
SendPropDataTable( "predictable_id", 0, &REFERENCE_SEND_TABLE( DT_PredictableId ), SendProxy_SendPredictableId ),
|
|
#endif
|
|
|
|
// FIXME: Collapse into another flag field?
|
|
SendPropInt (SENDINFO(m_bSimulatedEveryTick), 1, SPROP_UNSIGNED ),
|
|
SendPropInt (SENDINFO(m_bAnimatedEveryTick), 1, SPROP_UNSIGNED ),
|
|
SendPropBool( SENDINFO( m_bAlternateSorting )),
|
|
|
|
#ifdef TF_DLL
|
|
SendPropArray3( SENDINFO_ARRAY3(m_nModelIndexOverrides), SendPropInt( SENDINFO_ARRAY(m_nModelIndexOverrides), SP_MODEL_INDEX_BITS, 0 ) ),
|
|
#endif
|
|
|
|
END_SEND_TABLE()
|
|
|
|
|
|
// dynamic models
|
|
class CBaseEntityModelLoadProxy
|
|
{
|
|
protected:
|
|
class Handler : public IModelLoadCallback
|
|
{
|
|
public:
|
|
explicit Handler( CBaseEntity *pEntity ) : m_pEntity(pEntity) { }
|
|
virtual void OnModelLoadComplete( const model_t *pModel );
|
|
CBaseEntity* m_pEntity;
|
|
};
|
|
Handler* m_pHandler;
|
|
|
|
public:
|
|
explicit CBaseEntityModelLoadProxy( CBaseEntity *pEntity ) : m_pHandler( new Handler( pEntity ) ) { }
|
|
~CBaseEntityModelLoadProxy() { delete m_pHandler; }
|
|
void Register( int nModelIndex ) const { modelinfo->RegisterModelLoadCallback( nModelIndex, m_pHandler ); }
|
|
operator CBaseEntity * () const { return m_pHandler->m_pEntity; }
|
|
|
|
private:
|
|
CBaseEntityModelLoadProxy( const CBaseEntityModelLoadProxy& );
|
|
CBaseEntityModelLoadProxy& operator=( const CBaseEntityModelLoadProxy& );
|
|
};
|
|
|
|
static CUtlHashtable< CBaseEntityModelLoadProxy, empty_t, PointerHashFunctor, PointerEqualFunctor, CBaseEntity * > sg_DynamicLoadHandlers;
|
|
|
|
void CBaseEntityModelLoadProxy::Handler::OnModelLoadComplete( const model_t *pModel )
|
|
{
|
|
m_pEntity->OnModelLoadComplete( pModel );
|
|
sg_DynamicLoadHandlers.Remove( m_pEntity ); // NOTE: destroys *this!
|
|
}
|
|
|
|
|
|
CBaseEntity::CBaseEntity( bool bServerOnly )
|
|
{
|
|
COMPILE_TIME_ASSERT( MOVETYPE_LAST < (1 << MOVETYPE_MAX_BITS) );
|
|
COMPILE_TIME_ASSERT( MOVECOLLIDE_COUNT < (1 << MOVECOLLIDE_MAX_BITS) );
|
|
|
|
#ifdef _DEBUG
|
|
// necessary since in debug, we initialize vectors to NAN for debugging
|
|
m_vecAngVelocity.Init();
|
|
// m_vecAbsAngVelocity.Init();
|
|
m_vecViewOffset.Init();
|
|
m_vecBaseVelocity.GetForModify().Init();
|
|
m_vecVelocity.Init();
|
|
m_vecAbsVelocity.Init();
|
|
#endif
|
|
|
|
m_bAlternateSorting = false;
|
|
m_CollisionGroup = COLLISION_GROUP_NONE;
|
|
m_iParentAttachment = 0;
|
|
CollisionProp()->Init( this );
|
|
NetworkProp()->Init( this );
|
|
|
|
// NOTE: THIS MUST APPEAR BEFORE ANY SetMoveType() or SetNextThink() calls
|
|
AddEFlags( EFL_NO_THINK_FUNCTION | EFL_NO_GAME_PHYSICS_SIMULATION | EFL_USE_PARTITION_WHEN_NOT_SOLID );
|
|
|
|
// clear debug overlays
|
|
m_debugOverlays = 0;
|
|
m_pTimedOverlay = NULL;
|
|
m_pPhysicsObject = NULL;
|
|
m_flElasticity = 1.0f;
|
|
m_flShadowCastDistance = m_flDesiredShadowCastDistance = 0;
|
|
SetRenderColor( 255, 255, 255, 255 );
|
|
m_iTeamNum = m_iInitialTeamNum = TEAM_UNASSIGNED;
|
|
m_nLastThinkTick = gpGlobals->tickcount;
|
|
m_nSimulationTick = -1;
|
|
SetIdentityMatrix( m_rgflCoordinateFrame );
|
|
m_pBlocker = NULL;
|
|
#if _DEBUG
|
|
m_iCurrentThinkContext = NO_THINK_CONTEXT;
|
|
#endif
|
|
m_nWaterTouch = m_nSlimeTouch = 0;
|
|
|
|
SetSolid( SOLID_NONE );
|
|
ClearSolidFlags();
|
|
|
|
m_nModelIndex = 0;
|
|
m_bDynamicModelAllowed = false;
|
|
m_bDynamicModelPending = false;
|
|
m_bDynamicModelSetBounds = false;
|
|
|
|
SetMoveType( MOVETYPE_NONE );
|
|
SetOwnerEntity( NULL );
|
|
SetCheckUntouch( false );
|
|
SetModelIndex( 0 );
|
|
SetModelName( NULL_STRING );
|
|
m_nTransmitStateOwnedCounter = 0;
|
|
|
|
SetCollisionBounds( vec3_origin, vec3_origin );
|
|
ClearFlags();
|
|
|
|
SetFriction( 1.0f );
|
|
|
|
if ( bServerOnly )
|
|
{
|
|
AddEFlags( EFL_SERVER_ONLY );
|
|
}
|
|
NetworkProp()->MarkPVSInformationDirty();
|
|
|
|
#ifndef _XBOX
|
|
AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID );
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Scale up our physics hull and test against the new one
|
|
// Input : *pNewCollide - New collision hull
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SetScaledPhysics( IPhysicsObject *pNewObject )
|
|
{
|
|
if ( pNewObject )
|
|
{
|
|
AddSolidFlags( FSOLID_CUSTOMBOXTEST | FSOLID_CUSTOMRAYTEST );
|
|
}
|
|
else
|
|
{
|
|
RemoveSolidFlags( FSOLID_CUSTOMBOXTEST | FSOLID_CUSTOMRAYTEST );
|
|
}
|
|
}
|
|
|
|
extern bool g_bDisableEhandleAccess;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: See note below
|
|
//-----------------------------------------------------------------------------
|
|
CBaseEntity::~CBaseEntity( )
|
|
{
|
|
#ifdef MAPBASE_VSCRIPT
|
|
// HACKHACK: This is needed to fix a crash when an entity removes itself with Destroy() during its own think function.
|
|
// (see https://github.com/mapbase-source/source-sdk-2013/issues/138)
|
|
FOR_EACH_VEC( m_ScriptThinkFuncs, i )
|
|
{
|
|
HSCRIPT h = m_ScriptThinkFuncs[i]->m_hfnThink;
|
|
if ( h ) g_pScriptVM->ReleaseScript( h );
|
|
}
|
|
m_ScriptThinkFuncs.PurgeAndDeleteElements();
|
|
#endif // MAPBASE_VSCRIPT
|
|
|
|
// FIXME: This can't be called from UpdateOnRemove! There's at least one
|
|
// case where friction sounds are added between the call to UpdateOnRemove + ~CBaseEntity
|
|
PhysCleanupFrictionSounds( this );
|
|
|
|
Assert( !IsDynamicModelIndex( m_nModelIndex ) );
|
|
Verify( !sg_DynamicLoadHandlers.Remove( this ) );
|
|
|
|
// In debug make sure that we don't call delete on an entity without setting
|
|
// the disable flag first!
|
|
// EHANDLE accessors will check, in debug, for access to entities during destruction of
|
|
// another entity.
|
|
// That kind of operation should only occur in UpdateOnRemove calls
|
|
// Deletion should only occur via UTIL_Remove(Immediate) calls, not via naked delete calls
|
|
Assert( g_bDisableEhandleAccess );
|
|
|
|
VPhysicsDestroyObject();
|
|
|
|
// Need to remove references to this entity before EHANDLES go null
|
|
{
|
|
g_bDisableEhandleAccess = false;
|
|
CBaseEntity::PhysicsRemoveTouchedList( this );
|
|
CBaseEntity::PhysicsRemoveGroundList( this );
|
|
SetGroundEntity( NULL ); // remove us from the ground entity if we are on it
|
|
DestroyAllDataObjects();
|
|
g_bDisableEhandleAccess = true;
|
|
|
|
// Remove this entity from the ent list (NOTE: This Makes EHANDLES go NULL)
|
|
gEntList.RemoveEntity( GetRefEHandle() );
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::PostConstructor( const char *szClassname )
|
|
{
|
|
if ( szClassname )
|
|
{
|
|
SetClassname(szClassname);
|
|
}
|
|
|
|
Assert( m_iClassname != NULL_STRING && STRING(m_iClassname) != NULL );
|
|
|
|
// Possibly get an edict, and add self to global list of entites.
|
|
if ( IsEFlagSet( EFL_SERVER_ONLY ) )
|
|
{
|
|
gEntList.AddNonNetworkableEntity( this );
|
|
}
|
|
else
|
|
{
|
|
// Certain entities set up their edicts in the constructor
|
|
if ( !IsEFlagSet( EFL_NO_AUTO_EDICT_ATTACH ) )
|
|
{
|
|
NetworkProp()->AttachEdict( g_pForceAttachEdict );
|
|
g_pForceAttachEdict = NULL;
|
|
}
|
|
|
|
// Some ents like the player override the AttachEdict function and do it at a different time.
|
|
// While precaching, they don't ever have an edict, so we don't need to add them to
|
|
// the entity list in that case.
|
|
if ( edict() )
|
|
{
|
|
gEntList.AddNetworkableEntity( this, entindex() );
|
|
|
|
// Cache our IServerNetworkable pointer for the engine for fast access.
|
|
if ( edict() )
|
|
edict()->m_pNetworkable = NetworkProp();
|
|
}
|
|
}
|
|
|
|
CheckHasThinkFunction( false );
|
|
CheckHasGamePhysicsSimulation();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Called after player becomes active in the game
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::PostClientActive( void )
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Verifies that this entity's data description is valid in debug builds.
|
|
//-----------------------------------------------------------------------------
|
|
#ifdef _DEBUG
|
|
typedef CUtlVector< const char * > KeyValueNameList_t;
|
|
|
|
static void AddDataMapFieldNamesToList( KeyValueNameList_t &list, datamap_t *pDataMap )
|
|
{
|
|
while (pDataMap != NULL)
|
|
{
|
|
for (int i = 0; i < pDataMap->dataNumFields; i++)
|
|
{
|
|
typedescription_t *pField = &pDataMap->dataDesc[i];
|
|
|
|
if (pField->fieldType == FIELD_EMBEDDED)
|
|
{
|
|
AddDataMapFieldNamesToList( list, pField->td );
|
|
continue;
|
|
}
|
|
|
|
if (pField->flags & FTYPEDESC_KEY)
|
|
{
|
|
list.AddToTail( pField->externalName );
|
|
}
|
|
}
|
|
|
|
pDataMap = pDataMap->baseMap;
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::ValidateDataDescription(void)
|
|
{
|
|
// Multiple key fields that have the same name are not allowed - it creates an
|
|
// ambiguity when trying to parse keyvalues and outputs.
|
|
datamap_t *pDataMap = GetDataDescMap();
|
|
if ((pDataMap == NULL) || pDataMap->bValidityChecked)
|
|
return;
|
|
|
|
pDataMap->bValidityChecked = true;
|
|
|
|
// Let's generate a list of all keyvalue strings in the entire hierarchy...
|
|
KeyValueNameList_t names(128);
|
|
AddDataMapFieldNamesToList( names, pDataMap );
|
|
|
|
for (int i = names.Count(); --i > 0; )
|
|
{
|
|
for (int j = i - 1; --j >= 0; )
|
|
{
|
|
if (!Q_stricmp(names[i], names[j]))
|
|
{
|
|
DevMsg( "%s has multiple data description entries for \"%s\"\n", STRING(m_iClassname), names[i]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // _DEBUG
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Sets the collision bounds + the size
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SetCollisionBounds( const Vector& mins, const Vector &maxs )
|
|
{
|
|
m_Collision.SetCollisionBounds( mins, maxs );
|
|
}
|
|
|
|
|
|
void CBaseEntity::StopFollowingEntity( )
|
|
{
|
|
if( !IsFollowingEntity() )
|
|
{
|
|
// Assert( IsEffectActive( EF_BONEMERGE ) == 0 );
|
|
return;
|
|
}
|
|
|
|
SetParent( NULL );
|
|
RemoveEffects( EF_BONEMERGE );
|
|
RemoveSolidFlags( FSOLID_NOT_SOLID );
|
|
SetMoveType( MOVETYPE_NONE );
|
|
CollisionRulesChanged();
|
|
}
|
|
|
|
bool CBaseEntity::IsFollowingEntity()
|
|
{
|
|
return IsEffectActive( EF_BONEMERGE ) && (GetMoveType() == MOVETYPE_NONE) && GetMoveParent();
|
|
}
|
|
|
|
CBaseEntity *CBaseEntity::GetFollowedEntity()
|
|
{
|
|
if (!IsFollowingEntity())
|
|
return NULL;
|
|
return GetMoveParent();
|
|
}
|
|
|
|
void CBaseEntity::SetClassname( const char *className )
|
|
{
|
|
m_iClassname = AllocPooledString( className );
|
|
}
|
|
|
|
void CBaseEntity::SetModelIndex( int index )
|
|
{
|
|
if ( IsDynamicModelIndex( index ) && !(GetBaseAnimating() && m_bDynamicModelAllowed) )
|
|
{
|
|
AssertMsg( false, "dynamic model support not enabled on server entity" );
|
|
index = -1;
|
|
}
|
|
|
|
if ( index != m_nModelIndex )
|
|
{
|
|
if ( m_bDynamicModelPending )
|
|
{
|
|
sg_DynamicLoadHandlers.Remove( this );
|
|
}
|
|
|
|
modelinfo->ReleaseDynamicModel( m_nModelIndex );
|
|
modelinfo->AddRefDynamicModel( index );
|
|
m_nModelIndex = index;
|
|
|
|
m_bDynamicModelSetBounds = false;
|
|
|
|
if ( IsDynamicModelIndex( index ) )
|
|
{
|
|
m_bDynamicModelPending = true;
|
|
sg_DynamicLoadHandlers[ sg_DynamicLoadHandlers.Insert( this ) ].Register( index );
|
|
}
|
|
else
|
|
{
|
|
m_bDynamicModelPending = false;
|
|
OnNewModel();
|
|
}
|
|
}
|
|
DispatchUpdateTransmitState();
|
|
}
|
|
|
|
void CBaseEntity::ClearModelIndexOverrides( void )
|
|
{
|
|
#ifdef TF_DLL
|
|
for ( int index = 0 ; index < MAX_VISION_MODES ; index++ )
|
|
{
|
|
m_nModelIndexOverrides.Set( index, 0 );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void CBaseEntity::SetModelIndexOverride( int index, int nValue )
|
|
{
|
|
#ifdef TF_DLL
|
|
if ( ( index >= VISION_MODE_NONE ) && ( index < MAX_VISION_MODES ) )
|
|
{
|
|
if ( nValue != m_nModelIndexOverrides[index] )
|
|
{
|
|
m_nModelIndexOverrides.Set( index, nValue );
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// position to shoot at
|
|
Vector CBaseEntity::BodyTarget( const Vector &posSrc, bool bNoisy)
|
|
{
|
|
return WorldSpaceCenter( );
|
|
}
|
|
|
|
// return the position of my head. someone's trying to attack it.
|
|
Vector CBaseEntity::HeadTarget( const Vector &posSrc )
|
|
{
|
|
return EyePosition();
|
|
}
|
|
|
|
|
|
struct TimedOverlay_t
|
|
{
|
|
char *msg;
|
|
int msgEndTime;
|
|
int msgStartTime;
|
|
TimedOverlay_t *pNextTimedOverlay;
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Display an error message on the entity
|
|
// Input :
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::AddTimedOverlay( const char *msg, int endTime )
|
|
{
|
|
TimedOverlay_t *pNewTO = new TimedOverlay_t;
|
|
int len = strlen(msg);
|
|
pNewTO->msg = new char[len + 1];
|
|
Q_strncpy(pNewTO->msg,msg, len+1);
|
|
pNewTO->msgEndTime = gpGlobals->curtime + endTime;
|
|
pNewTO->msgStartTime = gpGlobals->curtime;
|
|
pNewTO->pNextTimedOverlay = m_pTimedOverlay;
|
|
m_pTimedOverlay = pNewTO;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Send debug overlay box to the client
|
|
// Input :
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::DrawBBoxOverlay( float flDuration )
|
|
{
|
|
if (edict())
|
|
{
|
|
NDebugOverlay::EntityBounds(this, 255, 100, 0, 0, flDuration );
|
|
|
|
if ( CollisionProp()->IsSolidFlagSet( FSOLID_USE_TRIGGER_BOUNDS ) )
|
|
{
|
|
Vector vecTriggerMins, vecTriggerMaxs;
|
|
CollisionProp()->WorldSpaceTriggerBounds( &vecTriggerMins, &vecTriggerMaxs );
|
|
Vector center = 0.5f * (vecTriggerMins + vecTriggerMaxs);
|
|
Vector extents = vecTriggerMaxs - center;
|
|
NDebugOverlay::Box(center, -extents, extents, 0, 255, 255, 0, flDuration );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CBaseEntity::DrawAbsBoxOverlay()
|
|
{
|
|
int red = 0;
|
|
int green = 200;
|
|
|
|
if ( VPhysicsGetObject() && VPhysicsGetObject()->IsAsleep() )
|
|
{
|
|
red = 90;
|
|
green = 120;
|
|
}
|
|
|
|
if (edict())
|
|
{
|
|
// Surrounding boxes are axially aligned, so ignore angles
|
|
Vector vecSurroundMins, vecSurroundMaxs;
|
|
CollisionProp()->WorldSpaceSurroundingBounds( &vecSurroundMins, &vecSurroundMaxs );
|
|
Vector center = 0.5f * (vecSurroundMins + vecSurroundMaxs);
|
|
Vector extents = vecSurroundMaxs - center;
|
|
NDebugOverlay::Box(center, -extents, extents, red, green, 0, 0 ,0);
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::DrawRBoxOverlay()
|
|
{
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Draws an axis overlay at the origin and angles of the entity
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SendDebugPivotOverlay( void )
|
|
{
|
|
if ( edict() )
|
|
{
|
|
NDebugOverlay::Axis( GetAbsOrigin(), GetAbsAngles(), 20, true, 0 );
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : Add new entity positioned overlay text
|
|
// Input : How many lines to offset text from origin
|
|
// The text to print
|
|
// How long to display text
|
|
// The color of the text
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::EntityText( int text_offset, const char *text, float duration, int r, int g, int b, int a )
|
|
{
|
|
Vector origin;
|
|
Vector vecLocalCenter;
|
|
|
|
VectorAdd( m_Collision.OBBMins(), m_Collision.OBBMaxs(), vecLocalCenter );
|
|
vecLocalCenter *= 0.5f;
|
|
|
|
if ( ( m_Collision.GetCollisionAngles() == vec3_angle ) || ( vecLocalCenter == vec3_origin ) )
|
|
{
|
|
VectorAdd( vecLocalCenter, m_Collision.GetCollisionOrigin(), origin );
|
|
}
|
|
else
|
|
{
|
|
VectorTransform( vecLocalCenter, m_Collision.CollisionToWorldTransform(), origin );
|
|
}
|
|
|
|
NDebugOverlay::EntityTextAtPosition( origin, text_offset, text, duration, r, g, b, a );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::DrawTimedOverlays(void)
|
|
{
|
|
// Draw name first if I have an overlay or am in message mode
|
|
if ((m_debugOverlays & OVERLAY_MESSAGE_BIT))
|
|
{
|
|
char tempstr[512];
|
|
Q_snprintf( tempstr, sizeof( tempstr ), "[%s]", GetDebugName() );
|
|
EntityText(0,tempstr, 0);
|
|
}
|
|
|
|
// Now draw overlays
|
|
TimedOverlay_t* pTO = m_pTimedOverlay;
|
|
TimedOverlay_t* pNextTO = NULL;
|
|
TimedOverlay_t* pLastTO = NULL;
|
|
int nCount = 1; // Offset by one
|
|
while (pTO)
|
|
{
|
|
pNextTO = pTO->pNextTimedOverlay;
|
|
|
|
// Remove old messages unless messages are paused
|
|
if ((!CBaseEntity::Debug_IsPaused() && gpGlobals->curtime > pTO->msgEndTime) ||
|
|
(nCount > 10))
|
|
{
|
|
if (pLastTO)
|
|
{
|
|
pLastTO->pNextTimedOverlay = pNextTO;
|
|
}
|
|
else
|
|
{
|
|
m_pTimedOverlay = pNextTO;
|
|
}
|
|
|
|
delete pTO->msg;
|
|
delete pTO;
|
|
}
|
|
else
|
|
{
|
|
int nAlpha = 0;
|
|
|
|
// If messages aren't paused fade out
|
|
if (!CBaseEntity::Debug_IsPaused())
|
|
{
|
|
nAlpha = 255*((gpGlobals->curtime - pTO->msgStartTime)/(pTO->msgEndTime - pTO->msgStartTime));
|
|
}
|
|
int r = 185;
|
|
int g = 145;
|
|
int b = 145;
|
|
|
|
// Brighter when new message
|
|
if (nAlpha < 50)
|
|
{
|
|
r = 255;
|
|
g = 205;
|
|
b = 205;
|
|
}
|
|
if (nAlpha < 0) nAlpha = 0;
|
|
EntityText(nCount,pTO->msg, 0.0, r, g, b, 255-nAlpha);
|
|
nCount++;
|
|
|
|
pLastTO = pTO;
|
|
}
|
|
pTO = pNextTO;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Draw all overlays (should be implemented by subclass to add
|
|
// any additional non-text overlays)
|
|
// Input :
|
|
// Output : Current text offset from the top
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::DrawDebugGeometryOverlays(void)
|
|
{
|
|
DrawTimedOverlays();
|
|
DrawDebugTextOverlays();
|
|
|
|
if (m_debugOverlays & OVERLAY_NAME_BIT)
|
|
{
|
|
EntityText(0,GetDebugName(), 0);
|
|
}
|
|
if (m_debugOverlays & OVERLAY_BBOX_BIT)
|
|
{
|
|
DrawBBoxOverlay();
|
|
}
|
|
if (m_debugOverlays & OVERLAY_ABSBOX_BIT )
|
|
{
|
|
DrawAbsBoxOverlay();
|
|
}
|
|
if (m_debugOverlays & OVERLAY_PIVOT_BIT)
|
|
{
|
|
SendDebugPivotOverlay();
|
|
}
|
|
if( m_debugOverlays & OVERLAY_RBOX_BIT )
|
|
{
|
|
DrawRBoxOverlay();
|
|
}
|
|
if ( m_debugOverlays & (OVERLAY_BBOX_BIT|OVERLAY_PIVOT_BIT) )
|
|
{
|
|
// draw mass center
|
|
if ( VPhysicsGetObject() )
|
|
{
|
|
Vector massCenter = VPhysicsGetObject()->GetMassCenterLocalSpace();
|
|
Vector worldPos;
|
|
VPhysicsGetObject()->LocalToWorld( &worldPos, massCenter );
|
|
NDebugOverlay::Cross3D( worldPos, 12, 255, 0, 0, false, 0 );
|
|
DebugDrawContactPoints(VPhysicsGetObject());
|
|
if ( GetMoveType() != MOVETYPE_VPHYSICS )
|
|
{
|
|
Vector pos;
|
|
QAngle angles;
|
|
VPhysicsGetObject()->GetPosition( &pos, &angles );
|
|
float dist = (pos - GetAbsOrigin()).Length();
|
|
|
|
Vector axis;
|
|
float deltaAngle;
|
|
RotationDeltaAxisAngle( angles, GetAbsAngles(), axis, deltaAngle );
|
|
if ( dist > 2 || fabsf(deltaAngle) > 2 )
|
|
{
|
|
Vector mins, maxs;
|
|
physcollision->CollideGetAABB( &mins, &maxs, VPhysicsGetObject()->GetCollide(), vec3_origin, vec3_angle );
|
|
NDebugOverlay::BoxAngles( pos, mins, maxs, angles, 255, 255, 0, 16, 0 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ( m_debugOverlays & OVERLAY_SHOW_BLOCKSLOS )
|
|
{
|
|
if ( BlocksLOS() )
|
|
{
|
|
NDebugOverlay::EntityBounds(this, 255, 255, 255, 0, 0 );
|
|
}
|
|
}
|
|
if ( m_debugOverlays & OVERLAY_AUTOAIM_BIT && (GetFlags()&FL_AIMTARGET) && AI_GetSinglePlayer() != NULL )
|
|
{
|
|
// Crude, but it gets the point across.
|
|
Vector vecCenter = GetAutoAimCenter();
|
|
Vector vecRight, vecUp, vecDiag;
|
|
CBasePlayer *pPlayer = AI_GetSinglePlayer();
|
|
float radius = GetAutoAimRadius();
|
|
|
|
QAngle angles = pPlayer->EyeAngles();
|
|
AngleVectors( angles, NULL, &vecRight, &vecUp );
|
|
|
|
int r,g,b;
|
|
|
|
if( ((int)gpGlobals->curtime) % 2 == 1 )
|
|
{
|
|
r = 255;
|
|
g = 255;
|
|
b = 255;
|
|
|
|
if( pPlayer->GetActiveWeapon() != NULL )
|
|
radius *= pPlayer->GetActiveWeapon()->WeaponAutoAimScale();
|
|
|
|
}
|
|
else
|
|
{
|
|
r = 255;g=0;b=0;
|
|
|
|
if( !ShouldAttractAutoAim(pPlayer) )
|
|
{
|
|
g = 255;
|
|
}
|
|
}
|
|
|
|
if( pPlayer->IsInAVehicle() )
|
|
{
|
|
radius *= sv_vehicle_autoaim_scale.GetFloat();
|
|
}
|
|
|
|
NDebugOverlay::Line( vecCenter, vecCenter + vecRight * radius, r, g, b, true, 0.1 );
|
|
NDebugOverlay::Line( vecCenter, vecCenter - vecRight * radius, r, g, b, true, 0.1 );
|
|
NDebugOverlay::Line( vecCenter, vecCenter + vecUp * radius, r, g, b, true, 0.1 );
|
|
NDebugOverlay::Line( vecCenter, vecCenter - vecUp * radius, r, g, b, true, 0.1 );
|
|
|
|
vecDiag = vecRight + vecUp;
|
|
VectorNormalize( vecDiag );
|
|
NDebugOverlay::Line( vecCenter - vecDiag * radius, vecCenter + vecDiag * radius, r, g, b, true, 0.1 );
|
|
|
|
vecDiag = vecRight - vecUp;
|
|
VectorNormalize( vecDiag );
|
|
NDebugOverlay::Line( vecCenter - vecDiag * radius, vecCenter + vecDiag * radius, r, g, b, true, 0.1 );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Draw any text overlays (override in subclass to add additional text)
|
|
// Output : Current text offset from the top
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::DrawDebugTextOverlays(void)
|
|
{
|
|
int offset = 1;
|
|
if (m_debugOverlays & OVERLAY_TEXT_BIT)
|
|
{
|
|
char tempstr[512];
|
|
Q_snprintf( tempstr, sizeof(tempstr), "(%d) Name: %s (%s)", entindex(), GetDebugName(), GetClassname() );
|
|
EntityText(offset,tempstr, 0);
|
|
offset++;
|
|
|
|
if( m_iGlobalname != NULL_STRING )
|
|
{
|
|
Q_snprintf( tempstr, sizeof(tempstr), "GLOBALNAME: %s", STRING(m_iGlobalname) );
|
|
EntityText(offset,tempstr, 0);
|
|
offset++;
|
|
}
|
|
|
|
Vector vecOrigin = GetAbsOrigin();
|
|
Q_snprintf( tempstr, sizeof(tempstr), "Position: %0.1f, %0.1f, %0.1f\n", vecOrigin.x, vecOrigin.y, vecOrigin.z );
|
|
EntityText( offset, tempstr, 0 );
|
|
offset++;
|
|
|
|
if( GetModelName() != NULL_STRING || GetBaseAnimating() )
|
|
{
|
|
Q_snprintf(tempstr, sizeof(tempstr), "Model:%s", STRING(GetModelName()) );
|
|
EntityText(offset,tempstr,0);
|
|
offset++;
|
|
}
|
|
|
|
if( m_hDamageFilter.Get() != NULL )
|
|
{
|
|
Q_snprintf( tempstr, sizeof(tempstr), "DAMAGE FILTER:%s", m_hDamageFilter->GetDebugName() );
|
|
EntityText( offset,tempstr,0 );
|
|
offset++;
|
|
}
|
|
|
|
#ifdef MAPBASE
|
|
if (m_ResponseContexts.Count() > 0)
|
|
{
|
|
const char *contexts = UTIL_VarArgs("%s:%s", STRING(m_ResponseContexts[0].m_iszName), STRING(m_ResponseContexts[0].m_iszValue));
|
|
for (int i = 1; i < GetContextCount(); i++)
|
|
{
|
|
contexts = UTIL_VarArgs("%s,%s:%s", contexts, STRING(m_ResponseContexts[i].m_iszName), STRING(m_ResponseContexts[i].m_iszValue));
|
|
}
|
|
Q_snprintf(tempstr, sizeof(tempstr), "Response Contexts:%s", contexts);
|
|
EntityText(offset, tempstr, 0);
|
|
offset++;
|
|
}
|
|
#endif
|
|
|
|
#ifdef MAPBASE_VSCRIPT
|
|
// 'OnEntText' hook inspired by later source games
|
|
if (m_ScriptScope.IsInitialized() && g_Hook_OnEntText.CanRunInScope( m_ScriptScope ))
|
|
{
|
|
if (ent_text_allow_script.GetBool())
|
|
{
|
|
ScriptVariant_t functionReturn;
|
|
if ( g_Hook_OnEntText.Call( m_ScriptScope, &functionReturn, NULL ) && functionReturn.m_type == FIELD_CSTRING )
|
|
{
|
|
CUtlStringList outStrings;
|
|
V_SplitString( functionReturn.m_pszString, "\n", outStrings );
|
|
|
|
FOR_EACH_VEC( outStrings, i )
|
|
{
|
|
EntityText( offset, outStrings[i], 0, 224, 240, 255 );
|
|
offset++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (m_debugOverlays & OVERLAY_VIEWOFFSET)
|
|
{
|
|
NDebugOverlay::Cross3D( EyePosition(), 16, 255, 0, 0, true, 0.05f );
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
|
|
void CBaseEntity::SetParent( string_t newParent, CBaseEntity *pActivator, int iAttachment )
|
|
{
|
|
// find and notify the new parent
|
|
#ifdef MAPBASE
|
|
CBaseEntity *pParent = gEntList.FindEntityByName( NULL, newParent, this, pActivator );
|
|
#else
|
|
CBaseEntity *pParent = gEntList.FindEntityByName( NULL, newParent, NULL, pActivator );
|
|
#endif
|
|
|
|
// debug check
|
|
if ( newParent != NULL_STRING && pParent == NULL )
|
|
{
|
|
Msg( "Entity %s(%s) has bad parent %s\n", STRING(m_iClassname), GetDebugName(), STRING(newParent) );
|
|
}
|
|
else
|
|
{
|
|
// make sure there isn't any ambiguity
|
|
#ifdef MAPBASE
|
|
if ( gEntList.FindEntityByName( pParent, newParent, this, pActivator ) )
|
|
#else
|
|
if ( gEntList.FindEntityByName( pParent, newParent, NULL, pActivator ) )
|
|
#endif
|
|
{
|
|
Msg( "Entity %s(%s) has ambigious parent %s\n", STRING(m_iClassname), GetDebugName(), STRING(newParent) );
|
|
}
|
|
SetParent( pParent, iAttachment );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Move our points from parent to worldspace
|
|
// Input : *pParent - Parent to use as reference
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::TransformStepData_ParentToWorld( CBaseEntity *pParent )
|
|
{
|
|
// Fix up our step simulation points to be in the proper local space
|
|
StepSimulationData *step = (StepSimulationData *) GetDataObject( STEPSIMULATION );
|
|
if ( step != NULL )
|
|
{
|
|
// Convert our positions
|
|
UTIL_ParentToWorldSpace( pParent, step->m_Previous2.vecOrigin, step->m_Previous2.qRotation );
|
|
UTIL_ParentToWorldSpace( pParent, step->m_Previous.vecOrigin, step->m_Previous.qRotation );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Move step data between two parent-spaces
|
|
// Input : *pOldParent - parent we were attached to
|
|
// *pNewParent - parent we're now attached to
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::TransformStepData_ParentToParent( CBaseEntity *pOldParent, CBaseEntity *pNewParent )
|
|
{
|
|
// Fix up our step simulation points to be in the proper local space
|
|
StepSimulationData *step = (StepSimulationData *) GetDataObject( STEPSIMULATION );
|
|
if ( step != NULL )
|
|
{
|
|
// Convert our positions
|
|
UTIL_ParentToWorldSpace( pOldParent, step->m_Previous2.vecOrigin, step->m_Previous2.qRotation );
|
|
UTIL_WorldToParentSpace( pNewParent, step->m_Previous2.vecOrigin, step->m_Previous2.qRotation );
|
|
|
|
UTIL_ParentToWorldSpace( pOldParent, step->m_Previous.vecOrigin, step->m_Previous.qRotation );
|
|
UTIL_WorldToParentSpace( pNewParent, step->m_Previous.vecOrigin, step->m_Previous.qRotation );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: After parenting to an object, we need to also correctly translate our
|
|
// step stimulation positions and angles into that parent space. Otherwise
|
|
// we end up splining between two different world spaces.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::TransformStepData_WorldToParent( CBaseEntity *pParent )
|
|
{
|
|
// Fix up our step simulation points to be in the proper local space
|
|
StepSimulationData *step = (StepSimulationData *) GetDataObject( STEPSIMULATION );
|
|
if ( step != NULL )
|
|
{
|
|
// Convert our positions
|
|
UTIL_WorldToParentSpace( pParent, step->m_Previous2.vecOrigin, step->m_Previous2.qRotation );
|
|
UTIL_WorldToParentSpace( pParent, step->m_Previous.vecOrigin, step->m_Previous.qRotation );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets the movement parent of this entity. This entity will be moved
|
|
// to a local coordinate calculated from its current absolute offset
|
|
// from the parent entity and will then follow the parent entity.
|
|
// Input : pParentEntity - This entity's new parent in the movement hierarchy.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SetParent( CBaseEntity *pParentEntity, int iAttachment )
|
|
{
|
|
// If they didn't specify an attachment, use our current
|
|
if ( iAttachment == -1 )
|
|
{
|
|
iAttachment = m_iParentAttachment;
|
|
}
|
|
|
|
bool bWasNotParented = ( GetParent() == NULL );
|
|
CBaseEntity *pOldParent = m_pParent;
|
|
|
|
// notify the old parent of the loss
|
|
UnlinkFromParent( this );
|
|
|
|
// set the new name
|
|
m_pParent = pParentEntity;
|
|
|
|
if ( m_pParent == this )
|
|
{
|
|
// should never set parent to 'this' - makes no sense
|
|
Assert(0);
|
|
m_pParent = NULL;
|
|
}
|
|
|
|
if ( m_pParent == NULL )
|
|
{
|
|
m_iParent = NULL_STRING;
|
|
|
|
// Transform step data from parent to worldspace
|
|
TransformStepData_ParentToWorld( pOldParent );
|
|
return;
|
|
}
|
|
|
|
m_iParent = m_pParent->m_iName;
|
|
|
|
RemoveSolidFlags( FSOLID_ROOT_PARENT_ALIGNED );
|
|
if ( pParentEntity )
|
|
{
|
|
if ( const_cast<CBaseEntity *>(pParentEntity)->GetRootMoveParent()->GetSolid() == SOLID_BSP )
|
|
{
|
|
AddSolidFlags( FSOLID_ROOT_PARENT_ALIGNED );
|
|
}
|
|
else
|
|
{
|
|
if ( GetSolid() == SOLID_BSP )
|
|
{
|
|
// Must be SOLID_VPHYSICS because parent might rotate
|
|
SetSolid( SOLID_VPHYSICS );
|
|
}
|
|
}
|
|
}
|
|
// set the move parent if we have one
|
|
if ( edict() )
|
|
{
|
|
// add ourselves to the list
|
|
LinkChild( m_pParent, this );
|
|
|
|
m_iParentAttachment = (char)iAttachment;
|
|
|
|
EntityMatrix matrix, childMatrix;
|
|
matrix.InitFromEntity( const_cast<CBaseEntity *>(pParentEntity), m_iParentAttachment ); // parent->world
|
|
childMatrix.InitFromEntityLocal( this ); // child->world
|
|
Vector localOrigin = matrix.WorldToLocal( GetLocalOrigin() );
|
|
|
|
// I have the axes of local space in world space. (childMatrix)
|
|
// I want to compute those world space axes in the parent's local space
|
|
// and set that transform (as angles) on the child's object so the net
|
|
// result is that the child is now in parent space, but still oriented the same way
|
|
VMatrix tmp = matrix.Transpose(); // world->parent
|
|
tmp.MatrixMul( childMatrix, matrix ); // child->parent
|
|
QAngle angles;
|
|
MatrixToAngles( matrix, angles );
|
|
SetLocalAngles( angles );
|
|
UTIL_SetOrigin( this, localOrigin );
|
|
|
|
// Move our step data into the correct space
|
|
if ( bWasNotParented )
|
|
{
|
|
// Transform step data from world to parent-space
|
|
TransformStepData_WorldToParent( this );
|
|
}
|
|
else
|
|
{
|
|
// Transform step data between parent-spaces
|
|
TransformStepData_ParentToParent( pOldParent, this );
|
|
}
|
|
}
|
|
if ( VPhysicsGetObject() )
|
|
{
|
|
if ( VPhysicsGetObject()->IsStatic())
|
|
{
|
|
if ( VPhysicsGetObject()->IsAttachedToConstraint(false) )
|
|
{
|
|
Warning("SetParent on static object, all constraints attached to %s (%s)will now be broken!\n", GetDebugName(), GetClassname() );
|
|
}
|
|
VPhysicsDestroyObject();
|
|
VPhysicsInitShadow(false, false);
|
|
}
|
|
}
|
|
CollisionRulesChanged();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::ValidateEntityConnections()
|
|
{
|
|
if ( m_target == NULL_STRING )
|
|
return;
|
|
|
|
if ( ClassMatches( "scripted_*" ) ||
|
|
ClassMatches( "trigger_relay" ) ||
|
|
ClassMatches( "trigger_auto" ) ||
|
|
ClassMatches( "path_*" ) ||
|
|
ClassMatches( "monster_*" ) ||
|
|
ClassMatches( "trigger_teleport" ) ||
|
|
ClassMatches( "func_train" ) ||
|
|
ClassMatches( "func_tracktrain" ) ||
|
|
ClassMatches( "func_plat*" ) ||
|
|
ClassMatches( "npc_*" ) ||
|
|
ClassMatches( "info_big*" ) ||
|
|
ClassMatches( "env_texturetoggle" ) ||
|
|
ClassMatches( "env_render" ) ||
|
|
ClassMatches( "func_areaportalwindow") ||
|
|
ClassMatches( "point_view*") ||
|
|
ClassMatches( "func_traincontrols" ) ||
|
|
ClassMatches( "multisource" ) ||
|
|
ClassMatches( "xen_plant*" ) )
|
|
return;
|
|
|
|
datamap_t *dmap = GetDataDescMap();
|
|
while ( dmap )
|
|
{
|
|
int fields = dmap->dataNumFields;
|
|
for ( int i = 0; i < fields; i++ )
|
|
{
|
|
typedescription_t *dataDesc = &dmap->dataDesc[i];
|
|
if ( ( dataDesc->fieldType == FIELD_CUSTOM ) && ( dataDesc->flags & FTYPEDESC_OUTPUT ) )
|
|
{
|
|
CBaseEntityOutput *pOutput = (CBaseEntityOutput *)((int)this + (int)dataDesc->fieldOffset[0]);
|
|
if ( pOutput->NumberOfElements() )
|
|
return;
|
|
}
|
|
}
|
|
|
|
dmap = dmap->baseMap;
|
|
}
|
|
|
|
Vector vecLoc = WorldSpaceCenter();
|
|
Warning("---------------------------------\n");
|
|
Warning( "Entity %s - (%s) has a target and NO OUTPUTS\n", GetDebugName(), GetClassname() );
|
|
Warning( "Location %f %f %f\n", vecLoc.x, vecLoc.y, vecLoc.z );
|
|
Warning("---------------------------------\n");
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::FireNamedOutput( const char *pszOutput, variant_t variant, CBaseEntity *pActivator, CBaseEntity *pCaller, float flDelay )
|
|
{
|
|
if ( pszOutput == NULL )
|
|
return;
|
|
|
|
CBaseEntityOutput *pOutput = FindNamedOutput( pszOutput );
|
|
if ( pOutput )
|
|
{
|
|
pOutput->FireOutput( variant, pActivator, pCaller, flDelay );
|
|
return;
|
|
}
|
|
}
|
|
|
|
#ifdef MAPBASE_VSCRIPT
|
|
void CBaseEntity::ScriptFireOutput( const char *pszOutput, HSCRIPT hActivator, HSCRIPT hCaller, const char *szValue, float flDelay )
|
|
{
|
|
variant_t value;
|
|
value.SetString( MAKE_STRING(szValue) );
|
|
|
|
FireNamedOutput( pszOutput, value, ToEnt(hActivator), ToEnt(hCaller), flDelay );
|
|
}
|
|
|
|
float CBaseEntity::GetMaxOutputDelay( const char *pszOutput )
|
|
{
|
|
CBaseEntityOutput *pOutput = FindNamedOutput( pszOutput );
|
|
if ( pOutput )
|
|
{
|
|
return pOutput->GetMaxDelay();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//void CBaseEntity::CancelEventsByInput( const char *szInput )
|
|
//{
|
|
// g_EventQueue.CancelEventsByInput( this, szInput );
|
|
//}
|
|
#endif // MAPBASE_VSCRIPT
|
|
|
|
CBaseEntityOutput *CBaseEntity::FindNamedOutput( const char *pszOutput )
|
|
{
|
|
if ( pszOutput == NULL )
|
|
return NULL;
|
|
|
|
datamap_t *dmap = GetDataDescMap();
|
|
while ( dmap )
|
|
{
|
|
int fields = dmap->dataNumFields;
|
|
for ( int i = 0; i < fields; i++ )
|
|
{
|
|
typedescription_t *dataDesc = &dmap->dataDesc[i];
|
|
if ( ( dataDesc->fieldType == FIELD_CUSTOM ) && ( dataDesc->flags & FTYPEDESC_OUTPUT ) )
|
|
{
|
|
CBaseEntityOutput *pOutput = ( CBaseEntityOutput * )( ( int )this + ( int )dataDesc->fieldOffset[0] );
|
|
if ( !Q_stricmp( dataDesc->externalName, pszOutput ) )
|
|
{
|
|
return pOutput;
|
|
}
|
|
}
|
|
}
|
|
dmap = dmap->baseMap;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void CBaseEntity::Activate( void )
|
|
{
|
|
#ifdef DEBUG
|
|
extern bool g_bCheckForChainedActivate;
|
|
extern bool g_bReceivedChainedActivate;
|
|
|
|
if ( g_bCheckForChainedActivate && g_bReceivedChainedActivate )
|
|
{
|
|
Assert( !"Multiple calls to base class Activate()\n" );
|
|
}
|
|
g_bReceivedChainedActivate = true;
|
|
#endif
|
|
|
|
// NOTE: This forces a team change so that stuff in the level
|
|
// that starts out on a team correctly changes team
|
|
if (m_iInitialTeamNum)
|
|
{
|
|
ChangeTeam( m_iInitialTeamNum );
|
|
}
|
|
|
|
// Get a handle to my damage filter entity if there is one.
|
|
if ( m_iszDamageFilterName != NULL_STRING )
|
|
{
|
|
m_hDamageFilter = gEntList.FindEntityByName( NULL, m_iszDamageFilterName );
|
|
}
|
|
|
|
// Add any non-null context strings to our context vector
|
|
if ( m_iszResponseContext != NULL_STRING )
|
|
{
|
|
AddContext( m_iszResponseContext.ToCStr() );
|
|
}
|
|
|
|
#ifdef HL1_DLL
|
|
ValidateEntityConnections();
|
|
#endif //HL1_DLL
|
|
}
|
|
|
|
//////////////////////////// old CBaseEntity stuff ///////////////////////////////////
|
|
|
|
|
|
// give health.
|
|
// Returns the amount of health actually taken.
|
|
int CBaseEntity::TakeHealth( float flHealth, int bitsDamageType )
|
|
{
|
|
if ( !edict() || m_takedamage < DAMAGE_YES )
|
|
return 0;
|
|
|
|
int iMax = GetMaxHealth();
|
|
|
|
// heal
|
|
if ( m_iHealth >= iMax )
|
|
return 0;
|
|
|
|
const int oldHealth = m_iHealth;
|
|
|
|
m_iHealth += flHealth;
|
|
|
|
if (m_iHealth > iMax)
|
|
m_iHealth = iMax;
|
|
|
|
return m_iHealth - oldHealth;
|
|
}
|
|
|
|
// inflict damage on this entity. bitsDamageType indicates type of damage inflicted, ie: DMG_CRUSH
|
|
|
|
int CBaseEntity::OnTakeDamage( const CTakeDamageInfo &info )
|
|
{
|
|
Vector vecTemp;
|
|
|
|
if ( !edict() || !m_takedamage )
|
|
return 0;
|
|
|
|
if ( info.GetInflictor() )
|
|
{
|
|
vecTemp = info.GetInflictor()->WorldSpaceCenter() - ( WorldSpaceCenter() );
|
|
}
|
|
else
|
|
{
|
|
vecTemp.Init( 1, 0, 0 );
|
|
}
|
|
|
|
// this global is still used for glass and other non-NPC killables, along with decals.
|
|
g_vecAttackDir = vecTemp;
|
|
VectorNormalize(g_vecAttackDir);
|
|
|
|
// save damage based on the target's armor level
|
|
|
|
// figure momentum add (don't let hurt brushes or other triggers move player)
|
|
|
|
// physics objects have their own calcs for this: (don't let fire move things around!)
|
|
if ( !IsEFlagSet( EFL_NO_DAMAGE_FORCES ) )
|
|
{
|
|
if ( ( GetMoveType() == MOVETYPE_VPHYSICS ) )
|
|
{
|
|
VPhysicsTakeDamage( info );
|
|
}
|
|
else
|
|
{
|
|
if ( info.GetInflictor() && (GetMoveType() == MOVETYPE_WALK || GetMoveType() == MOVETYPE_STEP) &&
|
|
!info.GetAttacker()->IsSolidFlagSet(FSOLID_TRIGGER) )
|
|
{
|
|
Vector vecDir, vecInflictorCentroid;
|
|
vecDir = WorldSpaceCenter( );
|
|
vecInflictorCentroid = info.GetInflictor()->WorldSpaceCenter( );
|
|
vecDir -= vecInflictorCentroid;
|
|
VectorNormalize( vecDir );
|
|
|
|
float flForce = info.GetDamage() * ((32 * 32 * 72.0) / (WorldAlignSize().x * WorldAlignSize().y * WorldAlignSize().z)) * 5;
|
|
|
|
if (flForce > 1000.0)
|
|
flForce = 1000.0;
|
|
ApplyAbsVelocityImpulse( vecDir * flForce );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( m_takedamage != DAMAGE_EVENTS_ONLY )
|
|
{
|
|
// do the damage
|
|
m_iHealth -= info.GetDamage();
|
|
if (m_iHealth <= 0)
|
|
{
|
|
Event_Killed( info );
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Scale damage done and call OnTakeDamage
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::TakeDamage( const CTakeDamageInfo &inputInfo )
|
|
{
|
|
if ( !g_pGameRules )
|
|
return;
|
|
|
|
bool bHasPhysicsForceDamage = !g_pGameRules->Damage_NoPhysicsForce( inputInfo.GetDamageType() );
|
|
if ( bHasPhysicsForceDamage && inputInfo.GetDamageType() != DMG_GENERIC )
|
|
{
|
|
// If you hit this assert, you've called TakeDamage with a damage type that requires a physics damage
|
|
// force & position without specifying one or both of them. Decide whether your damage that's causing
|
|
// this is something you believe should impart physics force on the receiver. If it is, you need to
|
|
// setup the damage force & position inside the CTakeDamageInfo (Utility functions for this are in
|
|
// takedamageinfo.cpp. If you think the damage shouldn't cause force (unlikely!) then you can set the
|
|
// damage type to DMG_GENERIC, or | DMG_CRUSH if you need to preserve the damage type for purposes of HUD display.
|
|
|
|
if ( inputInfo.GetDamageForce() == vec3_origin || inputInfo.GetDamagePosition() == vec3_origin )
|
|
{
|
|
static int warningCount = 0;
|
|
if ( ++warningCount < 10 )
|
|
{
|
|
if ( inputInfo.GetDamageForce() == vec3_origin )
|
|
{
|
|
DevWarning( "CBaseEntity::TakeDamage: with inputInfo.GetDamageForce() == vec3_origin\n" );
|
|
}
|
|
if ( inputInfo.GetDamagePosition() == vec3_origin )
|
|
{
|
|
DevWarning( "CBaseEntity::TakeDamage: with inputInfo.GetDamagePosition() == vec3_origin\n" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef MAPBASE // Moved below the gamerules AllowDamage() check
|
|
// Make sure our damage filter allows the damage.
|
|
if ( !PassesDamageFilter( inputInfo ))
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if( !g_pGameRules->AllowDamage(this, inputInfo) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
#ifdef MAPBASE
|
|
// Make sure our damage filter allows the damage.
|
|
if ( !PassesFinalDamageFilter( inputInfo ))
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if ( PhysIsInCallback() )
|
|
{
|
|
PhysCallbackDamage( this, inputInfo );
|
|
}
|
|
else
|
|
{
|
|
CTakeDamageInfo info = inputInfo;
|
|
|
|
// Scale the damage by the attacker's modifier.
|
|
if ( info.GetAttacker() )
|
|
{
|
|
info.ScaleDamage( info.GetAttacker()->GetAttackDamageScale( this ) );
|
|
}
|
|
|
|
// Scale the damage by my own modifiers
|
|
info.ScaleDamage( GetReceivedDamageScale( info.GetAttacker() ) );
|
|
|
|
//Msg("%s took %.2f Damage, at %.2f\n", GetClassname(), info.GetDamage(), gpGlobals->curtime );
|
|
|
|
#ifdef MAPBASE
|
|
// Modify damage if we have a filter that does that
|
|
DamageFilterDamageMod(info);
|
|
#endif
|
|
|
|
OnTakeDamage( info );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns a value that scales all damage done by this entity.
|
|
//-----------------------------------------------------------------------------
|
|
float CBaseEntity::GetAttackDamageScale( CBaseEntity *pVictim )
|
|
{
|
|
float flScale = 1;
|
|
FOR_EACH_LL( m_DamageModifiers, i )
|
|
{
|
|
if ( !m_DamageModifiers[i]->IsDamageDoneToMe() )
|
|
{
|
|
flScale *= m_DamageModifiers[i]->GetModifier();
|
|
}
|
|
}
|
|
return flScale;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns a value that scales all damage done to this entity
|
|
//-----------------------------------------------------------------------------
|
|
float CBaseEntity::GetReceivedDamageScale( CBaseEntity *pAttacker )
|
|
{
|
|
float flScale = 1;
|
|
FOR_EACH_LL( m_DamageModifiers, i )
|
|
{
|
|
if ( m_DamageModifiers[i]->IsDamageDoneToMe() )
|
|
{
|
|
flScale *= m_DamageModifiers[i]->GetModifier();
|
|
}
|
|
}
|
|
return flScale;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Applies forces to our physics object in response to damage.
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::VPhysicsTakeDamage( const CTakeDamageInfo &info )
|
|
{
|
|
// don't let physics impacts or fire cause objects to move (again)
|
|
bool bNoPhysicsForceDamage = g_pGameRules->Damage_NoPhysicsForce( info.GetDamageType() );
|
|
if ( bNoPhysicsForceDamage || info.GetDamageType() == DMG_GENERIC )
|
|
return 1;
|
|
|
|
Assert(VPhysicsGetObject() != NULL);
|
|
if ( VPhysicsGetObject() )
|
|
{
|
|
Vector force = info.GetDamageForce();
|
|
Vector offset = info.GetDamagePosition();
|
|
|
|
// If you hit this assert, you've called TakeDamage with a damage type that requires a physics damage
|
|
// force & position without specifying one or both of them. Decide whether your damage that's causing
|
|
// this is something you believe should impart physics force on the receiver. If it is, you need to
|
|
// setup the damage force & position inside the CTakeDamageInfo (Utility functions for this are in
|
|
// takedamageinfo.cpp. If you think the damage shouldn't cause force (unlikely!) then you can set the
|
|
// damage type to DMG_GENERIC, or | DMG_CRUSH if you need to preserve the damage type for purposes of HUD display.
|
|
#if !defined( TF_DLL )
|
|
Assert( force != vec3_origin && offset != vec3_origin );
|
|
#else
|
|
// this was spamming the console for Payload maps in TF (trigger_hurt entity on the front of the cart)
|
|
if ( !TFGameRules() || TFGameRules()->GetGameType() != TF_GAMETYPE_ESCORT )
|
|
{
|
|
Assert( force != vec3_origin && offset != vec3_origin );
|
|
}
|
|
#endif
|
|
|
|
unsigned short gameFlags = VPhysicsGetObject()->GetGameFlags();
|
|
if ( gameFlags & FVPHYSICS_PLAYER_HELD )
|
|
{
|
|
// if the player is holding the object, use it's real mass (player holding reduced the mass)
|
|
CBasePlayer *pPlayer = NULL;
|
|
|
|
if ( AI_IsSinglePlayer() )
|
|
{
|
|
pPlayer = UTIL_GetLocalPlayer();
|
|
}
|
|
else
|
|
{
|
|
// See which MP player is holding the physics object and then use that player to get the real mass of the object.
|
|
// This is ugly but better than having linkage between an object and its "holding" player.
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CBasePlayer *tempPlayer = UTIL_PlayerByIndex( i );
|
|
if ( tempPlayer && (tempPlayer->GetHeldObject() == this ) )
|
|
{
|
|
pPlayer = tempPlayer;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( pPlayer )
|
|
{
|
|
float mass = pPlayer->GetHeldObjectMass( VPhysicsGetObject() );
|
|
if ( mass != 0.0f )
|
|
{
|
|
float ratio = VPhysicsGetObject()->GetMass() / mass;
|
|
force *= ratio;
|
|
}
|
|
}
|
|
}
|
|
else if ( (gameFlags & FVPHYSICS_PART_OF_RAGDOLL) && (gameFlags & FVPHYSICS_CONSTRAINT_STATIC) )
|
|
{
|
|
IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
|
|
int count = VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
if ( !(pList[i]->GetGameFlags() & FVPHYSICS_CONSTRAINT_STATIC) )
|
|
{
|
|
pList[i]->ApplyForceOffset( force, offset );
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
}
|
|
VPhysicsGetObject()->ApplyForceOffset( force, offset );
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
// Character killed (only fired once)
|
|
void CBaseEntity::Event_Killed( const CTakeDamageInfo &info )
|
|
{
|
|
#ifdef MAPBASE_VSCRIPT
|
|
// False = Cheat death
|
|
if (ScriptDeathHook( const_cast<CTakeDamageInfo*>(&info) ) == false)
|
|
return;
|
|
#endif
|
|
|
|
if( info.GetAttacker() )
|
|
{
|
|
info.GetAttacker()->Event_KilledOther(this, info);
|
|
}
|
|
|
|
m_takedamage = DAMAGE_NO;
|
|
m_lifeState = LIFE_DEAD;
|
|
UTIL_Remove( this );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: helper method to send a game event when this entity is killed. Note:
|
|
// gets called specifically for particular entities (mostly NPC), this
|
|
// does not get called for every entity
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SendOnKilledGameEvent( const CTakeDamageInfo &info )
|
|
{
|
|
IGameEvent *event = gameeventmanager->CreateEvent( "entity_killed" );
|
|
if ( event )
|
|
{
|
|
event->SetInt( "entindex_killed", entindex() );
|
|
if ( info.GetAttacker())
|
|
{
|
|
event->SetInt( "entindex_attacker", info.GetAttacker()->entindex() );
|
|
}
|
|
if ( info.GetInflictor())
|
|
{
|
|
event->SetInt( "entindex_inflictor", info.GetInflictor()->entindex() );
|
|
}
|
|
event->SetInt( "damagebits", info.GetDamageType() );
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info )
|
|
{
|
|
#ifdef MAPBASE_VSCRIPT
|
|
if (m_ScriptScope.IsInitialized() && g_Hook_OnKilledOther.CanRunInScope( m_ScriptScope ))
|
|
{
|
|
HSCRIPT hInfo = g_pScriptVM->RegisterInstance( const_cast<CTakeDamageInfo*>(&info) );
|
|
|
|
// victim, info
|
|
ScriptVariant_t args[] = { ScriptVariant_t( pVictim->GetScriptInstance() ), ScriptVariant_t( hInfo ) };
|
|
g_Hook_OnKilledOther.Call( m_ScriptScope, NULL, args );
|
|
|
|
g_pScriptVM->RemoveInstance( hInfo );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
bool CBaseEntity::HasTarget( string_t targetname )
|
|
{
|
|
if( targetname != NULL_STRING && m_target != NULL_STRING )
|
|
return FStrEq(STRING(targetname), STRING(m_target) );
|
|
else
|
|
return false;
|
|
}
|
|
|
|
|
|
CBaseEntity *CBaseEntity::GetNextTarget( void )
|
|
{
|
|
if ( !m_target )
|
|
return NULL;
|
|
return gEntList.FindEntityByName( NULL, m_target );
|
|
}
|
|
|
|
class CThinkContextsSaveDataOps : public CDefSaveRestoreOps
|
|
{
|
|
virtual void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave )
|
|
{
|
|
AssertMsg( fieldInfo.pTypeDesc->fieldSize == 1, "CThinkContextsSaveDataOps does not support arrays");
|
|
|
|
// Write out the vector
|
|
CUtlVector< thinkfunc_t > *pUtlVector = (CUtlVector< thinkfunc_t > *)fieldInfo.pField;
|
|
SaveUtlVector( pSave, pUtlVector, FIELD_EMBEDDED );
|
|
|
|
// Get our owner
|
|
CBaseEntity *pOwner = (CBaseEntity*)fieldInfo.pOwner;
|
|
|
|
pSave->StartBlock();
|
|
// Now write out all the functions
|
|
for ( int i = 0; i < pUtlVector->Size(); i++ )
|
|
{
|
|
#ifdef WIN32
|
|
void **ppV = (void**)&((*pUtlVector)[i].m_pfnThink);
|
|
#else
|
|
BASEPTR *ppV = &((*pUtlVector)[i].m_pfnThink);
|
|
#endif
|
|
bool bHasFunc = (*ppV != NULL);
|
|
pSave->WriteBool( &bHasFunc, 1 );
|
|
if ( bHasFunc )
|
|
{
|
|
pSave->WriteFunction( pOwner->GetDataDescMap(), "m_pfnThink", (inputfunc_t **)ppV, 1 );
|
|
}
|
|
}
|
|
pSave->EndBlock();
|
|
}
|
|
|
|
virtual void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore )
|
|
{
|
|
AssertMsg( fieldInfo.pTypeDesc->fieldSize == 1, "CThinkContextsSaveDataOps does not support arrays");
|
|
|
|
// Read in the vector
|
|
CUtlVector< thinkfunc_t > *pUtlVector = (CUtlVector< thinkfunc_t > *)fieldInfo.pField;
|
|
RestoreUtlVector( pRestore, pUtlVector, FIELD_EMBEDDED );
|
|
|
|
// Get our owner
|
|
CBaseEntity *pOwner = (CBaseEntity*)fieldInfo.pOwner;
|
|
|
|
pRestore->StartBlock();
|
|
// Now read in all the functions
|
|
for ( int i = 0; i < pUtlVector->Size(); i++ )
|
|
{
|
|
bool bHasFunc;
|
|
pRestore->ReadBool( &bHasFunc, 1 );
|
|
#ifdef WIN32
|
|
void **ppV = (void**)&((*pUtlVector)[i].m_pfnThink);
|
|
#else
|
|
BASEPTR *ppV = &((*pUtlVector)[i].m_pfnThink);
|
|
Q_memset( (void *)ppV, 0x0, sizeof(inputfunc_t) );
|
|
#endif
|
|
if ( bHasFunc )
|
|
{
|
|
SaveRestoreRecordHeader_t header;
|
|
pRestore->ReadHeader( &header );
|
|
pRestore->ReadFunction( pOwner->GetDataDescMap(), (inputfunc_t **)ppV, 1, header.size );
|
|
}
|
|
else
|
|
{
|
|
*ppV = NULL;
|
|
}
|
|
}
|
|
pRestore->EndBlock();
|
|
}
|
|
|
|
virtual bool IsEmpty( const SaveRestoreFieldInfo_t &fieldInfo )
|
|
{
|
|
CUtlVector< thinkfunc_t > *pUtlVector = (CUtlVector< thinkfunc_t > *)fieldInfo.pField;
|
|
return ( pUtlVector->Count() == 0 );
|
|
}
|
|
|
|
virtual void MakeEmpty( const SaveRestoreFieldInfo_t &fieldInfo )
|
|
{
|
|
BASEPTR pFunc = *((BASEPTR*)fieldInfo.pField);
|
|
pFunc = NULL;
|
|
}
|
|
};
|
|
CThinkContextsSaveDataOps g_ThinkContextsSaveDataOps;
|
|
ISaveRestoreOps *thinkcontextFuncs = &g_ThinkContextsSaveDataOps;
|
|
|
|
BEGIN_SIMPLE_DATADESC( thinkfunc_t )
|
|
|
|
DEFINE_FIELD( m_iszContext, FIELD_STRING ),
|
|
// DEFINE_FIELD( m_pfnThink, FIELD_FUNCTION ), // Manually written
|
|
DEFINE_FIELD( m_nNextThinkTick, FIELD_TICK ),
|
|
DEFINE_FIELD( m_nLastThinkTick, FIELD_TICK ),
|
|
|
|
END_DATADESC()
|
|
|
|
BEGIN_SIMPLE_DATADESC( ResponseContext_t )
|
|
|
|
DEFINE_FIELD( m_iszName, FIELD_STRING ),
|
|
DEFINE_FIELD( m_iszValue, FIELD_STRING ),
|
|
DEFINE_FIELD( m_fExpirationTime, FIELD_TIME ),
|
|
|
|
END_DATADESC()
|
|
|
|
BEGIN_DATADESC_NO_BASE( CBaseEntity )
|
|
|
|
DEFINE_KEYFIELD( m_iClassname, FIELD_STRING, "classname" ),
|
|
DEFINE_GLOBAL_KEYFIELD( m_iGlobalname, FIELD_STRING, "globalname" ),
|
|
DEFINE_KEYFIELD( m_iParent, FIELD_STRING, "parentname" ),
|
|
|
|
DEFINE_KEYFIELD( m_iHammerID, FIELD_INTEGER, "hammerid" ), // save ID numbers so that entities can be tracked between save/restore and vmf
|
|
|
|
DEFINE_KEYFIELD( m_flSpeed, FIELD_FLOAT, "speed" ),
|
|
DEFINE_KEYFIELD( m_nRenderFX, FIELD_CHARACTER, "renderfx" ),
|
|
DEFINE_KEYFIELD( m_nRenderMode, FIELD_CHARACTER, "rendermode" ),
|
|
|
|
// Consider moving to CBaseAnimating?
|
|
DEFINE_FIELD( m_flPrevAnimTime, FIELD_TIME ),
|
|
DEFINE_FIELD( m_flAnimTime, FIELD_TIME ),
|
|
DEFINE_FIELD( m_flSimulationTime, FIELD_TIME ),
|
|
DEFINE_FIELD( m_nLastThinkTick, FIELD_TICK ),
|
|
|
|
DEFINE_FIELD(m_iszScriptId, FIELD_STRING),
|
|
// m_ScriptScope;
|
|
// m_hScriptInstance;
|
|
|
|
DEFINE_KEYFIELD(m_iszVScripts, FIELD_STRING, "vscripts"),
|
|
DEFINE_KEYFIELD(m_iszScriptThinkFunction, FIELD_STRING, "thinkfunction"),
|
|
DEFINE_KEYFIELD( m_nNextThinkTick, FIELD_TICK, "nextthink" ),
|
|
DEFINE_KEYFIELD( m_fEffects, FIELD_INTEGER, "effects" ),
|
|
DEFINE_KEYFIELD( m_clrRender, FIELD_COLOR32, "rendercolor" ),
|
|
DEFINE_GLOBAL_KEYFIELD( m_nModelIndex, FIELD_SHORT, "modelindex" ),
|
|
#ifdef MAPBASE
|
|
DEFINE_KEYFIELD( m_iViewHideFlags, FIELD_INTEGER, "viewhideflags" ),
|
|
DEFINE_KEYFIELD( m_bDisableFlashlight, FIELD_BOOLEAN, "disableflashlight" ),
|
|
#endif
|
|
#if !defined( NO_ENTITY_PREDICTION )
|
|
// DEFINE_FIELD( m_PredictableID, CPredictableId ),
|
|
#endif
|
|
DEFINE_FIELD( touchStamp, FIELD_INTEGER ),
|
|
DEFINE_CUSTOM_FIELD( m_aThinkFunctions, thinkcontextFuncs ),
|
|
// m_iCurrentThinkContext (not saved, debug field only, and think transient to boot)
|
|
|
|
DEFINE_UTLVECTOR(m_ResponseContexts, FIELD_EMBEDDED),
|
|
DEFINE_KEYFIELD( m_iszResponseContext, FIELD_STRING, "ResponseContext" ),
|
|
|
|
DEFINE_FIELD( m_pfnThink, FIELD_FUNCTION ),
|
|
DEFINE_FIELD( m_pfnTouch, FIELD_FUNCTION ),
|
|
DEFINE_FIELD( m_pfnUse, FIELD_FUNCTION ),
|
|
DEFINE_FIELD( m_pfnBlocked, FIELD_FUNCTION ),
|
|
DEFINE_FIELD( m_pfnMoveDone, FIELD_FUNCTION ),
|
|
|
|
DEFINE_FIELD( m_lifeState, FIELD_CHARACTER ),
|
|
DEFINE_FIELD( m_takedamage, FIELD_CHARACTER ),
|
|
DEFINE_KEYFIELD( m_iMaxHealth, FIELD_INTEGER, "max_health" ),
|
|
DEFINE_KEYFIELD( m_iHealth, FIELD_INTEGER, "health" ),
|
|
// DEFINE_FIELD( m_pLink, FIELD_CLASSPTR ),
|
|
DEFINE_KEYFIELD( m_target, FIELD_STRING, "target" ),
|
|
|
|
DEFINE_KEYFIELD( m_iszDamageFilterName, FIELD_STRING, "damagefilter" ),
|
|
DEFINE_FIELD( m_hDamageFilter, FIELD_EHANDLE ),
|
|
|
|
DEFINE_FIELD( m_debugOverlays, FIELD_INTEGER ),
|
|
|
|
DEFINE_GLOBAL_FIELD( m_pParent, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_iParentAttachment, FIELD_CHARACTER ),
|
|
DEFINE_GLOBAL_FIELD( m_hMoveParent, FIELD_EHANDLE ),
|
|
DEFINE_GLOBAL_FIELD( m_hMoveChild, FIELD_EHANDLE ),
|
|
DEFINE_GLOBAL_FIELD( m_hMovePeer, FIELD_EHANDLE ),
|
|
|
|
DEFINE_FIELD( m_iEFlags, FIELD_INTEGER ),
|
|
|
|
DEFINE_FIELD( m_iName, FIELD_STRING ),
|
|
DEFINE_EMBEDDED( m_Collision ),
|
|
DEFINE_EMBEDDED( m_Network ),
|
|
|
|
DEFINE_FIELD( m_MoveType, FIELD_CHARACTER ),
|
|
DEFINE_FIELD( m_MoveCollide, FIELD_CHARACTER ),
|
|
#ifdef MAPBASE
|
|
DEFINE_KEYFIELD( m_hOwnerEntity, FIELD_EHANDLE, "OwnerEntity" ),
|
|
#else
|
|
DEFINE_FIELD( m_hOwnerEntity, FIELD_EHANDLE ),
|
|
#endif
|
|
DEFINE_FIELD( m_CollisionGroup, FIELD_INTEGER ),
|
|
DEFINE_PHYSPTR( m_pPhysicsObject),
|
|
DEFINE_FIELD( m_flElasticity, FIELD_FLOAT ),
|
|
DEFINE_KEYFIELD( m_flShadowCastDistance, FIELD_FLOAT, "shadowcastdist" ),
|
|
DEFINE_FIELD( m_flDesiredShadowCastDistance, FIELD_FLOAT ),
|
|
|
|
DEFINE_INPUT( m_iInitialTeamNum, FIELD_INTEGER, "TeamNum" ),
|
|
DEFINE_FIELD( m_iTeamNum, FIELD_INTEGER ),
|
|
|
|
// DEFINE_FIELD( m_bSentLastFrame, FIELD_INTEGER ),
|
|
|
|
DEFINE_FIELD( m_hGroundEntity, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_flGroundChangeTime, FIELD_TIME ),
|
|
DEFINE_GLOBAL_KEYFIELD( m_ModelName, FIELD_MODELNAME, "model" ),
|
|
|
|
DEFINE_KEYFIELD( m_vecBaseVelocity, FIELD_VECTOR, "basevelocity" ),
|
|
DEFINE_FIELD( m_vecAbsVelocity, FIELD_VECTOR ),
|
|
DEFINE_KEYFIELD( m_vecAngVelocity, FIELD_VECTOR, "avelocity" ),
|
|
// DEFINE_FIELD( m_vecAbsAngVelocity, FIELD_VECTOR ),
|
|
DEFINE_ARRAY( m_rgflCoordinateFrame, FIELD_FLOAT, 12 ), // NOTE: MUST BE IN LOCAL SPACE, NOT POSITION_VECTOR!!! (see CBaseEntity::Restore)
|
|
|
|
DEFINE_KEYFIELD( m_nWaterLevel, FIELD_CHARACTER, "waterlevel" ),
|
|
DEFINE_FIELD( m_nWaterType, FIELD_CHARACTER ),
|
|
DEFINE_FIELD( m_pBlocker, FIELD_EHANDLE ),
|
|
|
|
DEFINE_KEYFIELD( m_flGravity, FIELD_FLOAT, "gravity" ),
|
|
DEFINE_KEYFIELD( m_flFriction, FIELD_FLOAT, "friction" ),
|
|
|
|
// Local time is local to each object. It doesn't need to be re-based if the clock
|
|
// changes. Therefore it is saved as a FIELD_FLOAT, not a FIELD_TIME
|
|
DEFINE_KEYFIELD( m_flLocalTime, FIELD_FLOAT, "ltime" ),
|
|
DEFINE_FIELD( m_flVPhysicsUpdateLocalTime, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_flMoveDoneTime, FIELD_FLOAT ),
|
|
|
|
// DEFINE_FIELD( m_nPushEnumCount, FIELD_INTEGER ),
|
|
|
|
DEFINE_FIELD( m_vecAbsOrigin, FIELD_POSITION_VECTOR ),
|
|
DEFINE_KEYFIELD( m_vecVelocity, FIELD_VECTOR, "velocity" ),
|
|
DEFINE_KEYFIELD( m_iTextureFrameIndex, FIELD_CHARACTER, "texframeindex" ),
|
|
DEFINE_FIELD( m_bSimulatedEveryTick, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_bAnimatedEveryTick, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_bAlternateSorting, FIELD_BOOLEAN ),
|
|
DEFINE_KEYFIELD( m_spawnflags, FIELD_INTEGER, "spawnflags" ),
|
|
DEFINE_FIELD( m_nTransmitStateOwnedCounter, FIELD_CHARACTER ),
|
|
DEFINE_FIELD( m_angAbsRotation, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_vecOrigin, FIELD_VECTOR ), // NOTE: MUST BE IN LOCAL SPACE, NOT POSITION_VECTOR!!! (see CBaseEntity::Restore)
|
|
DEFINE_FIELD( m_angRotation, FIELD_VECTOR ),
|
|
|
|
DEFINE_KEYFIELD( m_vecViewOffset, FIELD_VECTOR, "view_ofs" ),
|
|
|
|
#ifdef MAPBASE
|
|
// You know, m_fFlags access
|
|
DEFINE_KEYFIELD( m_fFlags, FIELD_INTEGER, "m_fFlags" ),
|
|
#else
|
|
DEFINE_FIELD( m_fFlags, FIELD_INTEGER ),
|
|
#endif
|
|
#if !defined( NO_ENTITY_PREDICTION )
|
|
// DEFINE_FIELD( m_bIsPlayerSimulated, FIELD_INTEGER ),
|
|
// DEFINE_FIELD( m_hPlayerSimulationOwner, FIELD_EHANDLE ),
|
|
#endif
|
|
// DEFINE_FIELD( m_pTimedOverlay, TimedOverlay_t* ),
|
|
DEFINE_FIELD( m_nSimulationTick, FIELD_TICK ),
|
|
// DEFINE_FIELD( m_RefEHandle, CBaseHandle ),
|
|
|
|
// DEFINE_FIELD( m_nWaterTouch, FIELD_INTEGER ),
|
|
// DEFINE_FIELD( m_nSlimeTouch, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_flNavIgnoreUntilTime, FIELD_TIME ),
|
|
|
|
// DEFINE_FIELD( m_bToolRecording, FIELD_BOOLEAN ),
|
|
// DEFINE_FIELD( m_ToolHandle, FIELD_INTEGER ),
|
|
|
|
// NOTE: This is tricky. TeamNum must be saved, but we can't directly
|
|
// read it in, because we can only set it after the team entity has been read in,
|
|
// which may or may not actually occur before the entity is parsed.
|
|
// Therefore, we set the TeamNum from the InitialTeamNum in Activate
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetTeam", InputSetTeam ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "Kill", InputKill ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "KillHierarchy", InputKillHierarchy ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "Use", InputUse ),
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "Alpha", InputAlpha ),
|
|
DEFINE_INPUTFUNC( FIELD_BOOLEAN, "AlternativeSorting", InputAlternativeSorting ),
|
|
DEFINE_INPUTFUNC( FIELD_COLOR32, "Color", InputColor ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetParent", InputSetParent ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetParentAttachment", InputSetParentAttachment ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetParentAttachmentMaintainOffset", InputSetParentAttachmentMaintainOffset ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "ClearParent", InputClearParent ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetDamageFilter", InputSetDamageFilter ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "EnableDamageForces", InputEnableDamageForces ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "DisableDamageForces", InputDisableDamageForces ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "DispatchEffect", InputDispatchEffect ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "DispatchResponse", InputDispatchResponse ),
|
|
|
|
// Entity I/O methods to alter context
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "AddContext", InputAddContext ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "RemoveContext", InputRemoveContext ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "ClearContext", InputClearContext ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "DisableShadow", InputDisableShadow ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "EnableShadow", InputEnableShadow ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "AddOutput", InputAddOutput ),
|
|
#ifdef MAPBASE
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "ChangeVariable", InputChangeVariable ),
|
|
#endif
|
|
|
|
#ifdef MAPBASE
|
|
DEFINE_INPUTFUNC( FIELD_INPUT, "PassUser1", InputPassUser1 ),
|
|
DEFINE_INPUTFUNC( FIELD_INPUT, "PassUser2", InputPassUser2 ),
|
|
DEFINE_INPUTFUNC( FIELD_INPUT, "PassUser3", InputPassUser3 ),
|
|
DEFINE_INPUTFUNC( FIELD_INPUT, "PassUser4", InputPassUser4 ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "FireUser1", InputFireUser1 ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "FireUser2", InputFireUser2 ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "FireUser3", InputFireUser3 ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "FireUser4", InputFireUser4 ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "FireRandomUser", InputFireRandomUser ),
|
|
DEFINE_INPUTFUNC( FIELD_INPUT, "PassRandomUser", InputPassRandomUser ),
|
|
#else
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "FireUser1", InputFireUser1 ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "FireUser2", InputFireUser2 ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "FireUser3", InputFireUser3 ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "FireUser4", InputFireUser4 ),
|
|
#endif
|
|
|
|
DEFINE_INPUTFUNC(FIELD_STRING, "RunScriptFile", InputRunScriptFile),
|
|
DEFINE_INPUTFUNC(FIELD_STRING, "RunScriptCode", InputRunScript),
|
|
DEFINE_INPUTFUNC(FIELD_STRING, "CallScriptFunction", InputCallScriptFunction),
|
|
#ifdef MAPBASE_VSCRIPT
|
|
DEFINE_INPUTFUNC(FIELD_STRING, "RunScriptCodeQuotable", InputRunScriptQuotable),
|
|
DEFINE_INPUTFUNC(FIELD_VOID, "ClearScriptScope", InputClearScriptScope),
|
|
#endif
|
|
|
|
#ifdef MAPBASE
|
|
DEFINE_OUTPUT( m_OutUser1, "OutUser1" ),
|
|
DEFINE_OUTPUT( m_OutUser2, "OutUser2" ),
|
|
DEFINE_OUTPUT( m_OutUser3, "OutUser3" ),
|
|
DEFINE_OUTPUT( m_OutUser4, "OutUser4" ),
|
|
#endif
|
|
|
|
DEFINE_OUTPUT( m_OnUser1, "OnUser1" ),
|
|
DEFINE_OUTPUT( m_OnUser2, "OnUser2" ),
|
|
DEFINE_OUTPUT( m_OnUser3, "OnUser3" ),
|
|
DEFINE_OUTPUT( m_OnUser4, "OnUser4" ),
|
|
|
|
#ifdef MAPBASE
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetEntityName", InputSetEntityName ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetTarget", InputSetTarget ),
|
|
DEFINE_INPUTFUNC( FIELD_EHANDLE, "SetOwnerEntity", InputSetOwnerEntity ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ),
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "AddHealth", InputAddHealth ),
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveHealth", InputRemoveHealth ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetMaxHealth", InputSetMaxHealth ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "FireOutput", InputFireOutput ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "RemoveOutput", InputRemoveOutput ),
|
|
//DEFINE_INPUTFUNC( FIELD_STRING, "CancelOutput", InputCancelOutput ), // Find a way to implement this
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "ReplaceOutput", InputReplaceOutput ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "AcceptInput", InputAcceptInput ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "CancelPending", InputCancelPending ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "FreeChildren", InputFreeChildren ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_VECTOR, "SetLocalOrigin", InputSetLocalOrigin ),
|
|
DEFINE_INPUTFUNC( FIELD_VECTOR, "SetLocalAngles", InputSetLocalAngles ),
|
|
DEFINE_INPUTFUNC( FIELD_VECTOR, "SetAbsOrigin", InputSetAbsOrigin ),
|
|
DEFINE_INPUTFUNC( FIELD_VECTOR, "SetAbsAngles", InputSetAbsAngles ),
|
|
DEFINE_INPUTFUNC( FIELD_VECTOR, "SetLocalVelocity", InputSetLocalVelocity ),
|
|
DEFINE_INPUTFUNC( FIELD_VECTOR, "SetLocalAngularVelocity", InputSetLocalAngularVelocity ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "AddSpawnFlags", InputAddSpawnFlags ),
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveSpawnFlags", InputRemoveSpawnFlags ),
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetRenderMode", InputSetRenderMode ),
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetRenderFX", InputSetRenderFX ),
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetViewHideFlags", InputSetViewHideFlags ),
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "AddEffects", InputAddEffects ),
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveEffects", InputRemoveEffects ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "EnableDraw", InputDrawEntity ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "DisableDraw", InputUndrawEntity ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "EnableReceivingFlashlight", InputEnableReceivingFlashlight ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "DisableReceivingFlashlight", InputDisableReceivingFlashlight ),
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "AddEFlags", InputAddEFlags ),
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveEFlags", InputRemoveEFlags ),
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "AddSolidFlags", InputAddSolidFlags ),
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveSolidFlags", InputRemoveSolidFlags ),
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetMoveType", InputSetMoveType ),
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetCollisionGroup", InputSetCollisionGroup ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_EHANDLE, "Touch", InputTouch ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_INPUT, "KilledNPC", InputKilledNPC ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "KillIfNotVisible", InputKillIfNotVisible ),
|
|
DEFINE_INPUTFUNC( FIELD_FLOAT, "KillWhenNotVisible", InputKillWhenNotVisible ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetThinkNull", InputSetThinkNull ),
|
|
|
|
DEFINE_OUTPUT( m_OnKilled, "OnKilled" ),
|
|
#endif
|
|
|
|
// Function Pointers
|
|
DEFINE_FUNCTION( SUB_Remove ),
|
|
DEFINE_FUNCTION( SUB_DoNothing ),
|
|
DEFINE_FUNCTION( SUB_StartFadeOut ),
|
|
DEFINE_FUNCTION( SUB_StartFadeOutInstant ),
|
|
DEFINE_FUNCTION( SUB_FadeOut ),
|
|
DEFINE_FUNCTION( SUB_Vanish ),
|
|
DEFINE_FUNCTION( SUB_CallUseToggle ),
|
|
DEFINE_THINKFUNC( ShadowCastDistThink ),
|
|
DEFINE_THINKFUNC( ScriptThink ),
|
|
#ifdef MAPBASE_VSCRIPT
|
|
DEFINE_THINKFUNC( ScriptContextThink ),
|
|
#endif
|
|
|
|
#ifdef MAPBASE
|
|
DEFINE_FUNCTION( SUB_RemoveWhenNotVisible ),
|
|
#endif
|
|
|
|
DEFINE_FIELD( m_hEffectEntity, FIELD_EHANDLE ),
|
|
|
|
//DEFINE_FIELD( m_DamageModifiers, FIELD_?? ), // can't save?
|
|
// DEFINE_FIELD( m_fDataObjectTypes, FIELD_INTEGER ),
|
|
|
|
#ifdef TF_DLL
|
|
DEFINE_ARRAY( m_nModelIndexOverrides, FIELD_INTEGER, MAX_VISION_MODES ),
|
|
#endif
|
|
|
|
END_DATADESC()
|
|
|
|
|
|
#ifdef MAPBASE_VSCRIPT
|
|
ScriptHook_t CBaseEntity::g_Hook_UpdateOnRemove;
|
|
ScriptHook_t CBaseEntity::g_Hook_OnEntText;
|
|
|
|
ScriptHook_t CBaseEntity::g_Hook_VPhysicsCollision;
|
|
ScriptHook_t CBaseEntity::g_Hook_FireBullets;
|
|
ScriptHook_t CBaseEntity::g_Hook_OnDeath;
|
|
ScriptHook_t CBaseEntity::g_Hook_OnKilledOther;
|
|
ScriptHook_t CBaseEntity::g_Hook_HandleInteraction;
|
|
ScriptHook_t CBaseEntity::g_Hook_ModifyEmitSoundParams;
|
|
ScriptHook_t CBaseEntity::g_Hook_ModifySentenceParams;
|
|
#endif
|
|
|
|
BEGIN_ENT_SCRIPTDESC_ROOT( CBaseEntity, "Root class of all server-side entities" )
|
|
DEFINE_SCRIPT_INSTANCE_HELPER( &g_BaseEntityScriptInstanceHelper )
|
|
DEFINE_SCRIPTFUNC_NAMED( ConnectOutputToScript, "ConnectOutput", "Adds an I/O connection that will call the named function when the specified output fires" )
|
|
DEFINE_SCRIPTFUNC_NAMED( DisconnectOutputFromScript, "DisconnectOutput", "Removes a connected script function from an I/O event." )
|
|
|
|
DEFINE_SCRIPTFUNC( GetHealth, "" )
|
|
DEFINE_SCRIPTFUNC( SetHealth, "" )
|
|
DEFINE_SCRIPTFUNC( GetMaxHealth, "" )
|
|
DEFINE_SCRIPTFUNC( SetMaxHealth, "" )
|
|
|
|
DEFINE_SCRIPTFUNC( SetModel, "" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetModelName, "GetModelName", "Returns the name of the model" )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptStopSound, "StopSound", "Stops a sound from this entity." )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptEmitSound, "EmitSound", "Plays a sound from this entity." )
|
|
DEFINE_SCRIPTFUNC_NAMED( VScriptPrecacheScriptSound, "PrecacheSoundScript", "Precache a sound for later playing." )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSoundDuration, "GetSoundDuration", "Returns float duration of the sound. Takes soundname and optional actormodelname.")
|
|
|
|
|
|
DEFINE_SCRIPTFUNC( GetClassname, "" )
|
|
DEFINE_SCRIPTFUNC_NAMED( GetEntityNameAsCStr, "GetName", "" )
|
|
#ifdef MAPBASE_VSCRIPT
|
|
DEFINE_SCRIPTFUNC( GetDebugName, "If name exists returns name, otherwise returns classname" )
|
|
DEFINE_SCRIPTFUNC_NAMED( SetNameAsCStr, "SetName", "" )
|
|
#endif
|
|
DEFINE_SCRIPTFUNC( GetPreTemplateName, "Get the entity name stripped of template unique decoration" )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( GetAbsOrigin, "GetOrigin", "" )
|
|
DEFINE_SCRIPTFUNC( SetAbsOrigin, "SetAbsOrigin" )
|
|
#ifdef MAPBASE_VSCRIPT
|
|
DEFINE_SCRIPTFUNC( SetAbsAngles, "SetAbsAngles" )
|
|
|
|
DEFINE_SCRIPTFUNC( GetLocalOrigin, "GetLocalOrigin" )
|
|
DEFINE_SCRIPTFUNC( SetLocalOrigin, "SetLocalOrigin" )
|
|
DEFINE_SCRIPTFUNC( GetLocalAngles, "GetLocalAngles" )
|
|
DEFINE_SCRIPTFUNC( SetLocalAngles, "SetLocalAngles" )
|
|
#endif
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetOrigin, "SetOrigin", "" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetForward, "SetForwardVector", "Set the orientation of the entity to have this forward vector" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetForward, "GetForwardVector", "Get the forward vector of the entity" )
|
|
#ifdef MAPBASE_VSCRIPT
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetRight, "GetRightVector", "Get the right vector of the entity" )
|
|
#endif
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetLeft, "GetLeftVector", SCRIPT_HIDE )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetUp, "GetUpVector", "Get the up vector of the entity" )
|
|
|
|
#ifdef MAPBASE_VSCRIPT
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetOriginAngles, "SetOriginAngles", "Set both the origin and the angles" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetOriginAnglesVelocity, "SetOriginAnglesVelocity", "Set the origin, the angles, and the velocity" )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptEntityToWorldTransform, "EntityToWorldTransform", "Get the entity's transform" )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetPhysicsObject, "GetPhysicsObject", "Get the entity's physics object if it has one" )
|
|
|
|
DEFINE_SCRIPTFUNC( ApplyAbsVelocityImpulse, "" )
|
|
DEFINE_SCRIPTFUNC( ApplyLocalAngularVelocityImpulse, "" )
|
|
|
|
DEFINE_SCRIPTFUNC( BodyTarget, "" )
|
|
DEFINE_SCRIPTFUNC( HeadTarget, "" )
|
|
#endif
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( GetAbsVelocity, "GetVelocity", "" )
|
|
DEFINE_SCRIPTFUNC_NAMED( SetAbsVelocity, "SetVelocity", "" )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetLocalAngularVelocity, "SetAngularVelocity", "Set the local angular velocity - takes float pitch,yaw,roll velocities" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetLocalAngularVelocity, "GetAngularVelocity", "Get the local angular velocity - returns a vector of pitch,yaw,roll" )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( WorldSpaceCenter, "GetCenter", "Get vector to center of object - absolute coords")
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptEyePosition, "EyePosition", "Get vector to eye position - absolute coords")
|
|
#ifdef MAPBASE_VSCRIPT
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptEyeAngles, "EyeAngles", "Get eye pitch, yaw, roll as a vector" )
|
|
#endif
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetAngles, "SetAngles", "Set entity pitch, yaw, roll")
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetAngles, "GetAngles", "Get entity pitch, yaw, roll as a vector")
|
|
|
|
DEFINE_SCRIPTFUNC( SetSize, "" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetBoundingMins, "GetBoundingMins", "Get a vector containing min bounds, centered on object")
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetBoundingMaxs, "GetBoundingMaxs", "Get a vector containing max bounds, centered on object")
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( Remove, "Destroy", "" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetOwner, "SetOwner", "" )
|
|
DEFINE_SCRIPTFUNC_NAMED( GetTeamNumber, "GetTeam", "" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ChangeTeam, "SetTeam", "" )
|
|
|
|
#ifdef MAPBASE_VSCRIPT
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetParent, "SetParent", "" )
|
|
#endif
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetMoveParent, "GetMoveParent", "If in hierarchy, retrieves the entity's parent" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetRootMoveParent, "GetRootMoveParent", "If in hierarchy, walks up the hierarchy to find the root parent" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptFirstMoveChild, "FirstMoveChild", "" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptNextMovePeer, "NextMovePeer", "" )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( KeyValueFromString, "__KeyValueFromString", SCRIPT_HIDE )
|
|
DEFINE_SCRIPTFUNC_NAMED( KeyValueFromFloat, "__KeyValueFromFloat", SCRIPT_HIDE )
|
|
DEFINE_SCRIPTFUNC_NAMED( KeyValueFromInt, "__KeyValueFromInt", SCRIPT_HIDE )
|
|
DEFINE_SCRIPTFUNC_NAMED( KeyValueFromVector, "__KeyValueFromVector", SCRIPT_HIDE )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetModelKeyValues, "GetModelKeyValues", "Get a KeyValue class instance on this entity's model")
|
|
|
|
#ifdef MAPBASE_VSCRIPT
|
|
DEFINE_SCRIPTFUNC( Activate, "" )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptIsVisible, "IsVisible", "Check if the specified position can be visible to this entity." )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptIsEntVisible, "IsEntVisible", "Check if the specified entity can be visible to this entity." )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptIsVisibleWithMask, "IsVisibleWithMask", "Check if the specified position can be visible to this entity with a specific trace mask." )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptTakeDamage, "TakeDamage", "Apply damage to this entity with a given info handle" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptFireBullets, "FireBullets", "Fire bullets from entity with a given info handle" )
|
|
|
|
DEFINE_SCRIPTFUNC( TakeHealth, "Give this entity health" )
|
|
DEFINE_SCRIPTFUNC( IsAlive, "Return true if this entity is alive" )
|
|
|
|
DEFINE_SCRIPTFUNC( GetWaterLevel, "Get current level of water submergence" )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetContext, "GetContext", "Get a response context value" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptAddContext, "AddContext", "Add a response context value" )
|
|
DEFINE_SCRIPTFUNC( GetContextExpireTime, "Get a response context's expiration time" )
|
|
DEFINE_SCRIPTFUNC( GetContextCount, "Get the number of response contexts" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetContextIndex, "GetContextIndex", "Get a response context at a specific index in the form of a table" )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptFollowEntity, "FollowEntity", "Begin following the specified entity. This makes this entity non-solid, parents it to the target entity, and teleports it to the specified entity's origin. The second parameter is whether or not to use bonemerging while following." )
|
|
DEFINE_SCRIPTFUNC( StopFollowingEntity, "Stops following an entity if we're following one." )
|
|
DEFINE_SCRIPTFUNC( IsFollowingEntity, "Returns true if this entity is following another entity." )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetFollowedEntity, "GetFollowedEntity", "Get the entity we're following." )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptClassify, "Classify", "Get Class_T class ID (corresponds to the CLASS_ set of constants)" )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptAcceptInput, "AcceptInput", "" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptFireOutput, "FireOutput", "Fire an entity output" )
|
|
DEFINE_SCRIPTFUNC( GetMaxOutputDelay, "Get the longest delay for all events attached to an output" )
|
|
//DEFINE_SCRIPTFUNC( CancelEventsByInput, "Cancel all I/O events for this entity, match input" ) // Commented out due to unpredictability and unknown risks
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptAddOutput, "AddOutput", "Add an output" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetKeyValue, "GetKeyValue", "Get a keyvalue" )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorVector, "GetRenderColorVector", "Get the render color as a vector" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorR, "GetRenderColorR", "Get the render color's R value" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorG, "GetRenderColorG", "Get the render color's G value" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorB, "GetRenderColorB", "Get the render color's B value" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetAlpha, "GetRenderAlpha", "Get the render color's alpha value" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorVector, "SetRenderColorVector", "Set the render color as a vector" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetColor, "SetRenderColor", "Set the render color" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorR, "SetRenderColorR", "Set the render color's R value" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorG, "SetRenderColorG", "Set the render color's G value" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorB, "SetRenderColorB", "Set the render color's B value" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetAlpha, "SetRenderAlpha", "Set the render color's alpha value" )
|
|
|
|
// LEGACY
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorVector, "GetColorVector", SCRIPT_HIDE )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorR, "GetColorR", SCRIPT_HIDE )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorG, "GetColorG", SCRIPT_HIDE )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorB, "GetColorB", SCRIPT_HIDE )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetAlpha, "GetAlpha", SCRIPT_HIDE )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorVector, "SetColorVector", SCRIPT_HIDE )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorR, "SetColorR", SCRIPT_HIDE )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorG, "SetColorG", SCRIPT_HIDE )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorB, "SetColorB", SCRIPT_HIDE )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetAlpha, "SetAlpha", SCRIPT_HIDE )
|
|
// END LEGACY
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetRenderMode, "GetRenderMode", "Get render mode" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetRenderMode, "SetRenderMode", "Set render mode" )
|
|
|
|
DEFINE_SCRIPTFUNC( GetSpawnFlags, "Get spawnflags" )
|
|
DEFINE_SCRIPTFUNC( AddSpawnFlags, "Add spawnflag(s)" )
|
|
DEFINE_SCRIPTFUNC( RemoveSpawnFlags, "Remove spawnflag(s)" )
|
|
DEFINE_SCRIPTFUNC( ClearSpawnFlags, "Clear spawnflag(s)" )
|
|
DEFINE_SCRIPTFUNC( HasSpawnFlags, "Check if the entity has specific spawnflag(s) ticked" )
|
|
|
|
DEFINE_SCRIPTFUNC( GetEffects, "Get effects" )
|
|
DEFINE_SCRIPTFUNC( AddEffects, "Add effect(s)" )
|
|
DEFINE_SCRIPTFUNC( RemoveEffects, "Remove effect(s)" )
|
|
DEFINE_SCRIPTFUNC( ClearEffects, "Clear effect(s)" )
|
|
DEFINE_SCRIPTFUNC( SetEffects, "Set effect(s)" )
|
|
DEFINE_SCRIPTFUNC( IsEffectActive, "Check if an effect is active" )
|
|
|
|
DEFINE_SCRIPTFUNC( GetFlags, "Get flags" )
|
|
DEFINE_SCRIPTFUNC( AddFlag, "Add flag" )
|
|
DEFINE_SCRIPTFUNC( RemoveFlag, "Remove flag" )
|
|
|
|
DEFINE_SCRIPTFUNC( GetEFlags, "Get Eflags" )
|
|
DEFINE_SCRIPTFUNC( AddEFlags, "Add Eflags" )
|
|
DEFINE_SCRIPTFUNC( RemoveEFlags, "Remove Eflags" )
|
|
|
|
DEFINE_SCRIPTFUNC( GetTransmitState, "" )
|
|
DEFINE_SCRIPTFUNC( SetTransmitState, "" )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetMoveType, "GetMoveType", "Get the move type" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetMoveType, "SetMoveType", "Set the move type" )
|
|
|
|
DEFINE_SCRIPTFUNC( GetCollisionGroup, "Get the collision group" )
|
|
DEFINE_SCRIPTFUNC( SetCollisionGroup, "Set the collision group" )
|
|
|
|
DEFINE_SCRIPTFUNC( GetGravity, "" )
|
|
DEFINE_SCRIPTFUNC( SetGravity, "" )
|
|
DEFINE_SCRIPTFUNC( GetFriction, "" )
|
|
DEFINE_SCRIPTFUNC( SetFriction, "" )
|
|
DEFINE_SCRIPTFUNC( GetMass, "" )
|
|
DEFINE_SCRIPTFUNC( SetMass, "" )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetSolid, "GetSolid", "" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetSolid, "SetSolid", "" )
|
|
|
|
DEFINE_SCRIPTFUNC( GetSolidFlags, "Get solid flags" )
|
|
DEFINE_SCRIPTFUNC( AddSolidFlags, "Add solid flags" )
|
|
DEFINE_SCRIPTFUNC( RemoveSolidFlags, "Remove solid flags" )
|
|
|
|
DEFINE_SCRIPTFUNC( IsPlayer, "Returns true if this entity is a player." )
|
|
DEFINE_SCRIPTFUNC( IsNPC, "Returns true if this entity is a NPC." )
|
|
DEFINE_SCRIPTFUNC( IsCombatCharacter, "Returns true if this entity is a combat character (player or NPC)." )
|
|
DEFINE_SCRIPTFUNC_NAMED( IsBaseCombatWeapon, "IsWeapon", "Returns true if this entity is a weapon." )
|
|
DEFINE_SCRIPTFUNC( IsWorld, "Returns true if this entity is the world." )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptDispatchInteraction, "DispatchInteraction", "Dispatches an interaction on this entity. See the g_interaction set of constants for more information." )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetTakeDamage, "GetTakeDamage", "Gets this entity's m_takedamage value. (DAMAGE_YES, DAMAGE_NO, etc.)" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetTakeDamage, "SetTakeDamage", "Sets this entity's m_takedamage value. (DAMAGE_YES, DAMAGE_NO, etc.)" )
|
|
|
|
// DEFINE_SCRIPTFUNC( IsMarkedForDeletion, "Returns true if the entity is valid and marked for deletion." )
|
|
#endif
|
|
|
|
DEFINE_SCRIPTFUNC( ValidateScriptScope, "Ensure that an entity's script scope has been created" )
|
|
DEFINE_SCRIPTFUNC( GetScriptScope, "Retrieve the script-side data associated with an entity" )
|
|
#ifdef MAPBASE_VSCRIPT
|
|
DEFINE_SCRIPTFUNC( GetOrCreatePrivateScriptScope, "Create and retrieve the script-side data associated with an entity" )
|
|
#endif
|
|
DEFINE_SCRIPTFUNC( GetScriptId, "Retrieve the unique identifier used to refer to the entity within the scripting system" )
|
|
DEFINE_SCRIPTFUNC_NAMED( GetScriptOwnerEntity, "GetOwner", "Gets this entity's owner" )
|
|
DEFINE_SCRIPTFUNC_NAMED( SetScriptOwnerEntity, "SetOwner", "Sets this entity's owner" )
|
|
DEFINE_SCRIPTFUNC( entindex, "" )
|
|
|
|
#ifdef MAPBASE_VSCRIPT
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetThinkFunction, "SetThinkFunction", "" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptStopThinkFunction, "StopThinkFunction", "" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetContextThink, "SetContextThink", "Set a think function on this entity." )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetThink, "SetThink", "" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptStopThink, "StopThink", "" )
|
|
|
|
//
|
|
// Hooks
|
|
//
|
|
DEFINE_SIMPLE_SCRIPTHOOK( CBaseEntity::g_Hook_UpdateOnRemove, "UpdateOnRemove", FIELD_VOID, "Called when the entity is being removed." )
|
|
DEFINE_SIMPLE_SCRIPTHOOK( CBaseEntity::g_Hook_OnEntText, "OnEntText", FIELD_CSTRING, "Called every frame when ent_text is enabled on the entity. Return a string to be added to the ent_text printout." )
|
|
|
|
BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_VPhysicsCollision, "VPhysicsCollision", FIELD_VOID, "Called for every single VPhysics-related collision experienced by this entity." )
|
|
DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT )
|
|
DEFINE_SCRIPTHOOK_PARAM( "speed", FIELD_FLOAT )
|
|
DEFINE_SCRIPTHOOK_PARAM( "point", FIELD_VECTOR )
|
|
DEFINE_SCRIPTHOOK_PARAM( "normal", FIELD_VECTOR )
|
|
END_SCRIPTHOOK()
|
|
|
|
BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_FireBullets, "FireBullets", FIELD_VOID, "Called for every single VPhysics-related collision experienced by this entity." )
|
|
DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT )
|
|
DEFINE_SCRIPTHOOK_PARAM( "speed", FIELD_FLOAT )
|
|
DEFINE_SCRIPTHOOK_PARAM( "point", FIELD_VECTOR )
|
|
DEFINE_SCRIPTHOOK_PARAM( "normal", FIELD_VECTOR )
|
|
END_SCRIPTHOOK()
|
|
|
|
BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_OnDeath, "OnDeath", FIELD_BOOLEAN, "Called when the entity dies (Event_Killed). Returning false makes the entity cancel death, although this could have unforeseen consequences. For hooking any damage instead of just death, see filter_script and PassesFinalDamageFilter." )
|
|
DEFINE_SCRIPTHOOK_PARAM( "info", FIELD_HSCRIPT )
|
|
END_SCRIPTHOOK()
|
|
|
|
BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_OnKilledOther, "OnKilledOther", FIELD_VOID, "Called when the entity kills another entity." )
|
|
DEFINE_SCRIPTHOOK_PARAM( "victim", FIELD_HSCRIPT )
|
|
DEFINE_SCRIPTHOOK_PARAM( "info", FIELD_HSCRIPT )
|
|
END_SCRIPTHOOK()
|
|
|
|
BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_HandleInteraction, "HandleInteraction", FIELD_BOOLEAN, "Called for internal game interactions. See the g_interaction set of constants for more information. Returning true or false will return that value without falling to any internal handling. Returning nothing will allow the interaction to fall to any internal handling." )
|
|
DEFINE_SCRIPTHOOK_PARAM( "interaction", FIELD_INTEGER )
|
|
//DEFINE_SCRIPTHOOK_PARAM( "data", FIELD_VARIANT )
|
|
DEFINE_SCRIPTHOOK_PARAM( "sourceEnt", FIELD_HSCRIPT )
|
|
END_SCRIPTHOOK()
|
|
|
|
BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_ModifyEmitSoundParams, "ModifyEmitSoundParams", FIELD_VOID, "Called every time a sound is emitted on this entity, allowing for its parameters to be modified." )
|
|
DEFINE_SCRIPTHOOK_PARAM( "params", FIELD_HSCRIPT )
|
|
END_SCRIPTHOOK()
|
|
|
|
BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_ModifySentenceParams, "ModifySentenceParams", FIELD_VOID, "Called every time a sentence is emitted on this entity, allowing for its parameters to be modified." )
|
|
DEFINE_SCRIPTHOOK_PARAM( "params", FIELD_HSCRIPT )
|
|
END_SCRIPTHOOK()
|
|
#endif
|
|
END_SCRIPTDESC();
|
|
|
|
|
|
// For code error checking
|
|
extern bool g_bReceivedChainedUpdateOnRemove;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Called just prior to object destruction
|
|
// Entities that need to unlink themselves from other entities should do the unlinking
|
|
// here rather than in their destructor. The reason why is that when the global entity list
|
|
// is told to Clear(), it first takes a pass through all active entities and calls UTIL_Remove
|
|
// on each such entity. Then it calls the delete function on each deleted entity in the list.
|
|
// In the old code, the objects were simply destroyed in order and there was no guarantee that the
|
|
// destructor of one object would not try to access another object that might already have been
|
|
// destructed (especially since the entity list order is more or less random!).
|
|
// NOTE: You should never call delete directly on an entity (there's an assert now), see note
|
|
// at CBaseEntity::~CBaseEntity for more information.
|
|
//
|
|
// NOTE: You should chain to BaseClass::UpdateOnRemove after doing your own cleanup code, e.g.:
|
|
//
|
|
// void CDerived::UpdateOnRemove( void )
|
|
// {
|
|
// ... cleanup code
|
|
// ...
|
|
//
|
|
// BaseClass::UpdateOnRemove();
|
|
// }
|
|
//
|
|
// In general, this function updates global tables that need to know about entities being removed
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::UpdateOnRemove( void )
|
|
{
|
|
g_bReceivedChainedUpdateOnRemove = true;
|
|
|
|
// Virtual call to shut down any looping sounds.
|
|
StopLoopingSounds();
|
|
|
|
// Notifies entity listeners, etc
|
|
gEntList.NotifyRemoveEntity( GetRefEHandle() );
|
|
|
|
if ( edict() )
|
|
{
|
|
AddFlag( FL_KILLME );
|
|
if ( GetFlags() & FL_GRAPHED )
|
|
{
|
|
/* <<TODO>>
|
|
// this entity was a LinkEnt in the world node graph, so we must remove it from
|
|
// the graph since we are removing it from the world.
|
|
for ( int i = 0 ; i < WorldGraph.m_cLinks ; i++ )
|
|
{
|
|
if ( WorldGraph.m_pLinkPool [ i ].m_pLinkEnt == pev )
|
|
{
|
|
// if this link has a link ent which is the same ent that is removing itself, remove it!
|
|
WorldGraph.m_pLinkPool [ i ].m_pLinkEnt = NULL;
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
|
|
if ( m_iGlobalname != NULL_STRING )
|
|
{
|
|
// NOTE: During level shutdown the global list will suppress this
|
|
// it assumes your changing levels or the game will end
|
|
// causing the whole list to be flushed
|
|
GlobalEntity_SetState( m_iGlobalname, GLOBAL_DEAD );
|
|
}
|
|
|
|
VPhysicsDestroyObject();
|
|
|
|
// This is only here to allow the MOVETYPE_NONE to be set without the
|
|
// assertion triggering. Why do we bother setting the MOVETYPE to none here?
|
|
RemoveEffects( EF_BONEMERGE );
|
|
SetMoveType(MOVETYPE_NONE);
|
|
|
|
// If we have a parent, unlink from it.
|
|
UnlinkFromParent( this );
|
|
|
|
// Any children still connected are orphans, mark all for delete
|
|
CUtlVector<CBaseEntity *> childrenList;
|
|
GetAllChildren( this, childrenList );
|
|
if ( childrenList.Count() )
|
|
{
|
|
DevMsg( 2, "Warning: Deleting orphaned children of %s\n", GetClassname() );
|
|
for ( int i = childrenList.Count()-1; i >= 0; --i )
|
|
{
|
|
UTIL_Remove( childrenList[i] );
|
|
}
|
|
}
|
|
|
|
SetGroundEntity( NULL );
|
|
|
|
if ( m_bDynamicModelPending )
|
|
{
|
|
sg_DynamicLoadHandlers.Remove( this );
|
|
}
|
|
|
|
if ( IsDynamicModelIndex( m_nModelIndex ) )
|
|
{
|
|
modelinfo->ReleaseDynamicModel( m_nModelIndex ); // no-op if not dynamic
|
|
m_nModelIndex = -1;
|
|
}
|
|
|
|
if ( m_hScriptInstance )
|
|
{
|
|
#ifdef MAPBASE_VSCRIPT
|
|
if (m_ScriptScope.IsInitialized())
|
|
{
|
|
g_Hook_UpdateOnRemove.Call( m_ScriptScope, NULL, NULL );
|
|
}
|
|
#endif // MAPBASE_VSCRIPT
|
|
|
|
g_pScriptVM->RemoveInstance( m_hScriptInstance );
|
|
m_hScriptInstance = NULL;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// capabilities
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::ObjectCaps( void )
|
|
{
|
|
#if 1
|
|
model_t *pModel = GetModel();
|
|
bool bIsBrush = ( pModel && modelinfo->GetModelType( pModel ) == mod_brush );
|
|
|
|
// We inherit our parent's use capabilities so that we can forward use commands
|
|
// to our parent.
|
|
CBaseEntity *pParent = GetParent();
|
|
if ( pParent )
|
|
{
|
|
int caps = pParent->ObjectCaps();
|
|
|
|
if ( !bIsBrush )
|
|
caps &= ( FCAP_ACROSS_TRANSITION | FCAP_IMPULSE_USE | FCAP_CONTINUOUS_USE | FCAP_ONOFF_USE | FCAP_DIRECTIONAL_USE );
|
|
else
|
|
caps &= ( FCAP_IMPULSE_USE | FCAP_CONTINUOUS_USE | FCAP_ONOFF_USE | FCAP_DIRECTIONAL_USE );
|
|
|
|
if ( pParent->IsPlayer() )
|
|
caps |= FCAP_ACROSS_TRANSITION;
|
|
|
|
return caps;
|
|
}
|
|
else if ( !bIsBrush )
|
|
{
|
|
return FCAP_ACROSS_TRANSITION;
|
|
}
|
|
|
|
return 0;
|
|
#else
|
|
// We inherit our parent's use capabilities so that we can forward use commands
|
|
// to our parent.
|
|
int parentCaps = 0;
|
|
if (GetParent())
|
|
{
|
|
parentCaps = GetParent()->ObjectCaps();
|
|
parentCaps &= ( FCAP_IMPULSE_USE | FCAP_CONTINUOUS_USE | FCAP_ONOFF_USE | FCAP_DIRECTIONAL_USE );
|
|
}
|
|
|
|
model_t *pModel = GetModel();
|
|
if ( pModel && modelinfo->GetModelType( pModel ) == mod_brush )
|
|
return parentCaps;
|
|
|
|
return FCAP_ACROSS_TRANSITION | parentCaps;
|
|
#endif
|
|
}
|
|
|
|
void CBaseEntity::StartTouch( CBaseEntity *pOther )
|
|
{
|
|
// notify parent
|
|
if ( m_pParent != NULL )
|
|
m_pParent->StartTouch( pOther );
|
|
}
|
|
|
|
void CBaseEntity::Touch( CBaseEntity *pOther )
|
|
{
|
|
if ( m_pfnTouch )
|
|
(this->*m_pfnTouch)( pOther );
|
|
|
|
// notify parent of touch
|
|
if ( m_pParent != NULL )
|
|
m_pParent->Touch( pOther );
|
|
}
|
|
|
|
void CBaseEntity::EndTouch( CBaseEntity *pOther )
|
|
{
|
|
// notify parent
|
|
if ( m_pParent != NULL )
|
|
{
|
|
m_pParent->EndTouch( pOther );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Dispatches blocked events to this entity's blocked handler, set via SetBlocked.
|
|
// Input : pOther - The entity that is blocking us.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::Blocked( CBaseEntity *pOther )
|
|
{
|
|
if ( m_pfnBlocked )
|
|
{
|
|
(this->*m_pfnBlocked)( pOther );
|
|
}
|
|
|
|
//
|
|
// Forward the blocked event to our parent, if any.
|
|
//
|
|
if ( m_pParent != NULL )
|
|
{
|
|
m_pParent->Blocked( pOther );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Dispatches use events to this entity's use handler, set via SetUse.
|
|
// Input : pActivator -
|
|
// pCaller -
|
|
// useType -
|
|
// value -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
|
|
{
|
|
if ( m_pfnUse != NULL )
|
|
{
|
|
(this->*m_pfnUse)( pActivator, pCaller, useType, value );
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We don't handle use events. Forward to our parent, if any.
|
|
//
|
|
if ( m_pParent != NULL )
|
|
{
|
|
m_pParent->Use( pActivator, pCaller, useType, value );
|
|
}
|
|
}
|
|
}
|
|
|
|
static CBaseEntity *FindPhysicsBlocker( IPhysicsObject *pPhysics, physicspushlist_t &list, const Vector &pushVel )
|
|
{
|
|
IPhysicsFrictionSnapshot *pSnapshot = pPhysics->CreateFrictionSnapshot();
|
|
CBaseEntity *pBlocker = NULL;
|
|
float maxForce = 0;
|
|
while ( pSnapshot->IsValid() )
|
|
{
|
|
IPhysicsObject *pOther = pSnapshot->GetObject(1);
|
|
CBaseEntity *pOtherEntity = static_cast<CBaseEntity *>(pOther->GetGameData());
|
|
bool inList = false;
|
|
for ( int i = 0; i < list.pushedCount; i++ )
|
|
{
|
|
if ( pOtherEntity == list.pushedEnts[i] )
|
|
{
|
|
inList = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Vector normal;
|
|
pSnapshot->GetSurfaceNormal(normal);
|
|
float dot = DotProduct( pushVel, pSnapshot->GetNormalForce() * normal );
|
|
if ( !pBlocker || (!inList && dot > maxForce) )
|
|
{
|
|
pBlocker = pOtherEntity;
|
|
if ( !inList )
|
|
{
|
|
maxForce = dot;
|
|
}
|
|
}
|
|
|
|
pSnapshot->NextFrictionData();
|
|
}
|
|
pPhysics->DestroyFrictionSnapshot( pSnapshot );
|
|
|
|
return pBlocker;
|
|
}
|
|
|
|
|
|
struct pushblock_t
|
|
{
|
|
physicspushlist_t *pList;
|
|
CBaseEntity *pRootParent;
|
|
CBaseEntity *pBlockedEntity;
|
|
float moveBackFraction;
|
|
float movetime;
|
|
};
|
|
|
|
static void ComputePushStartMatrix( matrix3x4_t &start, CBaseEntity *pEntity, const pushblock_t ¶ms )
|
|
{
|
|
Vector localOrigin;
|
|
QAngle localAngles;
|
|
if ( params.pList )
|
|
{
|
|
localOrigin = params.pList->localOrigin;
|
|
localAngles = params.pList->localAngles;
|
|
}
|
|
else
|
|
{
|
|
localOrigin = params.pRootParent->GetAbsOrigin() - params.pRootParent->GetAbsVelocity() * params.movetime;
|
|
localAngles = params.pRootParent->GetAbsAngles() - params.pRootParent->GetLocalAngularVelocity() * params.movetime;
|
|
}
|
|
matrix3x4_t xform, delta;
|
|
AngleMatrix( localAngles, localOrigin, xform );
|
|
|
|
matrix3x4_t srcInv;
|
|
// xform = src(-1) * dest
|
|
MatrixInvert( params.pRootParent->EntityToWorldTransform(), srcInv );
|
|
ConcatTransforms( xform, srcInv, delta );
|
|
ConcatTransforms( delta, pEntity->EntityToWorldTransform(), start );
|
|
}
|
|
|
|
#define DEBUG_PUSH_MESSAGES 0
|
|
static void CheckPushedEntity( CBaseEntity *pEntity, pushblock_t ¶ms )
|
|
{
|
|
IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject();
|
|
if ( !pPhysics )
|
|
return;
|
|
// somehow we've got a static or motion disabled physics object in hierarchy!
|
|
// This is not allowed! Don't test blocking in that case.
|
|
Assert(pPhysics->IsMoveable());
|
|
if ( !pPhysics->IsMoveable() || !pPhysics->GetShadowController() )
|
|
{
|
|
#if DEBUG_PUSH_MESSAGES
|
|
Msg("Blocking %s, not moveable!\n", pEntity->GetClassname());
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
bool checkrot = true;
|
|
bool checkmove = true;
|
|
Vector origin;
|
|
QAngle angles;
|
|
pPhysics->GetShadowPosition( &origin, &angles );
|
|
float fraction = -1.0f;
|
|
|
|
matrix3x4_t parentDelta;
|
|
if ( pEntity == params.pRootParent )
|
|
{
|
|
if ( pEntity->GetLocalAngularVelocity() == vec3_angle )
|
|
checkrot = false;
|
|
if ( pEntity->GetLocalVelocity() == vec3_origin)
|
|
checkmove = false;
|
|
}
|
|
else
|
|
{
|
|
#if DEBUG_PUSH_MESSAGES
|
|
if ( pPhysics->IsAttachedToConstraint(false))
|
|
{
|
|
Msg("Warning, hierarchical entity is attached to a constraint %s\n", pEntity->GetClassname());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if ( checkmove )
|
|
{
|
|
// project error onto the axis of movement
|
|
Vector dir = pEntity->GetAbsVelocity();
|
|
float speed = VectorNormalize(dir);
|
|
Vector targetPos;
|
|
pPhysics->GetShadowController()->GetTargetPosition( &targetPos, NULL );
|
|
float targetAmount = DotProduct(targetPos, dir);
|
|
float currentAmount = DotProduct(origin, dir);
|
|
float entityAmount = DotProduct(pEntity->GetAbsOrigin(), dir);
|
|
|
|
// if target and entity origin are not in sync, then the position of the entity was updated
|
|
// by something outside of push physics
|
|
if ( (targetAmount - entityAmount) > 1 )
|
|
{
|
|
pEntity->UpdatePhysicsShadowToCurrentPosition(0);
|
|
#if DEBUG_PUSH_MESSAGES
|
|
Warning("Someone slammed the position of a %s\n", pEntity->GetClassname() );
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
float dist = targetAmount - currentAmount;
|
|
if ( dist > 1 )
|
|
{
|
|
#if DEBUG_PUSH_MESSAGES
|
|
const char *pName = pEntity->GetClassname();
|
|
Msg( "%s blocked by %.2f units\n", pName, dist );
|
|
#endif
|
|
float movementAmount = targetAmount - (speed * params.movetime);
|
|
if ( pEntity == params.pRootParent )
|
|
{
|
|
if ( params.pList )
|
|
{
|
|
Vector localVel = pEntity->GetLocalVelocity();
|
|
VectorNormalize(localVel);
|
|
float localTargetAmt = DotProduct(pEntity->GetLocalOrigin(), localVel);
|
|
movementAmount = targetAmount + DotProduct(params.pList->localOrigin, localVel) - localTargetAmt;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
matrix3x4_t start;
|
|
ComputePushStartMatrix( start, pEntity, params );
|
|
Vector startPos;
|
|
MatrixPosition( start, startPos );
|
|
movementAmount = DotProduct(startPos, dir);
|
|
}
|
|
float expectedDist = targetAmount - movementAmount;
|
|
// compute the fraction to move back the AI to match the physics
|
|
if ( expectedDist <= 0 )
|
|
{
|
|
fraction = 1;
|
|
}
|
|
else
|
|
{
|
|
fraction = dist / expectedDist;
|
|
fraction = clamp(fraction, 0.f, 1.f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( checkrot )
|
|
{
|
|
Vector axis;
|
|
float deltaAngle;
|
|
RotationDeltaAxisAngle( angles, pEntity->GetAbsAngles(), axis, deltaAngle );
|
|
if ( fabsf(deltaAngle) > 0.5f )
|
|
{
|
|
Vector targetAxis;
|
|
QAngle targetRot;
|
|
float deltaTargetAngle;
|
|
pPhysics->GetShadowController()->GetTargetPosition( NULL, &targetRot );
|
|
RotationDeltaAxisAngle( angles, targetRot, targetAxis, deltaTargetAngle );
|
|
if ( fabsf(deltaTargetAngle) > 0.01f )
|
|
{
|
|
float expectedDist = deltaAngle;
|
|
#if DEBUG_PUSH_MESSAGES
|
|
const char *pName = pEntity->GetClassname();
|
|
Msg( "%s blocked by %.2f degrees\n", pName, deltaAngle );
|
|
if ( pPhysics->IsAsleep() )
|
|
{
|
|
Msg("Asleep while blocked?\n");
|
|
}
|
|
if ( pPhysics->GetGameFlags() & FVPHYSICS_PENETRATING )
|
|
{
|
|
Msg("Blocking for penetration!\n");
|
|
}
|
|
#endif
|
|
if ( pEntity == params.pRootParent )
|
|
{
|
|
expectedDist = pEntity->GetLocalAngularVelocity().Length() * params.movetime;
|
|
}
|
|
else
|
|
{
|
|
matrix3x4_t start;
|
|
ComputePushStartMatrix( start, pEntity, params );
|
|
Vector startAxis;
|
|
float startAngle;
|
|
Vector startPos;
|
|
QAngle startAngles;
|
|
MatrixAngles( start, startAngles, startPos );
|
|
RotationDeltaAxisAngle( startAngles, pEntity->GetAbsAngles(), startAxis, startAngle );
|
|
expectedDist = startAngle * DotProduct( startAxis, axis );
|
|
}
|
|
|
|
float t = expectedDist != 0.0f ? fabsf(deltaAngle / expectedDist) : 1.0f;
|
|
t = clamp(t,0.f,1.f);
|
|
fraction = MAX(fraction, t);
|
|
}
|
|
else
|
|
{
|
|
pEntity->UpdatePhysicsShadowToCurrentPosition(0);
|
|
#if DEBUG_PUSH_MESSAGES
|
|
Warning("Someone slammed the position of a %s\n", pEntity->GetClassname() );
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
if ( fraction >= params.moveBackFraction )
|
|
{
|
|
params.moveBackFraction = fraction;
|
|
params.pBlockedEntity = pEntity;
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::VPhysicsUpdatePusher( IPhysicsObject *pPhysics )
|
|
{
|
|
float movetime = m_flLocalTime - m_flVPhysicsUpdateLocalTime;
|
|
if (movetime <= 0)
|
|
return;
|
|
|
|
// only reconcile pushers on the final vphysics tick
|
|
if ( !PhysIsFinalTick() )
|
|
return;
|
|
|
|
Vector origin;
|
|
QAngle angles;
|
|
|
|
// physics updated the shadow, so check to see if I got blocked
|
|
// NOTE: SOLID_BSP cannont compute consistent collisions wrt vphysics, so
|
|
// don't allow vphysics to block. Assume game physics has handled it.
|
|
if ( GetSolid() != SOLID_BSP && pPhysics->GetShadowPosition( &origin, &angles ) )
|
|
{
|
|
CUtlVector<CBaseEntity *> list;
|
|
GetAllInHierarchy( this, list );
|
|
//NDebugOverlay::BoxAngles( origin, CollisionProp()->OBBMins(), CollisionProp()->OBBMaxs(), angles, 255,0,0,0, gpGlobals->frametime);
|
|
|
|
physicspushlist_t *pList = NULL;
|
|
if ( HasDataObjectType(PHYSICSPUSHLIST) )
|
|
{
|
|
pList = (physicspushlist_t *)GetDataObject( PHYSICSPUSHLIST );
|
|
Assert(pList);
|
|
}
|
|
bool checkrot = (GetLocalAngularVelocity() != vec3_angle) ? true : false;
|
|
bool checkmove = (GetLocalVelocity() != vec3_origin) ? true : false;
|
|
|
|
pushblock_t params;
|
|
params.pRootParent = this;
|
|
params.pList = pList;
|
|
params.pBlockedEntity = NULL;
|
|
params.moveBackFraction = 0.0f;
|
|
params.movetime = movetime;
|
|
for ( int i = 0; i < list.Count(); i++ )
|
|
{
|
|
if ( list[i]->IsSolid() )
|
|
{
|
|
CheckPushedEntity( list[i], params );
|
|
}
|
|
}
|
|
|
|
float physLocalTime = m_flLocalTime;
|
|
if ( params.pBlockedEntity )
|
|
{
|
|
float moveback = movetime * params.moveBackFraction;
|
|
if ( moveback > 0 )
|
|
{
|
|
physLocalTime = m_flLocalTime - moveback;
|
|
// add 1% noise for bouncing in collision.
|
|
if ( physLocalTime <= (m_flVPhysicsUpdateLocalTime + movetime * 0.99f) )
|
|
{
|
|
CBaseEntity *pBlocked = NULL;
|
|
IPhysicsObject *pOther;
|
|
if ( params.pBlockedEntity->VPhysicsGetObject()->GetContactPoint( NULL, &pOther ) )
|
|
{
|
|
pBlocked = static_cast<CBaseEntity *>(pOther->GetGameData());
|
|
}
|
|
// UNDONE: Need to traverse hierarchy here? Shouldn't.
|
|
if ( pList )
|
|
{
|
|
SetLocalOrigin( pList->localOrigin );
|
|
SetLocalAngles( pList->localAngles );
|
|
physLocalTime = pList->localMoveTime;
|
|
for ( int i = 0; i < pList->pushedCount; i++ )
|
|
{
|
|
CBaseEntity *pEntity = pList->pushedEnts[i];
|
|
if ( !pEntity )
|
|
continue;
|
|
|
|
pEntity->SetAbsOrigin( pEntity->GetAbsOrigin() - pList->pushVec[i] );
|
|
}
|
|
CBaseEntity *pPhysicsBlocker = FindPhysicsBlocker( VPhysicsGetObject(), *pList, pList->pushVec[0] );
|
|
if ( pPhysicsBlocker )
|
|
{
|
|
pBlocked = pPhysicsBlocker;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Vector origin = GetLocalOrigin();
|
|
QAngle angles = GetLocalAngles();
|
|
|
|
if ( checkmove )
|
|
{
|
|
origin -= GetLocalVelocity() * moveback;
|
|
}
|
|
if ( checkrot )
|
|
{
|
|
// BUGBUG: This is pretty hack-tastic!
|
|
angles -= GetLocalAngularVelocity() * moveback;
|
|
}
|
|
|
|
SetLocalOrigin( origin );
|
|
SetLocalAngles( angles );
|
|
}
|
|
|
|
if ( pBlocked )
|
|
{
|
|
Blocked( pBlocked );
|
|
}
|
|
m_flLocalTime = physLocalTime;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// this data is no longer useful, free the memory
|
|
if ( HasDataObjectType(PHYSICSPUSHLIST) )
|
|
{
|
|
DestroyDataObject( PHYSICSPUSHLIST );
|
|
}
|
|
|
|
m_flVPhysicsUpdateLocalTime = m_flLocalTime;
|
|
if ( m_flMoveDoneTime <= m_flLocalTime && m_flMoveDoneTime > 0 )
|
|
{
|
|
SetMoveDoneTime( -1 );
|
|
MoveDone();
|
|
}
|
|
}
|
|
|
|
|
|
void CBaseEntity::SetMoveDoneTime( float flDelay )
|
|
{
|
|
if (flDelay >= 0)
|
|
{
|
|
m_flMoveDoneTime = GetLocalTime() + flDelay;
|
|
}
|
|
else
|
|
{
|
|
m_flMoveDoneTime = -1;
|
|
}
|
|
CheckHasGamePhysicsSimulation();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Relinks all of a parents children into the collision tree
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::PhysicsRelinkChildren( float dt )
|
|
{
|
|
CBaseEntity *child;
|
|
|
|
// iterate through all children
|
|
for ( child = FirstMoveChild(); child != NULL; child = child->NextMovePeer() )
|
|
{
|
|
if ( child->IsSolid() || child->IsSolidFlagSet(FSOLID_TRIGGER) )
|
|
{
|
|
child->PhysicsTouchTriggers();
|
|
}
|
|
|
|
//
|
|
// Update their physics shadows. We should never have any children of
|
|
// movetype VPHYSICS.
|
|
//
|
|
if ( child->GetMoveType() != MOVETYPE_VPHYSICS )
|
|
{
|
|
child->UpdatePhysicsShadowToCurrentPosition( dt );
|
|
}
|
|
else if ( child->GetOwnerEntity() != this )
|
|
{
|
|
// the only case where this is valid is if this entity is an attached ragdoll.
|
|
// So assert here to catch the non-ragdoll case.
|
|
Assert( 0 );
|
|
}
|
|
|
|
if ( child->FirstMoveChild() )
|
|
{
|
|
child->PhysicsRelinkChildren(dt);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::PhysicsTouchTriggers( const Vector *pPrevAbsOrigin )
|
|
{
|
|
edict_t *pEdict = edict();
|
|
if ( pEdict && !IsWorld() )
|
|
{
|
|
Assert(CollisionProp());
|
|
bool isTriggerCheckSolids = IsSolidFlagSet( FSOLID_TRIGGER );
|
|
bool isSolidCheckTriggers = IsSolid() && !isTriggerCheckSolids; // NOTE: Moving triggers (items, ammo etc) are not
|
|
// checked against other triggers to reduce the number of touchlinks created
|
|
if ( !(isSolidCheckTriggers || isTriggerCheckSolids) )
|
|
return;
|
|
|
|
if ( GetSolid() == SOLID_BSP )
|
|
{
|
|
if ( !GetModel() && Q_strlen( STRING( GetModelName() ) ) == 0 )
|
|
{
|
|
Warning( "Inserted %s with no model\n", GetClassname() );
|
|
return;
|
|
}
|
|
}
|
|
|
|
SetCheckUntouch( true );
|
|
if ( isSolidCheckTriggers )
|
|
{
|
|
engine->SolidMoved( pEdict, CollisionProp(), pPrevAbsOrigin, sm_bAccurateTriggerBboxChecks );
|
|
}
|
|
if ( isTriggerCheckSolids )
|
|
{
|
|
engine->TriggerMoved( pEdict, sm_bAccurateTriggerBboxChecks );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::VPhysicsShadowCollision( int index, gamevcollisionevent_t *pEvent )
|
|
{
|
|
}
|
|
|
|
|
|
|
|
void CBaseEntity::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
|
|
{
|
|
// filter out ragdoll props hitting other parts of itself too often
|
|
// UNDONE: Store a sound time for this entity (not just this pair of objects)
|
|
// and filter repeats on that?
|
|
int otherIndex = !index;
|
|
CBaseEntity *pHitEntity = pEvent->pEntities[otherIndex];
|
|
|
|
#ifdef MAPBASE_VSCRIPT
|
|
if (m_ScriptScope.IsInitialized() && g_Hook_VPhysicsCollision.CanRunInScope(m_ScriptScope))
|
|
{
|
|
Vector vecContactPoint;
|
|
pEvent->pInternalData->GetContactPoint( vecContactPoint );
|
|
|
|
Vector vecSurfaceNormal;
|
|
pEvent->pInternalData->GetSurfaceNormal( vecSurfaceNormal );
|
|
|
|
// entity, speed, point, normal
|
|
ScriptVariant_t args[] = { ScriptVariant_t( pHitEntity->GetScriptInstance() ), pEvent->collisionSpeed, vecContactPoint, vecSurfaceNormal };
|
|
g_Hook_VPhysicsCollision.Call( m_ScriptScope, NULL, args );
|
|
}
|
|
#endif
|
|
|
|
// Don't make sounds / effects if neither entity is MOVETYPE_VPHYSICS. The game
|
|
// physics should have done so.
|
|
if ( GetMoveType() != MOVETYPE_VPHYSICS && pHitEntity->GetMoveType() != MOVETYPE_VPHYSICS )
|
|
return;
|
|
|
|
if ( pEvent->deltaCollisionTime < 0.5 && (pHitEntity == this) )
|
|
return;
|
|
|
|
// don't make noise for hidden/invisible/sky materials
|
|
surfacedata_t *phit = physprops->GetSurfaceData( pEvent->surfaceProps[otherIndex] );
|
|
const surfacedata_t *pprops = physprops->GetSurfaceData( pEvent->surfaceProps[index] );
|
|
if ( phit->game.material == 'X' || pprops->game.material == 'X' )
|
|
return;
|
|
|
|
if ( pHitEntity == this )
|
|
{
|
|
PhysCollisionSound( this, pEvent->pObjects[index], CHAN_BODY, pEvent->surfaceProps[index], pEvent->surfaceProps[otherIndex], pEvent->deltaCollisionTime, pEvent->collisionSpeed );
|
|
}
|
|
else
|
|
{
|
|
PhysCollisionSound( this, pEvent->pObjects[index], CHAN_STATIC, pEvent->surfaceProps[index], pEvent->surfaceProps[otherIndex], pEvent->deltaCollisionTime, pEvent->collisionSpeed );
|
|
}
|
|
PhysCollisionScreenShake( pEvent, index );
|
|
|
|
#if HL2_EPISODIC
|
|
// episodic does something different for when advisor shields are struck
|
|
if ( phit->game.material == 'Z' || pprops->game.material == 'Z')
|
|
{
|
|
PhysCollisionWarpEffect( pEvent, phit );
|
|
}
|
|
else
|
|
{
|
|
PhysCollisionDust( pEvent, phit );
|
|
}
|
|
#else
|
|
PhysCollisionDust( pEvent, phit );
|
|
#endif
|
|
}
|
|
|
|
void CBaseEntity::VPhysicsFriction( IPhysicsObject *pObject, float energy, int surfaceProps, int surfacePropsHit )
|
|
{
|
|
PhysFrictionSound( this, pObject, energy, surfaceProps, surfacePropsHit );
|
|
}
|
|
|
|
|
|
void CBaseEntity::VPhysicsSwapObject( IPhysicsObject *pSwap )
|
|
{
|
|
if ( !pSwap )
|
|
{
|
|
PhysRemoveShadow(this);
|
|
}
|
|
|
|
if ( !m_pPhysicsObject )
|
|
{
|
|
Warning( "Bad vphysics swap for %s\n", STRING(m_iClassname) );
|
|
}
|
|
m_pPhysicsObject = pSwap;
|
|
}
|
|
|
|
|
|
// Tells the physics shadow to update it's target to the current position
|
|
void CBaseEntity::UpdatePhysicsShadowToCurrentPosition( float deltaTime )
|
|
{
|
|
if ( GetMoveType() != MOVETYPE_VPHYSICS )
|
|
{
|
|
IPhysicsObject *pPhys = VPhysicsGetObject();
|
|
if ( pPhys )
|
|
{
|
|
pPhys->UpdateShadow( GetAbsOrigin(), GetAbsAngles(), false, deltaTime );
|
|
}
|
|
}
|
|
}
|
|
|
|
int CBaseEntity::VPhysicsGetObjectList( IPhysicsObject **pList, int listMax )
|
|
{
|
|
IPhysicsObject *pPhys = VPhysicsGetObject();
|
|
if ( pPhys )
|
|
{
|
|
// multi-object entities must implement this function
|
|
Assert( !(pPhys->GetGameFlags() & FVPHYSICS_MULTIOBJECT_ENTITY) );
|
|
if ( listMax > 0 )
|
|
{
|
|
pList[0] = pPhys;
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::VPhysicsIsFlesh( void )
|
|
{
|
|
IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
|
|
int count = VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
int material = pList[i]->GetMaterialIndex();
|
|
const surfacedata_t *pSurfaceData = physprops->GetSurfaceData( material );
|
|
// Is flesh ?, don't allow pickup
|
|
if ( pSurfaceData->game.material == CHAR_TEX_ANTLION || pSurfaceData->game.material == CHAR_TEX_FLESH || pSurfaceData->game.material == CHAR_TEX_BLOODYFLESH || pSurfaceData->game.material == CHAR_TEX_ALIENFLESH )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CBaseEntity::Intersects( CBaseEntity *pOther )
|
|
{
|
|
if ( !edict() || !pOther->edict() )
|
|
return false;
|
|
|
|
CCollisionProperty *pMyProp = CollisionProp();
|
|
CCollisionProperty *pOtherProp = pOther->CollisionProp();
|
|
|
|
return IsOBBIntersectingOBB(
|
|
pMyProp->GetCollisionOrigin(), pMyProp->GetCollisionAngles(), pMyProp->OBBMins(), pMyProp->OBBMaxs(),
|
|
pOtherProp->GetCollisionOrigin(), pOtherProp->GetCollisionAngles(), pOtherProp->OBBMins(), pOtherProp->OBBMaxs() );
|
|
}
|
|
|
|
extern ConVar ai_LOS_mode;
|
|
|
|
//=========================================================
|
|
// FVisible - returns true if a line can be traced from
|
|
// the caller's eyes to the target
|
|
//=========================================================
|
|
bool CBaseEntity::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
|
|
{
|
|
VPROF( "CBaseEntity::FVisible" );
|
|
|
|
if ( pEntity->GetFlags() & FL_NOTARGET )
|
|
return false;
|
|
|
|
#if HL1_DLL
|
|
// FIXME: only block LOS through opaque water
|
|
// don't look through water
|
|
if ((m_nWaterLevel != 3 && pEntity->m_nWaterLevel == 3)
|
|
|| (m_nWaterLevel == 3 && pEntity->m_nWaterLevel == 0))
|
|
return false;
|
|
#endif
|
|
|
|
Vector vecLookerOrigin = EyePosition();//look through the caller's 'eyes'
|
|
Vector vecTargetOrigin = pEntity->EyePosition();
|
|
|
|
trace_t tr;
|
|
if ( !IsXbox() && ai_LOS_mode.GetBool() )
|
|
{
|
|
UTIL_TraceLine(vecLookerOrigin, vecTargetOrigin, traceMask, this, COLLISION_GROUP_NONE, &tr);
|
|
}
|
|
else
|
|
{
|
|
// If we're doing an LOS search, include NPCs.
|
|
if ( traceMask == MASK_BLOCKLOS )
|
|
{
|
|
traceMask = MASK_BLOCKLOS_AND_NPCS;
|
|
}
|
|
|
|
// Player sees through nodraw
|
|
if ( IsPlayer() )
|
|
{
|
|
traceMask &= ~CONTENTS_BLOCKLOS;
|
|
}
|
|
|
|
// Use the custom LOS trace filter
|
|
CTraceFilterLOS traceFilter( this, COLLISION_GROUP_NONE, pEntity );
|
|
UTIL_TraceLine( vecLookerOrigin, vecTargetOrigin, traceMask, &traceFilter, &tr );
|
|
}
|
|
|
|
if (tr.fraction != 1.0 || tr.startsolid )
|
|
{
|
|
// If we hit the entity we're looking for, it's visible
|
|
if ( tr.m_pEnt == pEntity )
|
|
return true;
|
|
|
|
// Got line of sight on the vehicle the player is driving!
|
|
if ( pEntity && pEntity->IsPlayer() )
|
|
{
|
|
CBasePlayer *pPlayer = assert_cast<CBasePlayer*>( pEntity );
|
|
if ( tr.m_pEnt == pPlayer->GetVehicleEntity() )
|
|
return true;
|
|
}
|
|
|
|
if (ppBlocker)
|
|
{
|
|
*ppBlocker = tr.m_pEnt;
|
|
}
|
|
|
|
return false;// Line of sight is not established
|
|
}
|
|
|
|
return true;// line of sight is valid.
|
|
}
|
|
|
|
//=========================================================
|
|
// FVisible - returns true if a line can be traced from
|
|
// the caller's eyes to the wished position.
|
|
//=========================================================
|
|
bool CBaseEntity::FVisible( const Vector &vecTarget, int traceMask, CBaseEntity **ppBlocker )
|
|
{
|
|
#if HL1_DLL
|
|
|
|
// don't look through water
|
|
// FIXME: only block LOS through opaque water
|
|
bool inWater = ( UTIL_PointContents( vecTarget ) & (CONTENTS_SLIME|CONTENTS_WATER) ) ? true : false;
|
|
|
|
// Don't allow it if we're straddling two areas
|
|
if ( ( m_nWaterLevel == 3 && !inWater ) || ( m_nWaterLevel != 3 && inWater ) )
|
|
return false;
|
|
|
|
#endif
|
|
|
|
trace_t tr;
|
|
Vector vecLookerOrigin = EyePosition();// look through the caller's 'eyes'
|
|
|
|
if ( ai_LOS_mode.GetBool() )
|
|
{
|
|
UTIL_TraceLine( vecLookerOrigin, vecTarget, traceMask, this, COLLISION_GROUP_NONE, &tr);
|
|
}
|
|
else
|
|
{
|
|
// If we're doing an LOS search, include NPCs.
|
|
if ( traceMask == MASK_BLOCKLOS )
|
|
{
|
|
traceMask = MASK_BLOCKLOS_AND_NPCS;
|
|
}
|
|
|
|
// Player sees through nodraw and blocklos
|
|
if ( IsPlayer() )
|
|
{
|
|
traceMask |= CONTENTS_IGNORE_NODRAW_OPAQUE;
|
|
traceMask &= ~CONTENTS_BLOCKLOS;
|
|
}
|
|
|
|
// Use the custom LOS trace filter
|
|
CTraceFilterLOS traceFilter( this, COLLISION_GROUP_NONE );
|
|
UTIL_TraceLine( vecLookerOrigin, vecTarget, traceMask, &traceFilter, &tr );
|
|
}
|
|
|
|
if (tr.fraction != 1.0)
|
|
{
|
|
if (ppBlocker)
|
|
{
|
|
*ppBlocker = tr.m_pEnt;
|
|
}
|
|
return false;// Line of sight is not established
|
|
}
|
|
|
|
return true;// line of sight is valid.
|
|
}
|
|
|
|
extern ConVar ai_debug_los;
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Turn on prop LOS debugging mode
|
|
//-----------------------------------------------------------------------------
|
|
void CC_AI_LOS_Debug( IConVar *var, const char *pOldString, float flOldValue )
|
|
{
|
|
int iLOSMode = ai_debug_los.GetInt();
|
|
for ( CBaseEntity *pEntity = gEntList.FirstEnt(); pEntity != NULL; pEntity = gEntList.NextEnt(pEntity) )
|
|
{
|
|
if ( iLOSMode == 1 && pEntity->IsSolid() )
|
|
{
|
|
pEntity->m_debugOverlays |= OVERLAY_SHOW_BLOCKSLOS;
|
|
}
|
|
else if ( iLOSMode == 2 )
|
|
{
|
|
pEntity->m_debugOverlays |= OVERLAY_SHOW_BLOCKSLOS;
|
|
}
|
|
else
|
|
{
|
|
pEntity->m_debugOverlays &= ~OVERLAY_SHOW_BLOCKSLOS;
|
|
}
|
|
}
|
|
}
|
|
ConVar ai_debug_los("ai_debug_los", "0", FCVAR_CHEAT, "NPC Line-Of-Sight debug mode. If 1, solid entities that block NPC LOC will be highlighted with white bounding boxes. If 2, it'll show non-solid entities that would do it if they were solid.", CC_AI_LOS_Debug );
|
|
|
|
|
|
Class_T CBaseEntity::Classify ( void )
|
|
{
|
|
return CLASS_NONE;
|
|
}
|
|
|
|
float CBaseEntity::GetAutoAimRadius()
|
|
{
|
|
if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE )
|
|
return 48.0f;
|
|
else
|
|
return 24.0f;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Changes the shadow cast distance over time
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::ShadowCastDistThink( )
|
|
{
|
|
SetShadowCastDistance( m_flDesiredShadowCastDistance );
|
|
SetContextThink( NULL, gpGlobals->curtime, "ShadowCastDistThink" );
|
|
}
|
|
|
|
void CBaseEntity::SetShadowCastDistance( float flDesiredDistance, float flDelay )
|
|
{
|
|
m_flDesiredShadowCastDistance = flDesiredDistance;
|
|
if ( m_flDesiredShadowCastDistance != m_flShadowCastDistance )
|
|
{
|
|
SetContextThink( &CBaseEntity::ShadowCastDistThink, gpGlobals->curtime + flDelay, "ShadowCastDistThink" );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
TraceAttack
|
|
================
|
|
*/
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns whether a damage info can damage this entity.
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::PassesDamageFilter( const CTakeDamageInfo &info )
|
|
{
|
|
if (m_hDamageFilter)
|
|
{
|
|
CBaseFilter *pFilter = (CBaseFilter *)(m_hDamageFilter.Get());
|
|
#ifdef MAPBASE
|
|
return pFilter->PassesDamageFilter(this, info);
|
|
#else
|
|
return pFilter->PassesDamageFilter(info);
|
|
#endif
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifdef MAPBASE
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: A damage filter pass for when this is most certainly the part where we might actually take damage.
|
|
// Made for the "damage" family of filters, including filter_damage_transfer.
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::PassesFinalDamageFilter( const CTakeDamageInfo &info )
|
|
{
|
|
if (!PassesDamageFilter(info))
|
|
return false;
|
|
|
|
if (m_hDamageFilter)
|
|
{
|
|
CBaseFilter *pFilter = (CBaseFilter *)(m_hDamageFilter.Get());
|
|
if (!pFilter->PassesFinalDamageFilter(this, info))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: A hack for damage transfers.
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::DamageFilterAllowsBlood( const CTakeDamageInfo &info )
|
|
{
|
|
if (m_hDamageFilter)
|
|
{
|
|
CBaseFilter *pFilter = (CBaseFilter *)(m_hDamageFilter.Get());
|
|
if (!pFilter->BloodAllowed(this, info))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Modifies damage taken. Returns true if damage was successfully modded.
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::DamageFilterDamageMod( CTakeDamageInfo &info )
|
|
{
|
|
if (m_hDamageFilter)
|
|
{
|
|
CBaseFilter *pFilter = (CBaseFilter *)(m_hDamageFilter.Get());
|
|
if (pFilter->DamageMod(this, info))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
FORCEINLINE bool NamesMatch( const char *pszQuery, string_t nameToMatch )
|
|
{
|
|
#ifdef MAPBASE
|
|
// NamesMatch has been turned into Matcher_NamesMatch in matchers.h
|
|
// for a wider range of accessibility and flexibility.
|
|
return Matcher_NamesMatch(pszQuery, STRING(nameToMatch));
|
|
#else
|
|
if ( nameToMatch == NULL_STRING )
|
|
return (!pszQuery || *pszQuery == 0 || *pszQuery == '*');
|
|
|
|
const char *pszNameToMatch = STRING(nameToMatch);
|
|
|
|
// If the pointers are identical, we're identical
|
|
if ( pszNameToMatch == pszQuery )
|
|
return true;
|
|
|
|
while ( *pszNameToMatch && *pszQuery )
|
|
{
|
|
unsigned char cName = *pszNameToMatch;
|
|
unsigned char cQuery = *pszQuery;
|
|
// simple ascii case conversion
|
|
if ( cName == cQuery )
|
|
;
|
|
else if ( cName - 'A' <= (unsigned char)'Z' - 'A' && cName - 'A' + 'a' == cQuery )
|
|
;
|
|
else if ( cName - 'a' <= (unsigned char)'z' - 'a' && cName - 'a' + 'A' == cQuery )
|
|
;
|
|
else
|
|
break;
|
|
++pszNameToMatch;
|
|
++pszQuery;
|
|
}
|
|
|
|
if ( *pszQuery == 0 && *pszNameToMatch == 0 )
|
|
return true;
|
|
|
|
// @TODO (toml 03-18-03): Perhaps support real wildcards. Right now, only thing supported is trailing *
|
|
if ( *pszQuery == '*' )
|
|
return true;
|
|
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool CBaseEntity::NameMatchesComplex( const char *pszNameOrWildcard )
|
|
{
|
|
if ( !Q_stricmp( "!player", pszNameOrWildcard) )
|
|
return IsPlayer();
|
|
|
|
return NamesMatch( pszNameOrWildcard, m_iName );
|
|
}
|
|
|
|
bool CBaseEntity::ClassMatchesComplex( const char *pszClassOrWildcard )
|
|
{
|
|
return NamesMatch( pszClassOrWildcard, m_iClassname );
|
|
}
|
|
|
|
void CBaseEntity::MakeDormant( void )
|
|
{
|
|
AddEFlags( EFL_DORMANT );
|
|
|
|
// disable thinking for dormant entities
|
|
SetThink( NULL );
|
|
|
|
if ( !edict() )
|
|
return;
|
|
|
|
SETBITS( m_iEFlags, EFL_DORMANT );
|
|
|
|
// Don't touch
|
|
AddSolidFlags( FSOLID_NOT_SOLID );
|
|
// Don't move
|
|
SetMoveType( MOVETYPE_NONE );
|
|
// Don't draw
|
|
AddEffects( EF_NODRAW );
|
|
// Don't think
|
|
SetNextThink( TICK_NEVER_THINK );
|
|
}
|
|
|
|
int CBaseEntity::IsDormant( void )
|
|
{
|
|
return IsEFlagSet( EFL_DORMANT );
|
|
}
|
|
|
|
|
|
bool CBaseEntity::IsInWorld( void ) const
|
|
{
|
|
if ( !edict() )
|
|
return true;
|
|
|
|
// position
|
|
if (GetAbsOrigin().x >= MAX_COORD_INTEGER) return false;
|
|
if (GetAbsOrigin().y >= MAX_COORD_INTEGER) return false;
|
|
if (GetAbsOrigin().z >= MAX_COORD_INTEGER) return false;
|
|
if (GetAbsOrigin().x <= MIN_COORD_INTEGER) return false;
|
|
if (GetAbsOrigin().y <= MIN_COORD_INTEGER) return false;
|
|
if (GetAbsOrigin().z <= MIN_COORD_INTEGER) return false;
|
|
// speed
|
|
if (GetAbsVelocity().x >= 2000) return false;
|
|
if (GetAbsVelocity().y >= 2000) return false;
|
|
if (GetAbsVelocity().z >= 2000) return false;
|
|
if (GetAbsVelocity().x <= -2000) return false;
|
|
if (GetAbsVelocity().y <= -2000) return false;
|
|
if (GetAbsVelocity().z <= -2000) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool CBaseEntity::IsViewable( void )
|
|
{
|
|
if ( IsEffectActive( EF_NODRAW ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (IsBSPModel())
|
|
{
|
|
if (GetMoveType() != MOVETYPE_NONE)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else if (GetModelIndex() != 0)
|
|
{
|
|
// check for total transparency???
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
int CBaseEntity::ShouldToggle( USE_TYPE useType, int currentState )
|
|
{
|
|
if ( useType != USE_TOGGLE && useType != USE_SET )
|
|
{
|
|
if ( (currentState && useType == USE_ON) || (!currentState && useType == USE_OFF) )
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
// NOTE: szName must be a pointer to constant memory, e.g. "NPC_class" because the entity
|
|
// will keep a pointer to it after this call.
|
|
CBaseEntity *CBaseEntity::Create( const char *szName, const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity *pOwner )
|
|
{
|
|
CBaseEntity *pEntity = CreateNoSpawn( szName, vecOrigin, vecAngles, pOwner );
|
|
|
|
DispatchSpawn( pEntity );
|
|
return pEntity;
|
|
}
|
|
|
|
|
|
|
|
// NOTE: szName must be a pointer to constant memory, e.g. "NPC_class" because the entity
|
|
// will keep a pointer to it after this call.
|
|
CBaseEntity * CBaseEntity::CreateNoSpawn( const char *szName, const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity *pOwner )
|
|
{
|
|
CBaseEntity *pEntity = CreateEntityByName( szName );
|
|
if ( !pEntity )
|
|
{
|
|
Assert( !"CreateNoSpawn: only works for CBaseEntities" );
|
|
return NULL;
|
|
}
|
|
|
|
pEntity->SetLocalOrigin( vecOrigin );
|
|
pEntity->SetLocalAngles( vecAngles );
|
|
pEntity->SetOwnerEntity( pOwner );
|
|
|
|
gEntList.NotifyCreateEntity( pEntity );
|
|
|
|
return pEntity;
|
|
}
|
|
|
|
Vector CBaseEntity::GetSoundEmissionOrigin() const
|
|
{
|
|
return WorldSpaceCenter();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Saves the current object out to disk, by iterating through the objects
|
|
// data description hierarchy
|
|
// Input : &save - save buffer which the class data is written to
|
|
// Output : int - 0 if the save failed, 1 on success
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::Save( ISave &save )
|
|
{
|
|
// loop through the data description list, saving each data desc block
|
|
int status = SaveDataDescBlock( save, GetDataDescMap() );
|
|
|
|
return status;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Recursively saves all the classes in an object, in reverse order (top down)
|
|
// Output : int 0 on failure, 1 on success
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::SaveDataDescBlock( ISave &save, datamap_t *dmap )
|
|
{
|
|
return save.WriteAll( this, dmap );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Restores the current object from disk, by iterating through the objects
|
|
// data description hierarchy
|
|
// Input : &restore - restore buffer which the class data is read from
|
|
// Output : int - 0 if the restore failed, 1 on success
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::Restore( IRestore &restore )
|
|
{
|
|
// This is essential to getting the spatial partition info correct
|
|
CollisionProp()->DestroyPartitionHandle();
|
|
|
|
// loops through the data description list, restoring each data desc block in order
|
|
int status = RestoreDataDescBlock( restore, GetDataDescMap() );
|
|
|
|
// ---------------------------------------------------------------
|
|
// HACKHACK: We don't know the space of these vectors until now
|
|
// if they are worldspace, fix them up.
|
|
// ---------------------------------------------------------------
|
|
{
|
|
CGameSaveRestoreInfo *pGameInfo = restore.GetGameSaveRestoreInfo();
|
|
Vector parentSpaceOffset = pGameInfo->modelSpaceOffset;
|
|
if ( !GetParent() )
|
|
{
|
|
// parent is the world, so parent space is worldspace
|
|
// so update with the worldspace leveltransition transform
|
|
parentSpaceOffset += pGameInfo->GetLandmark();
|
|
}
|
|
|
|
// NOTE: Do *not* use GetAbsOrigin() here because it will
|
|
// try to recompute m_rgflCoordinateFrame!
|
|
MatrixSetColumn( m_vecAbsOrigin, 3, m_rgflCoordinateFrame );
|
|
|
|
m_vecOrigin += parentSpaceOffset;
|
|
}
|
|
|
|
// Gotta do this after the coordframe is set up as it depends on it.
|
|
|
|
// By definition, the surrounding bounds are dirty
|
|
// Also, twiddling with the flags here ensures it gets added to the KD tree dirty list
|
|
// (We don't want to use the saved version of this flag)
|
|
RemoveEFlags( EFL_DIRTY_SPATIAL_PARTITION );
|
|
CollisionProp()->MarkSurroundingBoundsDirty();
|
|
|
|
if ( edict() && GetModelIndex() != 0 && GetModelName() != NULL_STRING && restore.GetPrecacheMode() )
|
|
{
|
|
PrecacheModel( STRING( GetModelName() ) );
|
|
|
|
//Adrian: We should only need to do this after we precache. No point in setting the model again.
|
|
SetModelIndex( modelinfo->GetModelIndex( STRING(GetModelName() ) ) );
|
|
}
|
|
|
|
// Restablish ground entity
|
|
if ( m_hGroundEntity != NULL )
|
|
{
|
|
m_hGroundEntity->AddEntityToGroundList( this );
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// handler to do stuff before you are saved
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::OnSave( IEntitySaveUtils *pUtils )
|
|
{
|
|
// Here, we must force recomputation of all abs data so it gets saved correctly
|
|
// We can't leave the dirty bits set because the loader can't cope with it.
|
|
CalcAbsolutePosition();
|
|
CalcAbsoluteVelocity();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// handler to do stuff after you are restored
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::OnRestore()
|
|
{
|
|
#ifndef MAPBASE // It's your fault if you're trying to load old, broken saves from a possibly closed 2013 beta in Mapbase.
|
|
#if defined( PORTAL ) || defined( HL2_EPISODIC ) || defined ( HL2_DLL ) || defined( HL2_LOSTCOAST )
|
|
// We had a short period during the 2013 beta where the FL_* flags had a bogus value near the top, so detect
|
|
// these bad saves and just give up. Only saves from the short beta period should have been effected.
|
|
if ( GetFlags() & FL_FAKECLIENT )
|
|
{
|
|
char szMsg[256];
|
|
V_snprintf( szMsg, sizeof(szMsg), "\nInvalid save, unable to load. Please run \"map %s\" to restart this level manually\n\n", gpGlobals->mapname.ToCStr() );
|
|
Msg( "%s", szMsg );
|
|
|
|
engine->ServerCommand("wait;wait;disconnect;showconsole\n");
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
SimThink_EntityChanged( this );
|
|
|
|
// touchlinks get recomputed
|
|
if ( IsEFlagSet( EFL_CHECK_UNTOUCH ) )
|
|
{
|
|
RemoveEFlags( EFL_CHECK_UNTOUCH );
|
|
SetCheckUntouch( true );
|
|
}
|
|
|
|
// disable touch functions while we recreate the touch links between entities
|
|
// NOTE: We don't do this on transitions, because we'd miss the OnStartTouch call!
|
|
#if !defined(HL2_DLL) || ( defined(HL2_DLL) && defined(HL2_EPISODIC) )
|
|
CBaseEntity::sm_bDisableTouchFuncs = ( gpGlobals->eLoadType != MapLoad_Transition );
|
|
PhysicsTouchTriggers();
|
|
CBaseEntity::sm_bDisableTouchFuncs = false;
|
|
#endif // HL2_EPISODIC
|
|
|
|
//Adrian: If I'm restoring with these fields it means I've become a client side ragdoll.
|
|
//Don't create another one, just wait until is my time of being removed.
|
|
if ( GetFlags() & FL_TRANSRAGDOLL )
|
|
{
|
|
m_nRenderFX = kRenderFxNone;
|
|
AddEffects( EF_NODRAW );
|
|
RemoveFlag( FL_DISSOLVING | FL_ONFIRE );
|
|
}
|
|
|
|
if ( m_pParent )
|
|
{
|
|
CBaseEntity *pChild = m_pParent->FirstMoveChild();
|
|
while ( pChild )
|
|
{
|
|
if ( pChild == this )
|
|
break;
|
|
pChild = pChild->NextMovePeer();
|
|
}
|
|
if ( pChild != this )
|
|
{
|
|
#if _DEBUG
|
|
// generally this means you've got something marked FCAP_DONT_SAVE
|
|
// in a hierarchy. That's probably ok given this fixup, but the hierarhcy
|
|
// linked list is just saved/loaded in-place
|
|
Warning("Fixing up parent on %s\n", GetClassname() );
|
|
#endif
|
|
// We only need to be back in the parent's list because we're already in the right place and with the right data
|
|
LinkChild( m_pParent, this );
|
|
}
|
|
}
|
|
|
|
// We're not save/loading the PVS dirty state. Assume everything is dirty after a restore
|
|
NetworkProp()->MarkPVSInformationDirty();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Recursively restores all the classes in an object, in reverse order (top down)
|
|
// Output : int 0 on failure, 1 on success
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::RestoreDataDescBlock( IRestore &restore, datamap_t *dmap )
|
|
{
|
|
return restore.ReadAll( this, dmap );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool CBaseEntity::ShouldSavePhysics()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "tier0/memdbgoff.h"
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// CBaseEntity new/delete
|
|
// allocates and frees memory for itself from the engine->
|
|
// All fields in the object are all initialized to 0.
|
|
//-----------------------------------------------------------------------------
|
|
void *CBaseEntity::operator new( size_t stAllocateBlock )
|
|
{
|
|
// call into engine to get memory
|
|
Assert( stAllocateBlock != 0 );
|
|
return engine->PvAllocEntPrivateData(stAllocateBlock);
|
|
};
|
|
|
|
void *CBaseEntity::operator new( size_t stAllocateBlock, int nBlockUse, const char *pFileName, int nLine )
|
|
{
|
|
// call into engine to get memory
|
|
Assert( stAllocateBlock != 0 );
|
|
return engine->PvAllocEntPrivateData(stAllocateBlock);
|
|
}
|
|
|
|
void CBaseEntity::operator delete( void *pMem )
|
|
{
|
|
// get the engine to free the memory
|
|
engine->FreeEntPrivateData( pMem );
|
|
}
|
|
|
|
#include "tier0/memdbgon.h"
|
|
|
|
|
|
#ifdef _DEBUG
|
|
void CBaseEntity::FunctionCheck( void *pFunction, const char *name )
|
|
{
|
|
#ifdef USES_SAVERESTORE
|
|
// Note, if you crash here and your class is using multiple inheritance, it is
|
|
// probably the case that CBaseEntity (or a descendant) is not the first
|
|
// class in your list of ancestors, which it must be.
|
|
if (pFunction && !UTIL_FunctionToName( GetDataDescMap(), (inputfunc_t *)pFunction ) )
|
|
{
|
|
Warning( "FUNCTION NOT IN TABLE!: %s:%s (%08lx)\n", STRING(m_iClassname), name, (unsigned long)pFunction );
|
|
Assert(0);
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
|
|
bool CBaseEntity::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Perform hitbox test, returns true *if hitboxes were tested at all*!!
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
|
|
void CBaseEntity::SetOwnerEntity( CBaseEntity* pOwner )
|
|
{
|
|
if ( m_hOwnerEntity.Get() != pOwner )
|
|
{
|
|
m_hOwnerEntity = pOwner;
|
|
|
|
CollisionRulesChanged();
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::SetMoveType( MoveType_t val, MoveCollide_t moveCollide )
|
|
{
|
|
#ifdef _DEBUG
|
|
// Make sure the move type + move collide are compatible...
|
|
if ((val != MOVETYPE_FLY) && (val != MOVETYPE_FLYGRAVITY))
|
|
{
|
|
Assert( moveCollide == MOVECOLLIDE_DEFAULT );
|
|
}
|
|
|
|
if ( m_MoveType == MOVETYPE_VPHYSICS && val != m_MoveType )
|
|
{
|
|
if ( VPhysicsGetObject() && val != MOVETYPE_NONE )
|
|
{
|
|
// What am I supposed to do with the physics object if
|
|
// you're changing away from MOVETYPE_VPHYSICS without making the object
|
|
// shadow? This isn't likely to work, assert.
|
|
// You probably meant to call VPhysicsInitShadow() instead of VPhysicsInitNormal()!
|
|
Assert( VPhysicsGetObject()->GetShadowController() );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if ( m_MoveType == val )
|
|
{
|
|
m_MoveCollide = moveCollide;
|
|
return;
|
|
}
|
|
|
|
// This is needed to the removal of MOVETYPE_FOLLOW:
|
|
// We can't transition from follow to a different movetype directly
|
|
// or the leaf code will break.
|
|
Assert( !IsEffectActive( EF_BONEMERGE ) );
|
|
m_MoveType = val;
|
|
m_MoveCollide = moveCollide;
|
|
|
|
CollisionRulesChanged();
|
|
|
|
switch( m_MoveType )
|
|
{
|
|
case MOVETYPE_WALK:
|
|
{
|
|
SetSimulatedEveryTick( true );
|
|
SetAnimatedEveryTick( true );
|
|
}
|
|
break;
|
|
case MOVETYPE_STEP:
|
|
{
|
|
// This will probably go away once I remove the cvar that controls the test code
|
|
SetSimulatedEveryTick( g_bTestMoveTypeStepSimulation ? true : false );
|
|
SetAnimatedEveryTick( false );
|
|
}
|
|
break;
|
|
case MOVETYPE_FLY:
|
|
case MOVETYPE_FLYGRAVITY:
|
|
{
|
|
// Initialize our water state, because these movetypes care about transitions in/out of water
|
|
UpdateWaterState();
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
SetSimulatedEveryTick( true );
|
|
SetAnimatedEveryTick( false );
|
|
}
|
|
}
|
|
|
|
// This will probably go away or be handled in a better way once I remove the cvar that controls the test code
|
|
CheckStepSimulationChanged();
|
|
CheckHasGamePhysicsSimulation();
|
|
}
|
|
|
|
void CBaseEntity::Spawn( void )
|
|
{
|
|
}
|
|
|
|
|
|
CBaseEntity* CBaseEntity::Instance( const CBaseHandle &hEnt )
|
|
{
|
|
return gEntList.GetBaseEntity( hEnt );
|
|
}
|
|
|
|
int CBaseEntity::GetTransmitState( void )
|
|
{
|
|
edict_t *ed = edict();
|
|
|
|
if ( !ed )
|
|
return 0;
|
|
|
|
return ed->m_fStateFlags;
|
|
}
|
|
|
|
int CBaseEntity::SetTransmitState( int nFlag)
|
|
{
|
|
edict_t *ed = edict();
|
|
|
|
if ( !ed )
|
|
return 0;
|
|
|
|
// clear current flags = check ShouldTransmit()
|
|
ed->ClearTransmitState();
|
|
|
|
int oldFlags = ed->m_fStateFlags;
|
|
ed->m_fStateFlags |= nFlag;
|
|
|
|
// Tell the engine (used for a network backdoor optimization).
|
|
if ( (oldFlags & FL_EDICT_DONTSEND) != (ed->m_fStateFlags & FL_EDICT_DONTSEND) )
|
|
engine->NotifyEdictFlagsChange( entindex() );
|
|
|
|
return ed->m_fStateFlags;
|
|
}
|
|
|
|
int CBaseEntity::UpdateTransmitState()
|
|
{
|
|
// If you get this assert, you should be calling DispatchUpdateTransmitState
|
|
// instead of UpdateTransmitState.
|
|
Assert( g_nInsideDispatchUpdateTransmitState > 0 );
|
|
|
|
// If an object is the moveparent of something else, don't skip it just because it's marked EF_NODRAW or else
|
|
// the client won't have a proper origin for the child since the hierarchy won't be correctly transmitted down
|
|
if ( IsEffectActive( EF_NODRAW ) &&
|
|
!m_hMoveChild.Get() )
|
|
{
|
|
return SetTransmitState( FL_EDICT_DONTSEND );
|
|
}
|
|
|
|
if ( !IsEFlagSet( EFL_FORCE_CHECK_TRANSMIT ) )
|
|
{
|
|
if ( !GetModelIndex() || !GetModelName() )
|
|
{
|
|
return SetTransmitState( FL_EDICT_DONTSEND );
|
|
}
|
|
}
|
|
|
|
// Always send the world
|
|
if ( GetModelIndex() == 1 )
|
|
{
|
|
return SetTransmitState( FL_EDICT_ALWAYS );
|
|
}
|
|
|
|
if ( IsEFlagSet( EFL_IN_SKYBOX ) )
|
|
{
|
|
return SetTransmitState( FL_EDICT_ALWAYS );
|
|
}
|
|
|
|
// by default cull against PVS
|
|
return SetTransmitState( FL_EDICT_PVSCHECK );
|
|
}
|
|
|
|
int CBaseEntity::DispatchUpdateTransmitState()
|
|
{
|
|
edict_t *ed = edict();
|
|
if ( m_nTransmitStateOwnedCounter != 0 )
|
|
return ed ? ed->m_fStateFlags : 0;
|
|
|
|
g_nInsideDispatchUpdateTransmitState++;
|
|
int ret = UpdateTransmitState();
|
|
g_nInsideDispatchUpdateTransmitState--;
|
|
|
|
return ret;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Note, an entity can override the send table ( e.g., to send less data or to send minimal data for
|
|
// objects ( prob. players ) that are not in the pvs.
|
|
// Input : **ppSendTable -
|
|
// *recipient -
|
|
// *pvs -
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::ShouldTransmit( const CCheckTransmitInfo *pInfo )
|
|
{
|
|
int fFlags = DispatchUpdateTransmitState();
|
|
|
|
if ( fFlags & FL_EDICT_PVSCHECK )
|
|
{
|
|
return FL_EDICT_PVSCHECK;
|
|
}
|
|
else if ( fFlags & FL_EDICT_ALWAYS )
|
|
{
|
|
return FL_EDICT_ALWAYS;
|
|
}
|
|
else if ( fFlags & FL_EDICT_DONTSEND )
|
|
{
|
|
return FL_EDICT_DONTSEND;
|
|
}
|
|
|
|
// if ( IsToolRecording() )
|
|
// {
|
|
// return FL_EDICT_ALWAYS;
|
|
// }
|
|
|
|
CBaseEntity *pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt );
|
|
|
|
Assert( pRecipientEntity->IsPlayer() );
|
|
|
|
CBasePlayer *pRecipientPlayer = static_cast<CBasePlayer*>( pRecipientEntity );
|
|
|
|
|
|
// FIXME: Refactor once notion of "team" is moved into HL2 code
|
|
// Team rules may tell us that we should
|
|
if ( pRecipientPlayer->GetTeam() )
|
|
{
|
|
if ( pRecipientPlayer->GetTeam()->ShouldTransmitToPlayer( pRecipientPlayer, this ))
|
|
return FL_EDICT_ALWAYS;
|
|
}
|
|
|
|
|
|
/*#ifdef INVASION_DLL
|
|
// Check test network vis distance stuff. Eventually network LOD will do this.
|
|
float flTestDistSqr = pRecipientEntity->GetAbsOrigin().DistToSqr( WorldSpaceCenter() );
|
|
if ( flTestDistSqr > sv_netvisdist.GetFloat() * sv_netvisdist.GetFloat() )
|
|
return TRANSMIT_NO; // TODO doesn't work with HLTV
|
|
#endif*/
|
|
|
|
// by default do a PVS check
|
|
|
|
return FL_EDICT_PVSCHECK;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Rules about which entities need to transmit along with me
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways )
|
|
{
|
|
int index = entindex();
|
|
|
|
// Are we already marked for transmission?
|
|
if ( pInfo->m_pTransmitEdict->Get( index ) )
|
|
return;
|
|
|
|
CServerNetworkProperty *pNetworkParent = NetworkProp()->GetNetworkParent();
|
|
|
|
pInfo->m_pTransmitEdict->Set( index );
|
|
|
|
// HLTV/Replay need to know if this entity is culled by PVS limits
|
|
if ( pInfo->m_pTransmitAlways )
|
|
{
|
|
// in HLTV/Replay mode always transmit entitys with move-parents
|
|
// HLTV/Replay can't resolve the mode-parents relationships
|
|
if ( bAlways || pNetworkParent )
|
|
{
|
|
// tell HLTV/Replay that this entity is always transmitted
|
|
pInfo->m_pTransmitAlways->Set( index );
|
|
}
|
|
else
|
|
{
|
|
// HLTV/Replay will PVS cull this entity, so update the
|
|
// node/cluster infos if necessary
|
|
m_Network.RecomputePVSInformation();
|
|
}
|
|
}
|
|
|
|
// Force our aiment and move parent to be sent.
|
|
if ( pNetworkParent )
|
|
{
|
|
CBaseEntity *pMoveParent = pNetworkParent->GetBaseEntity();
|
|
pMoveParent->SetTransmit( pInfo, bAlways );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Returns which skybox the entity is in
|
|
//-----------------------------------------------------------------------------
|
|
CSkyCamera *CBaseEntity::GetEntitySkybox()
|
|
{
|
|
int area = engine->GetArea( WorldSpaceCenter() );
|
|
|
|
CSkyCamera *pCur = GetSkyCameraList();
|
|
while ( pCur )
|
|
{
|
|
if ( engine->CheckAreasConnected( area, pCur->m_skyboxData.area ) )
|
|
return pCur;
|
|
|
|
pCur = pCur->m_pNext;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
bool CBaseEntity::DetectInSkybox()
|
|
{
|
|
if ( GetEntitySkybox() != NULL )
|
|
{
|
|
AddEFlags( EFL_IN_SKYBOX );
|
|
return true;
|
|
}
|
|
|
|
RemoveEFlags( EFL_IN_SKYBOX );
|
|
return false;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Computes a world-aligned bounding box that surrounds everything in the entity
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::ComputeWorldSpaceSurroundingBox( Vector *pMins, Vector *pMaxs )
|
|
{
|
|
// Should never get here.. only use USE_GAME_CODE with bounding boxes
|
|
// if you have an implementation for this method
|
|
Assert( 0 );
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : If name exists returns name, otherwise returns classname
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
const char *CBaseEntity::GetDebugName(void)
|
|
{
|
|
if ( this == NULL )
|
|
return "<<null>>";
|
|
|
|
if ( m_iName.Get() != NULL_STRING )
|
|
{
|
|
return STRING(m_iName.Get());
|
|
}
|
|
else
|
|
{
|
|
return STRING(m_iClassname);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::DrawInputOverlay(const char *szInputName, CBaseEntity *pCaller, variant_t Value)
|
|
{
|
|
char bigstring[1024];
|
|
if ( Value.FieldType() == FIELD_INTEGER )
|
|
{
|
|
Q_snprintf( bigstring,sizeof(bigstring), "%3.1f (%s,%d) <-- (%s)\n", gpGlobals->curtime, szInputName, Value.Int(), pCaller ? pCaller->GetDebugName() : NULL);
|
|
}
|
|
else if ( Value.FieldType() == FIELD_STRING )
|
|
{
|
|
Q_snprintf( bigstring,sizeof(bigstring), "%3.1f (%s,%s) <-- (%s)\n", gpGlobals->curtime, szInputName, Value.String(), pCaller ? pCaller->GetDebugName() : NULL);
|
|
}
|
|
else
|
|
{
|
|
Q_snprintf( bigstring,sizeof(bigstring), "%3.1f (%s) <-- (%s)\n", gpGlobals->curtime, szInputName, pCaller ? pCaller->GetDebugName() : NULL);
|
|
}
|
|
AddTimedOverlay(bigstring, 10.0);
|
|
|
|
if ( Value.FieldType() == FIELD_INTEGER )
|
|
{
|
|
DevMsg( 2, "input: (%s,%d) -> (%s,%s), from (%s)\n", szInputName, Value.Int(), STRING(m_iClassname), GetDebugName(), pCaller ? pCaller->GetDebugName() : NULL);
|
|
}
|
|
else if ( Value.FieldType() == FIELD_STRING )
|
|
{
|
|
DevMsg( 2, "input: (%s,%s) -> (%s,%s), from (%s)\n", szInputName, Value.String(), STRING(m_iClassname), GetDebugName(), pCaller ? pCaller->GetDebugName() : NULL);
|
|
}
|
|
else
|
|
DevMsg( 2, "input: (%s) -> (%s,%s), from (%s)\n", szInputName, STRING(m_iClassname), GetDebugName(), pCaller ? pCaller->GetDebugName() : NULL);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::DrawOutputOverlay(CEventAction *ev)
|
|
{
|
|
// Print to entity
|
|
char bigstring[1024];
|
|
if ( ev->m_flDelay )
|
|
{
|
|
Q_snprintf( bigstring,sizeof(bigstring), "%3.1f (%s) --> (%s),%.1f) \n", gpGlobals->curtime, STRING(ev->m_iTargetInput), STRING(ev->m_iTarget), ev->m_flDelay);
|
|
}
|
|
else
|
|
{
|
|
Q_snprintf( bigstring,sizeof(bigstring), "%3.1f (%s) --> (%s)\n", gpGlobals->curtime, STRING(ev->m_iTargetInput), STRING(ev->m_iTarget));
|
|
}
|
|
AddTimedOverlay(bigstring, 10.0);
|
|
|
|
// Now print to the console
|
|
if ( ev->m_flDelay )
|
|
{
|
|
DevMsg( 2, "output: (%s,%s) -> (%s,%s,%.1f)\n", STRING(m_iClassname), GetDebugName(), STRING(ev->m_iTarget), STRING(ev->m_iTargetInput), ev->m_flDelay );
|
|
}
|
|
else
|
|
{
|
|
DevMsg( 2, "output: (%s,%s) -> (%s,%s)\n", STRING(m_iClassname), GetDebugName(), STRING(ev->m_iTarget), STRING(ev->m_iTargetInput) );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Entity events... these are events targetted to a particular entity
|
|
// Each event defines its own well-defined event data structure
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::OnEntityEvent( EntityEvent_t event, void *pEventData )
|
|
{
|
|
switch( event )
|
|
{
|
|
case ENTITY_EVENT_WATER_TOUCH:
|
|
{
|
|
int nContents = (int)pEventData;
|
|
if ( !nContents || (nContents & CONTENTS_WATER) )
|
|
{
|
|
++m_nWaterTouch;
|
|
}
|
|
if ( nContents & CONTENTS_SLIME )
|
|
{
|
|
++m_nSlimeTouch;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ENTITY_EVENT_WATER_UNTOUCH:
|
|
{
|
|
int nContents = (int)pEventData;
|
|
if ( !nContents || (nContents & CONTENTS_WATER) )
|
|
{
|
|
--m_nWaterTouch;
|
|
}
|
|
if ( nContents & CONTENTS_SLIME )
|
|
{
|
|
--m_nSlimeTouch;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
// Only do this for vphysics objects
|
|
if ( GetMoveType() != MOVETYPE_VPHYSICS )
|
|
return;
|
|
|
|
int nNewContents = 0;
|
|
if ( m_nWaterTouch > 0 )
|
|
{
|
|
nNewContents |= CONTENTS_WATER;
|
|
}
|
|
|
|
if ( m_nSlimeTouch > 0 )
|
|
{
|
|
nNewContents |= CONTENTS_SLIME;
|
|
}
|
|
|
|
if (( nNewContents & MASK_WATER ) == 0)
|
|
{
|
|
SetWaterLevel( 0 );
|
|
SetWaterType( CONTENTS_EMPTY );
|
|
return;
|
|
}
|
|
|
|
SetWaterLevel( 1 );
|
|
SetWaterType( nNewContents );
|
|
}
|
|
|
|
|
|
ConVar ent_messages_draw( "ent_messages_draw", "0", FCVAR_CHEAT, "Visualizes all entity input/output activity." );
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: calls the appropriate message mapped function in the entity according
|
|
// to the fired action.
|
|
// Input : char *szInputName - input destination
|
|
// *pActivator - entity which initiated this sequence of actions
|
|
// *pCaller - entity from which this event is sent
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::AcceptInput( const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t Value, int outputID )
|
|
{
|
|
if ( ent_messages_draw.GetBool() )
|
|
{
|
|
if ( pCaller != NULL )
|
|
{
|
|
NDebugOverlay::Line( pCaller->GetAbsOrigin(), GetAbsOrigin(), 255, 255, 255, false, 3 );
|
|
NDebugOverlay::Box( pCaller->GetAbsOrigin(), Vector(-4, -4, -4), Vector(4, 4, 4), 255, 0, 0, 0, 3 );
|
|
}
|
|
|
|
NDebugOverlay::Text( GetAbsOrigin(), szInputName, false, 3 );
|
|
NDebugOverlay::Box( GetAbsOrigin(), Vector(-4, -4, -4), Vector(4, 4, 4), 0, 255, 0, 0, 3 );
|
|
}
|
|
|
|
// loop through the data description list, restoring each data desc block
|
|
for ( datamap_t *dmap = GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap )
|
|
{
|
|
// search through all the actions in the data description, looking for a match
|
|
for ( int i = 0; i < dmap->dataNumFields; i++ )
|
|
{
|
|
if ( dmap->dataDesc[i].flags & FTYPEDESC_INPUT )
|
|
{
|
|
if ( !Q_stricmp(dmap->dataDesc[i].externalName, szInputName) )
|
|
{
|
|
// found a match
|
|
|
|
// mapper debug message
|
|
#ifdef MAPBASE
|
|
CGMsg( 2, CON_GROUP_IO_SYSTEM, "(%0.2f) input %s: %s.%s(%s)\n", gpGlobals->curtime, pCaller ? STRING(pCaller->m_iName.Get()) : "<NULL>", GetDebugName(), szInputName, Value.String() );
|
|
#else
|
|
DevMsg( 2, "(%0.2f) input %s: %s.%s(%s)\n", gpGlobals->curtime, pCaller ? STRING(pCaller->m_iName.Get()) : "<NULL>", GetDebugName(), szInputName, Value.String() );
|
|
#endif
|
|
ADD_DEBUG_HISTORY( HISTORY_ENTITY_IO, szBuffer );
|
|
|
|
if (m_debugOverlays & OVERLAY_MESSAGE_BIT)
|
|
{
|
|
DrawInputOverlay(szInputName,pCaller,Value);
|
|
}
|
|
|
|
// convert the value if necessary
|
|
if ( Value.FieldType() != dmap->dataDesc[i].fieldType )
|
|
{
|
|
if ( !(Value.FieldType() == FIELD_VOID && dmap->dataDesc[i].fieldType == FIELD_STRING) ) // allow empty strings
|
|
{
|
|
#ifdef MAPBASE
|
|
// Activator, etc. support for EHANDLE convert
|
|
if ( !Value.Convert( (fieldtype_t)dmap->dataDesc[i].fieldType, this, pActivator, pCaller ) )
|
|
{
|
|
bool bBadConversion = true;
|
|
|
|
// Attempt to convert to string and back.
|
|
// Almost all field types support being converted to a string, and many support being parsed from a string too.
|
|
fieldtype_t originalfield = Value.FieldType();
|
|
if (Value.Convert(FIELD_STRING))
|
|
{
|
|
bBadConversion = !(Value.Convert((fieldtype_t)dmap->dataDesc[i].fieldType, this, pActivator, pCaller));
|
|
if (!bBadConversion)
|
|
{
|
|
// Actual support should be added for each field, but if it works, it works.
|
|
// Warning against it only matters if you're a programmer and want to add support for each field.
|
|
// Only send a warning in dev mode.
|
|
DevWarning("!! Had to convert to string and back\n"
|
|
"!! Source Field Type: %i, Target Field Type: %i\n",
|
|
originalfield, dmap->dataDesc[i].fieldType);
|
|
}
|
|
}
|
|
|
|
if (bBadConversion)
|
|
{
|
|
Warning( "!! ERROR: bad input/output link:\n!! Unable to convert value \"%s\" from %s (%s) to field type %i\n!! Target Entity: %s (%s), Input: %s\n",
|
|
Value.GetDebug(),
|
|
( pCaller != NULL ) ? STRING(pCaller->m_iClassname) : "<null>",
|
|
( pCaller != NULL ) ? STRING(pCaller->m_iName.Get()) : "<null>",
|
|
dmap->dataDesc[i].fieldType,
|
|
STRING(m_iClassname), GetDebugName(), szInputName );
|
|
return false;
|
|
}
|
|
}
|
|
#else
|
|
if ( !Value.Convert( (fieldtype_t)dmap->dataDesc[i].fieldType ) )
|
|
{
|
|
// bad conversion
|
|
Warning( "!! ERROR: bad input/output link:\n!! %s(%s,%s) doesn't match type from %s(%s)\n",
|
|
STRING(m_iClassname), GetDebugName(), szInputName,
|
|
( pCaller != NULL ) ? STRING(pCaller->m_iClassname) : "<null>",
|
|
( pCaller != NULL ) ? STRING(pCaller->m_iName.Get()) : "<null>" );
|
|
return false;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// call the input handler, or if there is none just set the value
|
|
inputfunc_t pfnInput = dmap->dataDesc[i].inputFunc;
|
|
|
|
if ( pfnInput )
|
|
{
|
|
// Package the data into a struct for passing to the input handler.
|
|
inputdata_t data;
|
|
data.pActivator = pActivator;
|
|
data.pCaller = pCaller;
|
|
data.value = Value;
|
|
data.nOutputID = outputID;
|
|
|
|
|
|
// Now, see if there's a function named Input<Name of Input> in this entity's script file.
|
|
// If so, execute it and let it decide whether to allow the default behavior to also execute.
|
|
bool bCallInputFunc = true; // Always assume default behavior (do call the input function)
|
|
|
|
if ( m_ScriptScope.IsInitialized() )
|
|
{
|
|
ScriptVariant_t functionReturn;
|
|
if ( ScriptInputHook( szInputName, pActivator, pCaller, Value, functionReturn ) )
|
|
{
|
|
bCallInputFunc = functionReturn.m_bool;
|
|
}
|
|
}
|
|
|
|
if( bCallInputFunc )
|
|
{
|
|
(this->*pfnInput)( data );
|
|
}
|
|
}
|
|
else if ( dmap->dataDesc[i].flags & FTYPEDESC_KEY )
|
|
{
|
|
// set the value directly
|
|
Value.SetOther( ((char*)this) + dmap->dataDesc[i].fieldOffset[ TD_OFFSET_NORMAL ]);
|
|
|
|
// TODO: if this becomes evil and causes too many full entity updates, then we should make
|
|
// a macro like this:
|
|
//
|
|
// define MAKE_INPUTVAR(x) void Note##x##Modified() { x.GetForModify(); }
|
|
//
|
|
// Then the datadesc points at that function and we call it here. The only pain is to add
|
|
// that function for all the DEFINE_INPUT calls.
|
|
NetworkStateChanged();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef MAPBASE_VSCRIPT
|
|
// Allow VScript to handle unhandled inputs.
|
|
if (m_ScriptScope.IsInitialized())
|
|
{
|
|
ScriptVariant_t functionReturn;
|
|
|
|
if ( ScriptInputHook( szInputName, pActivator, pCaller, Value, functionReturn ) )
|
|
{
|
|
if (functionReturn.m_bool)
|
|
return true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef MAPBASE
|
|
CGMsg( 2, CON_GROUP_IO_SYSTEM, "unhandled input: (%s) -> (%s,%s)\n", szInputName, STRING(m_iClassname), GetDebugName() );
|
|
#else
|
|
DevMsg( 2, "unhandled input: (%s) -> (%s,%s)\n", szInputName, STRING(m_iClassname), GetDebugName()/*,", from (%s,%s)" STRING(pCaller->m_iClassname), STRING(pCaller->m_iName.Get())*/ );
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
#ifdef MAPBASE_VSCRIPT
|
|
bool CBaseEntity::ScriptAcceptInput( const char *szInputName, const char *szValue, HSCRIPT hActivator, HSCRIPT hCaller )
|
|
{
|
|
variant_t value;
|
|
value.SetString( MAKE_STRING( szValue ) );
|
|
|
|
return AcceptInput( szInputName, ToEnt( hActivator ), ToEnt( hCaller ), value, 0 );
|
|
}
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::ScriptInputHook( const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t Value, ScriptVariant_t &functionReturn )
|
|
{
|
|
char szScriptFunctionName[255];
|
|
Q_strcpy( szScriptFunctionName, "Input" );
|
|
Q_strcat( szScriptFunctionName, szInputName, 255 );
|
|
|
|
g_pScriptVM->SetValue( "activator", ( pActivator ) ? ScriptVariant_t( pActivator->GetScriptInstance() ) : SCRIPT_VARIANT_NULL );
|
|
g_pScriptVM->SetValue( "caller", ( pCaller ) ? ScriptVariant_t( pCaller->GetScriptInstance() ) : SCRIPT_VARIANT_NULL );
|
|
#ifdef MAPBASE_VSCRIPT
|
|
Value.SetScriptVariant( functionReturn );
|
|
g_pScriptVM->SetValue( "parameter", functionReturn );
|
|
#endif
|
|
|
|
bool bHandled = false;
|
|
if( CallScriptFunction( szScriptFunctionName, &functionReturn ) )
|
|
{
|
|
bHandled = true;
|
|
}
|
|
|
|
g_pScriptVM->ClearValue( "activator" );
|
|
g_pScriptVM->ClearValue( "caller" );
|
|
#ifdef MAPBASE_VSCRIPT
|
|
g_pScriptVM->ClearValue( "parameter" );
|
|
#endif
|
|
|
|
return bHandled;
|
|
}
|
|
|
|
#ifdef MAPBASE_VSCRIPT
|
|
bool CBaseEntity::ScriptDeathHook( CTakeDamageInfo *info )
|
|
{
|
|
if (m_ScriptScope.IsInitialized() && g_Hook_OnDeath.CanRunInScope( m_ScriptScope ))
|
|
{
|
|
HSCRIPT hInfo = g_pScriptVM->RegisterInstance( info );
|
|
|
|
// info
|
|
ScriptVariant_t functionReturn;
|
|
ScriptVariant_t args[] = { ScriptVariant_t( hInfo ) };
|
|
if ( g_Hook_OnDeath.Call( m_ScriptScope, &functionReturn, args ) && (functionReturn.m_type == FIELD_BOOLEAN && functionReturn.m_bool == false) )
|
|
{
|
|
// Make this entity cheat death
|
|
g_pScriptVM->RemoveInstance( hInfo );
|
|
return false;
|
|
}
|
|
|
|
g_pScriptVM->RemoveInstance( hInfo );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Input handler for the entity alpha.
|
|
// Input : nAlpha - Alpha value (0 - 255).
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputAlpha( inputdata_t &inputdata )
|
|
{
|
|
SetRenderColorA( clamp( inputdata.value.Int(), 0, 255 ) );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Activate alternative sorting
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputAlternativeSorting( inputdata_t &inputdata )
|
|
{
|
|
m_bAlternateSorting = inputdata.value.Bool();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Input handler for the entity color. Ignores alpha since that is handled
|
|
// by a separate input handler.
|
|
// Input : Color32 new value for color (alpha is ignored).
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputColor( inputdata_t &inputdata )
|
|
{
|
|
color32 clr = inputdata.value.Color32();
|
|
|
|
SetRenderColor( clr.r, clr.g, clr.b );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Called whenever the entity is 'Used'. This can be when a player hits
|
|
// use, or when an entity targets it without an output name (legacy entities)
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputUse( inputdata_t &inputdata )
|
|
{
|
|
Use( inputdata.pActivator, inputdata.pCaller, (USE_TYPE)inputdata.nOutputID, 0 );
|
|
|
|
#ifdef MAPBASE
|
|
IGameEvent *event = gameeventmanager->CreateEvent( "player_use" );
|
|
if ( event )
|
|
{
|
|
event->SetInt( "userid", inputdata.pActivator && inputdata.pActivator->IsPlayer() ?
|
|
((CBasePlayer*)inputdata.pActivator)->GetUserID() : 0 );
|
|
event->SetInt( "entity", entindex() );
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
#endif // MAPBASE
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Reads an output variable, by string name, from an entity
|
|
// Input : char *varName - the string name of the variable
|
|
// variant_t *var - the value is stored here
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::ReadKeyField( const char *varName, variant_t *var )
|
|
{
|
|
if ( !varName )
|
|
return false;
|
|
|
|
// loop through the data description list, restoring each data desc block
|
|
for ( datamap_t *dmap = GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap )
|
|
{
|
|
// search through all the readable fields in the data description, looking for a match
|
|
for ( int i = 0; i < dmap->dataNumFields; i++ )
|
|
{
|
|
if ( dmap->dataDesc[i].flags & (FTYPEDESC_OUTPUT | FTYPEDESC_KEY) )
|
|
{
|
|
if ( !Q_stricmp(dmap->dataDesc[i].externalName, varName) )
|
|
{
|
|
var->Set( dmap->dataDesc[i].fieldType, ((char*)this) + dmap->dataDesc[i].fieldOffset[ TD_OFFSET_NORMAL ] );
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets the damage filter on the object
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputEnableDamageForces( inputdata_t &inputdata )
|
|
{
|
|
RemoveEFlags( EFL_NO_DAMAGE_FORCES );
|
|
}
|
|
|
|
void CBaseEntity::InputDisableDamageForces( inputdata_t &inputdata )
|
|
{
|
|
AddEFlags( EFL_NO_DAMAGE_FORCES );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets the damage filter on the object
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetDamageFilter( inputdata_t &inputdata )
|
|
{
|
|
// Get a handle to my damage filter entity if there is one.
|
|
m_iszDamageFilterName = inputdata.value.StringID();
|
|
if ( m_iszDamageFilterName != NULL_STRING )
|
|
{
|
|
m_hDamageFilter = gEntList.FindEntityByName( NULL, m_iszDamageFilterName );
|
|
}
|
|
else
|
|
{
|
|
m_hDamageFilter = NULL;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Dispatch effects on this entity
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputDispatchEffect( inputdata_t &inputdata )
|
|
{
|
|
const char *sEffect = inputdata.value.String();
|
|
if ( sEffect && sEffect[0] )
|
|
{
|
|
CEffectData data;
|
|
GetInputDispatchEffectPosition( sEffect, data.m_vOrigin, data.m_vAngles );
|
|
AngleVectors( data.m_vAngles, &data.m_vNormal );
|
|
data.m_vStart = data.m_vOrigin;
|
|
data.m_nEntIndex = entindex();
|
|
|
|
// Clip off leading attachment point numbers
|
|
while ( sEffect[0] >= '0' && sEffect[0] <= '9' )
|
|
{
|
|
sEffect++;
|
|
}
|
|
DispatchEffect( sEffect, data );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns the origin at which to play an inputted dispatcheffect
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::GetInputDispatchEffectPosition( const char *sInputString, Vector &pOrigin, QAngle &pAngles )
|
|
{
|
|
pOrigin = GetAbsOrigin();
|
|
pAngles = GetAbsAngles();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Marks the entity for deletion
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputKill( inputdata_t &inputdata )
|
|
{
|
|
// tell owner ( if any ) that we're dead.This is mostly for NPCMaker functionality.
|
|
CBaseEntity *pOwner = GetOwnerEntity();
|
|
if ( pOwner )
|
|
{
|
|
pOwner->DeathNotice( this );
|
|
SetOwnerEntity( NULL );
|
|
}
|
|
|
|
#ifdef MAPBASE
|
|
m_OnKilled.FireOutput( inputdata.pActivator, this );
|
|
#endif
|
|
|
|
#ifdef MAPBASE
|
|
// Kick players
|
|
if ( IsPlayer() )
|
|
{
|
|
engine->ServerCommand( UTIL_VarArgs( "kickid %d CBaseEntity::InputKill()\n", engine->GetPlayerUserId(edict()) ) );
|
|
}
|
|
else
|
|
{
|
|
UTIL_Remove( this );
|
|
}
|
|
#else
|
|
UTIL_Remove( this );
|
|
#endif
|
|
}
|
|
|
|
void CBaseEntity::InputKillHierarchy( inputdata_t &inputdata )
|
|
{
|
|
CBaseEntity *pChild, *pNext;
|
|
for ( pChild = FirstMoveChild(); pChild; pChild = pNext )
|
|
{
|
|
pNext = pChild->NextMovePeer();
|
|
pChild->InputKillHierarchy( inputdata );
|
|
}
|
|
|
|
// tell owner ( if any ) that we're dead. This is mostly for NPCMaker functionality.
|
|
CBaseEntity *pOwner = GetOwnerEntity();
|
|
if ( pOwner )
|
|
{
|
|
pOwner->DeathNotice( this );
|
|
SetOwnerEntity( NULL );
|
|
}
|
|
|
|
#ifdef MAPBASE
|
|
m_OnKilled.FireOutput( inputdata.pActivator, this );
|
|
|
|
// Kicking players in InputKillHierarchy does not exist in future Valve games
|
|
// if ( IsPlayer() )
|
|
#endif
|
|
|
|
UTIL_Remove( this );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Input handler for changing this entity's movement parent.
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetParent( inputdata_t &inputdata )
|
|
{
|
|
// If we had a parent attachment, clear it, because it's no longer valid.
|
|
if ( m_iParentAttachment )
|
|
{
|
|
m_iParentAttachment = 0;
|
|
}
|
|
|
|
SetParent( inputdata.value.StringID(), inputdata.pActivator );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose:
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::SetParentAttachment( const char *szInputName, const char *szAttachment, bool bMaintainOffset )
|
|
{
|
|
// Must have a parent
|
|
if ( !m_pParent )
|
|
{
|
|
Warning("ERROR: Tried to %s for entity %s (%s), but it has no parent.\n", szInputName, GetClassname(), GetDebugName() );
|
|
return;
|
|
}
|
|
|
|
// Valid only on CBaseAnimating
|
|
CBaseAnimating *pAnimating = m_pParent->GetBaseAnimating();
|
|
if ( !pAnimating )
|
|
{
|
|
Warning("ERROR: Tried to %s for entity %s (%s), but its parent has no model.\n", szInputName, GetClassname(), GetDebugName() );
|
|
return;
|
|
}
|
|
|
|
// Lookup the attachment
|
|
int iAttachment = pAnimating->LookupAttachment( szAttachment );
|
|
if ( iAttachment <= 0 )
|
|
{
|
|
Warning("ERROR: Tried to %s for entity %s (%s), but it has no attachment named %s.\n", szInputName, GetClassname(), GetDebugName(), szAttachment );
|
|
return;
|
|
}
|
|
|
|
m_iParentAttachment = iAttachment;
|
|
SetParent( m_pParent, m_iParentAttachment );
|
|
|
|
// Now move myself directly onto the attachment point
|
|
SetMoveType( MOVETYPE_NONE );
|
|
|
|
if ( !bMaintainOffset )
|
|
{
|
|
SetLocalOrigin( vec3_origin );
|
|
SetLocalAngles( vec3_angle );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Input handler for changing this entity's movement parent's attachment point
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetParentAttachment( inputdata_t &inputdata )
|
|
{
|
|
SetParentAttachment( "SetParentAttachment", inputdata.value.String(), false );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Input handler for changing this entity's movement parent's attachment point
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetParentAttachmentMaintainOffset( inputdata_t &inputdata )
|
|
{
|
|
SetParentAttachment( "SetParentAttachmentMaintainOffset", inputdata.value.String(), true );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Input handler for clearing this entity's movement parent.
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::InputClearParent( inputdata_t &inputdata )
|
|
{
|
|
SetParent( NULL );
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : Returns velcocity of base entity. If physically simulated gets
|
|
// velocity from physics object
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::GetVelocity(Vector *vVelocity, AngularImpulse *vAngVelocity)
|
|
{
|
|
if (GetMoveType()==MOVETYPE_VPHYSICS && m_pPhysicsObject)
|
|
{
|
|
m_pPhysicsObject->GetVelocity(vVelocity,vAngVelocity);
|
|
}
|
|
else
|
|
{
|
|
if (vVelocity != NULL)
|
|
{
|
|
*vVelocity = GetAbsVelocity();
|
|
}
|
|
if (vAngVelocity != NULL)
|
|
{
|
|
QAngle tmp = GetLocalAngularVelocity();
|
|
QAngleToAngularImpulse( tmp, *vAngVelocity );
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CBaseEntity::IsMoving()
|
|
{
|
|
Vector velocity;
|
|
GetVelocity( &velocity, NULL );
|
|
return velocity != vec3_origin;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Retrieves the coordinate frame for this entity.
|
|
// Input : forward - Receives the entity's forward vector.
|
|
// right - Receives the entity's right vector.
|
|
// up - Receives the entity's up vector.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::GetVectors(Vector* pForward, Vector* pRight, Vector* pUp) const
|
|
{
|
|
// This call is necessary to cause m_rgflCoordinateFrame to be recomputed
|
|
const matrix3x4_t &entityToWorld = EntityToWorldTransform();
|
|
|
|
if (pForward != NULL)
|
|
{
|
|
MatrixGetColumn( entityToWorld, 0, *pForward );
|
|
}
|
|
|
|
if (pRight != NULL)
|
|
{
|
|
MatrixGetColumn( entityToWorld, 1, *pRight );
|
|
*pRight *= -1.0f;
|
|
}
|
|
|
|
if (pUp != NULL)
|
|
{
|
|
MatrixGetColumn( entityToWorld, 2, *pUp );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets the model, validates that it's of the appropriate type
|
|
// Input : *szModelName -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SetModel( const char *szModelName )
|
|
{
|
|
int modelIndex = modelinfo->GetModelIndex( szModelName );
|
|
const model_t *model = modelinfo->GetModel( modelIndex );
|
|
if ( model && modelinfo->GetModelType( model ) != mod_brush )
|
|
{
|
|
Msg( "Setting CBaseEntity to non-brush model %s\n", szModelName );
|
|
}
|
|
UTIL_SetModel( this, szModelName );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
CStudioHdr *CBaseEntity::OnNewModel()
|
|
{
|
|
// Do nothing.
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//================================================================================
|
|
// TEAM HANDLING
|
|
//================================================================================
|
|
void CBaseEntity::InputSetTeam( inputdata_t &inputdata )
|
|
{
|
|
ChangeTeam( inputdata.value.Int() );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Put the entity in the specified team
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::ChangeTeam( int iTeamNum )
|
|
{
|
|
m_iTeamNum = iTeamNum;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Get the Team this entity is on
|
|
//-----------------------------------------------------------------------------
|
|
CTeam *CBaseEntity::GetTeam( void ) const
|
|
{
|
|
return GetGlobalTeam( m_iTeamNum );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns true if these players are both in at least one team together
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::InSameTeam( CBaseEntity *pEntity ) const
|
|
{
|
|
if ( !pEntity )
|
|
return false;
|
|
|
|
return ( pEntity->GetTeam() == GetTeam() );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns the string name of the players team
|
|
//-----------------------------------------------------------------------------
|
|
const char *CBaseEntity::TeamID( void ) const
|
|
{
|
|
if ( GetTeam() == NULL )
|
|
return "";
|
|
|
|
return GetTeam()->GetName();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns true if the player is on the same team
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::IsInTeam( CTeam *pTeam ) const
|
|
{
|
|
return ( GetTeam() == pTeam );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::GetTeamNumber( void ) const
|
|
{
|
|
return m_iTeamNum;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::IsInAnyTeam( void ) const
|
|
{
|
|
return ( GetTeam() != NULL );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns the type of damage that this entity inflicts.
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::GetDamageType() const
|
|
{
|
|
return DMG_GENERIC;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// process notification
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CBaseEntity::NotifySystemEvent( CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t ¶ms )
|
|
{
|
|
}
|
|
|
|
|
|
#ifdef MAPBASE
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::DispatchInteraction( int interactionType, void *data, CBaseCombatCharacter* sourceEnt )
|
|
{
|
|
if (interactionType <= 0)
|
|
return false;
|
|
|
|
if (m_ScriptScope.IsInitialized() && g_Hook_HandleInteraction.CanRunInScope( m_ScriptScope ))
|
|
{
|
|
//HSCRIPT hData = g_pScriptVM->RegisterInstance( data );
|
|
|
|
// interaction, data, sourceEnt
|
|
ScriptVariant_t functionReturn;
|
|
ScriptVariant_t args[] = { interactionType/*, ScriptVariant_t( hData )*/, ScriptVariant_t( ToHScript( sourceEnt ) ) };
|
|
if ( g_Hook_HandleInteraction.Call( m_ScriptScope, &functionReturn, args ) && (functionReturn.m_type == FIELD_BOOLEAN) )
|
|
{
|
|
// Return the interaction here
|
|
//g_pScriptVM->RemoveInstance( hData );
|
|
return functionReturn.m_bool;
|
|
}
|
|
|
|
//g_pScriptVM->RemoveInstance( hData );
|
|
}
|
|
|
|
return HandleInteraction( interactionType, data, sourceEnt );
|
|
}
|
|
#endif
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Holds an entity's previous abs origin and angles at the time of
|
|
// teleportation. Used for child & constrained entity fixup to prevent
|
|
// lazy updates of abs origins and angles from messing things up.
|
|
//-----------------------------------------------------------------------------
|
|
struct TeleportListEntry_t
|
|
{
|
|
CBaseEntity *pEntity;
|
|
Vector prevAbsOrigin;
|
|
QAngle prevAbsAngles;
|
|
};
|
|
|
|
|
|
static void TeleportEntity( CBaseEntity *pSourceEntity, TeleportListEntry_t &entry, const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity )
|
|
{
|
|
CBaseEntity *pTeleport = entry.pEntity;
|
|
Vector prevOrigin = entry.prevAbsOrigin;
|
|
QAngle prevAngles = entry.prevAbsAngles;
|
|
|
|
int nSolidFlags = pTeleport->GetSolidFlags();
|
|
pTeleport->AddSolidFlags( FSOLID_NOT_SOLID );
|
|
|
|
// I'm teleporting myself
|
|
if ( pSourceEntity == pTeleport )
|
|
{
|
|
if ( newAngles )
|
|
{
|
|
pTeleport->SetLocalAngles( *newAngles );
|
|
if ( pTeleport->IsPlayer() )
|
|
{
|
|
CBasePlayer *pPlayer = (CBasePlayer *)pTeleport;
|
|
pPlayer->SnapEyeAngles( *newAngles );
|
|
}
|
|
}
|
|
|
|
if ( newVelocity )
|
|
{
|
|
pTeleport->SetAbsVelocity( *newVelocity );
|
|
pTeleport->SetBaseVelocity( vec3_origin );
|
|
}
|
|
|
|
if ( newPosition )
|
|
{
|
|
pTeleport->IncrementInterpolationFrame();
|
|
UTIL_SetOrigin( pTeleport, *newPosition );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// My parent is teleporting, just update my position & physics
|
|
pTeleport->CalcAbsolutePosition();
|
|
}
|
|
IPhysicsObject *pPhys = pTeleport->VPhysicsGetObject();
|
|
bool rotatePhysics = false;
|
|
|
|
// handle physics objects / shadows
|
|
if ( pPhys )
|
|
{
|
|
if ( newVelocity )
|
|
{
|
|
pPhys->SetVelocity( newVelocity, NULL );
|
|
}
|
|
const QAngle *rotAngles = &pTeleport->GetAbsAngles();
|
|
// don't rotate physics on players or bbox entities
|
|
if (pTeleport->IsPlayer() || pTeleport->GetSolid() == SOLID_BBOX )
|
|
{
|
|
rotAngles = &vec3_angle;
|
|
}
|
|
else
|
|
{
|
|
rotatePhysics = true;
|
|
}
|
|
|
|
pPhys->SetPosition( pTeleport->GetAbsOrigin(), *rotAngles, true );
|
|
}
|
|
|
|
g_pNotify->ReportTeleportEvent( pTeleport, prevOrigin, prevAngles, rotatePhysics );
|
|
|
|
pTeleport->SetSolidFlags( nSolidFlags );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Recurses an entity hierarchy and fills out a list of all entities
|
|
// in the hierarchy with their current origins and angles.
|
|
//
|
|
// This list is necessary to keep lazy updates of abs origins and angles
|
|
// from messing up our child/constrained entity fixup.
|
|
//-----------------------------------------------------------------------------
|
|
static void BuildTeleportList_r( CBaseEntity *pTeleport, CUtlVector<TeleportListEntry_t> &teleportList )
|
|
{
|
|
TeleportListEntry_t entry;
|
|
|
|
entry.pEntity = pTeleport;
|
|
entry.prevAbsOrigin = pTeleport->GetAbsOrigin();
|
|
entry.prevAbsAngles = pTeleport->GetAbsAngles();
|
|
|
|
teleportList.AddToTail( entry );
|
|
|
|
CBaseEntity *pList = pTeleport->FirstMoveChild();
|
|
while ( pList )
|
|
{
|
|
BuildTeleportList_r( pList, teleportList );
|
|
pList = pList->NextMovePeer();
|
|
}
|
|
}
|
|
|
|
|
|
static CUtlVector<CBaseEntity *> g_TeleportStack;
|
|
void CBaseEntity::Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity )
|
|
{
|
|
if ( g_TeleportStack.Find( this ) >= 0 )
|
|
return;
|
|
int index = g_TeleportStack.AddToTail( this );
|
|
|
|
CUtlVector<TeleportListEntry_t> teleportList;
|
|
BuildTeleportList_r( this, teleportList );
|
|
|
|
int i;
|
|
for ( i = 0; i < teleportList.Count(); i++)
|
|
{
|
|
TeleportEntity( this, teleportList[i], newPosition, newAngles, newVelocity );
|
|
}
|
|
|
|
for (i = 0; i < teleportList.Count(); i++)
|
|
{
|
|
teleportList[i].pEntity->CollisionRulesChanged();
|
|
}
|
|
|
|
if ( IsPlayer() )
|
|
{
|
|
// Tell the client being teleported
|
|
IGameEvent *event = gameeventmanager->CreateEvent( "base_player_teleported" );
|
|
if ( event )
|
|
{
|
|
event->SetInt( "entindex", entindex() );
|
|
gameeventmanager->FireEventClientSide( event );
|
|
}
|
|
}
|
|
|
|
Assert( g_TeleportStack[index] == this );
|
|
g_TeleportStack.FastRemove( index );
|
|
|
|
// FIXME: add an initializer function to StepSimulationData
|
|
StepSimulationData *step = ( StepSimulationData * )GetDataObject( STEPSIMULATION );
|
|
if (step)
|
|
{
|
|
Q_memset( step, 0, sizeof( *step ) );
|
|
}
|
|
}
|
|
|
|
// Stuff implemented for weapon prediction code
|
|
void CBaseEntity::SetSize( const Vector &vecMin, const Vector &vecMax )
|
|
{
|
|
UTIL_SetSize( this, vecMin, vecMax );
|
|
}
|
|
|
|
CStudioHdr *ModelSoundsCache_LoadModel( const char *filename )
|
|
{
|
|
// Load the file
|
|
int idx = engine->PrecacheModel( filename, true );
|
|
if ( idx != -1 )
|
|
{
|
|
model_t *mdl = (model_t *)modelinfo->GetModel( idx );
|
|
if ( mdl )
|
|
{
|
|
CStudioHdr *studioHdr = new CStudioHdr( modelinfo->GetStudiomodel( mdl ), mdlcache );
|
|
if ( studioHdr->IsValid() )
|
|
{
|
|
return studioHdr;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void ModelSoundsCache_FinishModel( CStudioHdr *hdr )
|
|
{
|
|
Assert( hdr );
|
|
delete hdr;
|
|
}
|
|
|
|
void ModelSoundsCache_PrecacheScriptSound( const char *soundname )
|
|
{
|
|
CBaseEntity::PrecacheScriptSound( soundname );
|
|
}
|
|
|
|
static CUtlCachedFileData< CModelSoundsCache > g_ModelSoundsCache( "modelsounds.cache", MODELSOUNDSCACHE_VERSION, 0, UTL_CACHED_FILE_USE_FILESIZE, false );
|
|
|
|
void ClearModelSoundsCache()
|
|
{
|
|
if ( IsX360() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
g_ModelSoundsCache.Reload();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool ModelSoundsCacheInit()
|
|
{
|
|
if ( IsX360() )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return g_ModelSoundsCache.Init();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void ModelSoundsCacheShutdown()
|
|
{
|
|
if ( IsX360() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
g_ModelSoundsCache.Shutdown();
|
|
}
|
|
|
|
static CUtlSymbolTable g_ModelSoundsSymbolHelper( 0, 32, true );
|
|
class CModelSoundsCacheSaver: public CAutoGameSystem
|
|
{
|
|
public:
|
|
CModelSoundsCacheSaver( const char *name ) : CAutoGameSystem( name )
|
|
{
|
|
}
|
|
virtual void LevelInitPostEntity()
|
|
{
|
|
if ( IsX360() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( g_ModelSoundsCache.IsDirty() )
|
|
{
|
|
g_ModelSoundsCache.Save();
|
|
}
|
|
}
|
|
virtual void LevelShutdownPostEntity()
|
|
{
|
|
if ( IsX360() )
|
|
{
|
|
// Unforunate that this table must persist through duration of level.
|
|
// It is the common case that PrecacheModel() still gets called (and needs this table),
|
|
// after LevelInitPostEntity, as PrecacheModel() redundantly precaches.
|
|
g_ModelSoundsSymbolHelper.RemoveAll();
|
|
return;
|
|
}
|
|
|
|
if ( g_ModelSoundsCache.IsDirty() )
|
|
{
|
|
g_ModelSoundsCache.Save();
|
|
}
|
|
}
|
|
};
|
|
|
|
static CModelSoundsCacheSaver g_ModelSoundsCacheSaver( "CModelSoundsCacheSaver" );
|
|
|
|
//#define WATCHACCESS
|
|
#if defined( WATCHACCESS )
|
|
|
|
static bool g_bWatching = true;
|
|
|
|
void ModelLogFunc( const char *fileName, const char *accessType )
|
|
{
|
|
if ( g_bWatching && !CBaseEntity::IsPrecacheAllowed() )
|
|
{
|
|
if ( Q_stristr( fileName, ".vcd" ) )
|
|
{
|
|
Msg( "%s\n", fileName );
|
|
}
|
|
}
|
|
}
|
|
|
|
class CWatchForModelAccess: public CAutoGameSystem
|
|
{
|
|
public:
|
|
virtual bool Init()
|
|
{
|
|
filesystem->AddLoggingFunc(&ModelLogFunc);
|
|
return true;
|
|
}
|
|
|
|
virtual void Shutdown()
|
|
{
|
|
filesystem->RemoveLoggingFunc(&ModelLogFunc);
|
|
}
|
|
|
|
};
|
|
|
|
static CWatchForModelAccess g_WatchForModels;
|
|
|
|
#endif
|
|
|
|
// HACK: This must match the #define in cl_animevent.h in the client .dll code!!!
|
|
#define CL_EVENT_SOUND 5004
|
|
#define CL_EVENT_FOOTSTEP_LEFT 6004
|
|
#define CL_EVENT_FOOTSTEP_RIGHT 6005
|
|
#define CL_EVENT_MFOOTSTEP_LEFT 6006
|
|
#define CL_EVENT_MFOOTSTEP_RIGHT 6007
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Precache model sound. Requires a local symbol table to prevent
|
|
// a very expensive call to PrecacheScriptSound().
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::PrecacheSoundHelper( const char *pName )
|
|
{
|
|
if ( !IsX360() )
|
|
{
|
|
// 360 only
|
|
Assert( 0 );
|
|
return;
|
|
}
|
|
|
|
if ( !pName || !pName[0] )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( UTL_INVAL_SYMBOL == g_ModelSoundsSymbolHelper.Find( pName ) )
|
|
{
|
|
g_ModelSoundsSymbolHelper.AddString( pName );
|
|
|
|
// very expensive, only call when required
|
|
PrecacheScriptSound( pName );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Precache model components
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::PrecacheModelComponents( int nModelIndex )
|
|
{
|
|
|
|
model_t *pModel = (model_t *)modelinfo->GetModel( nModelIndex );
|
|
if ( !pModel || modelinfo->GetModelType( pModel ) != mod_studio )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// sounds
|
|
if ( IsPC() )
|
|
{
|
|
const char *name = modelinfo->GetModelName( pModel );
|
|
if ( !g_ModelSoundsCache.EntryExists( name ) )
|
|
{
|
|
char extension[ 8 ];
|
|
Q_ExtractFileExtension( name, extension, sizeof( extension ) );
|
|
|
|
if ( Q_stristr( extension, "mdl" ) )
|
|
{
|
|
DevMsg( 2, "Late precache of %s, need to rebuild modelsounds.cache\n", name );
|
|
}
|
|
else
|
|
{
|
|
if ( !extension[ 0 ] )
|
|
{
|
|
Warning( "Precache of %s ambigious (no extension specified)\n", name );
|
|
}
|
|
else
|
|
{
|
|
Warning( "Late precache of %s (file missing?)\n", name );
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
CModelSoundsCache *entry = g_ModelSoundsCache.Get( name );
|
|
Assert( entry );
|
|
if ( entry )
|
|
{
|
|
entry->PrecacheSoundList();
|
|
}
|
|
}
|
|
|
|
// particles
|
|
{
|
|
// Check keyvalues for auto-emitting particles
|
|
KeyValues *pModelKeyValues = new KeyValues("");
|
|
KeyValues::AutoDelete autodelete_pModelKeyValues( pModelKeyValues );
|
|
if ( pModelKeyValues->LoadFromBuffer( modelinfo->GetModelName( pModel ), modelinfo->GetModelKeyValueText( pModel ) ) )
|
|
{
|
|
KeyValues *pParticleEffects = pModelKeyValues->FindKey("Particles");
|
|
if ( pParticleEffects )
|
|
{
|
|
// Start grabbing the sounds and slotting them in
|
|
for ( KeyValues *pSingleEffect = pParticleEffects->GetFirstSubKey(); pSingleEffect; pSingleEffect = pSingleEffect->GetNextKey() )
|
|
{
|
|
const char *pParticleEffectName = pSingleEffect->GetString( "name", "" );
|
|
PrecacheParticleSystem( pParticleEffectName );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// model anim event owned components
|
|
{
|
|
// Check animevents for particle events
|
|
CStudioHdr studioHdr( modelinfo->GetStudiomodel( pModel ), mdlcache );
|
|
if ( studioHdr.IsValid() )
|
|
{
|
|
// force animation event resolution!!!
|
|
VerifySequenceIndex( &studioHdr );
|
|
|
|
int nSeqCount = studioHdr.GetNumSeq();
|
|
for ( int i = 0; i < nSeqCount; ++i )
|
|
{
|
|
mstudioseqdesc_t &seq = studioHdr.pSeqdesc( i );
|
|
int nEventCount = seq.numevents;
|
|
for ( int j = 0; j < nEventCount; ++j )
|
|
{
|
|
mstudioevent_t *pEvent = seq.pEvent( j );
|
|
|
|
if ( !( pEvent->type & AE_TYPE_NEWEVENTSYSTEM ) || ( pEvent->type & AE_TYPE_CLIENT ) )
|
|
{
|
|
if ( pEvent->event == AE_CL_CREATE_PARTICLE_EFFECT )
|
|
{
|
|
char token[256];
|
|
const char *pOptions = pEvent->pszOptions();
|
|
nexttoken( token, pOptions, ' ', sizeof( token ) );
|
|
if ( token )
|
|
{
|
|
PrecacheParticleSystem( token );
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// 360 precaches the model sounds now at init time, the cost is now ~250 msecs worst case.
|
|
// The disk based solution was not needed. Now at runtime partly due to already crawling the sequences
|
|
// for the particles and the expensive part was redundant PrecacheScriptSound(), which is now prevented
|
|
// by a local symbol table.
|
|
if ( IsX360() )
|
|
{
|
|
switch ( pEvent->event )
|
|
{
|
|
default:
|
|
{
|
|
if ( ( pEvent->type & AE_TYPE_NEWEVENTSYSTEM ) && ( pEvent->event == AE_SV_PLAYSOUND ) )
|
|
{
|
|
PrecacheSoundHelper( pEvent->pszOptions() );
|
|
}
|
|
}
|
|
break;
|
|
case CL_EVENT_FOOTSTEP_LEFT:
|
|
case CL_EVENT_FOOTSTEP_RIGHT:
|
|
{
|
|
char soundname[256];
|
|
char const *options = pEvent->pszOptions();
|
|
if ( !options || !options[0] )
|
|
{
|
|
options = "NPC_CombineS";
|
|
}
|
|
|
|
Q_snprintf( soundname, sizeof( soundname ), "%s.RunFootstepLeft", options );
|
|
PrecacheSoundHelper( soundname );
|
|
Q_snprintf( soundname, sizeof( soundname ), "%s.RunFootstepRight", options );
|
|
PrecacheSoundHelper( soundname );
|
|
Q_snprintf( soundname, sizeof( soundname ), "%s.FootstepLeft", options );
|
|
PrecacheSoundHelper( soundname );
|
|
Q_snprintf( soundname, sizeof( soundname ), "%s.FootstepRight", options );
|
|
PrecacheSoundHelper( soundname );
|
|
}
|
|
break;
|
|
case AE_CL_PLAYSOUND:
|
|
{
|
|
if ( !( pEvent->type & AE_TYPE_CLIENT ) )
|
|
break;
|
|
|
|
if ( pEvent->pszOptions()[0] )
|
|
{
|
|
PrecacheSoundHelper( pEvent->pszOptions() );
|
|
}
|
|
else
|
|
{
|
|
Warning( "-- Error --: empty soundname, .qc error on AE_CL_PLAYSOUND in model %s, sequence %s, animevent # %i\n",
|
|
studioHdr.GetRenderHdr()->pszName(), seq.pszLabel(), j+1 );
|
|
}
|
|
}
|
|
break;
|
|
case CL_EVENT_SOUND:
|
|
case SCRIPT_EVENT_SOUND:
|
|
case SCRIPT_EVENT_SOUND_VOICE:
|
|
{
|
|
PrecacheSoundHelper( pEvent->pszOptions() );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Add model to level precache list
|
|
// Input : *name - model name
|
|
// Output : int -- model index for model
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::PrecacheModel( const char *name, bool bPreload )
|
|
{
|
|
if ( !name || !*name )
|
|
{
|
|
Msg( "Attempting to precache model, but model name is NULL\n");
|
|
return -1;
|
|
}
|
|
|
|
// Warn on out of order precache
|
|
if ( !CBaseEntity::IsPrecacheAllowed() )
|
|
{
|
|
if ( !engine->IsModelPrecached( name ) )
|
|
{
|
|
Assert( !"CBaseEntity::PrecacheModel: too late" );
|
|
Warning( "Late precache of %s\n", name );
|
|
}
|
|
}
|
|
#if defined( WATCHACCESS )
|
|
else
|
|
{
|
|
g_bWatching = false;
|
|
}
|
|
#endif
|
|
|
|
int idx = engine->PrecacheModel( name, bPreload );
|
|
if ( idx != -1 )
|
|
{
|
|
PrecacheModelComponents( idx );
|
|
}
|
|
|
|
#if defined( WATCHACCESS )
|
|
g_bWatching = true;
|
|
#endif
|
|
|
|
return idx;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::Remove( )
|
|
{
|
|
UTIL_Remove( this );
|
|
}
|
|
|
|
// Entity degugging console commands
|
|
extern CBaseEntity *FindPickerEntity( CBasePlayer *pPlayer );
|
|
extern void SetDebugBits( CBasePlayer* pPlayer, const char *name, int bit );
|
|
extern CBaseEntity *GetNextCommandEntity( CBasePlayer *pPlayer, const char *name, CBaseEntity *ent );
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void ConsoleFireTargets( CBasePlayer *pPlayer, const char *name)
|
|
{
|
|
// If no name was given use the picker
|
|
if (FStrEq(name,""))
|
|
{
|
|
CBaseEntity *pEntity = FindPickerEntity( pPlayer );
|
|
if ( pEntity && !pEntity->IsMarkedForDeletion())
|
|
{
|
|
Msg( "[%03d] Found: %s, firing\n", gpGlobals->tickcount%1000, pEntity->GetDebugName());
|
|
pEntity->Use( pPlayer, pPlayer, USE_TOGGLE, 0 );
|
|
return;
|
|
}
|
|
}
|
|
// Otherwise use name or classname
|
|
FireTargets( name, pPlayer, pPlayer, USE_TOGGLE, 0 );
|
|
}
|
|
|
|
#ifdef MAPBASE
|
|
inline bool UtlStringLessFunc( const CUtlString &lhs, const CUtlString &rhs )
|
|
{
|
|
return Q_stricmp( lhs.String(), rhs.String() ) < 0;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : More concommands needed access to entities, so this has been moved to its own function.
|
|
// Input : cmdname - The name of the command.
|
|
// &commands - Where the complete autocompletes should be sent to.
|
|
// substring - The current search query. (only pool entities that start with this)
|
|
// checklen - The number of characters to check.
|
|
// Output : A pointer to a cUtlRBTRee containing all of the entities.
|
|
//------------------------------------------------------------------------------
|
|
static int AutoCompleteEntities(const char *cmdname, CUtlVector< CUtlString > &commands, CUtlRBTree< CUtlString > &symbols, char *substring, int checklen = 0)
|
|
{
|
|
CBaseEntity *pos = NULL;
|
|
while ((pos = gEntList.NextEnt(pos)) != NULL)
|
|
{
|
|
const char *name = pos->GetClassname();
|
|
if (pos->GetEntityName() == NULL_STRING || Q_strnicmp(STRING(pos->GetEntityName()), substring, checklen))
|
|
{
|
|
if (Q_strnicmp(pos->GetClassname(), substring, checklen))
|
|
continue;
|
|
}
|
|
else
|
|
name = STRING(pos->GetEntityName());
|
|
|
|
CUtlString sym = name;
|
|
int idx = symbols.Find(sym);
|
|
if (idx == symbols.InvalidIndex())
|
|
{
|
|
symbols.Insert(sym);
|
|
}
|
|
|
|
// Too many
|
|
if (symbols.Count() >= COMMAND_COMPLETION_MAXITEMS)
|
|
break;
|
|
}
|
|
|
|
// Now fill in the results
|
|
for (int i = symbols.FirstInorder(); i != symbols.InvalidIndex(); i = symbols.NextInorder(i))
|
|
{
|
|
const char *name = symbols[i].String();
|
|
|
|
char buf[512];
|
|
Q_strncpy(buf, name, sizeof(buf));
|
|
Q_strlower(buf);
|
|
|
|
CUtlString command;
|
|
command = CFmtStr("%s %s", cmdname, buf);
|
|
commands.AddToTail(command);
|
|
}
|
|
|
|
return symbols.Count();
|
|
}
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Name( const CCommand& args )
|
|
{
|
|
SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_NAME_BIT);
|
|
}
|
|
static ConCommand ent_name("ent_name", CC_Ent_Name, 0, FCVAR_CHEAT);
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
//------------------------------------------------------------------------------
|
|
void DumpScriptScope(CBasePlayer* pPlayer, const char* name)
|
|
{
|
|
CBaseEntity* pEntity = NULL;
|
|
while ((pEntity = GetNextCommandEntity(pPlayer, name, pEntity)) != NULL)
|
|
{
|
|
if (pEntity->m_ScriptScope.IsInitialized())
|
|
{
|
|
Msg("----Script Dump for entity %s\n", pEntity->GetDebugName());
|
|
HSCRIPT hDumpScopeFunc = g_pScriptVM->LookupFunction("__DumpScope");
|
|
g_pScriptVM->Call(hDumpScopeFunc, NULL, true, NULL, 1, (HSCRIPT)pEntity->m_ScriptScope);
|
|
Msg("----End Script Dump\n");
|
|
}
|
|
else
|
|
{
|
|
DevWarning("ent_script_dump: Entity %s has no script scope!\n", pEntity->GetDebugName());
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Script_Dump( const CCommand& args )
|
|
{
|
|
DumpScriptScope(UTIL_GetCommandClient(),args[1]);
|
|
}
|
|
static ConCommand ent_script_dump("ent_script_dump", CC_Ent_Script_Dump, "Dumps the names and values of this entity's script scope to the console\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
#ifdef MAPBASE
|
|
class CEntTextAutoCompletionFunctor : public ICommandCallback, public ICommandCompletionCallback
|
|
{
|
|
public:
|
|
virtual void CommandCallback( const CCommand &command )
|
|
{
|
|
SetDebugBits(UTIL_GetCommandClient(), command.Arg(1), OVERLAY_TEXT_BIT);
|
|
}
|
|
|
|
virtual int CommandCompletionCallback( const char *partial, CUtlVector< CUtlString > &commands )
|
|
{
|
|
if ( !g_pGameRules )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
const char *cmdname = "ent_text";
|
|
|
|
char *substring = (char *)partial;
|
|
if ( Q_strstr( partial, cmdname ) )
|
|
{
|
|
substring = (char *)partial + strlen( cmdname ) + 1;
|
|
}
|
|
|
|
int checklen = Q_strlen( substring );
|
|
|
|
CUtlRBTree< CUtlString > symbols( 0, 0, UtlStringLessFunc );
|
|
return AutoCompleteEntities(cmdname, commands, symbols, substring, checklen);
|
|
}
|
|
};
|
|
|
|
static CEntTextAutoCompletionFunctor g_EntTextAutoComplete;
|
|
static ConCommand ent_text("ent_text", &g_EntTextAutoComplete, "Displays text debugging information about the given entity(ies) on top of the entity (See Overlay Text)\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT, &g_EntTextAutoComplete);
|
|
#else
|
|
void CC_Ent_Text( const CCommand& args )
|
|
{
|
|
SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_TEXT_BIT);
|
|
}
|
|
static ConCommand ent_text("ent_text", CC_Ent_Text, "Displays text debugging information about the given entity(ies) on top of the entity (See Overlay Text)\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_BBox( const CCommand& args )
|
|
{
|
|
SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_BBOX_BIT);
|
|
}
|
|
static ConCommand ent_bbox("ent_bbox", CC_Ent_BBox, "Displays the movement bounding box for the given entity(ies) in orange. Some entites will also display entity specific overlays.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_AbsBox( const CCommand& args )
|
|
{
|
|
SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_ABSBOX_BIT);
|
|
}
|
|
static ConCommand ent_absbox("ent_absbox", CC_Ent_AbsBox, "Displays the total bounding box for the given entity(s) in green. Some entites will also display entity specific overlays.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_RBox( const CCommand& args )
|
|
{
|
|
SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_RBOX_BIT);
|
|
}
|
|
static ConCommand ent_rbox("ent_rbox", CC_Ent_RBox, "Displays the total bounding box for the given entity(s) in green. Some entites will also display entity specific overlays.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_AttachmentPoints( const CCommand& args )
|
|
{
|
|
SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_ATTACHMENTS_BIT);
|
|
}
|
|
static ConCommand ent_attachments("ent_attachments", CC_Ent_AttachmentPoints, "Displays the attachment points on an entity.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_ViewOffset( const CCommand& args )
|
|
{
|
|
SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_VIEWOFFSET);
|
|
}
|
|
static ConCommand ent_viewoffset("ent_viewoffset", CC_Ent_ViewOffset, "Displays the eye position for the given entity(ies) in red.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Remove( const CCommand& args )
|
|
{
|
|
CBaseEntity *pEntity = NULL;
|
|
|
|
// If no name was given set bits based on the picked
|
|
if ( FStrEq( args[1],"") )
|
|
{
|
|
pEntity = FindPickerEntity( UTIL_GetCommandClient() );
|
|
}
|
|
else
|
|
{
|
|
int index = atoi( args[1] );
|
|
if ( index )
|
|
{
|
|
pEntity = CBaseEntity::Instance( index );
|
|
}
|
|
else
|
|
{
|
|
// Otherwise set bits based on name or classname
|
|
CBaseEntity *ent = NULL;
|
|
while ( (ent = gEntList.NextEnt(ent)) != NULL )
|
|
{
|
|
if ( (ent->GetEntityName() != NULL_STRING && FStrEq(args[1], STRING(ent->GetEntityName()))) ||
|
|
(ent->m_iClassname != NULL_STRING && FStrEq(args[1], STRING(ent->m_iClassname))) ||
|
|
(ent->GetClassname()!=NULL && FStrEq(args[1], ent->GetClassname())))
|
|
{
|
|
pEntity = ent;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Found one?
|
|
if ( pEntity )
|
|
{
|
|
Msg( "Removed %s(%s)\n", STRING(pEntity->m_iClassname), pEntity->GetDebugName() );
|
|
UTIL_Remove( pEntity );
|
|
}
|
|
}
|
|
static ConCommand ent_remove("ent_remove", CC_Ent_Remove, "Removes the given entity(s)\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_RemoveAll( const CCommand& args )
|
|
{
|
|
// If no name was given remove based on the picked
|
|
if ( args.ArgC() < 2 )
|
|
{
|
|
Msg( "Removes all entities of the specified type\n\tArguments: {entity_name} / {class_name}\n" );
|
|
}
|
|
else
|
|
{
|
|
// Otherwise remove based on name or classname
|
|
int iCount = 0;
|
|
CBaseEntity *ent = NULL;
|
|
while ( (ent = gEntList.NextEnt(ent)) != NULL )
|
|
{
|
|
if ( (ent->GetEntityName() != NULL_STRING && FStrEq(args[1], STRING(ent->GetEntityName()))) ||
|
|
(ent->m_iClassname != NULL_STRING && FStrEq(args[1], STRING(ent->m_iClassname))) ||
|
|
(ent->GetClassname()!=NULL && FStrEq(args[1], ent->GetClassname())))
|
|
{
|
|
UTIL_Remove( ent );
|
|
iCount++;
|
|
}
|
|
}
|
|
|
|
if ( iCount )
|
|
{
|
|
Msg( "Removed %d %s's\n", iCount, args[1] );
|
|
}
|
|
else
|
|
{
|
|
Msg( "No %s found.\n", args[1] );
|
|
}
|
|
}
|
|
}
|
|
static ConCommand ent_remove_all("ent_remove_all", CC_Ent_RemoveAll, "Removes all entities of the specified type\n\tArguments: {entity_name} / {class_name} ", FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_SetName( const CCommand& args )
|
|
{
|
|
CBaseEntity *pEntity = NULL;
|
|
|
|
if ( args.ArgC() < 1 )
|
|
{
|
|
CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() );
|
|
if (!pPlayer)
|
|
return;
|
|
|
|
ClientPrint( pPlayer, HUD_PRINTCONSOLE, "Usage:\n ent_setname <new name> <entity name>\n" );
|
|
}
|
|
else
|
|
{
|
|
// If no name was given set bits based on the picked
|
|
if ( FStrEq( args[2],"") )
|
|
{
|
|
pEntity = FindPickerEntity( UTIL_GetCommandClient() );
|
|
}
|
|
else
|
|
{
|
|
// Otherwise set bits based on name or classname
|
|
CBaseEntity *ent = NULL;
|
|
while ( (ent = gEntList.NextEnt(ent)) != NULL )
|
|
{
|
|
if ( (ent->GetEntityName() != NULL_STRING && FStrEq(args[1], STRING(ent->GetEntityName()))) ||
|
|
(ent->m_iClassname != NULL_STRING && FStrEq(args[1], STRING(ent->m_iClassname))) ||
|
|
(ent->GetClassname()!=NULL && FStrEq(args[1], ent->GetClassname())))
|
|
{
|
|
pEntity = ent;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Found one?
|
|
if ( pEntity )
|
|
{
|
|
Msg( "Set the name of %s to %s\n", STRING(pEntity->m_iClassname), args[1] );
|
|
pEntity->SetName( AllocPooledString( args[1] ) );
|
|
}
|
|
}
|
|
}
|
|
static ConCommand ent_setname("ent_setname", CC_Ent_SetName, "Sets the targetname of the given entity(s)\n\tArguments: {new entity name} {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Find_Ent( const CCommand& args )
|
|
{
|
|
if ( args.ArgC() < 2 )
|
|
{
|
|
Msg( "Total entities: %d (%d edicts)\n", gEntList.NumberOfEntities(), gEntList.NumberOfEdicts() );
|
|
Msg( "Format: find_ent <substring>\n" );
|
|
return;
|
|
}
|
|
|
|
int iCount = 0;
|
|
const char *pszSubString = args[1];
|
|
Msg("Searching for entities with class/target name containing substring: '%s'\n", pszSubString );
|
|
|
|
CBaseEntity *ent = NULL;
|
|
while ( (ent = gEntList.NextEnt(ent)) != NULL )
|
|
{
|
|
const char *pszClassname = ent->GetClassname();
|
|
const char *pszTargetname = STRING(ent->GetEntityName());
|
|
|
|
bool bMatches = false;
|
|
if ( pszClassname && pszClassname[0] )
|
|
{
|
|
if ( Q_stristr( pszClassname, pszSubString ) )
|
|
{
|
|
bMatches = true;
|
|
}
|
|
}
|
|
|
|
if ( !bMatches && pszTargetname && pszTargetname[0] )
|
|
{
|
|
if ( Q_stristr( pszTargetname, pszSubString ) )
|
|
{
|
|
bMatches = true;
|
|
}
|
|
}
|
|
|
|
if ( bMatches )
|
|
{
|
|
iCount++;
|
|
Msg(" '%s' : '%s' (entindex %d) \n", ent->GetClassname(), ent->GetEntityName().ToCStr(), ent->entindex() );
|
|
}
|
|
}
|
|
|
|
Msg("Found %d matches.\n", iCount);
|
|
}
|
|
static ConCommand find_ent("find_ent", CC_Find_Ent, "Find and list all entities with classnames or targetnames that contain the specified substring.\nFormat: find_ent <substring>\n", FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Find_Ent_Index( const CCommand& args )
|
|
{
|
|
if ( args.ArgC() < 2 )
|
|
{
|
|
Msg( "Format: find_ent_index <index>\n" );
|
|
return;
|
|
}
|
|
|
|
int iIndex = atoi(args[1]);
|
|
CBaseEntity *pEnt = UTIL_EntityByIndex( iIndex );
|
|
if ( pEnt )
|
|
{
|
|
Msg(" '%s' : '%s' (entindex %d) \n", pEnt->GetClassname(), pEnt->GetEntityName().ToCStr(), iIndex );
|
|
}
|
|
else
|
|
{
|
|
Msg("Found no entity at %d.\n", iIndex);
|
|
}
|
|
}
|
|
static ConCommand find_ent_index("find_ent_index", CC_Find_Ent_Index, "Display data for entity matching specified index.\nFormat: find_ent_index <index>\n", FCVAR_CHEAT);
|
|
|
|
// Purpose :
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Dump( const CCommand& args )
|
|
{
|
|
CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() );
|
|
if (!pPlayer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( args.ArgC() < 2 )
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTCONSOLE, "Usage:\n ent_dump <entity name>\n" );
|
|
}
|
|
else
|
|
{
|
|
// iterate through all the ents of this name, printing out their details
|
|
CBaseEntity *ent = NULL;
|
|
bool bFound = false;
|
|
while ( ( ent = gEntList.FindEntityByName(ent, args[1] ) ) != NULL )
|
|
{
|
|
bFound = true;
|
|
for ( datamap_t *dmap = ent->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap )
|
|
{
|
|
// search through all the actions in the data description, printing out details
|
|
for ( int i = 0; i < dmap->dataNumFields; i++ )
|
|
{
|
|
variant_t var;
|
|
if ( ent->ReadKeyField( dmap->dataDesc[i].externalName, &var) )
|
|
{
|
|
char buf[256];
|
|
buf[0] = 0;
|
|
switch( var.FieldType() )
|
|
{
|
|
case FIELD_STRING:
|
|
Q_strncpy( buf, var.String() ,sizeof(buf));
|
|
break;
|
|
case FIELD_INTEGER:
|
|
if ( var.Int() )
|
|
Q_snprintf( buf,sizeof(buf), "%d", var.Int() );
|
|
break;
|
|
case FIELD_FLOAT:
|
|
if ( var.Float() )
|
|
Q_snprintf( buf,sizeof(buf), "%.2f", var.Float() );
|
|
break;
|
|
case FIELD_EHANDLE:
|
|
{
|
|
// get the entities name
|
|
if ( var.Entity() )
|
|
{
|
|
Q_snprintf( buf,sizeof(buf), "%s", STRING(var.Entity()->GetEntityName()) );
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
// don't print out the duplicate keys
|
|
if ( !Q_stricmp("parentname",dmap->dataDesc[i].externalName) || !Q_stricmp("targetname",dmap->dataDesc[i].externalName) )
|
|
continue;
|
|
|
|
// don't print out empty keys
|
|
if ( buf[0] )
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTCONSOLE, UTIL_VarArgs(" %s: %s\n", dmap->dataDesc[i].externalName, buf) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !bFound )
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTCONSOLE, "ent_dump: no such entity" );
|
|
}
|
|
}
|
|
}
|
|
static ConCommand ent_dump("ent_dump", CC_Ent_Dump, "Usage:\n ent_dump <entity name>\n", FCVAR_CHEAT);
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_FireTarget( const CCommand& args )
|
|
{
|
|
ConsoleFireTargets(UTIL_GetCommandClient(),args[1]);
|
|
}
|
|
static ConCommand firetarget("firetarget", CC_Ent_FireTarget, 0, FCVAR_CHEAT);
|
|
|
|
#ifndef MAPBASE
|
|
static bool UtlStringLessFunc( const CUtlString &lhs, const CUtlString &rhs )
|
|
{
|
|
return Q_stricmp( lhs.String(), rhs.String() ) < 0;
|
|
}
|
|
#endif
|
|
|
|
class CEntFireAutoCompletionFunctor : public ICommandCallback, public ICommandCompletionCallback
|
|
{
|
|
public:
|
|
virtual void CommandCallback( const CCommand &command )
|
|
{
|
|
CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() );
|
|
if (!pPlayer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// fires a command from the console
|
|
if ( command.ArgC() < 2 )
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTCONSOLE, "Usage:\n ent_fire <target> [action] [value] [delay]\n" );
|
|
}
|
|
else
|
|
{
|
|
const char *target = "", *action = "Use";
|
|
variant_t value;
|
|
#ifdef MAPBASE
|
|
float delay = 0;
|
|
#else
|
|
int delay = 0;
|
|
#endif
|
|
|
|
target = STRING( AllocPooledString(command.Arg( 1 ) ) );
|
|
|
|
// Don't allow them to run anything on a point_servercommand unless they're the host player. Otherwise they can ent_fire
|
|
// and run any command on the server. Admittedly, they can only do the ent_fire if sv_cheats is on, but
|
|
// people complained about users resetting the rcon password if the server briefly turned on cheats like this:
|
|
// give point_servercommand
|
|
// ent_fire point_servercommand command "rcon_password mynewpassword"
|
|
//
|
|
// Robin: Unfortunately, they get around point_servercommand checks with this:
|
|
// ent_create point_servercommand; ent_setname mine; ent_fire mine command "rcon_password mynewpassword"
|
|
// So, I'm removing the ability for anyone to execute ent_fires on dedicated servers (we can't check to see if
|
|
// this command is going to connect with a point_servercommand entity here, because they could delay the event and create it later).
|
|
if ( engine->IsDedicatedServer() )
|
|
{
|
|
// We allow people with disabled autokick to do it, because they already have rcon.
|
|
if ( pPlayer->IsAutoKickDisabled() == false )
|
|
return;
|
|
}
|
|
else if ( gpGlobals->maxClients > 1 )
|
|
{
|
|
// On listen servers with more than 1 player, only allow the host to issue ent_fires.
|
|
CBasePlayer *pHostPlayer = UTIL_GetListenServerHost();
|
|
if ( pPlayer != pHostPlayer )
|
|
return;
|
|
}
|
|
|
|
if ( command.ArgC() >= 3 )
|
|
{
|
|
action = STRING( AllocPooledString(command.Arg( 2 )) );
|
|
}
|
|
if ( command.ArgC() >= 4 )
|
|
{
|
|
value.SetString( AllocPooledString(command.Arg( 3 )) );
|
|
}
|
|
if ( command.ArgC() >= 5 )
|
|
{
|
|
#ifdef MAPBASE
|
|
delay = atof( command.Arg( 4 ) );
|
|
#else
|
|
delay = atoi( command.Arg( 4 ) );
|
|
#endif
|
|
}
|
|
|
|
g_EventQueue.AddEvent( target, action, value, delay, pPlayer, pPlayer );
|
|
}
|
|
}
|
|
|
|
virtual int CommandCompletionCallback( const char *partial, CUtlVector< CUtlString > &commands )
|
|
{
|
|
if ( !g_pGameRules )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
const char *cmdname = "ent_fire";
|
|
|
|
char *substring = (char *)partial;
|
|
if ( Q_strstr( partial, cmdname ) )
|
|
{
|
|
substring = (char *)partial + strlen( cmdname ) + 1;
|
|
}
|
|
|
|
int checklen = 0;
|
|
char *space = Q_strstr( substring, " " );
|
|
if ( space )
|
|
{
|
|
return EntFire_AutoCompleteInput( partial, commands );;
|
|
}
|
|
else
|
|
{
|
|
checklen = Q_strlen( substring );
|
|
}
|
|
|
|
#ifdef MAPBASE
|
|
CUtlRBTree< CUtlString > symbols( 0, 0, UtlStringLessFunc );
|
|
return AutoCompleteEntities(cmdname, commands, symbols, substring, checklen);
|
|
#else
|
|
CUtlRBTree< CUtlString > symbols( 0, 0, UtlStringLessFunc );
|
|
|
|
CBaseEntity *pos = NULL;
|
|
while ( ( pos = gEntList.NextEnt( pos ) ) != NULL )
|
|
{
|
|
// Check target name against partial string
|
|
if ( pos->GetEntityName() == NULL_STRING )
|
|
continue;
|
|
|
|
if ( Q_strnicmp( STRING( pos->GetEntityName() ), substring, checklen ) )
|
|
continue;
|
|
|
|
CUtlString sym = STRING( pos->GetEntityName() );
|
|
int idx = symbols.Find( sym );
|
|
if ( idx == symbols.InvalidIndex() )
|
|
{
|
|
symbols.Insert( sym );
|
|
}
|
|
|
|
// Too many
|
|
if ( symbols.Count() >= COMMAND_COMPLETION_MAXITEMS )
|
|
break;
|
|
}
|
|
|
|
// Now fill in the results
|
|
for ( int i = symbols.FirstInorder(); i != symbols.InvalidIndex(); i = symbols.NextInorder( i ) )
|
|
{
|
|
const char *name = symbols[ i ].String();
|
|
|
|
char buf[ 512 ];
|
|
Q_strncpy( buf, name, sizeof( buf ) );
|
|
Q_strlower( buf );
|
|
|
|
CUtlString command;
|
|
command = CFmtStr( "%s %s", cmdname, buf );
|
|
commands.AddToTail( command );
|
|
}
|
|
|
|
return symbols.Count();
|
|
#endif
|
|
}
|
|
private:
|
|
int EntFire_AutoCompleteInput( const char *partial, CUtlVector< CUtlString > &commands )
|
|
{
|
|
const char *cmdname = "ent_fire";
|
|
|
|
char *substring = (char *)partial;
|
|
if ( Q_strstr( partial, cmdname ) )
|
|
{
|
|
substring = (char *)partial + strlen( cmdname ) + 1;
|
|
}
|
|
|
|
int checklen = 0;
|
|
char *space = Q_strstr( substring, " " );
|
|
if ( !space )
|
|
{
|
|
Assert( !"CC_EntFireAutoCompleteInputFunc is broken\n" );
|
|
return 0;
|
|
}
|
|
|
|
checklen = Q_strlen( substring );
|
|
|
|
char targetEntity[ 256 ];
|
|
targetEntity[0] = 0;
|
|
int nEntityNameLength = (space-substring);
|
|
Q_strncat( targetEntity, substring, sizeof( targetEntity ), nEntityNameLength );
|
|
|
|
// Find the target entity by name
|
|
#ifdef MAPBASE
|
|
CBasePlayer *pPlayer = UTIL_GetCommandClient();
|
|
CBaseEntity *target = gEntList.FindEntityGeneric( NULL, targetEntity, pPlayer, pPlayer, pPlayer );
|
|
#else
|
|
CBaseEntity *target = gEntList.FindEntityByName( NULL, targetEntity );
|
|
#endif
|
|
if ( target == NULL )
|
|
return 0;
|
|
|
|
CUtlRBTree< CUtlString > symbols( 0, 0, UtlStringLessFunc );
|
|
|
|
// Find the next portion of the text chain, if any (removing space)
|
|
int nInputNameLength = (checklen-nEntityNameLength-1);
|
|
|
|
// Starting past the last space, this is the remainder of the string
|
|
char *inputPartial = ( checklen > nEntityNameLength ) ? (space+1) : NULL;
|
|
|
|
for ( datamap_t *dmap = target->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap )
|
|
{
|
|
// Make sure we don't keep adding things in if the satisfied the limit
|
|
if ( symbols.Count() >= COMMAND_COMPLETION_MAXITEMS )
|
|
break;
|
|
|
|
int c = dmap->dataNumFields;
|
|
for ( int i = 0; i < c; i++ )
|
|
{
|
|
typedescription_t *field = &dmap->dataDesc[ i ];
|
|
|
|
// Only want inputs
|
|
if ( !( field->flags & FTYPEDESC_INPUT ) )
|
|
continue;
|
|
|
|
#ifndef MAPBASE // What did input variables ever do to you?
|
|
|
|
// Only want input functions
|
|
if ( field->flags & FTYPEDESC_SAVE )
|
|
continue;
|
|
|
|
#endif
|
|
|
|
// See if we've got a partial string for the input name already
|
|
if ( inputPartial != NULL )
|
|
{
|
|
if ( Q_strnicmp( inputPartial, field->externalName, nInputNameLength ) )
|
|
continue;
|
|
}
|
|
|
|
CUtlString sym = field->externalName;
|
|
|
|
int idx = symbols.Find( sym );
|
|
if ( idx == symbols.InvalidIndex() )
|
|
{
|
|
symbols.Insert( sym );
|
|
}
|
|
|
|
// Too many items have been added
|
|
if ( symbols.Count() >= COMMAND_COMPLETION_MAXITEMS )
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Now fill in the results
|
|
for ( int i = symbols.FirstInorder(); i != symbols.InvalidIndex(); i = symbols.NextInorder( i ) )
|
|
{
|
|
const char *name = symbols[ i ].String();
|
|
|
|
char buf[ 512 ];
|
|
Q_strncpy( buf, name, sizeof( buf ) );
|
|
Q_strlower( buf );
|
|
|
|
CUtlString command;
|
|
command = CFmtStr( "%s %s %s", cmdname, targetEntity, buf );
|
|
commands.AddToTail( command );
|
|
}
|
|
|
|
return symbols.Count();
|
|
}
|
|
};
|
|
|
|
static CEntFireAutoCompletionFunctor g_EntFireAutoComplete;
|
|
static ConCommand ent_fire("ent_fire", &g_EntFireAutoComplete, "Usage:\n ent_fire <target> [action] [value] [delay]\n", FCVAR_CHEAT, &g_EntFireAutoComplete );
|
|
|
|
void CC_Ent_CancelPendingEntFires( const CCommand& args )
|
|
{
|
|
if ( !UTIL_IsCommandIssuedByServerAdmin() )
|
|
return;
|
|
|
|
CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() );
|
|
if (!pPlayer)
|
|
return;
|
|
|
|
g_EventQueue.CancelEvents( pPlayer );
|
|
}
|
|
static ConCommand ent_cancelpendingentfires("ent_cancelpendingentfires", CC_Ent_CancelPendingEntFires, "Cancels all ent_fire created outputs that are currently waiting for their delay to expire." );
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Info( const CCommand& args )
|
|
{
|
|
CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() );
|
|
if (!pPlayer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( args.ArgC() < 2 )
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTCONSOLE, "Usage:\n ent_info <class name>\n" );
|
|
}
|
|
else
|
|
{
|
|
// iterate through all the ents printing out their details
|
|
CBaseEntity *ent = CreateEntityByName( args[1] );
|
|
|
|
if ( ent )
|
|
{
|
|
datamap_t *dmap;
|
|
for ( dmap = ent->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap )
|
|
{
|
|
// search through all the actions in the data description, printing out details
|
|
for ( int i = 0; i < dmap->dataNumFields; i++ )
|
|
{
|
|
if ( dmap->dataDesc[i].flags & FTYPEDESC_OUTPUT )
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTCONSOLE, UTIL_VarArgs(" output: %s\n", dmap->dataDesc[i].externalName) );
|
|
}
|
|
}
|
|
}
|
|
|
|
for ( dmap = ent->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap )
|
|
{
|
|
// search through all the actions in the data description, printing out details
|
|
for ( int i = 0; i < dmap->dataNumFields; i++ )
|
|
{
|
|
if ( dmap->dataDesc[i].flags & FTYPEDESC_INPUT )
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTCONSOLE, UTIL_VarArgs(" input: %s\n", dmap->dataDesc[i].externalName) );
|
|
}
|
|
}
|
|
}
|
|
|
|
delete ent;
|
|
}
|
|
else
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTCONSOLE, UTIL_VarArgs("no such entity %s\n", args[1]) );
|
|
}
|
|
}
|
|
}
|
|
static ConCommand ent_info("ent_info", CC_Ent_Info, "Usage:\n ent_info <class name>\n", FCVAR_CHEAT);
|
|
|
|
#ifdef MAPBASE
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Info_Datatable( const CCommand& args )
|
|
{
|
|
CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() );
|
|
if (!pPlayer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( args.ArgC() < 2 )
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTCONSOLE, "Usage:\n ent_info_datatable <class name>\n" );
|
|
}
|
|
else
|
|
{
|
|
// Each element corresponds to a specific field type.
|
|
// Hey, if you've got a better idea, be my guest.
|
|
static const char *g_FieldStrings[FIELD_TYPECOUNT] =
|
|
{
|
|
"VOID",
|
|
"FLOAT",
|
|
"STRING",
|
|
"VECTOR",
|
|
"QUATERNION",
|
|
"INTEGER",
|
|
"BOOLEAN",
|
|
"SHORT",
|
|
"CHARACTER",
|
|
"COLOR32",
|
|
"EMBEDDED",
|
|
"CUSTOM",
|
|
|
|
"CLASSPTR",
|
|
"EHANDLE",
|
|
"EDICT",
|
|
|
|
"POSITION_VECTOR",
|
|
"TIME",
|
|
"TICK",
|
|
"MODELNAME",
|
|
"SOUNDNAME",
|
|
|
|
"INPUT",
|
|
"FUNCTION",
|
|
"VMATRIX",
|
|
"VMATRIX_WORLDSPACE",
|
|
"MATRIX3X4_WORLDSPACE",
|
|
"INTERVAL",
|
|
"MODELINDEX",
|
|
"MATERIALINDEX",
|
|
|
|
"VECTOR2D",
|
|
};
|
|
|
|
// iterate through all the ents printing out their details
|
|
CBaseEntity *ent = CreateEntityByName( args[1] );
|
|
|
|
if ( ent )
|
|
{
|
|
#define ENT_INFO_BY_HIERARCHY 1
|
|
#ifdef ENT_INFO_BY_HIERARCHY
|
|
CUtlVector<const char*> dmap_namelist;
|
|
|
|
CUtlVector< CUtlVector<const char*> > dmap_fieldlist;
|
|
CUtlVector< CUtlVector<int> > dmap_fieldtypelist;
|
|
|
|
datamap_t *dmap;
|
|
int dmapnum = 0;
|
|
for ( dmap = ent->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap )
|
|
{
|
|
dmap_fieldlist.AddToTail();
|
|
dmap_fieldtypelist.AddToTail();
|
|
|
|
// search through all the actions in the data description, printing out details
|
|
for ( int i = 0; i < dmap->dataNumFields; i++ )
|
|
{
|
|
dmap_fieldlist[dmapnum].AddToTail(dmap->dataDesc[i].fieldName);
|
|
dmap_fieldtypelist[dmapnum].AddToTail(dmap->dataDesc[i].fieldType);
|
|
}
|
|
|
|
dmapnum++;
|
|
dmap_namelist.AddToTail(dmap->dataClassName);
|
|
}
|
|
|
|
char offset[64] = { 0 }; // Needed so garbage isn't spewed at the beginning
|
|
for ( int i = 0; i < dmapnum; i++ )
|
|
{
|
|
Q_strncat(offset, " ", sizeof(offset));
|
|
|
|
// Header for each class
|
|
ClientPrint( pPlayer, HUD_PRINTCONSOLE, UTIL_VarArgs("%s=========| %s |=========\n", offset, dmap_namelist[i]) );
|
|
|
|
Q_strncat(offset, " ", sizeof(offset));
|
|
|
|
int iFieldCount = dmap_fieldlist[i].Count();
|
|
for ( int index = 0; index < iFieldCount; index++ )
|
|
{
|
|
int iType = dmap_fieldtypelist[i][index];
|
|
ClientPrint( pPlayer, HUD_PRINTCONSOLE, UTIL_VarArgs("%s%s (%i): %s\n", offset, g_FieldStrings[iType], iType, dmap_fieldlist[i][index]) );
|
|
}
|
|
|
|
// Clean up after ourselves
|
|
dmap_fieldlist[i].RemoveAll();
|
|
dmap_fieldtypelist[i].RemoveAll();
|
|
}
|
|
#else // This sorts by field type instead
|
|
CUtlVector<const char*> fieldlist[FIELD_TYPECOUNT];
|
|
|
|
datamap_t *dmap;
|
|
for ( dmap = ent->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap )
|
|
{
|
|
// search through all the actions in the data description, printing out details
|
|
for ( int i = 0; i < dmap->dataNumFields; i++ )
|
|
{
|
|
fieldlist[dmap->dataDesc[i].fieldType].AddToTail(dmap->dataDesc[i].fieldName);
|
|
}
|
|
}
|
|
|
|
for ( int i = 0; i < FIELD_TYPECOUNT; i++ )
|
|
{
|
|
const char *typestring = g_FieldStrings[i];
|
|
for ( int index = 0; index < fieldlist[i].Count(); index++ )
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTCONSOLE, UTIL_VarArgs(" %s (%i): %s\n", typestring, i, fieldlist[i][index]) );
|
|
}
|
|
|
|
// Clean up after ourselves
|
|
fieldlist[i].RemoveAll();
|
|
}
|
|
#endif
|
|
|
|
delete ent;
|
|
}
|
|
else
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTCONSOLE, UTIL_VarArgs("no such entity %s\n", args[1]) );
|
|
}
|
|
}
|
|
}
|
|
static ConCommand ent_info_datatable("ent_info_datatable", CC_Ent_Info_Datatable, "Usage:\n ent_info_datatable <class name>\n", FCVAR_CHEAT);
|
|
#endif
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Messages( const CCommand& args )
|
|
{
|
|
SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_MESSAGE_BIT);
|
|
}
|
|
static ConCommand ent_messages("ent_messages", CC_Ent_Messages ,"Toggles input/output message display for the selected entity(ies). The name of the entity will be displayed as well as any messages that it sends or receives.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at", FCVAR_CHEAT);
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Pause( void )
|
|
{
|
|
if (CBaseEntity::Debug_IsPaused())
|
|
{
|
|
Msg( "Resuming entity I/O events\n" );
|
|
CBaseEntity::Debug_Pause(false);
|
|
}
|
|
else
|
|
{
|
|
Msg( "Pausing entity I/O events\n" );
|
|
CBaseEntity::Debug_Pause(true);
|
|
}
|
|
}
|
|
static ConCommand ent_pause("ent_pause", CC_Ent_Pause, "Toggles pausing of input/output message processing for entities. When turned on processing of all message will stop. Any messages displayed with 'ent_messages' will stop fading and be displayed indefinitely. To step through the messages one by one use 'ent_step'.", FCVAR_CHEAT);
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : Enables the entity picker, revelaing debug information about the
|
|
// entity under the crosshair.
|
|
// Input : an optional command line argument "full" enables all debug info.
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Picker( void )
|
|
{
|
|
CBaseEntity::m_bInDebugSelect = CBaseEntity::m_bInDebugSelect ? false : true;
|
|
|
|
// Remember the player that's making this request
|
|
CBaseEntity::m_nDebugPlayer = UTIL_GetCommandClientIndex();
|
|
}
|
|
static ConCommand picker("picker", CC_Ent_Picker, "Toggles 'picker' mode. When picker is on, the bounding box, pivot and debugging text is displayed for whatever entity the player is looking at.\n\tArguments: full - enables all debug information", FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Pivot( const CCommand& args )
|
|
{
|
|
SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_PIVOT_BIT);
|
|
}
|
|
static ConCommand ent_pivot("ent_pivot", CC_Ent_Pivot, "Displays the pivot for the given entity(ies).\n\t(y=up=green, z=forward=blue, x=left=red). \n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Step( const CCommand& args )
|
|
{
|
|
int nSteps = atoi(args[1]);
|
|
if (nSteps <= 0)
|
|
{
|
|
nSteps = 1;
|
|
}
|
|
CBaseEntity::Debug_SetSteps(nSteps);
|
|
}
|
|
static ConCommand ent_step("ent_step", CC_Ent_Step, "When 'ent_pause' is set this will step through one waiting input / output message at a time.", FCVAR_CHEAT);
|
|
|
|
void CBaseEntity::SetCheckUntouch( bool check )
|
|
{
|
|
// Invalidate touchstamp
|
|
if ( check )
|
|
{
|
|
touchStamp++;
|
|
if ( !IsEFlagSet( EFL_CHECK_UNTOUCH ) )
|
|
{
|
|
AddEFlags( EFL_CHECK_UNTOUCH );
|
|
EntityTouch_Add( this );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RemoveEFlags( EFL_CHECK_UNTOUCH );
|
|
}
|
|
}
|
|
|
|
model_t *CBaseEntity::GetModel( void )
|
|
{
|
|
return (model_t *)modelinfo->GetModel( GetModelIndex() );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Calculates the absolute position of an edict in the world
|
|
// assumes the parent's absolute origin has already been calculated
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::CalcAbsolutePosition( void )
|
|
{
|
|
if (!IsEFlagSet( EFL_DIRTY_ABSTRANSFORM ))
|
|
return;
|
|
|
|
RemoveEFlags( EFL_DIRTY_ABSTRANSFORM );
|
|
|
|
// Plop the entity->parent matrix into m_rgflCoordinateFrame
|
|
AngleMatrix( m_angRotation, m_vecOrigin, m_rgflCoordinateFrame );
|
|
|
|
CBaseEntity *pMoveParent = GetMoveParent();
|
|
if ( !pMoveParent )
|
|
{
|
|
// no move parent, so just copy existing values
|
|
m_vecAbsOrigin = m_vecOrigin;
|
|
m_angAbsRotation = m_angRotation;
|
|
if ( HasDataObjectType( POSITIONWATCHER ) )
|
|
{
|
|
ReportPositionChanged( this );
|
|
}
|
|
return;
|
|
}
|
|
|
|
// concatenate with our parent's transform
|
|
matrix3x4_t tmpMatrix, scratchSpace;
|
|
ConcatTransforms( GetParentToWorldTransform( scratchSpace ), m_rgflCoordinateFrame, tmpMatrix );
|
|
MatrixCopy( tmpMatrix, m_rgflCoordinateFrame );
|
|
|
|
// pull our absolute position out of the matrix
|
|
MatrixGetColumn( m_rgflCoordinateFrame, 3, m_vecAbsOrigin );
|
|
|
|
// if we have any angles, we have to extract our absolute angles from our matrix
|
|
if (( m_angRotation == vec3_angle ) && ( m_iParentAttachment == 0 ))
|
|
{
|
|
// just copy our parent's absolute angles
|
|
VectorCopy( pMoveParent->GetAbsAngles(), m_angAbsRotation );
|
|
}
|
|
else
|
|
{
|
|
MatrixAngles( m_rgflCoordinateFrame, m_angAbsRotation );
|
|
}
|
|
if ( HasDataObjectType( POSITIONWATCHER ) )
|
|
{
|
|
ReportPositionChanged( this );
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::CalcAbsoluteVelocity()
|
|
{
|
|
if (!IsEFlagSet( EFL_DIRTY_ABSVELOCITY ))
|
|
return;
|
|
|
|
RemoveEFlags( EFL_DIRTY_ABSVELOCITY );
|
|
|
|
CBaseEntity *pMoveParent = GetMoveParent();
|
|
if ( !pMoveParent )
|
|
{
|
|
m_vecAbsVelocity = m_vecVelocity;
|
|
return;
|
|
}
|
|
|
|
// This transforms the local velocity into world space
|
|
VectorRotate( m_vecVelocity, pMoveParent->EntityToWorldTransform(), m_vecAbsVelocity );
|
|
|
|
// Now add in the parent abs velocity
|
|
m_vecAbsVelocity += pMoveParent->GetAbsVelocity();
|
|
}
|
|
|
|
// FIXME: While we're using (dPitch, dYaw, dRoll) as our local angular velocity
|
|
// representation, we can't actually solve this problem
|
|
/*
|
|
void CBaseEntity::CalcAbsoluteAngularVelocity()
|
|
{
|
|
if (!IsEFlagSet( EFL_DIRTY_ABSANGVELOCITY ))
|
|
return;
|
|
|
|
RemoveEFlags( EFL_DIRTY_ABSANGVELOCITY );
|
|
|
|
CBaseEntity *pMoveParent = GetMoveParent();
|
|
if ( !pMoveParent )
|
|
{
|
|
m_vecAbsAngVelocity = m_vecAngVelocity;
|
|
return;
|
|
}
|
|
|
|
// This transforms the local ang velocity into world space
|
|
matrix3x4_t angVelToParent, angVelToWorld;
|
|
AngleMatrix( m_vecAngVelocity, angVelToParent );
|
|
ConcatTransforms( pMoveParent->EntityToWorldTransform(), angVelToParent, angVelToWorld );
|
|
MatrixAngles( angVelToWorld, m_vecAbsAngVelocity );
|
|
}
|
|
*/
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Computes the abs position of a point specified in local space
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::ComputeAbsPosition( const Vector &vecLocalPosition, Vector *pAbsPosition )
|
|
{
|
|
CBaseEntity *pMoveParent = GetMoveParent();
|
|
if ( !pMoveParent )
|
|
{
|
|
*pAbsPosition = vecLocalPosition;
|
|
}
|
|
else
|
|
{
|
|
VectorTransform( vecLocalPosition, pMoveParent->EntityToWorldTransform(), *pAbsPosition );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Computes the abs position of a point specified in local space
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::ComputeAbsDirection( const Vector &vecLocalDirection, Vector *pAbsDirection )
|
|
{
|
|
CBaseEntity *pMoveParent = GetMoveParent();
|
|
if ( !pMoveParent )
|
|
{
|
|
*pAbsDirection = vecLocalDirection;
|
|
}
|
|
else
|
|
{
|
|
VectorRotate( vecLocalDirection, pMoveParent->EntityToWorldTransform(), *pAbsDirection );
|
|
}
|
|
}
|
|
|
|
|
|
matrix3x4_t& CBaseEntity::GetParentToWorldTransform( matrix3x4_t &tempMatrix )
|
|
{
|
|
CBaseEntity *pMoveParent = GetMoveParent();
|
|
if ( !pMoveParent )
|
|
{
|
|
Assert( false );
|
|
SetIdentityMatrix( tempMatrix );
|
|
return tempMatrix;
|
|
}
|
|
|
|
if ( m_iParentAttachment != 0 )
|
|
{
|
|
MDLCACHE_CRITICAL_SECTION();
|
|
|
|
CBaseAnimating *pAnimating = pMoveParent->GetBaseAnimating();
|
|
if ( pAnimating && pAnimating->GetAttachment( m_iParentAttachment, tempMatrix ) )
|
|
{
|
|
return tempMatrix;
|
|
}
|
|
}
|
|
|
|
// If we fall through to here, then just use the move parent's abs origin and angles.
|
|
return pMoveParent->EntityToWorldTransform();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// These methods recompute local versions as well as set abs versions
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SetAbsOrigin( const Vector& absOrigin )
|
|
{
|
|
AssertMsg( absOrigin.IsValid(), "Invalid origin set" );
|
|
|
|
// This is necessary to get the other fields of m_rgflCoordinateFrame ok
|
|
CalcAbsolutePosition();
|
|
|
|
if ( m_vecAbsOrigin == absOrigin )
|
|
return;
|
|
|
|
// All children are invalid, but we are not
|
|
InvalidatePhysicsRecursive( POSITION_CHANGED );
|
|
RemoveEFlags( EFL_DIRTY_ABSTRANSFORM );
|
|
|
|
m_vecAbsOrigin = absOrigin;
|
|
|
|
MatrixSetColumn( absOrigin, 3, m_rgflCoordinateFrame );
|
|
|
|
Vector vecNewOrigin;
|
|
CBaseEntity *pMoveParent = GetMoveParent();
|
|
if (!pMoveParent)
|
|
{
|
|
vecNewOrigin = absOrigin;
|
|
}
|
|
else
|
|
{
|
|
matrix3x4_t tempMat;
|
|
matrix3x4_t &parentTransform = GetParentToWorldTransform( tempMat );
|
|
|
|
// Moveparent case: transform the abs position into local space
|
|
VectorITransform( absOrigin, parentTransform, vecNewOrigin );
|
|
}
|
|
|
|
if (m_vecOrigin != vecNewOrigin)
|
|
{
|
|
m_vecOrigin = vecNewOrigin;
|
|
SetSimulationTime( gpGlobals->curtime );
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::SetAbsAngles( const QAngle& absAngles )
|
|
{
|
|
// This is necessary to get the other fields of m_rgflCoordinateFrame ok
|
|
CalcAbsolutePosition();
|
|
|
|
// FIXME: The normalize caused problems in server code like momentary_rot_button that isn't
|
|
// handling things like +/-180 degrees properly. This should be revisited.
|
|
//QAngle angleNormalize( AngleNormalize( absAngles.x ), AngleNormalize( absAngles.y ), AngleNormalize( absAngles.z ) );
|
|
|
|
if ( m_angAbsRotation == absAngles )
|
|
return;
|
|
|
|
// All children are invalid, but we are not
|
|
InvalidatePhysicsRecursive( ANGLES_CHANGED );
|
|
RemoveEFlags( EFL_DIRTY_ABSTRANSFORM );
|
|
|
|
m_angAbsRotation = absAngles;
|
|
AngleMatrix( absAngles, m_rgflCoordinateFrame );
|
|
MatrixSetColumn( m_vecAbsOrigin, 3, m_rgflCoordinateFrame );
|
|
|
|
QAngle angNewRotation;
|
|
CBaseEntity *pMoveParent = GetMoveParent();
|
|
if (!pMoveParent)
|
|
{
|
|
angNewRotation = absAngles;
|
|
}
|
|
else
|
|
{
|
|
if ( m_angAbsRotation == pMoveParent->GetAbsAngles() )
|
|
{
|
|
angNewRotation.Init( );
|
|
}
|
|
else
|
|
{
|
|
// Moveparent case: transform the abs transform into local space
|
|
matrix3x4_t worldToParent, localMatrix;
|
|
MatrixInvert( pMoveParent->EntityToWorldTransform(), worldToParent );
|
|
ConcatTransforms( worldToParent, m_rgflCoordinateFrame, localMatrix );
|
|
MatrixAngles( localMatrix, angNewRotation );
|
|
}
|
|
}
|
|
|
|
if (m_angRotation != angNewRotation)
|
|
{
|
|
m_angRotation = angNewRotation;
|
|
SetSimulationTime( gpGlobals->curtime );
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::SetAbsVelocity( const Vector &vecAbsVelocity )
|
|
{
|
|
if ( m_vecAbsVelocity == vecAbsVelocity )
|
|
return;
|
|
|
|
// The abs velocity won't be dirty since we're setting it here
|
|
// All children are invalid, but we are not
|
|
InvalidatePhysicsRecursive( VELOCITY_CHANGED );
|
|
RemoveEFlags( EFL_DIRTY_ABSVELOCITY );
|
|
|
|
m_vecAbsVelocity = vecAbsVelocity;
|
|
|
|
// NOTE: Do *not* do a network state change in this case.
|
|
// m_vecVelocity is only networked for the player, which is not manual mode
|
|
CBaseEntity *pMoveParent = GetMoveParent();
|
|
if (!pMoveParent)
|
|
{
|
|
m_vecVelocity = vecAbsVelocity;
|
|
return;
|
|
}
|
|
|
|
// First subtract out the parent's abs velocity to get a relative
|
|
// velocity measured in world space
|
|
Vector relVelocity;
|
|
VectorSubtract( vecAbsVelocity, pMoveParent->GetAbsVelocity(), relVelocity );
|
|
|
|
// Transform relative velocity into parent space
|
|
Vector vNew;
|
|
VectorIRotate( relVelocity, pMoveParent->EntityToWorldTransform(), vNew );
|
|
m_vecVelocity = vNew;
|
|
}
|
|
|
|
// FIXME: While we're using (dPitch, dYaw, dRoll) as our local angular velocity
|
|
// representation, we can't actually solve this problem
|
|
/*
|
|
void CBaseEntity::SetAbsAngularVelocity( const QAngle &vecAbsAngVelocity )
|
|
{
|
|
// The abs velocity won't be dirty since we're setting it here
|
|
// All children are invalid, but we are not
|
|
InvalidatePhysicsRecursive( EFL_DIRTY_ABSANGVELOCITY );
|
|
RemoveEFlags( EFL_DIRTY_ABSANGVELOCITY );
|
|
|
|
m_vecAbsAngVelocity = vecAbsAngVelocity;
|
|
|
|
CBaseEntity *pMoveParent = GetMoveParent();
|
|
if (!pMoveParent)
|
|
{
|
|
m_vecAngVelocity = vecAbsAngVelocity;
|
|
return;
|
|
}
|
|
|
|
// NOTE: We *can't* subtract out parent ang velocity, it's nonsensical
|
|
matrix3x4_t entityToWorld;
|
|
AngleMatrix( vecAbsAngVelocity, entityToWorld );
|
|
|
|
// Moveparent case: transform the abs relative angular vel into local space
|
|
matrix3x4_t worldToParent, localMatrix;
|
|
MatrixInvert( pMoveParent->EntityToWorldTransform(), worldToParent );
|
|
ConcatTransforms( worldToParent, entityToWorld, localMatrix );
|
|
MatrixAngles( localMatrix, m_vecAngVelocity );
|
|
}
|
|
*/
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Methods that modify local physics state, and let us know to compute abs state later
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SetLocalOrigin( const Vector& origin )
|
|
{
|
|
// Safety check against NaN's or really huge numbers
|
|
if ( !IsEntityPositionReasonable( origin ) )
|
|
{
|
|
if ( CheckEmitReasonablePhysicsSpew() )
|
|
{
|
|
Warning( "Bad SetLocalOrigin(%f,%f,%f) on %s\n", origin.x, origin.y, origin.z, GetDebugName() );
|
|
}
|
|
Assert( false );
|
|
return;
|
|
}
|
|
|
|
// if ( !origin.IsValid() )
|
|
// {
|
|
// AssertMsg( 0, "Bad origin set" );
|
|
// return;
|
|
// }
|
|
|
|
if (m_vecOrigin != origin)
|
|
{
|
|
// Sanity check to make sure the origin is valid.
|
|
#ifdef _DEBUG
|
|
float largeVal = 1024 * 128;
|
|
Assert( origin.x >= -largeVal && origin.x <= largeVal );
|
|
Assert( origin.y >= -largeVal && origin.y <= largeVal );
|
|
Assert( origin.z >= -largeVal && origin.z <= largeVal );
|
|
#endif
|
|
|
|
InvalidatePhysicsRecursive( POSITION_CHANGED );
|
|
m_vecOrigin = origin;
|
|
SetSimulationTime( gpGlobals->curtime );
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::SetLocalAngles( const QAngle& angles )
|
|
{
|
|
// NOTE: The angle normalize is a little expensive, but we can save
|
|
// a bunch of time in interpolation if we don't have to invalidate everything
|
|
// and sometimes it's off by a normalization amount
|
|
|
|
// FIXME: The normalize caused problems in server code like momentary_rot_button that isn't
|
|
// handling things like +/-180 degrees properly. This should be revisited.
|
|
//QAngle angleNormalize( AngleNormalize( angles.x ), AngleNormalize( angles.y ), AngleNormalize( angles.z ) );
|
|
|
|
// Safety check against NaN's or really huge numbers
|
|
if ( !IsEntityQAngleReasonable( angles ) )
|
|
{
|
|
if ( CheckEmitReasonablePhysicsSpew() )
|
|
{
|
|
Warning( "Bad SetLocalAngles(%f,%f,%f) on %s\n", angles.x, angles.y, angles.z, GetDebugName() );
|
|
}
|
|
Assert( false );
|
|
return;
|
|
}
|
|
|
|
if (m_angRotation != angles)
|
|
{
|
|
InvalidatePhysicsRecursive( ANGLES_CHANGED );
|
|
m_angRotation = angles;
|
|
SetSimulationTime( gpGlobals->curtime );
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::SetLocalVelocity( const Vector &inVecVelocity )
|
|
{
|
|
Vector vecVelocity = inVecVelocity;
|
|
|
|
// Safety check against receive a huge impulse, which can explode physics
|
|
switch ( CheckEntityVelocity( vecVelocity ) )
|
|
{
|
|
case -1:
|
|
Warning( "Discarding SetLocalVelocity(%f,%f,%f) on %s\n", vecVelocity.x, vecVelocity.y, vecVelocity.z, GetDebugName() );
|
|
Assert( false );
|
|
return;
|
|
case 0:
|
|
if ( CheckEmitReasonablePhysicsSpew() )
|
|
{
|
|
Warning( "Clamping SetLocalVelocity(%f,%f,%f) on %s\n", inVecVelocity.x, inVecVelocity.y, inVecVelocity.z, GetDebugName() );
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (m_vecVelocity != vecVelocity)
|
|
{
|
|
InvalidatePhysicsRecursive( VELOCITY_CHANGED );
|
|
m_vecVelocity = vecVelocity;
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::SetLocalAngularVelocity( const QAngle &vecAngVelocity )
|
|
{
|
|
// Safety check against NaN's or really huge numbers
|
|
if ( !IsEntityQAngleVelReasonable( vecAngVelocity ) )
|
|
{
|
|
if ( CheckEmitReasonablePhysicsSpew() )
|
|
{
|
|
Warning( "Bad SetLocalAngularVelocity(%f,%f,%f) on %s\n", vecAngVelocity.x, vecAngVelocity.y, vecAngVelocity.z, GetDebugName() );
|
|
}
|
|
Assert( false );
|
|
return;
|
|
}
|
|
|
|
if (m_vecAngVelocity != vecAngVelocity)
|
|
{
|
|
// InvalidatePhysicsRecursive( EFL_DIRTY_ABSANGVELOCITY );
|
|
m_vecAngVelocity = vecAngVelocity;
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Sets the local position from a transform
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SetLocalTransform( const matrix3x4_t &localTransform )
|
|
{
|
|
// FIXME: Should angles go away? Should we just use transforms?
|
|
Vector vecLocalOrigin;
|
|
QAngle vecLocalAngles;
|
|
MatrixGetColumn( localTransform, 3, vecLocalOrigin );
|
|
MatrixAngles( localTransform, vecLocalAngles );
|
|
SetLocalOrigin( vecLocalOrigin );
|
|
SetLocalAngles( vecLocalAngles );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Is the entity floating?
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::IsFloating()
|
|
{
|
|
if ( !IsEFlagSet(EFL_TOUCHING_FLUID) )
|
|
return false;
|
|
|
|
IPhysicsObject *pObject = VPhysicsGetObject();
|
|
if ( !pObject )
|
|
return false;
|
|
|
|
int nMaterialIndex = pObject->GetMaterialIndex();
|
|
|
|
float flDensity;
|
|
float flThickness;
|
|
float flFriction;
|
|
float flElasticity;
|
|
physprops->GetPhysicsProperties( nMaterialIndex, &flDensity,
|
|
&flThickness, &flFriction, &flElasticity );
|
|
|
|
// FIXME: This really only works for water at the moment..
|
|
// Owing the check for density == 1000
|
|
return (flDensity < 1000.0f);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Created predictable and sets up Id. Note that persist is ignored on the server.
|
|
// Input : *classname -
|
|
// *module -
|
|
// line -
|
|
// persist -
|
|
// Output : CBaseEntity
|
|
//-----------------------------------------------------------------------------
|
|
CBaseEntity *CBaseEntity::CreatePredictedEntityByName( const char *classname, const char *module, int line, bool persist /* = false */ )
|
|
{
|
|
#if !defined( NO_ENTITY_PREDICTION )
|
|
CBasePlayer *player = CBaseEntity::GetPredictionPlayer();
|
|
Assert( player );
|
|
|
|
CBaseEntity *ent = NULL;
|
|
|
|
int command_number = player->CurrentCommandNumber();
|
|
int player_index = player->entindex() - 1;
|
|
|
|
CPredictableId testId;
|
|
testId.Init( player_index, command_number, classname, module, line );
|
|
|
|
ent = CreateEntityByName( classname );
|
|
// No factory???
|
|
if ( !ent )
|
|
return NULL;
|
|
|
|
ent->SetPredictionEligible( true );
|
|
|
|
// Set up "shared" id number
|
|
ent->m_PredictableID.GetForModify().SetRaw( testId.GetRaw() );
|
|
|
|
return ent;
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
|
|
}
|
|
|
|
void CBaseEntity::SetPredictionEligible( bool canpredict )
|
|
{
|
|
// Nothing in game code m_bPredictionEligible = canpredict;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// These could be virtual, but only the player is overriding them
|
|
// NOTE: If you make any of these virtual, remove this implementation!!!
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::AddPoints( int score, bool bAllowNegativeScore )
|
|
{
|
|
CBasePlayer *pPlayer = ToBasePlayer(this);
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->CBasePlayer::AddPoints( score, bAllowNegativeScore );
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::AddPointsToTeam( int score, bool bAllowNegativeScore )
|
|
{
|
|
CBasePlayer *pPlayer = ToBasePlayer(this);
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->CBasePlayer::AddPointsToTeam( score, bAllowNegativeScore );
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::ViewPunch( const QAngle &angleOffset )
|
|
{
|
|
CBasePlayer *pPlayer = ToBasePlayer(this);
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->CBasePlayer::ViewPunch( angleOffset );
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::VelocityPunch( const Vector &vecForce )
|
|
{
|
|
CBasePlayer *pPlayer = ToBasePlayer(this);
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->CBasePlayer::VelocityPunch( vecForce );
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Tell clients to remove all decals from this entity
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::RemoveAllDecals( void )
|
|
{
|
|
EntityMessageBegin( this );
|
|
WRITE_BYTE( BASEENTITY_MSG_REMOVE_DECALS );
|
|
MessageEnd();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : set -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::ModifyOrAppendCriteria( AI_CriteriaSet& set )
|
|
{
|
|
// TODO
|
|
// Append chapter/day?
|
|
|
|
set.AppendCriteria( "randomnum", UTIL_VarArgs("%d", RandomInt(0,100)) );
|
|
// Append map name
|
|
set.AppendCriteria( "map", gpGlobals->mapname.ToCStr() );
|
|
// Append our classname and game name
|
|
set.AppendCriteria( "classname", GetClassname() );
|
|
set.AppendCriteria( "name", GetEntityName().ToCStr() );
|
|
|
|
// Append our health
|
|
set.AppendCriteria( "health", UTIL_VarArgs( "%i", GetHealth() ) );
|
|
|
|
float healthfrac = 0.0f;
|
|
if ( GetMaxHealth() > 0 )
|
|
{
|
|
healthfrac = (float)GetHealth() / (float)GetMaxHealth();
|
|
}
|
|
|
|
set.AppendCriteria( "healthfrac", UTIL_VarArgs( "%.3f", healthfrac ) );
|
|
|
|
// Go through all the global states and append them
|
|
|
|
for ( int i = 0; i < GlobalEntity_GetNumGlobals(); i++ )
|
|
{
|
|
const char *szGlobalName = GlobalEntity_GetName(i);
|
|
int iGlobalState = (int)GlobalEntity_GetStateByIndex(i);
|
|
set.AppendCriteria( szGlobalName, UTIL_VarArgs( "%i", iGlobalState ) );
|
|
}
|
|
|
|
#ifndef MAPBASE // We do this later now so contexts can override criteria. I originally didn't want to remove it here in case there would be problems, but I think I have all of the bases covered.
|
|
// Append anything from I/O or keyvalues pairs
|
|
AppendContextToCriteria( set );
|
|
#endif
|
|
|
|
if( hl2_episodic.GetBool() )
|
|
{
|
|
set.AppendCriteria( "episodic", "1" );
|
|
}
|
|
|
|
// Append anything from world I/O/keyvalues with "world" as prefix
|
|
#ifdef MAPBASE
|
|
CWorld *world = GetWorldEntity();
|
|
#else
|
|
CWorld *world = dynamic_cast< CWorld * >( CBaseEntity::Instance( engine->PEntityOfEntIndex( 0 ) ) );
|
|
#endif
|
|
if ( world )
|
|
{
|
|
world->AppendContextToCriteria( set, "world" );
|
|
}
|
|
|
|
#ifdef MAPBASE
|
|
// Append base stuff
|
|
set.AppendCriteria("spawnflags", UTIL_VarArgs("%i", GetSpawnFlags()));
|
|
set.AppendCriteria("flags", UTIL_VarArgs("%i", GetFlags()));
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : set -
|
|
// "" -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::AppendContextToCriteria( AI_CriteriaSet& set, const char *prefix /*= ""*/ )
|
|
{
|
|
RemoveExpiredConcepts();
|
|
|
|
int c = GetContextCount();
|
|
int i;
|
|
|
|
char sz[ 128 ];
|
|
for ( i = 0; i < c; i++ )
|
|
{
|
|
const char *name = GetContextName( i );
|
|
const char *value = GetContextValue( i );
|
|
|
|
Q_snprintf( sz, sizeof( sz ), "%s%s", prefix, name );
|
|
|
|
set.AppendCriteria( sz, value );
|
|
}
|
|
}
|
|
|
|
#ifdef MAPBASE
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : set -
|
|
// "" -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::ReAppendContextCriteria( AI_CriteriaSet& set )
|
|
{
|
|
// Append contexts again. This allows it to override standard criteria, including that of derived classes.
|
|
CWorld *world = GetWorldEntity();
|
|
if ( world )
|
|
{
|
|
// I didn't know this until recently, but world contexts are actually prefixed by "world".
|
|
// I'm changing this here to reduce confusion and allow greater potential.
|
|
// (e.g. disabling combine soldier episodic on/off in Episodic binaries with world context "episodic:0", even though that's not happening anymore)
|
|
// The old prefixed ones still exist. They're just not appended again.
|
|
world->AppendContextToCriteria( set );
|
|
}
|
|
|
|
AppendContextToCriteria( set );
|
|
}
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Removes expired concepts from list
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::RemoveExpiredConcepts( void )
|
|
{
|
|
int c = GetContextCount();
|
|
int i;
|
|
|
|
for ( i = 0; i < c; i++ )
|
|
{
|
|
if ( ContextExpired( i ) )
|
|
{
|
|
m_ResponseContexts.Remove( i );
|
|
c--;
|
|
i--;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Get current context count
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::GetContextCount() const
|
|
{
|
|
return m_ResponseContexts.Count();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : index -
|
|
// Output : const char
|
|
//-----------------------------------------------------------------------------
|
|
const char *CBaseEntity::GetContextName( int index ) const
|
|
{
|
|
if ( index < 0 || index >= m_ResponseContexts.Count() )
|
|
{
|
|
Assert( 0 );
|
|
return "";
|
|
}
|
|
|
|
return m_ResponseContexts[ index ].m_iszName.ToCStr();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : index -
|
|
// Output : const char
|
|
//-----------------------------------------------------------------------------
|
|
const char *CBaseEntity::GetContextValue( int index ) const
|
|
{
|
|
if ( index < 0 || index >= m_ResponseContexts.Count() )
|
|
{
|
|
Assert( 0 );
|
|
return "";
|
|
}
|
|
|
|
return m_ResponseContexts[ index ].m_iszValue.ToCStr();
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Check if context has expired
|
|
// Input : index -
|
|
// Output : bool
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::ContextExpired( int index ) const
|
|
{
|
|
if ( index < 0 || index >= m_ResponseContexts.Count() )
|
|
{
|
|
Assert( 0 );
|
|
return true;
|
|
}
|
|
|
|
if ( !m_ResponseContexts[ index ].m_fExpirationTime )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return ( m_ResponseContexts[ index ].m_fExpirationTime <= gpGlobals->curtime );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Search for index of named context string
|
|
// Input : *name -
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::FindContextByName( const char *name ) const
|
|
{
|
|
int c = m_ResponseContexts.Count();
|
|
for ( int i = 0; i < c; i++ )
|
|
{
|
|
if ( FStrEq( name, GetContextName( i ) ) )
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
#ifdef MAPBASE
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Searches entity for named context string and/or value.
|
|
// Intended to be called by entities rather than the conventional response system.
|
|
// Input : *name - Context name.
|
|
// *value - Context value. (optional)
|
|
// Output : bool
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::HasContext( const char *name, const char *value ) const
|
|
{
|
|
int c = m_ResponseContexts.Count();
|
|
for ( int i = 0; i < c; i++ )
|
|
{
|
|
if ( Matcher_NamesMatch( name, STRING(m_ResponseContexts[i].m_iszName) ) )
|
|
{
|
|
if (value == NULL)
|
|
return true;
|
|
else
|
|
return Matcher_Match(STRING(m_ResponseContexts[i].m_iszValue), value);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Searches entity for named context string and/or value.
|
|
// Intended to be called by entities rather than the conventional response system.
|
|
// Input : *name - Context name.
|
|
// *value - Context value. (optional)
|
|
// Output : bool
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::HasContext( string_t name, string_t value ) const
|
|
{
|
|
int c = m_ResponseContexts.Count();
|
|
for ( int i = 0; i < c; i++ )
|
|
{
|
|
if ( name == m_ResponseContexts[i].m_iszName )
|
|
{
|
|
if (value == NULL_STRING)
|
|
return true;
|
|
else
|
|
return value == m_ResponseContexts[i].m_iszValue;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Searches entity for named context string and/or value.
|
|
// Intended to be called by entities rather than the conventional response system.
|
|
// Input : *nameandvalue - Context name and value.
|
|
// Output : bool
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::HasContext( const char *nameandvalue ) const
|
|
{
|
|
char key[ 128 ];
|
|
char value[ 128 ];
|
|
|
|
const char *p = nameandvalue;
|
|
while ( p )
|
|
{
|
|
#ifdef NEW_RESPONSE_SYSTEM
|
|
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), NULL, nameandvalue );
|
|
#else
|
|
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), NULL );
|
|
#endif
|
|
|
|
return HasContext( key, value );
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : index -
|
|
// Output : const char
|
|
//-----------------------------------------------------------------------------
|
|
const char *CBaseEntity::GetContextValue( const char *contextName ) const
|
|
{
|
|
int idx = FindContextByName( contextName );
|
|
if ( idx == -1 )
|
|
return "";
|
|
|
|
return m_ResponseContexts[ idx ].m_iszValue.ToCStr();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
float CBaseEntity::GetContextExpireTime( const char *name )
|
|
{
|
|
int idx = FindContextByName( name );
|
|
if ( idx == -1 )
|
|
return 0.0f;
|
|
|
|
return m_ResponseContexts[ idx ].m_fExpirationTime;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Internal method or removing contexts and can remove multiple contexts in one call
|
|
// Input : *contextName -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::RemoveContext( const char *contextName )
|
|
{
|
|
char key[ 128 ];
|
|
char value[ 128 ];
|
|
float duration;
|
|
|
|
const char *p = contextName;
|
|
while ( p )
|
|
{
|
|
duration = 0.0f;
|
|
#ifdef NEW_RESPONSE_SYSTEM
|
|
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), &duration, contextName );
|
|
#else
|
|
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), &duration );
|
|
#endif
|
|
if ( duration )
|
|
{
|
|
duration += gpGlobals->curtime;
|
|
}
|
|
|
|
int iIndex = FindContextByName( key );
|
|
if ( iIndex != -1 )
|
|
{
|
|
m_ResponseContexts.Remove( iIndex );
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : inputdata -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputAddContext( inputdata_t& inputdata )
|
|
{
|
|
const char *contextName = inputdata.value.String();
|
|
AddContext( contextName );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: User inputs. These fire the corresponding user outputs, and are
|
|
// a means of forwarding messages through !activator to a target known
|
|
// known by !activator but not by the targetting entity.
|
|
//
|
|
// For example, say you have three identical trains, following the same
|
|
// path. Each train has a sprite in hierarchy with it that needs to
|
|
// toggle on/off as it passes each path_track. You would hook each train's
|
|
// OnUser1 output to it's sprite's Toggle input, then connect each path_track's
|
|
// OnPass output to !activator's FireUser1 input.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputFireUser1( inputdata_t& inputdata )
|
|
{
|
|
m_OnUser1.FireOutput( inputdata.pActivator, this );
|
|
}
|
|
|
|
|
|
void CBaseEntity::InputFireUser2( inputdata_t& inputdata )
|
|
{
|
|
m_OnUser2.FireOutput( inputdata.pActivator, this );
|
|
}
|
|
|
|
|
|
void CBaseEntity::InputFireUser3( inputdata_t& inputdata )
|
|
{
|
|
m_OnUser3.FireOutput( inputdata.pActivator, this );
|
|
}
|
|
|
|
|
|
void CBaseEntity::InputFireUser4( inputdata_t& inputdata )
|
|
{
|
|
m_OnUser4.FireOutput( inputdata.pActivator, this );
|
|
}
|
|
|
|
|
|
#ifdef MAPBASE
|
|
void CBaseEntity::InputPassUser1( inputdata_t& inputdata )
|
|
{
|
|
m_OutUser1.Set( inputdata.value, inputdata.pActivator, this );
|
|
}
|
|
|
|
void CBaseEntity::InputPassUser2( inputdata_t& inputdata )
|
|
{
|
|
m_OutUser2.Set( inputdata.value, inputdata.pActivator, this );
|
|
}
|
|
|
|
void CBaseEntity::InputPassUser3( inputdata_t& inputdata )
|
|
{
|
|
m_OutUser3.Set( inputdata.value, inputdata.pActivator, this );
|
|
}
|
|
|
|
void CBaseEntity::InputPassUser4( inputdata_t& inputdata )
|
|
{
|
|
m_OutUser4.Set( inputdata.value, inputdata.pActivator, this );
|
|
}
|
|
|
|
|
|
void CBaseEntity::InputFireRandomUser( inputdata_t& inputdata )
|
|
{
|
|
switch (RandomInt(1, 4))
|
|
{
|
|
case 1: m_OnUser1.FireOutput( inputdata.pActivator, this ); break;
|
|
case 2: m_OnUser2.FireOutput( inputdata.pActivator, this ); break;
|
|
case 3: m_OnUser3.FireOutput( inputdata.pActivator, this ); break;
|
|
case 4: m_OnUser4.FireOutput( inputdata.pActivator, this ); break;
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::InputPassRandomUser( inputdata_t& inputdata )
|
|
{
|
|
switch (RandomInt(1, 4))
|
|
{
|
|
case 1: m_OutUser1.Set( inputdata.value, inputdata.pActivator, this ); break;
|
|
case 2: m_OutUser2.Set( inputdata.value, inputdata.pActivator, this ); break;
|
|
case 3: m_OutUser3.Set( inputdata.value, inputdata.pActivator, this ); break;
|
|
case 4: m_OutUser4.Set( inputdata.value, inputdata.pActivator, this ); break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef MAPBASE
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets the entity's targetname.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetEntityName( inputdata_t& inputdata )
|
|
{
|
|
SetName( inputdata.value.StringID() );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets the generic target field.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetTarget( inputdata_t& inputdata )
|
|
{
|
|
m_target = inputdata.value.StringID();
|
|
Activate();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets our owner entity.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetOwnerEntity( inputdata_t& inputdata )
|
|
{
|
|
SetOwnerEntity(inputdata.value.Entity());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Input handler for adding to the entity's health.
|
|
// Input : Integer health points to add.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputAddHealth( inputdata_t &inputdata )
|
|
{
|
|
TakeHealth( abs(inputdata.value.Int()), DMG_GENERIC );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Input handler for removing health from the entity.
|
|
// Input : Integer health points to remove.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputRemoveHealth( inputdata_t &inputdata )
|
|
{
|
|
TakeDamage( CTakeDamageInfo( this, this, abs(inputdata.value.Int()), DMG_GENERIC ) );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetHealth( inputdata_t &inputdata )
|
|
{
|
|
int iNewHealth = inputdata.value.Int();
|
|
int iDelta = abs(GetHealth() - iNewHealth);
|
|
if ( iNewHealth > GetHealth() )
|
|
{
|
|
TakeHealth( iDelta, DMG_GENERIC );
|
|
}
|
|
else if ( iNewHealth < GetHealth() )
|
|
{
|
|
TakeDamage( CTakeDamageInfo( this, this, iDelta, DMG_GENERIC ) );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetMaxHealth( inputdata_t &inputdata )
|
|
{
|
|
int iNewMaxHealth = inputdata.value.Int();
|
|
SetMaxHealth(iNewMaxHealth);
|
|
|
|
if (GetHealth() > iNewMaxHealth)
|
|
{
|
|
SetHealth(iNewMaxHealth);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Forces the named output to fire.
|
|
// In addition to the output itself, parameter may include !activator, !caller, and what to pass.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputFireOutput( inputdata_t& inputdata )
|
|
{
|
|
char sParameter[MAX_PATH];
|
|
Q_strncpy( sParameter, inputdata.value.String(), sizeof(sParameter) );
|
|
if ( sParameter )
|
|
{
|
|
int iter = 0;
|
|
char *data[5] = {sParameter};
|
|
char *sToken = strtok( sParameter, ":" );
|
|
while ( sToken && iter < 5 )
|
|
{
|
|
data[iter] = sToken;
|
|
iter++;
|
|
sToken = strtok( NULL, ":" );
|
|
}
|
|
|
|
//DevMsg("data[0]: %s\ndata[1]: %s\ndata[2]: %s\ndata[3]: %s\ndata[4]: %s\n", data[0], data[1], data[2], data[3], data[4]);
|
|
|
|
// Format: <output name>:<activator>:<caller>:<parameter>:<delay>
|
|
//
|
|
// data[0] = Output Name
|
|
// data[1] = Activator
|
|
// data[2] = Caller
|
|
// data[3] = Parameter
|
|
// data[4] = Delay
|
|
//
|
|
CBaseEntity *pActivator = inputdata.pActivator;
|
|
if (data[1])
|
|
pActivator = gEntList.FindEntityByName(NULL, data[1], this, inputdata.pActivator, inputdata.pCaller);
|
|
|
|
CBaseEntity *pCaller = this;
|
|
if (data[2])
|
|
pCaller = gEntList.FindEntityByName(NULL, data[2], this, inputdata.pActivator, inputdata.pCaller);
|
|
|
|
variant_t parameter;
|
|
if (data[3])
|
|
{
|
|
parameter.SetString(MAKE_STRING(data[3]));
|
|
}
|
|
|
|
float flDelay = 0.0f;
|
|
if (data[4])
|
|
flDelay = atof(data[4]);
|
|
|
|
FireNamedOutput(data[0], parameter, pActivator, pCaller, flDelay);
|
|
//Msg("Output Name: %s, Activator: %s, Caller: %s, Data: %s, Delay: %f\n", data[0], pActivator->GetDebugName(), pCaller->GetDebugName(), parameter.String(), flDelay);
|
|
}
|
|
else
|
|
{
|
|
Warning("FireOutput input fired with bad parameter. Format: <output name>:<activator>:<caller>:<parameter>:<delay>\n");
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Removes all outputs of the specified name.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputRemoveOutput( inputdata_t& inputdata )
|
|
{
|
|
const char *szOutput = inputdata.value.String();
|
|
datamap_t *dmap = GetDataDescMap();
|
|
while ( dmap )
|
|
{
|
|
int fields = dmap->dataNumFields;
|
|
for ( int i = 0; i < fields; i++ )
|
|
{
|
|
typedescription_t *dataDesc = &dmap->dataDesc[i];
|
|
if ( ( dataDesc->fieldType == FIELD_CUSTOM ) && ( dataDesc->flags & FTYPEDESC_OUTPUT ) )
|
|
{
|
|
// If our names match, remove
|
|
if (Matcher_NamesMatch(szOutput, dataDesc->externalName))
|
|
{
|
|
CBaseEntityOutput *pOutput = (CBaseEntityOutput *)((int)this + (int)dataDesc->fieldOffset[0]);
|
|
pOutput->DeleteAllElements();
|
|
}
|
|
}
|
|
}
|
|
|
|
dmap = dmap->baseMap;
|
|
}
|
|
}
|
|
|
|
// Find a way to implement this
|
|
/*
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Cancels all I/O events of a specific output.
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::InputCancelOutput( inputdata_t &inputdata )
|
|
{
|
|
|
|
}
|
|
*/
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Replaces all outputs of the specified name.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputReplaceOutput( inputdata_t& inputdata )
|
|
{
|
|
char sParameter[128];
|
|
Q_strncpy( sParameter, inputdata.value.String(), sizeof(sParameter) );
|
|
if (!sParameter)
|
|
return;
|
|
|
|
int iter = 0;
|
|
char *data[2];
|
|
char *sToken = strtok( sParameter, ": " );
|
|
while ( sToken && iter < 2 )
|
|
{
|
|
data[iter] = sToken;
|
|
iter++;
|
|
sToken = strtok( NULL, ": " );
|
|
}
|
|
|
|
const char *szOutput = data[0];
|
|
const char *szNewOutput = data[1];
|
|
if (!szOutput || !szNewOutput)
|
|
{
|
|
Warning("ReplaceOutput input fired with bad parameter. Format: <old output name>:<new output name>\n");
|
|
return;
|
|
}
|
|
|
|
int iOutputsReplaced = 0;
|
|
datamap_t *dmap = GetDataDescMap();
|
|
while ( dmap )
|
|
{
|
|
int fields = dmap->dataNumFields;
|
|
for ( int i = 0; i < fields; i++ )
|
|
{
|
|
typedescription_t *dataDesc = &dmap->dataDesc[i];
|
|
if ( ( dataDesc->fieldType == FIELD_CUSTOM ) && ( dataDesc->flags & FTYPEDESC_OUTPUT ) )
|
|
{
|
|
// If our names match, replace
|
|
if (Matcher_NamesMatch(szOutput, dataDesc->externalName))
|
|
{
|
|
CBaseEntityOutput *pOutput = (CBaseEntityOutput *)((int)this + (int)dataDesc->fieldOffset[0]);
|
|
const char *szTarget;
|
|
const char *szInputName;
|
|
const char *szParam;
|
|
float flDelay;
|
|
int iNumTimes;
|
|
char szData[256];
|
|
for ( CEventAction *ev = pOutput->GetActionList(); ev != NULL; ev = ev->m_pNext )
|
|
{
|
|
// This is the only way I think we could do this. Accomplishes the job more or less anyway
|
|
szTarget = STRING(ev->m_iTarget);
|
|
szInputName = STRING(ev->m_iTargetInput);
|
|
szParam = ev->m_iParameter == NULL_STRING ? "" : STRING(ev->m_iParameter);
|
|
flDelay = ev->m_flDelay;
|
|
iNumTimes = ev->m_nTimesToFire;
|
|
Q_snprintf(szData, sizeof(szData), "%s,%s,%s,%f,%i", szTarget, szInputName, szParam, flDelay, iNumTimes);
|
|
|
|
KeyValue(szNewOutput, szData);
|
|
|
|
DevMsg("ReplaceOutput: %s %s\n", szNewOutput, szData);
|
|
|
|
iOutputsReplaced++;
|
|
}
|
|
pOutput->DeleteAllElements();
|
|
}
|
|
}
|
|
}
|
|
|
|
dmap = dmap->baseMap;
|
|
}
|
|
|
|
if (iOutputsReplaced == 0)
|
|
{
|
|
Warning("ReplaceOutput unable to find %s on %s\n", szOutput, GetDebugName());
|
|
}
|
|
else
|
|
{
|
|
DevMsg("Replaced %i instances of %s with %s on %s\n", iOutputsReplaced, szOutput, szNewOutput, GetDebugName());
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Forces the named input to fire...what?
|
|
// Inputception...or is it just "Inception"? Whatever.
|
|
// True inception would be using this input to fire AcceptInput.
|
|
// (it would probably crash, I haven't tested it)
|
|
//
|
|
// In addition to the input itself, parameter may include !activator, !caller, and what to pass.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputAcceptInput( inputdata_t& inputdata )
|
|
{
|
|
char sParameter[MAX_PATH];
|
|
Q_strncpy( sParameter, inputdata.value.String(), sizeof(sParameter) );
|
|
if ( sParameter )
|
|
{
|
|
int iter = 0;
|
|
char *data[5] = {sParameter};
|
|
char *sToken = strtok( sParameter, ":" );
|
|
while ( sToken && iter < 5 )
|
|
{
|
|
data[iter] = sToken;
|
|
iter++;
|
|
sToken = strtok( NULL, ":" );
|
|
}
|
|
|
|
//DevMsg("data[0]: %s\ndata[1]: %s\ndata[2]: %s\ndata[3]: %s\ndata[4]: %s\n", data[0], data[1], data[2], data[3], data[4]);
|
|
|
|
// Format: <input name>:<parameter>:<activator>:<caller>:<output ID>
|
|
//
|
|
// data[0] = Input Name
|
|
// data[1] = Parameter
|
|
// data[2] = Activator
|
|
// data[3] = Caller
|
|
// data[4] = Output ID
|
|
//
|
|
variant_t parameter;
|
|
if (data[1])
|
|
{
|
|
parameter.SetString(MAKE_STRING(data[1]));
|
|
}
|
|
|
|
CBaseEntity *pActivator = inputdata.pActivator;
|
|
if (data[2])
|
|
pActivator = gEntList.FindEntityByName(NULL, data[2], this, inputdata.pActivator, inputdata.pCaller);
|
|
|
|
CBaseEntity *pCaller = this;
|
|
if (data[3])
|
|
pCaller = gEntList.FindEntityByName(NULL, data[3], this, inputdata.pActivator, inputdata.pCaller);
|
|
|
|
int iOutputID = -1;
|
|
if (data[4])
|
|
iOutputID = atoi(data[4]);
|
|
|
|
AcceptInput(data[0], pActivator, pCaller, parameter, iOutputID);
|
|
Msg("Input Name: %s, Activator: %s, Caller: %s, Data: %s, Output ID: %i\n", data[0], pActivator ? pActivator->GetDebugName() : "None", pCaller ? pCaller->GetDebugName() : "None", parameter.String(), iOutputID);
|
|
}
|
|
else
|
|
{
|
|
Warning("AcceptInput input fired with bad parameter. Format: <input name>:<parameter>:<activator>:<caller>:<output ID>\n");
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Cancels any I/O events in the queue that were fired by this entity.
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::InputCancelPending( inputdata_t &inputdata )
|
|
{
|
|
g_EventQueue.CancelEvents( this );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Frees all of our children, entities parented to this entity.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputFreeChildren( inputdata_t& inputdata )
|
|
{
|
|
UnlinkAllChildren( this );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets our origin.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetLocalOrigin( inputdata_t& inputdata )
|
|
{
|
|
Vector vec;
|
|
inputdata.value.Vector3D(vec);
|
|
SetLocalOrigin(vec);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets our angles.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetLocalAngles( inputdata_t& inputdata )
|
|
{
|
|
QAngle ang;
|
|
inputdata.value.Angle3D(ang);
|
|
SetLocalAngles(ang);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets our origin.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetAbsOrigin( inputdata_t& inputdata )
|
|
{
|
|
Vector vec;
|
|
inputdata.value.Vector3D(vec);
|
|
SetAbsOrigin(vec);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets our angles.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetAbsAngles( inputdata_t& inputdata )
|
|
{
|
|
QAngle ang;
|
|
inputdata.value.Angle3D(ang);
|
|
SetAbsAngles(ang);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets our velocity.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetLocalVelocity( inputdata_t& inputdata )
|
|
{
|
|
Vector vec;
|
|
inputdata.value.Vector3D(vec);
|
|
SetLocalVelocity(vec);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets our angular velocity.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetLocalAngularVelocity( inputdata_t& inputdata )
|
|
{
|
|
Vector vec;
|
|
inputdata.value.Vector3D(vec);
|
|
SetLocalAngularVelocity(QAngle(vec.x, vec.y, vec.z));
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Adds spawn flags.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputAddSpawnFlags( inputdata_t& inputdata )
|
|
{
|
|
AddSpawnFlags(inputdata.value.Int());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Removes spawn flags.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputRemoveSpawnFlags( inputdata_t& inputdata )
|
|
{
|
|
RemoveSpawnFlags(inputdata.value.Int());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets our render mode.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetRenderMode( inputdata_t& inputdata )
|
|
{
|
|
SetRenderMode((RenderMode_t)inputdata.value.Int());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets our render FX.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetRenderFX( inputdata_t& inputdata )
|
|
{
|
|
m_nRenderFX = inputdata.value.Int();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets our view hide flags.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetViewHideFlags( inputdata_t& inputdata )
|
|
{
|
|
m_iViewHideFlags = inputdata.value.Int();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Adds effects.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputAddEffects( inputdata_t& inputdata )
|
|
{
|
|
AddEffects(inputdata.value.Int());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Removes effects.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputRemoveEffects( inputdata_t& inputdata )
|
|
{
|
|
RemoveEffects(inputdata.value.Int());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Shortcut for removing nodraw.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputDrawEntity( inputdata_t& inputdata )
|
|
{
|
|
RemoveEffects(EF_NODRAW);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Shortcut to adding nodraw.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputUndrawEntity( inputdata_t& inputdata )
|
|
{
|
|
AddEffects(EF_NODRAW);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Inspired by the Portal 2 input of the same name.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputEnableReceivingFlashlight( inputdata_t& inputdata )
|
|
{
|
|
m_bDisableFlashlight = false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Inspired by the Portal 2 input of the same name.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputDisableReceivingFlashlight( inputdata_t& inputdata )
|
|
{
|
|
m_bDisableFlashlight = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Adds eflags.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputAddEFlags( inputdata_t& inputdata )
|
|
{
|
|
AddEFlags(inputdata.value.Int());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Removes eflags.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputRemoveEFlags( inputdata_t& inputdata )
|
|
{
|
|
RemoveEFlags(inputdata.value.Int());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Adds solid flags.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputAddSolidFlags( inputdata_t& inputdata )
|
|
{
|
|
AddSolidFlags(inputdata.value.Int());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Removes solid flags.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputRemoveSolidFlags( inputdata_t& inputdata )
|
|
{
|
|
RemoveSolidFlags(inputdata.value.Int());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets the movetype.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetMoveType( inputdata_t& inputdata )
|
|
{
|
|
SetMoveType((MoveType_t)inputdata.value.Int());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets the collision group.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetCollisionGroup( inputdata_t& inputdata )
|
|
{
|
|
SetCollisionGroup(inputdata.value.Int());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Touch touch :)
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputTouch( inputdata_t& inputdata )
|
|
{
|
|
if (inputdata.value.Entity())
|
|
Touch( inputdata.value.Entity() );
|
|
else
|
|
Warning( "%s InputTouch: Can't touch null entity", GetDebugName() );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Passes KilledNPC to our possibly more capable parents.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputKilledNPC( inputdata_t &inputdata )
|
|
{
|
|
// Don't get stuck in an endless loop
|
|
if (inputdata.value.Int() > 16)
|
|
return;
|
|
else
|
|
inputdata.value.SetInt(inputdata.value.Int() + 1);
|
|
|
|
if (GetOwnerEntity())
|
|
{
|
|
GetOwnerEntity()->AcceptInput("KilledNPC", inputdata.pActivator, inputdata.pCaller, inputdata.value, inputdata.nOutputID);
|
|
}
|
|
else if (HasPhysicsAttacker(4.0f))
|
|
{
|
|
HasPhysicsAttacker(4.0f)->AcceptInput("KilledNPC", inputdata.pActivator, inputdata.pCaller, inputdata.value, inputdata.nOutputID);
|
|
}
|
|
else if (GetMoveParent())
|
|
{
|
|
GetMoveParent()->AcceptInput("KilledNPC", inputdata.pActivator, inputdata.pCaller, inputdata.value, inputdata.nOutputID);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Remove if not visible by any players
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputKillIfNotVisible( inputdata_t& inputdata )
|
|
{
|
|
#ifdef MAPBASE_MP
|
|
// Go through each client and check if we're in their viewcone.
|
|
// If we're in someone's viewcone, return immediately.
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
|
|
if ( pPlayer && pPlayer->FInViewCone( this ) )
|
|
return;
|
|
}
|
|
#else
|
|
CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
|
|
if ( !pPlayer || !pPlayer->FInViewCone( this ) )
|
|
#endif
|
|
{
|
|
m_OnKilled.FireOutput(inputdata.pActivator, this);
|
|
UTIL_Remove(this);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Remove when not visible by any players
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputKillWhenNotVisible( inputdata_t& inputdata )
|
|
{
|
|
SetContextThink( &CBaseEntity::SUB_RemoveWhenNotVisible, gpGlobals->curtime + inputdata.value.Float(), "SUB_RemoveWhenNotVisible" );
|
|
//SetRenderColorA( 255 );
|
|
//m_nRenderMode = kRenderNormal;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Stop thinking
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetThinkNull( inputdata_t& inputdata )
|
|
{
|
|
const char *szContext = inputdata.value.String();
|
|
if (szContext && szContext[0] != '\0')
|
|
{
|
|
SetContextThink( NULL, TICK_NEVER_THINK, szContext );
|
|
}
|
|
else
|
|
{
|
|
SetThink( NULL );
|
|
SetNextThink( TICK_NEVER_THINK );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
//---------------------------------------------------------
|
|
// Use the string as the filename of a script file
|
|
// that should be loaded from disk, compiled, and run.
|
|
//---------------------------------------------------------
|
|
void CBaseEntity::InputRunScriptFile(inputdata_t& inputdata)
|
|
{
|
|
RunScriptFile(inputdata.value.String());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// Send the string to the VM as source code and execute it
|
|
//---------------------------------------------------------
|
|
void CBaseEntity::InputRunScript(inputdata_t& inputdata)
|
|
{
|
|
RunScript(inputdata.value.String(), "InputRunScript");
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// Make an explicit function call.
|
|
//---------------------------------------------------------
|
|
void CBaseEntity::InputCallScriptFunction(inputdata_t& inputdata)
|
|
{
|
|
CallScriptFunction(inputdata.value.String(), NULL);
|
|
}
|
|
|
|
#ifdef MAPBASE_VSCRIPT
|
|
//---------------------------------------------------------
|
|
// Send the string to the VM as source code and execute it
|
|
//---------------------------------------------------------
|
|
void CBaseEntity::InputRunScriptQuotable(inputdata_t& inputdata)
|
|
{
|
|
char szQuotableCode[1024];
|
|
if (V_StrSubst( inputdata.value.String(), "''", "\"", szQuotableCode, sizeof( szQuotableCode ), false ))
|
|
{
|
|
RunScript( szQuotableCode, "InputRunScriptQuotable" );
|
|
}
|
|
else
|
|
{
|
|
RunScript( inputdata.value.String(), "InputRunScriptQuotable" );
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// Clear this entity's script scope
|
|
//---------------------------------------------------------
|
|
void CBaseEntity::InputClearScriptScope(inputdata_t& inputdata)
|
|
{
|
|
m_ScriptScope.Term();
|
|
}
|
|
#endif
|
|
|
|
// #define VMPROFILE // define to profile vscript calls
|
|
|
|
#ifdef VMPROFILE
|
|
float g_debugCumulativeTime = 0.0;
|
|
float g_debugCounter = 0;
|
|
|
|
#define START_VMPROFILE float debugStartTime = Plat_FloatTime();
|
|
#define UPDATE_VMPROFILE \
|
|
g_debugCumulativeTime += Plat_FloatTime() - debugStartTime; \
|
|
g_debugCounter++; \
|
|
if ( g_debugCounter >= 500 ) \
|
|
{ \
|
|
DevMsg("***VSCRIPT PROFILE***: %s %s: %6.4f milliseconds\n", "500 vscript function calls", "", g_debugCumulativeTime*1000.0 ); \
|
|
g_debugCounter = 0; \
|
|
g_debugCumulativeTime = 0.0; \
|
|
} \
|
|
|
|
#else
|
|
|
|
#define START_VMPROFILE
|
|
#define UPDATE_VMPROFILE
|
|
|
|
#endif // VMPROFILE
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Returns true if the function was located and called. false otherwise.
|
|
// NOTE: Assumes the function takes no parameters at the moment.
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::CallScriptFunction(const char* pFunctionName, ScriptVariant_t* pFunctionReturn)
|
|
{
|
|
START_VMPROFILE
|
|
|
|
if (!ValidateScriptScope())
|
|
{
|
|
DevMsg("\n***\nFAILED to create private ScriptScope. ABORTING script\n***\n");
|
|
return false;
|
|
}
|
|
|
|
|
|
HSCRIPT hFunc = m_ScriptScope.LookupFunction(pFunctionName);
|
|
|
|
if (hFunc)
|
|
{
|
|
m_ScriptScope.Call(hFunc, pFunctionReturn);
|
|
m_ScriptScope.ReleaseFunction(hFunc);
|
|
|
|
UPDATE_VMPROFILE
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#ifdef MAPBASE_VSCRIPT
|
|
//-----------------------------------------------------------------------------
|
|
// Gets a function handle
|
|
//-----------------------------------------------------------------------------
|
|
HSCRIPT CBaseEntity::LookupScriptFunction(const char* pFunctionName)
|
|
{
|
|
START_VMPROFILE
|
|
|
|
if (!m_ScriptScope.IsInitialized())
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
return m_ScriptScope.LookupFunction(pFunctionName);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Calls and releases a function handle (ASSUMES SCRIPT SCOPE AND FUNCTION ARE VALID!)
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::CallScriptFunctionHandle(HSCRIPT hFunc, ScriptVariant_t* pFunctionReturn)
|
|
{
|
|
m_ScriptScope.Call(hFunc, pFunctionReturn);
|
|
m_ScriptScope.ReleaseFunction(hFunc);
|
|
|
|
UPDATE_VMPROFILE
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::ConnectOutputToScript(const char* pszOutput, const char* pszScriptFunc)
|
|
{
|
|
CBaseEntityOutput* pOutput = FindNamedOutput(pszOutput);
|
|
if (!pOutput)
|
|
{
|
|
DevMsg(2, "Script failed to find output \"%s\"\n", pszOutput);
|
|
return;
|
|
}
|
|
|
|
string_t iszSelf = AllocPooledString("!self"); // @TODO: cache this [4/25/2008 tom]
|
|
CEventAction* pAction = pOutput->GetActionList();
|
|
while (pAction)
|
|
{
|
|
if (pAction->m_iTarget == iszSelf &&
|
|
pAction->m_flDelay == 0 &&
|
|
pAction->m_nTimesToFire == EVENT_FIRE_ALWAYS &&
|
|
V_strcmp(STRING(pAction->m_iTargetInput), "CallScriptFunction") == 0 &&
|
|
V_strcmp(STRING(pAction->m_iParameter), pszScriptFunc) == 0)
|
|
{
|
|
return;
|
|
}
|
|
pAction = pAction->m_pNext;
|
|
}
|
|
|
|
pAction = new CEventAction(NULL);
|
|
pAction->m_iTarget = iszSelf;
|
|
pAction->m_iTargetInput = AllocPooledString("CallScriptFunction");
|
|
pAction->m_iParameter = AllocPooledString(pszScriptFunc);
|
|
pOutput->AddEventAction(pAction);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::DisconnectOutputFromScript(const char* pszOutput, const char* pszScriptFunc)
|
|
{
|
|
CBaseEntityOutput* pOutput = FindNamedOutput(pszOutput);
|
|
if (!pOutput)
|
|
{
|
|
DevMsg(2, "Script failed to find output \"%s\"\n", pszOutput);
|
|
return;
|
|
}
|
|
|
|
string_t iszSelf = AllocPooledString("!self"); // @TODO: cache this [4/25/2008 tom]
|
|
CEventAction* pAction = pOutput->GetActionList();
|
|
while (pAction)
|
|
{
|
|
if (pAction->m_iTarget == iszSelf &&
|
|
pAction->m_flDelay == 0 &&
|
|
pAction->m_nTimesToFire == EVENT_FIRE_ALWAYS &&
|
|
V_strcmp(STRING(pAction->m_iTargetInput), "CallScriptFunction") == 0 &&
|
|
V_strcmp(STRING(pAction->m_iParameter), pszScriptFunc) == 0)
|
|
{
|
|
pOutput->RemoveEventAction(pAction);
|
|
delete pAction;
|
|
return;
|
|
}
|
|
pAction = pAction->m_pNext;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::ScriptThink(void)
|
|
{
|
|
ScriptVariant_t varThinkRetVal;
|
|
if (CallScriptFunction(m_iszScriptThinkFunction.ToCStr(), &varThinkRetVal))
|
|
{
|
|
float flThinkFrequency = 0.0f;
|
|
if (!varThinkRetVal.AssignTo(&flThinkFrequency))
|
|
{
|
|
// use default think interval if script think function doesn't provide one
|
|
flThinkFrequency = sv_script_think_interval.GetFloat();
|
|
}
|
|
|
|
SetNextThink( gpGlobals->curtime + flThinkFrequency, "ScriptThink" );
|
|
}
|
|
else
|
|
{
|
|
DevWarning("%s FAILED to call script think function %s!\n", GetDebugName(), STRING(m_iszScriptThinkFunction));
|
|
}
|
|
}
|
|
|
|
#ifdef MAPBASE_VSCRIPT
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::ScriptSetThinkFunction( const char *szFunc, float flTime )
|
|
{
|
|
// Empty string stops thinking
|
|
if (!szFunc || szFunc[0] == '\0')
|
|
{
|
|
ScriptStopThinkFunction();
|
|
}
|
|
else
|
|
{
|
|
m_iszScriptThinkFunction = AllocPooledString(szFunc);
|
|
flTime = max( 0, flTime );
|
|
SetContextThink( &CBaseEntity::ScriptThink, gpGlobals->curtime + flTime, "ScriptThink" );
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::ScriptStopThinkFunction()
|
|
{
|
|
m_iszScriptThinkFunction = NULL_STRING;
|
|
SetContextThink( NULL, TICK_NEVER_THINK, "ScriptThink" );
|
|
}
|
|
#endif // MAPBASE_VSCRIPT
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
const char* CBaseEntity::GetScriptId()
|
|
{
|
|
#ifdef MAPBASE_VSCRIPT
|
|
return STRING(m_iszScriptId);
|
|
#else
|
|
return STRING(m_iszScriptThinkFunction);
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Recreate the old behaviour of GetScriptId under a new function
|
|
//-----------------------------------------------------------------------------
|
|
// const char* CBaseEntity::GetScriptThinkFunction()
|
|
// {
|
|
// return STRING(m_iszScriptThinkFunction);
|
|
// }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
HSCRIPT CBaseEntity::GetScriptScope()
|
|
{
|
|
return m_ScriptScope;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
HSCRIPT CBaseEntity::ScriptGetMoveParent(void)
|
|
{
|
|
return ToHScript(GetMoveParent());
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
HSCRIPT CBaseEntity::ScriptGetRootMoveParent()
|
|
{
|
|
return ToHScript(GetRootMoveParent());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
HSCRIPT CBaseEntity::ScriptFirstMoveChild(void)
|
|
{
|
|
return ToHScript(FirstMoveChild());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
HSCRIPT CBaseEntity::ScriptNextMovePeer(void)
|
|
{
|
|
return ToHScript(NextMovePeer());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Load, compile, and run a script file from disk.
|
|
// Input : *pScriptFile - The filename of the script file.
|
|
// bUseRootScope - If true, runs this script in the root scope, not
|
|
// in this entity's private scope.
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::RunScriptFile(const char* pScriptFile, bool bUseRootScope)
|
|
{
|
|
if (!ValidateScriptScope())
|
|
{
|
|
DevMsg("\n***\nFAILED to create private ScriptScope. ABORTING script\n***\n");
|
|
return false;
|
|
}
|
|
|
|
if (bUseRootScope)
|
|
{
|
|
return VScriptRunScript(pScriptFile);
|
|
}
|
|
else
|
|
{
|
|
return VScriptRunScript(pScriptFile, m_ScriptScope, true);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Compile and execute a discrete string of script source code
|
|
// Input : *pScriptText - A string containing script code to compile and run
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::RunScript(const char* pScriptText, const char* pDebugFilename)
|
|
{
|
|
if (!ValidateScriptScope())
|
|
{
|
|
DevMsg("\n***\nFAILED to create private ScriptScope. ABORTING script\n***\n");
|
|
return false;
|
|
}
|
|
|
|
if (m_ScriptScope.Run(pScriptText, pDebugFilename) == SCRIPT_ERROR)
|
|
{
|
|
DevWarning(" Entity %s encountered an error in RunScript()\n", GetDebugName());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *contextName -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::AddContext( const char *contextName )
|
|
{
|
|
char key[ 128 ];
|
|
char value[ 128 ];
|
|
float duration = 0.0f;
|
|
|
|
const char *p = contextName;
|
|
while ( p )
|
|
{
|
|
duration = 0.0f;
|
|
#ifdef NEW_RESPONSE_SYSTEM
|
|
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), &duration, contextName );
|
|
#else
|
|
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), &duration );
|
|
#endif
|
|
if ( duration )
|
|
{
|
|
duration += gpGlobals->curtime;
|
|
}
|
|
|
|
AddContext( key, value, duration );
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::AddContext( const char *name, const char *value, float duration )
|
|
{
|
|
int iIndex = FindContextByName( name );
|
|
if ( iIndex != -1 )
|
|
{
|
|
// Set the existing context to the new value
|
|
|
|
#ifdef NEW_RESPONSE_SYSTEM
|
|
char buf[64];
|
|
if ( RR::CApplyContextOperator::FindOperator( value )->Apply(
|
|
m_ResponseContexts[iIndex].m_iszValue.ToCStr(), value, buf, sizeof(buf) ) )
|
|
{
|
|
m_ResponseContexts[iIndex].m_iszValue = AllocPooledString( buf );
|
|
}
|
|
else
|
|
{
|
|
Warning( "RR: could not apply operator %s to prior value %s\n",
|
|
value, m_ResponseContexts[iIndex].m_iszValue.ToCStr() );
|
|
m_ResponseContexts[iIndex].m_iszValue = AllocPooledString( value );
|
|
}
|
|
#else
|
|
m_ResponseContexts[iIndex].m_iszValue = AllocPooledString( value );
|
|
#endif
|
|
m_ResponseContexts[iIndex].m_fExpirationTime = duration;
|
|
}
|
|
else
|
|
{
|
|
ResponseContext_t newContext;
|
|
newContext.m_iszName = AllocPooledString( name );
|
|
|
|
#ifdef NEW_RESPONSE_SYSTEM
|
|
char buf[64];
|
|
if ( RR::CApplyContextOperator::FindOperator( value )->Apply(
|
|
NULL, value, buf, sizeof(buf) ) )
|
|
{
|
|
newContext.m_iszValue = AllocPooledString( buf );
|
|
}
|
|
else
|
|
{
|
|
newContext.m_iszValue = AllocPooledString( value );
|
|
}
|
|
#else
|
|
newContext.m_iszValue = AllocPooledString( value );
|
|
#endif
|
|
|
|
newContext.m_fExpirationTime = duration;
|
|
|
|
m_ResponseContexts.AddToTail( newContext );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : inputdata -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputRemoveContext( inputdata_t& inputdata )
|
|
{
|
|
const char *contextName = inputdata.value.String();
|
|
int idx = FindContextByName( contextName );
|
|
if ( idx == -1 )
|
|
return;
|
|
|
|
m_ResponseContexts.Remove( idx );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : inputdata -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputClearContext( inputdata_t& inputdata )
|
|
{
|
|
m_ResponseContexts.RemoveAll();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : IResponseSystem
|
|
//-----------------------------------------------------------------------------
|
|
IResponseSystem *CBaseEntity::GetResponseSystem()
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : inputdata -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputDispatchResponse( inputdata_t& inputdata )
|
|
{
|
|
DispatchResponse( inputdata.value.String() );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputDisableShadow( inputdata_t &inputdata )
|
|
{
|
|
AddEffects( EF_NOSHADOW );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputEnableShadow( inputdata_t &inputdata )
|
|
{
|
|
RemoveEffects( EF_NOSHADOW );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: An input to add a new connection from this entity
|
|
// Input : &inputdata -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputAddOutput( inputdata_t &inputdata )
|
|
{
|
|
char sOutputName[MAX_PATH];
|
|
Q_strncpy( sOutputName, inputdata.value.String(), sizeof(sOutputName) );
|
|
char *sChar = strchr( sOutputName, ' ' );
|
|
if ( sChar )
|
|
{
|
|
*sChar = '\0';
|
|
// Now replace all the :'s in the string with ,'s.
|
|
// Has to be done this way because Hammer doesn't allow ,'s inside parameters.
|
|
char *sColon = strchr( sChar+1, ':' );
|
|
while ( sColon )
|
|
{
|
|
*sColon = ',';
|
|
sColon = strchr( sChar+1, ':' );
|
|
}
|
|
KeyValue( sOutputName, sChar+1 );
|
|
}
|
|
else
|
|
{
|
|
Warning("AddOutput input fired with bad string. Format: <output name> <targetname>,<inputname>,<parameter>,<delay>,<max times to fire (-1 == infinite)>\n");
|
|
}
|
|
}
|
|
|
|
#ifdef MAPBASE
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : &inputdata -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputChangeVariable( inputdata_t &inputdata )
|
|
{
|
|
const char *szKeyName = NULL;
|
|
const char *szValue = NULL;
|
|
|
|
char sOutputName[MAX_PATH];
|
|
Q_strncpy( sOutputName, inputdata.value.String(), sizeof(sOutputName) );
|
|
char *sChar = strchr( sOutputName, ' ' );
|
|
if ( sChar )
|
|
{
|
|
*sChar = '\0';
|
|
// Now replace all the :'s in the string with ,'s.
|
|
// Has to be done this way because Hammer doesn't allow ,'s inside parameters.
|
|
char *sColon = strchr( sChar+1, ':' );
|
|
while ( sColon )
|
|
{
|
|
*sColon = ',';
|
|
sColon = strchr( sChar+1, ':' );
|
|
}
|
|
|
|
szKeyName = sOutputName;
|
|
szValue = sChar + 1;
|
|
}
|
|
else
|
|
{
|
|
Warning("ChangeVariable input fired with bad string. Format: <output name> <targetname>,<inputname>,<parameter>,<delay>,<max times to fire (-1 == infinite)>\n");
|
|
return;
|
|
}
|
|
|
|
if (szKeyName == NULL)
|
|
return;
|
|
|
|
for ( datamap_t *dmap = GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap )
|
|
{
|
|
// search through all the readable fields in the data description, looking for a match
|
|
for ( int i = 0; i < dmap->dataNumFields; i++ )
|
|
{
|
|
if ( dmap->dataDesc[i].flags & (FTYPEDESC_SAVE | FTYPEDESC_KEY) )
|
|
{
|
|
if ( Matcher_NamesMatch(szKeyName, dmap->dataDesc[i].fieldName) )
|
|
{
|
|
// Copied from ::ParseKeyvalue...or technically logic_datadesc_accessor...
|
|
typedescription_t *pField = &dmap->dataDesc[i];
|
|
char *data = Datadesc_SetFieldString( szValue, this, pField, NULL );
|
|
|
|
if (!data)
|
|
{
|
|
Warning( "%s cannot set field of type %i.\n", GetDebugName(), dmap->dataDesc[i].fieldType );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *conceptName -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::DispatchResponse( const char *conceptName )
|
|
{
|
|
#ifdef NEW_RESPONSE_SYSTEM
|
|
#undef IResponseSystem
|
|
using namespace ResponseRules;
|
|
#endif
|
|
|
|
IResponseSystem *rs = GetResponseSystem();
|
|
if ( !rs )
|
|
return;
|
|
|
|
AI_CriteriaSet set;
|
|
// Always include the concept name
|
|
set.AppendCriteria( "concept", conceptName, CONCEPT_WEIGHT );
|
|
// Let NPC fill in most match criteria
|
|
ModifyOrAppendCriteria( set );
|
|
|
|
// Append local player criteria to set,too
|
|
CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
|
|
if( pPlayer )
|
|
pPlayer->ModifyOrAppendPlayerCriteria( set );
|
|
|
|
#ifdef MAPBASE
|
|
ReAppendContextCriteria( set );
|
|
#endif
|
|
|
|
// Now that we have a criteria set, ask for a suitable response
|
|
AI_Response result;
|
|
bool found = rs->FindBestResponse( set, result );
|
|
if ( !found )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Handle the response here...
|
|
char response[ 256 ];
|
|
result.GetResponse( response, sizeof( response ) );
|
|
#ifdef NEW_RESPONSE_SYSTEM
|
|
switch (result.GetType())
|
|
{
|
|
case ResponseRules::RESPONSE_SPEAK:
|
|
{
|
|
EmitSound(response);
|
|
}
|
|
break;
|
|
case ResponseRules::RESPONSE_SENTENCE:
|
|
{
|
|
int sentenceIndex = SENTENCEG_Lookup(response);
|
|
if (sentenceIndex == -1)
|
|
{
|
|
// sentence not found
|
|
break;
|
|
}
|
|
|
|
// FIXME: Get pitch from npc?
|
|
CPASAttenuationFilter filter(this);
|
|
CBaseEntity::EmitSentenceByIndex(filter, entindex(), CHAN_VOICE, sentenceIndex, 1, result.GetSoundLevel(), 0, PITCH_NORM);
|
|
}
|
|
break;
|
|
case ResponseRules::RESPONSE_SCENE:
|
|
{
|
|
// Try to fire scene w/o an actor
|
|
InstancedScriptedScene(NULL, response);
|
|
}
|
|
break;
|
|
case ResponseRules::RESPONSE_PRINT:
|
|
{
|
|
|
|
}
|
|
break;
|
|
case ResponseRules::RESPONSE_ENTITYIO:
|
|
{
|
|
CAI_Expresser::FireEntIOFromResponse(response, this);
|
|
break;
|
|
}
|
|
#ifdef MAPBASE_VSCRIPT
|
|
case ResponseRules::RESPONSE_VSCRIPT:
|
|
{
|
|
CAI_Expresser::RunScriptResponse( this, response, &set, false );
|
|
break;
|
|
}
|
|
case ResponseRules::RESPONSE_VSCRIPT_FILE:
|
|
{
|
|
CAI_Expresser::RunScriptResponse( this, response, &set, true );
|
|
break;
|
|
}
|
|
#endif
|
|
default:
|
|
// Don't know how to handle .vcds!!!
|
|
break;
|
|
}
|
|
#else
|
|
#ifdef MAPBASE
|
|
if (response[0] == '$')
|
|
{
|
|
const char *context = response + 1;
|
|
const char *replace = GetContextValue(context);
|
|
|
|
if (replace)
|
|
{
|
|
DevMsg("Replacing %s with %s...\n", response, replace);
|
|
Q_strncpy(response, replace, sizeof(response));
|
|
|
|
// Precache it now because it may not have been precached before
|
|
switch ( result.GetType() )
|
|
{
|
|
case RESPONSE_SPEAK:
|
|
{
|
|
PrecacheScriptSound( response );
|
|
}
|
|
break;
|
|
|
|
case RESPONSE_SCENE:
|
|
{
|
|
// TODO: Gender handling?
|
|
PrecacheInstancedScene( response );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
switch ( result.GetType() )
|
|
{
|
|
case RESPONSE_SPEAK:
|
|
{
|
|
EmitSound( response );
|
|
}
|
|
break;
|
|
case RESPONSE_SENTENCE:
|
|
{
|
|
#ifdef MAPBASE
|
|
if (response[0] != '!')
|
|
{
|
|
SENTENCEG_PlayRndSz( edict(), response, 1, result.GetSoundLevel(), 0, PITCH_NORM );
|
|
break;
|
|
}
|
|
#endif
|
|
int sentenceIndex = SENTENCEG_Lookup( response );
|
|
if( sentenceIndex == -1 )
|
|
{
|
|
// sentence not found
|
|
break;
|
|
}
|
|
|
|
// FIXME: Get pitch from npc?
|
|
CPASAttenuationFilter filter( this );
|
|
CBaseEntity::EmitSentenceByIndex( filter, entindex(), CHAN_VOICE, sentenceIndex, 1, result.GetSoundLevel(), 0, PITCH_NORM );
|
|
}
|
|
break;
|
|
case RESPONSE_SCENE:
|
|
{
|
|
#ifdef MAPBASE
|
|
// Most flexing actors that use scenes override DispatchResponse via CAI_Expresser in ai_speech.
|
|
// So, in order for non-actors to use scenes by themselves, they actually don't really use them at all.
|
|
// Most scenes that would be used as responses only have one sound, so we take the first sound in a scene and emit it manually.
|
|
//
|
|
// Of course, env_speaker uses scenes without using itself as an actor, but that overrides DispatchResponse in Mapbase
|
|
// with the original code intact. Hopefully no other entity uses this like that...
|
|
|
|
//if (!ClassMatches("env_speaker"))
|
|
{
|
|
// Expand gender string
|
|
GenderExpandString( response, response, sizeof( response ) );
|
|
|
|
// Trust that it's been precached
|
|
const char *pszSound = GetFirstSoundInScene(response);
|
|
EmitSound(pszSound);
|
|
}
|
|
//else
|
|
// InstancedScriptedScene(NULL, response);
|
|
#else
|
|
// Try to fire scene w/o an actor
|
|
InstancedScriptedScene( NULL, response );
|
|
#endif
|
|
}
|
|
break;
|
|
case RESPONSE_PRINT:
|
|
{
|
|
|
|
}
|
|
break;
|
|
default:
|
|
// Don't know how to handle .vcds!!!
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::DumpResponseCriteria( void )
|
|
{
|
|
Msg("----------------------------------------------\n");
|
|
Msg("RESPONSE CRITERIA FOR: %s (%s)\n", GetClassname(), GetDebugName() );
|
|
|
|
AI_CriteriaSet set;
|
|
// Let NPC fill in most match criteria
|
|
ModifyOrAppendCriteria( set );
|
|
|
|
// Append local player criteria to set,too
|
|
CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->ModifyOrAppendPlayerCriteria( set );
|
|
}
|
|
|
|
#ifdef MAPBASE
|
|
ReAppendContextCriteria( set );
|
|
#endif
|
|
|
|
// Now dump it all to console
|
|
set.Describe();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Show_Response_Criteria( const CCommand& args )
|
|
{
|
|
CBaseEntity *pEntity = NULL;
|
|
while ( (pEntity = GetNextCommandEntity( UTIL_GetCommandClient(), args[1], pEntity )) != NULL )
|
|
{
|
|
pEntity->DumpResponseCriteria();
|
|
}
|
|
}
|
|
static ConCommand ent_show_response_criteria("ent_show_response_criteria", CC_Ent_Show_Response_Criteria, "Print, to the console, an entity's current criteria set used to select responses.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Show an entity's autoaim radius
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Autoaim( const CCommand& args )
|
|
{
|
|
SetDebugBits( UTIL_GetCommandClient(),args[1], OVERLAY_AUTOAIM_BIT );
|
|
}
|
|
static ConCommand ent_autoaim("ent_autoaim", CC_Ent_Autoaim, "Displays the entity's autoaim radius.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at", FCVAR_CHEAT );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CAI_BaseNPC *CBaseEntity::MyNPCPointer( void )
|
|
{
|
|
if ( IsNPC() )
|
|
return assert_cast<CAI_BaseNPC *>(this);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
ConVar step_spline( "step_spline", "0" );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Run one tick's worth of faked simulation
|
|
// Input : *step -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::ComputeStepSimulationNetwork( StepSimulationData *step )
|
|
{
|
|
if ( !step )
|
|
{
|
|
Assert( !"ComputeStepSimulationNetworkOriginAndAngles with NULL step\n" );
|
|
return;
|
|
}
|
|
|
|
// Don't run again if we've already calculated this tick
|
|
if ( step->m_nLastProcessTickCount == gpGlobals->tickcount )
|
|
{
|
|
return;
|
|
}
|
|
|
|
step->m_nLastProcessTickCount = gpGlobals->tickcount;
|
|
|
|
// Origin
|
|
// It's inactive
|
|
if ( step->m_bOriginActive )
|
|
{
|
|
// First see if any external code moved the entity
|
|
if ( GetStepOrigin() != step->m_Next.vecOrigin )
|
|
{
|
|
step->m_bOriginActive = false;
|
|
}
|
|
else
|
|
{
|
|
// Compute interpolated info based on tick interval
|
|
float frac = 1.0f;
|
|
int tickdelta = step->m_Next.nTickCount - step->m_Previous.nTickCount;
|
|
if ( tickdelta > 0 )
|
|
{
|
|
frac = (float)( gpGlobals->tickcount - step->m_Previous.nTickCount ) / (float) tickdelta;
|
|
frac = clamp( frac, 0.0f, 1.0f );
|
|
}
|
|
|
|
if (step->m_Previous2.nTickCount == 0 || step->m_Previous2.nTickCount >= step->m_Previous.nTickCount)
|
|
{
|
|
Vector delta = step->m_Next.vecOrigin - step->m_Previous.vecOrigin;
|
|
VectorMA( step->m_Previous.vecOrigin, frac, delta, step->m_vecNetworkOrigin );
|
|
}
|
|
else if (!step_spline.GetBool())
|
|
{
|
|
StepSimulationStep *pOlder = &step->m_Previous;
|
|
StepSimulationStep *pNewer = &step->m_Next;
|
|
|
|
if (step->m_Discontinuity.nTickCount > step->m_Previous.nTickCount)
|
|
{
|
|
if (gpGlobals->tickcount > step->m_Discontinuity.nTickCount)
|
|
{
|
|
pOlder = &step->m_Discontinuity;
|
|
}
|
|
else
|
|
{
|
|
pNewer = &step->m_Discontinuity;
|
|
}
|
|
|
|
tickdelta = pNewer->nTickCount - pOlder->nTickCount;
|
|
if ( tickdelta > 0 )
|
|
{
|
|
frac = (float)( gpGlobals->tickcount - pOlder->nTickCount ) / (float) tickdelta;
|
|
frac = clamp( frac, 0.0f, 1.0f );
|
|
}
|
|
}
|
|
|
|
Vector delta = pNewer->vecOrigin - pOlder->vecOrigin;
|
|
VectorMA( pOlder->vecOrigin, frac, delta, step->m_vecNetworkOrigin );
|
|
}
|
|
else
|
|
{
|
|
Hermite_Spline( step->m_Previous2.vecOrigin, step->m_Previous.vecOrigin, step->m_Next.vecOrigin, frac, step->m_vecNetworkOrigin );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Angles
|
|
if ( step->m_bAnglesActive )
|
|
{
|
|
// See if external code changed the orientation of the entity
|
|
if ( GetStepAngles() != step->m_angNextRotation )
|
|
{
|
|
step->m_bAnglesActive = false;
|
|
}
|
|
else
|
|
{
|
|
// Compute interpolated info based on tick interval
|
|
float frac = 1.0f;
|
|
int tickdelta = step->m_Next.nTickCount - step->m_Previous.nTickCount;
|
|
if ( tickdelta > 0 )
|
|
{
|
|
frac = (float)( gpGlobals->tickcount - step->m_Previous.nTickCount ) / (float) tickdelta;
|
|
frac = clamp( frac, 0.0f, 1.0f );
|
|
}
|
|
|
|
if (step->m_Previous2.nTickCount == 0 || step->m_Previous2.nTickCount >= step->m_Previous.nTickCount)
|
|
{
|
|
// Pure blend between start/end orientations
|
|
Quaternion outangles;
|
|
QuaternionBlend( step->m_Previous.qRotation, step->m_Next.qRotation, frac, outangles );
|
|
QuaternionAngles( outangles, step->m_angNetworkAngles );
|
|
}
|
|
else if (!step_spline.GetBool())
|
|
{
|
|
StepSimulationStep *pOlder = &step->m_Previous;
|
|
StepSimulationStep *pNewer = &step->m_Next;
|
|
|
|
if (step->m_Discontinuity.nTickCount > step->m_Previous.nTickCount)
|
|
{
|
|
if (gpGlobals->tickcount > step->m_Discontinuity.nTickCount)
|
|
{
|
|
pOlder = &step->m_Discontinuity;
|
|
}
|
|
else
|
|
{
|
|
pNewer = &step->m_Discontinuity;
|
|
}
|
|
|
|
tickdelta = pNewer->nTickCount - pOlder->nTickCount;
|
|
if ( tickdelta > 0 )
|
|
{
|
|
frac = (float)( gpGlobals->tickcount - pOlder->nTickCount ) / (float) tickdelta;
|
|
frac = clamp( frac, 0.0f, 1.0f );
|
|
}
|
|
}
|
|
|
|
// Pure blend between start/end orientations
|
|
Quaternion outangles;
|
|
QuaternionBlend( pOlder->qRotation, pNewer->qRotation, frac, outangles );
|
|
QuaternionAngles( outangles, step->m_angNetworkAngles );
|
|
}
|
|
else
|
|
{
|
|
// FIXME: enable spline interpolation when turning is debounced.
|
|
Quaternion outangles;
|
|
Hermite_Spline( step->m_Previous2.qRotation, step->m_Previous.qRotation, step->m_Next.qRotation, frac, outangles );
|
|
QuaternionAngles( outangles, step->m_angNetworkAngles );
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::UseStepSimulationNetworkOrigin( const Vector **out_v )
|
|
{
|
|
Assert( out_v );
|
|
|
|
|
|
if ( g_bTestMoveTypeStepSimulation &&
|
|
GetMoveType() == MOVETYPE_STEP &&
|
|
HasDataObjectType( STEPSIMULATION ) )
|
|
{
|
|
StepSimulationData *step = ( StepSimulationData * )GetDataObject( STEPSIMULATION );
|
|
ComputeStepSimulationNetwork( step );
|
|
*out_v = &step->m_vecNetworkOrigin;
|
|
|
|
return step->m_bOriginActive;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::UseStepSimulationNetworkAngles( const QAngle **out_a )
|
|
{
|
|
Assert( out_a );
|
|
|
|
if ( g_bTestMoveTypeStepSimulation &&
|
|
GetMoveType() == MOVETYPE_STEP &&
|
|
HasDataObjectType( STEPSIMULATION ) )
|
|
{
|
|
StepSimulationData *step = ( StepSimulationData * )GetDataObject( STEPSIMULATION );
|
|
ComputeStepSimulationNetwork( step );
|
|
*out_a = &step->m_angNetworkAngles;
|
|
return step->m_bAnglesActive;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool CBaseEntity::AddStepDiscontinuity( float flTime, const Vector &vecOrigin, const QAngle &vecAngles )
|
|
{
|
|
if ((GetMoveType() != MOVETYPE_STEP ) || !HasDataObjectType( STEPSIMULATION ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
StepSimulationData *step = ( StepSimulationData * )GetDataObject( STEPSIMULATION );
|
|
|
|
if (!step)
|
|
{
|
|
Assert( 0 );
|
|
return false;
|
|
}
|
|
|
|
step->m_Discontinuity.nTickCount = TIME_TO_TICKS( flTime );
|
|
step->m_Discontinuity.vecOrigin = vecOrigin;
|
|
AngleQuaternion( vecAngles, step->m_Discontinuity.qRotation );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
Vector CBaseEntity::GetStepOrigin( void ) const
|
|
{
|
|
return GetLocalOrigin();
|
|
}
|
|
|
|
QAngle CBaseEntity::GetStepAngles( void ) const
|
|
{
|
|
return GetLocalAngles();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: For each client who appears to be a valid recipient, checks the client has disabled CC and if so, removes them from
|
|
// the recipient list.
|
|
// Input : filter -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::RemoveRecipientsIfNotCloseCaptioning( CRecipientFilter& filter )
|
|
{
|
|
int c = filter.GetRecipientCount();
|
|
for ( int i = c - 1; i >= 0; --i )
|
|
{
|
|
int playerIndex = filter.GetRecipientIndex( i );
|
|
|
|
CBasePlayer *player = static_cast< CBasePlayer * >( CBaseEntity::Instance( playerIndex ) );
|
|
if ( !player )
|
|
continue;
|
|
#if !defined( _XBOX )
|
|
const char *cvarvalue = engine->GetClientConVarValue( playerIndex, "closecaption" );
|
|
Assert( cvarvalue );
|
|
if ( !cvarvalue[ 0 ] )
|
|
continue;
|
|
|
|
int value = atoi( cvarvalue );
|
|
#else
|
|
static ConVar *s_pCloseCaption = NULL;
|
|
if ( !s_pCloseCaption )
|
|
{
|
|
s_pCloseCaption = cvar->FindVar( "closecaption" );
|
|
if ( !s_pCloseCaption )
|
|
{
|
|
Error( "XBOX couldn't find closecaption convar!!!" );
|
|
}
|
|
}
|
|
|
|
int value = s_pCloseCaption->GetInt();
|
|
#endif
|
|
// No close captions?
|
|
if ( value == 0 )
|
|
{
|
|
filter.RemoveRecipient( player );
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef MAPBASE // Moved to SoundEmitterSystem.cpp
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Wrapper to emit a sentence and also a close caption token for the sentence as appropriate.
|
|
// Input : filter -
|
|
// iEntIndex -
|
|
// iChannel -
|
|
// iSentenceIndex -
|
|
// flVolume -
|
|
// iSoundlevel -
|
|
// iFlags -
|
|
// iPitch -
|
|
// bUpdatePositions -
|
|
// soundtime -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::EmitSentenceByIndex( IRecipientFilter& filter, int iEntIndex, int iChannel, int iSentenceIndex,
|
|
float flVolume, soundlevel_t iSoundlevel, int iFlags /*= 0*/, int iPitch /*=PITCH_NORM*/,
|
|
const Vector *pOrigin /*=NULL*/, const Vector *pDirection /*=NULL*/,
|
|
bool bUpdatePositions /*=true*/, float soundtime /*=0.0f*/ )
|
|
{
|
|
CUtlVector< Vector > dummy;
|
|
enginesound->EmitSentenceByIndex( filter, iEntIndex, iChannel, iSentenceIndex,
|
|
flVolume, iSoundlevel, iFlags, iPitch, 0, pOrigin, pDirection, &dummy, bUpdatePositions, soundtime );
|
|
}
|
|
#endif
|
|
|
|
|
|
void CBaseEntity::SetRefEHandle( const CBaseHandle &handle )
|
|
{
|
|
m_RefEHandle = handle;
|
|
if ( edict() )
|
|
{
|
|
COMPILE_TIME_ASSERT( NUM_NETWORKED_EHANDLE_SERIAL_NUMBER_BITS <= 8*sizeof( edict()->m_NetworkSerialNumber ) );
|
|
edict()->m_NetworkSerialNumber = (m_RefEHandle.GetSerialNumber() & (1 << NUM_NETWORKED_EHANDLE_SERIAL_NUMBER_BITS) - 1);
|
|
}
|
|
}
|
|
|
|
|
|
bool CPointEntity::KeyValue( const char *szKeyName, const char *szValue )
|
|
{
|
|
if ( FStrEq( szKeyName, "mins" ) || FStrEq( szKeyName, "maxs" ) )
|
|
{
|
|
Warning("Warning! Can't specify mins/maxs for point entities! (%s)\n", GetClassname() );
|
|
return true;
|
|
}
|
|
|
|
return BaseClass::KeyValue( szKeyName, szValue );
|
|
}
|
|
|
|
bool CServerOnlyPointEntity::KeyValue( const char *szKeyName, const char *szValue )
|
|
{
|
|
if ( FStrEq( szKeyName, "mins" ) || FStrEq( szKeyName, "maxs" ) )
|
|
{
|
|
Warning("Warning! Can't specify mins/maxs for point entities! (%s)\n", GetClassname() );
|
|
return true;
|
|
}
|
|
|
|
return BaseClass::KeyValue( szKeyName, szValue );
|
|
}
|
|
|
|
bool CLogicalEntity::KeyValue( const char *szKeyName, const char *szValue )
|
|
{
|
|
if ( FStrEq( szKeyName, "mins" ) || FStrEq( szKeyName, "maxs" ) )
|
|
{
|
|
Warning("Warning! Can't specify mins/maxs for point entities! (%s)\n", GetClassname() );
|
|
return true;
|
|
}
|
|
|
|
return BaseClass::KeyValue( szKeyName, szValue );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets the entity invisible, and makes it remove itself on the next frame
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::RemoveDeferred( void )
|
|
{
|
|
// Set our next think to remove us
|
|
SetThink( &CBaseEntity::SUB_Remove );
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
|
|
// Hide us completely
|
|
AddEffects( EF_NODRAW );
|
|
AddSolidFlags( FSOLID_NOT_SOLID );
|
|
SetMoveType( MOVETYPE_NONE );
|
|
}
|
|
|
|
#define MIN_CORPSE_FADE_TIME 10.0
|
|
#define MIN_CORPSE_FADE_DIST 256.0
|
|
#define MAX_CORPSE_FADE_DIST 1500.0
|
|
|
|
//
|
|
// fade out - slowly fades a entity out, then removes it.
|
|
//
|
|
// DON'T USE ME FOR GIBS AND STUFF IN MULTIPLAYER!
|
|
// SET A FUTURE THINK AND A RENDERMODE!!
|
|
void CBaseEntity::SUB_StartFadeOut( float delay, bool notSolid )
|
|
{
|
|
SetThink( &CBaseEntity::SUB_FadeOut );
|
|
SetNextThink( gpGlobals->curtime + delay );
|
|
SetRenderColorA( 255 );
|
|
m_nRenderMode = kRenderNormal;
|
|
|
|
if ( notSolid )
|
|
{
|
|
AddSolidFlags( FSOLID_NOT_SOLID );
|
|
SetLocalAngularVelocity( vec3_angle );
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::SUB_StartFadeOutInstant()
|
|
{
|
|
SUB_StartFadeOut( 0, true );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Vanish when players aren't looking
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SUB_Vanish( void )
|
|
{
|
|
//Always think again next frame
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
|
|
CBasePlayer *pPlayer;
|
|
|
|
//Get all players
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
//Get the next client
|
|
if ( ( pPlayer = UTIL_PlayerByIndex( i ) ) != NULL )
|
|
{
|
|
Vector corpseDir = (GetAbsOrigin() - pPlayer->WorldSpaceCenter() );
|
|
|
|
float flDistSqr = corpseDir.LengthSqr();
|
|
//If the player is close enough, don't fade out
|
|
if ( flDistSqr < (MIN_CORPSE_FADE_DIST*MIN_CORPSE_FADE_DIST) )
|
|
return;
|
|
|
|
// If the player's far enough away, we don't care about looking at it
|
|
if ( flDistSqr < (MAX_CORPSE_FADE_DIST*MAX_CORPSE_FADE_DIST) )
|
|
{
|
|
VectorNormalize( corpseDir );
|
|
|
|
Vector plForward;
|
|
pPlayer->EyeVectors( &plForward );
|
|
|
|
float dot = plForward.Dot( corpseDir );
|
|
|
|
if ( dot > 0.0f )
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//If we're here, then we can vanish safely
|
|
m_iHealth = 0;
|
|
SetThink( &CBaseEntity::SUB_Remove );
|
|
}
|
|
|
|
void CBaseEntity::SUB_PerformFadeOut( void )
|
|
{
|
|
float dt = gpGlobals->frametime;
|
|
if ( dt > 0.1f )
|
|
{
|
|
dt = 0.1f;
|
|
}
|
|
m_nRenderMode = kRenderTransTexture;
|
|
int speed = MAX(1,256*dt); // fade out over 1 second
|
|
SetRenderColorA( UTIL_Approach( 0, m_clrRender->a, speed ) );
|
|
}
|
|
|
|
bool CBaseEntity::SUB_AllowedToFade( void )
|
|
{
|
|
if( VPhysicsGetObject() )
|
|
{
|
|
if( VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD || GetEFlags() & EFL_IS_BEING_LIFTED_BY_BARNACLE )
|
|
return false;
|
|
}
|
|
|
|
// on Xbox, allow these to fade out
|
|
#ifndef _XBOX
|
|
CBasePlayer *pPlayer = ( AI_IsSinglePlayer() ) ? UTIL_GetLocalPlayer() : NULL;
|
|
|
|
if ( pPlayer && pPlayer->FInViewCone( this ) )
|
|
return false;
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Fade out slowly
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SUB_FadeOut( void )
|
|
{
|
|
if ( SUB_AllowedToFade() == false )
|
|
{
|
|
SetNextThink( gpGlobals->curtime + 1 );
|
|
SetRenderColorA( 255 );
|
|
return;
|
|
}
|
|
|
|
SUB_PerformFadeOut();
|
|
|
|
if ( m_clrRender->a == 0 )
|
|
{
|
|
#ifdef MAPBASE
|
|
// This was meant for KillWhenNotVisible before it used its own function,
|
|
// but there's not really any harm for keeping this here.
|
|
m_OnKilled.FireOutput(this, this);
|
|
#endif
|
|
UTIL_Remove(this);
|
|
}
|
|
else
|
|
{
|
|
SetNextThink( gpGlobals->curtime );
|
|
}
|
|
}
|
|
|
|
#ifdef MAPBASE
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: For KillWhenNotVisible, based off of SUB_FadeOut
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SUB_RemoveWhenNotVisible( void )
|
|
{
|
|
if ( SUB_AllowedToFade() == false )
|
|
{
|
|
SetNextThink( gpGlobals->curtime + 1, "SUB_RemoveWhenNotVisible" );
|
|
SetRenderColorA( 255 );
|
|
return;
|
|
}
|
|
|
|
SetRenderColorA( m_clrRender->a - 1 );
|
|
|
|
if ( m_clrRender->a == 0 )
|
|
{
|
|
m_OnKilled.FireOutput(this, this);
|
|
UTIL_Remove(this);
|
|
}
|
|
else
|
|
{
|
|
SetNextThink( gpGlobals->curtime, "SUB_RemoveWhenNotVisible" );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
inline bool AnyPlayersInHierarchy_R( CBaseEntity *pEnt )
|
|
{
|
|
if ( pEnt->IsPlayer() )
|
|
return true;
|
|
|
|
for ( CBaseEntity *pCur = pEnt->FirstMoveChild(); pCur; pCur=pCur->NextMovePeer() )
|
|
{
|
|
if ( AnyPlayersInHierarchy_R( pCur ) )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void CBaseEntity::RecalcHasPlayerChildBit()
|
|
{
|
|
if ( AnyPlayersInHierarchy_R( this ) )
|
|
AddEFlags( EFL_HAS_PLAYER_CHILD );
|
|
else
|
|
RemoveEFlags( EFL_HAS_PLAYER_CHILD );
|
|
}
|
|
|
|
|
|
bool CBaseEntity::DoesHavePlayerChild()
|
|
{
|
|
return IsEFlagSet( EFL_HAS_PLAYER_CHILD );
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::IncrementInterpolationFrame()
|
|
{
|
|
m_ubInterpolationFrame = (m_ubInterpolationFrame + 1) % NOINTERP_PARITY_MAX;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void CBaseEntity::OnModelLoadComplete( const model_t* model )
|
|
{
|
|
Assert( m_bDynamicModelPending && IsDynamicModelIndex( m_nModelIndex ) );
|
|
Assert( model == modelinfo->GetModel( m_nModelIndex ) );
|
|
|
|
m_bDynamicModelPending = false;
|
|
|
|
if ( m_bDynamicModelSetBounds )
|
|
{
|
|
m_bDynamicModelSetBounds = false;
|
|
SetCollisionBoundsFromModel();
|
|
}
|
|
|
|
OnNewModel();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void CBaseEntity::SetCollisionBoundsFromModel()
|
|
{
|
|
if ( IsDynamicModelLoading() )
|
|
{
|
|
m_bDynamicModelSetBounds = true;
|
|
return;
|
|
}
|
|
|
|
if ( const model_t *pModel = GetModel() )
|
|
{
|
|
Vector mns, mxs;
|
|
modelinfo->GetModelBounds( pModel, mns, mxs );
|
|
UTIL_SetSize( this, mns, mxs );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
HSCRIPT CBaseEntity::GetScriptInstance()
|
|
{
|
|
if (!m_hScriptInstance)
|
|
{
|
|
if (m_iszScriptId == NULL_STRING)
|
|
{
|
|
char* szName = (char*)stackalloc(1024);
|
|
g_pScriptVM->GenerateUniqueKey((m_iName.Get() != NULL_STRING) ? STRING(GetEntityName()) : GetClassname(), szName, 1024);
|
|
m_iszScriptId = AllocPooledString(szName);
|
|
}
|
|
|
|
m_hScriptInstance = g_pScriptVM->RegisterInstance(GetScriptDesc(), this);
|
|
g_pScriptVM->SetInstanceUniqeId(m_hScriptInstance, STRING(m_iszScriptId));
|
|
}
|
|
return m_hScriptInstance;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Using my edict, cook up a unique VScript scope that's private to me, and
|
|
// persistent.
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::ValidateScriptScope()
|
|
{
|
|
if (!m_ScriptScope.IsInitialized())
|
|
{
|
|
if (scriptmanager == NULL)
|
|
{
|
|
ExecuteOnce(DevMsg("Cannot execute script because scripting is disabled (-scripting)\n"));
|
|
return false;
|
|
}
|
|
|
|
if (g_pScriptVM == NULL)
|
|
{
|
|
ExecuteOnce(DevMsg(" Cannot execute script because there is no available VM\n"));
|
|
return false;
|
|
}
|
|
|
|
// Force instance creation
|
|
GetScriptInstance();
|
|
|
|
EHANDLE hThis;
|
|
hThis.Set(this);
|
|
|
|
bool bResult = m_ScriptScope.Init(STRING(m_iszScriptId));
|
|
|
|
if (!bResult)
|
|
{
|
|
DevMsg("%s couldn't create ScriptScope!\n", GetDebugName());
|
|
return false;
|
|
}
|
|
g_pScriptVM->SetValue(m_ScriptScope, "self", GetScriptInstance());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Run all of the vscript files that are set in this entity's VSCRIPTS
|
|
// field in Hammer. The list is space-delimited.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::RunVScripts()
|
|
{
|
|
if (m_iszVScripts == NULL_STRING)
|
|
{
|
|
return;
|
|
}
|
|
|
|
#ifdef MAPBASE_VSCRIPT
|
|
if (g_pScriptVM == NULL)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
ValidateScriptScope();
|
|
|
|
// All functions we want to have call chained instead of overwritten
|
|
// by other scripts in this entities list.
|
|
static const char* sCallChainFunctions[] =
|
|
{
|
|
"OnPostSpawn",
|
|
"Precache"
|
|
};
|
|
|
|
ScriptLanguage_t language = g_pScriptVM->GetLanguage();
|
|
|
|
// Make a call chainer for each in this entities scope
|
|
for (int j = 0; j < ARRAYSIZE(sCallChainFunctions); ++j)
|
|
{
|
|
|
|
if (language == SL_PYTHON)
|
|
{
|
|
// UNDONE - handle call chaining in python
|
|
;
|
|
}
|
|
else if (language == SL_SQUIRREL)
|
|
{
|
|
//TODO: For perf, this should be precompiled and the %s should be passed as a parameter
|
|
HSCRIPT hCreateChainScript = g_pScriptVM->CompileScript(CFmtStr("%sCallChain <- CSimpleCallChainer(\"%s\", self.GetScriptScope(), true)", sCallChainFunctions[j], sCallChainFunctions[j]));
|
|
g_pScriptVM->Run(hCreateChainScript, (HSCRIPT)m_ScriptScope);
|
|
}
|
|
}
|
|
|
|
char szScriptsList[255];
|
|
Q_strcpy(szScriptsList, STRING(m_iszVScripts));
|
|
CUtlStringList szScripts;
|
|
|
|
V_SplitString(szScriptsList, " ", szScripts);
|
|
|
|
for (int i = 0; i < szScripts.Count(); i++)
|
|
{
|
|
#ifdef MAPBASE
|
|
CGMsg( 0, CON_GROUP_VSCRIPT, "%s executing script: %s\n", GetDebugName(), szScripts[i] );
|
|
#else
|
|
Log( "%s executing script: %s\n", GetDebugName(), szScripts[i]);
|
|
#endif
|
|
|
|
RunScriptFile(szScripts[i], IsWorld());
|
|
|
|
for (int j = 0; j < ARRAYSIZE(sCallChainFunctions); ++j)
|
|
{
|
|
if (language == SL_PYTHON)
|
|
{
|
|
// UNDONE - handle call chaining in python
|
|
;
|
|
}
|
|
else if (language == SL_SQUIRREL)
|
|
{
|
|
//TODO: For perf, this should be precompiled and the %s should be passed as a parameter.
|
|
HSCRIPT hRunPostScriptExecute = g_pScriptVM->CompileScript(CFmtStr("%sCallChain.PostScriptExecute()", sCallChainFunctions[j]));
|
|
g_pScriptVM->Run(hRunPostScriptExecute, (HSCRIPT)m_ScriptScope);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_iszScriptThinkFunction != NULL_STRING)
|
|
{
|
|
SetContextThink(&CBaseEntity::ScriptThink, gpGlobals->curtime + sv_script_think_interval.GetFloat(), "ScriptThink");
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// This is called during entity spawning and after restore to allow scripts to precache any
|
|
// resources they need.
|
|
//--------------------------------------------------------------------------------------------------
|
|
void CBaseEntity::RunPrecacheScripts(void)
|
|
{
|
|
if (m_iszVScripts == NULL_STRING)
|
|
{
|
|
return;
|
|
}
|
|
|
|
#ifdef MAPBASE_VSCRIPT
|
|
if (g_pScriptVM == NULL)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
HSCRIPT hScriptPrecache = m_ScriptScope.LookupFunction("DispatchPrecache");
|
|
if (hScriptPrecache)
|
|
{
|
|
g_pScriptVM->Call(hScriptPrecache, m_ScriptScope);
|
|
m_ScriptScope.ReleaseFunction(hScriptPrecache);
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::RunOnPostSpawnScripts(void)
|
|
{
|
|
if (m_iszVScripts == NULL_STRING)
|
|
{
|
|
return;
|
|
}
|
|
|
|
#ifdef MAPBASE_VSCRIPT
|
|
if (g_pScriptVM == NULL)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
HSCRIPT hFuncConnect = g_pScriptVM->LookupFunction("ConnectOutputs");
|
|
if (hFuncConnect)
|
|
{
|
|
g_pScriptVM->Call(hFuncConnect, NULL, true, NULL, (HSCRIPT)m_ScriptScope);
|
|
g_pScriptVM->ReleaseFunction(hFuncConnect);
|
|
}
|
|
|
|
HSCRIPT hFuncDisp = m_ScriptScope.LookupFunction("DispatchOnPostSpawn");
|
|
if (hFuncDisp)
|
|
{
|
|
variant_t variant;
|
|
variant.SetString(MAKE_STRING("DispatchOnPostSpawn"));
|
|
g_EventQueue.AddEvent(this, "CallScriptFunction", variant, 0, this, this);
|
|
m_ScriptScope.ReleaseFunction(hFuncDisp);
|
|
|
|
}
|
|
}
|
|
|
|
#ifndef MAPBASE_VSCRIPT // This is shared now
|
|
HSCRIPT CBaseEntity::GetScriptOwnerEntity()
|
|
{
|
|
return ToHScript(GetOwnerEntity());
|
|
}
|
|
|
|
void CBaseEntity::SetScriptOwnerEntity(HSCRIPT pOwner)
|
|
{
|
|
SetOwnerEntity(ToEnt(pOwner));
|
|
}
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// VScript access to model's key values
|
|
// for iteration and value access, use:
|
|
// ScriptFindKey, ScriptGetFirstSubKey, ScriptGetString,
|
|
// ScriptGetInt, ScriptGetFloat, ScriptGetNextKey
|
|
//-----------------------------------------------------------------------------
|
|
HSCRIPT CBaseEntity::ScriptGetModelKeyValues( void )
|
|
{
|
|
KeyValues *pModelKeyValues = new KeyValues("");
|
|
HSCRIPT hScript = NULL;
|
|
const char *pszModelName = modelinfo->GetModelName( GetModel() );
|
|
const char *pBuffer = modelinfo->GetModelKeyValueText( GetModel() ) ;
|
|
|
|
if ( pModelKeyValues->LoadFromBuffer( pszModelName, pBuffer ) )
|
|
{
|
|
// UNDONE: how does destructor get called on this
|
|
#ifdef MAPBASE_VSCRIPT
|
|
m_pScriptModelKeyValues = hScript = scriptmanager->CreateScriptKeyValues( g_pScriptVM, pModelKeyValues, true ); // Allow VScript to delete this when the instance is removed.
|
|
#else
|
|
m_pScriptModelKeyValues = new CScriptKeyValues( pModelKeyValues );
|
|
#endif
|
|
|
|
// UNDONE: who calls ReleaseInstance on this??? Does name need to be unique???
|
|
|
|
#ifndef MAPBASE_VSCRIPT
|
|
hScript = g_pScriptVM->RegisterInstance( m_pScriptModelKeyValues );
|
|
#endif
|
|
|
|
/*
|
|
KeyValues *pParticleEffects = pModelKeyValues->FindKey("Particles");
|
|
if ( pParticleEffects )
|
|
{
|
|
// Start grabbing the sounds and slotting them in
|
|
for ( KeyValues *pSingleEffect = pParticleEffects->GetFirstSubKey(); pSingleEffect; pSingleEffect = pSingleEffect->GetNextKey() )
|
|
{
|
|
const char *pParticleEffectName = pSingleEffect->GetString( "name", "" );
|
|
PrecacheParticleSystem( pParticleEffectName );
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|
|
return hScript;
|
|
}
|
|
|
|
void CBaseEntity::ScriptSetLocalAngularVelocity(float pitchVel, float yawVel, float rollVel)
|
|
{
|
|
QAngle qa;
|
|
qa.Init(pitchVel, yawVel, rollVel);
|
|
SetLocalAngularVelocity(qa);
|
|
}
|
|
|
|
const Vector& CBaseEntity::ScriptGetLocalAngularVelocity(void)
|
|
{
|
|
QAngle qa = GetLocalAngularVelocity();
|
|
static Vector v;
|
|
v.x = qa.x;
|
|
v.y = qa.y;
|
|
v.z = qa.z;
|
|
return v;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Vscript: Gets the min collision bounds, centered on object
|
|
//-----------------------------------------------------------------------------
|
|
const Vector& CBaseEntity::ScriptGetBoundingMins(void)
|
|
{
|
|
return m_Collision.OBBMins();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Vscript: Gets the max collision bounds, centered on object
|
|
//-----------------------------------------------------------------------------
|
|
const Vector& CBaseEntity::ScriptGetBoundingMaxs(void)
|
|
{
|
|
return m_Collision.OBBMaxs();
|
|
}
|
|
|
|
#ifdef MAPBASE_VSCRIPT
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::ScriptTakeDamage( HSCRIPT pInfo )
|
|
{
|
|
if (pInfo)
|
|
{
|
|
CTakeDamageInfo *info = HScriptToClass<CTakeDamageInfo>( pInfo ); //ToDamageInfo( pInfo );
|
|
if (info)
|
|
{
|
|
return OnTakeDamage( *info );
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::ScriptFireBullets( HSCRIPT pInfo )
|
|
{
|
|
if (pInfo)
|
|
{
|
|
extern FireBulletsInfo_t *GetFireBulletsInfoFromInfo( HSCRIPT hBulletsInfo );
|
|
FireBulletsInfo_t *info = GetFireBulletsInfoFromInfo( pInfo );
|
|
if (info)
|
|
{
|
|
FireBullets( *info );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::ScriptAddContext( const char *name, const char *value, float duration )
|
|
{
|
|
AddContext( name, value, duration );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
const char *CBaseEntity::ScriptGetContext( const char *name )
|
|
{
|
|
return GetContextValue( name );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
HSCRIPT CBaseEntity::ScriptGetContextIndex( int index )
|
|
{
|
|
if (index >= m_ResponseContexts.Count())
|
|
return NULL;
|
|
|
|
ScriptVariant_t varTable;
|
|
g_pScriptVM->CreateTable( varTable );
|
|
|
|
g_pScriptVM->SetValue( varTable, "name", STRING( m_ResponseContexts[index].m_iszName ) );
|
|
g_pScriptVM->SetValue( varTable, "value", STRING( m_ResponseContexts[index].m_iszValue ) );
|
|
g_pScriptVM->SetValue( varTable, "expiration_time", m_ResponseContexts[index].m_fExpirationTime );
|
|
|
|
return varTable.m_hScript;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::ScriptClassify( void )
|
|
{
|
|
return (int)Classify();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::ScriptAddOutput( const char *pszOutputName, const char *pszTarget, const char *pszAction, const char *pszParameter, float flDelay, int iMaxTimes )
|
|
{
|
|
const char *pszValue = UTIL_VarArgs("%s,%s,%s,%f,%i", pszTarget, pszAction, pszParameter, flDelay, iMaxTimes);
|
|
return KeyValue( pszOutputName, pszValue );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
const char *CBaseEntity::ScriptGetKeyValue( const char *pszKeyName )
|
|
{
|
|
static char szValue[128];
|
|
GetKeyValue( pszKeyName, szValue, sizeof(szValue) );
|
|
return szValue;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Vscript: Dispatch an interaction to the entity
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::ScriptDispatchInteraction( int interactionType, HSCRIPT data, HSCRIPT sourceEnt )
|
|
{
|
|
return DispatchInteraction( interactionType, data, ToEnt( sourceEnt ) ? ToEnt( sourceEnt )->MyCombatCharacterPointer() : NULL );
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef MAPBASE
|
|
extern int EntityFactory_AutoComplete( const char *cmdname, CUtlVector< CUtlString > &commands, CUtlRBTree< CUtlString > &symbols, char *substring, int checklen = 0 );
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Create an entity of the given type
|
|
//------------------------------------------------------------------------------
|
|
class CEntCreateAutoCompletionFunctor : public ICommandCallback, public ICommandCompletionCallback
|
|
{
|
|
public:
|
|
virtual bool CreateAimed() { return false; }
|
|
|
|
virtual void CommandCallback( const CCommand &args )
|
|
{
|
|
MDLCACHE_CRITICAL_SECTION();
|
|
|
|
CBasePlayer *pPlayer = UTIL_GetCommandClient();
|
|
if (!pPlayer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Don't allow regular users to create point_servercommand entities for the same reason as blocking ent_fire
|
|
if ( !Q_stricmp( args[1], "point_servercommand" ) )
|
|
{
|
|
if ( engine->IsDedicatedServer() )
|
|
{
|
|
// We allow people with disabled autokick to do it, because they already have rcon.
|
|
if ( pPlayer->IsAutoKickDisabled() == false )
|
|
return;
|
|
}
|
|
else if ( gpGlobals->maxClients > 1 )
|
|
{
|
|
// On listen servers with more than 1 player, only allow the host to create point_servercommand.
|
|
CBasePlayer *pHostPlayer = UTIL_GetListenServerHost();
|
|
if ( pPlayer != pHostPlayer )
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool allowPrecache = CBaseEntity::IsPrecacheAllowed();
|
|
CBaseEntity::SetAllowPrecache( true );
|
|
|
|
// Try to create entity
|
|
CBaseEntity *entity = dynamic_cast< CBaseEntity * >( CreateEntityByName(args[1]) );
|
|
if (entity)
|
|
{
|
|
// Pass in any additional parameters.
|
|
for ( int i = 2; i + 1 < args.ArgC(); i += 2 )
|
|
{
|
|
const char *pKeyName = args[i];
|
|
const char *pValue = args[i+1];
|
|
entity->KeyValue( pKeyName, pValue );
|
|
}
|
|
|
|
DispatchSpawn(entity);
|
|
|
|
// Now attempt to drop into the world
|
|
trace_t tr;
|
|
Vector forward;
|
|
pPlayer->EyeVectors( &forward );
|
|
|
|
// Pass through the player's vehicle
|
|
CTraceFilterSkipTwoEntities filter( pPlayer, pPlayer->GetVehicleEntity(), COLLISION_GROUP_NONE );
|
|
UTIL_TraceLine(pPlayer->EyePosition(),
|
|
pPlayer->EyePosition() + forward * MAX_TRACE_LENGTH,MASK_SOLID,
|
|
&filter, &tr );
|
|
|
|
if ( tr.fraction != 1.0 )
|
|
{
|
|
// Raise the end position a little up off the floor, place the npc and drop him down
|
|
tr.endpos.z += 12;
|
|
|
|
if (CreateAimed())
|
|
{
|
|
QAngle angles;
|
|
VectorAngles( forward, angles );
|
|
angles.x = 0;
|
|
angles.z = 0;
|
|
entity->Teleport( &tr.endpos, &angles, NULL );
|
|
}
|
|
else
|
|
{
|
|
entity->Teleport( &tr.endpos, NULL, NULL );
|
|
}
|
|
|
|
UTIL_DropToFloor( entity, MASK_SOLID );
|
|
}
|
|
|
|
entity->Activate();
|
|
}
|
|
CBaseEntity::SetAllowPrecache( allowPrecache );
|
|
}
|
|
|
|
virtual int CommandCompletionCallback( const char *partial, CUtlVector< CUtlString > &commands )
|
|
{
|
|
if ( !g_pGameRules )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
const char *cmdname = CreateAimed() ? "ent_create_aimed" : "ent_create";
|
|
|
|
char *substring = (char *)partial;
|
|
if ( Q_strstr( partial, cmdname ) )
|
|
{
|
|
substring = (char *)partial + strlen( cmdname ) + 1;
|
|
}
|
|
|
|
int checklen = Q_strlen( substring );
|
|
|
|
CUtlRBTree< CUtlString > symbols( 0, 0, UtlStringLessFunc );
|
|
return EntityFactory_AutoComplete( cmdname, commands, symbols, substring, checklen );
|
|
}
|
|
};
|
|
|
|
static CEntCreateAutoCompletionFunctor g_EntCreateAutoComplete;
|
|
static ConCommand ent_create("ent_create", &g_EntCreateAutoComplete, "Creates an entity of the given type where the player is looking. Additional parameters can be passed in in the form: ent_create <entity name> <param 1 name> <param 1> <param 2 name> <param 2>...<param N name> <param N>", FCVAR_GAMEDLL | FCVAR_CHEAT, &g_EntCreateAutoComplete);
|
|
|
|
class CEntCreateAimedAutoCompletionFunctor : public CEntCreateAutoCompletionFunctor
|
|
{
|
|
public:
|
|
virtual bool CreateAimed() { return true; }
|
|
};
|
|
|
|
static CEntCreateAimedAutoCompletionFunctor g_EntCreateAimedAutoComplete;
|
|
|
|
static ConCommand ent_create_aimed("ent_create_aimed", &g_EntCreateAimedAutoComplete, "Creates an entity of the given type where the player is looking. Additional parameters can be passed in in the form: ent_create_aimed <entity name> <param 1 name> <param 1> <param 2 name> <param 2>...<param N name> <param N>", FCVAR_CHEAT, &g_EntCreateAimedAutoComplete);
|
|
#else
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Create an NPC of the given type
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Create( const CCommand& args )
|
|
{
|
|
MDLCACHE_CRITICAL_SECTION();
|
|
|
|
CBasePlayer *pPlayer = UTIL_GetCommandClient();
|
|
if (!pPlayer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Don't allow regular users to create point_servercommand entities for the same reason as blocking ent_fire
|
|
if ( !Q_stricmp( args[1], "point_servercommand" ) )
|
|
{
|
|
if ( engine->IsDedicatedServer() )
|
|
{
|
|
// We allow people with disabled autokick to do it, because they already have rcon.
|
|
if ( pPlayer->IsAutoKickDisabled() == false )
|
|
return;
|
|
}
|
|
else if ( gpGlobals->maxClients > 1 )
|
|
{
|
|
// On listen servers with more than 1 player, only allow the host to create point_servercommand.
|
|
CBasePlayer *pHostPlayer = UTIL_GetListenServerHost();
|
|
if ( pPlayer != pHostPlayer )
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool allowPrecache = CBaseEntity::IsPrecacheAllowed();
|
|
CBaseEntity::SetAllowPrecache( true );
|
|
|
|
// Try to create entity
|
|
CBaseEntity *entity = dynamic_cast< CBaseEntity * >( CreateEntityByName(args[1]) );
|
|
if (entity)
|
|
{
|
|
entity->Precache();
|
|
|
|
// Pass in any additional parameters.
|
|
for ( int i = 2; i + 1 < args.ArgC(); i += 2 )
|
|
{
|
|
const char *pKeyName = args[i];
|
|
const char *pValue = args[i+1];
|
|
entity->KeyValue( pKeyName, pValue );
|
|
}
|
|
|
|
DispatchSpawn(entity);
|
|
|
|
// Now attempt to drop into the world
|
|
trace_t tr;
|
|
Vector forward;
|
|
pPlayer->EyeVectors( &forward );
|
|
UTIL_TraceLine(pPlayer->EyePosition(),
|
|
pPlayer->EyePosition() + forward * MAX_TRACE_LENGTH,MASK_SOLID,
|
|
pPlayer, COLLISION_GROUP_NONE, &tr );
|
|
if ( tr.fraction != 1.0 )
|
|
{
|
|
// Raise the end position a little up off the floor, place the npc and drop him down
|
|
tr.endpos.z += 12;
|
|
entity->Teleport( &tr.endpos, NULL, NULL );
|
|
UTIL_DropToFloor( entity, MASK_SOLID );
|
|
}
|
|
|
|
entity->Activate();
|
|
}
|
|
CBaseEntity::SetAllowPrecache( allowPrecache );
|
|
}
|
|
static ConCommand ent_create("ent_create", CC_Ent_Create, "Creates an entity of the given type where the player is looking. Additional parameters can be passed in in the form: ent_create <entity name> <param 1 name> <param 1> <param 2 name> <param 2>...<param N name> <param N>", FCVAR_GAMEDLL | FCVAR_CHEAT);
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Teleport a specified entity to where the player is looking
|
|
//------------------------------------------------------------------------------
|
|
bool CC_GetCommandEnt( const CCommand& args, CBaseEntity **ent, Vector *vecTargetPoint, QAngle *vecPlayerAngle )
|
|
{
|
|
// Find the entity
|
|
*ent = NULL;
|
|
// First try using it as an entindex
|
|
int iEntIndex = atoi( args[1] );
|
|
if ( iEntIndex )
|
|
{
|
|
*ent = CBaseEntity::Instance( iEntIndex );
|
|
}
|
|
else
|
|
{
|
|
// Try finding it by name
|
|
*ent = gEntList.FindEntityByName( NULL, args[1] );
|
|
|
|
if ( !*ent )
|
|
{
|
|
// Finally, try finding it by classname
|
|
*ent = gEntList.FindEntityByClassname( NULL, args[1] );
|
|
}
|
|
}
|
|
|
|
if ( !*ent )
|
|
{
|
|
Msg( "Couldn't find any entity named '%s'\n", args[1] );
|
|
return false;
|
|
}
|
|
|
|
CBasePlayer *pPlayer = UTIL_GetCommandClient();
|
|
if ( vecTargetPoint )
|
|
{
|
|
trace_t tr;
|
|
Vector forward;
|
|
pPlayer->EyeVectors( &forward );
|
|
UTIL_TraceLine(pPlayer->EyePosition(),
|
|
pPlayer->EyePosition() + forward * MAX_TRACE_LENGTH,MASK_NPCSOLID,
|
|
pPlayer, COLLISION_GROUP_NONE, &tr );
|
|
|
|
if ( tr.fraction != 1.0 )
|
|
{
|
|
*vecTargetPoint = tr.endpos;
|
|
}
|
|
}
|
|
|
|
if ( vecPlayerAngle )
|
|
{
|
|
*vecPlayerAngle = pPlayer->EyeAngles();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifdef MAPBASE
|
|
class CEntTeleportAutoCompletionFunctor : public ICommandCallback, public ICommandCompletionCallback
|
|
{
|
|
public:
|
|
virtual void CommandCallback( const CCommand &command )
|
|
{
|
|
if ( command.ArgC() < 2 )
|
|
{
|
|
Msg( "Format: ent_teleport <entity name>\n" );
|
|
return;
|
|
}
|
|
|
|
CBaseEntity *pEnt;
|
|
Vector vecTargetPoint;
|
|
if ( CC_GetCommandEnt( command, &pEnt, &vecTargetPoint, NULL ) )
|
|
{
|
|
pEnt->Teleport( &vecTargetPoint, NULL, NULL );
|
|
}
|
|
}
|
|
|
|
virtual int CommandCompletionCallback( const char *partial, CUtlVector< CUtlString > &commands )
|
|
{
|
|
if ( !g_pGameRules )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
const char *cmdname = "ent_teleport";
|
|
|
|
char *substring = (char *)partial;
|
|
if ( Q_strstr( partial, cmdname ) )
|
|
{
|
|
substring = (char *)partial + strlen( cmdname ) + 1;
|
|
}
|
|
|
|
int checklen = Q_strlen( substring );
|
|
|
|
CUtlRBTree< CUtlString > symbols( 0, 0, UtlStringLessFunc );
|
|
return AutoCompleteEntities(cmdname, commands, symbols, substring, checklen);
|
|
}
|
|
};
|
|
|
|
static CEntTeleportAutoCompletionFunctor g_EntTeleportAutoComplete;
|
|
static ConCommand ent_teleport("ent_teleport", &g_EntTeleportAutoComplete, "Teleport the specified entity to where the player is looking.\n\tFormat: ent_teleport <entity name>", FCVAR_CHEAT, &g_EntTeleportAutoComplete);
|
|
#else
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Teleport a specified entity to where the player is looking
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Teleport( const CCommand& args )
|
|
{
|
|
if ( args.ArgC() < 2 )
|
|
{
|
|
Msg( "Format: ent_teleport <entity name>\n" );
|
|
return;
|
|
}
|
|
|
|
CBaseEntity *pEnt;
|
|
Vector vecTargetPoint;
|
|
if ( CC_GetCommandEnt( args, &pEnt, &vecTargetPoint, NULL ) )
|
|
{
|
|
pEnt->Teleport( &vecTargetPoint, NULL, NULL );
|
|
}
|
|
}
|
|
|
|
static ConCommand ent_teleport("ent_teleport", CC_Ent_Teleport, "Teleport the specified entity to where the player is looking.\n\tFormat: ent_teleport <entity name>", FCVAR_CHEAT);
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Orient a specified entity to match the player's angles
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Orient( const CCommand& args )
|
|
{
|
|
if ( args.ArgC() < 2 )
|
|
{
|
|
Msg( "Format: ent_orient <entity name> <optional: allangles>\n" );
|
|
return;
|
|
}
|
|
|
|
CBaseEntity *pEnt;
|
|
QAngle vecPlayerAngles;
|
|
if ( CC_GetCommandEnt( args, &pEnt, NULL, &vecPlayerAngles ) )
|
|
{
|
|
QAngle vecEntAngles = pEnt->GetAbsAngles();
|
|
if ( args.ArgC() == 3 && !Q_strncmp( args[2], "allangles", 9 ) )
|
|
{
|
|
vecEntAngles = vecPlayerAngles;
|
|
}
|
|
else
|
|
{
|
|
vecEntAngles[YAW] = vecPlayerAngles[YAW];
|
|
}
|
|
|
|
pEnt->SetAbsAngles( vecEntAngles );
|
|
}
|
|
}
|
|
|
|
static ConCommand ent_orient("ent_orient", CC_Ent_Orient, "Orient the specified entity to match the player's angles. By default, only orients target entity's YAW. Use the 'allangles' option to orient on all axis.\n\tFormat: ent_orient <entity name> <optional: allangles>", FCVAR_CHEAT);
|