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