You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3450 lines
97 KiB
3450 lines
97 KiB
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Utility code. |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "saverestore.h" |
|
#include "globalstate.h" |
|
#include <stdarg.h> |
|
#include "shake.h" |
|
#include "decals.h" |
|
#include "player.h" |
|
#include "gamerules.h" |
|
#include "entitylist.h" |
|
#include "bspfile.h" |
|
#include "mathlib/mathlib.h" |
|
#include "IEffects.h" |
|
#include "vstdlib/random.h" |
|
#include "soundflags.h" |
|
#include "ispatialpartition.h" |
|
#include "igamesystem.h" |
|
#include "saverestoretypes.h" |
|
#include "checksum_crc.h" |
|
#include "hierarchy.h" |
|
#include "iservervehicle.h" |
|
#include "te_effect_dispatch.h" |
|
#include "utldict.h" |
|
#include "collisionutils.h" |
|
#include "movevars_shared.h" |
|
#include "inetchannelinfo.h" |
|
#include "tier0/vprof.h" |
|
#include "ndebugoverlay.h" |
|
#include "engine/ivdebugoverlay.h" |
|
#include "datacache/imdlcache.h" |
|
#include "util.h" |
|
|
|
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
extern int g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud |
|
extern int g_sModelIndexBloodDrop; // (in combatweapon.cpp) holds the sprite index for the initial blood |
|
extern int g_sModelIndexBloodSpray; // (in combatweapon.cpp) holds the sprite index for splattered blood |
|
|
|
// this is true if the engine should be sent log output. Once per frame it is rechecked to see if logging has been enabled. |
|
bool g_bIsLogging = true; |
|
|
|
#ifdef DEBUG |
|
void DBG_AssertFunction( bool fExpr, const char *szExpr, const char *szFile, int szLine, const char *szMessage ) |
|
{ |
|
if (fExpr) |
|
return; |
|
char szOut[512]; |
|
if (szMessage != NULL) |
|
Q_snprintf(szOut,sizeof(szOut), "ASSERT FAILED:\n %s \n(%s@%d)\n%s", szExpr, szFile, szLine, szMessage); |
|
else |
|
Q_snprintf(szOut,sizeof(szOut), "ASSERT FAILED:\n %s \n(%s@%d)\n", szExpr, szFile, szLine); |
|
Warning( szOut); |
|
} |
|
#endif // DEBUG |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Entity creation factory |
|
//----------------------------------------------------------------------------- |
|
class CEntityFactoryDictionary : public IEntityFactoryDictionary |
|
{ |
|
public: |
|
CEntityFactoryDictionary(); |
|
|
|
virtual void InstallFactory( IEntityFactory *pFactory, const char *pClassName ); |
|
virtual IServerNetworkable *Create( const char *pClassName ); |
|
virtual void Destroy( const char *pClassName, IServerNetworkable *pNetworkable ); |
|
virtual const char *GetCannonicalName( const char *pClassName ); |
|
void ReportEntitySizes(); |
|
|
|
private: |
|
IEntityFactory *FindFactory( const char *pClassName ); |
|
public: |
|
CUtlDict< IEntityFactory *, unsigned short > m_Factories; |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Singleton accessor |
|
//----------------------------------------------------------------------------- |
|
IEntityFactoryDictionary *EntityFactoryDictionary() |
|
{ |
|
static CEntityFactoryDictionary s_EntityFactory; |
|
return &s_EntityFactory; |
|
} |
|
|
|
void DumpEntityFactories_f() |
|
{ |
|
CEntityFactoryDictionary *dict = ( CEntityFactoryDictionary * )EntityFactoryDictionary(); |
|
if ( dict ) |
|
{ |
|
for ( int i = dict->m_Factories.First(); i != dict->m_Factories.InvalidIndex(); i = dict->m_Factories.Next( i ) ) |
|
{ |
|
Warning( "%s\n", dict->m_Factories.GetElementName( i ) ); |
|
} |
|
} |
|
} |
|
|
|
static ConCommand dumpentityfactories( "dumpentityfactories", DumpEntityFactories_f, "Lists all entity factory names.", FCVAR_GAMEDLL ); |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
CON_COMMAND( dump_entity_sizes, "Print sizeof(entclass)" ) |
|
{ |
|
((CEntityFactoryDictionary*)EntityFactoryDictionary())->ReportEntitySizes(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Constructor |
|
//----------------------------------------------------------------------------- |
|
CEntityFactoryDictionary::CEntityFactoryDictionary() : m_Factories( true, 0, 128 ) |
|
{ |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Finds a new factory |
|
//----------------------------------------------------------------------------- |
|
IEntityFactory *CEntityFactoryDictionary::FindFactory( const char *pClassName ) |
|
{ |
|
unsigned short nIndex = m_Factories.Find( pClassName ); |
|
if ( nIndex == m_Factories.InvalidIndex() ) |
|
return NULL; |
|
return m_Factories[nIndex]; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Install a new factory |
|
//----------------------------------------------------------------------------- |
|
void CEntityFactoryDictionary::InstallFactory( IEntityFactory *pFactory, const char *pClassName ) |
|
{ |
|
AssertMsg1( FindFactory( pClassName ) == NULL, "Double installation of factory for %s", pClassName ); |
|
m_Factories.Insert( pClassName, pFactory ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Instantiate something using a factory |
|
//----------------------------------------------------------------------------- |
|
IServerNetworkable *CEntityFactoryDictionary::Create( const char *pClassName ) |
|
{ |
|
IEntityFactory *pFactory = FindFactory( pClassName ); |
|
if ( !pFactory ) |
|
{ |
|
Warning("Attempted to create unknown entity type %s!\n", pClassName ); |
|
return NULL; |
|
} |
|
#if defined(TRACK_ENTITY_MEMORY) && defined(USE_MEM_DEBUG) |
|
MEM_ALLOC_CREDIT_( m_Factories.GetElementName( m_Factories.Find( pClassName ) ) ); |
|
#endif |
|
return pFactory->Create( pClassName ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
const char *CEntityFactoryDictionary::GetCannonicalName( const char *pClassName ) |
|
{ |
|
return m_Factories.GetElementName( m_Factories.Find( pClassName ) ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Destroy a networkable |
|
//----------------------------------------------------------------------------- |
|
void CEntityFactoryDictionary::Destroy( const char *pClassName, IServerNetworkable *pNetworkable ) |
|
{ |
|
IEntityFactory *pFactory = FindFactory( pClassName ); |
|
if ( !pFactory ) |
|
{ |
|
Warning("Attempted to destroy unknown entity type %s!\n", pClassName ); |
|
return; |
|
} |
|
|
|
pFactory->Destroy( pNetworkable ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
void CEntityFactoryDictionary::ReportEntitySizes() |
|
{ |
|
for ( int i = m_Factories.First(); i != m_Factories.InvalidIndex(); i = m_Factories.Next( i ) ) |
|
{ |
|
Msg( " %s: %d", m_Factories.GetElementName( i ), m_Factories[i]->GetEntitySize() ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int UTIL_PrecacheDecal( const char *name, bool preload ) |
|
{ |
|
// If this is out of order, make sure to warn. |
|
if ( !CBaseEntity::IsPrecacheAllowed() ) |
|
{ |
|
if ( !engine->IsDecalPrecached( name ) ) |
|
{ |
|
Assert( !"UTIL_PrecacheDecal: too late" ); |
|
|
|
Warning( "Late precache of %s\n", name ); |
|
} |
|
} |
|
|
|
return engine->PrecacheDecal( name, preload ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
float UTIL_GetSimulationInterval() |
|
{ |
|
if ( CBaseEntity::IsSimulatingOnAlternateTicks() ) |
|
return ( TICK_INTERVAL * 2.0 ); |
|
return TICK_INTERVAL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int UTIL_EntitiesInBox( const Vector &mins, const Vector &maxs, CFlaggedEntitiesEnum *pEnum ) |
|
{ |
|
partition->EnumerateElementsInBox( PARTITION_ENGINE_NON_STATIC_EDICTS, mins, maxs, false, pEnum ); |
|
return pEnum->GetCount(); |
|
} |
|
|
|
int UTIL_EntitiesInSphere( const Vector ¢er, float radius, CFlaggedEntitiesEnum *pEnum ) |
|
{ |
|
partition->EnumerateElementsInSphere( PARTITION_ENGINE_NON_STATIC_EDICTS, center, radius, false, pEnum ); |
|
return pEnum->GetCount(); |
|
} |
|
|
|
CEntitySphereQuery::CEntitySphereQuery( const Vector ¢er, float radius, int flagMask ) |
|
{ |
|
m_listIndex = 0; |
|
m_listCount = UTIL_EntitiesInSphere( m_pList, ARRAYSIZE(m_pList), center, radius, flagMask ); |
|
} |
|
|
|
CBaseEntity *CEntitySphereQuery::GetCurrentEntity() |
|
{ |
|
if ( m_listIndex < m_listCount ) |
|
return m_pList[m_listIndex]; |
|
return NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Simple trace filter |
|
//----------------------------------------------------------------------------- |
|
class CTracePassFilter : public CTraceFilter |
|
{ |
|
public: |
|
CTracePassFilter( IHandleEntity *pPassEnt ) : m_pPassEnt( pPassEnt ) {} |
|
|
|
bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) |
|
{ |
|
if ( !StandardFilterRules( pHandleEntity, contentsMask ) ) |
|
return false; |
|
|
|
if (!PassServerEntityFilter( pHandleEntity, m_pPassEnt )) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
private: |
|
IHandleEntity *m_pPassEnt; |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Drops an entity onto the floor |
|
//----------------------------------------------------------------------------- |
|
int UTIL_DropToFloor( CBaseEntity *pEntity, unsigned int mask, CBaseEntity *pIgnore) |
|
{ |
|
// Assume no ground |
|
pEntity->SetGroundEntity( NULL ); |
|
|
|
Assert( pEntity ); |
|
|
|
trace_t trace; |
|
// HACK: is this really the only sure way to detect crossing a terrain boundry? |
|
UTIL_TraceEntity( pEntity, pEntity->GetAbsOrigin(), pEntity->GetAbsOrigin(), mask, pIgnore, pEntity->GetCollisionGroup(), &trace ); |
|
if (trace.fraction == 0.0) |
|
return -1; |
|
|
|
UTIL_TraceEntity( pEntity, pEntity->GetAbsOrigin(), pEntity->GetAbsOrigin() - Vector(0,0,256), mask, pIgnore, pEntity->GetCollisionGroup(), &trace ); |
|
|
|
if (trace.allsolid) |
|
return -1; |
|
|
|
if (trace.fraction == 1) |
|
return 0; |
|
|
|
pEntity->SetAbsOrigin( trace.endpos ); |
|
pEntity->SetGroundEntity( trace.m_pEnt ); |
|
|
|
return 1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns false if any part of the bottom of the entity is off an edge that |
|
// is not a staircase. |
|
//----------------------------------------------------------------------------- |
|
bool UTIL_CheckBottom( CBaseEntity *pEntity, ITraceFilter *pTraceFilter, float flStepSize ) |
|
{ |
|
Vector mins, maxs, start, stop; |
|
trace_t trace; |
|
int x, y; |
|
float mid, bottom; |
|
|
|
Assert( pEntity ); |
|
|
|
CTracePassFilter traceFilter(pEntity); |
|
if ( !pTraceFilter ) |
|
{ |
|
pTraceFilter = &traceFilter; |
|
} |
|
|
|
unsigned int mask = pEntity->PhysicsSolidMaskForEntity(); |
|
|
|
VectorAdd (pEntity->GetAbsOrigin(), pEntity->WorldAlignMins(), mins); |
|
VectorAdd (pEntity->GetAbsOrigin(), pEntity->WorldAlignMaxs(), maxs); |
|
|
|
// if all of the points under the corners are solid world, don't bother |
|
// with the tougher checks |
|
// the corners must be within 16 of the midpoint |
|
start[2] = mins[2] - 1; |
|
for (x=0 ; x<=1 ; x++) |
|
{ |
|
for (y=0 ; y<=1 ; y++) |
|
{ |
|
start[0] = x ? maxs[0] : mins[0]; |
|
start[1] = y ? maxs[1] : mins[1]; |
|
if (enginetrace->GetPointContents(start) != CONTENTS_SOLID) |
|
goto realcheck; |
|
} |
|
} |
|
return true; // we got out easy |
|
|
|
realcheck: |
|
// check it for real... |
|
start[2] = mins[2] + flStepSize; // seems to help going up/down slopes. |
|
|
|
// the midpoint must be within 16 of the bottom |
|
start[0] = stop[0] = (mins[0] + maxs[0])*0.5; |
|
start[1] = stop[1] = (mins[1] + maxs[1])*0.5; |
|
stop[2] = start[2] - 2*flStepSize; |
|
|
|
UTIL_TraceLine( start, stop, mask, pTraceFilter, &trace ); |
|
|
|
if (trace.fraction == 1.0) |
|
return false; |
|
mid = bottom = trace.endpos[2]; |
|
|
|
// the corners must be within 16 of the midpoint |
|
for (x=0 ; x<=1 ; x++) |
|
{ |
|
for (y=0 ; y<=1 ; y++) |
|
{ |
|
start[0] = stop[0] = x ? maxs[0] : mins[0]; |
|
start[1] = stop[1] = y ? maxs[1] : mins[1]; |
|
|
|
UTIL_TraceLine( start, stop, mask, pTraceFilter, &trace ); |
|
|
|
if (trace.fraction != 1.0 && trace.endpos[2] > bottom) |
|
bottom = trace.endpos[2]; |
|
if (trace.fraction == 1.0 || mid - trace.endpos[2] > flStepSize) |
|
return false; |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
|
|
|
|
bool g_bDisableEhandleAccess = false; |
|
bool g_bReceivedChainedUpdateOnRemove = false; |
|
//----------------------------------------------------------------------------- |
|
// Purpose: Sets the entity up for deletion. Entity will not actually be deleted |
|
// until the next frame, so there can be no pointer errors. |
|
// Input : *oldObj - object to delete |
|
//----------------------------------------------------------------------------- |
|
void UTIL_Remove( IServerNetworkable *oldObj ) |
|
{ |
|
CServerNetworkProperty* pProp = static_cast<CServerNetworkProperty*>( oldObj ); |
|
if ( !pProp || pProp->IsMarkedForDeletion() ) |
|
return; |
|
|
|
if ( PhysIsInCallback() ) |
|
{ |
|
// This assert means that someone is deleting an entity inside a callback. That isn't supported so |
|
// this code will defer the deletion of that object until the end of the current physics simulation frame |
|
// Since this is hidden from the calling code it's preferred to call PhysCallbackRemove() directly from the caller |
|
// in case the deferred delete will have unwanted results (like continuing to receive callbacks). That will make it |
|
// obvious why the unwanted results are happening so the caller can handle them appropriately. (some callbacks can be masked |
|
// or the calling entity can be flagged to filter them in most cases) |
|
Assert(0); |
|
PhysCallbackRemove(oldObj); |
|
return; |
|
} |
|
|
|
// mark it for deletion |
|
pProp->MarkForDeletion( ); |
|
|
|
CBaseEntity *pBaseEnt = oldObj->GetBaseEntity(); |
|
if ( pBaseEnt ) |
|
{ |
|
|
|
g_bReceivedChainedUpdateOnRemove = false; |
|
pBaseEnt->UpdateOnRemove(); |
|
|
|
Assert( g_bReceivedChainedUpdateOnRemove ); |
|
|
|
// clear oldObj targetname / other flags now |
|
pBaseEnt->SetName( NULL_STRING ); |
|
|
|
|
|
} |
|
|
|
gEntList.AddToDeleteList( oldObj ); |
|
} |
|
|
|
void UTIL_Remove( CBaseEntity *oldObj ) |
|
{ |
|
if ( !oldObj ) |
|
return; |
|
UTIL_Remove( oldObj->NetworkProp() ); |
|
} |
|
|
|
static int s_RemoveImmediateSemaphore = 0; |
|
void UTIL_DisableRemoveImmediate() |
|
{ |
|
s_RemoveImmediateSemaphore++; |
|
} |
|
void UTIL_EnableRemoveImmediate() |
|
{ |
|
s_RemoveImmediateSemaphore--; |
|
Assert(s_RemoveImmediateSemaphore>=0); |
|
} |
|
//----------------------------------------------------------------------------- |
|
// Purpose: deletes an entity, without any delay. WARNING! Only use this when sure |
|
// no pointers rely on this entity. |
|
// Input : *oldObj - the entity to delete |
|
//----------------------------------------------------------------------------- |
|
void UTIL_RemoveImmediate( CBaseEntity *oldObj ) |
|
{ |
|
// valid pointer or already removed? |
|
if ( !oldObj || oldObj->IsEFlagSet(EFL_KILLME) ) |
|
return; |
|
|
|
if ( s_RemoveImmediateSemaphore ) |
|
{ |
|
UTIL_Remove(oldObj); |
|
return; |
|
} |
|
|
|
|
|
|
|
oldObj->AddEFlags( EFL_KILLME ); // Make sure to ignore further calls into here or UTIL_Remove. |
|
|
|
g_bReceivedChainedUpdateOnRemove = false; |
|
oldObj->UpdateOnRemove(); |
|
Assert( g_bReceivedChainedUpdateOnRemove ); |
|
|
|
// Entities shouldn't reference other entities in their destructors |
|
// that type of code should only occur in an UpdateOnRemove call |
|
g_bDisableEhandleAccess = true; |
|
delete oldObj; |
|
g_bDisableEhandleAccess = false; |
|
|
|
|
|
} |
|
|
|
|
|
// returns a CBaseEntity pointer to a player by index. Only returns if the player is spawned and connected |
|
// otherwise returns NULL |
|
// Index is 1 based |
|
CBasePlayer *UTIL_PlayerByIndex( int playerIndex ) |
|
{ |
|
CBasePlayer *pPlayer = NULL; |
|
|
|
if ( playerIndex > 0 && playerIndex <= gpGlobals->maxClients ) |
|
{ |
|
edict_t *pPlayerEdict = INDEXENT( playerIndex ); |
|
if ( pPlayerEdict && !pPlayerEdict->IsFree() ) |
|
{ |
|
pPlayer = (CBasePlayer*)GetContainingEntity( pPlayerEdict ); |
|
} |
|
} |
|
|
|
return pPlayer; |
|
} |
|
|
|
CBasePlayer* UTIL_PlayerByName( const char *name ) |
|
{ |
|
if ( !name || !name[0] ) |
|
return NULL; |
|
|
|
for (int i = 1; i<=gpGlobals->maxClients; i++ ) |
|
{ |
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); |
|
|
|
if ( !pPlayer ) |
|
continue; |
|
|
|
if ( !pPlayer->IsConnected() ) |
|
continue; |
|
|
|
if ( Q_stricmp( pPlayer->GetPlayerName(), name ) == 0 ) |
|
{ |
|
return pPlayer; |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
CBasePlayer* UTIL_PlayerByUserId( int userID ) |
|
{ |
|
for (int i = 1; i<=gpGlobals->maxClients; i++ ) |
|
{ |
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); |
|
|
|
if ( !pPlayer ) |
|
continue; |
|
|
|
if ( !pPlayer->IsConnected() ) |
|
continue; |
|
|
|
if ( engine->GetPlayerUserId(pPlayer->edict()) == userID ) |
|
{ |
|
return pPlayer; |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
// |
|
// Return the local player. |
|
// If this is a multiplayer game, return NULL. |
|
// |
|
CBasePlayer *UTIL_GetLocalPlayer( void ) |
|
{ |
|
if ( gpGlobals->maxClients > 1 ) |
|
{ |
|
if ( developer.GetBool() ) |
|
{ |
|
Assert( !"UTIL_GetLocalPlayer" ); |
|
|
|
#ifdef DEBUG |
|
Warning( "UTIL_GetLocalPlayer() called in multiplayer game.\n" ); |
|
#endif |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
return UTIL_PlayerByIndex( 1 ); |
|
} |
|
|
|
// |
|
// Get the local player on a listen server - this is for multiplayer use only |
|
// |
|
CBasePlayer *UTIL_GetListenServerHost( void ) |
|
{ |
|
// no "local player" if this is a dedicated server or a single player game |
|
if (engine->IsDedicatedServer()) |
|
{ |
|
Assert( !"UTIL_GetListenServerHost" ); |
|
Warning( "UTIL_GetListenServerHost() called from a dedicated server or single-player game.\n" ); |
|
return NULL; |
|
} |
|
|
|
return UTIL_PlayerByIndex( 1 ); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Returns true if the command was issued by the listenserver host, or by the dedicated server, via rcon or the server console. |
|
* This is valid during ConCommand execution. |
|
*/ |
|
bool UTIL_IsCommandIssuedByServerAdmin( void ) |
|
{ |
|
int issuingPlayerIndex = UTIL_GetCommandClientIndex(); |
|
|
|
if ( engine->IsDedicatedServer() && issuingPlayerIndex > 0 ) |
|
return false; |
|
|
|
if ( issuingPlayerIndex > 1 ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Returns a CBaseEntity pointer by entindex. Index is 1 based. |
|
*/ |
|
CBaseEntity *UTIL_EntityByIndex( int entityIndex ) |
|
{ |
|
CBaseEntity *entity = NULL; |
|
|
|
if ( entityIndex > 0 ) |
|
{ |
|
edict_t *edict = INDEXENT( entityIndex ); |
|
if ( edict && !edict->IsFree() ) |
|
{ |
|
entity = GetContainingEntity( edict ); |
|
} |
|
} |
|
|
|
return entity; |
|
} |
|
|
|
|
|
int ENTINDEX( CBaseEntity *pEnt ) |
|
{ |
|
// This works just like ENTINDEX for edicts. |
|
if ( pEnt ) |
|
return pEnt->entindex(); |
|
else |
|
return 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : playerIndex - |
|
// ping - |
|
// packetloss - |
|
//----------------------------------------------------------------------------- |
|
void UTIL_GetPlayerConnectionInfo( int playerIndex, int& ping, int &packetloss ) |
|
{ |
|
CBasePlayer *player = UTIL_PlayerByIndex( playerIndex ); |
|
if ( player->IsSplitScreenPlayer() && |
|
player->GetSplitScreenPlayerOwner() ) |
|
{ |
|
player = player->GetSplitScreenPlayerOwner(); |
|
playerIndex = player->entindex(); |
|
} |
|
|
|
INetChannelInfo *nci = engine->GetPlayerNetInfo(playerIndex); |
|
|
|
if ( nci && player && !player->IsBot() ) |
|
{ |
|
float latency = nci->GetAvgLatency( FLOW_OUTGOING ); // in seconds |
|
|
|
// that should be the correct latency, we assume that cmdrate is higher |
|
// then updaterate, what is the case for default settings |
|
const char * szCmdRate = engine->GetClientConVarValue( playerIndex, "cl_cmdrate" ); |
|
|
|
int nCmdRate = MAX( 1, Q_atoi( szCmdRate ) ); |
|
latency -= (0.5f/nCmdRate) + TICKS_TO_TIME( 1.0f ); // correct latency |
|
|
|
// in GoldSrc we had a different, not fixed tickrate. so we have to adjust |
|
// Source pings by half a tick to match the old GoldSrc pings. |
|
latency -= TICKS_TO_TIME( 0.5f ); |
|
|
|
ping = latency * 1000.0f; // as msecs |
|
ping = clamp( ping, 5, 1000 ); // set bounds, dont show pings under 5 msecs |
|
|
|
packetloss = 100.0f * nci->GetAvgLoss( FLOW_INCOMING ); // loss in percentage |
|
packetloss = clamp( packetloss, 0, 100 ); |
|
} |
|
else |
|
{ |
|
ping = 0; |
|
packetloss = 0; |
|
} |
|
} |
|
|
|
static unsigned short FixedUnsigned16( float value, float scale ) |
|
{ |
|
int output; |
|
|
|
output = value * scale; |
|
if ( output < 0 ) |
|
output = 0; |
|
if ( output > 0xFFFF ) |
|
output = 0xFFFF; |
|
|
|
return (unsigned short)output; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Compute shake amplitude |
|
//----------------------------------------------------------------------------- |
|
inline float ComputeShakeAmplitude( const Vector ¢er, const Vector &shakePt, float amplitude, float radius ) |
|
{ |
|
if ( radius <= 0 ) |
|
return amplitude; |
|
|
|
float localAmplitude = -1; |
|
Vector delta = center - shakePt; |
|
float distance = delta.Length(); |
|
|
|
if ( distance <= radius ) |
|
{ |
|
// Make the amplitude fall off over distance |
|
float flPerc = 1.0 - (distance / radius); |
|
localAmplitude = amplitude * flPerc; |
|
} |
|
|
|
return localAmplitude; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Transmits the actual shake event |
|
//----------------------------------------------------------------------------- |
|
inline void TransmitShakeEvent( CBasePlayer *pPlayer, float localAmplitude, float frequency, float duration, ShakeCommand_t eCommand ) |
|
{ |
|
if (( localAmplitude > 0 ) || ( eCommand == SHAKE_STOP )) |
|
{ |
|
if ( eCommand == SHAKE_STOP ) |
|
localAmplitude = 0; |
|
|
|
CSingleUserRecipientFilter user( pPlayer ); |
|
user.MakeReliable(); |
|
UserMessageBegin( user, "Shake" ); |
|
WRITE_BYTE( eCommand ); // shake command (SHAKE_START, STOP, FREQUENCY, AMPLITUDE) |
|
WRITE_FLOAT( localAmplitude ); // shake magnitude/amplitude |
|
WRITE_FLOAT( frequency ); // shake noise frequency |
|
WRITE_FLOAT( duration ); // shake lasts this long |
|
MessageEnd(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Shake the screen of all clients within radius. |
|
// radius == 0, shake all clients |
|
// UNDONE: Fix falloff model (disabled)? |
|
// UNDONE: Affect user controls? |
|
// Input : center - Center of screen shake, radius is measured from here. |
|
// amplitude - Amplitude of shake |
|
// frequency - |
|
// duration - duration of shake in seconds. |
|
// radius - Radius of effect, 0 shakes all clients. |
|
// command - One of the following values: |
|
// SHAKE_START - starts the screen shake for all players within the radius |
|
// SHAKE_STOP - stops the screen shake for all players within the radius |
|
// SHAKE_AMPLITUDE - modifies the amplitude of the screen shake |
|
// for all players within the radius |
|
// SHAKE_FREQUENCY - modifies the frequency of the screen shake |
|
// for all players within the radius |
|
// bAirShake - if this is false, then it will only shake players standing on the ground. |
|
//----------------------------------------------------------------------------- |
|
const float MAX_SHAKE_AMPLITUDE = 16.0f; |
|
void UTIL_ScreenShake( const Vector ¢er, float amplitude, float frequency, float duration, float radius, ShakeCommand_t eCommand, bool bAirShake, CUtlVector<CBasePlayer *> *ignore ) |
|
{ |
|
int i; |
|
float localAmplitude; |
|
|
|
if ( amplitude > MAX_SHAKE_AMPLITUDE ) |
|
{ |
|
amplitude = MAX_SHAKE_AMPLITUDE; |
|
} |
|
for ( i = 1; i <= gpGlobals->maxClients; i++ ) |
|
{ |
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); |
|
|
|
// |
|
// Only start shakes for players that are on the ground unless doing an air shake. |
|
// |
|
if ( !pPlayer || (!bAirShake && (eCommand == SHAKE_START) && !(pPlayer->GetFlags() & FL_ONGROUND)) ) |
|
{ |
|
continue; |
|
} |
|
|
|
if ( ignore && ignore->HasElement( pPlayer ) ) |
|
{ |
|
continue; |
|
} |
|
|
|
localAmplitude = ComputeShakeAmplitude( center, pPlayer->WorldSpaceCenter(), amplitude, radius ); |
|
|
|
// This happens if the player is outside the radius, in which case we should ignore |
|
// all commands |
|
if (localAmplitude < 0) |
|
continue; |
|
|
|
TransmitShakeEvent( pPlayer, localAmplitude, frequency, duration, eCommand ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Shake an object and all players on or near it |
|
//----------------------------------------------------------------------------- |
|
void UTIL_ScreenShakeObject( CBaseEntity *pEnt, const Vector ¢er, float amplitude, float frequency, float duration, float radius, ShakeCommand_t eCommand, bool bAirShake ) |
|
{ |
|
int i; |
|
float localAmplitude; |
|
|
|
CBaseEntity *pHighestParent = pEnt->GetRootMoveParent(); |
|
for ( i = 1; i <= gpGlobals->maxClients; i++ ) |
|
{ |
|
CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); |
|
if (!pPlayer) |
|
continue; |
|
|
|
// Shake the object, or anything hierarchically attached to it at maximum amplitude |
|
localAmplitude = 0; |
|
if (pHighestParent == pPlayer->GetRootMoveParent()) |
|
{ |
|
localAmplitude = amplitude; |
|
} |
|
else if ((pPlayer->GetFlags() & FL_ONGROUND) && pPlayer->GetGroundEntity() && (pPlayer->GetGroundEntity()->GetRootMoveParent() == pHighestParent)) |
|
{ |
|
// If the player is standing on the object, use maximum amplitude |
|
localAmplitude = amplitude; |
|
} |
|
else |
|
{ |
|
// Only shake players that are on the ground. |
|
if ( !bAirShake && !(pPlayer->GetFlags() & FL_ONGROUND) ) |
|
{ |
|
continue; |
|
} |
|
|
|
localAmplitude = ComputeShakeAmplitude( center, pPlayer->WorldSpaceCenter(), amplitude, radius ); |
|
|
|
// This happens if the player is outside the radius, |
|
// in which case we should ignore all commands |
|
if (localAmplitude < 0) |
|
continue; |
|
} |
|
|
|
TransmitShakeEvent( (CBasePlayer *)pPlayer, localAmplitude, frequency, duration, eCommand ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Transmits the actual tilt event |
|
//----------------------------------------------------------------------------- |
|
inline void TransmitTiltEvent( CBasePlayer *pPlayer, QAngle tiltAngle, float duration, float tiltTime, ShakeCommand_t eCommand, bool bEaseInOut ) |
|
{ |
|
CSingleUserRecipientFilter user( pPlayer ); |
|
user.MakeReliable(); |
|
UserMessageBegin( user, "Tilt" ); |
|
WRITE_BYTE( eCommand ); // tilt command (SHAKE_START, STOP, FREQUENCY, AMPLITUDE) |
|
WRITE_BYTE( bEaseInOut ); // tilt ease in/out |
|
WRITE_FLOAT( tiltAngle.x ); // tilt angle |
|
WRITE_FLOAT( tiltAngle.y ); |
|
WRITE_FLOAT( tiltAngle.z ); |
|
WRITE_FLOAT( duration ); // tilt lasts this long |
|
WRITE_FLOAT( tiltTime ); // tilt time |
|
MessageEnd(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Tilt the screen of all clients within radius. |
|
// radius == 0, shake all clients |
|
// Input : center - Center of screen tilt, radius is measured from here. |
|
// tiltAngle - Angle that the world is pretending to tilt |
|
// duration - duration of tilt in seconds. |
|
// radius - Radius of effect, 0 shakes all clients. |
|
// tiltTime - how long it takes to reach full tilt |
|
// command - One of the following values: |
|
// SHAKE_START - starts the screen tilt for all players within the radius |
|
// SHAKE_STOP - stops the screen tilt for all players within the radius |
|
// SHAKE_AMPLITUDE - modifies the amplitude of the screen tilt |
|
// for all players within the radius |
|
// SHAKE_FREQUENCY - modifies the frequency of the screen tilt |
|
// for all players within the radius |
|
// bAirShake - if this is false, then it will only tilt players standing on the ground. |
|
//----------------------------------------------------------------------------- |
|
void UTIL_ScreenTilt( const Vector ¢er, const QAngle &tiltAngle, float duration, float radius, float tiltTime, ShakeCommand_t eCommand, bool bEaseInOut ) |
|
{ |
|
int i; |
|
|
|
for ( i = 1; i <= gpGlobals->maxClients; i++ ) |
|
{ |
|
CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); |
|
|
|
// |
|
// Only start shakes for players that are on the ground unless doing an air shake. |
|
// |
|
if ( !pPlayer ) |
|
{ |
|
continue; |
|
} |
|
|
|
// This happens if the player is outside the radius, in which case we should ignore |
|
// all commands |
|
if ( radius != 0.0f && pPlayer->WorldSpaceCenter().DistTo( center ) > radius ) |
|
continue; |
|
|
|
TransmitTiltEvent( (CBasePlayer *)pPlayer, tiltAngle, duration, tiltTime, eCommand, bEaseInOut ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Punches the view of all clients within radius. |
|
// If radius is 0, punches all clients. |
|
// Input : center - Center of punch, radius is measured from here. |
|
// radius - Radius of effect, 0 punches all clients. |
|
// bInAir - if this is false, then it will only punch players standing on the ground. |
|
//----------------------------------------------------------------------------- |
|
void UTIL_ViewPunch( const Vector ¢er, QAngle angPunch, float radius, bool bInAir ) |
|
{ |
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ ) |
|
{ |
|
CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); |
|
|
|
// |
|
// Only apply the punch to players that are on the ground unless doing an air punch. |
|
// |
|
if ( !pPlayer || (!bInAir && !(pPlayer->GetFlags() & FL_ONGROUND)) ) |
|
{ |
|
continue; |
|
} |
|
|
|
QAngle angTemp = angPunch; |
|
|
|
if ( radius > 0 ) |
|
{ |
|
Vector delta = center - pPlayer->GetAbsOrigin(); |
|
float distance = delta.Length(); |
|
|
|
if ( distance <= radius ) |
|
{ |
|
// Make the punch amplitude fall off over distance. |
|
float flPerc = 1.0 - (distance / radius); |
|
angTemp *= flPerc; |
|
} |
|
else |
|
{ |
|
continue; |
|
} |
|
} |
|
|
|
pPlayer->ViewPunch( angTemp ); |
|
} |
|
} |
|
|
|
|
|
void UTIL_ScreenFadeBuild( ScreenFade_t &fade, const color32 &color, float fadeTime, float fadeHold, int flags ) |
|
{ |
|
fade.duration = FixedUnsigned16( fadeTime, 1<<SCREENFADE_FRACBITS ); // 7.9 fixed |
|
fade.holdTime = FixedUnsigned16( fadeHold, 1<<SCREENFADE_FRACBITS ); // 7.9 fixed |
|
fade.r = color.r; |
|
fade.g = color.g; |
|
fade.b = color.b; |
|
fade.a = color.a; |
|
fade.fadeFlags = flags; |
|
} |
|
|
|
|
|
void UTIL_ScreenFadeWrite( const ScreenFade_t &fade, CBaseEntity *pEntity ) |
|
{ |
|
if ( !pEntity || !pEntity->IsNetClient() ) |
|
return; |
|
|
|
CBasePlayer *pRecipient = static_cast< CBasePlayer * >( pEntity ); |
|
|
|
if ( pRecipient->ShouldThrottleUserMessage( "Fade" ) ) |
|
{ |
|
return; |
|
} |
|
|
|
CSingleUserRecipientFilter user( pRecipient ); |
|
user.MakeReliable(); |
|
|
|
UserMessageBegin( user, "Fade" ); |
|
WRITE_SHORT( fade.duration ); // fade lasts this long |
|
WRITE_SHORT( fade.holdTime ); // fade lasts this long |
|
WRITE_SHORT( fade.fadeFlags ); // fade type (in / out) |
|
WRITE_BYTE( fade.r ); // fade red |
|
WRITE_BYTE( fade.g ); // fade green |
|
WRITE_BYTE( fade.b ); // fade blue |
|
WRITE_BYTE( fade.a ); // fade blue |
|
MessageEnd(); |
|
} |
|
|
|
|
|
void UTIL_ScreenFadeAll( const color32 &color, float fadeTime, float fadeHold, int flags ) |
|
{ |
|
int i; |
|
ScreenFade_t fade; |
|
|
|
|
|
UTIL_ScreenFadeBuild( fade, color, fadeTime, fadeHold, flags ); |
|
|
|
for ( i = 1; i <= gpGlobals->maxClients; i++ ) |
|
{ |
|
CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); |
|
|
|
UTIL_ScreenFadeWrite( fade, pPlayer ); |
|
} |
|
} |
|
|
|
|
|
void UTIL_ScreenFade( CBaseEntity *pEntity, const color32 &color, float fadeTime, float fadeHold, int flags ) |
|
{ |
|
ScreenFade_t fade; |
|
|
|
UTIL_ScreenFadeBuild( fade, color, fadeTime, fadeHold, flags ); |
|
UTIL_ScreenFadeWrite( fade, pEntity ); |
|
} |
|
|
|
|
|
void UTIL_HudMessage( CBasePlayer *pToPlayer, const hudtextparms_t &textparms, const char *pMessage ) |
|
{ |
|
CRecipientFilter filter; |
|
|
|
if( pToPlayer ) |
|
{ |
|
filter.AddRecipient( pToPlayer ); |
|
} |
|
else |
|
{ |
|
filter.AddAllPlayers(); |
|
} |
|
|
|
filter.MakeReliable(); |
|
|
|
UserMessageBegin( filter, "HudMsg" ); |
|
WRITE_BYTE ( textparms.channel & 0xFF ); |
|
WRITE_FLOAT( textparms.x ); |
|
WRITE_FLOAT( textparms.y ); |
|
WRITE_BYTE ( textparms.r1 ); |
|
WRITE_BYTE ( textparms.g1 ); |
|
WRITE_BYTE ( textparms.b1 ); |
|
WRITE_BYTE ( textparms.a1 ); |
|
WRITE_BYTE ( textparms.r2 ); |
|
WRITE_BYTE ( textparms.g2 ); |
|
WRITE_BYTE ( textparms.b2 ); |
|
WRITE_BYTE ( textparms.a2 ); |
|
WRITE_BYTE ( textparms.effect ); |
|
WRITE_FLOAT( textparms.fadeinTime ); |
|
WRITE_FLOAT( textparms.fadeoutTime ); |
|
WRITE_FLOAT( textparms.holdTime ); |
|
WRITE_FLOAT( textparms.fxTime ); |
|
WRITE_STRING( pMessage ); |
|
MessageEnd(); |
|
} |
|
|
|
void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage ) |
|
{ |
|
UTIL_HudMessage( NULL, textparms, pMessage ); |
|
} |
|
|
|
void UTIL_HudHintText( CBaseEntity *pEntity, const char *pMessage ) |
|
{ |
|
if ( !pEntity ) |
|
return; |
|
|
|
CSingleUserRecipientFilter user( (CBasePlayer *)pEntity ); |
|
user.MakeReliable(); |
|
UserMessageBegin( user, "KeyHintText" ); |
|
WRITE_BYTE( 1 ); // one string |
|
WRITE_STRING( pMessage ); |
|
MessageEnd(); |
|
} |
|
|
|
void UTIL_ClientPrintFilter( IRecipientFilter& filter, int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) |
|
{ |
|
UserMessageBegin( filter, "TextMsg" ); |
|
WRITE_BYTE( msg_dest ); |
|
WRITE_STRING( msg_name ); |
|
|
|
if ( param1 ) |
|
WRITE_STRING( param1 ); |
|
else |
|
WRITE_STRING( "" ); |
|
|
|
if ( param2 ) |
|
WRITE_STRING( param2 ); |
|
else |
|
WRITE_STRING( "" ); |
|
|
|
if ( param3 ) |
|
WRITE_STRING( param3 ); |
|
else |
|
WRITE_STRING( "" ); |
|
|
|
if ( param4 ) |
|
WRITE_STRING( param4 ); |
|
else |
|
WRITE_STRING( "" ); |
|
|
|
MessageEnd(); |
|
} |
|
|
|
void UTIL_ClientPrintAll( int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) |
|
{ |
|
CReliableBroadcastRecipientFilter filter; |
|
|
|
UTIL_ClientPrintFilter( filter, msg_dest, msg_name, param1, param2, param3, param4 ); |
|
} |
|
|
|
void ClientPrint( CBasePlayer *player, int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) |
|
{ |
|
if ( !player ) |
|
return; |
|
|
|
CSingleUserRecipientFilter user( player ); |
|
user.MakeReliable(); |
|
|
|
UTIL_ClientPrintFilter( user, msg_dest, msg_name, param1, param2, param3, param4 ); |
|
} |
|
|
|
void UTIL_SayTextFilter( IRecipientFilter& filter, const char *pText, CBasePlayer *pPlayer, bool bChat ) |
|
{ |
|
UserMessageBegin( filter, "SayText" ); |
|
if ( pPlayer ) |
|
{ |
|
WRITE_BYTE( pPlayer->entindex() ); |
|
} |
|
else |
|
{ |
|
WRITE_BYTE( 0 ); // world, dedicated server says |
|
} |
|
WRITE_STRING( pText ); |
|
WRITE_BYTE( bChat ); |
|
MessageEnd(); |
|
} |
|
|
|
void UTIL_SayText2Filter( IRecipientFilter& filter, CBasePlayer *pEntity, bool bChat, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) |
|
{ |
|
UserMessageBegin( filter, "SayText2" ); |
|
if ( pEntity ) |
|
{ |
|
WRITE_BYTE( pEntity->entindex() ); |
|
} |
|
else |
|
{ |
|
WRITE_BYTE( 0 ); // world, dedicated server says |
|
} |
|
|
|
WRITE_BYTE( bChat ); |
|
|
|
WRITE_STRING( msg_name ); |
|
|
|
if ( param1 ) |
|
WRITE_STRING( param1 ); |
|
else |
|
WRITE_STRING( "" ); |
|
|
|
if ( param2 ) |
|
WRITE_STRING( param2 ); |
|
else |
|
WRITE_STRING( "" ); |
|
|
|
if ( param3 ) |
|
WRITE_STRING( param3 ); |
|
else |
|
WRITE_STRING( "" ); |
|
|
|
if ( param4 ) |
|
WRITE_STRING( param4 ); |
|
else |
|
WRITE_STRING( "" ); |
|
|
|
MessageEnd(); |
|
} |
|
|
|
void UTIL_SayText( const char *pText, CBasePlayer *pToPlayer ) |
|
{ |
|
if ( !pToPlayer->IsNetClient() ) |
|
return; |
|
|
|
CSingleUserRecipientFilter user( pToPlayer ); |
|
user.MakeReliable(); |
|
|
|
UTIL_SayTextFilter( user, pText, pToPlayer, false ); |
|
} |
|
|
|
void UTIL_SayTextAll( const char *pText, CBasePlayer *pPlayer, bool bChat ) |
|
{ |
|
CReliableBroadcastRecipientFilter filter; |
|
UTIL_SayTextFilter( filter, pText, pPlayer, bChat ); |
|
} |
|
|
|
void UTIL_ShowMessage( const char *pString, CBasePlayer *pPlayer ) |
|
{ |
|
CRecipientFilter filter; |
|
|
|
if ( pPlayer ) |
|
{ |
|
filter.AddRecipient( pPlayer ); |
|
} |
|
else |
|
{ |
|
filter.AddAllPlayers(); |
|
} |
|
|
|
filter.MakeReliable(); |
|
|
|
UserMessageBegin( filter, "HudText" ); |
|
WRITE_STRING( pString ); |
|
MessageEnd(); |
|
} |
|
|
|
|
|
void UTIL_ShowMessageAll( const char *pString ) |
|
{ |
|
UTIL_ShowMessage( pString, NULL ); |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------------------------- |
|
// HudMessagePanel helper |
|
void UTIL_MessageTextAll( const char *text, Color color ) |
|
{ |
|
CReliableBroadcastRecipientFilter filter; |
|
UserMessageBegin( filter, "MessageText" ); |
|
WRITE_BYTE( color.r() ); |
|
WRITE_BYTE( color.g() ); |
|
WRITE_BYTE( color.b() ); |
|
WRITE_STRING( text ); |
|
MessageEnd(); |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------------------------- |
|
// HudMessagePanel helper |
|
void UTIL_MessageText( CBasePlayer *player, const char *text, Color color ) |
|
{ |
|
CSingleUserRecipientFilter filter( player ); |
|
filter.MakeReliable(); |
|
UserMessageBegin( filter, "MessageText" ); |
|
WRITE_BYTE( color.r() ); |
|
WRITE_BYTE( color.g() ); |
|
WRITE_BYTE( color.b() ); |
|
WRITE_STRING( text ); |
|
MessageEnd(); |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------------------------- |
|
// HudMessagePanel helper |
|
void UTIL_ResetMessageTextAll( void ) |
|
{ |
|
CReliableBroadcastRecipientFilter filter; |
|
UserMessageBegin( filter, "MessageText" ); |
|
MessageEnd(); |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------------------------- |
|
// HudMessagePanel helper |
|
void UTIL_ResetMessageText( CBasePlayer *player ) |
|
{ |
|
CSingleUserRecipientFilter filter( player ); |
|
filter.MakeReliable(); |
|
UserMessageBegin( filter, "MessageText" ); |
|
MessageEnd(); |
|
} |
|
|
|
|
|
// So we always return a valid surface |
|
static csurface_t g_NullSurface = { "**empty**", 0 }; |
|
|
|
void UTIL_SetTrace(trace_t& trace, const Ray_t &ray, edict_t *ent, float fraction, |
|
int hitgroup, unsigned int contents, const Vector& normal, float intercept ) |
|
{ |
|
trace.startsolid = (fraction == 0.0f); |
|
trace.fraction = fraction; |
|
VectorCopy( ray.m_Start, trace.startpos ); |
|
VectorMA( ray.m_Start, fraction, ray.m_Delta, trace.endpos ); |
|
VectorCopy( normal, trace.plane.normal ); |
|
trace.plane.dist = intercept; |
|
trace.m_pEnt = CBaseEntity::Instance( ent ); |
|
trace.hitgroup = hitgroup; |
|
trace.surface = g_NullSurface; |
|
trace.contents = contents; |
|
} |
|
|
|
void UTIL_ClearTrace( trace_t &trace ) |
|
{ |
|
memset( &trace, 0, sizeof(trace)); |
|
trace.fraction = 1.f; |
|
trace.fractionleftsolid = 0; |
|
trace.surface = g_NullSurface; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Sets the entity size |
|
//----------------------------------------------------------------------------- |
|
static void SetMinMaxSize (CBaseEntity *pEnt, const Vector& mins, const Vector& maxs ) |
|
{ |
|
for ( int i=0 ; i<3 ; i++ ) |
|
{ |
|
if ( mins[i] > maxs[i] ) |
|
{ |
|
Error( "%s: backwards mins/maxs", ( pEnt ) ? pEnt->GetDebugName() : "<NULL>" ); |
|
} |
|
} |
|
|
|
Assert( pEnt ); |
|
|
|
pEnt->SetCollisionBounds( mins, maxs ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Sets the model size |
|
//----------------------------------------------------------------------------- |
|
void UTIL_SetSize( CBaseEntity *pEnt, const Vector &vecMin, const Vector &vecMax ) |
|
{ |
|
SetMinMaxSize (pEnt, vecMin, vecMax); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Sets the model to be associated with an entity |
|
//----------------------------------------------------------------------------- |
|
void UTIL_SetModel( CBaseEntity *pEntity, const char *pModelName ) |
|
{ |
|
// check to see if model was properly precached |
|
int i = modelinfo->GetModelIndex( pModelName ); |
|
if ( i < 0 ) |
|
{ |
|
Error("%i/%s - %s: UTIL_SetModel: not precached: %s\n", pEntity->entindex(), |
|
STRING( pEntity->GetEntityName() ), |
|
pEntity->GetClassname(), pModelName); |
|
} |
|
|
|
pEntity->SetModelIndex( i ) ; |
|
pEntity->SetModelName( AllocPooledString( pModelName ) ); |
|
|
|
// brush model |
|
const model_t *mod = modelinfo->GetModel( i ); |
|
if ( mod ) |
|
{ |
|
Vector mins, maxs; |
|
modelinfo->GetModelBounds( mod, mins, maxs ); |
|
SetMinMaxSize (pEntity, mins, maxs); |
|
} |
|
else |
|
{ |
|
SetMinMaxSize (pEntity, vec3_origin, vec3_origin); |
|
} |
|
|
|
CBaseAnimating *pAnimating = pEntity->GetBaseAnimating(); |
|
if ( pAnimating ) |
|
{ |
|
pAnimating->m_nForceBone = 0; |
|
} |
|
} |
|
|
|
|
|
void UTIL_SetOrigin( CBaseEntity *entity, const Vector &vecOrigin, bool bFireTriggers ) |
|
{ |
|
entity->SetLocalOrigin( vecOrigin ); |
|
if ( bFireTriggers ) |
|
{ |
|
entity->PhysicsTouchTriggers(); |
|
} |
|
} |
|
|
|
|
|
void UTIL_ParticleEffect( const Vector &vecOrigin, const Vector &vecDirection, uint32 ulColor, int ulCount ) |
|
{ |
|
Msg( "UTIL_ParticleEffect: Disabled\n" ); |
|
} |
|
|
|
void UTIL_Smoke( const Vector &origin, const float scale, const float framerate ) |
|
{ |
|
g_pEffects->Smoke( origin, g_sModelIndexSmoke, scale, framerate ); |
|
} |
|
|
|
// snaps a vector to the nearest axis vector (if within epsilon) |
|
void UTIL_SnapDirectionToAxis( Vector &direction, float epsilon ) |
|
{ |
|
float proj = 1 - epsilon; |
|
for ( int i = 0; i < 3; i ++ ) |
|
{ |
|
if ( fabs(direction[i]) > proj ) |
|
{ |
|
// snap to axis unit vector |
|
if ( direction[i] < 0 ) |
|
direction[i] = -1.0f; |
|
else |
|
direction[i] = 1.0f; |
|
direction[(i+1)%3] = 0; |
|
direction[(i+2)%3] = 0; |
|
return; |
|
} |
|
} |
|
} |
|
|
|
char *UTIL_VarArgs( const char *format, ... ) |
|
{ |
|
va_list argptr; |
|
static char string[1024]; |
|
|
|
va_start (argptr, format); |
|
Q_vsnprintf(string, sizeof(string), format,argptr); |
|
va_end (argptr); |
|
|
|
return string; |
|
} |
|
|
|
bool UTIL_IsMasterTriggered(string_t sMaster, CBaseEntity *pActivator) |
|
{ |
|
if (sMaster != NULL_STRING) |
|
{ |
|
CBaseEntity *pMaster = gEntList.FindEntityByName( NULL, sMaster, NULL, pActivator ); |
|
|
|
if ( pMaster && (pMaster->ObjectCaps() & FCAP_MASTER) ) |
|
{ |
|
return pMaster->IsTriggered( pActivator ); |
|
} |
|
|
|
Warning( "Master was null or not a master!\n"); |
|
} |
|
|
|
// if this isn't a master entity, just say yes. |
|
return true; |
|
} |
|
|
|
void UTIL_BloodStream( const Vector &origin, const Vector &direction, int color, int amount ) |
|
{ |
|
if ( !UTIL_ShouldShowBlood( color ) ) |
|
return; |
|
|
|
if ( g_Language.GetInt() == LANGUAGE_GERMAN && color == BLOOD_COLOR_RED ) |
|
color = 0; |
|
|
|
CPVSFilter filter( origin ); |
|
te->BloodStream( filter, 0.0, &origin, &direction, 247, 63, 14, 255, MIN( amount, 255 ) ); |
|
} |
|
|
|
|
|
Vector UTIL_RandomBloodVector( void ) |
|
{ |
|
Vector direction; |
|
|
|
direction.x = random->RandomFloat ( -1, 1 ); |
|
direction.y = random->RandomFloat ( -1, 1 ); |
|
direction.z = random->RandomFloat ( 0, 1 ); |
|
|
|
return direction; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : Creates both an decal and any associated impact effects (such |
|
// as flecks) for the given iDamageType and the trace's end position |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
void UTIL_ImpactTrace( trace_t *pTrace, int iDamageType, char *pCustomImpactName ) |
|
{ |
|
CBaseEntity *pEntity = pTrace->m_pEnt; |
|
|
|
// Is the entity valid, is the surface sky? |
|
if ( !pEntity || !UTIL_IsValidEntity( pEntity ) || (pTrace->surface.flags & SURF_SKY) ) |
|
return; |
|
|
|
if ( pTrace->fraction == 1.0 ) |
|
return; |
|
|
|
pEntity->ImpactTrace( pTrace, iDamageType, pCustomImpactName ); |
|
} |
|
|
|
/* |
|
============== |
|
UTIL_PlayerDecalTrace |
|
|
|
A player is trying to apply his custom decal for the spray can. |
|
Tell connected clients to display it, or use the default spray can decal |
|
if the custom can't be loaded. |
|
============== |
|
*/ |
|
void UTIL_PlayerDecalTrace( trace_t *pTrace, int playernum ) |
|
{ |
|
if (pTrace->fraction == 1.0) |
|
return; |
|
|
|
CBroadcastRecipientFilter filter; |
|
|
|
te->PlayerDecal( filter, 0.0, |
|
&pTrace->endpos, playernum, pTrace->m_pEnt->entindex() ); |
|
} |
|
|
|
bool UTIL_TeamsMatch( const char *pTeamName1, const char *pTeamName2 ) |
|
{ |
|
// Everyone matches unless it's teamplay |
|
if ( !g_pGameRules->IsTeamplay() ) |
|
return true; |
|
|
|
// Both on a team? |
|
if ( *pTeamName1 != 0 && *pTeamName2 != 0 ) |
|
{ |
|
if ( !stricmp( pTeamName1, pTeamName2 ) ) // Same Team? |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
void UTIL_AxisStringToPointPoint( Vector &start, Vector &end, const char *pString ) |
|
{ |
|
char tmpstr[256]; |
|
|
|
Q_strncpy( tmpstr, pString, sizeof(tmpstr) ); |
|
char *pVec = strtok( tmpstr, "," ); |
|
int i = 0; |
|
while ( pVec != NULL && *pVec ) |
|
{ |
|
if ( i == 0 ) |
|
{ |
|
UTIL_StringToVector( start.Base(), pVec ); |
|
i++; |
|
} |
|
else |
|
{ |
|
UTIL_StringToVector( end.Base(), pVec ); |
|
} |
|
pVec = strtok( NULL, "," ); |
|
} |
|
} |
|
|
|
void UTIL_AxisStringToPointDir( Vector &start, Vector &dir, const char *pString ) |
|
{ |
|
Vector end; |
|
UTIL_AxisStringToPointPoint( start, end, pString ); |
|
dir = end - start; |
|
VectorNormalize(dir); |
|
} |
|
|
|
void UTIL_AxisStringToUnitDir( Vector &dir, const char *pString ) |
|
{ |
|
Vector start; |
|
UTIL_AxisStringToPointDir( start, dir, pString ); |
|
} |
|
|
|
/* |
|
================================================== |
|
UTIL_ClipPunchAngleOffset |
|
================================================== |
|
*/ |
|
|
|
void UTIL_ClipPunchAngleOffset( QAngle &in, const QAngle &punch, const QAngle &clip ) |
|
{ |
|
QAngle final = in + punch; |
|
|
|
//Clip each component |
|
for ( int i = 0; i < 3; i++ ) |
|
{ |
|
if ( final[i] > clip[i] ) |
|
{ |
|
final[i] = clip[i]; |
|
} |
|
else if ( final[i] < -clip[i] ) |
|
{ |
|
final[i] = -clip[i]; |
|
} |
|
|
|
//Return the result |
|
in[i] = final[i] - punch[i]; |
|
} |
|
} |
|
|
|
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; |
|
} |
|
|
|
|
|
extern int g_sModelIndexBubbles;// holds the index for the bubbles model |
|
|
|
void UTIL_Bubbles( const Vector& mins, const Vector& maxs, int count ) |
|
{ |
|
Vector mid = (mins + maxs) * 0.5; |
|
|
|
float flHeight = UTIL_WaterLevel( mid, mid.z, mid.z + 1024 ); |
|
flHeight = flHeight - mins.z; |
|
|
|
CPASFilter filter( mid ); |
|
|
|
te->Bubbles( filter, 0.0, |
|
&mins, &maxs, flHeight, g_sModelIndexBubbles, count, 8.0 ); |
|
} |
|
|
|
void UTIL_BubbleTrail( const Vector& from, const Vector& to, int count ) |
|
{ |
|
// Find water surface will return from.z if the from point is above water |
|
float flStartHeight = UTIL_FindWaterSurface( from, from.z, from.z + 256 ); |
|
flStartHeight = flStartHeight - from.z; |
|
|
|
float flEndHeight = UTIL_FindWaterSurface( to, to.z, to.z + 256 ); |
|
flEndHeight = flEndHeight - to.z; |
|
|
|
if ( ( flStartHeight == 0 ) && ( flEndHeight == 0 ) ) |
|
return; |
|
|
|
float flWaterZ = flStartHeight + from.z; |
|
|
|
const Vector *pFrom = &from; |
|
const Vector *pTo = &to; |
|
Vector vecWaterPoint; |
|
if ( ( flStartHeight == 0 ) || ( flEndHeight == 0 ) ) |
|
{ |
|
if ( flStartHeight == 0 ) |
|
{ |
|
flWaterZ = flEndHeight + to.z; |
|
} |
|
|
|
float t = IntersectRayWithAAPlane( from, to, 2, 1.0f, flWaterZ ); |
|
Assert( (t >= -1e-3f) && ( t <= 1.0f ) ); |
|
VectorLerp( from, to, t, vecWaterPoint ); |
|
if ( flStartHeight == 0 ) |
|
{ |
|
pFrom = &vecWaterPoint; |
|
|
|
// Reduce the count by the actual length |
|
count = (int)( count * ( 1.0f - t ) ); |
|
} |
|
else |
|
{ |
|
pTo = &vecWaterPoint; |
|
|
|
// Reduce the count by the actual length |
|
count = (int)( count * t ); |
|
} |
|
} |
|
|
|
CBroadcastRecipientFilter filter; |
|
te->BubbleTrail( filter, 0.0, pFrom, pTo, flWaterZ, g_sModelIndexBubbles, count, 8.0 ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : Start - |
|
// End - |
|
// ModelIndex - |
|
// FrameStart - |
|
// FrameRate - |
|
// Life - |
|
// Width - |
|
// Noise - |
|
// Red - |
|
// Green - |
|
// Brightness - |
|
// Speed - |
|
//----------------------------------------------------------------------------- |
|
void UTIL_Beam( Vector &Start, Vector &End, int nModelIndex, int nHaloIndex, unsigned char FrameStart, unsigned char FrameRate, |
|
float Life, unsigned char Width, unsigned char EndWidth, unsigned char FadeLength, unsigned char Noise, unsigned char Red, unsigned char Green, |
|
unsigned char Blue, unsigned char Brightness, unsigned char Speed) |
|
{ |
|
CBroadcastRecipientFilter filter; |
|
|
|
te->BeamPoints( filter, 0.0, |
|
&Start, |
|
&End, |
|
nModelIndex, |
|
nHaloIndex, |
|
FrameStart, |
|
FrameRate, |
|
Life, |
|
Width, |
|
EndWidth, |
|
FadeLength, |
|
Noise, |
|
Red, |
|
Green, |
|
Blue, |
|
Brightness, |
|
Speed ); |
|
} |
|
|
|
bool UTIL_IsValidEntity( CBaseEntity *pEnt ) |
|
{ |
|
edict_t *pEdict = pEnt->edict(); |
|
if ( !pEdict || pEdict->IsFree() ) |
|
return false; |
|
return true; |
|
} |
|
|
|
|
|
#define PRECACHE_OTHER_ONCE |
|
// UNDONE: Do we need this to avoid doing too much of this? Measure startup times and see |
|
#if defined( PRECACHE_OTHER_ONCE ) |
|
|
|
#include "utlsymbol.h" |
|
class CPrecacheOtherList : public CAutoGameSystem |
|
{ |
|
public: |
|
CPrecacheOtherList( char const *name ) : CAutoGameSystem( name ) |
|
{ |
|
} |
|
virtual void LevelInitPreEntity(); |
|
virtual void LevelShutdownPostEntity(); |
|
|
|
bool AddOrMarkPrecached( const char *pClassname ); |
|
|
|
private: |
|
CUtlSymbolTable m_list; |
|
}; |
|
|
|
void CPrecacheOtherList::LevelInitPreEntity() |
|
{ |
|
m_list.RemoveAll(); |
|
} |
|
|
|
void CPrecacheOtherList::LevelShutdownPostEntity() |
|
{ |
|
m_list.RemoveAll(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: mark or add |
|
// Input : *pEntity - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CPrecacheOtherList::AddOrMarkPrecached( const char *pClassname ) |
|
{ |
|
CUtlSymbol sym = m_list.Find( pClassname ); |
|
if ( sym.IsValid() ) |
|
return false; |
|
|
|
m_list.AddString( pClassname ); |
|
return true; |
|
} |
|
|
|
CPrecacheOtherList g_PrecacheOtherList( "CPrecacheOtherList" ); |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *szClassname - |
|
// *modelName - |
|
//----------------------------------------------------------------------------- |
|
void UTIL_PrecacheOther( const char *szClassname, const char *modelName ) |
|
{ |
|
#if defined( PRECACHE_OTHER_ONCE ) |
|
// already done this one?, if not, mark as done |
|
if ( !g_PrecacheOtherList.AddOrMarkPrecached( szClassname ) ) |
|
return; |
|
#endif |
|
|
|
CBaseEntity *pEntity = CreateEntityByName( szClassname ); |
|
if ( !pEntity ) |
|
{ |
|
Warning( "NULL Ent in UTIL_PrecacheOther\n" ); |
|
return; |
|
} |
|
|
|
// If we have a specified model, set it before calling precache |
|
if ( modelName && modelName[0] ) |
|
{ |
|
pEntity->SetModelName( AllocPooledString( modelName ) ); |
|
} |
|
|
|
if (pEntity) |
|
pEntity->Precache( ); |
|
|
|
UTIL_RemoveImmediate( pEntity ); |
|
} |
|
|
|
//========================================================= |
|
// UTIL_LogPrintf - Prints a logged message to console. |
|
// Preceded by LOG: ( timestamp ) < message > |
|
//========================================================= |
|
void UTIL_LogPrintf( char *fmt, ... ) |
|
{ |
|
if ( !g_bIsLogging ) |
|
return; |
|
|
|
va_list argptr; |
|
char tempString[1024]; |
|
|
|
va_start ( argptr, fmt ); |
|
Q_vsnprintf( tempString, sizeof(tempString), fmt, argptr ); |
|
va_end ( argptr ); |
|
|
|
// Print to server console |
|
engine->LogPrint( tempString ); |
|
} |
|
|
|
//========================================================= |
|
// UTIL_DotPoints - returns the dot product of a line from |
|
// src to check and vecdir. |
|
//========================================================= |
|
float UTIL_DotPoints ( const Vector &vecSrc, const Vector &vecCheck, const Vector &vecDir ) |
|
{ |
|
Vector2D vec2LOS; |
|
|
|
vec2LOS = ( vecCheck - vecSrc ).AsVector2D(); |
|
Vector2DNormalize( vec2LOS ); |
|
|
|
return DotProduct2D(vec2LOS, vecDir.AsVector2D()); |
|
} |
|
|
|
|
|
//========================================================= |
|
// UTIL_StripToken - for redundant keynames |
|
//========================================================= |
|
void UTIL_StripToken( const char *pKey, char *pDest ) |
|
{ |
|
int i = 0; |
|
|
|
while ( pKey[i] && pKey[i] != '#' ) |
|
{ |
|
pDest[i] = pKey[i]; |
|
i++; |
|
} |
|
pDest[i] = 0; |
|
} |
|
|
|
|
|
// computes gravity scale for an absolute gravity. Pass the result into CBaseEntity::SetGravity() |
|
float UTIL_ScaleForGravity( float desiredGravity ) |
|
{ |
|
float worldGravity = sv_gravity.GetFloat(); |
|
return worldGravity > 0 ? desiredGravity / worldGravity : 0; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Implemented for mathlib.c error handling |
|
// Input : *error - |
|
//----------------------------------------------------------------------------- |
|
extern "C" void Sys_Error( char *error, ... ) |
|
{ |
|
va_list argptr; |
|
char string[1024]; |
|
|
|
va_start( argptr, error ); |
|
Q_vsnprintf( string, sizeof(string), error, argptr ); |
|
va_end( argptr ); |
|
|
|
Warning( "%s", string ); |
|
Assert(0); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Spawns an entity into the game, initializing it with the map ent data block |
|
// Input : *pEntity - the newly created entity |
|
// *mapData - pointer a block of entity map data |
|
// Output : -1 if the entity was not successfully created; 0 on success |
|
//----------------------------------------------------------------------------- |
|
int DispatchSpawn( CBaseEntity *pEntity, bool bRunVScripts ) |
|
{ |
|
if ( pEntity ) |
|
{ |
|
MDLCACHE_CRITICAL_SECTION(); |
|
|
|
// keep a smart pointer that will now if the object gets deleted |
|
EHANDLE pEntSafe; |
|
pEntSafe = pEntity; |
|
|
|
// Initialize these or entities who don't link to the world won't have anything in here |
|
// is this necessary? |
|
//pEntity->SetAbsMins( pEntity->GetOrigin() - Vector(1,1,1) ); |
|
//pEntity->SetAbsMaxs( pEntity->GetOrigin() + Vector(1,1,1) ); |
|
|
|
|
|
if( bRunVScripts ) |
|
{ |
|
pEntity->RunVScripts(); |
|
pEntity->RunPrecacheScripts(); |
|
} |
|
|
|
#if defined(TRACK_ENTITY_MEMORY) && defined(USE_MEM_DEBUG) |
|
const char *pszClassname = NULL; |
|
int iClassname = ((CEntityFactoryDictionary*)EntityFactoryDictionary())->m_Factories.Find( pEntity->GetClassname() ); |
|
if ( iClassname != ((CEntityFactoryDictionary*)EntityFactoryDictionary())->m_Factories.InvalidIndex() ) |
|
pszClassname = ((CEntityFactoryDictionary*)EntityFactoryDictionary())->m_Factories.GetElementName( iClassname ); |
|
if ( pszClassname ) |
|
{ |
|
MemAlloc_PushAllocDbgInfo( pszClassname, __LINE__ ); |
|
} |
|
#endif |
|
bool bAsyncAnims = mdlcache->SetAsyncLoad( MDLCACHE_ANIMBLOCK, false ); |
|
CBaseAnimating *pAnimating = pEntity->GetBaseAnimating(); |
|
if (!pAnimating) |
|
{ |
|
pEntity->Spawn(); |
|
} |
|
else |
|
{ |
|
// Don't allow the PVS check to skip animation setup during spawning |
|
pAnimating->SetBoneCacheFlags( BCF_IS_IN_SPAWN ); |
|
pEntity->Spawn(); |
|
pAnimating->ClearBoneCacheFlags( BCF_IS_IN_SPAWN ); |
|
} |
|
mdlcache->SetAsyncLoad( MDLCACHE_ANIMBLOCK, bAsyncAnims ); |
|
|
|
#if defined(TRACK_ENTITY_MEMORY) && defined(USE_MEM_DEBUG) |
|
if ( pszClassname ) |
|
{ |
|
MemAlloc_PopAllocDbgInfo(); |
|
} |
|
#endif |
|
// Try to get the pointer again, in case the spawn function deleted the entity. |
|
// UNDONE: Spawn() should really return a code to ask that the entity be deleted, but |
|
// that would touch too much code for me to do that right now. |
|
|
|
if ( pEntSafe == NULL || pEntity->IsMarkedForDeletion() ) |
|
return -1; |
|
|
|
if ( pEntity->m_iGlobalname != NULL_STRING ) |
|
{ |
|
// Handle global stuff here |
|
int globalIndex = GlobalEntity_GetIndex( pEntity->m_iGlobalname ); |
|
if ( globalIndex >= 0 ) |
|
{ |
|
// Already dead? delete |
|
if ( GlobalEntity_GetState(globalIndex) == GLOBAL_DEAD ) |
|
{ |
|
pEntity->Remove(); |
|
return -1; |
|
} |
|
else if ( !FStrEq(STRING(gpGlobals->mapname), GlobalEntity_GetMap(globalIndex)) ) |
|
{ |
|
pEntity->MakeDormant(); // Hasn't been moved to this level yet, wait but stay alive |
|
} |
|
// In this level & not dead, continue on as normal |
|
} |
|
else |
|
{ |
|
// Spawned entities default to 'On' |
|
GlobalEntity_Add( pEntity->m_iGlobalname, gpGlobals->mapname, GLOBAL_ON ); |
|
// Msg( "Added global entity %s (%s)\n", pEntity->GetClassname(), STRING(pEntity->m_iGlobalname) ); |
|
} |
|
} |
|
|
|
gEntList.NotifySpawn( pEntity ); |
|
|
|
if( bRunVScripts ) |
|
{ |
|
pEntity->RunOnPostSpawnScripts(); |
|
} |
|
|
|
} |
|
|
|
return 0; |
|
} |
|
|
|
// UNDONE: This could be a better test - can we run the absbox through the bsp and see |
|
// if it contains any solid space? or would that eliminate some entities we want to keep? |
|
int UTIL_EntityInSolid( CBaseEntity *ent ) |
|
{ |
|
Vector point; |
|
|
|
CBaseEntity *pParent = ent->GetMoveParent(); |
|
// HACKHACK -- If you're attached to a client, always go through |
|
if ( pParent ) |
|
{ |
|
if ( pParent->IsPlayer() ) |
|
return 0; |
|
|
|
ent = ent->GetRootMoveParent(); |
|
} |
|
|
|
point = ent->WorldSpaceCenter(); |
|
return ( enginetrace->GetPointContents( point ) & MASK_SOLID ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Initialize the matrix from an entity |
|
// Input : *pEntity - |
|
//----------------------------------------------------------------------------- |
|
void EntityMatrix::InitFromEntity( CBaseEntity *pEntity, int iAttachment ) |
|
{ |
|
if ( !pEntity ) |
|
{ |
|
Identity(); |
|
return; |
|
} |
|
|
|
// Get an attachment's matrix? |
|
if ( iAttachment != 0 ) |
|
{ |
|
CBaseAnimating *pAnimating = pEntity->GetBaseAnimating(); |
|
if ( pAnimating && pAnimating->GetModelPtr() ) |
|
{ |
|
Vector vOrigin; |
|
QAngle vAngles; |
|
if ( pAnimating->GetAttachment( iAttachment, vOrigin, vAngles ) ) |
|
{ |
|
((VMatrix *)this)->SetupMatrixOrgAngles( vOrigin, vAngles ); |
|
return; |
|
} |
|
} |
|
} |
|
|
|
((VMatrix *)this)->SetupMatrixOrgAngles( pEntity->GetAbsOrigin(), pEntity->GetAbsAngles() ); |
|
} |
|
|
|
|
|
void EntityMatrix::InitFromEntityLocal( CBaseEntity *entity ) |
|
{ |
|
if ( !entity || !entity->edict() ) |
|
{ |
|
Identity(); |
|
return; |
|
} |
|
((VMatrix *)this)->SetupMatrixOrgAngles( entity->GetLocalOrigin(), entity->GetLocalAngles() ); |
|
} |
|
|
|
//================================================== |
|
// Purpose: |
|
// Input: |
|
// Output: |
|
//================================================== |
|
|
|
void UTIL_ValidateSoundName( string_t &name, const char *defaultStr ) |
|
{ |
|
if ( ( !name || |
|
strlen( (char*) STRING( name ) ) < 1 ) || |
|
!Q_stricmp( (char *)STRING(name), "0" ) ) |
|
{ |
|
name = AllocPooledString( defaultStr ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Slightly modified strtok. Does not modify the input string. Does |
|
// not skip over more than one separator at a time. This allows parsing |
|
// strings where tokens between separators may or may not be present: |
|
// |
|
// Door01,,,0 would be parsed as "Door01" "" "" "0" |
|
// Door01,Open,,0 would be parsed as "Door01" "Open" "" "0" |
|
// |
|
// Input : token - Returns with a token, or zero length if the token was missing. |
|
// str - String to parse. |
|
// sep - Character to use as separator. UNDONE: allow multiple separator chars |
|
// Output : Returns a pointer to the next token to be parsed. |
|
//----------------------------------------------------------------------------- |
|
const char *nexttoken(char *token, const char *str, char sep) |
|
{ |
|
if ((str == NULL) || (*str == '\0')) |
|
{ |
|
*token = '\0'; |
|
return(NULL); |
|
} |
|
|
|
// |
|
// Copy everything up to the first separator into the return buffer. |
|
// Do not include separators in the return buffer. |
|
// |
|
while ((*str != sep) && (*str != '\0')) |
|
{ |
|
*token++ = *str++; |
|
} |
|
*token = '\0'; |
|
|
|
// |
|
// Advance the pointer unless we hit the end of the input string. |
|
// |
|
if (*str == '\0') |
|
{ |
|
return(str); |
|
} |
|
|
|
return(++str); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Helper for UTIL_FindClientInPVS |
|
// Input : check - last checked client |
|
// Output : static int UTIL_GetNewCheckClient |
|
//----------------------------------------------------------------------------- |
|
// FIXME: include bspfile.h here? |
|
class CCheckClient : public CAutoGameSystem |
|
{ |
|
public: |
|
CCheckClient( char const *name ) : CAutoGameSystem( name ) |
|
{ |
|
} |
|
|
|
void LevelInitPreEntity() |
|
{ |
|
m_checkCluster = -1; |
|
m_lastcheck = 1; |
|
m_lastchecktime = -1; |
|
m_bClientPVSIsExpanded = false; |
|
} |
|
|
|
byte m_checkPVS[MAX_MAP_LEAFS/8]; |
|
byte m_checkVisibilityPVS[MAX_MAP_LEAFS/8]; |
|
int m_checkCluster; |
|
int m_lastcheck; |
|
float m_lastchecktime; |
|
bool m_bClientPVSIsExpanded; |
|
}; |
|
|
|
CCheckClient g_CheckClient( "CCheckClient" ); |
|
|
|
|
|
static int UTIL_GetNewCheckClient( int check ) |
|
{ |
|
int i; |
|
edict_t *ent; |
|
Vector org; |
|
|
|
// cycle to the next one |
|
|
|
if (check < 1) |
|
check = 1; |
|
if (check > gpGlobals->maxClients) |
|
check = gpGlobals->maxClients; |
|
|
|
if (check == gpGlobals->maxClients) |
|
i = 1; |
|
else |
|
i = check + 1; |
|
|
|
for ( ; ; i++) |
|
{ |
|
if ( i > gpGlobals->maxClients ) |
|
{ |
|
i = 1; |
|
} |
|
|
|
ent = INDEXENT( i ); |
|
if ( !ent ) |
|
continue; |
|
|
|
// Looped but didn't find anything else |
|
if ( i == check ) |
|
break; |
|
|
|
if ( !ent->GetUnknown() ) |
|
continue; |
|
|
|
CBaseEntity *entity = GetContainingEntity( ent ); |
|
if ( !entity ) |
|
continue; |
|
|
|
if ( entity->GetFlags() & FL_NOTARGET ) |
|
continue; |
|
|
|
// anything that is a client, or has a client as an enemy |
|
break; |
|
} |
|
|
|
if ( i != check ) |
|
{ |
|
memset( g_CheckClient.m_checkVisibilityPVS, 0, sizeof(g_CheckClient.m_checkVisibilityPVS) ); |
|
g_CheckClient.m_bClientPVSIsExpanded = false; |
|
} |
|
|
|
if ( ent ) |
|
{ |
|
// get the PVS for the entity |
|
CBaseEntity *pce = GetContainingEntity( ent ); |
|
if ( !pce ) |
|
return i; |
|
|
|
org = pce->EyePosition(); |
|
|
|
int clusterIndex = engine->GetClusterForOrigin( org ); |
|
if ( clusterIndex != g_CheckClient.m_checkCluster ) |
|
{ |
|
g_CheckClient.m_checkCluster = clusterIndex; |
|
engine->GetPVSForCluster( clusterIndex, sizeof(g_CheckClient.m_checkPVS), g_CheckClient.m_checkPVS ); |
|
|
|
|
|
} |
|
} |
|
|
|
return i; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Gets the current check client.... |
|
//----------------------------------------------------------------------------- |
|
static edict_t *UTIL_GetCurrentCheckClient() |
|
{ |
|
edict_t *ent; |
|
|
|
// find a new check if on a new frame |
|
float delta = gpGlobals->curtime - g_CheckClient.m_lastchecktime; |
|
if ( delta >= 0.1 || delta < 0 ) |
|
{ |
|
g_CheckClient.m_lastcheck = UTIL_GetNewCheckClient( g_CheckClient.m_lastcheck ); |
|
g_CheckClient.m_lastchecktime = gpGlobals->curtime; |
|
} |
|
|
|
// return check if it might be visible |
|
ent = INDEXENT( g_CheckClient.m_lastcheck ); |
|
|
|
// Allow dead clients -- JAY |
|
// Our monsters know the difference, and this function gates alot of behavior |
|
// It's annoying to die and see monsters stop thinking because you're no longer |
|
// "in" their PVS |
|
if ( !ent || ent->IsFree() || !ent->GetUnknown()) |
|
{ |
|
return NULL; |
|
} |
|
|
|
return ent; |
|
} |
|
|
|
|
|
|
|
void UTIL_SetClientVisibilityPVS( edict_t *pClient, const unsigned char *pvs, int pvssize ) |
|
{ |
|
if ( pClient == UTIL_GetCurrentCheckClient() ) |
|
{ |
|
Assert( pvssize <= sizeof(g_CheckClient.m_checkVisibilityPVS) ); |
|
|
|
g_CheckClient.m_bClientPVSIsExpanded = false; |
|
|
|
unsigned *pFrom = (unsigned *)pvs; |
|
unsigned *pMask = (unsigned *)g_CheckClient.m_checkPVS; |
|
unsigned *pTo = (unsigned *)g_CheckClient.m_checkVisibilityPVS; |
|
|
|
int limit = pvssize / 4; |
|
int i; |
|
|
|
for ( i = 0; i < limit; i++ ) |
|
{ |
|
pTo[i] = pFrom[i] & ~pMask[i]; |
|
|
|
if ( pFrom[i] ) |
|
{ |
|
g_CheckClient.m_bClientPVSIsExpanded = true; |
|
} |
|
} |
|
|
|
int remainder = pvssize % 4; |
|
for ( i = 0; i < remainder; i++ ) |
|
{ |
|
((unsigned char *)&pTo[limit])[i] = ((unsigned char *)&pFrom[limit])[i] & !((unsigned char *)&pMask[limit])[i]; |
|
|
|
if ( ((unsigned char *)&pFrom[limit])[i] != 0) |
|
{ |
|
g_CheckClient.m_bClientPVSIsExpanded = true; |
|
} |
|
} |
|
} |
|
} |
|
|
|
bool UTIL_ClientPVSIsExpanded() |
|
{ |
|
return g_CheckClient.m_bClientPVSIsExpanded; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns a client (or object that has a client enemy) that would be a valid target. |
|
// If there are more than one valid options, they are cycled each frame |
|
// If (self.origin + self.viewofs) is not in the PVS of the current target, it is not returned at all. |
|
// Input : *pEdict - |
|
// Output : edict_t* |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *UTIL_FindClientInPVS( const Vector &vecBoxMins, const Vector &vecBoxMaxs ) |
|
{ |
|
edict_t *ent = UTIL_GetCurrentCheckClient(); |
|
if ( !ent ) |
|
{ |
|
return NULL; |
|
} |
|
|
|
if ( !engine->CheckBoxInPVS( vecBoxMins, vecBoxMaxs, g_CheckClient.m_checkPVS, sizeof( g_CheckClient.m_checkPVS ) ) ) |
|
{ |
|
return NULL; |
|
} |
|
|
|
// might be able to see it |
|
return GetContainingEntity( ent ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns a client (or object that has a client enemy) that would be a valid target. |
|
// If there are more than one valid options, they are cycled each frame |
|
// If (self.origin + self.viewofs) is not in the PVS of the current target, it is not returned at all. |
|
// Input : *pEdict - |
|
// Output : edict_t* |
|
//----------------------------------------------------------------------------- |
|
ConVar sv_strict_notarget( "sv_strict_notarget", "0", 0, "If set, notarget will cause entities to never think they are in the pvs" ); |
|
|
|
edict_t *UTIL_FindClientInPVSGuts(edict_t *pEdict, unsigned char *pvs, unsigned pvssize ) |
|
{ |
|
Vector view; |
|
|
|
edict_t *ent = UTIL_GetCurrentCheckClient(); |
|
if ( !ent ) |
|
{ |
|
return NULL; |
|
} |
|
|
|
CBaseEntity *pPlayerEntity = GetContainingEntity( ent ); |
|
if( (!pPlayerEntity || (pPlayerEntity->GetFlags() & FL_NOTARGET)) && sv_strict_notarget.GetBool() ) |
|
{ |
|
return NULL; |
|
} |
|
// if current entity can't possibly see the check entity, return 0 |
|
// UNDONE: Build a box for this and do it over that box |
|
// UNDONE: Use CM_BoxLeafnums() |
|
CBaseEntity *pe = GetContainingEntity( pEdict ); |
|
if ( pe ) |
|
{ |
|
view = pe->EyePosition(); |
|
|
|
if ( !engine->CheckOriginInPVS( view, pvs, pvssize ) ) |
|
{ |
|
return NULL; |
|
} |
|
} |
|
|
|
// might be able to see it |
|
return ent; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns a client that could see the entity directly |
|
//----------------------------------------------------------------------------- |
|
|
|
edict_t *UTIL_FindClientInPVS(edict_t *pEdict) |
|
{ |
|
return g_pGameRules->DoFindClientInPVS( pEdict, g_CheckClient.m_checkPVS, sizeof( g_CheckClient.m_checkPVS ) ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns a client that could see the entity, including through a camera |
|
//----------------------------------------------------------------------------- |
|
edict_t *UTIL_FindClientInVisibilityPVS( edict_t *pEdict ) |
|
{ |
|
return g_pGameRules->DoFindClientInPVS( pEdict, g_CheckClient.m_checkVisibilityPVS, sizeof( g_CheckClient.m_checkVisibilityPVS ) ); |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns a chain of entities within the PVS of another entity (client) |
|
// starting_ent is the ent currently at in the list |
|
// a starting_ent of NULL signifies the beginning of a search |
|
// Input : *pplayer - |
|
// *starting_ent - |
|
// Output : edict_t |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *UTIL_EntitiesInPVS( CBaseEntity *pPVSEntity, CBaseEntity *pStartingEntity ) |
|
{ |
|
Vector org; |
|
static byte pvs[ MAX_MAP_CLUSTERS/8 ]; |
|
static Vector lastOrg( 0, 0, 0 ); |
|
static int lastCluster = -1; |
|
|
|
if ( !pPVSEntity ) |
|
return NULL; |
|
|
|
// NOTE: These used to be caching code here to prevent this from |
|
// being called over+over which breaks when you go back + forth |
|
// across level transitions |
|
// So, we'll always get the PVS each time we start a new EntitiesInPVS iteration. |
|
// Given that weapon_binocs + leveltransition code is the only current clients |
|
// of this, this seems safe. |
|
if ( !pStartingEntity ) |
|
{ |
|
org = pPVSEntity->EyePosition(); |
|
int clusterIndex = engine->GetClusterForOrigin( org ); |
|
Assert( clusterIndex >= 0 ); |
|
engine->GetPVSForCluster( clusterIndex, sizeof(pvs), pvs ); |
|
} |
|
|
|
for ( CBaseEntity *pEntity = gEntList.NextEnt(pStartingEntity); pEntity; pEntity = gEntList.NextEnt(pEntity) ) |
|
{ |
|
// Only return attached ents. |
|
if ( !pEntity->edict() ) |
|
continue; |
|
|
|
CBaseEntity *pParent = pEntity->GetRootMoveParent(); |
|
|
|
Vector vecSurroundMins, vecSurroundMaxs; |
|
pParent->CollisionProp()->WorldSpaceSurroundingBounds( &vecSurroundMins, &vecSurroundMaxs ); |
|
if ( !engine->CheckBoxInPVS( vecSurroundMins, vecSurroundMaxs, pvs, sizeof( pvs ) ) ) |
|
continue; |
|
|
|
return pEntity; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get the predicted postion of an entity of a certain number of seconds |
|
// Use this function with caution, it has great potential for annoying the player, especially |
|
// if used for target firing predition |
|
// Input : *pTarget - target entity to predict |
|
// timeDelta - amount of time to predict ahead (in seconds) |
|
// &vecPredictedPosition - output |
|
//----------------------------------------------------------------------------- |
|
void UTIL_PredictedPosition( CBaseEntity *pTarget, float flTimeDelta, Vector *vecPredictedPosition ) |
|
{ |
|
if ( ( pTarget == NULL ) || ( vecPredictedPosition == NULL ) ) |
|
return; |
|
|
|
Vector vecPredictedVel; |
|
|
|
//FIXME: Should we look at groundspeed or velocity for non-clients?? |
|
|
|
//Get the proper velocity to predict with |
|
CBasePlayer *pPlayer = ToBasePlayer( pTarget ); |
|
|
|
//Player works differently than other entities |
|
if ( pPlayer != NULL ) |
|
{ |
|
if ( pPlayer->IsInAVehicle() ) |
|
{ |
|
//Calculate the predicted position in this vehicle |
|
vecPredictedVel = pPlayer->GetVehicleEntity()->GetSmoothedVelocity(); |
|
} |
|
else |
|
{ |
|
//Get the player's stored velocity |
|
vecPredictedVel = pPlayer->GetSmoothedVelocity(); |
|
} |
|
} |
|
else |
|
{ |
|
// See if we're a combat character in a vehicle |
|
CBaseCombatCharacter *pCCTarget = pTarget->MyCombatCharacterPointer(); |
|
if ( pCCTarget != NULL && pCCTarget->IsInAVehicle() ) |
|
{ |
|
//Calculate the predicted position in this vehicle |
|
vecPredictedVel = pCCTarget->GetVehicleEntity()->GetSmoothedVelocity(); |
|
} |
|
else |
|
{ |
|
// See if we're an animating entity |
|
CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating *>(pTarget); |
|
if ( pAnimating != NULL ) |
|
{ |
|
vecPredictedVel = pAnimating->GetGroundSpeedVelocity(); |
|
} |
|
else |
|
{ |
|
// Otherwise we're a vanilla entity |
|
vecPredictedVel = pTarget->GetSmoothedVelocity(); |
|
} |
|
} |
|
} |
|
|
|
//Get the result |
|
(*vecPredictedPosition) = pTarget->GetAbsOrigin() + ( vecPredictedVel * flTimeDelta ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Points the destination entity at the target entity |
|
// Input : *pDest - entity to be pointed at the target |
|
// *pTarget - target to point at |
|
//----------------------------------------------------------------------------- |
|
bool UTIL_PointAtEntity( CBaseEntity *pDest, CBaseEntity *pTarget ) |
|
{ |
|
if ( ( pDest == NULL ) || ( pTarget == NULL ) ) |
|
{ |
|
return false; |
|
} |
|
|
|
Vector dir = (pTarget->GetAbsOrigin() - pDest->GetAbsOrigin()); |
|
|
|
VectorNormalize( dir ); |
|
|
|
//Store off as angles |
|
QAngle angles; |
|
VectorAngles( dir, angles ); |
|
pDest->SetLocalAngles( angles ); |
|
pDest->SetAbsAngles( angles ); |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Points the destination entity at the target entity by name |
|
// Input : *pDest - entity to be pointed at the target |
|
// strTarget - name of entity to target (will only choose the first!) |
|
//----------------------------------------------------------------------------- |
|
void UTIL_PointAtNamedEntity( CBaseEntity *pDest, string_t strTarget ) |
|
{ |
|
//Attempt to find the entity |
|
if ( !UTIL_PointAtEntity( pDest, gEntList.FindEntityByName( NULL, strTarget ) ) ) |
|
{ |
|
DevMsg( 1, "%s (%s) was unable to point at an entity named: %s\n", pDest->GetClassname(), pDest->GetDebugName(), STRING( strTarget ) ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Copy the pose parameter values from one entity to the other |
|
// Input : *pSourceEntity - entity to copy from |
|
// *pDestEntity - entity to copy to |
|
//----------------------------------------------------------------------------- |
|
bool UTIL_TransferPoseParameters( CBaseEntity *pSourceEntity, CBaseEntity *pDestEntity ) |
|
{ |
|
CBaseAnimating *pSourceBaseAnimating = dynamic_cast<CBaseAnimating*>( pSourceEntity ); |
|
CBaseAnimating *pDestBaseAnimating = dynamic_cast<CBaseAnimating*>( pDestEntity ); |
|
|
|
if ( !pSourceBaseAnimating || !pDestBaseAnimating ) |
|
return false; |
|
|
|
for ( int iPose = 0; iPose < MAXSTUDIOPOSEPARAM; ++iPose ) |
|
{ |
|
pDestBaseAnimating->SetPoseParameter( iPose, pSourceBaseAnimating->GetPoseParameter( iPose ) ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Make a muzzle flash appear |
|
// Input : &origin - position of the muzzle flash |
|
// &angles - angles of the fire direction |
|
// scale - scale of the muzzle flash |
|
// type - type of muzzle flash |
|
//----------------------------------------------------------------------------- |
|
void UTIL_MuzzleFlash( const Vector &origin, const QAngle &angles, int scale, int type ) |
|
{ |
|
CPASFilter filter( origin ); |
|
|
|
te->MuzzleFlash( filter, 0.0f, origin, angles, scale, type ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : vStartPos - start of the line |
|
// vEndPos - end of the line |
|
// vPoint - point to find nearest point to on specified line |
|
// clampEnds - clamps returned points to being on the line segment specified |
|
// Output : Vector - nearest point on the specified line |
|
//----------------------------------------------------------------------------- |
|
Vector UTIL_PointOnLineNearestPoint(const Vector& vStartPos, const Vector& vEndPos, const Vector& vPoint, bool clampEnds ) |
|
{ |
|
Vector vEndToStart = (vEndPos - vStartPos); |
|
Vector vOrgToStart = (vPoint - vStartPos); |
|
float fNumerator = DotProduct(vEndToStart,vOrgToStart); |
|
float fDenominator = vEndToStart.Length() * vOrgToStart.Length(); |
|
float fIntersectDist = vOrgToStart.Length()*(fNumerator/fDenominator); |
|
float flLineLength = VectorNormalize( vEndToStart ); |
|
|
|
if ( clampEnds ) |
|
{ |
|
fIntersectDist = clamp( fIntersectDist, 0.0f, flLineLength ); |
|
} |
|
|
|
Vector vIntersectPos = vStartPos + vEndToStart * fIntersectDist; |
|
|
|
return vIntersectPos; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
AngularImpulse WorldToLocalRotation( const VMatrix &localToWorld, const Vector &worldAxis, float rotation ) |
|
{ |
|
// fix axes of rotation to match axes of vector |
|
Vector rot = worldAxis * rotation; |
|
// since the matrix maps local to world, do a transpose rotation to get world to local |
|
AngularImpulse ang = localToWorld.VMul3x3Transpose( rot ); |
|
|
|
return ang; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *filename - |
|
// *pLength - |
|
// Output : byte |
|
//----------------------------------------------------------------------------- |
|
byte *UTIL_LoadFileForMe( const char *filename, int *pLength ) |
|
{ |
|
void *buffer = NULL; |
|
|
|
int length = filesystem->ReadFileEx( filename, "GAME", &buffer, true, true ); |
|
|
|
if ( pLength ) |
|
{ |
|
*pLength = length; |
|
} |
|
|
|
return (byte *)buffer; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *buffer - |
|
//----------------------------------------------------------------------------- |
|
void UTIL_FreeFile( byte *buffer ) |
|
{ |
|
filesystem->FreeOptimalReadBuffer( buffer ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Determines whether an entity is within a certain angular tolerance to viewer |
|
// Input : *pEntity - entity which is the "viewer" |
|
// vecPosition - position to test against |
|
// flTolerance - tolerance (as dot-product) |
|
// *pflDot - if not NULL, holds the |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool UTIL_IsFacingWithinTolerance( CBaseEntity *pViewer, const Vector &vecPosition, float flDotTolerance, float *pflDot /*= NULL*/ ) |
|
{ |
|
if ( pflDot ) |
|
{ |
|
*pflDot = 0.0f; |
|
} |
|
|
|
// Required elements |
|
if ( pViewer == NULL ) |
|
return false; |
|
|
|
Vector forward; |
|
pViewer->GetVectors( &forward, NULL, NULL ); |
|
|
|
Vector dir = vecPosition - pViewer->GetAbsOrigin(); |
|
VectorNormalize( dir ); |
|
|
|
// Larger dot product corresponds to a smaller angle |
|
float flDot = dir.Dot( forward ); |
|
|
|
// Return the result |
|
if ( pflDot ) |
|
{ |
|
*pflDot = flDot; |
|
} |
|
|
|
// Within the goal tolerance |
|
if ( flDot >= flDotTolerance ) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Determines whether an entity is within a certain angular tolerance to viewer |
|
// Input : *pEntity - entity which is the "viewer" |
|
// *pTarget - entity to test against |
|
// flTolerance - tolerance (as dot-product) |
|
// *pflDot - if not NULL, holds the |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool UTIL_IsFacingWithinTolerance( CBaseEntity *pViewer, CBaseEntity *pTarget, float flDotTolerance, float *pflDot /*= NULL*/ ) |
|
{ |
|
if ( pViewer == NULL || pTarget == NULL ) |
|
return false; |
|
|
|
return UTIL_IsFacingWithinTolerance( pViewer, pTarget->GetAbsOrigin(), flDotTolerance, pflDot ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Fills in color for debug purposes based on a relationship |
|
// Input : nRelationship - relationship to test |
|
// *pR, *pG, *pB - colors to fill |
|
//----------------------------------------------------------------------------- |
|
void UTIL_GetDebugColorForRelationship( int nRelationship, int &r, int &g, int &b ) |
|
{ |
|
switch ( nRelationship ) |
|
{ |
|
case D_LI: |
|
r = 0; |
|
g = 255; |
|
b = 0; |
|
break; |
|
case D_NU: |
|
r = 0; |
|
g = 0; |
|
b = 255; |
|
break; |
|
case D_HT: |
|
r = 255; |
|
g = 0; |
|
b = 0; |
|
break; |
|
case D_FR: |
|
r = 255; |
|
g = 255; |
|
b = 0; |
|
break; |
|
default: |
|
r = 255; |
|
g = 255; |
|
b = 255; |
|
break; |
|
} |
|
} |
|
|
|
void LoadAndSpawnEntities_ParseEntKVBlockHelper( CBaseEntity *pNode, KeyValues *pkvNode ) |
|
{ |
|
KeyValues *pkvNodeData = pkvNode->GetFirstSubKey(); |
|
while ( pkvNodeData ) |
|
{ |
|
// Handle the connections block |
|
if ( !Q_strcmp(pkvNodeData->GetName(), "connections") ) |
|
{ |
|
LoadAndSpawnEntities_ParseEntKVBlockHelper( pNode, pkvNodeData ); |
|
} |
|
else |
|
{ |
|
pNode->KeyValue( pkvNodeData->GetName(), pkvNodeData->GetString() ); |
|
} |
|
|
|
pkvNodeData = pkvNodeData->GetNextKey(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Loads and parses a file and spawns entities defined in it. |
|
//----------------------------------------------------------------------------- |
|
bool UTIL_LoadAndSpawnEntitiesFromScript( CUtlVector <CBaseEntity*> &entities, const char *pScriptFile, const char *pBlock, bool bActivate ) |
|
{ |
|
KeyValues *pkvFile = new KeyValues( pBlock ); |
|
|
|
if ( pkvFile->LoadFromFile( filesystem, pScriptFile, "GAME" ) ) |
|
{ |
|
// Load each block, and spawn the entities |
|
KeyValues *pkvNode = pkvFile->GetFirstSubKey(); |
|
while ( pkvNode ) |
|
{ |
|
// Get name |
|
const char *pNodeName = pkvNode->GetName(); |
|
|
|
if ( stricmp( pNodeName, "entity" ) ) |
|
{ |
|
pkvNode = pkvNode->GetNextKey(); |
|
continue; |
|
} |
|
|
|
KeyValues *pClassname = pkvNode->FindKey( "classname" ); |
|
|
|
if ( pClassname ) |
|
{ |
|
// Use the classname instead |
|
pNodeName = pClassname->GetString(); |
|
} |
|
|
|
// Spawn the entity |
|
CBaseEntity *pNode = CreateEntityByName( pNodeName ); |
|
|
|
if ( pNode ) |
|
{ |
|
LoadAndSpawnEntities_ParseEntKVBlockHelper( pNode, pkvNode ); |
|
DispatchSpawn( pNode ); |
|
entities.AddToTail( pNode ); |
|
} |
|
else |
|
{ |
|
Warning( "UTIL_LoadAndSpawnEntitiesFromScript: Failed to spawn entity, type: '%s'\n", pNodeName ); |
|
} |
|
|
|
// Move to next entity |
|
pkvNode = pkvNode->GetNextKey(); |
|
} |
|
|
|
if ( bActivate == true ) |
|
{ |
|
bool bAsyncAnims = mdlcache->SetAsyncLoad( MDLCACHE_ANIMBLOCK, false ); |
|
// Then activate all the entities |
|
for ( int i = 0; i < entities.Count(); i++ ) |
|
{ |
|
entities[i]->Activate(); |
|
} |
|
mdlcache->SetAsyncLoad( MDLCACHE_ANIMBLOCK, bAsyncAnims ); |
|
} |
|
} |
|
else |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Convert a vector an angle from worldspace to the entity's parent's local space |
|
// Input : *pEntity - Entity whose parent we're concerned with |
|
//----------------------------------------------------------------------------- |
|
void UTIL_ParentToWorldSpace( CBaseEntity *pEntity, Vector &vecPosition, QAngle &vecAngles ) |
|
{ |
|
if ( pEntity == NULL ) |
|
return; |
|
|
|
// Construct the entity-to-world matrix |
|
// Start with making an entity-to-parent matrix |
|
matrix3x4_t matEntityToParent; |
|
AngleMatrix( vecAngles, matEntityToParent ); |
|
MatrixSetColumn( vecPosition, 3, matEntityToParent ); |
|
|
|
// concatenate with our parent's transform |
|
matrix3x4_t matScratch, matResult; |
|
matrix3x4_t matParentToWorld; |
|
|
|
if ( pEntity->GetParent() != NULL ) |
|
{ |
|
matParentToWorld = pEntity->GetParentToWorldTransform( matScratch ); |
|
} |
|
else |
|
{ |
|
matParentToWorld = pEntity->EntityToWorldTransform(); |
|
} |
|
|
|
ConcatTransforms( matParentToWorld, matEntityToParent, matResult ); |
|
|
|
// pull our absolute position out of the matrix |
|
MatrixGetColumn( matResult, 3, vecPosition ); |
|
MatrixAngles( matResult, vecAngles ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Convert a vector and quaternion from worldspace to the entity's parent's local space |
|
// Input : *pEntity - Entity whose parent we're concerned with |
|
//----------------------------------------------------------------------------- |
|
void UTIL_ParentToWorldSpace( CBaseEntity *pEntity, Vector &vecPosition, Quaternion &quat ) |
|
{ |
|
if ( pEntity == NULL ) |
|
return; |
|
|
|
QAngle vecAngles; |
|
QuaternionAngles( quat, vecAngles ); |
|
UTIL_ParentToWorldSpace( pEntity, vecPosition, vecAngles ); |
|
AngleQuaternion( vecAngles, quat ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Convert a vector an angle from worldspace to the entity's parent's local space |
|
// Input : *pEntity - Entity whose parent we're concerned with |
|
//----------------------------------------------------------------------------- |
|
void UTIL_WorldToParentSpace( CBaseEntity *pEntity, Vector &vecPosition, QAngle &vecAngles ) |
|
{ |
|
if ( pEntity == NULL ) |
|
return; |
|
|
|
// Construct the entity-to-world matrix |
|
// Start with making an entity-to-parent matrix |
|
matrix3x4_t matEntityToParent; |
|
AngleMatrix( vecAngles, matEntityToParent ); |
|
MatrixSetColumn( vecPosition, 3, matEntityToParent ); |
|
|
|
// concatenate with our parent's transform |
|
matrix3x4_t matScratch, matResult; |
|
matrix3x4_t matWorldToParent; |
|
|
|
if ( pEntity->GetParent() != NULL ) |
|
{ |
|
matScratch = pEntity->GetParentToWorldTransform( matScratch ); |
|
} |
|
else |
|
{ |
|
matScratch = pEntity->EntityToWorldTransform(); |
|
} |
|
|
|
MatrixInvert( matScratch, matWorldToParent ); |
|
ConcatTransforms( matWorldToParent, matEntityToParent, matResult ); |
|
|
|
// pull our absolute position out of the matrix |
|
MatrixGetColumn( matResult, 3, vecPosition ); |
|
MatrixAngles( matResult, vecAngles ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Convert a vector and quaternion from worldspace to the entity's parent's local space |
|
// Input : *pEntity - Entity whose parent we're concerned with |
|
//----------------------------------------------------------------------------- |
|
void UTIL_WorldToParentSpace( CBaseEntity *pEntity, Vector &vecPosition, Quaternion &quat ) |
|
{ |
|
if ( pEntity == NULL ) |
|
return; |
|
|
|
QAngle vecAngles; |
|
QuaternionAngles( quat, vecAngles ); |
|
UTIL_WorldToParentSpace( pEntity, vecPosition, vecAngles ); |
|
AngleQuaternion( vecAngles, quat ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Given a vector, clamps the scalar axes to MAX_COORD_FLOAT ranges from worldsize.h |
|
// Input : *pVecPos - |
|
//----------------------------------------------------------------------------- |
|
void UTIL_BoundToWorldSize( Vector *pVecPos ) |
|
{ |
|
Assert( pVecPos ); |
|
for ( int i = 0; i < 3; ++i ) |
|
{ |
|
(*pVecPos)[ i ] = clamp( (*pVecPos)[ i ], MIN_COORD_FLOAT, MAX_COORD_FLOAT ); |
|
} |
|
} |
|
|
|
//-------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Return true if ground is fairly level within the given radius around an entity |
|
* Trace 4 vertical hull-quadrants and test their collisions and ground heights and normals |
|
*/ |
|
bool UTIL_IsGroundLevel( float radius, const Vector &position, float hullHeight, int mask, const CBaseEntity *ignore, bool debugTraces ) |
|
{ |
|
const int subdivisions = 3; |
|
const int samples = subdivisions * subdivisions; |
|
|
|
// trace down below floor level to detect ledges and overhangs |
|
trace_t result[ samples ]; |
|
float size = 2.0f * radius / subdivisions; |
|
|
|
int i, j, s = 0; |
|
float x, y = -radius; |
|
for( j=0; j<subdivisions; ++j ) |
|
{ |
|
x = -radius; |
|
for( int i=0; i<subdivisions; ++i ) |
|
{ |
|
UTIL_TraceHull( position + Vector( 0, 0, hullHeight ), |
|
position + Vector( 0, 0, -100 ), |
|
Vector( x, y, 0 ), |
|
Vector( x + size, y + size, 10.0f ), |
|
mask, ignore, COLLISION_GROUP_PLAYER_MOVEMENT, |
|
&result[ s ] ); |
|
|
|
if ( debugTraces ) |
|
{ |
|
NDebugOverlay::SweptBox( position + Vector( 0, 0, hullHeight ), |
|
result[ s ].endpos, |
|
Vector( x, y, 0 ), |
|
Vector( x + size, y + size, 10.0f ), |
|
vec3_angle, 255, 255, 0, 255, 5.0f ); |
|
} |
|
|
|
++s; |
|
x += size; |
|
} |
|
|
|
y += size; |
|
} |
|
|
|
// UTIL_TraceHull( GetPosition() + Vector( 0, 0, body->GetCrouchHullHeight() ), GetPosition() + Vector( 0, 0, -100 ), Vector( -radius, -radius, 0 ), Vector( 0, 0, 10.0f ), MASK_ZOMBIESOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &result[0] ); |
|
// UTIL_TraceHull( GetPosition() + Vector( 0, 0, body->GetCrouchHullHeight() ), GetPosition() + Vector( 0, 0, -100 ), Vector( -radius, 0, 0 ), Vector( 0, radius, 10.0f ), MASK_ZOMBIESOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &result[1] ); |
|
// UTIL_TraceHull( GetPosition() + Vector( 0, 0, body->GetCrouchHullHeight() ), GetPosition() + Vector( 0, 0, -100 ), Vector( 0, 0, 0 ), Vector( radius, radius, 10.0f ), MASK_ZOMBIESOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &result[2] ); |
|
// UTIL_TraceHull( GetPosition() + Vector( 0, 0, body->GetCrouchHullHeight() ), GetPosition() + Vector( 0, 0, -100 ), Vector( 0, -radius, 0 ), Vector( radius, 0, 10.0f ), MASK_ZOMBIESOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &result[3] ); |
|
|
|
for( i=0; i<samples; ++i ) |
|
{ |
|
if ( result[i].startsolid ) |
|
{ |
|
return false; |
|
} |
|
|
|
if ( result[i].DidHit() ) |
|
{ |
|
const float flat = 0.9f; |
|
if ( result[i].plane.normal.z < flat ) |
|
{ |
|
// too steep to sit here |
|
return false; |
|
} |
|
|
|
const float maxZDelta = 10.0f; |
|
for( j = 0; j<samples; ++j ) |
|
{ |
|
if ( i != j && abs( result[i].endpos.z - result[j].endpos.z ) > maxZDelta ) |
|
{ |
|
return false; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
//============================================================================= |
|
// |
|
// Tests! |
|
// |
|
|
|
#define NUM_KDTREE_TESTS 2500 |
|
#define NUM_KDTREE_ENTITY_SIZE 256 |
|
|
|
void CC_KDTreeTest( const CCommand &args ) |
|
{ |
|
Msg( "Testing kd-tree entity queries." ); |
|
|
|
// Get the testing spot. |
|
// CBaseEntity *pSpot = gEntList.FindEntityByClassname( NULL, "info_player_start" ); |
|
// Vector vecStart = pSpot->GetAbsOrigin(); |
|
|
|
CBasePlayer *pPlayer = static_cast<CBasePlayer*>( UTIL_GetLocalPlayer() ); |
|
Vector vecStart = pPlayer->GetAbsOrigin(); |
|
|
|
static Vector *vecTargets = NULL; |
|
static bool bFirst = true; |
|
|
|
// Generate the targets - rays (1K long). |
|
if ( bFirst ) |
|
{ |
|
vecTargets = new Vector [NUM_KDTREE_TESTS]; |
|
double flRadius = 0; |
|
double flTheta = 0; |
|
double flPhi = 0; |
|
for ( int i = 0; i < NUM_KDTREE_TESTS; ++i ) |
|
{ |
|
flRadius += NUM_KDTREE_TESTS * 123.123; |
|
flRadius = fmod( flRadius, 128.0 ); |
|
flRadius = fabs( flRadius ); |
|
|
|
flTheta += NUM_KDTREE_TESTS * 76.76; |
|
flTheta = fmod( flTheta, (double) DEG2RAD( 360 ) ); |
|
flTheta = fabs( flTheta ); |
|
|
|
flPhi += NUM_KDTREE_TESTS * 1997.99; |
|
flPhi = fmod( flPhi, (double) DEG2RAD( 180 ) ); |
|
flPhi = fabs( flPhi ); |
|
|
|
float st, ct, sp, cp; |
|
SinCos( flTheta, &st, &ct ); |
|
SinCos( flPhi, &sp, &cp ); |
|
|
|
vecTargets[i].x = flRadius * ct * sp; |
|
vecTargets[i].y = flRadius * st * sp; |
|
vecTargets[i].z = flRadius * cp; |
|
|
|
// Make the trace 1024 units long. |
|
Vector vecDir = vecTargets[i] - vecStart; |
|
VectorNormalize( vecDir ); |
|
vecTargets[i] = vecStart + vecDir * 1024; |
|
} |
|
|
|
bFirst = false; |
|
} |
|
|
|
int nTestType = 0; |
|
if ( args.ArgC() >= 2 ) |
|
{ |
|
nTestType = atoi( args[ 1 ] ); |
|
} |
|
|
|
vtune( true ); |
|
|
|
#ifdef VPROF_ENABLED |
|
g_VProfCurrentProfile.Resume(); |
|
g_VProfCurrentProfile.Start(); |
|
g_VProfCurrentProfile.Reset(); |
|
g_VProfCurrentProfile.MarkFrame(); |
|
#endif |
|
|
|
switch ( nTestType ) |
|
{ |
|
case 0: |
|
{ |
|
VPROF( "TraceTotal" ); |
|
|
|
trace_t trace; |
|
for ( int iTest = 0; iTest < NUM_KDTREE_TESTS; ++iTest ) |
|
{ |
|
UTIL_TraceLine( vecStart, vecTargets[iTest], MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &trace ); |
|
} |
|
break; |
|
} |
|
case 1: |
|
{ |
|
VPROF( "TraceTotal" ); |
|
|
|
trace_t trace; |
|
for ( int iTest = 0; iTest < NUM_KDTREE_TESTS; ++iTest ) |
|
{ |
|
UTIL_TraceHull( vecStart, vecTargets[iTest], VEC_HULL_MIN, VEC_HULL_MAX, MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &trace ); |
|
} |
|
break; |
|
} |
|
case 2: |
|
{ |
|
Vector vecMins[NUM_KDTREE_TESTS]; |
|
Vector vecMaxs[NUM_KDTREE_TESTS]; |
|
int iTest; |
|
for ( iTest = 0; iTest < NUM_KDTREE_TESTS; ++iTest ) |
|
{ |
|
vecMins[iTest] = vecStart; |
|
vecMaxs[iTest] = vecStart; |
|
for ( int iAxis = 0; iAxis < 3; ++iAxis ) |
|
{ |
|
if ( vecTargets[iTest].x < vecMins[iTest].x ) { vecMins[iTest].x = vecTargets[iTest].x; } |
|
if ( vecTargets[iTest].y < vecMins[iTest].y ) { vecMins[iTest].y = vecTargets[iTest].y; } |
|
if ( vecTargets[iTest].z < vecMins[iTest].z ) { vecMins[iTest].z = vecTargets[iTest].z; } |
|
|
|
if ( vecTargets[iTest].x > vecMaxs[iTest].x ) { vecMaxs[iTest].x = vecTargets[iTest].x; } |
|
if ( vecTargets[iTest].y > vecMaxs[iTest].y ) { vecMaxs[iTest].y = vecTargets[iTest].y; } |
|
if ( vecTargets[iTest].z > vecMaxs[iTest].z ) { vecMaxs[iTest].z = vecTargets[iTest].z; } |
|
} |
|
} |
|
|
|
|
|
VPROF( "TraceTotal" ); |
|
|
|
int nCount = 0; |
|
|
|
Vector vecDelta; |
|
trace_t trace; |
|
CBaseEntity *pList[1024]; |
|
for ( iTest = 0; iTest < NUM_KDTREE_TESTS; ++iTest ) |
|
{ |
|
nCount += UTIL_EntitiesInBox( pList, 1024, vecMins[iTest], vecMaxs[iTest], 0 ); |
|
} |
|
|
|
Msg( "Count = %d\n", nCount ); |
|
break; |
|
} |
|
case 3: |
|
{ |
|
Vector vecDelta; |
|
float flRadius[NUM_KDTREE_TESTS]; |
|
int iTest; |
|
for ( iTest = 0; iTest < NUM_KDTREE_TESTS; ++iTest ) |
|
{ |
|
VectorSubtract( vecTargets[iTest], vecStart, vecDelta ); |
|
flRadius[iTest] = vecDelta.Length() * 0.5f; |
|
} |
|
|
|
VPROF( "TraceTotal" ); |
|
|
|
int nCount = 0; |
|
|
|
trace_t trace; |
|
CBaseEntity *pList[1024]; |
|
for ( iTest = 0; iTest < NUM_KDTREE_TESTS; ++iTest ) |
|
{ |
|
nCount += UTIL_EntitiesInSphere( pList, 1024, vecStart, flRadius[iTest], 0 ); |
|
} |
|
|
|
Msg( "Count = %d\n", nCount ); |
|
break; |
|
} |
|
default: |
|
{ |
|
break; |
|
} |
|
} |
|
|
|
#ifdef VPROF_ENABLED |
|
g_VProfCurrentProfile.MarkFrame(); |
|
g_VProfCurrentProfile.Pause(); |
|
g_VProfCurrentProfile.OutputReport( VPRT_FULL ); |
|
#endif |
|
|
|
vtune( false ); |
|
} |
|
|
|
static ConCommand kdtree_test( "kdtree_test", CC_KDTreeTest, "Tests spatial partition for entities queries.", FCVAR_CHEAT ); |
|
|
|
void CC_VoxelTreeView( void ) |
|
{ |
|
Msg( "VoxelTreeView\n" ); |
|
partition->RenderAllObjectsInTree( 10.0f ); |
|
} |
|
|
|
static ConCommand voxeltree_view( "voxeltree_view", CC_VoxelTreeView, "View entities in the voxel-tree.", FCVAR_CHEAT ); |
|
|
|
void CC_VoxelTreePlayerView( void ) |
|
{ |
|
Msg( "VoxelTreePlayerView\n" ); |
|
|
|
CBasePlayer *pPlayer = static_cast<CBasePlayer*>( UTIL_GetLocalPlayer() ); |
|
Vector vecStart = pPlayer->GetAbsOrigin(); |
|
partition->RenderObjectsInPlayerLeafs( vecStart - VEC_HULL_MIN, vecStart + VEC_HULL_MAX, 3.0f ); |
|
} |
|
|
|
static ConCommand voxeltree_playerview( "voxeltree_playerview", CC_VoxelTreePlayerView, "View entities in the voxel-tree at the player position.", FCVAR_CHEAT ); |
|
|
|
void CC_VoxelTreeBox( const CCommand &args ) |
|
{ |
|
Vector vecMin, vecMax; |
|
if ( args.ArgC() >= 6 ) |
|
{ |
|
vecMin.x = atof( args[ 1 ] ); |
|
vecMin.y = atof( args[ 2 ] ); |
|
vecMin.z = atof( args[ 3 ] ); |
|
|
|
vecMax.x = atof( args[ 4 ] ); |
|
vecMax.y = atof( args[ 5 ] ); |
|
vecMax.z = atof( args[ 6 ] ); |
|
} |
|
else |
|
{ |
|
return; |
|
} |
|
|
|
float flTime = 10.0f; |
|
|
|
Vector vecPoints[8]; |
|
vecPoints[0].Init( vecMin.x, vecMin.y, vecMin.z ); |
|
vecPoints[1].Init( vecMin.x, vecMax.y, vecMin.z ); |
|
vecPoints[2].Init( vecMax.x, vecMax.y, vecMin.z ); |
|
vecPoints[3].Init( vecMax.x, vecMin.y, vecMin.z ); |
|
vecPoints[4].Init( vecMin.x, vecMin.y, vecMax.z ); |
|
vecPoints[5].Init( vecMin.x, vecMax.y, vecMax.z ); |
|
vecPoints[6].Init( vecMax.x, vecMax.y, vecMax.z ); |
|
vecPoints[7].Init( vecMax.x, vecMin.y, vecMax.z ); |
|
|
|
debugoverlay->AddLineOverlay( vecPoints[0], vecPoints[1], 255, 0, 0, true, flTime ); |
|
debugoverlay->AddLineOverlay( vecPoints[1], vecPoints[2], 255, 0, 0, true, flTime ); |
|
debugoverlay->AddLineOverlay( vecPoints[2], vecPoints[3], 255, 0, 0, true, flTime ); |
|
debugoverlay->AddLineOverlay( vecPoints[3], vecPoints[0], 255, 0, 0, true, flTime ); |
|
|
|
debugoverlay->AddLineOverlay( vecPoints[4], vecPoints[5], 255, 0, 0, true, flTime ); |
|
debugoverlay->AddLineOverlay( vecPoints[5], vecPoints[6], 255, 0, 0, true, flTime ); |
|
debugoverlay->AddLineOverlay( vecPoints[6], vecPoints[7], 255, 0, 0, true, flTime ); |
|
debugoverlay->AddLineOverlay( vecPoints[7], vecPoints[4], 255, 0, 0, true, flTime ); |
|
|
|
debugoverlay->AddLineOverlay( vecPoints[0], vecPoints[4], 255, 0, 0, true, flTime ); |
|
debugoverlay->AddLineOverlay( vecPoints[3], vecPoints[7], 255, 0, 0, true, flTime ); |
|
debugoverlay->AddLineOverlay( vecPoints[1], vecPoints[5], 255, 0, 0, true, flTime ); |
|
debugoverlay->AddLineOverlay( vecPoints[2], vecPoints[6], 255, 0, 0, true, flTime ); |
|
|
|
Msg( "VoxelTreeBox - (%f %f %f) to (%f %f %f)\n", vecMin.x, vecMin.y, vecMin.z, vecMax.x, vecMax.y, vecMax.z ); |
|
partition->RenderObjectsInBox( vecMin, vecMax, flTime ); |
|
} |
|
|
|
static ConCommand voxeltree_box( "voxeltree_box", CC_VoxelTreeBox, "View entities in the voxel-tree inside box <Vector(min), Vector(max)>.", FCVAR_CHEAT ); |
|
|
|
void CC_VoxelTreeSphere( const CCommand &args ) |
|
{ |
|
Vector vecCenter; |
|
float flRadius; |
|
if ( args.ArgC() >= 4 ) |
|
{ |
|
vecCenter.x = atof( args[ 1 ] ); |
|
vecCenter.y = atof( args[ 2 ] ); |
|
vecCenter.z = atof( args[ 3 ] ); |
|
|
|
flRadius = atof( args[ 3 ] ); |
|
} |
|
else |
|
{ |
|
return; |
|
} |
|
|
|
float flTime = 3.0f; |
|
|
|
Vector vecMin, vecMax; |
|
vecMin.Init( vecCenter.x - flRadius, vecCenter.y - flRadius, vecCenter.z - flRadius ); |
|
vecMax.Init( vecCenter.x + flRadius, vecCenter.y + flRadius, vecCenter.z + flRadius ); |
|
|
|
Vector vecPoints[8]; |
|
vecPoints[0].Init( vecMin.x, vecMin.y, vecMin.z ); |
|
vecPoints[1].Init( vecMin.x, vecMax.y, vecMin.z ); |
|
vecPoints[2].Init( vecMax.x, vecMax.y, vecMin.z ); |
|
vecPoints[3].Init( vecMax.x, vecMin.y, vecMin.z ); |
|
vecPoints[4].Init( vecMin.x, vecMin.y, vecMax.z ); |
|
vecPoints[5].Init( vecMin.x, vecMax.y, vecMax.z ); |
|
vecPoints[6].Init( vecMax.x, vecMax.y, vecMax.z ); |
|
vecPoints[7].Init( vecMax.x, vecMin.y, vecMax.z ); |
|
|
|
debugoverlay->AddLineOverlay( vecPoints[0], vecPoints[1], 255, 0, 0, true, flTime ); |
|
debugoverlay->AddLineOverlay( vecPoints[1], vecPoints[2], 255, 0, 0, true, flTime ); |
|
debugoverlay->AddLineOverlay( vecPoints[2], vecPoints[3], 255, 0, 0, true, flTime ); |
|
debugoverlay->AddLineOverlay( vecPoints[3], vecPoints[0], 255, 0, 0, true, flTime ); |
|
|
|
debugoverlay->AddLineOverlay( vecPoints[4], vecPoints[5], 255, 0, 0, true, flTime ); |
|
debugoverlay->AddLineOverlay( vecPoints[5], vecPoints[6], 255, 0, 0, true, flTime ); |
|
debugoverlay->AddLineOverlay( vecPoints[6], vecPoints[7], 255, 0, 0, true, flTime ); |
|
debugoverlay->AddLineOverlay( vecPoints[7], vecPoints[4], 255, 0, 0, true, flTime ); |
|
|
|
debugoverlay->AddLineOverlay( vecPoints[0], vecPoints[4], 255, 0, 0, true, flTime ); |
|
debugoverlay->AddLineOverlay( vecPoints[3], vecPoints[7], 255, 0, 0, true, flTime ); |
|
debugoverlay->AddLineOverlay( vecPoints[1], vecPoints[5], 255, 0, 0, true, flTime ); |
|
debugoverlay->AddLineOverlay( vecPoints[2], vecPoints[6], 255, 0, 0, true, flTime ); |
|
|
|
Msg( "VoxelTreeSphere - (%f %f %f), %f\n", vecCenter.x, vecCenter.y, vecCenter.z, flRadius ); |
|
partition->RenderObjectsInSphere( vecCenter, flRadius, flTime ); |
|
} |
|
|
|
static ConCommand voxeltree_sphere( "voxeltree_sphere", CC_VoxelTreeSphere, "View entities in the voxel-tree inside sphere <Vector(center), float(radius)>.", FCVAR_CHEAT ); |
|
|
|
|
|
|
|
#define NUM_COLLISION_TESTS 2500 |
|
void CC_CollisionTest( const CCommand &args ) |
|
{ |
|
if ( !physenv ) |
|
return; |
|
|
|
Msg( "Testing collision system\n" ); |
|
partition->ReportStats( "" ); |
|
int i; |
|
CBaseEntity *pSpot = gEntList.FindEntityByClassname( NULL, "info_player_start"); |
|
Vector start = pSpot->GetAbsOrigin(); |
|
static Vector *targets = NULL; |
|
static bool first = true; |
|
static float test[2] = {1,1}; |
|
if ( first ) |
|
{ |
|
targets = new Vector[NUM_COLLISION_TESTS]; |
|
float radius = 0; |
|
float theta = 0; |
|
float phi = 0; |
|
for ( i = 0; i < NUM_COLLISION_TESTS; i++ ) |
|
{ |
|
radius += NUM_COLLISION_TESTS * 123.123; |
|
radius = fabs(fmod(radius, 128)); |
|
theta += NUM_COLLISION_TESTS * 76.76; |
|
theta = fabs(fmod(theta, DEG2RAD(360))); |
|
phi += NUM_COLLISION_TESTS * 1997.99; |
|
phi = fabs(fmod(phi, DEG2RAD(180))); |
|
|
|
float st, ct, sp, cp; |
|
SinCos( theta, &st, &ct ); |
|
SinCos( phi, &sp, &cp ); |
|
|
|
targets[i].x = radius * ct * sp; |
|
targets[i].y = radius * st * sp; |
|
targets[i].z = radius * cp; |
|
|
|
// make the trace 1024 units long |
|
Vector dir = targets[i] - start; |
|
VectorNormalize(dir); |
|
targets[i] = start + dir * 1024; |
|
} |
|
first = false; |
|
} |
|
|
|
//Vector results[NUM_COLLISION_TESTS]; |
|
|
|
int testType = 0; |
|
if ( args.ArgC() >= 2 ) |
|
{ |
|
testType = atoi(args[1]); |
|
} |
|
float duration = 0; |
|
Vector size[2]; |
|
size[0].Init(0,0,0); |
|
size[1].Init(16,16,16); |
|
unsigned int dots = 0; |
|
int nMask = MASK_ALL & ~(CONTENTS_MONSTER | CONTENTS_HITBOX ); |
|
for ( int j = 0; j < 2; j++ ) |
|
{ |
|
float startTime = Plat_FloatTime(); |
|
if ( testType == 1 ) |
|
{ |
|
trace_t tr; |
|
for ( i = 0; i < NUM_COLLISION_TESTS; i++ ) |
|
{ |
|
UTIL_TraceHull( start, targets[i], -size[1], size[1], nMask, NULL, COLLISION_GROUP_NONE, &tr ); |
|
} |
|
} |
|
else |
|
{ |
|
testType = 0; |
|
trace_t tr; |
|
|
|
for ( i = 0; i < NUM_COLLISION_TESTS; i++ ) |
|
{ |
|
if ( i == 0 ) |
|
{ |
|
partition->RenderLeafsForRayTraceStart( 10.0f ); |
|
} |
|
|
|
UTIL_TraceLine( start, targets[i], nMask, NULL, COLLISION_GROUP_NONE, &tr ); |
|
|
|
if ( i == 0 ) |
|
{ |
|
partition->RenderLeafsForRayTraceEnd( ); |
|
} |
|
} |
|
} |
|
|
|
duration += Plat_FloatTime() - startTime; |
|
} |
|
test[testType] = duration; |
|
Msg("%d collisions in %.2f ms (%u dots)\n", NUM_COLLISION_TESTS, duration*1000, dots ); |
|
partition->ReportStats( "" ); |
|
#if 1 |
|
int red = 255, green = 0, blue = 0; |
|
for ( i = 0; i < 1 /*NUM_COLLISION_TESTS*/; i++ ) |
|
{ |
|
NDebugOverlay::Line( start, targets[i], red, green, blue, false, 2 ); |
|
} |
|
#endif |
|
} |
|
static ConCommand collision_test("collision_test", CC_CollisionTest, "Tests collision system", FCVAR_CHEAT ); |
|
|
|
|
|
|
|
|
|
|