//========= 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 #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 #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"); // 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 ), #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( ) { // 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 } 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(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(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; } } 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 = UTIL_GetLocalPlayer(); if ( pPlayer ) { float mass = pPlayer->GetHeldObjectMass( VPhysicsGetObject() ); if ( mass != 0.0f ) { float ratio = VPhysicsGetObject()->GetMass() / mass; force *= ratio; } } } else if ( (gameFlags & FVPHYSICS_PART_OF_RAGDOLL) && (gameFlags & FVPHYSICS_CONSTRAINT_STATIC) ) { IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; int count = VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); for ( int i = 0; i < count; i++ ) { if ( !(pList[i]->GetGameFlags() & FVPHYSICS_CONSTRAINT_STATIC) ) { pList[i]->ApplyForceOffset( force, offset ); return 1; } } } VPhysicsGetObject()->ApplyForceOffset( force, offset ); } return 1; } // Character killed (only fired once) void CBaseEntity::Event_Killed( const CTakeDamageInfo &info ) { if( info.GetAttacker() ) { info.GetAttacker()->Event_KilledOther(this, info); } m_takedamage = DAMAGE_NO; m_lifeState = LIFE_DEAD; UTIL_Remove( this ); } //----------------------------------------------------------------------------- // Purpose: helper method to send a game event when this entity is killed. Note: // gets called specifically for particular entities (mostly NPC), this // does not get called for every entity //----------------------------------------------------------------------------- void CBaseEntity::SendOnKilledGameEvent( const CTakeDamageInfo &info ) { IGameEvent *event = gameeventmanager->CreateEvent( "entity_killed" ); if ( event ) { event->SetInt( "entindex_killed", entindex() ); if ( info.GetAttacker()) { event->SetInt( "entindex_attacker", info.GetAttacker()->entindex() ); } if ( info.GetInflictor()) { event->SetInt( "entindex_inflictor", info.GetInflictor()->entindex() ); } event->SetInt( "damagebits", info.GetDamageType() ); gameeventmanager->FireEvent( event ); } } bool CBaseEntity::HasTarget( string_t targetname ) { if( targetname != NULL_STRING && m_target != NULL_STRING ) return FStrEq(STRING(targetname), STRING(m_target) ); else return false; } CBaseEntity *CBaseEntity::GetNextTarget( void ) { if ( !m_target ) return NULL; return gEntList.FindEntityByName( NULL, m_target ); } class CThinkContextsSaveDataOps : public CDefSaveRestoreOps { virtual void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave ) { AssertMsg( fieldInfo.pTypeDesc->fieldSize == 1, "CThinkContextsSaveDataOps does not support arrays"); // Write out the vector CUtlVector< thinkfunc_t > *pUtlVector = (CUtlVector< thinkfunc_t > *)fieldInfo.pField; SaveUtlVector( pSave, pUtlVector, FIELD_EMBEDDED ); // Get our owner CBaseEntity *pOwner = (CBaseEntity*)fieldInfo.pOwner; pSave->StartBlock(); // Now write out all the functions for ( int i = 0; i < pUtlVector->Size(); i++ ) { #ifdef WIN32 void **ppV = (void**)&((*pUtlVector)[i].m_pfnThink); #else BASEPTR *ppV = &((*pUtlVector)[i].m_pfnThink); #endif bool bHasFunc = (*ppV != NULL); pSave->WriteBool( &bHasFunc, 1 ); if ( bHasFunc ) { pSave->WriteFunction( pOwner->GetDataDescMap(), "m_pfnThink", (inputfunc_t **)ppV, 1 ); } } pSave->EndBlock(); } virtual void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore ) { AssertMsg( fieldInfo.pTypeDesc->fieldSize == 1, "CThinkContextsSaveDataOps does not support arrays"); // Read in the vector CUtlVector< thinkfunc_t > *pUtlVector = (CUtlVector< thinkfunc_t > *)fieldInfo.pField; RestoreUtlVector( pRestore, pUtlVector, FIELD_EMBEDDED ); // Get our owner CBaseEntity *pOwner = (CBaseEntity*)fieldInfo.pOwner; pRestore->StartBlock(); // Now read in all the functions for ( int i = 0; i < pUtlVector->Size(); i++ ) { bool bHasFunc; pRestore->ReadBool( &bHasFunc, 1 ); #ifdef WIN32 void **ppV = (void**)&((*pUtlVector)[i].m_pfnThink); #else BASEPTR *ppV = &((*pUtlVector)[i].m_pfnThink); Q_memset( (void *)ppV, 0x0, sizeof(inputfunc_t) ); #endif if ( bHasFunc ) { SaveRestoreRecordHeader_t header; pRestore->ReadHeader( &header ); pRestore->ReadFunction( pOwner->GetDataDescMap(), (inputfunc_t **)ppV, 1, header.size ); } else { *ppV = NULL; } } pRestore->EndBlock(); } virtual bool IsEmpty( const SaveRestoreFieldInfo_t &fieldInfo ) { CUtlVector< thinkfunc_t > *pUtlVector = (CUtlVector< thinkfunc_t > *)fieldInfo.pField; return ( pUtlVector->Count() == 0 ); } virtual void MakeEmpty( const SaveRestoreFieldInfo_t &fieldInfo ) { BASEPTR pFunc = *((BASEPTR*)fieldInfo.pField); pFunc = NULL; } }; CThinkContextsSaveDataOps g_ThinkContextsSaveDataOps; ISaveRestoreOps *thinkcontextFuncs = &g_ThinkContextsSaveDataOps; BEGIN_SIMPLE_DATADESC( thinkfunc_t ) DEFINE_FIELD( m_iszContext, FIELD_STRING ), // DEFINE_FIELD( m_pfnThink, FIELD_FUNCTION ), // Manually written DEFINE_FIELD( m_nNextThinkTick, FIELD_TICK ), DEFINE_FIELD( m_nLastThinkTick, FIELD_TICK ), END_DATADESC() BEGIN_SIMPLE_DATADESC( ResponseContext_t ) DEFINE_FIELD( m_iszName, FIELD_STRING ), DEFINE_FIELD( m_iszValue, FIELD_STRING ), DEFINE_FIELD( m_fExpirationTime, FIELD_TIME ), END_DATADESC() BEGIN_DATADESC_NO_BASE( CBaseEntity ) DEFINE_KEYFIELD( m_iClassname, FIELD_STRING, "classname" ), DEFINE_GLOBAL_KEYFIELD( m_iGlobalname, FIELD_STRING, "globalname" ), DEFINE_KEYFIELD( m_iParent, FIELD_STRING, "parentname" ), DEFINE_KEYFIELD( m_iHammerID, FIELD_INTEGER, "hammerid" ), // save ID numbers so that entities can be tracked between save/restore and vmf DEFINE_KEYFIELD( m_flSpeed, FIELD_FLOAT, "speed" ), DEFINE_KEYFIELD( m_nRenderFX, FIELD_CHARACTER, "renderfx" ), DEFINE_KEYFIELD( m_nRenderMode, FIELD_CHARACTER, "rendermode" ), // Consider moving to CBaseAnimating? DEFINE_FIELD( m_flPrevAnimTime, FIELD_TIME ), DEFINE_FIELD( m_flAnimTime, FIELD_TIME ), DEFINE_FIELD( m_flSimulationTime, FIELD_TIME ), DEFINE_FIELD( m_nLastThinkTick, FIELD_TICK ), DEFINE_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" ), #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_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 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() 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( 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", "" ) DEFINE_SCRIPTFUNC( GetPreTemplateName, "Get the entity name stripped of template unique decoration" ) DEFINE_SCRIPTFUNC_NAMED( GetAbsOrigin, "GetOrigin", "" ) DEFINE_SCRIPTFUNC( SetAbsOrigin, "SetAbsOrigin" ) DEFINE_SCRIPTFUNC_NAMED( ScriptSetOrigin, "SetOrigin", "" ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetForward, "GetForwardVector", "Get the forward vector of the entity" ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetLeft, "GetLeftVector", "Get the left vector of the entity" ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetUp, "GetUpVector", "Get the up vector of the entity" ) #ifdef MAPBASE_VSCRIPT DEFINE_SCRIPTFUNC_NAMED( ScriptEntityToWorldTransform, "EntityToWorldTransform", "Get the entity's transform" ) #endif DEFINE_SCRIPTFUNC_NAMED( ScriptSetForward, "SetForwardVector", "Set the orientation of the entity to have this forward vector" ) 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_NAMED( ScriptSetSize, "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( ScriptUtilRemove, "Destroy", "" ) DEFINE_SCRIPTFUNC_NAMED( ScriptSetOwner, "SetOwner", "" ) DEFINE_SCRIPTFUNC_NAMED( GetTeamNumber, "GetTeam", "" ) DEFINE_SCRIPTFUNC_NAMED( ChangeTeam, "SetTeam", "" ) 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_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_NAMED( ScriptGetContext, "GetContext", "Get a response context value" ) DEFINE_SCRIPTFUNC_NAMED( ScriptAddContext, "AddContext", "Add a response context value" ) DEFINE_SCRIPTFUNC_NAMED( ScriptClassify, "Classify", "Get Class_T class ID" ) DEFINE_SCRIPTFUNC_NAMED( ScriptAddOutput, "AddOutput", "Add an output" ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetKeyValue, "GetKeyValue", "Get a keyvalue" ) 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( 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." ) #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" ) 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, "" ) 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 ) { /* <> // 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 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 ) { 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(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 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(pOther->GetGameData()); } // UNDONE: Need to traverse hierarchy here? Shouldn't. if ( pList ) { SetLocalOrigin( pList->localOrigin ); SetLocalAngles( pList->localAngles ); physLocalTime = pList->localMoveTime; for ( int i = 0; i < pList->pushedCount; i++ ) { CBaseEntity *pEntity = pList->pushedEnts[i]; if ( !pEntity ) continue; pEntity->SetAbsOrigin( pEntity->GetAbsOrigin() - pList->pushVec[i] ); } CBaseEntity *pPhysicsBlocker = FindPhysicsBlocker( VPhysicsGetObject(), *pList, pList->pushVec[0] ); if ( pPhysicsBlocker ) { pBlocked = pPhysicsBlocker; } } else { Vector origin = GetLocalOrigin(); QAngle angles = GetLocalAngles(); if ( checkmove ) { origin -= GetLocalVelocity() * moveback; } if ( checkrot ) { // BUGBUG: This is pretty hack-tastic! angles -= GetLocalAngularVelocity() * moveback; } SetLocalOrigin( origin ); SetLocalAngles( angles ); } if ( pBlocked ) { Blocked( pBlocked ); } m_flLocalTime = physLocalTime; } } } } // this data is no longer useful, free the memory if ( HasDataObjectType(PHYSICSPUSHLIST) ) { DestroyDataObject( PHYSICSPUSHLIST ); } m_flVPhysicsUpdateLocalTime = m_flLocalTime; if ( m_flMoveDoneTime <= m_flLocalTime && m_flMoveDoneTime > 0 ) { SetMoveDoneTime( -1 ); MoveDone(); } } void CBaseEntity::SetMoveDoneTime( float flDelay ) { if (flDelay >= 0) { m_flMoveDoneTime = GetLocalTime() + flDelay; } else { m_flMoveDoneTime = -1; } CheckHasGamePhysicsSimulation(); } //----------------------------------------------------------------------------- // Purpose: Relinks all of a parents children into the collision tree //----------------------------------------------------------------------------- void CBaseEntity::PhysicsRelinkChildren( float dt ) { CBaseEntity *child; // iterate through all children for ( child = FirstMoveChild(); child != NULL; child = child->NextMovePeer() ) { if ( child->IsSolid() || child->IsSolidFlagSet(FSOLID_TRIGGER) ) { child->PhysicsTouchTriggers(); } // // Update their physics shadows. We should never have any children of // movetype VPHYSICS. // if ( child->GetMoveType() != MOVETYPE_VPHYSICS ) { child->UpdatePhysicsShadowToCurrentPosition( dt ); } else if ( child->GetOwnerEntity() != this ) { // the only case where this is valid is if this entity is an attached ragdoll. // So assert here to catch the non-ragdoll case. Assert( 0 ); } if ( child->FirstMoveChild() ) { child->PhysicsRelinkChildren(dt); } } } void CBaseEntity::PhysicsTouchTriggers( const Vector *pPrevAbsOrigin ) { edict_t *pEdict = edict(); if ( pEdict && !IsWorld() ) { Assert(CollisionProp()); bool isTriggerCheckSolids = IsSolidFlagSet( FSOLID_TRIGGER ); bool isSolidCheckTriggers = IsSolid() && !isTriggerCheckSolids; // NOTE: Moving triggers (items, ammo etc) are not // checked against other triggers to reduce the number of touchlinks created if ( !(isSolidCheckTriggers || isTriggerCheckSolids) ) return; if ( GetSolid() == SOLID_BSP ) { if ( !GetModel() && Q_strlen( STRING( GetModelName() ) ) == 0 ) { Warning( "Inserted %s with no model\n", GetClassname() ); return; } } SetCheckUntouch( true ); if ( isSolidCheckTriggers ) { engine->SolidMoved( pEdict, CollisionProp(), pPrevAbsOrigin, sm_bAccurateTriggerBboxChecks ); } if ( isTriggerCheckSolids ) { engine->TriggerMoved( pEdict, sm_bAccurateTriggerBboxChecks ); } } } void CBaseEntity::VPhysicsShadowCollision( int index, gamevcollisionevent_t *pEvent ) { } void CBaseEntity::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) { // filter out ragdoll props hitting other parts of itself too often // UNDONE: Store a sound time for this entity (not just this pair of objects) // and filter repeats on that? int otherIndex = !index; CBaseEntity *pHitEntity = pEvent->pEntities[otherIndex]; // Don't make sounds / effects if neither entity is MOVETYPE_VPHYSICS. The game // physics should have done so. if ( GetMoveType() != MOVETYPE_VPHYSICS && pHitEntity->GetMoveType() != MOVETYPE_VPHYSICS ) return; if ( pEvent->deltaCollisionTime < 0.5 && (pHitEntity == this) ) return; // don't make noise for hidden/invisible/sky materials surfacedata_t *phit = physprops->GetSurfaceData( pEvent->surfaceProps[otherIndex] ); const surfacedata_t *pprops = physprops->GetSurfaceData( pEvent->surfaceProps[index] ); if ( phit->game.material == 'X' || pprops->game.material == 'X' ) return; if ( pHitEntity == this ) { PhysCollisionSound( this, pEvent->pObjects[index], CHAN_BODY, pEvent->surfaceProps[index], pEvent->surfaceProps[otherIndex], pEvent->deltaCollisionTime, pEvent->collisionSpeed ); } else { PhysCollisionSound( this, pEvent->pObjects[index], CHAN_STATIC, pEvent->surfaceProps[index], pEvent->surfaceProps[otherIndex], pEvent->deltaCollisionTime, pEvent->collisionSpeed ); } PhysCollisionScreenShake( pEvent, index ); #if HL2_EPISODIC // episodic does something different for when advisor shields are struck if ( phit->game.material == 'Z' || pprops->game.material == 'Z') { PhysCollisionWarpEffect( pEvent, phit ); } else { PhysCollisionDust( pEvent, phit ); } #else PhysCollisionDust( pEvent, phit ); #endif } void CBaseEntity::VPhysicsFriction( IPhysicsObject *pObject, float energy, int surfaceProps, int surfacePropsHit ) { PhysFrictionSound( this, pObject, energy, surfaceProps, surfacePropsHit ); } void CBaseEntity::VPhysicsSwapObject( IPhysicsObject *pSwap ) { if ( !pSwap ) { PhysRemoveShadow(this); } if ( !m_pPhysicsObject ) { Warning( "Bad vphysics swap for %s\n", STRING(m_iClassname) ); } m_pPhysicsObject = pSwap; } // Tells the physics shadow to update it's target to the current position void CBaseEntity::UpdatePhysicsShadowToCurrentPosition( float deltaTime ) { if ( GetMoveType() != MOVETYPE_VPHYSICS ) { IPhysicsObject *pPhys = VPhysicsGetObject(); if ( pPhys ) { pPhys->UpdateShadow( GetAbsOrigin(), GetAbsAngles(), false, deltaTime ); } } } int CBaseEntity::VPhysicsGetObjectList( IPhysicsObject **pList, int listMax ) { IPhysicsObject *pPhys = VPhysicsGetObject(); if ( pPhys ) { // multi-object entities must implement this function Assert( !(pPhys->GetGameFlags() & FVPHYSICS_MULTIOBJECT_ENTITY) ); if ( listMax > 0 ) { pList[0] = pPhys; return 1; } } return 0; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CBaseEntity::VPhysicsIsFlesh( void ) { IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; int count = VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); for ( int i = 0; i < count; i++ ) { int material = pList[i]->GetMaterialIndex(); const surfacedata_t *pSurfaceData = physprops->GetSurfaceData( material ); // Is flesh ?, don't allow pickup if ( pSurfaceData->game.material == CHAR_TEX_ANTLION || pSurfaceData->game.material == CHAR_TEX_FLESH || pSurfaceData->game.material == CHAR_TEX_BLOODYFLESH || pSurfaceData->game.material == CHAR_TEX_ALIENFLESH ) return true; } return false; } bool CBaseEntity::Intersects( CBaseEntity *pOther ) { if ( !edict() || !pOther->edict() ) return false; CCollisionProperty *pMyProp = CollisionProp(); CCollisionProperty *pOtherProp = pOther->CollisionProp(); return IsOBBIntersectingOBB( pMyProp->GetCollisionOrigin(), pMyProp->GetCollisionAngles(), pMyProp->OBBMins(), pMyProp->OBBMaxs(), pOtherProp->GetCollisionOrigin(), pOtherProp->GetCollisionAngles(), pOtherProp->OBBMins(), pOtherProp->OBBMaxs() ); } extern ConVar ai_LOS_mode; //========================================================= // FVisible - returns true if a line can be traced from // the caller's eyes to the target //========================================================= bool CBaseEntity::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker ) { VPROF( "CBaseEntity::FVisible" ); if ( pEntity->GetFlags() & FL_NOTARGET ) return false; #if HL1_DLL // FIXME: only block LOS through opaque water // don't look through water if ((m_nWaterLevel != 3 && pEntity->m_nWaterLevel == 3) || (m_nWaterLevel == 3 && pEntity->m_nWaterLevel == 0)) return false; #endif Vector vecLookerOrigin = EyePosition();//look through the caller's 'eyes' Vector vecTargetOrigin = pEntity->EyePosition(); trace_t tr; if ( !IsXbox() && ai_LOS_mode.GetBool() ) { UTIL_TraceLine(vecLookerOrigin, vecTargetOrigin, traceMask, this, COLLISION_GROUP_NONE, &tr); } else { // If we're doing an LOS search, include NPCs. if ( traceMask == MASK_BLOCKLOS ) { traceMask = MASK_BLOCKLOS_AND_NPCS; } // Player sees through nodraw if ( IsPlayer() ) { traceMask &= ~CONTENTS_BLOCKLOS; } // Use the custom LOS trace filter CTraceFilterLOS traceFilter( this, COLLISION_GROUP_NONE, pEntity ); UTIL_TraceLine( vecLookerOrigin, vecTargetOrigin, traceMask, &traceFilter, &tr ); } if (tr.fraction != 1.0 || tr.startsolid ) { // If we hit the entity we're looking for, it's visible if ( tr.m_pEnt == pEntity ) return true; // Got line of sight on the vehicle the player is driving! if ( pEntity && pEntity->IsPlayer() ) { CBasePlayer *pPlayer = assert_cast( 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( 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 "<>"; 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 char szBuffer[256]; // mapper debug message if (pCaller != NULL) { Q_snprintf( szBuffer, sizeof(szBuffer), "(%0.2f) input %s: %s.%s(%s)\n", gpGlobals->curtime, STRING(pCaller->m_iName.Get()), GetDebugName(), szInputName, Value.String() ); } else { Q_snprintf( szBuffer, sizeof(szBuffer), "(%0.2f) input : %s.%s(%s)\n", gpGlobals->curtime, GetDebugName(), szInputName, Value.String() ); } DevMsg( 2, "%s", szBuffer ); ADD_DEBUG_HISTORY( HISTORY_ENTITY_IO, szBuffer ); if (m_debugOverlays & OVERLAY_MESSAGE_BIT) { DrawInputOverlay(szInputName,pCaller,Value); } // convert the value if necessary if ( Value.FieldType() != dmap->dataDesc[i].fieldType ) { if ( !(Value.FieldType() == FIELD_VOID && dmap->dataDesc[i].fieldType == FIELD_STRING) ) // allow empty strings { #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) : "", ( pCaller != NULL ) ? STRING(pCaller->m_iName.Get()) : "", 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) : "", ( pCaller != NULL ) ? STRING(pCaller->m_iName.Get()) : "" ); 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 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) ScriptVariant_t functionReturn; if ( m_ScriptScope.IsInitialized() ) { 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 if( CallScriptFunction( szScriptFunctionName, &functionReturn ) ) { bCallInputFunc = functionReturn.m_bool; } } if( bCallInputFunc ) { (this->*pfnInput)( data ); } if ( m_ScriptScope.IsInitialized() ) { g_pScriptVM->ClearValue( "activator" ); g_pScriptVM->ClearValue( "caller" ); #ifdef MAPBASE_VSCRIPT g_pScriptVM->ClearValue( "parameter" ); #endif } } else if ( dmap->dataDesc[i].flags & FTYPEDESC_KEY ) { // set the value directly Value.SetOther( ((char*)this) + dmap->dataDesc[i].fieldOffset[ TD_OFFSET_NORMAL ]); // TODO: if this becomes evil and causes too many full entity updates, then we should make // a macro like this: // // define MAKE_INPUTVAR(x) void Note##x##Modified() { x.GetForModify(); } // // Then the datadesc points at that function and we call it here. The only pain is to add // that function for all the DEFINE_INPUT calls. NetworkStateChanged(); } return true; } } } } DevMsg( 2, "unhandled input: (%s) -> (%s,%s)\n", szInputName, STRING(m_iClassname), GetDebugName()/*,", from (%s,%s)" STRING(pCaller->m_iClassname), STRING(pCaller->m_iName.Get())*/ ); return false; } //----------------------------------------------------------------------------- // Purpose: Input handler for the entity alpha. // Input : nAlpha - Alpha value (0 - 255). //----------------------------------------------------------------------------- void CBaseEntity::InputAlpha( inputdata_t &inputdata ) { SetRenderColorA( clamp( inputdata.value.Int(), 0, 255 ) ); } //----------------------------------------------------------------------------- // Activate alternative sorting //----------------------------------------------------------------------------- void CBaseEntity::InputAlternativeSorting( inputdata_t &inputdata ) { m_bAlternateSorting = inputdata.value.Bool(); } //----------------------------------------------------------------------------- // Purpose: Input handler for the entity color. Ignores alpha since that is handled // by a separate input handler. // Input : Color32 new value for color (alpha is ignored). //----------------------------------------------------------------------------- void CBaseEntity::InputColor( inputdata_t &inputdata ) { color32 clr = inputdata.value.Color32(); SetRenderColor( clr.r, clr.g, clr.b ); } //----------------------------------------------------------------------------- // Purpose: Called whenever the entity is 'Used'. This can be when a player hits // use, or when an entity targets it without an output name (legacy entities) //----------------------------------------------------------------------------- void CBaseEntity::InputUse( inputdata_t &inputdata ) { Use( inputdata.pActivator, inputdata.pCaller, (USE_TYPE)inputdata.nOutputID, 0 ); } //----------------------------------------------------------------------------- // Purpose: Reads an output variable, by string name, from an entity // Input : char *varName - the string name of the variable // variant_t *var - the value is stored here // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CBaseEntity::ReadKeyField( const char *varName, variant_t *var ) { if ( !varName ) return false; // loop through the data description list, restoring each data desc block for ( datamap_t *dmap = GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap ) { // search through all the readable fields in the data description, looking for a match for ( int i = 0; i < dmap->dataNumFields; i++ ) { if ( dmap->dataDesc[i].flags & (FTYPEDESC_OUTPUT | FTYPEDESC_KEY) ) { if ( !Q_stricmp(dmap->dataDesc[i].externalName, varName) ) { var->Set( dmap->dataDesc[i].fieldType, ((char*)this) + dmap->dataDesc[i].fieldOffset[ TD_OFFSET_NORMAL ] ); return true; } } } } return false; } //----------------------------------------------------------------------------- // Purpose: Sets the damage filter on the object //----------------------------------------------------------------------------- void CBaseEntity::InputEnableDamageForces( inputdata_t &inputdata ) { RemoveEFlags( EFL_NO_DAMAGE_FORCES ); } void CBaseEntity::InputDisableDamageForces( inputdata_t &inputdata ) { AddEFlags( EFL_NO_DAMAGE_FORCES ); } //----------------------------------------------------------------------------- // Purpose: Sets the damage filter on the object //----------------------------------------------------------------------------- void CBaseEntity::InputSetDamageFilter( inputdata_t &inputdata ) { // Get a handle to my damage filter entity if there is one. m_iszDamageFilterName = inputdata.value.StringID(); if ( m_iszDamageFilterName != NULL_STRING ) { m_hDamageFilter = gEntList.FindEntityByName( NULL, m_iszDamageFilterName ); } else { m_hDamageFilter = NULL; } } //----------------------------------------------------------------------------- // Purpose: Dispatch effects on this entity //----------------------------------------------------------------------------- void CBaseEntity::InputDispatchEffect( inputdata_t &inputdata ) { const char *sEffect = inputdata.value.String(); if ( sEffect && sEffect[0] ) { CEffectData data; GetInputDispatchEffectPosition( sEffect, data.m_vOrigin, data.m_vAngles ); AngleVectors( data.m_vAngles, &data.m_vNormal ); data.m_vStart = data.m_vOrigin; data.m_nEntIndex = entindex(); // Clip off leading attachment point numbers while ( sEffect[0] >= '0' && sEffect[0] <= '9' ) { sEffect++; } DispatchEffect( sEffect, data ); } } //----------------------------------------------------------------------------- // Purpose: Returns the origin at which to play an inputted dispatcheffect //----------------------------------------------------------------------------- void CBaseEntity::GetInputDispatchEffectPosition( const char *sInputString, Vector &pOrigin, QAngle &pAngles ) { pOrigin = GetAbsOrigin(); pAngles = GetAbsAngles(); } //----------------------------------------------------------------------------- // Purpose: Marks the entity for deletion //----------------------------------------------------------------------------- void CBaseEntity::InputKill( inputdata_t &inputdata ) { // tell owner ( if any ) that we're dead.This is mostly for NPCMaker functionality. CBaseEntity *pOwner = GetOwnerEntity(); if ( pOwner ) { pOwner->DeathNotice( this ); SetOwnerEntity( NULL ); } #ifdef MAPBASE m_OnKilled.FireOutput( inputdata.pActivator, this ); #endif UTIL_Remove( this ); } void CBaseEntity::InputKillHierarchy( inputdata_t &inputdata ) { CBaseEntity *pChild, *pNext; for ( pChild = FirstMoveChild(); pChild; pChild = pNext ) { pNext = pChild->NextMovePeer(); pChild->InputKillHierarchy( inputdata ); } // tell owner ( if any ) that we're dead. This is mostly for NPCMaker functionality. CBaseEntity *pOwner = GetOwnerEntity(); if ( pOwner ) { pOwner->DeathNotice( this ); SetOwnerEntity( NULL ); } #ifdef MAPBASE m_OnKilled.FireOutput( inputdata.pActivator, this ); #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 ) { } //----------------------------------------------------------------------------- // 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 &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 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 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 \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 \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 \n", FCVAR_CHEAT); //------------------------------------------------------------------------------ void CC_Find_Ent_Index( const CCommand& args ) { if ( args.ArgC() < 2 ) { Msg( "Format: find_ent_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 \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 \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 \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 [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 [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 \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 \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 \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 dmap_namelist; CUtlVector< CUtlVector > dmap_fieldlist; CUtlVector< CUtlVector > 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 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 \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 ) { p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), NULL ); 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(); } //----------------------------------------------------------------------------- // 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; p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), &duration ); 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: :::: // // 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: ::::\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: :\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: :::: // // 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: ::::\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: 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 ) { Touch(inputdata.value.Entity()); } //----------------------------------------------------------------------------- // 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; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- 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(); } SetContextThink(&CBaseEntity::ScriptThink, gpGlobals->curtime + flThinkFrequency, "ScriptThink"); } else { DevWarning("%s FAILED to call script think function %s!\n", GetDebugName(), STRING(m_iszScriptThinkFunction)); } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- const char* CBaseEntity::GetScriptId() { 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; const char *p = contextName; while ( p ) { duration = 0.0f; p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), &duration ); if ( duration ) { duration += gpGlobals->curtime; } int iIndex = FindContextByName( key ); if ( iIndex != -1 ) { // Set the existing context to the new value m_ResponseContexts[iIndex].m_iszValue = AllocPooledString( value ); m_ResponseContexts[iIndex].m_fExpirationTime = duration; continue; } ResponseContext_t newContext; newContext.m_iszName = AllocPooledString( key ); newContext.m_iszValue = AllocPooledString( value ); newContext.m_fExpirationTime = duration; m_ResponseContexts.AddToTail( newContext ); } } #ifdef MAPBASE 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 m_ResponseContexts[iIndex].m_iszValue = AllocPooledString( value ); m_ResponseContexts[iIndex].m_fExpirationTime = duration; return; } ResponseContext_t newContext; newContext.m_iszName = AllocPooledString( name ); newContext.m_iszValue = AllocPooledString( value ); newContext.m_fExpirationTime = duration; m_ResponseContexts.AddToTail( newContext ); } #endif //----------------------------------------------------------------------------- // 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: ,,,,\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: ,,,,\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 ) { 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 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; } } //----------------------------------------------------------------------------- // 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(this); return NULL; } ConVar step_spline( "step_spline", "0" ); //----------------------------------------------------------------------------- // Purpose: Run one tick's worth of faked simulation // Input : *step - //----------------------------------------------------------------------------- void CBaseEntity::ComputeStepSimulationNetwork( StepSimulationData *step ) { if ( !step ) { Assert( !"ComputeStepSimulationNetworkOriginAndAngles with NULL step\n" ); return; } // Don't run again if we've already calculated this tick if ( step->m_nLastProcessTickCount == gpGlobals->tickcount ) { return; } step->m_nLastProcessTickCount = gpGlobals->tickcount; // Origin // It's inactive if ( step->m_bOriginActive ) { // First see if any external code moved the entity if ( GetStepOrigin() != step->m_Next.vecOrigin ) { step->m_bOriginActive = false; } else { // Compute interpolated info based on tick interval float frac = 1.0f; int tickdelta = step->m_Next.nTickCount - step->m_Previous.nTickCount; if ( tickdelta > 0 ) { frac = (float)( gpGlobals->tickcount - step->m_Previous.nTickCount ) / (float) tickdelta; frac = clamp( frac, 0.0f, 1.0f ); } if (step->m_Previous2.nTickCount == 0 || step->m_Previous2.nTickCount >= step->m_Previous.nTickCount) { Vector delta = step->m_Next.vecOrigin - step->m_Previous.vecOrigin; VectorMA( step->m_Previous.vecOrigin, frac, delta, step->m_vecNetworkOrigin ); } else if (!step_spline.GetBool()) { StepSimulationStep *pOlder = &step->m_Previous; StepSimulationStep *pNewer = &step->m_Next; if (step->m_Discontinuity.nTickCount > step->m_Previous.nTickCount) { if (gpGlobals->tickcount > step->m_Discontinuity.nTickCount) { pOlder = &step->m_Discontinuity; } else { pNewer = &step->m_Discontinuity; } tickdelta = pNewer->nTickCount - pOlder->nTickCount; if ( tickdelta > 0 ) { frac = (float)( gpGlobals->tickcount - pOlder->nTickCount ) / (float) tickdelta; frac = clamp( frac, 0.0f, 1.0f ); } } Vector delta = pNewer->vecOrigin - pOlder->vecOrigin; VectorMA( pOlder->vecOrigin, frac, delta, step->m_vecNetworkOrigin ); } else { Hermite_Spline( step->m_Previous2.vecOrigin, step->m_Previous.vecOrigin, step->m_Next.vecOrigin, frac, step->m_vecNetworkOrigin ); } } } // Angles if ( step->m_bAnglesActive ) { // See if external code changed the orientation of the entity if ( GetStepAngles() != step->m_angNextRotation ) { step->m_bAnglesActive = false; } else { // Compute interpolated info based on tick interval float frac = 1.0f; int tickdelta = step->m_Next.nTickCount - step->m_Previous.nTickCount; if ( tickdelta > 0 ) { frac = (float)( gpGlobals->tickcount - step->m_Previous.nTickCount ) / (float) tickdelta; frac = clamp( frac, 0.0f, 1.0f ); } if (step->m_Previous2.nTickCount == 0 || step->m_Previous2.nTickCount >= step->m_Previous.nTickCount) { // Pure blend between start/end orientations Quaternion outangles; QuaternionBlend( step->m_Previous.qRotation, step->m_Next.qRotation, frac, outangles ); QuaternionAngles( outangles, step->m_angNetworkAngles ); } else if (!step_spline.GetBool()) { StepSimulationStep *pOlder = &step->m_Previous; StepSimulationStep *pNewer = &step->m_Next; if (step->m_Discontinuity.nTickCount > step->m_Previous.nTickCount) { if (gpGlobals->tickcount > step->m_Discontinuity.nTickCount) { pOlder = &step->m_Discontinuity; } else { pNewer = &step->m_Discontinuity; } tickdelta = pNewer->nTickCount - pOlder->nTickCount; if ( tickdelta > 0 ) { frac = (float)( gpGlobals->tickcount - pOlder->nTickCount ) / (float) tickdelta; frac = clamp( frac, 0.0f, 1.0f ); } } // Pure blend between start/end orientations Quaternion outangles; QuaternionBlend( pOlder->qRotation, pNewer->qRotation, frac, outangles ); QuaternionAngles( outangles, step->m_angNetworkAngles ); } else { // FIXME: enable spline interpolation when turning is debounced. Quaternion outangles; Hermite_Spline( step->m_Previous2.qRotation, step->m_Previous.qRotation, step->m_Next.qRotation, frac, outangles ); QuaternionAngles( outangles, step->m_angNetworkAngles ); } } } } //----------------------------------------------------------------------------- bool CBaseEntity::UseStepSimulationNetworkOrigin( const Vector **out_v ) { Assert( out_v ); if ( g_bTestMoveTypeStepSimulation && GetMoveType() == MOVETYPE_STEP && HasDataObjectType( STEPSIMULATION ) ) { StepSimulationData *step = ( StepSimulationData * )GetDataObject( STEPSIMULATION ); ComputeStepSimulationNetwork( step ); *out_v = &step->m_vecNetworkOrigin; return step->m_bOriginActive; } return false; } //----------------------------------------------------------------------------- bool CBaseEntity::UseStepSimulationNetworkAngles( const QAngle **out_a ) { Assert( out_a ); if ( g_bTestMoveTypeStepSimulation && GetMoveType() == MOVETYPE_STEP && HasDataObjectType( STEPSIMULATION ) ) { StepSimulationData *step = ( StepSimulationData * )GetDataObject( STEPSIMULATION ); ComputeStepSimulationNetwork( step ); *out_a = &step->m_angNetworkAngles; return step->m_bAnglesActive; } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CBaseEntity::AddStepDiscontinuity( float flTime, const Vector &vecOrigin, const QAngle &vecAngles ) { if ((GetMoveType() != MOVETYPE_STEP ) || !HasDataObjectType( STEPSIMULATION ) ) { return false; } StepSimulationData *step = ( StepSimulationData * )GetDataObject( STEPSIMULATION ); if (!step) { Assert( 0 ); return false; } step->m_Discontinuity.nTickCount = TIME_TO_TICKS( flTime ); step->m_Discontinuity.vecOrigin = vecOrigin; AngleQuaternion( vecAngles, step->m_Discontinuity.qRotation ); return true; } Vector CBaseEntity::GetStepOrigin( void ) const { return GetLocalOrigin(); } QAngle CBaseEntity::GetStepAngles( void ) const { return GetLocalAngles(); } //----------------------------------------------------------------------------- // Purpose: For each client who appears to be a valid recipient, checks the client has disabled CC and if so, removes them from // the recipient list. // Input : filter - //----------------------------------------------------------------------------- void CBaseEntity::RemoveRecipientsIfNotCloseCaptioning( CRecipientFilter& filter ) { int c = filter.GetRecipientCount(); for ( int i = c - 1; i >= 0; --i ) { int playerIndex = filter.GetRecipientIndex( i ); CBasePlayer *player = static_cast< CBasePlayer * >( CBaseEntity::Instance( playerIndex ) ); if ( !player ) continue; #if !defined( _XBOX ) const char *cvarvalue = engine->GetClientConVarValue( playerIndex, "closecaption" ); Assert( cvarvalue ); if ( !cvarvalue[ 0 ] ) continue; int value = atoi( cvarvalue ); #else static ConVar *s_pCloseCaption = NULL; if ( !s_pCloseCaption ) { s_pCloseCaption = cvar->FindVar( "closecaption" ); if ( !s_pCloseCaption ) { Error( "XBOX couldn't find closecaption convar!!!" ); } } int value = s_pCloseCaption->GetInt(); #endif // No close captions? if ( value == 0 ) { filter.RemoveRecipient( player ); } } } //----------------------------------------------------------------------------- // Purpose: Wrapper to emit a sentence and also a close caption token for the sentence as appropriate. // Input : filter - // iEntIndex - // iChannel - // iSentenceIndex - // flVolume - // iSoundlevel - // iFlags - // iPitch - // bUpdatePositions - // soundtime - //----------------------------------------------------------------------------- void CBaseEntity::EmitSentenceByIndex( IRecipientFilter& filter, int iEntIndex, int iChannel, int iSentenceIndex, float flVolume, soundlevel_t iSoundlevel, int iFlags /*= 0*/, int iPitch /*=PITCH_NORM*/, const Vector *pOrigin /*=NULL*/, const Vector *pDirection /*=NULL*/, bool bUpdatePositions /*=true*/, float soundtime /*=0.0f*/ ) { CUtlVector< Vector > dummy; enginesound->EmitSentenceByIndex( filter, iEntIndex, iChannel, iSentenceIndex, flVolume, iSoundlevel, iFlags, iPitch, 0, pOrigin, pDirection, &dummy, bUpdatePositions, soundtime ); } void CBaseEntity::SetRefEHandle( const CBaseHandle &handle ) { m_RefEHandle = handle; if ( edict() ) { COMPILE_TIME_ASSERT( NUM_NETWORKED_EHANDLE_SERIAL_NUMBER_BITS <= 8*sizeof( edict()->m_NetworkSerialNumber ) ); edict()->m_NetworkSerialNumber = (m_RefEHandle.GetSerialNumber() & (1 << NUM_NETWORKED_EHANDLE_SERIAL_NUMBER_BITS) - 1); } } bool CPointEntity::KeyValue( const char *szKeyName, const char *szValue ) { if ( FStrEq( szKeyName, "mins" ) || FStrEq( szKeyName, "maxs" ) ) { Warning("Warning! Can't specify mins/maxs for point entities! (%s)\n", GetClassname() ); return true; } return BaseClass::KeyValue( szKeyName, szValue ); } bool CServerOnlyPointEntity::KeyValue( const char *szKeyName, const char *szValue ) { if ( FStrEq( szKeyName, "mins" ) || FStrEq( szKeyName, "maxs" ) ) { Warning("Warning! Can't specify mins/maxs for point entities! (%s)\n", GetClassname() ); return true; } return BaseClass::KeyValue( szKeyName, szValue ); } bool CLogicalEntity::KeyValue( const char *szKeyName, const char *szValue ) { if ( FStrEq( szKeyName, "mins" ) || FStrEq( szKeyName, "maxs" ) ) { Warning("Warning! Can't specify mins/maxs for point entities! (%s)\n", GetClassname() ); return true; } return BaseClass::KeyValue( szKeyName, szValue ); } //----------------------------------------------------------------------------- // Purpose: Sets the entity invisible, and makes it remove itself on the next frame //----------------------------------------------------------------------------- void CBaseEntity::RemoveDeferred( void ) { // Set our next think to remove us SetThink( &CBaseEntity::SUB_Remove ); SetNextThink( gpGlobals->curtime + 0.1f ); // Hide us completely AddEffects( EF_NODRAW ); AddSolidFlags( FSOLID_NOT_SOLID ); SetMoveType( MOVETYPE_NONE ); } #define MIN_CORPSE_FADE_TIME 10.0 #define MIN_CORPSE_FADE_DIST 256.0 #define MAX_CORPSE_FADE_DIST 1500.0 // // fade out - slowly fades a entity out, then removes it. // // DON'T USE ME FOR GIBS AND STUFF IN MULTIPLAYER! // SET A FUTURE THINK AND A RENDERMODE!! void CBaseEntity::SUB_StartFadeOut( float delay, bool notSolid ) { SetThink( &CBaseEntity::SUB_FadeOut ); SetNextThink( gpGlobals->curtime + delay ); SetRenderColorA( 255 ); m_nRenderMode = kRenderNormal; if ( notSolid ) { AddSolidFlags( FSOLID_NOT_SOLID ); SetLocalAngularVelocity( vec3_angle ); } } void CBaseEntity::SUB_StartFadeOutInstant() { SUB_StartFadeOut( 0, true ); } //----------------------------------------------------------------------------- // Purpose: Vanish when players aren't looking //----------------------------------------------------------------------------- void CBaseEntity::SUB_Vanish( void ) { //Always think again next frame SetNextThink( gpGlobals->curtime + 0.1f ); CBasePlayer *pPlayer; //Get all players for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { //Get the next client if ( ( pPlayer = UTIL_PlayerByIndex( i ) ) != NULL ) { Vector corpseDir = (GetAbsOrigin() - pPlayer->WorldSpaceCenter() ); float flDistSqr = corpseDir.LengthSqr(); //If the player is close enough, don't fade out if ( flDistSqr < (MIN_CORPSE_FADE_DIST*MIN_CORPSE_FADE_DIST) ) return; // If the player's far enough away, we don't care about looking at it if ( flDistSqr < (MAX_CORPSE_FADE_DIST*MAX_CORPSE_FADE_DIST) ) { VectorNormalize( corpseDir ); Vector plForward; pPlayer->EyeVectors( &plForward ); float dot = plForward.Dot( corpseDir ); if ( dot > 0.0f ) return; } } } //If we're here, then we can vanish safely m_iHealth = 0; SetThink( &CBaseEntity::SUB_Remove ); } void CBaseEntity::SUB_PerformFadeOut( void ) { float dt = gpGlobals->frametime; if ( dt > 0.1f ) { dt = 0.1f; } m_nRenderMode = kRenderTransTexture; int speed = MAX(1,256*dt); // fade out over 1 second SetRenderColorA( UTIL_Approach( 0, m_clrRender->a, speed ) ); } bool CBaseEntity::SUB_AllowedToFade( void ) { if( VPhysicsGetObject() ) { if( VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD || GetEFlags() & EFL_IS_BEING_LIFTED_BY_BARNACLE ) return false; } // on Xbox, allow these to fade out #ifndef _XBOX CBasePlayer *pPlayer = ( AI_IsSinglePlayer() ) ? UTIL_GetLocalPlayer() : NULL; if ( pPlayer && pPlayer->FInViewCone( this ) ) return false; #endif return true; } //----------------------------------------------------------------------------- // Purpose: Fade out slowly //----------------------------------------------------------------------------- void CBaseEntity::SUB_FadeOut( void ) { if ( SUB_AllowedToFade() == false ) { SetNextThink( gpGlobals->curtime + 1 ); SetRenderColorA( 255 ); return; } SUB_PerformFadeOut(); if ( m_clrRender->a == 0 ) { #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; } 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++) { Log( "%s executing script: %s\n", GetDebugName(), szScripts[i]); 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; } 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; } 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); } } HSCRIPT CBaseEntity::GetScriptOwnerEntity() { return ToHScript(GetOwnerEntity()); } void CBaseEntity::SetScriptOwnerEntity(HSCRIPT pOwner) { SetOwnerEntity(ToEnt(pOwner)); } //----------------------------------------------------------------------------- // 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 m_pScriptModelKeyValues = new CScriptKeyValues( pModelKeyValues ); // UNDONE: who calls ReleaseInstance on this??? Does name need to be unique??? hScript = g_pScriptVM->RegisterInstance( m_pScriptModelKeyValues ); /* 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( 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 ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- 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; } #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 ... ", 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 ... ", 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 ... ", 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 \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 ", 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 \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 ", 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 \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 ", FCVAR_CHEAT);