You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2574 lines
69 KiB
2574 lines
69 KiB
//========= 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; |
|
#ifdef GAME_DLL |
|
m_nPredictionRandomSeedServer = -1; |
|
#endif |
|
|
|
return; |
|
} |
|
|
|
m_nPredictionRandomSeed = ( cmd->random_seed ); |
|
#ifdef GAME_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 indexD = decalsystem->GetDecalIndexForName( decalName ); |
|
if ( indexD < 0 ) |
|
return; |
|
|
|
Assert( pTrace->m_pEnt ); |
|
|
|
CBroadcastRecipientFilter filter; |
|
te->Decal( filter, 0.0, &pTrace->endpos, &pTrace->startpos, |
|
pTrace->GetEntityIndex(), pTrace->hitbox, indexD ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Base handling for impacts against entities |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::ImpactTrace( trace_t *pTrace, int iDamageType, const char *pCustomImpactName ) |
|
{ |
|
VPROF( "CBaseEntity::ImpactTrace" ); |
|
Assert( pTrace->m_pEnt ); |
|
|
|
CBaseEntity *pEntity = pTrace->m_pEnt; |
|
|
|
// Build the impact data |
|
CEffectData data; |
|
data.m_vOrigin = pTrace->endpos; |
|
data.m_vStart = pTrace->startpos; |
|
data.m_nSurfaceProp = pTrace->surface.surfaceProps; |
|
data.m_nDamageType = iDamageType; |
|
data.m_nHitBox = pTrace->hitbox; |
|
#ifdef CLIENT_DLL |
|
data.m_hEntity = ClientEntityList().EntIndexToHandle( pEntity->entindex() ); |
|
#else |
|
data.m_nEntIndex = pEntity->entindex(); |
|
#endif |
|
|
|
// Send it on its way |
|
if ( !pCustomImpactName ) |
|
{ |
|
DispatchEffect( "Impact", data ); |
|
} |
|
else |
|
{ |
|
DispatchEffect( pCustomImpactName, data ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: returns the damage decal to use, given a damage type |
|
// Input : bitsDamageType - the damage type |
|
// Output : the index of the damage decal to use |
|
//----------------------------------------------------------------------------- |
|
char const *CBaseEntity::DamageDecal( int bitsDamageType, int gameMaterial ) |
|
{ |
|
if ( m_nRenderMode == kRenderTransAlpha ) |
|
return ""; |
|
|
|
if ( m_nRenderMode != kRenderNormal && gameMaterial == 'G' ) |
|
return "BulletProof"; |
|
|
|
if ( bitsDamageType == DMG_SLASH ) |
|
return "ManhackCut"; |
|
|
|
// This will get translated at a lower layer based on game material |
|
return "Impact.Concrete"; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CBaseEntity::GetIndexForThinkContext( const char *pszContext ) |
|
{ |
|
for ( int i = 0; i < m_aThinkFunctions.Size(); i++ ) |
|
{ |
|
if ( !Q_strncmp( STRING( m_aThinkFunctions[i].m_iszContext ), pszContext, MAX_CONTEXT_LENGTH ) ) |
|
return i; |
|
} |
|
|
|
return NO_THINK_CONTEXT; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get a fresh think context for this entity |
|
//----------------------------------------------------------------------------- |
|
int CBaseEntity::RegisterThinkContext( const char *szContext ) |
|
{ |
|
int iIndex = GetIndexForThinkContext( szContext ); |
|
if ( iIndex != NO_THINK_CONTEXT ) |
|
return iIndex; |
|
|
|
// Make a new think func |
|
thinkfunc_t sNewFunc; |
|
Q_memset( &sNewFunc, 0, sizeof( sNewFunc ) ); |
|
sNewFunc.m_pfnThink = NULL; |
|
sNewFunc.m_nNextThinkTick = 0; |
|
sNewFunc.m_iszContext = AllocPooledString(szContext); |
|
|
|
// Insert it into our list |
|
return m_aThinkFunctions.AddToTail( sNewFunc ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
BASEPTR CBaseEntity::ThinkSet( BASEPTR func, float thinkTime, const char *szContext ) |
|
{ |
|
#if !defined( CLIENT_DLL ) |
|
#ifdef _DEBUG |
|
#ifdef GNUC |
|
COMPILE_TIME_ASSERT( sizeof(func) == 8 ); |
|
#else |
|
COMPILE_TIME_ASSERT( sizeof(func) == 4 ); |
|
#endif |
|
#endif |
|
#endif |
|
|
|
// Old system? |
|
if ( !szContext ) |
|
{ |
|
m_pfnThink = func; |
|
#if !defined( CLIENT_DLL ) |
|
#ifdef _DEBUG |
|
FunctionCheck( *(reinterpret_cast<void **>(&m_pfnThink)), "BaseThinkFunc" ); |
|
#endif |
|
#endif |
|
return m_pfnThink; |
|
} |
|
|
|
// Find the think function in our list, and if we couldn't find it, register it |
|
int iIndex = GetIndexForThinkContext( szContext ); |
|
if ( iIndex == NO_THINK_CONTEXT ) |
|
{ |
|
iIndex = RegisterThinkContext( szContext ); |
|
} |
|
|
|
m_aThinkFunctions[ iIndex ].m_pfnThink = func; |
|
#if !defined( CLIENT_DLL ) |
|
#ifdef _DEBUG |
|
FunctionCheck( *(reinterpret_cast<void **>(&m_aThinkFunctions[ iIndex ].m_pfnThink)), szContext ); |
|
#endif |
|
#endif |
|
|
|
if ( thinkTime != 0 ) |
|
{ |
|
int thinkTick = ( thinkTime == TICK_NEVER_THINK ) ? TICK_NEVER_THINK : TIME_TO_TICKS( thinkTime ); |
|
m_aThinkFunctions[ iIndex ].m_nNextThinkTick = thinkTick; |
|
CheckHasThinkFunction( thinkTick == TICK_NEVER_THINK ? false : true ); |
|
} |
|
return func; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::SetNextThink( float thinkTime, const char *szContext ) |
|
{ |
|
int thinkTick = ( thinkTime == TICK_NEVER_THINK ) ? TICK_NEVER_THINK : TIME_TO_TICKS( thinkTime ); |
|
|
|
// Are we currently in a think function with a context? |
|
int iIndex = 0; |
|
if ( !szContext ) |
|
{ |
|
#ifdef _DEBUG |
|
if ( m_iCurrentThinkContext != NO_THINK_CONTEXT ) |
|
{ |
|
Msg( "Warning: Setting base think function within think context %s\n", STRING(m_aThinkFunctions[m_iCurrentThinkContext].m_iszContext) ); |
|
} |
|
#endif |
|
|
|
// Old system |
|
m_nNextThinkTick = thinkTick; |
|
CheckHasThinkFunction( thinkTick == TICK_NEVER_THINK ? false : true ); |
|
return; |
|
} |
|
else |
|
{ |
|
// Find the think function in our list, and if we couldn't find it, register it |
|
iIndex = GetIndexForThinkContext( szContext ); |
|
if ( iIndex == NO_THINK_CONTEXT ) |
|
{ |
|
iIndex = RegisterThinkContext( szContext ); |
|
} |
|
} |
|
|
|
// Old system |
|
m_aThinkFunctions[ iIndex ].m_nNextThinkTick = thinkTick; |
|
CheckHasThinkFunction( thinkTick == TICK_NEVER_THINK ? false : true ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
float CBaseEntity::GetNextThink( const char *szContext ) |
|
{ |
|
// Are we currently in a think function with a context? |
|
int iIndex = 0; |
|
if ( !szContext ) |
|
{ |
|
#ifdef _DEBUG |
|
if ( m_iCurrentThinkContext != NO_THINK_CONTEXT ) |
|
{ |
|
Msg( "Warning: Getting base nextthink time within think context %s\n", STRING(m_aThinkFunctions[m_iCurrentThinkContext].m_iszContext) ); |
|
} |
|
#endif |
|
|
|
if ( m_nNextThinkTick == TICK_NEVER_THINK ) |
|
return TICK_NEVER_THINK; |
|
|
|
// Old system |
|
return TICK_INTERVAL * (m_nNextThinkTick ); |
|
} |
|
else |
|
{ |
|
// Find the think function in our list |
|
iIndex = GetIndexForThinkContext( szContext ); |
|
} |
|
|
|
if ( iIndex == m_aThinkFunctions.InvalidIndex() ) |
|
return TICK_NEVER_THINK; |
|
|
|
if ( m_aThinkFunctions[ iIndex ].m_nNextThinkTick == TICK_NEVER_THINK ) |
|
{ |
|
return TICK_NEVER_THINK; |
|
} |
|
return TICK_INTERVAL * (m_aThinkFunctions[ iIndex ].m_nNextThinkTick ); |
|
} |
|
|
|
int CBaseEntity::GetNextThinkTick( const char *szContext /*= NULL*/ ) |
|
{ |
|
// Are we currently in a think function with a context? |
|
int iIndex = 0; |
|
if ( !szContext ) |
|
{ |
|
#ifdef _DEBUG |
|
if ( m_iCurrentThinkContext != NO_THINK_CONTEXT ) |
|
{ |
|
Msg( "Warning: Getting base nextthink time within think context %s\n", STRING(m_aThinkFunctions[m_iCurrentThinkContext].m_iszContext) ); |
|
} |
|
#endif |
|
|
|
if ( m_nNextThinkTick == TICK_NEVER_THINK ) |
|
return TICK_NEVER_THINK; |
|
|
|
// Old system |
|
return m_nNextThinkTick; |
|
} |
|
else |
|
{ |
|
// Find the think function in our list |
|
iIndex = GetIndexForThinkContext( szContext ); |
|
|
|
// Looking up an invalid think context! |
|
Assert( iIndex != -1 ); |
|
} |
|
|
|
if ( ( iIndex == -1 ) || ( m_aThinkFunctions[ iIndex ].m_nNextThinkTick == TICK_NEVER_THINK ) ) |
|
{ |
|
return TICK_NEVER_THINK; |
|
} |
|
|
|
return m_aThinkFunctions[ iIndex ].m_nNextThinkTick; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
float CBaseEntity::GetLastThink( const char *szContext ) |
|
{ |
|
// Are we currently in a think function with a context? |
|
int iIndex = 0; |
|
if ( !szContext ) |
|
{ |
|
#ifdef _DEBUG |
|
if ( m_iCurrentThinkContext != NO_THINK_CONTEXT ) |
|
{ |
|
Msg( "Warning: Getting base lastthink time within think context %s\n", STRING(m_aThinkFunctions[m_iCurrentThinkContext].m_iszContext) ); |
|
} |
|
#endif |
|
// Old system |
|
return m_nLastThinkTick * TICK_INTERVAL; |
|
} |
|
else |
|
{ |
|
// Find the think function in our list |
|
iIndex = GetIndexForThinkContext( szContext ); |
|
} |
|
|
|
return m_aThinkFunctions[ iIndex ].m_nLastThinkTick * TICK_INTERVAL; |
|
} |
|
|
|
int CBaseEntity::GetLastThinkTick( const char *szContext /*= NULL*/ ) |
|
{ |
|
// Are we currently in a think function with a context? |
|
int iIndex = 0; |
|
if ( !szContext ) |
|
{ |
|
#ifdef _DEBUG |
|
if ( m_iCurrentThinkContext != NO_THINK_CONTEXT ) |
|
{ |
|
Msg( "Warning: Getting base lastthink time within think context %s\n", STRING(m_aThinkFunctions[m_iCurrentThinkContext].m_iszContext) ); |
|
} |
|
#endif |
|
// Old system |
|
return m_nLastThinkTick; |
|
} |
|
else |
|
{ |
|
// Find the think function in our list |
|
iIndex = GetIndexForThinkContext( szContext ); |
|
} |
|
|
|
return m_aThinkFunctions[ iIndex ].m_nLastThinkTick; |
|
} |
|
|
|
bool CBaseEntity::WillThink() |
|
{ |
|
if ( m_nNextThinkTick > 0 ) |
|
return true; |
|
|
|
for ( int i = 0; i < m_aThinkFunctions.Count(); i++ ) |
|
{ |
|
if ( m_aThinkFunctions[i].m_nNextThinkTick > 0 ) |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
// returns the first tick the entity will run any think function |
|
// returns TICK_NEVER_THINK if no think functions are scheduled |
|
int CBaseEntity::GetFirstThinkTick() |
|
{ |
|
int minTick = TICK_NEVER_THINK; |
|
if ( m_nNextThinkTick > 0 ) |
|
{ |
|
minTick = m_nNextThinkTick; |
|
} |
|
|
|
for ( int i = 0; i < m_aThinkFunctions.Count(); i++ ) |
|
{ |
|
int next = m_aThinkFunctions[i].m_nNextThinkTick; |
|
if ( next > 0 ) |
|
{ |
|
if ( next < minTick || minTick == TICK_NEVER_THINK ) |
|
{ |
|
minTick = next; |
|
} |
|
} |
|
} |
|
return minTick; |
|
} |
|
|
|
// NOTE: pass in the isThinking hint so we have to search the think functions less |
|
void CBaseEntity::CheckHasThinkFunction( bool isThinking ) |
|
{ |
|
if ( IsEFlagSet( EFL_NO_THINK_FUNCTION ) && isThinking ) |
|
{ |
|
RemoveEFlags( EFL_NO_THINK_FUNCTION ); |
|
} |
|
else if ( !isThinking && !IsEFlagSet( EFL_NO_THINK_FUNCTION ) && !WillThink() ) |
|
{ |
|
AddEFlags( EFL_NO_THINK_FUNCTION ); |
|
} |
|
#if !defined( CLIENT_DLL ) |
|
SimThink_EntityChanged( this ); |
|
#endif |
|
} |
|
|
|
bool CBaseEntity::WillSimulateGamePhysics() |
|
{ |
|
// players always simulate game physics |
|
if ( !IsPlayer() ) |
|
{ |
|
MoveType_t movetype = GetMoveType(); |
|
|
|
if ( movetype == MOVETYPE_NONE || movetype == MOVETYPE_VPHYSICS ) |
|
return false; |
|
|
|
#if !defined( CLIENT_DLL ) |
|
// MOVETYPE_PUSH not supported on the client |
|
if ( movetype == MOVETYPE_PUSH && GetMoveDoneTime() <= 0 ) |
|
return false; |
|
#endif |
|
} |
|
|
|
return true; |
|
} |
|
|
|
void CBaseEntity::CheckHasGamePhysicsSimulation() |
|
{ |
|
bool isSimulating = WillSimulateGamePhysics(); |
|
if ( isSimulating != IsEFlagSet(EFL_NO_GAME_PHYSICS_SIMULATION) ) |
|
return; |
|
if ( isSimulating ) |
|
{ |
|
RemoveEFlags( EFL_NO_GAME_PHYSICS_SIMULATION ); |
|
} |
|
else |
|
{ |
|
AddEFlags( EFL_NO_GAME_PHYSICS_SIMULATION ); |
|
} |
|
#if !defined( CLIENT_DLL ) |
|
SimThink_EntityChanged( this ); |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Sets/Gets the next think based on context index |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::SetNextThink( int nContextIndex, float thinkTime ) |
|
{ |
|
int thinkTick = ( thinkTime == TICK_NEVER_THINK ) ? TICK_NEVER_THINK : TIME_TO_TICKS( thinkTime ); |
|
|
|
if (nContextIndex < 0) |
|
{ |
|
SetNextThink( thinkTime ); |
|
} |
|
else |
|
{ |
|
m_aThinkFunctions[nContextIndex].m_nNextThinkTick = thinkTick; |
|
} |
|
CheckHasThinkFunction( thinkTick == TICK_NEVER_THINK ? false : true ); |
|
} |
|
|
|
void CBaseEntity::SetLastThink( int nContextIndex, float thinkTime ) |
|
{ |
|
int thinkTick = ( thinkTime == TICK_NEVER_THINK ) ? TICK_NEVER_THINK : TIME_TO_TICKS( thinkTime ); |
|
|
|
if (nContextIndex < 0) |
|
{ |
|
m_nLastThinkTick = thinkTick; |
|
} |
|
else |
|
{ |
|
m_aThinkFunctions[nContextIndex].m_nLastThinkTick = thinkTick; |
|
} |
|
} |
|
|
|
float CBaseEntity::GetNextThink( int nContextIndex ) const |
|
{ |
|
if (nContextIndex < 0) |
|
return m_nNextThinkTick * TICK_INTERVAL; |
|
|
|
return m_aThinkFunctions[nContextIndex].m_nNextThinkTick * TICK_INTERVAL; |
|
} |
|
|
|
int CBaseEntity::GetNextThinkTick( int nContextIndex ) const |
|
{ |
|
if (nContextIndex < 0) |
|
return m_nNextThinkTick; |
|
|
|
return m_aThinkFunctions[nContextIndex].m_nNextThinkTick; |
|
} |
|
|
|
|
|
int CheckEntityVelocity( Vector &v ) |
|
{ |
|
float r = k_flMaxEntitySpeed; |
|
if ( |
|
v.x > -r && v.x < r && |
|
v.y > -r && v.y < r && |
|
v.z > -r && v.z < r) |
|
{ |
|
// The usual case. It's totally reasonable |
|
return 1; |
|
} |
|
float speed = v.Length(); |
|
if ( speed < k_flMaxEntitySpeed * 100.0f ) |
|
{ |
|
// Sort of suspicious. Clamp it |
|
v *= k_flMaxEntitySpeed / speed; |
|
return 0; |
|
} |
|
|
|
// A terrible, horrible, no good, very bad velocity. |
|
return -1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: My physics object has been updated, react or extract data |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::VPhysicsUpdate( IPhysicsObject *pPhysics ) |
|
{ |
|
switch( GetMoveType() ) |
|
{ |
|
case MOVETYPE_VPHYSICS: |
|
{ |
|
if ( GetMoveParent() ) |
|
{ |
|
DevWarning("Updating physics on object in hierarchy %s!\n", GetClassname()); |
|
return; |
|
} |
|
Vector origin; |
|
QAngle angles; |
|
|
|
pPhysics->GetPosition( &origin, &angles ); |
|
|
|
if ( !IsEntityQAngleReasonable( angles ) ) |
|
{ |
|
if ( CheckEmitReasonablePhysicsSpew() ) |
|
{ |
|
Warning( "Ignoring bogus angles (%f,%f,%f) from vphysics! (entity %s)\n", angles.x, angles.y, angles.z, GetDebugName() ); |
|
} |
|
angles = vec3_angle; |
|
} |
|
#ifndef CLIENT_DLL |
|
Vector prevOrigin = GetAbsOrigin(); |
|
#endif |
|
|
|
if ( IsEntityPositionReasonable( origin ) ) |
|
{ |
|
SetAbsOrigin( origin ); |
|
} |
|
else |
|
{ |
|
if ( CheckEmitReasonablePhysicsSpew() ) |
|
{ |
|
Warning( "Ignoring unreasonable position (%f,%f,%f) from vphysics! (entity %s)\n", origin.x, origin.y, origin.z, GetDebugName() ); |
|
} |
|
} |
|
|
|
for ( int i = 0; i < 3; ++i ) |
|
{ |
|
angles[ i ] = AngleNormalize( angles[ i ] ); |
|
} |
|
SetAbsAngles( angles ); |
|
|
|
// Interactive debris converts back to debris when it comes to rest |
|
if ( pPhysics->IsAsleep() && GetCollisionGroup() == COLLISION_GROUP_INTERACTIVE_DEBRIS ) |
|
{ |
|
SetCollisionGroup( COLLISION_GROUP_DEBRIS ); |
|
} |
|
|
|
#ifndef CLIENT_DLL |
|
PhysicsTouchTriggers( &prevOrigin ); |
|
PhysicsRelinkChildren(gpGlobals->frametime); |
|
#endif |
|
} |
|
break; |
|
|
|
case MOVETYPE_STEP: |
|
break; |
|
|
|
case MOVETYPE_PUSH: |
|
#ifndef CLIENT_DLL |
|
VPhysicsUpdatePusher( pPhysics ); |
|
#endif |
|
break; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Init this object's physics as a static |
|
//----------------------------------------------------------------------------- |
|
IPhysicsObject *CBaseEntity::VPhysicsInitStatic( void ) |
|
{ |
|
if ( !VPhysicsInitSetup() ) |
|
return NULL; |
|
|
|
#ifndef CLIENT_DLL |
|
// If this entity has a move parent, it needs to be shadow, not static |
|
if ( GetMoveParent() ) |
|
{ |
|
// must be SOLID_VPHYSICS if in hierarchy to solve collisions correctly |
|
if ( GetSolid() == SOLID_BSP && GetRootMoveParent()->GetSolid() != SOLID_BSP ) |
|
{ |
|
SetSolid( SOLID_VPHYSICS ); |
|
} |
|
|
|
return VPhysicsInitShadow( false, false ); |
|
} |
|
#endif |
|
|
|
// No physics |
|
if ( GetSolid() == SOLID_NONE ) |
|
return NULL; |
|
|
|
// create a static physics objct |
|
IPhysicsObject *pPhysicsObject = NULL; |
|
if ( GetSolid() == SOLID_BBOX ) |
|
{ |
|
pPhysicsObject = PhysModelCreateBox( this, WorldAlignMins(), WorldAlignMaxs(), GetAbsOrigin(), true ); |
|
} |
|
else |
|
{ |
|
pPhysicsObject = PhysModelCreateUnmoveable( this, GetModelIndex(), GetAbsOrigin(), GetAbsAngles() ); |
|
} |
|
VPhysicsSetObject( pPhysicsObject ); |
|
return pPhysicsObject; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pPhysics - |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::VPhysicsSetObject( IPhysicsObject *pPhysics ) |
|
{ |
|
if ( m_pPhysicsObject && pPhysics ) |
|
{ |
|
Warning( "Overwriting physics object for %s\n", GetClassname() ); |
|
} |
|
m_pPhysicsObject = pPhysics; |
|
if ( pPhysics && !m_pPhysicsObject ) |
|
{ |
|
CollisionRulesChanged(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::VPhysicsDestroyObject( void ) |
|
{ |
|
if ( m_pPhysicsObject ) |
|
{ |
|
#ifndef CLIENT_DLL |
|
PhysRemoveShadow( this ); |
|
#endif |
|
PhysDestroyObject( m_pPhysicsObject, this ); |
|
m_pPhysicsObject = NULL; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CBaseEntity::VPhysicsInitSetup() |
|
{ |
|
#ifndef CLIENT_DLL |
|
// don't support logical ents |
|
if ( !edict() || IsMarkedForDeletion() ) |
|
return false; |
|
#endif |
|
|
|
// If this entity already has a physics object, then it should have been deleted prior to making this call. |
|
Assert(!m_pPhysicsObject); |
|
VPhysicsDestroyObject(); |
|
|
|
// make sure absorigin / absangles are correct |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: This creates a normal vphysics simulated object |
|
// physics alone determines where it goes (gravity, friction, etc) |
|
// and the entity receives updates from vphysics. SetAbsOrigin(), etc do not affect the object! |
|
//----------------------------------------------------------------------------- |
|
IPhysicsObject *CBaseEntity::VPhysicsInitNormal( SolidType_t solidType, int nSolidFlags, bool createAsleep, solid_t *pSolid ) |
|
{ |
|
if ( !VPhysicsInitSetup() ) |
|
return NULL; |
|
|
|
// NOTE: This has to occur before PhysModelCreate because that call will |
|
// call back into ShouldCollide(), which uses solidtype for rules. |
|
SetSolid( solidType ); |
|
SetSolidFlags( nSolidFlags ); |
|
|
|
// No physics |
|
if ( solidType == SOLID_NONE ) |
|
{ |
|
return NULL; |
|
} |
|
|
|
// create a normal physics object |
|
IPhysicsObject *pPhysicsObject = PhysModelCreate( this, GetModelIndex(), GetAbsOrigin(), GetAbsAngles(), pSolid ); |
|
if ( pPhysicsObject ) |
|
{ |
|
VPhysicsSetObject( pPhysicsObject ); |
|
SetMoveType( MOVETYPE_VPHYSICS ); |
|
|
|
if ( !createAsleep ) |
|
{ |
|
pPhysicsObject->Wake(); |
|
} |
|
} |
|
|
|
return pPhysicsObject; |
|
} |
|
|
|
// This creates a vphysics object with a shadow controller that follows the AI |
|
IPhysicsObject *CBaseEntity::VPhysicsInitShadow( bool allowPhysicsMovement, bool allowPhysicsRotation, solid_t *pSolid ) |
|
{ |
|
if ( !VPhysicsInitSetup() ) |
|
return NULL; |
|
|
|
// No physics |
|
if ( GetSolid() == SOLID_NONE ) |
|
return NULL; |
|
|
|
const Vector &origin = GetAbsOrigin(); |
|
QAngle angles = GetAbsAngles(); |
|
IPhysicsObject *pPhysicsObject = NULL; |
|
|
|
if ( GetSolid() == SOLID_BBOX ) |
|
{ |
|
// adjust these so the game tracing epsilons match the physics minimum separation distance |
|
// this will shrink the vphysics version of the model by the difference in epsilons |
|
float radius = 0.25f - DIST_EPSILON; |
|
Vector mins = WorldAlignMins() + Vector(radius, radius, radius); |
|
Vector maxs = WorldAlignMaxs() - Vector(radius, radius, radius); |
|
pPhysicsObject = PhysModelCreateBox( this, mins, maxs, origin, false ); |
|
angles = vec3_angle; |
|
} |
|
else if ( GetSolid() == SOLID_OBB ) |
|
{ |
|
pPhysicsObject = PhysModelCreateOBB( this, CollisionProp()->OBBMins(), CollisionProp()->OBBMaxs(), origin, angles, false ); |
|
} |
|
else |
|
{ |
|
pPhysicsObject = PhysModelCreate( this, GetModelIndex(), origin, angles, pSolid ); |
|
} |
|
if ( !pPhysicsObject ) |
|
return NULL; |
|
|
|
VPhysicsSetObject( pPhysicsObject ); |
|
// UNDONE: Tune these speeds!!! |
|
pPhysicsObject->SetShadow( 1e4, 1e4, allowPhysicsMovement, allowPhysicsRotation ); |
|
pPhysicsObject->UpdateShadow( origin, angles, false, 0 ); |
|
return pPhysicsObject; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CBaseEntity::CreateVPhysics() |
|
{ |
|
return false; |
|
} |
|
|
|
bool CBaseEntity::IsStandable() const |
|
{ |
|
if (GetSolidFlags() & FSOLID_NOT_STANDABLE) |
|
return false; |
|
|
|
if ( GetSolid() == SOLID_BSP || GetSolid() == SOLID_VPHYSICS || GetSolid() == SOLID_BBOX ) |
|
return true; |
|
|
|
return IsBSPModel( ); |
|
} |
|
|
|
bool CBaseEntity::IsBSPModel() const |
|
{ |
|
if ( GetSolid() == SOLID_BSP ) |
|
return true; |
|
|
|
const model_t *pModel = modelinfo->GetModel( GetModelIndex() ); |
|
|
|
if ( GetSolid() == SOLID_VPHYSICS && modelinfo->GetModelType( pModel ) == mod_brush ) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Invalidates the abs state of all children |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::InvalidatePhysicsRecursive( int nChangeFlags ) |
|
{ |
|
// Main entry point for dirty flag setting for the 90% case |
|
// 1) If the origin changes, then we have to update abstransform, Shadow projection, PVS, KD-tree, |
|
// client-leaf system. |
|
// 2) If the angles change, then we have to update abstransform, Shadow projection, |
|
// shadow render-to-texture, client-leaf system, and surrounding bounds. |
|
// Children have to additionally update absvelocity, KD-tree, and PVS. |
|
// If the surrounding bounds actually update, when we also need to update the KD-tree and the PVS. |
|
// 3) If it's due to attachment, then all children who are attached to an attachment point |
|
// are assumed to have dirty origin + angles. |
|
|
|
// Other stuff: |
|
// 1) Marking the surrounding bounds dirty will automatically mark KD tree + PVS dirty. |
|
|
|
int nDirtyFlags = 0; |
|
|
|
if ( nChangeFlags & VELOCITY_CHANGED ) |
|
{ |
|
nDirtyFlags |= EFL_DIRTY_ABSVELOCITY; |
|
} |
|
|
|
if ( nChangeFlags & POSITION_CHANGED ) |
|
{ |
|
nDirtyFlags |= EFL_DIRTY_ABSTRANSFORM; |
|
|
|
#ifndef CLIENT_DLL |
|
NetworkProp()->MarkPVSInformationDirty(); |
|
#endif |
|
|
|
// NOTE: This will also mark shadow projection + client leaf dirty |
|
CollisionProp()->MarkPartitionHandleDirty(); |
|
} |
|
|
|
// NOTE: This has to be done after velocity + position are changed |
|
// because we change the nChangeFlags for the child entities |
|
if ( nChangeFlags & ANGLES_CHANGED ) |
|
{ |
|
nDirtyFlags |= EFL_DIRTY_ABSTRANSFORM; |
|
if ( CollisionProp()->DoesRotationInvalidateSurroundingBox() ) |
|
{ |
|
// NOTE: This will handle the KD-tree, surrounding bounds, PVS |
|
// render-to-texture shadow, shadow projection, and client leaf dirty |
|
CollisionProp()->MarkSurroundingBoundsDirty(); |
|
} |
|
else |
|
{ |
|
#ifdef CLIENT_DLL |
|
MarkRenderHandleDirty(); |
|
g_pClientShadowMgr->AddToDirtyShadowList( this ); |
|
g_pClientShadowMgr->MarkRenderToTextureShadowDirty( GetShadowHandle() ); |
|
#endif |
|
} |
|
|
|
// This is going to be used for all children: children |
|
// have position + velocity changed |
|
nChangeFlags |= POSITION_CHANGED | VELOCITY_CHANGED; |
|
} |
|
|
|
AddEFlags( nDirtyFlags ); |
|
|
|
// Set flags for children |
|
bool bOnlyDueToAttachment = false; |
|
if ( nChangeFlags & ANIMATION_CHANGED ) |
|
{ |
|
#ifdef CLIENT_DLL |
|
g_pClientShadowMgr->MarkRenderToTextureShadowDirty( GetShadowHandle() ); |
|
#endif |
|
|
|
// Only set this flag if the only thing that changed us was the animation. |
|
// If position or something else changed us, then we must tell all children. |
|
if ( !( nChangeFlags & (POSITION_CHANGED | VELOCITY_CHANGED | ANGLES_CHANGED) ) ) |
|
{ |
|
bOnlyDueToAttachment = true; |
|
} |
|
|
|
nChangeFlags = POSITION_CHANGED | ANGLES_CHANGED | VELOCITY_CHANGED; |
|
} |
|
|
|
for (CBaseEntity *pChild = FirstMoveChild(); pChild; pChild = pChild->NextMovePeer()) |
|
{ |
|
// If this is due to the parent animating, only invalidate children that are parented to an attachment |
|
// Entities that are following also access attachments points on parents and must be invalidated. |
|
if ( bOnlyDueToAttachment ) |
|
{ |
|
#ifdef CLIENT_DLL |
|
if ( (pChild->GetParentAttachment() == 0) && !pChild->IsFollowingEntity() ) |
|
continue; |
|
#else |
|
if ( pChild->GetParentAttachment() == 0 ) |
|
continue; |
|
#endif |
|
} |
|
pChild->InvalidatePhysicsRecursive( nChangeFlags ); |
|
} |
|
|
|
// |
|
// This code should really be in here, or the bone cache should not be in world space. |
|
// Since the bone transforms are in world space, if we move or rotate the entity, its |
|
// bones should be marked invalid. |
|
// |
|
// As it is, we're near ship, and don't have time to setup a good A/B test of how much |
|
// overhead this fix would add. We've also only got one known case where the lack of |
|
// this fix is screwing us, and I just fixed it, so I'm leaving this commented out for now. |
|
// |
|
// Hopefully, we'll put the bone cache in entity space and remove the need for this fix. |
|
// |
|
//#ifdef CLIENT_DLL |
|
// if ( nChangeFlags & (POSITION_CHANGED | ANGLES_CHANGED | ANIMATION_CHANGED) ) |
|
// { |
|
// C_BaseAnimating *pAnim = GetBaseAnimating(); |
|
// if ( pAnim ) |
|
// pAnim->InvalidateBoneCache(); |
|
// } |
|
//#endif |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns the highest parent of an entity |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CBaseEntity::GetRootMoveParent() |
|
{ |
|
CBaseEntity *pEntity = this; |
|
CBaseEntity *pParent = this->GetMoveParent(); |
|
while ( pParent ) |
|
{ |
|
pEntity = pParent; |
|
pParent = pEntity->GetMoveParent(); |
|
} |
|
|
|
return pEntity; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: static method |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseEntity::IsPrecacheAllowed() |
|
{ |
|
return m_bAllowPrecache; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: static method |
|
// Input : allow - |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::SetAllowPrecache( bool allow ) |
|
{ |
|
m_bAllowPrecache = allow; |
|
} |
|
|
|
/* |
|
================ |
|
FireBullets |
|
|
|
Go to the trouble of combining multiple pellets into a single damage call. |
|
================ |
|
*/ |
|
|
|
#if defined( GAME_DLL ) |
|
class CBulletsTraceFilter : public CTraceFilterSimpleList |
|
{ |
|
public: |
|
CBulletsTraceFilter( int collisionGroup ) : CTraceFilterSimpleList( collisionGroup ) {} |
|
|
|
bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) |
|
{ |
|
if ( m_PassEntities.Count() ) |
|
{ |
|
CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); |
|
CBaseEntity *pPassEntity = EntityFromEntityHandle( m_PassEntities[0] ); |
|
if ( pEntity && pPassEntity && pEntity->GetOwnerEntity() == pPassEntity && |
|
pPassEntity->IsSolidFlagSet(FSOLID_NOT_SOLID) && pPassEntity->IsSolidFlagSet( FSOLID_CUSTOMBOXTEST ) && |
|
pPassEntity->IsSolidFlagSet( FSOLID_CUSTOMRAYTEST ) ) |
|
{ |
|
// It's a bone follower of the entity to ignore (toml 8/3/2007) |
|
return false; |
|
} |
|
} |
|
return CTraceFilterSimpleList::ShouldHitEntity( pHandleEntity, contentsMask ); |
|
} |
|
|
|
}; |
|
#else |
|
typedef CTraceFilterSimpleList CBulletsTraceFilter; |
|
#endif |
|
|
|
void CBaseEntity::FireBullets( const FireBulletsInfo_t &info ) |
|
{ |
|
static int tracerCount; |
|
trace_t tr; |
|
CAmmoDef* pAmmoDef = GetAmmoDef(); |
|
int nDamageType = pAmmoDef->DamageType(info.m_iAmmoType); |
|
int nAmmoFlags = pAmmoDef->Flags(info.m_iAmmoType); |
|
|
|
bool bDoServerEffects = true; |
|
|
|
#if defined( HL2MP ) && defined( GAME_DLL ) |
|
bDoServerEffects = false; |
|
#endif |
|
|
|
#if defined( GAME_DLL ) |
|
if( IsPlayer() ) |
|
{ |
|
CBasePlayer *pPlayer = dynamic_cast<CBasePlayer*>(this); |
|
|
|
int rumbleEffect = pPlayer->GetActiveWeapon()->GetRumbleEffect(); |
|
|
|
if( rumbleEffect != RUMBLE_INVALID ) |
|
{ |
|
if( rumbleEffect == RUMBLE_SHOTGUN_SINGLE ) |
|
{ |
|
if( info.m_iShots == 12 ) |
|
{ |
|
// Upgrade to double barrel rumble effect |
|
rumbleEffect = RUMBLE_SHOTGUN_DOUBLE; |
|
} |
|
} |
|
|
|
pPlayer->RumbleEffect( rumbleEffect, 0, RUMBLE_FLAG_RESTART ); |
|
} |
|
} |
|
#endif// GAME_DLL |
|
|
|
int iPlayerDamage = info.m_iPlayerDamage; |
|
if ( iPlayerDamage == 0 ) |
|
{ |
|
if ( nAmmoFlags & AMMO_INTERPRET_PLRDAMAGE_AS_DAMAGE_TO_PLAYER ) |
|
{ |
|
iPlayerDamage = pAmmoDef->PlrDamage( info.m_iAmmoType ); |
|
} |
|
} |
|
|
|
// the default attacker is ourselves |
|
CBaseEntity *pAttacker = info.m_pAttacker ? info.m_pAttacker : this; |
|
|
|
// Make sure we don't have a dangling damage target from a recursive call |
|
if ( g_MultiDamage.GetTarget() != NULL ) |
|
{ |
|
ApplyMultiDamage(); |
|
} |
|
|
|
ClearMultiDamage(); |
|
g_MultiDamage.SetDamageType( nDamageType | DMG_NEVERGIB ); |
|
|
|
Vector vecDir; |
|
Vector vecEnd; |
|
|
|
// Skip multiple entities when tracing |
|
CBulletsTraceFilter traceFilter( COLLISION_GROUP_NONE ); |
|
traceFilter.SetPassEntity( this ); // Standard pass entity for THIS so that it can be easily removed from the list after passing through a portal |
|
traceFilter.AddEntityToIgnore( info.m_pAdditionalIgnoreEnt ); |
|
|
|
#if defined( HL2_EPISODIC ) && defined( GAME_DLL ) |
|
// FIXME: We need to emulate this same behavior on the client as well -- jdw |
|
// Also ignore a vehicle we're a passenger in |
|
if ( MyCombatCharacterPointer() != NULL && MyCombatCharacterPointer()->IsInAVehicle() ) |
|
{ |
|
traceFilter.AddEntityToIgnore( MyCombatCharacterPointer()->GetVehicleEntity() ); |
|
} |
|
#endif // SERVER_DLL |
|
|
|
bool bUnderwaterBullets = ShouldDrawUnderwaterBulletBubbles(); |
|
bool bStartedInWater = false; |
|
if ( bUnderwaterBullets ) |
|
{ |
|
bStartedInWater = ( enginetrace->GetPointContents( info.m_vecSrc ) & (CONTENTS_WATER|CONTENTS_SLIME) ) != 0; |
|
} |
|
|
|
// Prediction is only usable on players |
|
int iSeed = 0; |
|
if ( IsPlayer() ) |
|
{ |
|
iSeed = CBaseEntity::GetPredictionRandomSeed( info.m_bUseServerRandomSeed ) & 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; |
|
} |
|
|
|
float CBaseEntity::HealthFraction() const |
|
{ |
|
if ( GetMaxHealth() == 0 ) |
|
return 1.0f; |
|
|
|
float flFraction = ( float )GetHealth() / ( float )GetMaxHealth(); |
|
flFraction = clamp( flFraction, 0.0f, 1.0f ); |
|
return flFraction; |
|
} |
|
|
|
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
|
|
|