//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "decals.h" #include "effect_dispatch_data.h" #include "model_types.h" #include "gamestringpool.h" #include "ammodef.h" #include "takedamageinfo.h" #include "shot_manipulator.h" #include "ai_debug_shared.h" #include "mapentities_shared.h" #include "debugoverlay_shared.h" #include "coordsize.h" #include "AI_Criteria.h" #ifdef CLIENT_DLL #include "c_te_effect_dispatch.h" #else #include "te_effect_dispatch.h" #include "soundent.h" #include "iservervehicle.h" #include "player_pickup.h" #include "waterbullet.h" #include "func_break.h" #include "GameStats.h" #include "globalstate.h" #include "world.h" #endif #ifdef HL2_EPISODIC ConVar hl2_episodic( "hl2_episodic", "1", FCVAR_REPLICATED ); #else ConVar hl2_episodic( "hl2_episodic", "0", FCVAR_REPLICATED ); #endif//HL2_EPISODIC #include "rumble_shared.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #ifdef GAME_DLL ConVar ent_debugkeys( "ent_debugkeys", "" ); extern bool ParseKeyvalue( void *pObject, typedescription_t *pFields, int iNumFields, const char *szKeyName, const char *szValue ); extern bool ExtractKeyvalue( void *pObject, typedescription_t *pFields, int iNumFields, const char *szKeyName, char *szValue, int iMaxLen ); #endif bool CBaseEntity::m_bAllowPrecache = false; bool CBaseEntity::sm_bAccurateTriggerBboxChecks = true; // set to false for legacy behavior in ep1 ConVar ai_shot_bias_min( "ai_shot_bias_min", "-1.0", FCVAR_REPLICATED ); ConVar ai_shot_bias_max( "ai_shot_bias_max", "1.0", FCVAR_REPLICATED ); ConVar ai_debug_shoot_positions( "ai_debug_shoot_positions", "0", FCVAR_REPLICATED | FCVAR_CHEAT ); DEFINE_LOGGING_CHANNEL_NO_TAGS( LOG_DEVELOPER_VERBOSE, "DeveloperVerbose" ); //----------------------------------------------------------------------------- // Purpose: Spawn some blood particles //----------------------------------------------------------------------------- void SpawnBlood(Vector vecSpot, const Vector &vecDir, int bloodColor, float flDamage) { UTIL_BloodDrips( vecSpot, vecDir, bloodColor, (int)flDamage ); } #if !defined( NO_ENTITY_PREDICTION ) //----------------------------------------------------------------------------- // The player drives simulation of this entity //----------------------------------------------------------------------------- void CBaseEntity::SetPlayerSimulated( CBasePlayer *pOwner ) { m_bIsPlayerSimulated = true; pOwner->AddToPlayerSimulationList( this ); m_hPlayerSimulationOwner = pOwner; } void CBaseEntity::UnsetPlayerSimulated( void ) { if ( m_hPlayerSimulationOwner != NULL ) { m_hPlayerSimulationOwner->RemoveFromPlayerSimulationList( this ); } m_hPlayerSimulationOwner = NULL; m_bIsPlayerSimulated = false; } #endif // position of eyes Vector CBaseEntity::EyePosition( void ) { return GetAbsOrigin() + m_vecViewOffset; } const QAngle &CBaseEntity::EyeAngles( void ) { return GetAbsAngles(); } const QAngle &CBaseEntity::LocalEyeAngles( void ) { return GetLocalAngles(); } // position of ears Vector CBaseEntity::EarPosition( void ) { return EyePosition( ); } void CBaseEntity::SetViewOffset( const Vector& v ) { m_vecViewOffset = v; } const Vector& CBaseEntity::GetViewOffset() const { return m_vecViewOffset; } //----------------------------------------------------------------------------- // center point of entity //----------------------------------------------------------------------------- const Vector &CBaseEntity::WorldSpaceCenter( ) const { return CollisionProp()->WorldSpaceCenter(); } #if !defined( CLIENT_DLL ) #define CHANGE_FLAGS(flags,newFlags) { unsigned int old = flags; flags = (newFlags); gEntList.ReportEntityFlagsChanged( this, old, flags ); } #else #define CHANGE_FLAGS(flags,newFlags) (flags = (newFlags)) #endif void CBaseEntity::AddFlag( int flags ) { CHANGE_FLAGS( m_fFlags, m_fFlags | flags ); } void CBaseEntity::RemoveFlag( int flagsToRemove ) { CHANGE_FLAGS( m_fFlags, m_fFlags & ~flagsToRemove ); } void CBaseEntity::ClearFlags( void ) { CHANGE_FLAGS( m_fFlags, 0 ); } void CBaseEntity::ToggleFlag( int flagToToggle ) { CHANGE_FLAGS( m_fFlags, m_fFlags ^ flagToToggle ); } void CBaseEntity::SetEffects( int nEffects ) { if ( nEffects != m_fEffects ) { #if !defined( CLIENT_DLL ) #ifdef HL2_EPISODIC // Hack for now, to avoid player emitting radius with his flashlight if ( !IsPlayer() ) { if ( (nEffects & (EF_BRIGHTLIGHT|EF_DIMLIGHT)) && !(m_fEffects & (EF_BRIGHTLIGHT|EF_DIMLIGHT)) ) { AddEntityToDarknessCheck( this ); } else if ( !(nEffects & (EF_BRIGHTLIGHT|EF_DIMLIGHT)) && (m_fEffects & (EF_BRIGHTLIGHT|EF_DIMLIGHT)) ) { RemoveEntityFromDarknessCheck( this ); } } #endif // HL2_EPISODIC #endif // !CLIENT_DLL m_fEffects = nEffects; #if !defined( CLIENT_DLL ) if ( nEffects & ( EF_NOINTERP ) ) { gEntList.AddPostClientMessageEntity( this ); } #endif if ( ( nEffects & EF_NOINTERP ) && IsPlayer() ) { ((CBasePlayer *)this)->IncrementEFNoInterpParity(); } #ifndef CLIENT_DLL DispatchUpdateTransmitState(); #else UpdateVisibility(); #endif } } void CBaseEntity::AddEffects( int nEffects ) { #if !defined( CLIENT_DLL ) #ifdef HL2_EPISODIC if ( (nEffects & (EF_BRIGHTLIGHT|EF_DIMLIGHT)) && !(m_fEffects & (EF_BRIGHTLIGHT|EF_DIMLIGHT)) ) { // Hack for now, to avoid player emitting radius with his flashlight if ( !IsPlayer() ) { AddEntityToDarknessCheck( this ); } } #endif // HL2_EPISODIC #endif // !CLIENT_DLL m_fEffects |= nEffects; #if !defined( CLIENT_DLL ) if ( nEffects & ( EF_NOINTERP ) ) { gEntList.AddPostClientMessageEntity( this ); } #else if ( m_fEffects & (EF_DIMLIGHT|EF_DIMLIGHT) ) { AddToEntityList(ENTITY_LIST_PRERENDER); } #endif if ( nEffects & EF_NODRAW) { #ifndef CLIENT_DLL DispatchUpdateTransmitState(); #else UpdateVisibility(); #endif } } void CBaseEntity::SetBlocksLOS( bool bBlocksLOS ) { if ( bBlocksLOS ) { RemoveEFlags( EFL_DONTBLOCKLOS ); } else { AddEFlags( EFL_DONTBLOCKLOS ); } } bool CBaseEntity::BlocksLOS( void ) { return !IsEFlagSet(EFL_DONTBLOCKLOS); } void CBaseEntity::SetAIWalkable( bool bBlocksLOS ) { if ( bBlocksLOS ) { RemoveEFlags( EFL_DONTWALKON ); } else { AddEFlags( EFL_DONTWALKON ); } } bool CBaseEntity::IsAIWalkable( void ) { return !IsEFlagSet(EFL_DONTWALKON); } //----------------------------------------------------------------------------- // Purpose: Handles keys and outputs from the BSP. // Input : mapData - Text block of keys and values from the BSP. //----------------------------------------------------------------------------- void CBaseEntity::ParseMapData( CEntityMapData *mapData ) { char keyName[MAPKEY_MAXLENGTH]; char value[MAPKEY_MAXLENGTH]; #ifdef _DEBUG #ifdef GAME_DLL ValidateDataDescription(); #endif // GAME_DLL #endif // _DEBUG // loop through all keys in the data block and pass the info back into the object if ( mapData->GetFirstKey(keyName, value) ) { do { KeyValue( keyName, value ); } while ( mapData->GetNextKey(keyName, value) ); } OnParseMapDataFinished(); } //----------------------------------------------------------------------------- // Parse data from a map file //----------------------------------------------------------------------------- bool CBaseEntity::KeyValue( const char *szKeyName, const char *szValue ) { //!! temp hack, until worldcraft is fixed // strip the # tokens from (duplicate) key names char *s = (char *)strchr( szKeyName, '#' ); if ( s ) { *s = '\0'; } if ( FStrEq( szKeyName, "rendercolor" ) || FStrEq( szKeyName, "rendercolor32" )) { color32 tmp; V_StringToColor32( &tmp, szValue ); SetRenderColor( tmp.r, tmp.g, tmp.b ); SetRenderAlpha( tmp.a ); return true; } if ( FStrEq( szKeyName, "renderamt" ) ) { SetRenderAlpha( Q_atoi( szValue ) ); return true; } if ( FStrEq( szKeyName, "disableshadows" )) { int val = atoi( szValue ); if (val) { AddEffects( EF_NOSHADOW ); } return true; } if ( FStrEq( szKeyName, "mins" )) { Vector mins; UTIL_StringToVector( mins.Base(), szValue ); CollisionProp()->SetCollisionBounds( mins, CollisionProp()->OBBMaxs() ); return true; } if ( FStrEq( szKeyName, "maxs" )) { Vector maxs; UTIL_StringToVector( maxs.Base(), szValue ); CollisionProp()->SetCollisionBounds( CollisionProp()->OBBMins(), maxs ); return true; } if ( FStrEq( szKeyName, "disablereceiveshadows" )) { int val = atoi( szValue ); if (val) { AddEffects( EF_NORECEIVESHADOW ); } return true; } if ( FStrEq( szKeyName, "nodamageforces" )) { int val = atoi( szValue ); if (val) { AddEFlags( EFL_NO_DAMAGE_FORCES ); } return true; } // Fix up single angles if( FStrEq( szKeyName, "angle" ) ) { static char szBuf[64]; float y = atof( szValue ); if (y >= 0) { Q_snprintf( szBuf,sizeof(szBuf), "%f %f %f", GetLocalAngles()[0], y, GetLocalAngles()[2] ); } else if ((int)y == -1) { Q_strncpy( szBuf, "-90 0 0", sizeof(szBuf) ); } else { Q_strncpy( szBuf, "90 0 0", sizeof(szBuf) ); } // Do this so inherited classes looking for 'angles' don't have to bother with 'angle' return KeyValue( szKeyName, szBuf ); } // NOTE: Have to do these separate because they set two values instead of one if( FStrEq( szKeyName, "angles" ) ) { QAngle angles; UTIL_StringToVector( angles.Base(), szValue ); // If you're hitting this assert, it's probably because you're // calling SetLocalAngles from within a KeyValues method.. use SetAbsAngles instead! Assert( (GetMoveParent() == NULL) && !IsEFlagSet( EFL_DIRTY_ABSTRANSFORM ) ); SetAbsAngles( angles ); return true; } if( FStrEq( szKeyName, "origin" ) ) { Vector vecOrigin; UTIL_StringToVector( vecOrigin.Base(), szValue ); // If you're hitting this assert, it's probably because you're // calling SetLocalOrigin from within a KeyValues method.. use SetAbsOrigin instead! Assert( (GetMoveParent() == NULL) && !IsEFlagSet( EFL_DIRTY_ABSTRANSFORM ) ); SetAbsOrigin( vecOrigin ); return true; } #ifdef GAME_DLL if ( FStrEq( szKeyName, "targetname" ) ) { m_iName = AllocPooledString( szValue ); return true; } // loop through the data description, and try and place the keys in if ( !*ent_debugkeys.GetString() ) { for ( datamap_t *dmap = GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap ) { if ( ::ParseKeyvalue(this, dmap->dataDesc, dmap->dataNumFields, szKeyName, szValue) ) return true; } } else { // debug version - can be used to see what keys have been parsed in bool printKeyHits = false; const char *debugName = ""; if ( *ent_debugkeys.GetString() && !Q_stricmp(ent_debugkeys.GetString(), STRING(m_iClassname)) ) { // Msg( "-- found entity of type %s\n", STRING(m_iClassname) ); printKeyHits = true; debugName = STRING(m_iClassname); } // loop through the data description, and try and place the keys in for ( datamap_t *dmap = GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap ) { if ( !printKeyHits && *ent_debugkeys.GetString() && !Q_stricmp(dmap->dataClassName, ent_debugkeys.GetString()) ) { // Msg( "-- found class of type %s\n", dmap->dataClassName ); printKeyHits = true; debugName = dmap->dataClassName; } if ( ::ParseKeyvalue(this, dmap->dataDesc, dmap->dataNumFields, szKeyName, szValue) ) { if ( printKeyHits ) Msg( "(%s) key: %-16s value: %s\n", debugName, szKeyName, szValue ); return true; } } if ( printKeyHits ) Msg( "!! (%s) key not handled: \"%s\" \"%s\"\n", STRING(m_iClassname), szKeyName, szValue ); } #else // HACK: Read dxlevels for client-side entities if ( FStrEq( szKeyName, "mincpulevel" )) { m_nMinCPULevel = atoi( szValue ); return true; } if ( FStrEq( szKeyName, "maxcpulevel" )) { m_nMaxCPULevel = atoi( szValue ); return true; } if ( FStrEq( szKeyName, "mingpulevel" )) { m_nMinGPULevel = atoi( szValue ); return true; } if ( FStrEq( szKeyName, "maxgpulevel" )) { m_nMaxGPULevel = atoi( szValue ); return true; } #endif // key hasn't been handled return false; } bool CBaseEntity::KeyValue( const char *szKeyName, float flValue ) { char string[256]; Q_snprintf(string,sizeof(string), "%f", flValue ); return KeyValue( szKeyName, string ); } bool CBaseEntity::KeyValue( const char *szKeyName, int nValue ) { char string[256]; Q_snprintf(string,sizeof(string), "%d", nValue ); return KeyValue( szKeyName, string ); } bool CBaseEntity::KeyValue( const char *szKeyName, const Vector &vecValue ) { char string[256]; Q_snprintf(string,sizeof(string), "%f %f %f", vecValue.x, vecValue.y, vecValue.z ); return KeyValue( szKeyName, string ); } //----------------------------------------------------------------------------- // Purpose: // Input : // Output : //----------------------------------------------------------------------------- bool CBaseEntity::GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ) { if ( FStrEq( szKeyName, "rendercolor" ) || FStrEq( szKeyName, "rendercolor32" )) { color24 tmp = GetRenderColor(); unsigned char a = GetRenderAlpha(); Q_snprintf( szValue, iMaxLen, "%d %d %d %d", tmp.r, tmp.g, tmp.b, a ); return true; } if ( FStrEq( szKeyName, "renderamt" ) ) { unsigned char a = GetRenderAlpha(); Q_snprintf( szValue, iMaxLen, "%d", a ); return true; } if ( FStrEq( szKeyName, "disableshadows" )) { Q_snprintf( szValue, iMaxLen, "%d", IsEffectActive( EF_NOSHADOW ) ); return true; } if ( FStrEq( szKeyName, "mins" )) { Assert( 0 ); return false; } if ( FStrEq( szKeyName, "maxs" )) { Assert( 0 ); return false; } if ( FStrEq( szKeyName, "disablereceiveshadows" )) { Q_snprintf( szValue, iMaxLen, "%d", IsEffectActive( EF_NORECEIVESHADOW ) ); return true; } if ( FStrEq( szKeyName, "nodamageforces" )) { Q_snprintf( szValue, iMaxLen, "%d", IsEffectActive( EFL_NO_DAMAGE_FORCES ) ); return true; } // Fix up single angles if( FStrEq( szKeyName, "angle" ) ) { return false; } // NOTE: Have to do these separate because they set two values instead of one if( FStrEq( szKeyName, "angles" ) ) { QAngle angles = GetAbsAngles(); Q_snprintf( szValue, iMaxLen, "%f %f %f", angles.x, angles.y, angles.z ); return true; } if( FStrEq( szKeyName, "origin" ) ) { Vector vecOrigin = GetAbsOrigin(); Q_snprintf( szValue, iMaxLen, "%f %f %f", vecOrigin.x, vecOrigin.y, vecOrigin.z ); return true; } #ifdef GAME_DLL if ( FStrEq( szKeyName, "targetname" ) ) { Q_snprintf( szValue, iMaxLen, "%s", STRING( GetEntityName() ) ); return true; } if ( FStrEq( szKeyName, "classname" ) ) { Q_snprintf( szValue, iMaxLen, "%s", GetClassname() ); return true; } for ( datamap_t *dmap = GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap ) { if ( ::ExtractKeyvalue( this, dmap->dataDesc, dmap->dataNumFields, szKeyName, szValue, iMaxLen ) ) return true; } #endif return false; } //----------------------------------------------------------------------------- // Purpose: // Input : collisionGroup - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CBaseEntity::ShouldCollide( int collisionGroup, int contentsMask ) const { if ( m_CollisionGroup == COLLISION_GROUP_DEBRIS ) { if ( ! (contentsMask & CONTENTS_DEBRIS) ) return false; } return true; } //----------------------------------------------------------------------------- // Purpose: // Input : seed - //----------------------------------------------------------------------------- void CBaseEntity::SetPredictionRandomSeed( const CUserCmd *cmd ) { if ( !cmd ) { m_nPredictionRandomSeed = -1; return; } m_nPredictionRandomSeed = ( cmd->random_seed ); } //------------------------------------------------------------------------------ // Purpose : Base implimentation for entity handling decals //------------------------------------------------------------------------------ void CBaseEntity::DecalTrace( trace_t *pTrace, char const *decalName ) { int index = decalsystem->GetDecalIndexForName( decalName ); if ( index < 0 ) return; Assert( pTrace->m_pEnt ); CBroadcastRecipientFilter filter; te->Decal( filter, 0.0, &pTrace->endpos, &pTrace->startpos, pTrace->GetEntityIndex(), pTrace->hitbox, index ); } //----------------------------------------------------------------------------- // Purpose: Base handling for impacts against entities //----------------------------------------------------------------------------- void CBaseEntity::ImpactTrace( trace_t *pTrace, int iDamageType, char *pCustomImpactName ) { VPROF( "CBaseEntity::ImpactTrace" ); Assert( pTrace->m_pEnt ); CBaseEntity *pEntity = pTrace->m_pEnt; // Build the impact data CEffectData data; data.m_vOrigin = pTrace->endpos; data.m_vStart = pTrace->startpos; data.m_nSurfaceProp = pTrace->surface.surfaceProps; if ( data.m_nSurfaceProp < 0 ) { data.m_nSurfaceProp = 0; } data.m_nDamageType = iDamageType; data.m_nHitBox = pTrace->hitbox; #ifdef CLIENT_DLL data.m_hEntity = ClientEntityList().EntIndexToHandle( pEntity->entindex() ); #else data.m_nEntIndex = pEntity->entindex(); #endif // Send it on its way if ( !pCustomImpactName ) { DispatchEffect( "Impact", data ); } else { DispatchEffect( pCustomImpactName, data ); } } //----------------------------------------------------------------------------- // Purpose: returns the damage decal to use, given a damage type // Input : bitsDamageType - the damage type // Output : the index of the damage decal to use //----------------------------------------------------------------------------- char const *CBaseEntity::DamageDecal( int bitsDamageType, int gameMaterial ) { if ( m_nRenderMode == kRenderTransAlpha ) return ""; if ( m_nRenderMode != kRenderNormal && gameMaterial == 'G' ) return "BulletProof"; if ( bitsDamageType == DMG_SLASH ) return "ManhackCut"; // This will get translated at a lower layer based on game material return "Impact.Concrete"; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CBaseEntity::GetIndexForThinkContext( const char *pszContext ) { for ( int i = 0; i < m_aThinkFunctions.Count(); i++ ) { if ( !Q_strncmp( STRING( m_aThinkFunctions[i].m_iszContext ), pszContext, MAX_CONTEXT_LENGTH ) ) return i; } return NO_THINK_CONTEXT; } //----------------------------------------------------------------------------- // Purpose: Get a fresh think context for this entity //----------------------------------------------------------------------------- int CBaseEntity::RegisterThinkContext( const char *szContext ) { int iIndex = GetIndexForThinkContext( szContext ); if ( iIndex != NO_THINK_CONTEXT ) return iIndex; // Make a new think func thinkfunc_t sNewFunc; Q_memset( &sNewFunc, 0, sizeof( sNewFunc ) ); sNewFunc.m_pfnThink = NULL; sNewFunc.m_nNextThinkTick = 0; sNewFunc.m_iszContext = AllocPooledString(szContext); // Insert it into our list return m_aThinkFunctions.AddToTail( sNewFunc ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- BASEPTR CBaseEntity::ThinkSet( BASEPTR func, float thinkTime, const char *szContext ) { #if !defined( CLIENT_DLL ) #ifdef _DEBUG #ifdef GNUC COMPILE_TIME_ASSERT( sizeof(func) == 8 ); #else COMPILE_TIME_ASSERT( sizeof(func) == 4 ); #endif #endif #endif // Old system? if ( !szContext ) { m_pfnThink = func; #if !defined( CLIENT_DLL ) #ifdef _DEBUG FunctionCheck( *(reinterpret_cast(&m_pfnThink)), "BaseThinkFunc" ); #endif #endif return m_pfnThink; } // Find the think function in our list, and if we couldn't find it, register it int iIndex = GetIndexForThinkContext( szContext ); if ( iIndex == NO_THINK_CONTEXT ) { iIndex = RegisterThinkContext( szContext ); } m_aThinkFunctions[ iIndex ].m_pfnThink = func; #if !defined( CLIENT_DLL ) #ifdef _DEBUG FunctionCheck( *(reinterpret_cast(&m_aThinkFunctions[ iIndex ].m_pfnThink)), szContext ); #endif #endif if ( thinkTime != 0 ) { int thinkTick = ( thinkTime == TICK_NEVER_THINK ) ? TICK_NEVER_THINK : TIME_TO_TICKS( thinkTime ); m_aThinkFunctions[ iIndex ].m_nNextThinkTick = thinkTick; CheckHasThinkFunction( thinkTick == TICK_NEVER_THINK ? false : true ); } return func; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseEntity::SetNextThink( float thinkTime, const char *szContext ) { int thinkTick = ( thinkTime == TICK_NEVER_THINK ) ? TICK_NEVER_THINK : TIME_TO_TICKS( thinkTime ); // Are we currently in a think function with a context? int iIndex = 0; if ( !szContext ) { #ifdef _DEBUG if ( m_iCurrentThinkContext != NO_THINK_CONTEXT ) { Msg( "Warning: Setting base think function within think context %s\n", STRING(m_aThinkFunctions[m_iCurrentThinkContext].m_iszContext) ); } #endif // Old system m_nNextThinkTick = thinkTick; CheckHasThinkFunction( thinkTick == TICK_NEVER_THINK ? false : true ); return; } else { // Find the think function in our list, and if we couldn't find it, register it iIndex = GetIndexForThinkContext( szContext ); if ( iIndex == NO_THINK_CONTEXT ) { iIndex = RegisterThinkContext( szContext ); } } // Old system m_aThinkFunctions[ iIndex ].m_nNextThinkTick = thinkTick; CheckHasThinkFunction( thinkTick == TICK_NEVER_THINK ? false : true ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CBaseEntity::GetNextThink( const char *szContext ) { // Are we currently in a think function with a context? int iIndex = 0; if ( !szContext ) { #ifdef _DEBUG if ( m_iCurrentThinkContext != NO_THINK_CONTEXT ) { Msg( "Warning: Getting base nextthink time within think context %s\n", STRING(m_aThinkFunctions[m_iCurrentThinkContext].m_iszContext) ); } #endif if ( m_nNextThinkTick == TICK_NEVER_THINK ) return TICK_NEVER_THINK; // Old system return TICK_INTERVAL * (m_nNextThinkTick ); } else { // Find the think function in our list iIndex = GetIndexForThinkContext( szContext ); } if ( iIndex == m_aThinkFunctions.InvalidIndex() ) return TICK_NEVER_THINK; if ( m_aThinkFunctions[ iIndex ].m_nNextThinkTick == TICK_NEVER_THINK ) { return TICK_NEVER_THINK; } return TICK_INTERVAL * (m_aThinkFunctions[ iIndex ].m_nNextThinkTick ); } int CBaseEntity::GetNextThinkTick( const char *szContext /*= NULL*/ ) { // Are we currently in a think function with a context? int iIndex = 0; if ( !szContext ) { #ifdef _DEBUG if ( m_iCurrentThinkContext != NO_THINK_CONTEXT ) { Msg( "Warning: Getting base nextthink time within think context %s\n", STRING(m_aThinkFunctions[m_iCurrentThinkContext].m_iszContext) ); } #endif if ( m_nNextThinkTick == TICK_NEVER_THINK ) return TICK_NEVER_THINK; // Old system return m_nNextThinkTick; } else { // Find the think function in our list iIndex = GetIndexForThinkContext( szContext ); // Looking up an invalid think context! Assert( iIndex != -1 ); } if ( ( iIndex == -1 ) || ( m_aThinkFunctions[ iIndex ].m_nNextThinkTick == TICK_NEVER_THINK ) ) { return TICK_NEVER_THINK; } return m_aThinkFunctions[ iIndex ].m_nNextThinkTick; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CBaseEntity::GetLastThink( const char *szContext ) { // Are we currently in a think function with a context? int iIndex = 0; if ( !szContext ) { #ifdef _DEBUG if ( m_iCurrentThinkContext != NO_THINK_CONTEXT ) { Msg( "Warning: Getting base lastthink time within think context %s\n", STRING(m_aThinkFunctions[m_iCurrentThinkContext].m_iszContext) ); } #endif // Old system return m_nLastThinkTick * TICK_INTERVAL; } else { // Find the think function in our list iIndex = GetIndexForThinkContext( szContext ); } return m_aThinkFunctions[ iIndex ].m_nLastThinkTick * TICK_INTERVAL; } int CBaseEntity::GetLastThinkTick( const char *szContext /*= NULL*/ ) { // Are we currently in a think function with a context? int iIndex = 0; if ( !szContext ) { #ifdef _DEBUG if ( m_iCurrentThinkContext != NO_THINK_CONTEXT ) { Msg( "Warning: Getting base lastthink time within think context %s\n", STRING(m_aThinkFunctions[m_iCurrentThinkContext].m_iszContext) ); } #endif // Old system return m_nLastThinkTick; } else { // Find the think function in our list iIndex = GetIndexForThinkContext( szContext ); } return m_aThinkFunctions[ iIndex ].m_nLastThinkTick; } bool CBaseEntity::WillThink() { if ( m_nNextThinkTick > 0 ) return true; for ( int i = 0; i < m_aThinkFunctions.Count(); i++ ) { if ( m_aThinkFunctions[i].m_nNextThinkTick > 0 ) return true; } return false; } #if !defined( CLIENT_DLL ) // Rebase all the current ticks in the think functions as delta ticks or from delta ticks to absolute ticks void CBaseEntity::RebaseThinkTicks( bool bMakeDeltas ) { int nCurTick = TIME_TO_TICKS( gpGlobals->curtime ); for ( int i = 0; i < m_aThinkFunctions.Count(); i++ ) { if ( m_aThinkFunctions[i].m_nNextThinkTick > 0 ) { if ( bMakeDeltas ) { // Turn into a delta value m_aThinkFunctions[i].m_nNextThinkTick = m_aThinkFunctions[i].m_nNextThinkTick - nCurTick; m_aThinkFunctions[i].m_nLastThinkTick = m_aThinkFunctions[i].m_nLastThinkTick - nCurTick; } else { // Change a delta to an absolute tick value m_aThinkFunctions[i].m_nNextThinkTick = m_aThinkFunctions[i].m_nNextThinkTick + nCurTick; m_aThinkFunctions[i].m_nLastThinkTick = m_aThinkFunctions[i].m_nLastThinkTick + nCurTick; } } } } #endif // !CLIENT_DLL // returns the first tick the entity will run any think function // returns TICK_NEVER_THINK if no think functions are scheduled int CBaseEntity::GetFirstThinkTick() { int minTick = TICK_NEVER_THINK; if ( m_nNextThinkTick > 0 ) { minTick = m_nNextThinkTick; } for ( int i = 0; i < m_aThinkFunctions.Count(); i++ ) { int next = m_aThinkFunctions[i].m_nNextThinkTick; if ( next > 0 ) { if ( next < minTick || minTick == TICK_NEVER_THINK ) { minTick = next; } } } return minTick; } // NOTE: pass in the isThinking hint so we have to search the think functions less void CBaseEntity::CheckHasThinkFunction( bool isThinking ) { if ( IsEFlagSet( EFL_NO_THINK_FUNCTION ) && isThinking ) { RemoveEFlags( EFL_NO_THINK_FUNCTION ); } else if ( !isThinking && !IsEFlagSet( EFL_NO_THINK_FUNCTION ) && !WillThink() ) { AddEFlags( EFL_NO_THINK_FUNCTION ); } #if !defined( CLIENT_DLL ) SimThink_EntityChanged( this ); #endif } bool CBaseEntity::WillSimulateGamePhysics() { // players always simulate game physics if ( !IsPlayer() ) { MoveType_t movetype = GetMoveType(); if ( movetype == MOVETYPE_NONE || movetype == MOVETYPE_VPHYSICS ) return false; #if !defined( CLIENT_DLL ) // MOVETYPE_PUSH not supported on the client if ( movetype == MOVETYPE_PUSH && GetMoveDoneTime() <= 0 ) return false; #endif } return true; } void CBaseEntity::CheckHasGamePhysicsSimulation() { bool isSimulating = WillSimulateGamePhysics(); if ( isSimulating != IsEFlagSet(EFL_NO_GAME_PHYSICS_SIMULATION) ) return; if ( isSimulating ) { RemoveEFlags( EFL_NO_GAME_PHYSICS_SIMULATION ); } else { AddEFlags( EFL_NO_GAME_PHYSICS_SIMULATION ); } #if !defined( CLIENT_DLL ) SimThink_EntityChanged( this ); #endif } //----------------------------------------------------------------------------- // Sets/Gets the next think based on context index //----------------------------------------------------------------------------- void CBaseEntity::SetNextThink( int nContextIndex, float thinkTime ) { int thinkTick = ( thinkTime == TICK_NEVER_THINK ) ? TICK_NEVER_THINK : TIME_TO_TICKS( thinkTime ); if (nContextIndex < 0) { SetNextThink( thinkTime ); } else { m_aThinkFunctions[nContextIndex].m_nNextThinkTick = thinkTick; } CheckHasThinkFunction( thinkTick == TICK_NEVER_THINK ? false : true ); } void CBaseEntity::SetLastThink( int nContextIndex, float thinkTime ) { int thinkTick = ( thinkTime == TICK_NEVER_THINK ) ? TICK_NEVER_THINK : TIME_TO_TICKS( thinkTime ); if (nContextIndex < 0) { m_nLastThinkTick = thinkTick; } else { m_aThinkFunctions[nContextIndex].m_nLastThinkTick = thinkTick; } } float CBaseEntity::GetNextThink( int nContextIndex ) const { if (nContextIndex < 0) return m_nNextThinkTick * TICK_INTERVAL; return m_aThinkFunctions[nContextIndex].m_nNextThinkTick * TICK_INTERVAL; } int CBaseEntity::GetNextThinkTick( int nContextIndex ) const { if (nContextIndex < 0) return m_nNextThinkTick; return m_aThinkFunctions[nContextIndex].m_nNextThinkTick; } //----------------------------------------------------------------------------- // Purpose: My physics object has been updated, react or extract data //----------------------------------------------------------------------------- void CBaseEntity::VPhysicsUpdate( IPhysicsObject *pPhysics ) { switch( GetMoveType() ) { case MOVETYPE_VPHYSICS: { if ( GetMoveParent() ) { Log_Warning( LOG_DEVELOPER_VERBOSE, "Updating physics on object in hierarchy %s!\n", GetClassname()); return; } Vector origin; QAngle angles; pPhysics->GetPosition( &origin, &angles ); if ( !IsFinite( angles.x ) || !IsFinite( angles.y ) || !IsFinite( angles.x ) ) { Msg( "Infinite angles from vphysics! (entity %s)\n", GetDebugName() ); angles = vec3_angle; } #ifndef CLIENT_DLL Vector prevOrigin = GetAbsOrigin(); #endif for ( int i = 0; i < 3; ++i ) { angles[ i ] = AngleNormalize( angles[ i ] ); } #ifndef CLIENT_DLL NetworkQuantize( origin, angles ); #endif if ( origin.IsValid() ) { SetAbsOrigin( origin ); } else { Msg( "Infinite origin from vphysics! (entity %s)\n", GetDebugName() ); } SetAbsAngles( angles ); // Interactive debris converts back to debris when it comes to rest if ( pPhysics->IsAsleep() && GetCollisionGroup() == COLLISION_GROUP_INTERACTIVE_DEBRIS ) { SetCollisionGroup( COLLISION_GROUP_DEBRIS ); } #ifndef CLIENT_DLL PhysicsTouchTriggers( &prevOrigin ); PhysicsRelinkChildren(gpGlobals->frametime); #endif } break; case MOVETYPE_STEP: break; case MOVETYPE_PUSH: #ifndef CLIENT_DLL VPhysicsUpdatePusher( pPhysics ); #endif break; } } //----------------------------------------------------------------------------- // Purpose: Init this object's physics as a static //----------------------------------------------------------------------------- IPhysicsObject *CBaseEntity::VPhysicsInitStatic( void ) { if ( !VPhysicsInitSetup() ) return NULL; #ifndef CLIENT_DLL // If this entity has a move parent, it needs to be shadow, not static if ( GetMoveParent() ) { // must be SOLID_VPHYSICS if in hierarchy to solve collisions correctly if ( GetSolid() == SOLID_BSP && GetRootMoveParent()->GetSolid() != SOLID_BSP ) { SetSolid( SOLID_VPHYSICS ); } return VPhysicsInitShadow( false, false ); } #endif // No physics if ( GetSolid() == SOLID_NONE ) return NULL; // create a static physics objct IPhysicsObject *pPhysicsObject = NULL; if ( GetSolid() == SOLID_BBOX ) { pPhysicsObject = PhysModelCreateBox( this, WorldAlignMins(), WorldAlignMaxs(), GetAbsOrigin(), true ); } else { pPhysicsObject = PhysModelCreateUnmoveable( this, GetModelIndex(), GetAbsOrigin(), GetAbsAngles() ); } VPhysicsSetObject( pPhysicsObject ); return pPhysicsObject; } //----------------------------------------------------------------------------- // Purpose: // Input : *pPhysics - //----------------------------------------------------------------------------- void CBaseEntity::VPhysicsSetObject( IPhysicsObject *pPhysics ) { if ( m_pPhysicsObject && pPhysics ) { Warning( "Overwriting physics object for %s\n", GetClassname() ); } m_pPhysicsObject = pPhysics; #ifndef CLIENT_DLL RemoveSolidFlags(FSOLID_NOT_MOVEABLE); #endif if ( m_pPhysicsObject ) { #ifndef CLIENT_DLL if ( m_pPhysicsObject->IsStatic() ) { AddSolidFlags(FSOLID_NOT_MOVEABLE); } #endif } if ( pPhysics && !m_pPhysicsObject ) { CollisionRulesChanged(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseEntity::VPhysicsDestroyObject( void ) { if ( m_pPhysicsObject ) { #ifndef CLIENT_DLL PhysRemoveShadow( this ); #endif PhysDestroyObject( m_pPhysicsObject, this ); m_pPhysicsObject = NULL; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CBaseEntity::VPhysicsInitSetup() { #ifndef CLIENT_DLL // don't support logical ents if ( !edict() || IsMarkedForDeletion() ) return false; #endif // If this entity already has a physics object, then it should have been deleted prior to making this call. Assert(!m_pPhysicsObject); VPhysicsDestroyObject(); // make sure absorigin / absangles are correct return true; } //----------------------------------------------------------------------------- // Purpose: This creates a normal vphysics simulated object // physics alone determines where it goes (gravity, friction, etc) // and the entity receives updates from vphysics. SetAbsOrigin(), etc do not affect the object! //----------------------------------------------------------------------------- IPhysicsObject *CBaseEntity::VPhysicsInitNormal( SolidType_t solidType, int nSolidFlags, bool createAsleep, solid_t *pSolid ) { if ( !VPhysicsInitSetup() ) return NULL; // NOTE: This has to occur before PhysModelCreate because that call will // call back into ShouldCollide(), which uses solidtype for rules. SetSolid( solidType ); SetSolidFlags( nSolidFlags ); // No physics if ( solidType == SOLID_NONE ) { return NULL; } // create a normal physics object IPhysicsObject *pPhysicsObject = PhysModelCreate( this, GetModelIndex(), GetAbsOrigin(), GetAbsAngles(), pSolid ); if ( pPhysicsObject ) { VPhysicsSetObject( pPhysicsObject ); SetMoveType( MOVETYPE_VPHYSICS ); if ( !createAsleep ) { pPhysicsObject->Wake(); } } return pPhysicsObject; } // This creates a vphysics object with a shadow controller that follows the AI IPhysicsObject *CBaseEntity::VPhysicsInitShadow( bool allowPhysicsMovement, bool allowPhysicsRotation, solid_t *pSolid ) { if ( !VPhysicsInitSetup() ) return NULL; // No physics if ( GetSolid() == SOLID_NONE ) return NULL; const Vector &origin = GetAbsOrigin(); QAngle angles = GetAbsAngles(); IPhysicsObject *pPhysicsObject = NULL; if ( GetSolid() == SOLID_BBOX ) { // adjust these so the game tracing epsilons match the physics minimum separation distance // this will shrink the vphysics version of the model by the difference in epsilons float radius = 0.25f - DIST_EPSILON; Vector mins = WorldAlignMins() + Vector(radius, radius, radius); Vector maxs = WorldAlignMaxs() - Vector(radius, radius, radius); pPhysicsObject = PhysModelCreateBox( this, mins, maxs, origin, false ); angles = vec3_angle; } else if ( GetSolid() == SOLID_OBB ) { pPhysicsObject = PhysModelCreateOBB( this, CollisionProp()->OBBMins(), CollisionProp()->OBBMaxs(), origin, angles, false ); } else { pPhysicsObject = PhysModelCreate( this, GetModelIndex(), origin, angles, pSolid ); } if ( !pPhysicsObject ) return NULL; VPhysicsSetObject( pPhysicsObject ); // UNDONE: Tune these speeds!!! pPhysicsObject->SetShadow( 1e4, 1e4, allowPhysicsMovement, allowPhysicsRotation ); pPhysicsObject->UpdateShadow( origin, angles, false, 0 ); return pPhysicsObject; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CBaseEntity::CreateVPhysics() { return false; } bool CBaseEntity::IsStandable() const { if (GetSolidFlags() & FSOLID_NOT_STANDABLE) return false; if ( GetSolid() == SOLID_BSP || GetSolid() == SOLID_VPHYSICS || GetSolid() == SOLID_BBOX ) return true; return IsBSPModel( ); } bool CBaseEntity::IsBSPModel() const { if ( GetSolid() == SOLID_BSP ) return true; const model_t *model = modelinfo->GetModel( GetModelIndex() ); if ( GetSolid() == SOLID_VPHYSICS && modelinfo->GetModelType( model ) == mod_brush ) return true; return false; } //----------------------------------------------------------------------------- // Invalidates the abs state of all children //----------------------------------------------------------------------------- void CBaseEntity::InvalidatePhysicsRecursive( int nChangeFlags ) { // Main entry point for dirty flag setting for the 90% case // 1) If the origin changes, then we have to update abstransform, Shadow projection, PVS, KD-tree, // client-leaf system. // 2) If the angles change, then we have to update abstransform, Shadow projection, // shadow render-to-texture, client-leaf system, and surrounding bounds. // Children have to additionally update absvelocity, KD-tree, and PVS. // If the surrounding bounds actually update, when we also need to update the KD-tree and the PVS. // 3) If it's due to attachment, then all children who are attached to an attachment point // are assumed to have dirty origin + angles. // Other stuff: // 1) Marking the surrounding bounds dirty will automatically mark KD tree + PVS dirty. int nDirtyFlags = 0; if ( nChangeFlags & VELOCITY_CHANGED ) { nDirtyFlags |= EFL_DIRTY_ABSVELOCITY; } bool bSurroundDirty = false; if ( nChangeFlags & POSITION_CHANGED ) { nDirtyFlags |= EFL_DIRTY_ABSTRANSFORM; #ifndef CLIENT_DLL NetworkProp()->MarkPVSInformationDirty(); #endif // NOTE: This will also mark shadow projection + client leaf dirty if ( entindex() != 0 ) { CollisionProp()->MarkPartitionHandleDirty(); } } // NOTE: This has to be done after velocity + position are changed // because we change the nChangeFlags for the child entities if ( nChangeFlags & ANGLES_CHANGED ) { nDirtyFlags |= EFL_DIRTY_ABSTRANSFORM; if ( CollisionProp()->DoesRotationInvalidateSurroundingBox() ) { // NOTE: This will handle the KD-tree, surrounding bounds, PVS // render-to-texture shadow, shadow projection, and client leaf dirty CollisionProp()->MarkSurroundingBoundsDirty(); bSurroundDirty = true; } // This is going to be used for all children: children // have position + velocity changed nChangeFlags |= POSITION_CHANGED | VELOCITY_CHANGED; } if ( nChangeFlags & SEQUENCE_CHANGED ) { if ( !bSurroundDirty ) { if ( CollisionProp()->DoesSequenceChangeInvalidateSurroundingBox() ) { // NOTE: This will handle the KD-tree, surrounding bounds, PVS // render-to-texture shadow, shadow projection, and client leaf dirty CollisionProp()->MarkSurroundingBoundsDirty(); bSurroundDirty = true; } } // Children sequences do not change as a result of parent sequence changes nChangeFlags &= ~SEQUENCE_CHANGED; } #ifdef CLIENT_DLL if ( !bSurroundDirty && (nChangeFlags & (POSITION_CHANGED|ANGLES_CHANGED|BOUNDS_CHANGED)) ) { if ( entindex() != 0 ) { MarkRenderHandleDirty(); g_pClientShadowMgr->AddToDirtyShadowList( this ); g_pClientShadowMgr->MarkRenderToTextureShadowDirty( GetShadowHandle() ); } } #endif AddEFlags( nDirtyFlags ); // Set flags for children bool bOnlyDueToAttachment = false; if ( nChangeFlags & ( ANIMATION_CHANGED | BOUNDS_CHANGED ) ) { #ifdef CLIENT_DLL if ( ( nChangeFlags & BOUNDS_CHANGED ) == 0 ) { g_pClientShadowMgr->MarkRenderToTextureShadowDirty( GetShadowHandle() ); } #endif // Only set this flag if the only thing that changed us was the animation. // If position or something else changed us, then we must tell all children. if ( !( nChangeFlags & (POSITION_CHANGED | VELOCITY_CHANGED | ANGLES_CHANGED) ) ) { bOnlyDueToAttachment = true; } nChangeFlags = POSITION_CHANGED | ANGLES_CHANGED | VELOCITY_CHANGED; } for (CBaseEntity *pChild = FirstMoveChild(); pChild; pChild = pChild->NextMovePeer()) { // If this is due to the parent animating, only invalidate children that are parented to an attachment // Entities that are following also access attachments points on parents and must be invalidated. if ( bOnlyDueToAttachment ) { #ifdef CLIENT_DLL if ( (pChild->GetParentAttachment() == 0) && !pChild->IsFollowingEntity() ) continue; #else if ( pChild->GetParentAttachment() == 0 ) continue; #endif } pChild->InvalidatePhysicsRecursive( nChangeFlags ); } // Clear out cached bones if ( nChangeFlags & (POSITION_CHANGED | ANGLES_CHANGED | ANIMATION_CHANGED) ) { CBaseAnimating *pAnim = GetBaseAnimating(); if ( pAnim ) pAnim->InvalidateBoneCache(); } } //----------------------------------------------------------------------------- // Returns the highest parent of an entity //----------------------------------------------------------------------------- CBaseEntity *CBaseEntity::GetRootMoveParent() { CBaseEntity *pEntity = this; CBaseEntity *pParent = this->GetMoveParent(); while ( pParent ) { pEntity = pParent; pParent = pEntity->GetMoveParent(); } return pEntity; } //----------------------------------------------------------------------------- // Purpose: static method // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CBaseEntity::IsPrecacheAllowed() { return m_bAllowPrecache; } //----------------------------------------------------------------------------- // Purpose: static method // Input : allow - //----------------------------------------------------------------------------- void CBaseEntity::SetAllowPrecache( bool allow ) { m_bAllowPrecache = allow; } /* ================ FireBullets Go to the trouble of combining multiple pellets into a single damage call. ================ */ #if defined( GAME_DLL ) class CBulletsTraceFilter : public CTraceFilterSimpleList { public: CBulletsTraceFilter( int collisionGroup ) : CTraceFilterSimpleList( collisionGroup ) {} bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) { if ( m_PassEntities.Count() ) { CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); CBaseEntity *pPassEntity = EntityFromEntityHandle( m_PassEntities[0] ); if ( pEntity && pPassEntity && pEntity->GetOwnerEntity() == pPassEntity && pPassEntity->IsSolidFlagSet(FSOLID_NOT_SOLID) && pPassEntity->IsSolidFlagSet( FSOLID_CUSTOMBOXTEST ) && pPassEntity->IsSolidFlagSet( FSOLID_CUSTOMRAYTEST ) ) { // It's a bone follower of the entity to ignore (toml 8/3/2007) return false; } } return CTraceFilterSimpleList::ShouldHitEntity( pHandleEntity, contentsMask ); } }; #else typedef CTraceFilterSimpleList CBulletsTraceFilter; #endif void CBaseEntity::FireBullets( const FireBulletsInfo_t &info ) { static int tracerCount; trace_t tr; CAmmoDef* pAmmoDef = GetAmmoDef(); int nDamageType = pAmmoDef->DamageType(info.m_iAmmoType); int nAmmoFlags = pAmmoDef->Flags(info.m_iAmmoType); bool bDoServerEffects = true; #if defined( GAME_DLL ) if( IsPlayer() ) { CBasePlayer *pPlayer = dynamic_cast(this); int rumbleEffect = pPlayer->GetActiveWeapon()->GetRumbleEffect(); if( rumbleEffect != RUMBLE_INVALID ) { if( rumbleEffect == RUMBLE_SHOTGUN_SINGLE ) { if( info.m_iShots == 12 ) { // Upgrade to double barrel rumble effect rumbleEffect = RUMBLE_SHOTGUN_DOUBLE; } } pPlayer->RumbleEffect( rumbleEffect, 0, RUMBLE_FLAG_RESTART ); } } #endif// GAME_DLL float flPlayerDamage = info.m_flPlayerDamage; if ( flPlayerDamage == 0.0f ) { if ( nAmmoFlags & AMMO_INTERPRET_PLRDAMAGE_AS_DAMAGE_TO_PLAYER ) { flPlayerDamage = pAmmoDef->PlrDamage( info.m_iAmmoType ); } } // the default attacker is ourselves CBaseEntity *pAttacker = info.m_pAttacker ? info.m_pAttacker : this; // Make sure we don't have a dangling damage target from a recursive call if ( g_MultiDamage.GetTarget() != NULL ) { ApplyMultiDamage(); } ClearMultiDamage(); g_MultiDamage.SetDamageType( nDamageType | DMG_NEVERGIB ); Vector vecDir; Vector vecEnd; // Skip multiple entities when tracing CBulletsTraceFilter traceFilter( COLLISION_GROUP_NONE ); traceFilter.SetPassEntity( this ); // Standard pass entity for THIS so that it can be easily removed from the list after passing through a portal traceFilter.AddEntityToIgnore( info.m_pAdditionalIgnoreEnt ); #if defined( HL2_EPISODIC ) && defined( GAME_DLL ) // FIXME: We need to emulate this same behavior on the client as well -- jdw // Also ignore a vehicle we're a passenger in if ( MyCombatCharacterPointer() != NULL && MyCombatCharacterPointer()->IsInAVehicle() ) { traceFilter.AddEntityToIgnore( MyCombatCharacterPointer()->GetVehicleEntity() ); } #endif // SERVER_DLL bool bUnderwaterBullets = ShouldDrawUnderwaterBulletBubbles(); bool bStartedInWater = false; if ( bUnderwaterBullets ) { bStartedInWater = ( enginetrace->GetPointContents( info.m_vecSrc, MASK_WATER ) & (CONTENTS_WATER|CONTENTS_SLIME) ) != 0; } // Prediction is only usable on players int iSeed = 0; if ( IsPlayer() ) { iSeed = CBaseEntity::GetPredictionRandomSeed() & 255; } //----------------------------------------------------- // Set up our shot manipulator. //----------------------------------------------------- CShotManipulator Manipulator( info.m_vecDirShooting ); bool bDoImpacts = false; bool bDoTracers = false; float flCumulativeDamage = 0.0f; for (int iShot = 0; iShot < info.m_iShots; iShot++) { bool bHitWater = false; bool bHitGlass = false; // Prediction is only usable on players if ( IsPlayer() ) { RandomSeed( iSeed ); // init random system with this seed } // If we're firing multiple shots, and the first shot has to be bang on target, ignore spread if ( iShot == 0 && info.m_iShots > 1 && (info.m_nFlags & FIRE_BULLETS_FIRST_SHOT_ACCURATE) ) { vecDir = Manipulator.GetShotDirection(); } else { // Don't run the biasing code for the player at the moment. vecDir = Manipulator.ApplySpread( info.m_vecSpread ); } vecEnd = info.m_vecSrc + vecDir * info.m_flDistance; if( IsPlayer() && info.m_iShots > 1 && iShot % 2 ) { // Half of the shotgun pellets are hulls that make it easier to hit targets with the shotgun. AI_TraceHull( info.m_vecSrc, vecEnd, Vector( -3, -3, -3 ), Vector( 3, 3, 3 ), MASK_SHOT, &traceFilter, &tr ); } else { AI_TraceLine(info.m_vecSrc, vecEnd, MASK_SHOT, &traceFilter, &tr); } // Tracker 70354/63250: ywb 8/2/07 // Fixes bug where trace from turret with attachment point outside of Vcollide // starts solid so doesn't hit anything else in the world and the final coord // is outside of the MAX_COORD_FLOAT range. This cause trying to send the end pos // of the tracer down to the client with an origin which is out-of-range for networking if ( tr.startsolid ) { tr.endpos = tr.startpos; tr.fraction = 0.0f; } #ifdef GAME_DLL if ( ai_debug_shoot_positions.GetBool() ) NDebugOverlay::Line(info.m_vecSrc, vecEnd, 255, 255, 255, false, .1 ); #endif if ( bStartedInWater ) { #ifdef GAME_DLL Vector vBubbleStart = info.m_vecSrc; Vector vBubbleEnd = tr.endpos; CreateBubbleTrailTracer( vBubbleStart, vBubbleEnd, vecDir ); #endif //#ifdef GAME_DLL bHitWater = true; } // Now hit all triggers along the ray that respond to shots... // Clip the ray to the first collided solid returned from traceline CTakeDamageInfo triggerInfo( pAttacker, pAttacker, info.m_flDamage, nDamageType ); CalculateBulletDamageForce( &triggerInfo, info.m_iAmmoType, vecDir, tr.endpos ); triggerInfo.ScaleDamageForce( info.m_flDamageForceScale ); triggerInfo.SetAmmoType( info.m_iAmmoType ); #ifdef GAME_DLL TraceAttackToTriggers( triggerInfo, tr.startpos, tr.endpos, vecDir ); #endif // Make sure given a valid bullet type if (info.m_iAmmoType == -1) { DevMsg("ERROR: Undefined ammo type!\n"); return; } Vector vecTracerDest = tr.endpos; // do damage, paint decals if (tr.fraction != 1.0) { #ifdef GAME_DLL UpdateShotStatistics( tr ); // For shots that don't need persistance int soundEntChannel = ( info.m_nFlags&FIRE_BULLETS_TEMPORARY_DANGER_SOUND ) ? SOUNDENT_CHANNEL_BULLET_IMPACT : SOUNDENT_CHANNEL_UNSPECIFIED; CSoundEnt::InsertSound( SOUND_BULLET_IMPACT, tr.endpos, 200, 0.5, this, soundEntChannel ); #endif // See if the bullet ended up underwater + started out of the water if ( !bHitWater && ( enginetrace->GetPointContents( tr.endpos, MASK_WATER ) & (CONTENTS_WATER|CONTENTS_SLIME) ) ) { bHitWater = HandleShotImpactingWater( info, vecEnd, &traceFilter, &vecTracerDest ); } float flActualDamage = info.m_flDamage; // If we hit a player, and we have player damage specified, use that instead // Adrian: Make sure to use the currect value if we hit a vehicle the player is currently driving. if ( flPlayerDamage != 0.0f ) { if ( tr.m_pEnt->IsPlayer() ) { flActualDamage = flPlayerDamage; } #ifdef GAME_DLL else if ( tr.m_pEnt->GetServerVehicle() ) { if ( tr.m_pEnt->GetServerVehicle()->GetPassenger() && tr.m_pEnt->GetServerVehicle()->GetPassenger()->IsPlayer() ) { flActualDamage = flPlayerDamage; } } #endif } int nActualDamageType = nDamageType; if ( flActualDamage == 0.0 ) { flActualDamage = g_pGameRules->GetAmmoDamage( pAttacker, tr.m_pEnt, info.m_iAmmoType ); } else { nActualDamageType = nDamageType | ((flActualDamage > 16) ? DMG_ALWAYSGIB : DMG_NEVERGIB ); } if ( !bHitWater || ((info.m_nFlags & FIRE_BULLETS_DONT_HIT_UNDERWATER) == 0) ) { // Damage specified by function parameter CTakeDamageInfo dmgInfo( this, pAttacker, flActualDamage, nActualDamageType ); CalculateBulletDamageForce( &dmgInfo, info.m_iAmmoType, vecDir, tr.endpos ); dmgInfo.ScaleDamageForce( info.m_flDamageForceScale ); dmgInfo.SetAmmoType( info.m_iAmmoType ); tr.m_pEnt->DispatchTraceAttack( dmgInfo, vecDir, &tr ); if ( ToBaseCombatCharacter( tr.m_pEnt ) ) { flCumulativeDamage += dmgInfo.GetDamage(); } if ( bStartedInWater || !bHitWater || (info.m_nFlags & FIRE_BULLETS_ALLOW_WATER_SURFACE_IMPACTS) ) { if ( bDoServerEffects == true ) { DoImpactEffect( tr, nDamageType ); } else { bDoImpacts = true; } } else { // We may not impact, but we DO need to affect ragdolls on the client CEffectData data; data.m_vStart = tr.startpos; data.m_vOrigin = tr.endpos; data.m_nDamageType = nDamageType; DispatchEffect( "RagdollImpact", data ); } #ifdef GAME_DLL if ( nAmmoFlags & AMMO_FORCE_DROP_IF_CARRIED ) { // Make sure if the player is holding this, he drops it Pickup_ForcePlayerToDropThisObject( tr.m_pEnt ); } #endif } } // See if we hit glass if ( tr.m_pEnt != NULL ) { #ifdef GAME_DLL surfacedata_t *psurf = physprops->GetSurfaceData( tr.surface.surfaceProps ); if ( ( psurf != NULL ) && ( psurf->game.material == CHAR_TEX_GLASS ) && ( tr.m_pEnt->ClassMatches( "func_breakable" ) ) ) { // Query the func_breakable for whether it wants to allow for bullet penetration if ( tr.m_pEnt->HasSpawnFlags( SF_BREAK_NO_BULLET_PENETRATION ) == false ) { bHitGlass = true; } } #endif } if ( ( info.m_iTracerFreq != 0 ) && ( tracerCount++ % info.m_iTracerFreq ) == 0 && ( bHitGlass == false ) ) { if ( bDoServerEffects == true ) { Vector vecTracerSrc = vec3_origin; ComputeTracerStartPosition( info.m_vecSrc, &vecTracerSrc ); trace_t Tracer; Tracer = tr; Tracer.endpos = vecTracerDest; MakeTracer( vecTracerSrc, Tracer, pAmmoDef->TracerType(info.m_iAmmoType) ); } else { bDoTracers = true; } } //NOTENOTE: We could expand this to a more general solution for various material penetration types (wood, thin metal, etc) // See if we should pass through glass #ifdef GAME_DLL if ( bHitGlass ) { HandleShotImpactingGlass( info, tr, vecDir, &traceFilter ); } #endif iSeed++; } #ifdef GAME_DLL ApplyMultiDamage(); if ( IsPlayer() && flCumulativeDamage > 0.0f ) { CBasePlayer *pPlayer = static_cast< CBasePlayer * >( this ); CTakeDamageInfo dmgInfo( this, pAttacker, flCumulativeDamage, nDamageType ); gamestats->Event_WeaponHit( pPlayer, info.m_bPrimaryAttack, pPlayer->GetActiveWeapon()->GetClassname(), dmgInfo ); } #endif } //----------------------------------------------------------------------------- // Should we draw bubbles underwater? //----------------------------------------------------------------------------- bool CBaseEntity::ShouldDrawUnderwaterBulletBubbles() { #if defined( HL2_DLL ) && defined( GAME_DLL ) CBaseEntity *pPlayer = ( gpGlobals->maxClients == 1 ) ? UTIL_GetLocalPlayer() : NULL; return pPlayer && (pPlayer->GetWaterLevel() == 3); #else return false; #endif } //----------------------------------------------------------------------------- // Handle shot entering water //----------------------------------------------------------------------------- bool CBaseEntity::HandleShotImpactingWater( const FireBulletsInfo_t &info, const Vector &vecEnd, ITraceFilter *pTraceFilter, Vector *pVecTracerDest ) { trace_t waterTrace; // Trace again with water enabled AI_TraceLine( info.m_vecSrc, vecEnd, (MASK_SHOT|CONTENTS_WATER|CONTENTS_SLIME), pTraceFilter, &waterTrace ); // See if this is the point we entered if ( ( enginetrace->GetPointContents( waterTrace.endpos - Vector(0,0,0.1f), MASK_WATER ) & (CONTENTS_WATER|CONTENTS_SLIME) ) == 0 ) return false; if ( ShouldDrawWaterImpacts() ) { int nMinSplashSize = GetAmmoDef()->MinSplashSize(info.m_iAmmoType); int nMaxSplashSize = GetAmmoDef()->MaxSplashSize(info.m_iAmmoType); CEffectData data; data.m_vOrigin = waterTrace.endpos; data.m_vNormal = waterTrace.plane.normal; data.m_flScale = random->RandomFloat( nMinSplashSize, nMaxSplashSize ); if ( waterTrace.contents & CONTENTS_SLIME ) { data.m_fFlags |= FX_WATER_IN_SLIME; } DispatchEffect( "gunshotsplash", data ); } #ifdef GAME_DLL if ( ShouldDrawUnderwaterBulletBubbles() ) { CWaterBullet *pWaterBullet = ( CWaterBullet * )CreateEntityByName( "waterbullet" ); if ( pWaterBullet ) { pWaterBullet->Spawn( waterTrace.endpos, info.m_vecDirShooting ); CEffectData tracerData; tracerData.m_vStart = waterTrace.endpos; tracerData.m_vOrigin = waterTrace.endpos + info.m_vecDirShooting * 400.0f; tracerData.m_fFlags = TRACER_TYPE_WATERBULLET; DispatchEffect( "TracerSound", tracerData ); } } #endif *pVecTracerDest = waterTrace.endpos; return true; } ITraceFilter* CBaseEntity::GetBeamTraceFilter( void ) { return NULL; } void CBaseEntity::DispatchTraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr ) { #ifdef GAME_DLL // Make sure our damage filter allows the damage. if ( !PassesDamageFilter( info )) { return; } #endif TraceAttack( info, vecDir, ptr ); } void CBaseEntity::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr ) { Vector vecOrigin = ptr->endpos - vecDir * 4; if ( m_takedamage ) { AddMultiDamage( info, this ); int blood = BloodColor(); if ( blood != DONT_BLEED ) { SpawnBlood( vecOrigin, vecDir, blood, info.GetDamage() );// a little surface blood. TraceBleed( info.GetDamage(), vecDir, ptr, info.GetDamageType() ); } } } //----------------------------------------------------------------------------- // Allows the shooter to change the impact effect of his bullets //----------------------------------------------------------------------------- void CBaseEntity::DoImpactEffect( trace_t &tr, int nDamageType ) { // give shooter a chance to do a custom impact. UTIL_ImpactTrace( &tr, nDamageType ); } //----------------------------------------------------------------------------- // Computes the tracer start position //----------------------------------------------------------------------------- void CBaseEntity::ComputeTracerStartPosition( const Vector &vecShotSrc, Vector *pVecTracerStart ) { if ( g_pGameRules->IsMultiplayer() ) { // NOTE: we do this because in MakeTracer, we force it to use the attachment position // in multiplayer, so the results from this function should never actually get used. pVecTracerStart->Init( 999, 999, 999 ); return; } if ( IsPlayer() ) { // adjust tracer position for player Vector forward, right; CBasePlayer *pPlayer = ToBasePlayer( this ); pPlayer->EyeVectors( &forward, &right, NULL ); *pVecTracerStart = vecShotSrc + Vector ( 0 , 0 , -4 ) + right * 2 + forward * 16; } else { *pVecTracerStart = vecShotSrc; CBaseCombatCharacter *pBCC = MyCombatCharacterPointer(); if ( pBCC != NULL ) { CBaseCombatWeapon *pWeapon = pBCC->GetActiveWeapon(); if ( pWeapon != NULL ) { Vector vecMuzzle; QAngle vecMuzzleAngles; if ( pWeapon->GetAttachment( 1, vecMuzzle, vecMuzzleAngles ) ) { *pVecTracerStart = vecMuzzle; } } } } } //----------------------------------------------------------------------------- // Purpose: Virtual function allows entities to handle tracer presentation // as they see fit. // // Input : vecTracerSrc - the point at which to start the tracer (not always the // same spot as the traceline! // // tr - the entire trace result for the shot. // // Output : //----------------------------------------------------------------------------- void CBaseEntity::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType ) { const char *pszTracerName = GetTracerType(); Vector vNewSrc = vecTracerSrc; int iAttachment = GetTracerAttachment(); switch ( iTracerType ) { case TRACER_LINE: UTIL_Tracer( vNewSrc, tr.endpos, entindex(), iAttachment, 0.0f, false, pszTracerName ); break; case TRACER_LINE_AND_WHIZ: UTIL_Tracer( vNewSrc, tr.endpos, entindex(), iAttachment, 0.0f, true, pszTracerName ); break; } } //----------------------------------------------------------------------------- // Default tracer attachment //----------------------------------------------------------------------------- int CBaseEntity::GetTracerAttachment( void ) { int iAttachment = TRACER_DONT_USE_ATTACHMENT; if ( g_pGameRules->IsMultiplayer() ) { iAttachment = 1; } return iAttachment; } int CBaseEntity::BloodColor() { return DONT_BLEED; } void CBaseEntity::TraceBleed( float flDamage, const Vector &vecDir, trace_t *ptr, int bitsDamageType ) { if ((BloodColor() == DONT_BLEED) || (BloodColor() == BLOOD_COLOR_MECH)) { return; } if (flDamage == 0) return; if (! (bitsDamageType & (DMG_CRUSH | DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB | DMG_AIRBOAT))) return; // make blood decal on the wall! trace_t Bloodtr; Vector vecTraceDir; float flNoise; int cCount; int i; #ifdef GAME_DLL if ( !IsAlive() ) { // dealing with a dead npc. if ( GetMaxHealth() <= 0 ) { // no blood decal for a npc that has already decalled its limit. return; } else { m_iMaxHealth -= 1; } } #endif if (flDamage < 10) { flNoise = 0.1; cCount = 1; } else if (flDamage < 25) { flNoise = 0.2; cCount = 2; } else { flNoise = 0.3; cCount = 4; } float flTraceDist = (bitsDamageType & DMG_AIRBOAT) ? 384 : 172; for ( i = 0 ; i < cCount ; i++ ) { vecTraceDir = vecDir * -1;// trace in the opposite direction the shot came from (the direction the shot is going) vecTraceDir.x += random->RandomFloat( -flNoise, flNoise ); vecTraceDir.y += random->RandomFloat( -flNoise, flNoise ); vecTraceDir.z += random->RandomFloat( -flNoise, flNoise ); // Don't bleed on grates. AI_TraceLine( ptr->endpos, ptr->endpos + vecTraceDir * -flTraceDist, MASK_SOLID_BRUSHONLY & ~CONTENTS_GRATE, this, COLLISION_GROUP_NONE, &Bloodtr); if ( Bloodtr.fraction != 1.0 ) { UTIL_BloodDecalTrace( &Bloodtr, BloodColor() ); } } } const char* CBaseEntity::GetTracerType() { return NULL; } //----------------------------------------------------------------------------- // These methods encapsulate MOVETYPE_FOLLOW, which became obsolete //----------------------------------------------------------------------------- void CBaseEntity::FollowEntity( CBaseEntity *pBaseEntity, bool bBoneMerge ) { if (pBaseEntity) { SetParent( pBaseEntity ); SetMoveType( MOVETYPE_NONE ); if ( bBoneMerge ) AddEffects( EF_BONEMERGE ); AddSolidFlags( FSOLID_NOT_SOLID ); SetLocalOrigin( vec3_origin ); SetLocalAngles( vec3_angle ); } else { StopFollowingEntity(); } } void CBaseEntity::SetEffectEntity( CBaseEntity *pEffectEnt ) { if ( m_hEffectEntity.Get() != pEffectEnt ) { m_hEffectEntity = pEffectEnt; } } void CBaseEntity::ApplyLocalVelocityImpulse( const Vector &vecImpulse ) { // NOTE: Don't have to use GetVelocity here because local values // are always guaranteed to be correct, unlike abs values which may // require recomputation if (vecImpulse != vec3_origin ) { if ( GetMoveType() == MOVETYPE_VPHYSICS ) { IPhysicsObject *ppPhysObjs[ VPHYSICS_MAX_OBJECT_LIST_COUNT ]; int nNumPhysObjs = VPhysicsGetObjectList( ppPhysObjs, VPHYSICS_MAX_OBJECT_LIST_COUNT ); for ( int i = 0; i < nNumPhysObjs; i++ ) { Vector worldVel; ppPhysObjs[ i ]->LocalToWorld( &worldVel, vecImpulse ); ppPhysObjs[ i ]->AddVelocity( &worldVel, NULL ); } } else { InvalidatePhysicsRecursive( VELOCITY_CHANGED ); m_vecVelocity += vecImpulse; } } } void CBaseEntity::ApplyAbsVelocityImpulse( const Vector &vecImpulse ) { if (vecImpulse != vec3_origin ) { if ( GetMoveType() == MOVETYPE_VPHYSICS ) { IPhysicsObject *ppPhysObjs[ VPHYSICS_MAX_OBJECT_LIST_COUNT ]; int nNumPhysObjs = VPhysicsGetObjectList( ppPhysObjs, VPHYSICS_MAX_OBJECT_LIST_COUNT ); for ( int i = 0; i < nNumPhysObjs; i++ ) { ppPhysObjs[ i ]->AddVelocity( &vecImpulse, NULL ); } } else { // NOTE: Have to use GetAbsVelocity here to ensure it's the correct value Vector vecResult; VectorAdd( GetAbsVelocity(), vecImpulse, vecResult ); SetAbsVelocity( vecResult ); } } } void CBaseEntity::ApplyLocalAngularVelocityImpulse( const AngularImpulse &angImpulse ) { if (angImpulse != vec3_origin ) { if ( GetMoveType() == MOVETYPE_VPHYSICS ) { IPhysicsObject *ppPhysObjs[ VPHYSICS_MAX_OBJECT_LIST_COUNT ]; int nNumPhysObjs = VPhysicsGetObjectList( ppPhysObjs, VPHYSICS_MAX_OBJECT_LIST_COUNT ); for ( int i = 0; i < nNumPhysObjs; i++ ) { ppPhysObjs[ i ]->AddVelocity( NULL, &angImpulse ); } } else { QAngle vecResult; AngularImpulseToQAngle( angImpulse, vecResult ); VectorAdd( GetLocalAngularVelocity(), vecResult, vecResult ); SetLocalAngularVelocity( vecResult ); } } } void CBaseEntity::SetCollisionGroup( int collisionGroup ) { if ( (int)m_CollisionGroup != collisionGroup ) { m_CollisionGroup = collisionGroup; CollisionRulesChanged(); } } void CBaseEntity::CollisionRulesChanged() { // ivp maintains state based on recent return values from the collision filter, so anything // that can change the state that a collision filter will return (like m_Solid) needs to call RecheckCollisionFilter. if ( VPhysicsGetObject() ) { extern bool PhysIsInCallback(); if ( PhysIsInCallback() ) { Warning("Changing collision rules within a callback is likely to cause crashes!\n"); Assert(0); } IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; int count = VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); for ( int i = 0; i < count; i++ ) { if ( pList[i] != NULL ) //this really shouldn't happen, but it does >_< pList[i]->RecheckCollisionFilter(); } } } int CBaseEntity::GetWaterType() const { int out = 0; if ( m_nWaterType & 1 ) out |= CONTENTS_WATER; if ( m_nWaterType & 2 ) out |= CONTENTS_SLIME; return out; } void CBaseEntity::SetWaterType( int nType ) { m_nWaterType = 0; if ( nType & CONTENTS_WATER ) m_nWaterType |= 1; if ( nType & CONTENTS_SLIME ) m_nWaterType |= 2; } ConVar sv_alternateticks( "sv_alternateticks", ( IsX360() ) ? "1" : "0", FCVAR_SPONLY, "If set, server only simulates entities on even numbered ticks.\n" ); //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CBaseEntity::IsSimulatingOnAlternateTicks() { if ( gpGlobals->maxClients != 1 ) { return false; } return sv_alternateticks.GetBool(); } #ifdef CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: // Input : - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CBaseEntity::IsToolRecording() const { #ifndef NO_TOOLFRAMEWORK return m_bToolRecording; #else return false; #endif } #endif #if defined( CLIENT_DLL ) #define FAST_TRIGGER_TOUCH extern void TouchTriggerPlayerMovement( C_BaseEntity *pEntity ); #endif void CBaseEntity::PhysicsTouchTriggers( const Vector *pPrevAbsOrigin ) { #if defined( CLIENT_DLL ) #if defined( FAST_TRIGGER_TOUCH ) { Assert( !pPrevAbsOrigin ); TouchTriggerPlayerMovement( this ); return; } #endif // FAST_TRIGGER_TOUCH IClientEntity *pEntity = this; #else edict_t *pEntity = edict(); #endif if ( pEntity && !IsWorld() ) { Assert(CollisionProp()); bool isTriggerCheckSolids = IsSolidFlagSet( FSOLID_TRIGGER ); bool isSolidCheckTriggers = IsSolid() && !isTriggerCheckSolids; // NOTE: Moving triggers (items, ammo etc) are not // checked against other triggers ot 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( pEntity, CollisionProp(), pPrevAbsOrigin, sm_bAccurateTriggerBboxChecks ); } if ( isTriggerCheckSolids ) { engine->TriggerMoved( pEntity, sm_bAccurateTriggerBboxChecks ); } } } //----------------------------------------------------------------------------- // Purpose: // Input : set - //----------------------------------------------------------------------------- void CBaseEntity::ModifyOrAppendCriteria( AI_CriteriaSet& set ) { // TODO // Append chapter/day? set.AppendCriteria( "randomnum", UTIL_VarArgs("%d", RandomInt(0,100)) ); // Append our classname and game name set.AppendCriteria( "classname", GetClassname() ); const char *pEntityName = ""; #ifdef CLIENT_DLL pEntityName = GetEntityName(); #else pEntityName = GetEntityNameAsCStr(); #endif set.AppendCriteria( "name", pEntityName ); // 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 #ifdef GAME_DLL for ( int i = 0; i < GlobalEntity_GetNumGlobals(); i++ ) { const char *szGlobalName = GlobalEntity_GetName(i); int iGlobalState = (int)GlobalEntity_GetStateByIndex(i); set.AppendCriteria( szGlobalName, UTIL_VarArgs( "%i", iGlobalState ) ); } // Append map name set.AppendCriteria( "map", gpGlobals->mapname.ToCStr() ); // Append anything from I/O or keyvalues pairs AppendContextToCriteria( set ); if( hl2_episodic.GetBool() ) { set.AppendCriteria( "episodic", "1" ); } // Append anything from world I/O/keyvalues with "world" as prefix CWorld *world = assert_cast< CWorld * >( CBaseEntity::Instance( INDEXENT( 0 ) ) ); if ( world ) { world->AppendContextToCriteria( set, "world" ); } #endif }