diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/shared/baseentity_shared.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip | |
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/shared/baseentity_shared.cpp')
| -rw-r--r-- | mp/src/game/shared/baseentity_shared.cpp | 5116 |
1 files changed, 2558 insertions, 2558 deletions
diff --git a/mp/src/game/shared/baseentity_shared.cpp b/mp/src/game/shared/baseentity_shared.cpp index 949143b1..43348cb1 100644 --- a/mp/src/game/shared/baseentity_shared.cpp +++ b/mp/src/game/shared/baseentity_shared.cpp @@ -1,2558 +1,2558 @@ -//========= 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
-
-#ifdef TF_DLL
-#include "tf_gamerules.h"
-#include "tf_weaponbase.h"
-#endif // TF_DLL
-
-#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;
- }
-#elif TF_DLL
- CTraceFilterIgnoreFriendlyCombatItems traceFilterCombatItem( this, COLLISION_GROUP_NONE, GetTeamNumber() );
- if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() )
- {
- CTraceFilterChain traceFilterChain( &traceFilter, &traceFilterCombatItem );
- AI_TraceLine(info.m_vecSrc, vecEnd, MASK_SHOT, &traceFilterChain, &tr);
- }
- else
- {
- AI_TraceLine(info.m_vecSrc, vecEnd, MASK_SHOT, &traceFilter, &tr);
- }
-#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
+//========= 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 + +#ifdef TF_DLL +#include "tf_gamerules.h" +#include "tf_weaponbase.h" +#endif // TF_DLL + +#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; + } +#elif TF_DLL + CTraceFilterIgnoreFriendlyCombatItems traceFilterCombatItem( this, COLLISION_GROUP_NONE, GetTeamNumber() ); + if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() ) + { + CTraceFilterChain traceFilterChain( &traceFilter, &traceFilterCombatItem ); + AI_TraceLine(info.m_vecSrc, vecEnd, MASK_SHOT, &traceFilterChain, &tr); + } + else + { + AI_TraceLine(info.m_vecSrc, vecEnd, MASK_SHOT, &traceFilter, &tr); + } +#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 |