diff options
| author | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
|---|---|---|
| committer | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
| commit | 39ed87570bdb2f86969d4be821c94b722dc71179 (patch) | |
| tree | abc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/shared/baseentity_shared.cpp | |
| download | source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip | |
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/shared/baseentity_shared.cpp')
| -rw-r--r-- | mp/src/game/shared/baseentity_shared.cpp | 2542 |
1 files changed, 2542 insertions, 0 deletions
diff --git a/mp/src/game/shared/baseentity_shared.cpp b/mp/src/game/shared/baseentity_shared.cpp new file mode 100644 index 00000000..470b2d2e --- /dev/null +++ b/mp/src/game/shared/baseentity_shared.cpp @@ -0,0 +1,2542 @@ +//========= Copyright 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 "vphysics/performance.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"
+
+#ifdef HL2MP
+ #include "te_hl2mp_shotgun_shot.h"
+#endif
+
+ #include "gamestats.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
+
+#ifdef PORTAL
+ #include "prop_portal_shared.h"
+#endif
+
+#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;
+
+// Set default max values for entities based on the existing constants from elsewhere
+float k_flMaxEntityPosCoord = MAX_COORD_FLOAT;
+float k_flMaxEntityEulerAngle = 360.0 * 1000.0f; // really should be restricted to +/-180, but some code doesn't adhere to this. let's just trap NANs, etc
+// Sometimes the resulting computed speeds are legitimately above the original
+// constants; use bumped up versions for the downstream validation logic to
+// account for this.
+float k_flMaxEntitySpeed = k_flMaxVelocity * 2.0f;
+float k_flMaxEntitySpinRate = k_flMaxAngularVelocity * 10.0f;
+
+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 );
+
+// Utility func to throttle rate at which the "reasonable position" spew goes out
+static double s_LastEntityReasonableEmitTime;
+bool CheckEmitReasonablePhysicsSpew()
+{
+
+ // Reported recently?
+ double now = Plat_FloatTime();
+ if ( now >= s_LastEntityReasonableEmitTime && now < s_LastEntityReasonableEmitTime + 5.0 )
+ {
+ // Already reported recently
+ return false;
+ }
+
+ // Not reported recently. Report it now
+ s_LastEntityReasonableEmitTime = now;
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// 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() + GetViewOffset();
+}
+
+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;
+
+#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 ( 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) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// 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;
+ UTIL_StringToColor32( &tmp, szValue );
+ SetRenderColor( tmp.r, tmp.g, tmp.b );
+ // don't copy alpha, legacy support uses renderamt
+ return true;
+ }
+
+ if ( FStrEq( szKeyName, "renderamt" ) )
+ {
+ SetRenderColorA( 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 );
+ }
+
+#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, 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" ))
+ {
+ color32 tmp = GetRenderColor();
+ Q_snprintf( szValue, iMaxLen, "%d %d %d %d", tmp.r, tmp.g, tmp.b, tmp.a );
+ return true;
+ }
+
+ if ( FStrEq( szKeyName, "renderamt" ) )
+ {
+ color32 tmp = GetRenderColor();
+ Q_snprintf( szValue, iMaxLen, "%d", tmp.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, const 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;
+ 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.Size(); 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<void **>(&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<void **>(&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;
+}
+
+// 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;
+}
+
+
+int CheckEntityVelocity( Vector &v )
+{
+ float r = k_flMaxEntitySpeed;
+ if (
+ v.x > -r && v.x < r &&
+ v.y > -r && v.y < r &&
+ v.z > -r && v.z < r)
+ {
+ // The usual case. It's totally reasonable
+ return 1;
+ }
+ float speed = v.Length();
+ if ( speed < k_flMaxEntitySpeed * 100.0f )
+ {
+ // Sort of suspicious. Clamp it
+ v *= k_flMaxEntitySpeed / speed;
+ return 0;
+ }
+
+ // A terrible, horrible, no good, very bad velocity.
+ return -1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: My physics object has been updated, react or extract data
+//-----------------------------------------------------------------------------
+void CBaseEntity::VPhysicsUpdate( IPhysicsObject *pPhysics )
+{
+ switch( GetMoveType() )
+ {
+ case MOVETYPE_VPHYSICS:
+ {
+ if ( GetMoveParent() )
+ {
+ DevWarning("Updating physics on object in hierarchy %s!\n", GetClassname());
+ return;
+ }
+ Vector origin;
+ QAngle angles;
+
+ pPhysics->GetPosition( &origin, &angles );
+
+ if ( !IsEntityQAngleReasonable( angles ) )
+ {
+ if ( CheckEmitReasonablePhysicsSpew() )
+ {
+ Warning( "Ignoring bogus angles (%f,%f,%f) from vphysics! (entity %s)\n", angles.x, angles.y, angles.z, GetDebugName() );
+ }
+ angles = vec3_angle;
+ }
+#ifndef CLIENT_DLL
+ Vector prevOrigin = GetAbsOrigin();
+#endif
+
+ if ( IsEntityPositionReasonable( origin ) )
+ {
+ SetAbsOrigin( origin );
+ }
+ else
+ {
+ if ( CheckEmitReasonablePhysicsSpew() )
+ {
+ Warning( "Ignoring unreasonable position (%f,%f,%f) from vphysics! (entity %s)\n", origin.x, origin.y, origin.z, GetDebugName() );
+ }
+ }
+
+ for ( int i = 0; i < 3; ++i )
+ {
+ angles[ i ] = AngleNormalize( angles[ i ] );
+ }
+ 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;
+ 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;
+ }
+
+ if ( nChangeFlags & POSITION_CHANGED )
+ {
+ nDirtyFlags |= EFL_DIRTY_ABSTRANSFORM;
+
+#ifndef CLIENT_DLL
+ NetworkProp()->MarkPVSInformationDirty();
+#endif
+
+ // NOTE: This will also mark shadow projection + client leaf dirty
+ 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();
+ }
+ else
+ {
+#ifdef CLIENT_DLL
+ MarkRenderHandleDirty();
+ g_pClientShadowMgr->AddToDirtyShadowList( this );
+ g_pClientShadowMgr->MarkRenderToTextureShadowDirty( GetShadowHandle() );
+#endif
+ }
+
+ // This is going to be used for all children: children
+ // have position + velocity changed
+ nChangeFlags |= POSITION_CHANGED | VELOCITY_CHANGED;
+ }
+
+ AddEFlags( nDirtyFlags );
+
+ // Set flags for children
+ bool bOnlyDueToAttachment = false;
+ if ( nChangeFlags & ANIMATION_CHANGED )
+ {
+#ifdef CLIENT_DLL
+ 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 );
+ }
+
+ //
+ // This code should really be in here, or the bone cache should not be in world space.
+ // Since the bone transforms are in world space, if we move or rotate the entity, its
+ // bones should be marked invalid.
+ //
+ // As it is, we're near ship, and don't have time to setup a good A/B test of how much
+ // overhead this fix would add. We've also only got one known case where the lack of
+ // this fix is screwing us, and I just fixed it, so I'm leaving this commented out for now.
+ //
+ // Hopefully, we'll put the bone cache in entity space and remove the need for this fix.
+ //
+ //#ifdef CLIENT_DLL
+ // if ( nChangeFlags & (POSITION_CHANGED | ANGLES_CHANGED | ANIMATION_CHANGED) )
+ // {
+ // C_BaseAnimating *pAnim = GetBaseAnimating();
+ // if ( pAnim )
+ // pAnim->InvalidateBoneCache();
+ // }
+ //#endif
+}
+
+
+
+//-----------------------------------------------------------------------------
+// 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( HL2MP ) && defined( GAME_DLL )
+ bDoServerEffects = false;
+#endif
+
+#if defined( GAME_DLL )
+ if( IsPlayer() )
+ {
+ CBasePlayer *pPlayer = dynamic_cast<CBasePlayer*>(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
+
+ int iPlayerDamage = info.m_iPlayerDamage;
+ if ( iPlayerDamage == 0 )
+ {
+ if ( nAmmoFlags & AMMO_INTERPRET_PLRDAMAGE_AS_DAMAGE_TO_PLAYER )
+ {
+ iPlayerDamage = 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 ) & (CONTENTS_WATER|CONTENTS_SLIME) ) != 0;
+ }
+
+ // Prediction is only usable on players
+ int iSeed = 0;
+ if ( IsPlayer() )
+ {
+ iSeed = CBaseEntity::GetPredictionRandomSeed() & 255;
+ }
+
+#if defined( HL2MP ) && defined( GAME_DLL )
+ int iEffectSeed = iSeed;
+#endif
+ //-----------------------------------------------------
+ // 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;
+
+#ifdef PORTAL
+ CProp_Portal *pShootThroughPortal = NULL;
+ float fPortalFraction = 2.0f;
+#endif
+
+
+ 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.
+#ifdef PORTAL
+ Ray_t rayBullet;
+ rayBullet.Init( info.m_vecSrc, vecEnd );
+ pShootThroughPortal = UTIL_Portal_FirstAlongRay( rayBullet, fPortalFraction );
+ if ( !UTIL_Portal_TraceRay_Bullets( pShootThroughPortal, rayBullet, MASK_SHOT, &traceFilter, &tr ) )
+ {
+ pShootThroughPortal = NULL;
+ }
+#else
+ AI_TraceHull( info.m_vecSrc, vecEnd, Vector( -3, -3, -3 ), Vector( 3, 3, 3 ), MASK_SHOT, &traceFilter, &tr );
+#endif //#ifdef PORTAL
+ }
+ else
+ {
+#ifdef PORTAL
+ Ray_t rayBullet;
+ rayBullet.Init( info.m_vecSrc, vecEnd );
+ pShootThroughPortal = UTIL_Portal_FirstAlongRay( rayBullet, fPortalFraction );
+ if ( !UTIL_Portal_TraceRay_Bullets( pShootThroughPortal, rayBullet, MASK_SHOT, &traceFilter, &tr ) )
+ {
+ pShootThroughPortal = NULL;
+ }
+#else
+ AI_TraceLine(info.m_vecSrc, vecEnd, MASK_SHOT, &traceFilter, &tr);
+#endif //#ifdef PORTAL
+ }
+
+ // 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;
+ }
+
+ // bullet's final direction can be changed by passing through a portal
+#ifdef PORTAL
+ if ( !tr.startsolid )
+ {
+ vecDir = tr.endpos - tr.startpos;
+ VectorNormalize( vecDir );
+ }
+#endif
+
+#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;
+
+#ifdef PORTAL
+ if ( pShootThroughPortal )
+ {
+ vBubbleEnd = info.m_vecSrc + ( vecEnd - info.m_vecSrc ) * fPortalFraction;
+ }
+#endif //#ifdef PORTAL
+
+ CreateBubbleTrailTracer( vBubbleStart, vBubbleEnd, vecDir );
+
+#ifdef PORTAL
+ if ( pShootThroughPortal )
+ {
+ Vector vTransformedIntersection;
+ UTIL_Portal_PointTransform( pShootThroughPortal->MatrixThisToLinked(), vBubbleEnd, vTransformedIntersection );
+
+ CreateBubbleTrailTracer( vTransformedIntersection, tr.endpos, vecDir );
+ }
+#endif //#ifdef PORTAL
+
+#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 ) & (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 ( iPlayerDamage )
+ {
+ if ( tr.m_pEnt->IsPlayer() )
+ {
+ flActualDamage = iPlayerDamage;
+ }
+#ifdef GAME_DLL
+ else if ( tr.m_pEnt->GetServerVehicle() )
+ {
+ if ( tr.m_pEnt->GetServerVehicle()->GetPassenger() && tr.m_pEnt->GetServerVehicle()->GetPassenger()->IsPlayer() )
+ {
+ flActualDamage = iPlayerDamage;
+ }
+ }
+#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 );
+ ModifyFireBulletsDamage( &dmgInfo );
+ 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;
+
+#ifdef PORTAL
+ if ( pShootThroughPortal )
+ {
+ Tracer.endpos = info.m_vecSrc + ( vecEnd - info.m_vecSrc ) * fPortalFraction;
+ }
+#endif //#ifdef PORTAL
+
+ MakeTracer( vecTracerSrc, Tracer, pAmmoDef->TracerType(info.m_iAmmoType) );
+
+#ifdef PORTAL
+ if ( pShootThroughPortal )
+ {
+ Vector vTransformedIntersection;
+ UTIL_Portal_PointTransform( pShootThroughPortal->MatrixThisToLinked(), Tracer.endpos, vTransformedIntersection );
+ ComputeTracerStartPosition( vTransformedIntersection, &vecTracerSrc );
+
+ Tracer.endpos = vecTracerDest;
+
+ MakeTracer( vecTracerSrc, Tracer, pAmmoDef->TracerType(info.m_iAmmoType) );
+
+ // Shooting through a portal, the damage direction is translated through the passed-through portal
+ // so the damage indicator hud animation is correct
+ Vector vDmgOriginThroughPortal;
+ UTIL_Portal_PointTransform( pShootThroughPortal->MatrixThisToLinked(), info.m_vecSrc, vDmgOriginThroughPortal );
+ g_MultiDamage.SetDamagePosition ( vDmgOriginThroughPortal );
+ }
+ else
+ {
+ g_MultiDamage.SetDamagePosition ( info.m_vecSrc );
+ }
+#endif //#ifdef PORTAL
+ }
+ 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++;
+ }
+
+#if defined( HL2MP ) && defined( GAME_DLL )
+ if ( bDoServerEffects == false )
+ {
+ TE_HL2MPFireBullets( entindex(), tr.startpos, info.m_vecDirShooting, info.m_iAmmoType, iEffectSeed, info.m_iShots, info.m_vecSpread.x, bDoTracers, bDoImpacts );
+ }
+#endif
+
+#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) ) & (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, CDmgAccumulator *pAccumulator )
+{
+#ifdef GAME_DLL
+ // Make sure our damage filter allows the damage.
+ if ( !PassesDamageFilter( info ))
+ {
+ return;
+ }
+#endif
+
+ TraceAttack( info, vecDir, ptr, pAccumulator );
+}
+
+void CBaseEntity::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ Vector vecOrigin = ptr->endpos - vecDir * 4;
+
+ if ( m_takedamage )
+ {
+#ifdef GAME_DLL
+ if ( pAccumulator )
+ {
+ pAccumulator->AccumulateMultiDamage( info, this );
+ }
+ else
+#endif // GAME_DLL
+ {
+ 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 )
+{
+#ifndef HL2MP
+ 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;
+ }
+#endif
+
+ 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;
+}
+
+void CBaseEntity::ModifyEmitSoundParams( EmitSound_t ¶ms )
+{
+#ifdef CLIENT_DLL
+ if ( GameRules() )
+ {
+ params.m_pSoundName = GameRules()->TranslateEffectForVisionFilter( "sounds", params.m_pSoundName );
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// 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 &inVecImpulse )
+{
+ // 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 ( inVecImpulse != vec3_origin )
+ {
+ Vector vecImpulse = inVecImpulse;
+
+ // Safety check against receive a huge impulse, which can explode physics
+ switch ( CheckEntityVelocity( vecImpulse ) )
+ {
+ case -1:
+ Warning( "Discarding ApplyLocalVelocityImpulse(%f,%f,%f) on %s\n", vecImpulse.x, vecImpulse.y, vecImpulse.z, GetDebugName() );
+ Assert( false );
+ return;
+ case 0:
+ if ( CheckEmitReasonablePhysicsSpew() )
+ {
+ Warning( "Clamping ApplyLocalVelocityImpulse(%f,%f,%f) on %s\n", inVecImpulse.x, inVecImpulse.y, inVecImpulse.z, GetDebugName() );
+ }
+ break;
+ }
+
+ if ( GetMoveType() == MOVETYPE_VPHYSICS )
+ {
+ Vector worldVel;
+ VPhysicsGetObject()->LocalToWorld( &worldVel, vecImpulse );
+ VPhysicsGetObject()->AddVelocity( &worldVel, NULL );
+ }
+ else
+ {
+ InvalidatePhysicsRecursive( VELOCITY_CHANGED );
+ m_vecVelocity += vecImpulse;
+ }
+ }
+}
+
+void CBaseEntity::ApplyAbsVelocityImpulse( const Vector &inVecImpulse )
+{
+ if ( inVecImpulse != vec3_origin )
+ {
+ Vector vecImpulse = inVecImpulse;
+
+ // Safety check against receive a huge impulse, which can explode physics
+ switch ( CheckEntityVelocity( vecImpulse ) )
+ {
+ case -1:
+ Warning( "Discarding ApplyAbsVelocityImpulse(%f,%f,%f) on %s\n", vecImpulse.x, vecImpulse.y, vecImpulse.z, GetDebugName() );
+ Assert( false );
+ return;
+ case 0:
+ if ( CheckEmitReasonablePhysicsSpew() )
+ {
+ Warning( "Clamping ApplyAbsVelocityImpulse(%f,%f,%f) on %s\n", inVecImpulse.x, inVecImpulse.y, inVecImpulse.z, GetDebugName() );
+ }
+ break;
+ }
+
+ if ( GetMoveType() == MOVETYPE_VPHYSICS )
+ {
+ VPhysicsGetObject()->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 )
+ {
+ // Safety check against receive a huge impulse, which can explode physics
+ if ( !IsEntityAngularVelocityReasonable( angImpulse ) )
+ {
+ Warning( "Bad ApplyLocalAngularVelocityImpulse(%f,%f,%f) on %s\n", angImpulse.x, angImpulse.y, angImpulse.z, GetDebugName() );
+ Assert( false );
+ return;
+ }
+
+ if ( GetMoveType() == MOVETYPE_VPHYSICS )
+ {
+ VPhysicsGetObject()->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
|