From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/shared/baseentity_shared.cpp | 5116 +++++++++++++++--------------- 1 file changed, 2558 insertions(+), 2558 deletions(-) (limited to 'mp/src/game/shared/baseentity_shared.cpp') 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(&m_pfnThink)), "BaseThinkFunc" ); -#endif -#endif - return m_pfnThink; - } - - // Find the think function in our list, and if we couldn't find it, register it - int iIndex = GetIndexForThinkContext( szContext ); - if ( iIndex == NO_THINK_CONTEXT ) - { - iIndex = RegisterThinkContext( szContext ); - } - - m_aThinkFunctions[ iIndex ].m_pfnThink = func; -#if !defined( CLIENT_DLL ) -#ifdef _DEBUG - FunctionCheck( *(reinterpret_cast(&m_aThinkFunctions[ iIndex ].m_pfnThink)), szContext ); -#endif -#endif - - if ( thinkTime != 0 ) - { - int thinkTick = ( thinkTime == TICK_NEVER_THINK ) ? TICK_NEVER_THINK : TIME_TO_TICKS( thinkTime ); - m_aThinkFunctions[ iIndex ].m_nNextThinkTick = thinkTick; - CheckHasThinkFunction( thinkTick == TICK_NEVER_THINK ? false : true ); - } - return func; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CBaseEntity::SetNextThink( float thinkTime, const char *szContext ) -{ - int thinkTick = ( thinkTime == TICK_NEVER_THINK ) ? TICK_NEVER_THINK : TIME_TO_TICKS( thinkTime ); - - // Are we currently in a think function with a context? - int iIndex = 0; - if ( !szContext ) - { -#ifdef _DEBUG - if ( m_iCurrentThinkContext != NO_THINK_CONTEXT ) - { - Msg( "Warning: Setting base think function within think context %s\n", STRING(m_aThinkFunctions[m_iCurrentThinkContext].m_iszContext) ); - } -#endif - - // Old system - m_nNextThinkTick = thinkTick; - CheckHasThinkFunction( thinkTick == TICK_NEVER_THINK ? false : true ); - return; - } - else - { - // Find the think function in our list, and if we couldn't find it, register it - iIndex = GetIndexForThinkContext( szContext ); - if ( iIndex == NO_THINK_CONTEXT ) - { - iIndex = RegisterThinkContext( szContext ); - } - } - - // Old system - m_aThinkFunctions[ iIndex ].m_nNextThinkTick = thinkTick; - CheckHasThinkFunction( thinkTick == TICK_NEVER_THINK ? false : true ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -float CBaseEntity::GetNextThink( const char *szContext ) -{ - // Are we currently in a think function with a context? - int iIndex = 0; - if ( !szContext ) - { -#ifdef _DEBUG - if ( m_iCurrentThinkContext != NO_THINK_CONTEXT ) - { - Msg( "Warning: Getting base nextthink time within think context %s\n", STRING(m_aThinkFunctions[m_iCurrentThinkContext].m_iszContext) ); - } -#endif - - if ( m_nNextThinkTick == TICK_NEVER_THINK ) - return TICK_NEVER_THINK; - - // Old system - return TICK_INTERVAL * (m_nNextThinkTick ); - } - else - { - // Find the think function in our list - iIndex = GetIndexForThinkContext( szContext ); - } - - if ( iIndex == m_aThinkFunctions.InvalidIndex() ) - return TICK_NEVER_THINK; - - if ( m_aThinkFunctions[ iIndex ].m_nNextThinkTick == TICK_NEVER_THINK ) - { - return TICK_NEVER_THINK; - } - return TICK_INTERVAL * (m_aThinkFunctions[ iIndex ].m_nNextThinkTick ); -} - -int CBaseEntity::GetNextThinkTick( const char *szContext /*= NULL*/ ) -{ - // Are we currently in a think function with a context? - int iIndex = 0; - if ( !szContext ) - { -#ifdef _DEBUG - if ( m_iCurrentThinkContext != NO_THINK_CONTEXT ) - { - Msg( "Warning: Getting base nextthink time within think context %s\n", STRING(m_aThinkFunctions[m_iCurrentThinkContext].m_iszContext) ); - } -#endif - - if ( m_nNextThinkTick == TICK_NEVER_THINK ) - return TICK_NEVER_THINK; - - // Old system - return m_nNextThinkTick; - } - else - { - // Find the think function in our list - iIndex = GetIndexForThinkContext( szContext ); - - // Looking up an invalid think context! - Assert( iIndex != -1 ); - } - - if ( ( iIndex == -1 ) || ( m_aThinkFunctions[ iIndex ].m_nNextThinkTick == TICK_NEVER_THINK ) ) - { - return TICK_NEVER_THINK; - } - - return m_aThinkFunctions[ iIndex ].m_nNextThinkTick; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -float CBaseEntity::GetLastThink( const char *szContext ) -{ - // Are we currently in a think function with a context? - int iIndex = 0; - if ( !szContext ) - { -#ifdef _DEBUG - if ( m_iCurrentThinkContext != NO_THINK_CONTEXT ) - { - Msg( "Warning: Getting base lastthink time within think context %s\n", STRING(m_aThinkFunctions[m_iCurrentThinkContext].m_iszContext) ); - } -#endif - // Old system - return m_nLastThinkTick * TICK_INTERVAL; - } - else - { - // Find the think function in our list - iIndex = GetIndexForThinkContext( szContext ); - } - - return m_aThinkFunctions[ iIndex ].m_nLastThinkTick * TICK_INTERVAL; -} - -int CBaseEntity::GetLastThinkTick( const char *szContext /*= NULL*/ ) -{ - // Are we currently in a think function with a context? - int iIndex = 0; - if ( !szContext ) - { -#ifdef _DEBUG - if ( m_iCurrentThinkContext != NO_THINK_CONTEXT ) - { - Msg( "Warning: Getting base lastthink time within think context %s\n", STRING(m_aThinkFunctions[m_iCurrentThinkContext].m_iszContext) ); - } -#endif - // Old system - return m_nLastThinkTick; - } - else - { - // Find the think function in our list - iIndex = GetIndexForThinkContext( szContext ); - } - - return m_aThinkFunctions[ iIndex ].m_nLastThinkTick; -} - -bool CBaseEntity::WillThink() -{ - if ( m_nNextThinkTick > 0 ) - return true; - - for ( int i = 0; i < m_aThinkFunctions.Count(); i++ ) - { - if ( m_aThinkFunctions[i].m_nNextThinkTick > 0 ) - return true; - } - - return false; -} - -// 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(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(&m_pfnThink)), "BaseThinkFunc" ); +#endif +#endif + return m_pfnThink; + } + + // Find the think function in our list, and if we couldn't find it, register it + int iIndex = GetIndexForThinkContext( szContext ); + if ( iIndex == NO_THINK_CONTEXT ) + { + iIndex = RegisterThinkContext( szContext ); + } + + m_aThinkFunctions[ iIndex ].m_pfnThink = func; +#if !defined( CLIENT_DLL ) +#ifdef _DEBUG + FunctionCheck( *(reinterpret_cast(&m_aThinkFunctions[ iIndex ].m_pfnThink)), szContext ); +#endif +#endif + + if ( thinkTime != 0 ) + { + int thinkTick = ( thinkTime == TICK_NEVER_THINK ) ? TICK_NEVER_THINK : TIME_TO_TICKS( thinkTime ); + m_aThinkFunctions[ iIndex ].m_nNextThinkTick = thinkTick; + CheckHasThinkFunction( thinkTick == TICK_NEVER_THINK ? false : true ); + } + return func; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseEntity::SetNextThink( float thinkTime, const char *szContext ) +{ + int thinkTick = ( thinkTime == TICK_NEVER_THINK ) ? TICK_NEVER_THINK : TIME_TO_TICKS( thinkTime ); + + // Are we currently in a think function with a context? + int iIndex = 0; + if ( !szContext ) + { +#ifdef _DEBUG + if ( m_iCurrentThinkContext != NO_THINK_CONTEXT ) + { + Msg( "Warning: Setting base think function within think context %s\n", STRING(m_aThinkFunctions[m_iCurrentThinkContext].m_iszContext) ); + } +#endif + + // Old system + m_nNextThinkTick = thinkTick; + CheckHasThinkFunction( thinkTick == TICK_NEVER_THINK ? false : true ); + return; + } + else + { + // Find the think function in our list, and if we couldn't find it, register it + iIndex = GetIndexForThinkContext( szContext ); + if ( iIndex == NO_THINK_CONTEXT ) + { + iIndex = RegisterThinkContext( szContext ); + } + } + + // Old system + m_aThinkFunctions[ iIndex ].m_nNextThinkTick = thinkTick; + CheckHasThinkFunction( thinkTick == TICK_NEVER_THINK ? false : true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CBaseEntity::GetNextThink( const char *szContext ) +{ + // Are we currently in a think function with a context? + int iIndex = 0; + if ( !szContext ) + { +#ifdef _DEBUG + if ( m_iCurrentThinkContext != NO_THINK_CONTEXT ) + { + Msg( "Warning: Getting base nextthink time within think context %s\n", STRING(m_aThinkFunctions[m_iCurrentThinkContext].m_iszContext) ); + } +#endif + + if ( m_nNextThinkTick == TICK_NEVER_THINK ) + return TICK_NEVER_THINK; + + // Old system + return TICK_INTERVAL * (m_nNextThinkTick ); + } + else + { + // Find the think function in our list + iIndex = GetIndexForThinkContext( szContext ); + } + + if ( iIndex == m_aThinkFunctions.InvalidIndex() ) + return TICK_NEVER_THINK; + + if ( m_aThinkFunctions[ iIndex ].m_nNextThinkTick == TICK_NEVER_THINK ) + { + return TICK_NEVER_THINK; + } + return TICK_INTERVAL * (m_aThinkFunctions[ iIndex ].m_nNextThinkTick ); +} + +int CBaseEntity::GetNextThinkTick( const char *szContext /*= NULL*/ ) +{ + // Are we currently in a think function with a context? + int iIndex = 0; + if ( !szContext ) + { +#ifdef _DEBUG + if ( m_iCurrentThinkContext != NO_THINK_CONTEXT ) + { + Msg( "Warning: Getting base nextthink time within think context %s\n", STRING(m_aThinkFunctions[m_iCurrentThinkContext].m_iszContext) ); + } +#endif + + if ( m_nNextThinkTick == TICK_NEVER_THINK ) + return TICK_NEVER_THINK; + + // Old system + return m_nNextThinkTick; + } + else + { + // Find the think function in our list + iIndex = GetIndexForThinkContext( szContext ); + + // Looking up an invalid think context! + Assert( iIndex != -1 ); + } + + if ( ( iIndex == -1 ) || ( m_aThinkFunctions[ iIndex ].m_nNextThinkTick == TICK_NEVER_THINK ) ) + { + return TICK_NEVER_THINK; + } + + return m_aThinkFunctions[ iIndex ].m_nNextThinkTick; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CBaseEntity::GetLastThink( const char *szContext ) +{ + // Are we currently in a think function with a context? + int iIndex = 0; + if ( !szContext ) + { +#ifdef _DEBUG + if ( m_iCurrentThinkContext != NO_THINK_CONTEXT ) + { + Msg( "Warning: Getting base lastthink time within think context %s\n", STRING(m_aThinkFunctions[m_iCurrentThinkContext].m_iszContext) ); + } +#endif + // Old system + return m_nLastThinkTick * TICK_INTERVAL; + } + else + { + // Find the think function in our list + iIndex = GetIndexForThinkContext( szContext ); + } + + return m_aThinkFunctions[ iIndex ].m_nLastThinkTick * TICK_INTERVAL; +} + +int CBaseEntity::GetLastThinkTick( const char *szContext /*= NULL*/ ) +{ + // Are we currently in a think function with a context? + int iIndex = 0; + if ( !szContext ) + { +#ifdef _DEBUG + if ( m_iCurrentThinkContext != NO_THINK_CONTEXT ) + { + Msg( "Warning: Getting base lastthink time within think context %s\n", STRING(m_aThinkFunctions[m_iCurrentThinkContext].m_iszContext) ); + } +#endif + // Old system + return m_nLastThinkTick; + } + else + { + // Find the think function in our list + iIndex = GetIndexForThinkContext( szContext ); + } + + return m_aThinkFunctions[ iIndex ].m_nLastThinkTick; +} + +bool CBaseEntity::WillThink() +{ + if ( m_nNextThinkTick > 0 ) + return true; + + for ( int i = 0; i < m_aThinkFunctions.Count(); i++ ) + { + if ( m_aThinkFunctions[i].m_nNextThinkTick > 0 ) + return true; + } + + return false; +} + +// 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(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 -- cgit v1.2.3