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.
1177 lines
34 KiB
1177 lines
34 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "mathlib/mathlib.h" |
|
#include "util_shared.h" |
|
#include "model_types.h" |
|
#include "convar.h" |
|
#include "IEffects.h" |
|
#include "vphysics/object_hash.h" |
|
#include "mathlib/IceKey.H" |
|
#include "checksum_crc.h" |
|
#ifdef TF_CLIENT_DLL |
|
#include "cdll_util.h" |
|
#endif |
|
#include "particle_parse.h" |
|
#include "KeyValues.h" |
|
#include "time.h" |
|
|
|
#ifdef USES_ECON_ITEMS |
|
#include "econ_item_constants.h" |
|
#include "econ_holidays.h" |
|
#include "rtime.h" |
|
#endif // USES_ECON_ITEMS |
|
|
|
#ifdef CLIENT_DLL |
|
#include "c_te_effect_dispatch.h" |
|
#else |
|
#include "te_effect_dispatch.h" |
|
|
|
bool NPC_CheckBrushExclude( CBaseEntity *pEntity, CBaseEntity *pBrush ); |
|
#endif |
|
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
ConVar r_visualizetraces( "r_visualizetraces", "0", FCVAR_CHEAT ); |
|
ConVar developer("developer", "0", 0, "Set developer message level." ); // developer mode |
|
|
|
float UTIL_VecToYaw( const Vector &vec ) |
|
{ |
|
if (vec.y == 0 && vec.x == 0) |
|
return 0; |
|
|
|
float yaw = atan2( vec.y, vec.x ); |
|
|
|
yaw = RAD2DEG(yaw); |
|
|
|
if (yaw < 0) |
|
yaw += 360; |
|
|
|
return yaw; |
|
} |
|
|
|
|
|
float UTIL_VecToPitch( const Vector &vec ) |
|
{ |
|
if (vec.y == 0 && vec.x == 0) |
|
{ |
|
if (vec.z < 0) |
|
return 180.0; |
|
else |
|
return -180.0; |
|
} |
|
|
|
float dist = vec.Length2D(); |
|
float pitch = atan2( -vec.z, dist ); |
|
|
|
pitch = RAD2DEG(pitch); |
|
|
|
return pitch; |
|
} |
|
|
|
float UTIL_VecToYaw( const matrix3x4_t &matrix, const Vector &vec ) |
|
{ |
|
Vector tmp = vec; |
|
VectorNormalize( tmp ); |
|
|
|
float x = matrix[0][0] * tmp.x + matrix[1][0] * tmp.y + matrix[2][0] * tmp.z; |
|
float y = matrix[0][1] * tmp.x + matrix[1][1] * tmp.y + matrix[2][1] * tmp.z; |
|
|
|
if (x == 0.0f && y == 0.0f) |
|
return 0.0f; |
|
|
|
float yaw = atan2( -y, x ); |
|
|
|
yaw = RAD2DEG(yaw); |
|
|
|
if (yaw < 0) |
|
yaw += 360; |
|
|
|
return yaw; |
|
} |
|
|
|
|
|
float UTIL_VecToPitch( const matrix3x4_t &matrix, const Vector &vec ) |
|
{ |
|
Vector tmp = vec; |
|
VectorNormalize( tmp ); |
|
|
|
float x = matrix[0][0] * tmp.x + matrix[1][0] * tmp.y + matrix[2][0] * tmp.z; |
|
float z = matrix[0][2] * tmp.x + matrix[1][2] * tmp.y + matrix[2][2] * tmp.z; |
|
|
|
if (x == 0.0f && z == 0.0f) |
|
return 0.0f; |
|
|
|
float pitch = atan2( z, x ); |
|
|
|
pitch = RAD2DEG(pitch); |
|
|
|
if (pitch < 0) |
|
pitch += 360; |
|
|
|
return pitch; |
|
} |
|
|
|
Vector UTIL_YawToVector( float yaw ) |
|
{ |
|
Vector ret; |
|
|
|
ret.z = 0; |
|
float angle = DEG2RAD( yaw ); |
|
SinCos( angle, &ret.y, &ret.x ); |
|
|
|
return ret; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Helper function get get determinisitc random values for shared/prediction code |
|
// Input : seedvalue - |
|
// *module - |
|
// line - |
|
// Output : static int |
|
//----------------------------------------------------------------------------- |
|
static int SeedFileLineHash( int seedvalue, const char *sharedname, int additionalSeed ) |
|
{ |
|
CRC32_t retval; |
|
|
|
CRC32_Init( &retval ); |
|
|
|
CRC32_ProcessBuffer( &retval, (void *)&seedvalue, sizeof( int ) ); |
|
CRC32_ProcessBuffer( &retval, (void *)&additionalSeed, sizeof( int ) ); |
|
CRC32_ProcessBuffer( &retval, (void *)sharedname, Q_strlen( sharedname ) ); |
|
|
|
CRC32_Final( &retval ); |
|
|
|
return (int)( retval ); |
|
} |
|
|
|
float SharedRandomFloat( const char *sharedname, float flMinVal, float flMaxVal, int additionalSeed /*=0*/ ) |
|
{ |
|
Assert( CBaseEntity::GetPredictionRandomSeed() != -1 ); |
|
|
|
int seed = SeedFileLineHash( CBaseEntity::GetPredictionRandomSeed(), sharedname, additionalSeed ); |
|
RandomSeed( seed ); |
|
return RandomFloat( flMinVal, flMaxVal ); |
|
} |
|
|
|
int SharedRandomInt( const char *sharedname, int iMinVal, int iMaxVal, int additionalSeed /*=0*/ ) |
|
{ |
|
Assert( CBaseEntity::GetPredictionRandomSeed() != -1 ); |
|
|
|
int seed = SeedFileLineHash( CBaseEntity::GetPredictionRandomSeed(), sharedname, additionalSeed ); |
|
RandomSeed( seed ); |
|
return RandomInt( iMinVal, iMaxVal ); |
|
} |
|
|
|
Vector SharedRandomVector( const char *sharedname, float minVal, float maxVal, int additionalSeed /*=0*/ ) |
|
{ |
|
Assert( CBaseEntity::GetPredictionRandomSeed() != -1 ); |
|
|
|
int seed = SeedFileLineHash( CBaseEntity::GetPredictionRandomSeed(), sharedname, additionalSeed ); |
|
RandomSeed( seed ); |
|
// HACK: Can't call RandomVector/Angle because it uses rand() not vstlib Random*() functions! |
|
// Get a random vector. |
|
Vector random; |
|
random.x = RandomFloat( minVal, maxVal ); |
|
random.y = RandomFloat( minVal, maxVal ); |
|
random.z = RandomFloat( minVal, maxVal ); |
|
return random; |
|
} |
|
|
|
QAngle SharedRandomAngle( const char *sharedname, float minVal, float maxVal, int additionalSeed /*=0*/ ) |
|
{ |
|
Assert( CBaseEntity::GetPredictionRandomSeed() != -1 ); |
|
|
|
int seed = SeedFileLineHash( CBaseEntity::GetPredictionRandomSeed(), sharedname, additionalSeed ); |
|
RandomSeed( seed ); |
|
|
|
// HACK: Can't call RandomVector/Angle because it uses rand() not vstlib Random*() functions! |
|
// Get a random vector. |
|
Vector random; |
|
random.x = RandomFloat( minVal, maxVal ); |
|
random.y = RandomFloat( minVal, maxVal ); |
|
random.z = RandomFloat( minVal, maxVal ); |
|
return QAngle( random.x, random.y, random.z ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
// Shared client/server trace filter code |
|
// |
|
//----------------------------------------------------------------------------- |
|
bool PassServerEntityFilter( const IHandleEntity *pTouch, const IHandleEntity *pPass ) |
|
{ |
|
if ( !pPass ) |
|
return true; |
|
|
|
if ( pTouch == pPass ) |
|
return false; |
|
|
|
const CBaseEntity *pEntTouch = EntityFromEntityHandle( pTouch ); |
|
const CBaseEntity *pEntPass = EntityFromEntityHandle( pPass ); |
|
if ( !pEntTouch || !pEntPass ) |
|
return true; |
|
|
|
// don't clip against own missiles |
|
if ( pEntTouch->GetOwnerEntity() == pEntPass ) |
|
return false; |
|
|
|
// don't clip against owner |
|
if ( pEntPass->GetOwnerEntity() == pEntTouch ) |
|
return false; |
|
|
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// A standard filter to be applied to just about everything. |
|
//----------------------------------------------------------------------------- |
|
bool StandardFilterRules( IHandleEntity *pHandleEntity, int fContentsMask ) |
|
{ |
|
CBaseEntity *pCollide = EntityFromEntityHandle( pHandleEntity ); |
|
|
|
// Static prop case... |
|
if ( !pCollide ) |
|
return true; |
|
|
|
SolidType_t solid = pCollide->GetSolid(); |
|
const model_t *pModel = pCollide->GetModel(); |
|
|
|
if ( ( modelinfo->GetModelType( pModel ) != mod_brush ) || (solid != SOLID_BSP && solid != SOLID_VPHYSICS) ) |
|
{ |
|
if ( (fContentsMask & CONTENTS_MONSTER) == 0 ) |
|
return false; |
|
} |
|
|
|
// This code is used to cull out tests against see-thru entities |
|
if ( !(fContentsMask & CONTENTS_WINDOW) && pCollide->IsTransparent() ) |
|
return false; |
|
|
|
// FIXME: this is to skip BSP models that are entities that can be |
|
// potentially moved/deleted, similar to a monster but doors don't seem to |
|
// be flagged as monsters |
|
// FIXME: the FL_WORLDBRUSH looked promising, but it needs to be set on |
|
// everything that's actually a worldbrush and it currently isn't |
|
if ( !(fContentsMask & CONTENTS_MOVEABLE) && (pCollide->GetMoveType() == MOVETYPE_PUSH))// !(touch->flags & FL_WORLDBRUSH) ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Simple trace filter |
|
//----------------------------------------------------------------------------- |
|
CTraceFilterSimple::CTraceFilterSimple( const IHandleEntity *passedict, int collisionGroup, |
|
ShouldHitFunc_t pExtraShouldHitFunc ) |
|
{ |
|
m_pPassEnt = passedict; |
|
m_collisionGroup = collisionGroup; |
|
m_pExtraShouldHitCheckFunction = pExtraShouldHitFunc; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// The trace filter! |
|
//----------------------------------------------------------------------------- |
|
bool CTraceFilterSimple::ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) |
|
{ |
|
if ( !StandardFilterRules( pHandleEntity, contentsMask ) ) |
|
return false; |
|
|
|
if ( m_pPassEnt ) |
|
{ |
|
if ( !PassServerEntityFilter( pHandleEntity, m_pPassEnt ) ) |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
// Don't test if the game code tells us we should ignore this collision... |
|
CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); |
|
if ( !pEntity ) |
|
return false; |
|
if ( !pEntity->ShouldCollide( m_collisionGroup, contentsMask ) ) |
|
return false; |
|
if ( pEntity && !g_pGameRules->ShouldCollide( m_collisionGroup, pEntity->GetCollisionGroup() ) ) |
|
return false; |
|
if ( m_pExtraShouldHitCheckFunction && |
|
(! ( m_pExtraShouldHitCheckFunction( pHandleEntity, contentsMask ) ) ) ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Trace filter that only hits NPCs and the player |
|
//----------------------------------------------------------------------------- |
|
bool CTraceFilterOnlyNPCsAndPlayer::ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) |
|
{ |
|
if ( CTraceFilterSimple::ShouldHitEntity( pHandleEntity, contentsMask ) ) |
|
{ |
|
CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); |
|
if ( !pEntity ) |
|
return false; |
|
|
|
#ifdef CSTRIKE_DLL |
|
#ifndef CLIENT_DLL |
|
if ( pEntity->Classify() == CLASS_PLAYER_ALLY ) |
|
return true; // CS hostages are CLASS_PLAYER_ALLY but not IsNPC() |
|
#endif // !CLIENT_DLL |
|
#endif // CSTRIKE_DLL |
|
return (pEntity->IsNPC() || pEntity->IsPlayer()); |
|
} |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Trace filter that only hits anything but NPCs and the player |
|
//----------------------------------------------------------------------------- |
|
bool CTraceFilterNoNPCsOrPlayer::ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) |
|
{ |
|
if ( CTraceFilterSimple::ShouldHitEntity( pHandleEntity, contentsMask ) ) |
|
{ |
|
CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); |
|
if ( !pEntity ) |
|
return NULL; |
|
#ifndef CLIENT_DLL |
|
if ( pEntity->Classify() == CLASS_PLAYER_ALLY ) |
|
return false; // CS hostages are CLASS_PLAYER_ALLY but not IsNPC() |
|
#endif |
|
return (!pEntity->IsNPC() && !pEntity->IsPlayer()); |
|
} |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Trace filter that skips two entities |
|
//----------------------------------------------------------------------------- |
|
CTraceFilterSkipTwoEntities::CTraceFilterSkipTwoEntities( const IHandleEntity *passentity, const IHandleEntity *passentity2, int collisionGroup ) : |
|
BaseClass( passentity, collisionGroup ), m_pPassEnt2(passentity2) |
|
{ |
|
} |
|
|
|
bool CTraceFilterSkipTwoEntities::ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) |
|
{ |
|
Assert( pHandleEntity ); |
|
if ( !PassServerEntityFilter( pHandleEntity, m_pPassEnt2 ) ) |
|
return false; |
|
|
|
return BaseClass::ShouldHitEntity( pHandleEntity, contentsMask ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Trace filter that can take a list of entities to ignore |
|
//----------------------------------------------------------------------------- |
|
CTraceFilterSimpleList::CTraceFilterSimpleList( int collisionGroup ) : |
|
CTraceFilterSimple( NULL, collisionGroup ) |
|
{ |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CTraceFilterSimpleList::ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) |
|
{ |
|
if ( m_PassEntities.Find(pHandleEntity) != m_PassEntities.InvalidIndex() ) |
|
return false; |
|
|
|
return CTraceFilterSimple::ShouldHitEntity( pHandleEntity, contentsMask ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Add an entity to my list of entities to ignore in the trace |
|
//----------------------------------------------------------------------------- |
|
void CTraceFilterSimpleList::AddEntityToIgnore( IHandleEntity *pEntity ) |
|
{ |
|
m_PassEntities.AddToTail( pEntity ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Custom trace filter used for NPC LOS traces |
|
//----------------------------------------------------------------------------- |
|
CTraceFilterLOS::CTraceFilterLOS( IHandleEntity *pHandleEntity, int collisionGroup, IHandleEntity *pHandleEntity2 ) : |
|
CTraceFilterSkipTwoEntities( pHandleEntity, pHandleEntity2, collisionGroup ) |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CTraceFilterLOS::ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) |
|
{ |
|
CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); |
|
|
|
if ( !pEntity->BlocksLOS() ) |
|
return false; |
|
|
|
return CTraceFilterSimple::ShouldHitEntity( pHandleEntity, contentsMask ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Trace filter that can take a classname to ignore |
|
//----------------------------------------------------------------------------- |
|
CTraceFilterSkipClassname::CTraceFilterSkipClassname( const IHandleEntity *passentity, const char *pchClassname, int collisionGroup ) : |
|
CTraceFilterSimple( passentity, collisionGroup ), m_pchClassname( pchClassname ) |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CTraceFilterSkipClassname::ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) |
|
{ |
|
CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); |
|
if ( !pEntity || FClassnameIs( pEntity, m_pchClassname ) ) |
|
return false; |
|
|
|
return CTraceFilterSimple::ShouldHitEntity( pHandleEntity, contentsMask ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Trace filter that skips two classnames |
|
//----------------------------------------------------------------------------- |
|
CTraceFilterSkipTwoClassnames::CTraceFilterSkipTwoClassnames( const IHandleEntity *passentity, const char *pchClassname, const char *pchClassname2, int collisionGroup ) : |
|
BaseClass( passentity, pchClassname, collisionGroup ), m_pchClassname2(pchClassname2) |
|
{ |
|
} |
|
|
|
bool CTraceFilterSkipTwoClassnames::ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) |
|
{ |
|
CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); |
|
if ( !pEntity || FClassnameIs( pEntity, m_pchClassname2 ) ) |
|
return false; |
|
|
|
return BaseClass::ShouldHitEntity( pHandleEntity, contentsMask ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Trace filter that can take a list of entities to ignore |
|
//----------------------------------------------------------------------------- |
|
CTraceFilterSimpleClassnameList::CTraceFilterSimpleClassnameList( const IHandleEntity *passentity, int collisionGroup ) : |
|
CTraceFilterSimple( passentity, collisionGroup ) |
|
{ |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CTraceFilterSimpleClassnameList::ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) |
|
{ |
|
CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); |
|
if ( !pEntity ) |
|
return false; |
|
|
|
for ( int i = 0; i < m_PassClassnames.Count(); ++i ) |
|
{ |
|
if ( FClassnameIs( pEntity, m_PassClassnames[ i ] ) ) |
|
return false; |
|
} |
|
|
|
return CTraceFilterSimple::ShouldHitEntity( pHandleEntity, contentsMask ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Add an entity to my list of entities to ignore in the trace |
|
//----------------------------------------------------------------------------- |
|
void CTraceFilterSimpleClassnameList::AddClassnameToIgnore( const char *pchClassname ) |
|
{ |
|
m_PassClassnames.AddToTail( pchClassname ); |
|
} |
|
|
|
CTraceFilterChain::CTraceFilterChain( ITraceFilter *pTraceFilter1, ITraceFilter *pTraceFilter2 ) |
|
{ |
|
m_pTraceFilter1 = pTraceFilter1; |
|
m_pTraceFilter2 = pTraceFilter2; |
|
} |
|
|
|
bool CTraceFilterChain::ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) |
|
{ |
|
bool bResult1 = true; |
|
bool bResult2 = true; |
|
|
|
if ( m_pTraceFilter1 ) |
|
bResult1 = m_pTraceFilter1->ShouldHitEntity( pHandleEntity, contentsMask ); |
|
|
|
if ( m_pTraceFilter2 ) |
|
bResult2 = m_pTraceFilter2->ShouldHitEntity( pHandleEntity, contentsMask ); |
|
|
|
return ( bResult1 && bResult2 ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Sweeps against a particular model, using collision rules |
|
//----------------------------------------------------------------------------- |
|
void UTIL_TraceModel( const Vector &vecStart, const Vector &vecEnd, const Vector &hullMin, |
|
const Vector &hullMax, CBaseEntity *pentModel, int collisionGroup, trace_t *ptr ) |
|
{ |
|
// Cull it.... |
|
if ( pentModel && pentModel->ShouldCollide( collisionGroup, MASK_ALL ) ) |
|
{ |
|
Ray_t ray; |
|
ray.Init( vecStart, vecEnd, hullMin, hullMax ); |
|
enginetrace->ClipRayToEntity( ray, MASK_ALL, pentModel, ptr ); |
|
} |
|
else |
|
{ |
|
memset( ptr, 0, sizeof(trace_t) ); |
|
ptr->fraction = 1.0f; |
|
} |
|
} |
|
|
|
bool UTIL_EntityHasMatchingRootParent( CBaseEntity *pRootParent, CBaseEntity *pEntity ) |
|
{ |
|
if ( pRootParent ) |
|
{ |
|
// NOTE: Don't let siblings/parents collide. |
|
if ( pRootParent == pEntity->GetRootMoveParent() ) |
|
return true; |
|
if ( pEntity->GetOwnerEntity() && pRootParent == pEntity->GetOwnerEntity()->GetRootMoveParent() ) |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Sweep an entity from the starting to the ending position |
|
//----------------------------------------------------------------------------- |
|
class CTraceFilterEntity : public CTraceFilterSimple |
|
{ |
|
DECLARE_CLASS( CTraceFilterEntity, CTraceFilterSimple ); |
|
|
|
public: |
|
CTraceFilterEntity( CBaseEntity *pEntity, int nCollisionGroup ) |
|
: CTraceFilterSimple( pEntity, nCollisionGroup ) |
|
{ |
|
m_pRootParent = pEntity->GetRootMoveParent(); |
|
m_pEntity = pEntity; |
|
m_checkHash = g_EntityCollisionHash->IsObjectInHash(pEntity); |
|
} |
|
|
|
bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) |
|
{ |
|
CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); |
|
if ( !pEntity ) |
|
return false; |
|
|
|
// Check parents against each other |
|
// NOTE: Don't let siblings/parents collide. |
|
if ( UTIL_EntityHasMatchingRootParent( m_pRootParent, pEntity ) ) |
|
return false; |
|
|
|
if ( m_checkHash ) |
|
{ |
|
if ( g_EntityCollisionHash->IsObjectPairInHash( m_pEntity, pEntity ) ) |
|
return false; |
|
} |
|
|
|
#ifndef CLIENT_DLL |
|
if ( m_pEntity->IsNPC() ) |
|
{ |
|
if ( NPC_CheckBrushExclude( m_pEntity, pEntity ) ) |
|
return false; |
|
|
|
} |
|
#endif |
|
|
|
return BaseClass::ShouldHitEntity( pHandleEntity, contentsMask ); |
|
} |
|
|
|
private: |
|
|
|
CBaseEntity *m_pRootParent; |
|
CBaseEntity *m_pEntity; |
|
bool m_checkHash; |
|
}; |
|
|
|
class CTraceFilterEntityIgnoreOther : public CTraceFilterEntity |
|
{ |
|
DECLARE_CLASS( CTraceFilterEntityIgnoreOther, CTraceFilterEntity ); |
|
public: |
|
CTraceFilterEntityIgnoreOther( CBaseEntity *pEntity, const IHandleEntity *pIgnore, int nCollisionGroup ) : |
|
CTraceFilterEntity( pEntity, nCollisionGroup ), m_pIgnoreOther( pIgnore ) |
|
{ |
|
} |
|
|
|
bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) |
|
{ |
|
if ( pHandleEntity == m_pIgnoreOther ) |
|
return false; |
|
|
|
return BaseClass::ShouldHitEntity( pHandleEntity, contentsMask ); |
|
} |
|
|
|
private: |
|
const IHandleEntity *m_pIgnoreOther; |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Sweeps a particular entity through the world |
|
//----------------------------------------------------------------------------- |
|
void UTIL_TraceEntity( CBaseEntity *pEntity, const Vector &vecAbsStart, const Vector &vecAbsEnd, unsigned int mask, trace_t *ptr ) |
|
{ |
|
ICollideable *pCollision = pEntity->GetCollideable(); |
|
|
|
// Adding this assertion here so game code catches it, but really the assertion belongs in the engine |
|
// because one day, rotated collideables will work! |
|
Assert( pCollision->GetCollisionAngles() == vec3_angle ); |
|
|
|
CTraceFilterEntity traceFilter( pEntity, pCollision->GetCollisionGroup() ); |
|
|
|
#ifdef PORTAL |
|
UTIL_Portal_TraceEntity( pEntity, vecAbsStart, vecAbsEnd, mask, &traceFilter, ptr ); |
|
#else |
|
enginetrace->SweepCollideable( pCollision, vecAbsStart, vecAbsEnd, pCollision->GetCollisionAngles(), mask, &traceFilter, ptr ); |
|
#endif |
|
} |
|
|
|
void UTIL_TraceEntity( CBaseEntity *pEntity, const Vector &vecAbsStart, const Vector &vecAbsEnd, |
|
unsigned int mask, const IHandleEntity *pIgnore, int nCollisionGroup, trace_t *ptr ) |
|
{ |
|
ICollideable *pCollision; |
|
pCollision = pEntity->GetCollideable(); |
|
|
|
// Adding this assertion here so game code catches it, but really the assertion belongs in the engine |
|
// because one day, rotated collideables will work! |
|
Assert( pCollision->GetCollisionAngles() == vec3_angle ); |
|
|
|
CTraceFilterEntityIgnoreOther traceFilter( pEntity, pIgnore, nCollisionGroup ); |
|
|
|
#ifdef PORTAL |
|
UTIL_Portal_TraceEntity( pEntity, vecAbsStart, vecAbsEnd, mask, &traceFilter, ptr ); |
|
#else |
|
enginetrace->SweepCollideable( pCollision, vecAbsStart, vecAbsEnd, pCollision->GetCollisionAngles(), mask, &traceFilter, ptr ); |
|
#endif |
|
} |
|
|
|
void UTIL_TraceEntity( CBaseEntity *pEntity, const Vector &vecAbsStart, const Vector &vecAbsEnd, |
|
unsigned int mask, ITraceFilter *pFilter, trace_t *ptr ) |
|
{ |
|
ICollideable *pCollision; |
|
pCollision = pEntity->GetCollideable(); |
|
|
|
// Adding this assertion here so game code catches it, but really the assertion belongs in the engine |
|
// because one day, rotated collideables will work! |
|
Assert( pCollision->GetCollisionAngles() == vec3_angle ); |
|
|
|
#ifdef PORTAL |
|
UTIL_Portal_TraceEntity( pEntity, vecAbsStart, vecAbsEnd, mask, pFilter, ptr ); |
|
#else |
|
enginetrace->SweepCollideable( pCollision, vecAbsStart, vecAbsEnd, pCollision->GetCollisionAngles(), mask, pFilter, ptr ); |
|
#endif |
|
} |
|
|
|
// ---- |
|
// This is basically a regular TraceLine that uses the FilterEntity filter. |
|
void UTIL_TraceLineFilterEntity( CBaseEntity *pEntity, const Vector &vecAbsStart, const Vector &vecAbsEnd, |
|
unsigned int mask, int nCollisionGroup, trace_t *ptr ) |
|
{ |
|
CTraceFilterEntity traceFilter( pEntity, nCollisionGroup ); |
|
UTIL_TraceLine( vecAbsStart, vecAbsEnd, mask, &traceFilter, ptr ); |
|
} |
|
|
|
void UTIL_ClipTraceToPlayers( const Vector& vecAbsStart, const Vector& vecAbsEnd, unsigned int mask, ITraceFilter *filter, trace_t *tr ) |
|
{ |
|
trace_t playerTrace; |
|
Ray_t ray; |
|
float smallestFraction = tr->fraction; |
|
const float maxRange = 60.0f; |
|
|
|
ray.Init( vecAbsStart, vecAbsEnd ); |
|
|
|
for ( int k = 1; k <= gpGlobals->maxClients; ++k ) |
|
{ |
|
CBasePlayer *player = UTIL_PlayerByIndex( k ); |
|
|
|
if ( !player || !player->IsAlive() ) |
|
continue; |
|
|
|
#ifdef CLIENT_DLL |
|
if ( player->IsDormant() ) |
|
continue; |
|
#endif // CLIENT_DLL |
|
|
|
if ( filter && filter->ShouldHitEntity( player, mask ) == false ) |
|
continue; |
|
|
|
float range = DistanceToRay( player->WorldSpaceCenter(), vecAbsStart, vecAbsEnd ); |
|
if ( range < 0.0f || range > maxRange ) |
|
continue; |
|
|
|
enginetrace->ClipRayToEntity( ray, mask|CONTENTS_HITBOX, player, &playerTrace ); |
|
if ( playerTrace.fraction < smallestFraction ) |
|
{ |
|
// we shortened the ray - save off the trace |
|
*tr = playerTrace; |
|
smallestFraction = playerTrace.fraction; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Make a tracer using a particle effect |
|
//----------------------------------------------------------------------------- |
|
void UTIL_ParticleTracer( const char *pszTracerEffectName, const Vector &vecStart, const Vector &vecEnd, |
|
int iEntIndex, int iAttachment, bool bWhiz ) |
|
{ |
|
int iParticleIndex = GetParticleSystemIndex( pszTracerEffectName ); |
|
UTIL_Tracer( vecStart, vecEnd, iEntIndex, iAttachment, 0, bWhiz, "ParticleTracer", iParticleIndex ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Make a tracer effect using the old, non-particle system, tracer effects. |
|
//----------------------------------------------------------------------------- |
|
void UTIL_Tracer( const Vector &vecStart, const Vector &vecEnd, int iEntIndex, |
|
int iAttachment, float flVelocity, bool bWhiz, const char *pCustomTracerName, int iParticleID ) |
|
{ |
|
CEffectData data; |
|
data.m_vStart = vecStart; |
|
data.m_vOrigin = vecEnd; |
|
#ifdef CLIENT_DLL |
|
data.m_hEntity = ClientEntityList().EntIndexToHandle( iEntIndex ); |
|
#else |
|
data.m_nEntIndex = iEntIndex; |
|
#endif |
|
data.m_flScale = flVelocity; |
|
data.m_nHitBox = iParticleID; |
|
|
|
// Flags |
|
if ( bWhiz ) |
|
{ |
|
data.m_fFlags |= TRACER_FLAG_WHIZ; |
|
} |
|
|
|
if ( iAttachment != TRACER_DONT_USE_ATTACHMENT ) |
|
{ |
|
data.m_fFlags |= TRACER_FLAG_USEATTACHMENT; |
|
data.m_nAttachmentIndex = iAttachment; |
|
} |
|
|
|
// Fire it off |
|
if ( pCustomTracerName ) |
|
{ |
|
DispatchEffect( pCustomTracerName, data ); |
|
} |
|
else |
|
{ |
|
DispatchEffect( "Tracer", data ); |
|
} |
|
} |
|
|
|
|
|
void UTIL_BloodDrips( const Vector &origin, const Vector &direction, int color, int amount ) |
|
{ |
|
if ( !UTIL_ShouldShowBlood( color ) ) |
|
return; |
|
|
|
if ( color == DONT_BLEED || amount == 0 ) |
|
return; |
|
|
|
if ( g_Language.GetInt() == LANGUAGE_GERMAN && color == BLOOD_COLOR_RED ) |
|
color = 0; |
|
|
|
if ( g_pGameRules->IsMultiplayer() ) |
|
{ |
|
// scale up blood effect in multiplayer for better visibility |
|
amount *= 5; |
|
} |
|
|
|
if ( amount > 255 ) |
|
amount = 255; |
|
|
|
if (color == BLOOD_COLOR_MECH) |
|
{ |
|
g_pEffects->Sparks(origin); |
|
if (random->RandomFloat(0, 2) >= 1) |
|
{ |
|
UTIL_Smoke(origin, random->RandomInt(10, 15), 10); |
|
} |
|
} |
|
else |
|
{ |
|
// Normal blood impact |
|
UTIL_BloodImpact( origin, direction, color, amount ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns low violence settings |
|
//----------------------------------------------------------------------------- |
|
static ConVar violence_hblood( "violence_hblood","1", 0, "Draw human blood" ); |
|
static ConVar violence_hgibs( "violence_hgibs","1", 0, "Show human gib entities" ); |
|
static ConVar violence_ablood( "violence_ablood","1", 0, "Draw alien blood" ); |
|
static ConVar violence_agibs( "violence_agibs","1", 0, "Show alien gib entities" ); |
|
|
|
bool UTIL_IsLowViolence( void ) |
|
{ |
|
// These convars are no longer necessary -- the engine is the final arbiter of |
|
// violence settings -- but they're here for legacy support and for testing low |
|
// violence when the engine is in normal violence mode. |
|
if ( !violence_hblood.GetBool() || !violence_ablood.GetBool() || !violence_hgibs.GetBool() || !violence_agibs.GetBool() ) |
|
return true; |
|
|
|
#ifdef TF_CLIENT_DLL |
|
// Use low violence if the local player has an item that allows them to see it (Pyro Goggles) |
|
if ( IsLocalPlayerUsingVisionFilterFlags( TF_VISION_FILTER_PYRO ) ) |
|
{ |
|
return true; |
|
} |
|
#endif |
|
|
|
return engine->IsLowViolence(); |
|
} |
|
|
|
bool UTIL_ShouldShowBlood( int color ) |
|
{ |
|
if ( color != DONT_BLEED ) |
|
{ |
|
if ( color == BLOOD_COLOR_RED ) |
|
{ |
|
return violence_hblood.GetBool(); |
|
} |
|
else |
|
{ |
|
return violence_ablood.GetBool(); |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : Use trace to pass a specific decal type to the entity being decaled |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
void UTIL_DecalTrace( trace_t *pTrace, char const *decalName ) |
|
{ |
|
if (pTrace->fraction == 1.0) |
|
return; |
|
|
|
CBaseEntity *pEntity = pTrace->m_pEnt; |
|
pEntity->DecalTrace( pTrace, decalName ); |
|
} |
|
|
|
|
|
void UTIL_BloodDecalTrace( trace_t *pTrace, int bloodColor ) |
|
{ |
|
if ( UTIL_ShouldShowBlood( bloodColor ) ) |
|
{ |
|
if ( bloodColor == BLOOD_COLOR_RED ) |
|
{ |
|
UTIL_DecalTrace( pTrace, "Blood" ); |
|
} |
|
else |
|
{ |
|
UTIL_DecalTrace( pTrace, "YellowBlood" ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &pos - |
|
// &dir - |
|
// color - |
|
// amount - |
|
//----------------------------------------------------------------------------- |
|
void UTIL_BloodImpact( const Vector &pos, const Vector &dir, int color, int amount ) |
|
{ |
|
CEffectData data; |
|
|
|
data.m_vOrigin = pos; |
|
data.m_vNormal = dir; |
|
data.m_flScale = (float)amount; |
|
data.m_nColor = (unsigned char)color; |
|
|
|
DispatchEffect( "bloodimpact", data ); |
|
} |
|
|
|
bool UTIL_IsSpaceEmpty( CBaseEntity *pMainEnt, const Vector &vMin, const Vector &vMax ) |
|
{ |
|
Vector vHalfDims = ( vMax - vMin ) * 0.5f; |
|
Vector vCenter = vMin + vHalfDims; |
|
|
|
trace_t trace; |
|
UTIL_TraceHull( vCenter, vCenter, -vHalfDims, vHalfDims, MASK_SOLID, pMainEnt, COLLISION_GROUP_NONE, &trace ); |
|
|
|
bool bClear = ( trace.fraction == 1 && trace.allsolid != 1 && (trace.startsolid != 1) ); |
|
return bClear; |
|
} |
|
|
|
void UTIL_StringToFloatArray( float *pVector, int count, const char *pString ) |
|
{ |
|
char *pstr, *pfront, tempString[128]; |
|
int j; |
|
|
|
Q_strncpy( tempString, pString, sizeof(tempString) ); |
|
pstr = pfront = tempString; |
|
|
|
for ( j = 0; j < count; j++ ) // lifted from pr_edict.c |
|
{ |
|
pVector[j] = atof( pfront ); |
|
|
|
// skip any leading whitespace |
|
while ( *pstr && *pstr <= ' ' ) |
|
pstr++; |
|
|
|
// skip to next whitespace |
|
while ( *pstr && *pstr > ' ' ) |
|
pstr++; |
|
|
|
if (!*pstr) |
|
break; |
|
|
|
pstr++; |
|
pfront = pstr; |
|
} |
|
for ( j++; j < count; j++ ) |
|
{ |
|
pVector[j] = 0; |
|
} |
|
} |
|
|
|
void UTIL_StringToVector( float *pVector, const char *pString ) |
|
{ |
|
UTIL_StringToFloatArray( pVector, 3, pString ); |
|
} |
|
|
|
void UTIL_StringToIntArray( int *pVector, int count, const char *pString ) |
|
{ |
|
char *pstr, *pfront, tempString[128]; |
|
int j; |
|
|
|
Q_strncpy( tempString, pString, sizeof(tempString) ); |
|
pstr = pfront = tempString; |
|
|
|
for ( j = 0; j < count; j++ ) // lifted from pr_edict.c |
|
{ |
|
pVector[j] = atoi( pfront ); |
|
|
|
while ( *pstr && *pstr != ' ' ) |
|
pstr++; |
|
if (!*pstr) |
|
break; |
|
pstr++; |
|
pfront = pstr; |
|
} |
|
|
|
for ( j++; j < count; j++ ) |
|
{ |
|
pVector[j] = 0; |
|
} |
|
} |
|
|
|
void UTIL_StringToColor32( color32 *color, const char *pString ) |
|
{ |
|
int tmp[4]; |
|
UTIL_StringToIntArray( tmp, 4, pString ); |
|
color->r = tmp[0]; |
|
color->g = tmp[1]; |
|
color->b = tmp[2]; |
|
color->a = tmp[3]; |
|
} |
|
|
|
#ifndef _XBOX |
|
void UTIL_DecodeICE( unsigned char * buffer, int size, const unsigned char *key) |
|
{ |
|
if ( !key ) |
|
return; |
|
|
|
IceKey ice( 0 ); // level 0 = 64bit key |
|
ice.set( key ); // set key |
|
|
|
int blockSize = ice.blockSize(); |
|
|
|
unsigned char *temp = (unsigned char *)_alloca( PAD_NUMBER( size, blockSize ) ); |
|
unsigned char *p1 = buffer; |
|
unsigned char *p2 = temp; |
|
|
|
// encrypt data in 8 byte blocks |
|
int bytesLeft = size; |
|
while ( bytesLeft >= blockSize ) |
|
{ |
|
ice.decrypt( p1, p2 ); |
|
bytesLeft -= blockSize; |
|
p1+=blockSize; |
|
p2+=blockSize; |
|
} |
|
|
|
// copy encrypted data back to original buffer |
|
Q_memcpy( buffer, temp, size-bytesLeft ); |
|
} |
|
#endif |
|
|
|
// work-around since client header doesn't like inlined gpGlobals->curtime |
|
float IntervalTimer::Now( void ) const |
|
{ |
|
return gpGlobals->curtime; |
|
} |
|
|
|
// work-around since client header doesn't like inlined gpGlobals->curtime |
|
float CountdownTimer::Now( void ) const |
|
{ |
|
return gpGlobals->curtime; |
|
} |
|
|
|
|
|
#ifdef CLIENT_DLL |
|
CBasePlayer *UTIL_PlayerByIndex( int entindex ) |
|
{ |
|
return ToBasePlayer( ClientEntityList().GetEnt( entindex ) ); |
|
} |
|
|
|
//============================================================================= |
|
// HPE_BEGIN: |
|
// [menglish] Added UTIL function for events in client win_panel which transmit the player as a user ID |
|
//============================================================================= |
|
|
|
CBasePlayer* UTIL_PlayerByUserId( int userID ) |
|
{ |
|
for (int i = 1; i<=gpGlobals->maxClients; i++ ) |
|
{ |
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); |
|
|
|
if ( !pPlayer ) |
|
continue; |
|
|
|
if ( pPlayer->GetUserID() == userID ) |
|
{ |
|
return pPlayer; |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
//============================================================================= |
|
// HPE_END |
|
//============================================================================= |
|
|
|
#endif |
|
|
|
|
|
char* ReadAndAllocStringValue( KeyValues *pSub, const char *pName, const char *pFilename ) |
|
{ |
|
const char *pValue = pSub->GetString( pName, NULL ); |
|
if ( !pValue ) |
|
{ |
|
if ( pFilename ) |
|
{ |
|
DevWarning( "Can't get key value '%s' from file '%s'.\n", pName, pFilename ); |
|
} |
|
return ""; |
|
} |
|
|
|
int len = Q_strlen( pValue ) + 1; |
|
char *pAlloced = new char[ len ]; |
|
Assert( pAlloced ); |
|
Q_strncpy( pAlloced, pValue, len ); |
|
return pAlloced; |
|
} |
|
|
|
int UTIL_StringFieldToInt( const char *szValue, const char **pValueStrings, int iNumStrings ) |
|
{ |
|
if ( !szValue || !szValue[0] ) |
|
return -1; |
|
|
|
for ( int i = 0; i < iNumStrings; i++ ) |
|
{ |
|
if ( FStrEq(szValue, pValueStrings[i]) ) |
|
return i; |
|
} |
|
|
|
Assert(0); |
|
return -1; |
|
} |
|
|
|
|
|
int find_day_of_week( struct tm& found_day, int day_of_week, int step ) |
|
{ |
|
return 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
#ifdef USES_ECON_ITEMS |
|
static bool s_HolidaysCalculated = false; |
|
static CBitVec<kHolidayCount> s_HolidaysActive; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Used at level change and round start to re-calculate which holiday is active |
|
//----------------------------------------------------------------------------- |
|
void UTIL_CalculateHolidays() |
|
{ |
|
s_HolidaysActive.ClearAll(); |
|
|
|
CRTime::UpdateRealTime(); |
|
for ( int iHoliday = 0; iHoliday < kHolidayCount; iHoliday++ ) |
|
{ |
|
if ( EconHolidays_IsHolidayActive( iHoliday, CRTime::RTime32TimeCur() ) ) |
|
{ |
|
s_HolidaysActive.Set( iHoliday ); |
|
} |
|
} |
|
|
|
s_HolidaysCalculated = true; |
|
} |
|
#endif // USES_ECON_ITEMS |
|
|
|
bool UTIL_IsHolidayActive( /*EHoliday*/ int eHoliday ) |
|
{ |
|
#ifdef USES_ECON_ITEMS |
|
if ( IsX360() ) |
|
return false; |
|
|
|
if ( !s_HolidaysCalculated ) |
|
{ |
|
UTIL_CalculateHolidays(); |
|
} |
|
|
|
return s_HolidaysActive.IsBitSet( eHoliday ); |
|
#else |
|
return false; |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int UTIL_GetHolidayForString( const char* pszHolidayName ) |
|
{ |
|
#ifdef USES_ECON_ITEMS |
|
if ( !pszHolidayName ) |
|
return kHoliday_None; |
|
|
|
return EconHolidays_GetHolidayForString( pszHolidayName ); |
|
#else |
|
return 0; |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
const char* UTIL_GetActiveHolidayString() |
|
{ |
|
#ifdef USES_ECON_ITEMS |
|
return EconHolidays_GetActiveHolidayString(); |
|
#else |
|
return NULL; |
|
#endif |
|
}
|
|
|