|
|
//===== Copyright � 1996-2005, 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"
#include "particle_parse.h"
#include "keyvalues.h"
#include "icommandline.h"
#ifdef CLIENT_DLL
#include "clientleafsystem.h"
#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", FCVAR_RELEASE, "Set developer message level" ); // developer mode
#ifdef DETECT_TRACE_SPIKES
float g_TraceSpikeTolerance = 0.25; ConVar trace_spike_tolerance( "trace_spike_tolerance", "0.25" );
void DoReportExpensiveTrace( bool repeat, float time ) { if ( g_TraceSpikeTolerance > 0.0f ) { Msg( "%s%f!\n", ( repeat ) ? " R: " : "", time ); } g_TraceSpikeTolerance = trace_spike_tolerance.GetFloat(); } #endif
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 ) { float pitch = 0; Vector tmp = vec; if ( VectorNormalize( tmp ) > 0 ) { pitch = RAD2DEG( asin( -tmp.z ) ); } 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 ) { float pitch = 0; Vector tmp = vec; if ( VectorNormalize( tmp ) > 0 ) { float z = matrix[0][2] * tmp.x + matrix[1][2] * tmp.y + matrix[2][2] * tmp.z; pitch = RAD2DEG( asin( -z ) ); 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 );
//ensure cross-platform agreement
seedvalue = LittleDWord( seedvalue ); additionalSeed = LittleDWord( additionalSeed );
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) ) { #ifdef CLIENT_DLL
ClientRenderHandle_t hRender = pCollide->RenderHandle(); if ( hRender == INVALID_CLIENT_RENDER_HANDLE ) { // this is to handle invisible owned entities (e.g. bone followers) with transparent owners
// not being filtered properly. This is kind of a hack but seems to provide the behavior we
// want in all cases - if you are owned by something transparent and you yourself are not visible
// we filter based on your owner's transparency
CBaseEntity *pOwner = pCollide->GetOwnerEntity(); if ( pOwner ) { hRender = pOwner->RenderHandle(); } } if ( g_pClientLeafSystem->GetTranslucencyType( hRender ) == RENDERABLE_IS_TRANSLUCENT ) return false; #else
bool bIsTranslucent = modelinfo->IsTranslucent( pModel ); bool bIsTwoPass = modelinfo->IsTranslucentTwoPass( pModel ); if ( bIsTranslucent && !bIsTwoPass ) return false; #endif
}
// 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; }
bool IsWeaponClassname( const char *pszClassName ) { // Look at the name of the class to determine if this is a weapon.
return ( !Q_strnicmp( "weapon_", pszClassName, WEAPON_CLASSNAME_PREFIX_LENGTH ) ); }
//-----------------------------------------------------------------------------
// 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 doesn't hit NPCs or players
//-----------------------------------------------------------------------------
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; }
//-----------------------------------------------------------------------------
// Purpose: Trace filter that doesn't hit players
//-----------------------------------------------------------------------------
bool CTraceFilterNoPlayers::ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) { if ( CTraceFilterSimple::ShouldHitEntity( pHandleEntity, contentsMask ) ) { CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); if ( !pEntity ) return NULL;
return !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 ); }
void CTraceFilterSimpleList::AddEntitiesToIgnore( int nCount, IHandleEntity **ppEntities ) { int nIndex = m_PassEntities.AddMultipleToTail( nCount ); memcpy( &m_PassEntities[nIndex], ppEntities, nCount * sizeof( IHandleEntity* ) ); }
//-----------------------------------------------------------------------------
// Trace filter that hits only the pass entity
//-----------------------------------------------------------------------------
CTraceFilterOnlyHitThis::CTraceFilterOnlyHitThis( const IHandleEntity *hitentity ) { m_pHitEnt = hitentity; }
bool CTraceFilterOnlyHitThis::ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) { return m_pHitEnt == pHandleEntity; }
//-----------------------------------------------------------------------------
// 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;
CBaseEntity *pOwner = pEntity->GetOwnerEntity(); if ( pOwner ) { const char *pOwnerClass = pOwner->GetClassname(); for ( int i = 0; i < m_PassClassnames.Count(); i++ ) { if ( !V_strcmp( m_PassClassnames[i], pOwnerClass ) ) return false; } } const char *pEntityClass = pEntity->GetClassname(); for ( int i = 0; i < m_PassClassnames.Count(); ++i ) { if ( !V_strcmp( pEntityClass, 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(); //Vector vec = pCollision->OBBMaxs();
// 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; // Stomp the start, since it's not going to be used anyway
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;
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; if ( !pEntity ) return; 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" ); } #if defined( HL2_EPISODIC )
else if ( bloodColor == BLOOD_COLOR_BLOB ) { UTIL_DecalTrace( pTrace, "BlobBlood" ); } //don't draw a any decals if the blob is frozen
else if ( bloodColor == BLOOD_COLOR_BLOB_FROZEN ) { return; } #endif
else if (bloodColor == BLOOD_COLOR_BRIGHTGREEN) { UTIL_DecalTrace( pTrace, "GreenBlood" ); } 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; int mask = (pMainEnt) ? pMainEnt->PhysicsSolidMaskForEntity() : MASK_SOLID; UTIL_TraceHull( vCenter, vCenter, -vHalfDims, vHalfDims, mask, pMainEnt, COLLISION_GROUP_NONE, &trace );
bool bClear = ( trace.fraction == 1 && trace.allsolid != 1 && (trace.startsolid != 1) ); return bClear; }
bool UTIL_IsSpaceEmpty( CBaseEntity *pMainEnt, const Vector &vMin, const Vector &vMax, unsigned int mask, ITraceFilter *pFilter ) { Vector vHalfDims = ( vMax - vMin ) * 0.5f; Vector vCenter = vMin + vHalfDims;
trace_t trace; UTIL_TraceHull( vCenter, vCenter, -vHalfDims, vHalfDims, mask, pFilter, &trace );
bool bClear = ( trace.fraction == 1 && trace.allsolid != 1 && (trace.startsolid != 1) ); return bClear; }
float UTIL_WaterLevel( const Vector &position, float minz, float maxz ) { Vector midUp = position; midUp.z = minz;
if ( !(UTIL_PointContents(midUp, MASK_WATER) & MASK_WATER) ) return minz;
midUp.z = maxz; if ( UTIL_PointContents(midUp, MASK_WATER) & MASK_WATER ) return maxz;
float diff = maxz - minz; while (diff > 1.0) { midUp.z = minz + diff/2.0; if ( UTIL_PointContents(midUp, MASK_WATER) & MASK_WATER ) { minz = midUp.z; } else { maxz = midUp.z; } diff = maxz - minz; }
return midUp.z; }
//-----------------------------------------------------------------------------
// Like UTIL_WaterLevel, but *way* less expensive.
// I didn't replace UTIL_WaterLevel everywhere to avoid breaking anything.
//-----------------------------------------------------------------------------
class CWaterTraceFilter : public CTraceFilter { public: bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) { CBaseEntity *pCollide = EntityFromEntityHandle( pHandleEntity );
// Static prop case...
if ( !pCollide ) return false;
// Only impact water stuff...
if ( pCollide->GetSolidFlags() & FSOLID_VOLUME_CONTENTS ) return true;
return false; } };
float UTIL_FindWaterSurface( const Vector &position, float minz, float maxz ) { Vector vecStart, vecEnd; vecStart.Init( position.x, position.y, maxz ); vecEnd.Init( position.x, position.y, minz );
Ray_t ray; trace_t tr; CWaterTraceFilter waterTraceFilter; ray.Init( vecStart, vecEnd ); enginetrace->TraceRay( ray, MASK_WATER, &waterTraceFilter, &tr );
return tr.endpos.z; }
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_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 *) stackalloc( 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 ); }
void UTIL_EncodeICE( unsigned char * buffer, unsigned int size, const unsigned char *key ) { if ( !key ) return;
IceKey ice( 0 ); // level 0 = 64bit key
ice.set( key ); // set key
unsigned char *cipherText = buffer; unsigned char *plainText = buffer; uint bytesEncrypted = 0;
while (bytesEncrypted < size) { ice.encrypt( plainText, cipherText ); bytesEncrypted += 8; cipherText += 8; plainText += 8; } }
// 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; }
BEGIN_DATADESC_NO_BASE( IntervalTimer ) END_DATADESC()
BEGIN_NETWORK_TABLE_NOBASE( IntervalTimer, DT_IntervalTimer ) #ifdef CLIENT_DLL
RecvPropFloat(RECVINFO(m_timestamp)), #else
SendPropFloat (SENDINFO(m_timestamp), 0, SPROP_NOSCALE ), #endif
END_NETWORK_TABLE()
#ifdef CLIENT_DLL
BEGIN_PREDICTION_DATA_NO_BASE( IntervalTimer ) DEFINE_PRED_FIELD( m_timestamp, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), END_PREDICTION_DATA() #endif
#ifdef CLIENT_DLL
BEGIN_RECV_TABLE_NOBASE( CountdownTimer, DT_CountdownTimer ) RecvPropFloat(RECVINFO(m_duration)), RecvPropFloat(RECVINFO(m_timestamp)), END_RECV_TABLE() BEGIN_PREDICTION_DATA_NO_BASE( CountdownTimer ) DEFINE_PRED_FIELD( m_duration, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_timestamp, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), END_PREDICTION_DATA() #else
BEGIN_SEND_TABLE_NOBASE( CountdownTimer, DT_CountdownTimer ) SendPropFloat (SENDINFO(m_duration), 0, SPROP_NOSCALE ), SendPropFloat (SENDINFO(m_timestamp), 0, SPROP_NOSCALE ), END_SEND_TABLE() #endif
BEGIN_DATADESC( CTimeline ) DEFINE_ARRAY( m_flValues, FIELD_FLOAT, TIMELINE_ARRAY_SIZE ), DEFINE_ARRAY( m_nValueCounts, FIELD_FLOAT, TIMELINE_ARRAY_SIZE ), DEFINE_FIELD( m_nBucketCount, FIELD_INTEGER ), DEFINE_FIELD( m_flInterval, FIELD_FLOAT ), DEFINE_FIELD( m_flFinalValue, FIELD_FLOAT ), DEFINE_FIELD( m_nCompressionType, FIELD_INTEGER ), DEFINE_FIELD( m_bStopped, FIELD_BOOLEAN ), END_DATADESC()
BEGIN_NETWORK_TABLE_NOBASE( CTimeline, DT_Timeline ) #ifdef CLIENT_DLL
RecvPropArray3( RECVINFO_ARRAY( m_flValues ), RecvPropFloat( RECVINFO( m_flValues[0] ) ) ), RecvPropArray3( RECVINFO_ARRAY( m_nValueCounts ), RecvPropFloat( RECVINFO( m_nValueCounts[0] ) ) ), RecvPropInt( RECVINFO( m_nBucketCount ) ), RecvPropFloat( RECVINFO( m_flInterval ) ), RecvPropFloat( RECVINFO( m_flFinalValue ) ), RecvPropInt( RECVINFO( m_nCompressionType ) ), RecvPropBool( RECVINFO( m_bStopped ) ), #else
SendPropArray3( SENDINFO_ARRAY3( m_flValues ), SendPropFloat( SENDINFO_ARRAY( m_flValues ), 0, SPROP_NOSCALE ) ), SendPropArray3( SENDINFO_ARRAY3( m_nValueCounts ), SendPropFloat( SENDINFO_ARRAY( m_nValueCounts ), 0, SPROP_NOSCALE ) ), SendPropInt( SENDINFO( m_nBucketCount ), NumBitsForCount( TIMELINE_ARRAY_SIZE ), SPROP_UNSIGNED ), SendPropFloat( SENDINFO( m_flInterval ), 0, SPROP_NOSCALE ), SendPropFloat( SENDINFO( m_flFinalValue ), 0, SPROP_NOSCALE ), SendPropInt( SENDINFO( m_nCompressionType ), -1, SPROP_UNSIGNED ), SendPropBool( SENDINFO( m_bStopped ) ), #endif
END_NETWORK_TABLE()
void CTimeline::ClearValues( void ) { Invalidate();
memset( m_flValues.m_Value, 0, sizeof( m_flValues.m_Value ) ); memset( m_nValueCounts.m_Value, 0, sizeof( m_nValueCounts.m_Value ) ); m_nBucketCount = 0; m_flInterval = TIMELINE_INTERVAL_START; m_flFinalValue = 0.0f; m_bStopped = false; }
void CTimeline::RecordValue( float flValue ) { if ( !HasStarted() || m_bStopped ) return;
int iBucket = GetCurrentBucket();
Assert( iBucket >= 0 );
while ( iBucket >= TIMELINE_ARRAY_SIZE ) { Compress(); iBucket = GetCurrentBucket(); }
if ( iBucket >= m_nBucketCount ) { m_nBucketCount = iBucket + 1; }
m_flValues.GetForModify( iBucket ) += flValue; m_nValueCounts.GetForModify( iBucket )++;
if ( m_nCompressionType == TIMELINE_COMPRESSION_SUM || m_nCompressionType == TIMELINE_COMPRESSION_AVERAGE || m_nCompressionType == TIMELINE_COMPRESSION_AVERAGE_BLEND ) { // Fill in blank preceding entries
float flCurrentValue = m_flValues[ iBucket ] / m_nValueCounts[ iBucket ];
int iPrecedingBucket = iBucket - 1;
while ( iPrecedingBucket >= 0 && m_nValueCounts[ iPrecedingBucket ] <= 0 ) { iPrecedingBucket--; }
// Get the last logged value (or the current if this is the first)
float flPrecedingValue = flCurrentValue;
if ( iPrecedingBucket >= 0 ) { // Last logged value
if ( m_nCompressionType == TIMELINE_COMPRESSION_SUM ) { flPrecedingValue = m_flValues[ iPrecedingBucket ];
if ( m_nValueCounts[ iBucket ] == 1 ) { // Sum in the previous bucket if this is the first value in this bucket
m_flValues.GetForModify( iBucket ) += flPrecedingValue; } } else { flPrecedingValue = m_flFinalValue; } }
// Number of buckets for blending from old to new value
float flNumBuckets = ( iBucket - iPrecedingBucket ) + 1;
if ( flNumBuckets >= 3.0f ) { if ( m_nCompressionType == TIMELINE_COMPRESSION_AVERAGE_BLEND ) { // Blend empty values between preceding and current
float flInterpBucket = 1.0f;
for ( int i = iPrecedingBucket + 1; i < iBucket; ++i, flInterpBucket += 1.0f ) { float flInterp = flInterpBucket / flNumBuckets; m_flValues.Set( i, flPrecedingValue * ( 1.0f - flInterp ) + flCurrentValue * flInterp ); m_nValueCounts.Set( i, 1 ); } } else { // Set empty values to preceding value
for ( int i = iPrecedingBucket + 1; i < iBucket; ++i ) { m_flValues.Set( i, flPrecedingValue ); m_nValueCounts.Set( i, 1 ); } } } }
m_flFinalValue = flValue; }
float CTimeline::GetValue( int i ) const { Assert( i >= 0 && i < m_nBucketCount );
if ( i < 0 || i >= m_nBucketCount ) { return 0.0f; }
if ( m_nValueCounts[ i ] <= 0 ) { return 0.0f; } else if ( i == 0 && m_nCompressionType == TIMELINE_COMPRESSION_SUM && m_nBucketCount > 1 ) { // Aways start at 0 for sums!
return 0.0; } else { switch ( m_nCompressionType ) { case TIMELINE_COMPRESSION_AVERAGE: case TIMELINE_COMPRESSION_AVERAGE_BLEND: return m_flValues[ i ] / m_nValueCounts[ i ];
case TIMELINE_COMPRESSION_SUM: case TIMELINE_COMPRESSION_COUNT_PER_INTERVAL: default: return m_flValues[ i ]; } } }
float CTimeline::GetValueAtInterp( float fInterp ) const { if ( fInterp <= 0.0f ) { return GetValue( 0 ); }
if ( fInterp >= 1.0f ) { if ( m_nCompressionType == TIMELINE_COMPRESSION_SUM || m_nCompressionType == TIMELINE_COMPRESSION_COUNT_PER_INTERVAL ) { return GetValue( Count() - 1 ); } else { return m_flFinalValue; } }
float fBucket = fInterp * ( Count() - 1 ); int nBucket = fBucket; fBucket -= nBucket;
float fValue = GetValue( nBucket ); float fNextValue = GetValue( nBucket + 1 );
return fValue * ( 1.0f - fBucket ) + fNextValue * fBucket; }
void CTimeline::Compress( void ) { int i, j;
switch ( m_nCompressionType ) { case TIMELINE_COMPRESSION_SUM: for ( i = 0, j = 0; i < TIMELINE_ARRAY_SIZE; i += 2, ++j ) { m_flValues.GetForModify( j ) = MAX( m_flValues[ i ], m_flValues[ i + 1 ] ); m_nValueCounts.GetForModify( j ) = m_nValueCounts[ i ] + m_nValueCounts[ i + 1 ]; } break;
default: for ( i = 0, j = 0; i < TIMELINE_ARRAY_SIZE; i += 2, ++j ) { m_flValues.GetForModify( j ) = m_flValues[ i ] + m_flValues[ i + 1 ]; m_nValueCounts.GetForModify( j ) = m_nValueCounts[ i ] + m_nValueCounts[ i + 1 ]; } break; }
int nRemainingBytes = ( TIMELINE_ARRAY_SIZE - j ) * sizeof( m_flValues[0] );
memset( &( m_flValues.GetForModify( j ) ), 0, nRemainingBytes ); memset( &( m_nValueCounts.GetForModify( j ) ), 0, nRemainingBytes );
m_flInterval *= 2.0f;
m_nBucketCount = j; }
#ifdef CLIENT_DLL
CBasePlayer *UTIL_PlayerByIndex( int entindex ) { // Sanity check the index being passed in
if ( entindex < 1 || entindex > gpGlobals->maxClients ) return NULL;
return ToBasePlayer( ClientEntityList().GetEnt( entindex ) ); } #endif
unsigned short UTIL_GetAchievementEventMask( void ) { CRC32_t mapCRC; CRC32_Init( &mapCRC );
char lowercase[ 256 ]; #ifdef CLIENT_DLL
Q_FileBase( engine->GetLevelName(), lowercase, sizeof( lowercase ) ); #else
Q_strncpy( lowercase, STRING( gpGlobals->mapname ), sizeof( lowercase ) ); #endif
Q_strlower( lowercase );
CRC32_ProcessBuffer( &mapCRC, lowercase, Q_strlen( lowercase ) ); CRC32_Final( &mapCRC );
return ( mapCRC & 0xFFFF ); }
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; }
static char s_NumBitsInNibble[ 16 ] = { 0, // 0000 = 0
1, // 0001 = 1
1, // 0010 = 2
2, // 0011 = 3
1, // 0100 = 4
2, // 0101 = 5
2, // 0110 = 6
3, // 0111 = 7
1, // 1000 = 8
2, // 1001 = 9
2, // 1010 = 10
3, // 1011 = 11
2, // 1100 = 12
3, // 1101 = 13
3, // 1110 = 14
4, // 1111 = 15
};
int UTIL_CountNumBitsSet( unsigned int nVar ) { int nNumBits = 0;
while ( nVar > 0 ) { // Look up and add in bits in the bottom nibble
nNumBits += s_NumBitsInNibble[ nVar & 0x0f ];
// Shift one nibble to the right
nVar >>= 4; }
return nNumBits; }
int UTIL_CountNumBitsSet( uint64 nVar ) { int nNumBits = 0;
while ( nVar > 0 ) { // Look up and add in bits in the bottom nibble
nNumBits += s_NumBitsInNibble[ nVar & 0x0f ];
// Shift one nibble to the right
nVar >>= 4; }
return nNumBits; }
bool UTIL_FindClosestPassableSpace( const Vector &vOriginalCenter, const Vector &vExtents, const Vector &vIndecisivePush, unsigned int iIterations, Vector &vCenterOut, int nAxisRestrictionFlags, FindClosestPassableSpace_TraceAdapter_t *pTraceAdapter ) { Assert( vExtents != vec3_origin );
trace_t traces[2]; Ray_t entRay; entRay.m_Extents = vExtents; entRay.m_IsRay = false; entRay.m_IsSwept = true; entRay.m_StartOffset = vec3_origin;
Vector vOriginalExtents = vExtents; Vector vCenter = vOriginalCenter; Vector vGrowSize = vExtents * (1.0f / (float)(iIterations + 1)); Vector vCurrentExtents = vExtents - vGrowSize;
int iLargestExtent = 0; { float fLargestExtent = vOriginalExtents[0]; for( int i = 1; i != 3; ++i ) { if( vOriginalExtents[i] > fLargestExtent ) { iLargestExtent = i; fLargestExtent = vOriginalExtents[i]; } } }
Ray_t testRay; testRay.m_Extents = vGrowSize; testRay.m_IsRay = false; testRay.m_IsSwept = true; testRay.m_StartOffset = vec3_origin;
float fOriginalExtentDists[8]; //distance between extents
//generate distance lookup. We reference this by XOR'ing the indices of two extents to find the axis of difference
{ //Since the ratios of lengths never change, we're going to normalize these distances to a value so we can simply scale on each iteration
//We've picked the largest extent as the basis simply because it's nonzero
float fNormalizer = 1.0f / vOriginalExtents[iLargestExtent]; float fXDiff = vOriginalExtents.x * 2.0f * fNormalizer; float fXSqr = fXDiff * fXDiff;
float fYDiff = vOriginalExtents.y * 2.0f * fNormalizer; float fYSqr = fYDiff * fYDiff;
float fZDiff = vOriginalExtents.z * 2.0f * fNormalizer; float fZSqr = fZDiff * fZDiff;
fOriginalExtentDists[0] = 0.0f; //should never get hit
fOriginalExtentDists[1] = fXDiff; //line along x axis
fOriginalExtentDists[2] = fYDiff; //line along y axis
fOriginalExtentDists[3] = sqrt( fXSqr + fYSqr ); //diagonal perpendicular to z-axis
fOriginalExtentDists[4] = fZDiff; //line along z axis
fOriginalExtentDists[5] = sqrt( fXSqr + fZSqr ); //diagonal perpendicular to y-axis
fOriginalExtentDists[6] = sqrt( fYSqr + fZSqr ); //diagonal perpendicular to x-axis
fOriginalExtentDists[7] = sqrt( fXSqr + fYSqr + fZSqr ); //diagonal on all axes
}
Vector ptExtents[8]; //ordering is going to be like 3 bits, where 0 is a min on the related axis, and 1 is a max on the same axis, axis order x y z
float fExtentsValidation[8]; //some points are more valid than others, and this is our measure
vCenter.z += 0.001f; //to satisfy m_IsSwept on first pass
unsigned int iFailCount; for( iFailCount = 0; iFailCount != iIterations; ++iFailCount ) { //float fXDistribution[2] = { -vCurrentExtents.x, vCurrentExtents.x };
//float fYDistribution[3] = { -vCurrentExtents.y, 0.0f, vCurrentExtents.y };
//float fZDistribution[5] = { -vCurrentExtents.z, 0.0f, 0.0f, 0.0f, vCurrentExtents.z };
//hey look, they can overlap
float fExtentDistribution[6]; fExtentDistribution[ 0 ] = vCenter.z + ( ( ( nAxisRestrictionFlags & FL_AXIS_DIRECTION_NZ ) == 0 ) ? ( -vCurrentExtents.z ) : ( 0.0f ) ); // Z-
fExtentDistribution[ 1 ] = vCenter.x + ( ( ( nAxisRestrictionFlags & FL_AXIS_DIRECTION_NX ) == 0 ) ? ( -vCurrentExtents.x ) : ( 0.0f ) ); // X-
fExtentDistribution[ 2 ] = vCenter.x + ( ( ( nAxisRestrictionFlags & FL_AXIS_DIRECTION_X ) == 0 ) ? ( vCurrentExtents.x ) : ( 0.0f ) ); // X+
fExtentDistribution[ 3 ] = vCenter.y + ( ( ( nAxisRestrictionFlags & FL_AXIS_DIRECTION_NY ) == 0 ) ? ( -vCurrentExtents.y ) : ( 0.0f ) ); // Y-
fExtentDistribution[ 4 ] = vCenter.z + ( ( ( nAxisRestrictionFlags & FL_AXIS_DIRECTION_Z ) == 0 ) ? ( vCurrentExtents.z ) : ( 0.0f ) ); // Z+
fExtentDistribution[ 5 ] = vCenter.y + ( ( ( nAxisRestrictionFlags & FL_AXIS_DIRECTION_Y ) == 0 ) ? ( vCurrentExtents.y ) : ( 0.0f ) ); // Y+
float *pXDistribution = &fExtentDistribution[1]; float *pYDistribution = &fExtentDistribution[3];
bool bExtentInvalid[8]; float fExtentDists[8]; bool bAnyInvalid = false; for( int i = 0; i != 8; ++i ) { ptExtents[i].x = pXDistribution[i & (1<<0)]; //fExtentDistribution[(0 or 1) + 1]
ptExtents[i].y = pYDistribution[i & (1<<1)]; //fExtentDistribution[(0 or 2) + 3]
ptExtents[i].z = fExtentDistribution[i & (1<<2)]; //fExtentDistribution[(0 or 4)]
fExtentsValidation[i] = 0.0f; bExtentInvalid[i] = pTraceAdapter->pPointOutsideWorldFunc( ptExtents[i], pTraceAdapter ); bAnyInvalid |= bExtentInvalid[i]; fExtentDists[i] = fOriginalExtentDists[i] * vExtents[iLargestExtent]; }
//trace from all extents to all other extents and rate the validity
{ unsigned int counters[2]; //I know it's weird, get over it
for( counters[0] = 0; counters[0] != 7; ++counters[0] ) { for( counters[1] = counters[0] + 1; counters[1] != 8; ++counters[1] ) { for( int i = 0; i != 2; ++i ) { if( bExtentInvalid[counters[i]] ) { traces[i].startsolid = true; traces[i].fraction = 0.0f; } else { testRay.m_Start = ptExtents[counters[i]]; testRay.m_Delta = ptExtents[counters[1-i]] - ptExtents[counters[i]]; pTraceAdapter->pTraceFunc( testRay, &traces[i], pTraceAdapter ); } }
float fDistance = fExtentDists[counters[0] ^ counters[1]];
for( int i = 0; i != 2; ++i ) { if( (traces[i].fraction == 1.0f) && (traces[1-i].fraction != 1.0f) ) { //One sided collision >_<
traces[i].startsolid = true; traces[i].fraction = 0.0f; break; } }
for( int i = 0; i != 2; ++i ) { if( traces[i].startsolid ) { bExtentInvalid[counters[i]] = true; bAnyInvalid = true; } else { fExtentsValidation[counters[i]] += traces[i].fraction * fDistance; } } } } }
//optimally we should do this check before tracing extents. But one sided collision is a bitch
if( !bAnyInvalid ) { //try to trace back to the starting position (if we start in valid, the endpoint will be closer to the original center)
entRay.m_Start = vCenter; entRay.m_Delta = vOriginalCenter - vCenter;
pTraceAdapter->pTraceFunc( entRay, &traces[0], pTraceAdapter ); if( traces[0].startsolid == false ) { //damned one sided collision
vCenterOut = traces[0].endpos; return !pTraceAdapter->pPointOutsideWorldFunc( vCenterOut, pTraceAdapter ); } }
//find the direction to move based on the extent validity
{ Vector vNewOriginDirection( 0.0f, 0.0f, 0.0f ); float fTotalValidation = 0.0f; for( int i = 0; i != 8; ++i ) { if( !bExtentInvalid[i] ) { vNewOriginDirection += (ptExtents[i] - vCenter) * fExtentsValidation[i]; fTotalValidation += fExtentsValidation[i]; } }
if( fTotalValidation != 0.0f ) { vCenter += (vNewOriginDirection / fTotalValidation);
//increase sizing
testRay.m_Extents += vGrowSize; //increase the ray size
vCurrentExtents -= vGrowSize; //while reducing the overall test region size (so outermost ray extents are the same)
} else { //no point was valid, apply the indecisive vector
vCenter += vIndecisivePush;
//reset sizing
testRay.m_Extents = vGrowSize; vCurrentExtents = vOriginalExtents - vGrowSize; } } }
//Warning( "FindClosestPassableSpace() failure.\n" );
// X360TBD: Hits in portal devtest
//AssertMsg( IsGameConsole() || iFailCount != iIterations, "FindClosestPassableSpace() failure." );
vCenterOut = vOriginalCenter; return false; }
static void EngineTraceFunc( const Ray_t &ray, trace_t *pResult, FindClosestPassableSpace_TraceAdapter_t *pTraceAdapter ) { enginetrace->TraceRay( ray, pTraceAdapter->fMask, pTraceAdapter->pTraceFilter, pResult ); }
static bool EnginePointOutsideWorldFunc( const Vector &vTest, FindClosestPassableSpace_TraceAdapter_t *pTraceAdapter ) { return enginetrace->PointOutsideWorld( vTest ); }
bool UTIL_FindClosestPassableSpace( const Vector &vOriginalCenter, const Vector &vExtents, const Vector &vIndecisivePush, ITraceFilter *pTraceFilter, unsigned int fMask, unsigned int iIterations, Vector &vCenterOut, int nAxisRestrictionFlags ) { FindClosestPassableSpace_TraceAdapter_t adapter; adapter.pTraceFunc = EngineTraceFunc; adapter.pPointOutsideWorldFunc = EnginePointOutsideWorldFunc; adapter.pTraceFilter = pTraceFilter; adapter.fMask = fMask;
return UTIL_FindClosestPassableSpace( vOriginalCenter, vExtents, vIndecisivePush, iIterations, vCenterOut, nAxisRestrictionFlags, &adapter ); }
bool UTIL_FindClosestPassableSpace( CBaseEntity *pEntity, const Vector &vIndecisivePush, unsigned int fMask, unsigned int iIterations, Vector &vOriginOut, Vector *pStartingPosition, int nAxisRestrictionFlags ) //assumes the object is already in a mostly passable space
{ // Don't ever do this to entities with a move parent
if ( pEntity->GetMoveParent() ) { vOriginOut = pEntity->GetAbsOrigin(); return false; }
Vector vEntityMaxs; Vector vEntityMins; pEntity->CollisionProp()->WorldSpaceAABB( &vEntityMins, &vEntityMaxs );
Vector ptEntityCenter = ((vEntityMins + vEntityMaxs) / 2.0f); //vEntityMins -= ptEntityCenter;
vEntityMaxs -= ptEntityCenter; Vector vCenterToOrigin = pEntity->GetAbsOrigin() - ptEntityCenter; if( pStartingPosition != NULL ) { Vector vOriginOffset = (*pStartingPosition) - pEntity->GetAbsOrigin(); ptEntityCenter += vOriginOffset; }
CTraceFilterSimple traceFilter( pEntity, pEntity->GetCollisionGroup() );
Vector vResult; bool bSuccess = UTIL_FindClosestPassableSpace( ptEntityCenter, vEntityMaxs, vIndecisivePush, &traceFilter, fMask, iIterations, vResult ); vResult += vCenterToOrigin; vOriginOut = vResult;
return bSuccess; }
bool UTIL_FindClosestPassableSpace( CBaseEntity *pEntity, const Vector &vIndecisivePush, unsigned int fMask, Vector *pStartingPosition, int nAxisRestrictionFlags ) { Vector vNewPos; bool bWorked = UTIL_FindClosestPassableSpace( pEntity, vIndecisivePush, fMask, 100, vNewPos, pStartingPosition, nAxisRestrictionFlags ); if( bWorked ) { #ifdef CLIENT_DLL
pEntity->SetAbsOrigin( vNewPos ); #else
pEntity->Teleport( &vNewPos, NULL, NULL ); #endif
} return bWorked; }
//-----------------------------------------------------------------------------
// Purpose: Retrieves the MOD directory for the active game (ie. "hl2")
//-----------------------------------------------------------------------------
bool UTIL_GetModDir( char *lpszTextOut, unsigned int nSize ) { // Must pass in a buffer at least large enough to hold the desired string
const char *pGameDir = CommandLine()->ParmValue( "-game", "hl2" ); Assert( strlen(pGameDir) <= nSize ); if ( strlen(pGameDir) > nSize ) return false;
Q_strncpy( lpszTextOut, pGameDir, nSize ); if ( Q_strnchr( lpszTextOut, '/', nSize ) || Q_strnchr( lpszTextOut, '\\', nSize ) ) { // Strip the last directory off (which will be our game dir)
Q_StripLastDir( lpszTextOut, nSize );
// Find the difference in string lengths and take that difference from the original string as the mod dir
int dirlen = Q_strlen( lpszTextOut ); Q_strncpy( lpszTextOut, pGameDir + dirlen, Q_strlen( pGameDir ) - dirlen + 1 ); }
return true; }
//#define FRUSTUM_DEBUGGING //for dumping some clipping information in UTIL_CalcFrustumThroughConvexPolygon
int UTIL_CalcFrustumThroughConvexPolygon( const Vector *pPolyVertices, int iPolyVertCount, const Vector &vFrustumOrigin, const VPlane *pInputFrustumPlanes, int iInputFrustumPlanes, VPlane *pOutputFrustumPlanes, int iMaxOutputPlanes, int iPreserveCount ) { Assert( iPreserveCount <= iMaxOutputPlanes ); Assert( iPreserveCount <= iInputFrustumPlanes ); if( iPolyVertCount < 3 ) return 0;
#if defined( FRUSTUM_DEBUGGING )
//put case-specific debug logic here and it will propogate
const bool bDebugThisCall = (iInputFrustumPlanes > 0); const float fDisplayTime = 0.0f; #endif
int iMaxComplexity = iMaxOutputPlanes - iPreserveCount; Vector *pClippedVerts; int iClippedVertCount; if( iInputFrustumPlanes > 0 ) { //clip the polygon by the input frustum
int iAllocSize = iPolyVertCount + iInputFrustumPlanes;
Vector *pWorkVerts[2]; pWorkVerts[0] = (Vector *)stackalloc( sizeof( Vector ) * iAllocSize * 2 ); //possible to add 1 point per cut, iPolyVertCount starting points, iInputFrustumPlaneCount cuts
pWorkVerts[1] = pWorkVerts[0] + iAllocSize;
//clip by first plane and put output into pInVerts
iClippedVertCount = ClipPolyToPlane( (Vector *)pPolyVertices, iPolyVertCount, pWorkVerts[0], pInputFrustumPlanes[0].m_Normal, pInputFrustumPlanes[0].m_Dist, 0.01f );
//clip by other planes and flipflop in and out pointers
for( int i = 1; i != iInputFrustumPlanes; ++i ) { if( iClippedVertCount < 3 ) return 0; //nothing left in the frustum
iClippedVertCount = ClipPolyToPlane( pWorkVerts[(i & 1) ^ 1], iClippedVertCount, pWorkVerts[i & 1], pInputFrustumPlanes[i].m_Normal, pInputFrustumPlanes[i].m_Dist, 0.01f ); }
if( iClippedVertCount < 3 ) return false; //nothing left in the frustum
pClippedVerts = pWorkVerts[(iInputFrustumPlanes & 1) ^ 1]; } else { //no input frustum
if( iPolyVertCount > iMaxComplexity ) { //we'll need to reduce our output frustum, copy the input polygon
pClippedVerts = (Vector *)stackalloc( sizeof( Vector ) * iPolyVertCount ); memcpy( pClippedVerts, pPolyVertices, sizeof( Vector ) * iPolyVertCount ); } else { //we won't need to simplify the polygon to reduce output planes, just point at the input polygon
pClippedVerts = (Vector *)pPolyVertices; } iClippedVertCount = iPolyVertCount; }
#if defined( FRUSTUM_DEBUGGING ) //for visibility culling debugging
if( bDebugThisCall ) { NDebugOverlay::Line( pClippedVerts[iClippedVertCount - 1], pClippedVerts[0], 255, 0, 0, true, fDisplayTime ); for( int j = 0; j != iClippedVertCount - 1; ++j ) { NDebugOverlay::Line( pClippedVerts[j], pClippedVerts[j+1], 255, 0, 0, true, fDisplayTime ); } } #endif
Assert( iClippedVertCount <= (iPolyVertCount + iInputFrustumPlanes) );
if( iClippedVertCount > iMaxComplexity ) { #if defined( FRUSTUM_DEBUGGING )
if( bDebugThisCall ) { NDebugOverlay::Line( pClippedVerts[iClippedVertCount - 1], pClippedVerts[0], 0, 255, 0, false, fDisplayTime ); for( int j = 0; j != iClippedVertCount - 1; ++j ) { NDebugOverlay::Line( pClippedVerts[j], pClippedVerts[j+1], 0, 255, 0, true, fDisplayTime ); } } #endif
float *fLineLengthSqr = (float *)stackalloc( sizeof( float ) * iClippedVertCount );
for( int i = 0; i != (iClippedVertCount - 1); ++i ) { fLineLengthSqr[i] = (pClippedVerts[i + 1] - pClippedVerts[i]).LengthSqr(); } fLineLengthSqr[(iClippedVertCount - 1)] = (pClippedVerts[0] - pClippedVerts[(iClippedVertCount - 1)]).LengthSqr(); //wrap around
#if defined( FRUSTUM_DEBUGGING ) //for visibility culling debugging
Vector vDebugBoxExtent; vDebugBoxExtent.Init( 1.0f, 1.0f, 1.0f ); #endif
while( iClippedVertCount > iMaxComplexity ) //vert count == number of planes we need to bound the polygon
{ //we have too many verts to represent this accurately in the output frustum plane count
//so, we're going to eliminate the smallest sides one at a time and bridge the surrounding sides until we're down to iMaxComplexity
float fMinSide = fLineLengthSqr[0]; int iMinSideFirstPoint = 0; int iOldVertCount = iClippedVertCount; --iClippedVertCount; //we're going to decrement this sometime in this block, it makes math easier to do it now
for( int i = 1; i != iOldVertCount; ++i ) { if( fLineLengthSqr[i] < fMinSide ) { fMinSide = fLineLengthSqr[i]; iMinSideFirstPoint = i; } }
int i1, i2, i3, i4; i1 = (iMinSideFirstPoint + iClippedVertCount)%(iOldVertCount); //-1 with a wrap
i2 = iMinSideFirstPoint; i3 = (iMinSideFirstPoint + 1)%(iOldVertCount); i4 = (iMinSideFirstPoint + 2)%(iOldVertCount);
Vector *p1, *p2, *p3, *p4; p1 = &pClippedVerts[i1]; p2 = &pClippedVerts[i2]; p3 = &pClippedVerts[i3]; //this is the one we'll actually be dropping in the merge
p4 = &pClippedVerts[i4];
//now we know the two points that we have to merge to one, project and make a merged point from the surrounding lines
//if( fMinSide >= 0.1f ) //only worth doing the math if it's actually going to be accurate and make a difference
{ //http://mathworld.wolfram.com/Line-LineIntersection.html (20)
Vector vA = *p2 - *p1; Vector vB = *p4 - *p3; Vector vC = *p3 - *p1; Vector vCxB = vC.Cross( vB ); Vector vAxB = vA.Cross( vB ); float fS = vCxB.Dot(vAxB)/vAxB.LengthSqr();
*p2 = *p1 + (vA * fS);
fLineLengthSqr[i1] = (*p2 - *p1).LengthSqr(); } fLineLengthSqr[i2] = (*p4 - *p2).LengthSqr(); //must do this BEFORE possibly shifting points p4+ left
if( i3 < i4 ) //not the last point in the array
{ int iElementShift = (iOldVertCount - i4);
//eliminate p3, we merged p2+p3 and already stored the result in p2
memmove( p3, p4, sizeof( Vector ) * iElementShift ); memmove( &fLineLengthSqr[i3], &fLineLengthSqr[i4], sizeof( float ) * iElementShift ); } }
#if defined(FRUSTUM_DEBUGGING) //for visibility culling debugging
if( bDebugThisCall ) { NDebugOverlay::Line( pClippedVerts[iClippedVertCount - 1], pClippedVerts[0], 0, 0, 255, false, fDisplayTime ); for( int j = 0; j != iClippedVertCount - 1; ++j ) { NDebugOverlay::Line( pClippedVerts[j], pClippedVerts[j+1], 0, 0, 255, true, fDisplayTime ); } } #endif
}
//generate planes defined by each line around the convex and the frustum origin
{ int iFlipNormalsXOR = 0; //this algorithm was written assuming polygon vertices would be in a clockwise order from the perspective of vFrustumOrigin, some logic needs to flip if the inverse is true
{ Vector vLine1 = pPolyVertices[1] - pPolyVertices[0]; Vector vLine2 = pPolyVertices[2] - pPolyVertices[1]; Vector vFrontFace = vLine2.Cross( vLine1 );
iFlipNormalsXOR = (vFrontFace.Dot( vFrustumOrigin - pPolyVertices[0] ) < 0.0f) ? 1 : 0; //this will assist in reversing the normal by flipping the cross product
}
Vector vTemp[2]; vTemp[0] = pClippedVerts[iClippedVertCount - 1] - vFrustumOrigin; for( int i = 0; i != iClippedVertCount; ++i ) { int iIndexing = i & 1; //we can carry over the line computation from one iteration to the next, flip which order we look at the temps with
vTemp[iIndexing ^ 1] = pClippedVerts[i] - vFrustumOrigin;
Vector vNormal = vTemp[iIndexing ^ iFlipNormalsXOR].Cross( vTemp[(iIndexing ^ iFlipNormalsXOR) ^ 1] ); //vLine1.Cross( vLine2 );
vNormal.NormalizeInPlace();
pOutputFrustumPlanes[i].Init( vNormal, vNormal.Dot( vFrustumOrigin ) ); } }
//preserve input planes on request
if( iPreserveCount > 0 ) { memcpy( &pOutputFrustumPlanes[iClippedVertCount], &pInputFrustumPlanes[iInputFrustumPlanes - iPreserveCount], sizeof( VPlane ) * iPreserveCount ); }
return (iClippedVertCount + iPreserveCount); }
//-----------------------------------------------------------------------------
// class CFlaggedEntitiesEnum
//-----------------------------------------------------------------------------
CFlaggedEntitiesEnum::CFlaggedEntitiesEnum( CBaseEntity **pList, int listMax, int flagMask ) { m_pList = pList; m_listMax = listMax; m_flagMask = flagMask; m_count = 0; }
bool CFlaggedEntitiesEnum::AddToList( CBaseEntity *pEntity ) { if ( m_count >= m_listMax ) { AssertMsgOnce( 0, "reached enumerated list limit. Increase limit, decrease radius, or make it so entity flags will work for you" ); return false; } m_pList[m_count] = pEntity; m_count++; return true; }
IterationRetval_t CFlaggedEntitiesEnum::EnumElement( IHandleEntity *pHandleEntity ) { #if defined( CLIENT_DLL )
IClientEntity *pClientEntity = cl_entitylist->GetClientEntityFromHandle( pHandleEntity->GetRefEHandle() ); C_BaseEntity *pEntity = pClientEntity ? pClientEntity->GetBaseEntity() : NULL; #else
CBaseEntity *pEntity = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() ); #endif
if ( pEntity ) { if ( m_flagMask && !(pEntity->GetFlags() & m_flagMask) ) // Does it meet the criteria?
return ITERATION_CONTINUE;
if ( !AddToList( pEntity ) ) return ITERATION_STOP; }
return ITERATION_CONTINUE; }
//-----------------------------------------------------------------------------
// class CHurtableEntitiesEnum
//-----------------------------------------------------------------------------
CHurtableEntitiesEnum::CHurtableEntitiesEnum( CBaseEntity **pList, int listMax ) { m_pList = pList; m_listMax = listMax; m_count = 0; }
bool CHurtableEntitiesEnum::AddToList( CBaseEntity *pEntity ) { if ( m_count >= m_listMax ) { AssertMsgOnce( 0, "reached enumerated list limit. Increase limit, decrease radius, or make it so entity flags will work for you" ); return false; } m_pList[m_count] = pEntity; m_count++; return true; }
IterationRetval_t CHurtableEntitiesEnum::EnumElement( IHandleEntity *pHandleEntity ) { #if defined( CLIENT_DLL )
IClientEntity *pClientEntity = cl_entitylist->GetClientEntityFromHandle( pHandleEntity->GetRefEHandle() ); C_BaseEntity *pEntity = pClientEntity ? pClientEntity->GetBaseEntity() : NULL; #else
CBaseEntity *pEntity = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() ); #endif
if ( pEntity ) { if ( ( pEntity->m_takedamage == DAMAGE_NO || pEntity->GetHealth() <= 0 ) && pEntity->GetMoveType() != MOVETYPE_VPHYSICS ) // Does it meet the criteria?
return ITERATION_CONTINUE;
if ( !AddToList( pEntity ) ) return ITERATION_STOP; }
return ITERATION_CONTINUE; }
int UTIL_EntitiesAlongRay( const Ray_t &ray, CFlaggedEntitiesEnum *pEnum ) { #if defined( CLIENT_DLL )
::partition->EnumerateElementsAlongRay( PARTITION_CLIENT_NON_STATIC_EDICTS, ray, false, pEnum ); #else
::partition->EnumerateElementsAlongRay( PARTITION_ENGINE_NON_STATIC_EDICTS, ray, false, pEnum ); #endif
return pEnum->GetCount(); }
//-----------------------------------------------------------------------------
// Returns the length of the next command
//-----------------------------------------------------------------------------
void UTIL_GetNextCommandLength( const char *pText, int nMaxLen, int *pCommandLength, int *pNextCommandOffset ) { int nCommandLength = 0; int nNextCommandOffset; bool bIsQuoted = false; bool bIsCommented = false; for ( nNextCommandOffset=0; nNextCommandOffset < nMaxLen; ++nNextCommandOffset, nCommandLength += bIsCommented ? 0 : 1 ) { char c = pText[nNextCommandOffset]; if ( !bIsCommented ) { if ( c == '"' ) { bIsQuoted = !bIsQuoted; continue; }
// don't break if inside a C++ style comment
if ( !bIsQuoted && c == '/' ) { bIsCommented = ( nNextCommandOffset < nMaxLen-1 ) && pText[nNextCommandOffset+1] == '/'; if ( bIsCommented ) { ++nNextCommandOffset; continue; } }
// don't break if inside a quoted string
if ( !bIsQuoted && c == ';' ) break; }
// FIXME: This is legacy behavior; should we not break if a \n is inside a quoted string?
if ( c == '\n' ) break; }
*pCommandLength = nCommandLength; *pNextCommandOffset = nNextCommandOffset; }
/**
* remove double spaces and empty bold/italic HTML tags from a string */ void UTIL_TrimEmptyWhitespaceFromHTML( OUT_Z_BYTECAP( descWriterByteSize ) wchar_t* pszDescWriter, size_t descWriterByteSize, const wchar_t* pszDescReader ) { size_t writerArraySize = descWriterByteSize / sizeof( wchar_t ); wchar_t* pszDescWriterEnd = pszDescWriter + writerArraySize - 1; // leave 1 character for '\0' terminator
while ( *pszDescReader != 0 && pszDescWriter < pszDescWriterEnd ) { if ( !wcsncmp( pszDescReader, L" <b></b> ", 9 ) || !wcsncmp( pszDescReader, L" <i></i> ", 9 ) ) { pszDescReader += 8; } else if ( !wcsncmp( pszDescReader, L" ", 2 ) ) { pszDescReader += 1; } else { *pszDescWriter = *pszDescReader; pszDescWriter++; pszDescReader++; } }
// terminate buffer
*pszDescWriter = 0; }
void UTIL_TrimEmptyWhitespaceFromHTML( OUT_Z_BYTECAP( descWriterByteSize ) char* pszDescWriter, size_t descWriterByteSize, const char* pszDescReader ) { size_t writerArraySize = descWriterByteSize / sizeof( char ); char* pszDescWriterEnd = pszDescWriter + writerArraySize - 1; // leave 1 character for '\0' terminator
while ( *pszDescReader != 0 && pszDescWriter < pszDescWriterEnd ) { if ( !strncmp( pszDescReader, " <b></b> ", 9 ) || !strncmp( pszDescReader, " <i></i> ", 9 ) ) { pszDescReader += 8; } else if ( !strncmp( pszDescReader, " ", 2 ) ) { pszDescReader += 1; } else { *pszDescWriter = *pszDescReader; pszDescWriter++; pszDescReader++; } }
// terminate buffer
*pszDescWriter = 0; }
|