Team Fortress 2 Source Code as on 22/4/2020
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.
 
 
 
 
 
 

1406 lines
40 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
#include "steam/steam_api.h"
// 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 vRandom;
vRandom.x = RandomFloat( minVal, maxVal );
vRandom.y = RandomFloat( minVal, maxVal );
vRandom.z = RandomFloat( minVal, maxVal );
return vRandom;
}
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 vRandom;
vRandom.x = RandomFloat( minVal, maxVal );
vRandom.y = RandomFloat( minVal, maxVal );
vRandom.z = RandomFloat( minVal, maxVal );
return QAngle( vRandom.x, vRandom.y, vRandom.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 ) );
}
#endif
CBasePlayer *UTIL_PlayerBySteamID( const CSteamID &steamID )
{
CSteamID steamIDPlayer;
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
if ( !pPlayer )
continue;
if ( !pPlayer->GetSteamID( &steamIDPlayer ) )
continue;
if ( steamIDPlayer == steamID )
return pPlayer;
}
return NULL;
}
// Helper for use with console commands and the like.
// Returns NULL if not found or if the provided arg would match multiple players.
// Currently accepts, in descending priority:
// - Formatted SteamID ([U:1:1234])
// - SteamID64 (76561197989728462)
// - Legacy SteamID (STEAM_0:1:1234)
// - UserID preceded by a pound (#4)
// - Partial name match (if unique)
// - UserID not preceded by a pound*
//
// *Does not count as ambiguous with higher priority items
CBasePlayer* UTIL_PlayerByCommandArg( const char *arg )
{
size_t nLength = V_strlen( arg );
if ( nLength < 1 )
{ return NULL; }
// Is the argument numeric?
bool bAllButFirstNumbers = true;
for ( size_t idx = 1; bAllButFirstNumbers && idx < nLength; idx++ )
{
bAllButFirstNumbers = V_isdigit( arg[idx] );
}
bool bAllNumbers = V_isdigit( arg[0] ) && bAllButFirstNumbers;
// Keep searching when we find a match to track ambiguous results
CBasePlayer *pFound = NULL;
// Assign pFound unless we already found a different player, in which case return NULL due to ambiguous
// WTB Lambdas
#define UTIL_PLAYERBYCMDARG_CHECKMATCH( pEvalMatch ) \
do \
{ \
CBasePlayer *_pMacroMatch = (pEvalMatch); \
if ( _pMacroMatch ) \
{ \
/* Ambiguity check */ \
if ( pFound && pFound != _pMacroMatch ) \
{ return NULL; } \
pFound = _pMacroMatch; \
} \
} while ( false );
// Formatted SteamID or SteamID64
if ( bAllNumbers || ( arg[0] == '[' && arg[nLength-1] == ']' ) )
{
CSteamID steamID;
bool bMatch = steamID.SetFromStringStrict( arg, GetUniverse() );
UTIL_PLAYERBYCMDARG_CHECKMATCH( bMatch ? UTIL_PlayerBySteamID( steamID ) : NULL );
}
// Legacy SteamID?
const char szPrefix[] = "STEAM_";
if ( nLength >= V_ARRAYSIZE( szPrefix ) && V_strncmp( szPrefix, arg, V_ARRAYSIZE( szPrefix ) - 1 ) == 0 )
{
CSteamID steamID;
bool bMatch = steamID.SetFromSteam2String( arg, GetUniverse() );
UTIL_PLAYERBYCMDARG_CHECKMATCH( bMatch ? UTIL_PlayerBySteamID( steamID ) : NULL );
}
// UserID preceded by a pound (#4)
if ( nLength > 1 && arg[0] == '#' && bAllButFirstNumbers )
{
UTIL_PLAYERBYCMDARG_CHECKMATCH( UTIL_PlayerByUserId( V_atoi( arg + 1 ) ) );
}
// Partial name match (if unique)
UTIL_PLAYERBYCMDARG_CHECKMATCH( UTIL_PlayerByPartialName( arg ) );
// UserID not preceded by a pound
// *Does not count as ambiguous with higher priority items
if ( bAllNumbers && !pFound )
{
UTIL_PLAYERBYCMDARG_CHECKMATCH( UTIL_PlayerByUserId( V_atoi( arg ) ) );
}
return pFound;
#undef UTIL_PLAYERBYCMDARG_CHECKMATCH
}
CBasePlayer* UTIL_PlayerByName( const char *name )
{
if ( !name || !name[0] )
return NULL;
for (int i = 1; i<=gpGlobals->maxClients; i++ )
{
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
if ( !pPlayer )
continue;
#ifndef CLIENT_DLL
if ( !pPlayer->IsConnected() )
continue;
#endif
if ( Q_stricmp( pPlayer->GetPlayerName(), name ) == 0 )
{
return pPlayer;
}
}
return NULL;
}
// Finds a player who has this non-ambiguous substring
CBasePlayer* UTIL_PlayerByPartialName( const char *name )
{
if ( !name || !name[0] )
return NULL;
CBasePlayer *pFound = NULL;
for (int i = 1; i<=gpGlobals->maxClients; i++ )
{
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
if ( !pPlayer )
continue;
#ifndef CLIENT_DLL
if ( !pPlayer->IsConnected() )
continue;
#endif
if ( Q_stristr( pPlayer->GetPlayerName(), name ) )
{
if ( pFound )
{
// Ambiguous
return NULL;
}
pFound = pPlayer;
}
}
return pFound;
}
CBasePlayer* UTIL_PlayerByUserId( int userID )
{
for (int i = 1; i<=gpGlobals->maxClients; i++ )
{
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
if ( !pPlayer )
continue;
#ifndef CLIENT_DLL
if ( !pPlayer->IsConnected() )
continue;
#endif
if ( pPlayer->GetUserID() == userID )
{
return pPlayer;
}
}
return NULL;
}
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
}
extern ISoundEmitterSystemBase *soundemitterbase;
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
const char *UTIL_GetRandomSoundFromEntry( const char* pszEntryName )
{
Assert( pszEntryName );
if ( pszEntryName )
{
int soundIndex = soundemitterbase->GetSoundIndex( pszEntryName );
CSoundParametersInternal *internal = ( soundIndex != -1 ) ? soundemitterbase->InternalGetParametersForSound( soundIndex ) : NULL;
// See if we need to pick a random one
if ( internal )
{
int wave = RandomInt( 0, internal->NumSoundNames() - 1 );
pszEntryName = soundemitterbase->GetWaveName( internal->GetSoundNames()[wave].symbol );
}
}
return pszEntryName;
}
/// Clamp and round float vals to int. The values are in the 0...255 range.
Color FloatRGBAToColor( float r, float g, float b, float a )
{
return Color(
(unsigned char)clamp(r + .5f, 0.0, 255.0f),
(unsigned char)clamp(g + .5f, 0.0, 255.0f),
(unsigned char)clamp(b + .5f, 0.0, 255.0f),
(unsigned char)clamp(a + .5f, 0.0, 255.0f)
);
}
float LerpFloat( float x0, float x1, float t )
{
return x0 + (x1 - x0) * t;
}
Color LerpColor( const Color &c0, const Color &c1, float t )
{
if ( t <= 0.0f ) return c0;
if ( t >= 1.0f ) return c1;
return FloatRGBAToColor(
LerpFloat( (float)c0.r(), (float)c1.r(), t ),
LerpFloat( (float)c0.g(), (float)c1.g(), t ),
LerpFloat( (float)c0.b(), (float)c1.b(), t ),
LerpFloat( (float)c0.a(), (float)c1.a(), t )
);
}
ISteamUtils* GetSteamUtils()
{
#ifdef GAME_DLL
// Use steamgameserver context if this isn't a client/listenserver.
if ( engine->IsDedicatedServer() )
{
return steamgameserverapicontext ? steamgameserverapicontext->SteamGameServerUtils() : NULL;
}
#endif
return steamapicontext ? steamapicontext->SteamUtils() : NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
EUniverse GetUniverse()
{
if ( !GetSteamUtils() )
return k_EUniverseInvalid;
static EUniverse steamUniverse = GetSteamUtils()->GetConnectedUniverse();
return steamUniverse;
}