sqwarmed/sdk_src/game/shared/util_shared.cpp

2020 lines
63 KiB
C++

//===== 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 );
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
if ( g_pClientLeafSystem->GetTranslucencyType( pCollide->RenderHandle() ) == 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;
}
//-----------------------------------------------------------------------------
// 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;
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 );
}
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;
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() );
enginetrace->SweepCollideable( pCollision, vecAbsStart, vecAbsEnd, pCollision->GetCollisionAngles(), mask, &traceFilter, ptr );
}
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 );
enginetrace->SweepCollideable( pCollision, vecAbsStart, vecAbsEnd, pCollision->GetCollisionAngles(), mask, &traceFilter, ptr );
}
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 );
enginetrace->SweepCollideable( pCollision, vecAbsStart, vecAbsEnd, pCollision->GetCollisionAngles(), mask, pFilter, ptr );
}
// ----
// 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;
}
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 );
}
#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 *) 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;
}
}
#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;
}
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, ITraceFilter *pTraceFilter, unsigned int fMask, unsigned int iIterations, Vector &vCenterOut, int nAxisRestrictionFlags )
{
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] = enginetrace->PointOutsideWorld( ptExtents[i] );
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]];
enginetrace->TraceRay( testRay, fMask, pTraceFilter, &traces[i] );
}
}
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;
enginetrace->TraceRay( entRay, fMask, pTraceFilter, &traces[0] );
if( traces[0].startsolid == false )
{
//damned one sided collision
vCenterOut = traces[0].endpos;
return true; //current placement worked
}
}
//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( IsX360() || iFailCount != iIterations, "FindClosestPassableSpace() failure." );
vCenterOut = vOriginalCenter;
return false;
}
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, nAxisRestrictionFlags );
vOriginOut = vResult + vCenterToOrigin;
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();
}