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.
3518 lines
101 KiB
3518 lines
101 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"
|
|
#include "usermessages.h"
|
|
|
|
#ifdef PORTAL
|
|
#include "portal_base2d_shared.h"
|
|
#endif
|
|
|
|
#include "CegClientWrapper.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( "%s", 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()
|
|
{
|
|
if ( !UTIL_IsCommandIssuedByServerAdmin() )
|
|
return;
|
|
|
|
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)" )
|
|
{
|
|
if ( !UTIL_IsCommandIssuedByServerAdmin() )
|
|
return;
|
|
|
|
((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 )
|
|
{
|
|
#ifdef PORTAL //make sure entities are in the primary physics environment for the portal mod, this code should be safe even if the entity is in neither extra environment
|
|
CPortalSimulator::Pre_UTIL_Remove( pBaseEnt );
|
|
#endif
|
|
g_bReceivedChainedUpdateOnRemove = false;
|
|
pBaseEnt->UpdateOnRemove();
|
|
|
|
Assert( g_bReceivedChainedUpdateOnRemove );
|
|
|
|
// clear oldObj targetname / other flags now
|
|
pBaseEnt->SetName( NULL_STRING );
|
|
|
|
#ifdef PORTAL
|
|
CPortalSimulator::Post_UTIL_Remove( pBaseEnt );
|
|
#endif
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#ifdef PORTAL //make sure entities are in the primary physics environment for the portal mod, this code should be safe even if the entity is in neither extra environment
|
|
CPortalSimulator::Pre_UTIL_Remove( oldObj );
|
|
#endif
|
|
|
|
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;
|
|
|
|
#ifdef PORTAL
|
|
CPortalSimulator::Post_UTIL_Remove( oldObj );
|
|
#endif
|
|
}
|
|
|
|
|
|
// 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;
|
|
}
|
|
|
|
CBasePlayer* UTIL_PlayerByAccountID( AccountID_t accountID )
|
|
{
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
|
|
|
|
if ( !pPlayer )
|
|
continue;
|
|
|
|
if ( !pPlayer->IsConnected( ) )
|
|
continue;
|
|
|
|
if ( engine->GetClientSteamID( pPlayer->edict( ) ) &&
|
|
( engine->GetClientSteamID( pPlayer->edict( ) )->GetAccountID( ) == accountID ) )
|
|
{
|
|
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() )
|
|
{
|
|
if ( issuingPlayerIndex > 0 )
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if ( issuingPlayerIndex != engine->GetLocalClientIndex() ) // comparing 1-based client index, also called Player Index in some places
|
|
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" );
|
|
const char * szUpdateRate = engine->GetClientConVarValue( playerIndex, "cl_updaterate" );
|
|
|
|
static const ConVar *pSvClientCmdrateDifference = g_pCVar->FindVar( "sv_client_cmdrate_difference" );
|
|
static const ConVar *pMinUpdateRate = g_pCVar->FindVar( "sv_minupdaterate" );
|
|
static const ConVar *pMaxUpdateRate = g_pCVar->FindVar( "sv_maxupdaterate" );
|
|
|
|
float flUpdateRateValue = Q_atof( szUpdateRate );
|
|
if ( !player->IsHLTV() )
|
|
{
|
|
if ( pMinUpdateRate && pMaxUpdateRate )
|
|
flUpdateRateValue = clamp( flUpdateRateValue, pMinUpdateRate->GetFloat(), pMaxUpdateRate->GetFloat() );
|
|
}
|
|
|
|
float flCmdRateValue = Q_atof( szCmdRate );
|
|
if ( player->IsHLTV() )
|
|
{
|
|
flCmdRateValue = flUpdateRateValue;
|
|
}
|
|
else
|
|
{
|
|
// First, we make it stay within range of cl_updaterate.
|
|
if ( pSvClientCmdrateDifference )
|
|
{
|
|
float diff = flCmdRateValue - flUpdateRateValue;
|
|
float diffMaxCap = pSvClientCmdrateDifference->GetFloat();
|
|
if ( fabs( diff ) > diffMaxCap )
|
|
{
|
|
if ( diff > 0 )
|
|
flCmdRateValue = flUpdateRateValue + diffMaxCap;
|
|
else
|
|
flCmdRateValue = flUpdateRateValue - diffMaxCap;
|
|
}
|
|
}
|
|
static const ConVar *pMinCmdRate = g_pCVar->FindVar( "sv_mincmdrate" );
|
|
static const ConVar *pMaxCmdRate = g_pCVar->FindVar( "sv_maxcmdrate" );
|
|
if ( pMinUpdateRate && pMaxUpdateRate )
|
|
flCmdRateValue = clamp( flCmdRateValue, pMinCmdRate->GetFloat(), pMaxCmdRate->GetFloat() );
|
|
}
|
|
|
|
latency -= (0.5f/flCmdRateValue) + 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();
|
|
CCSUsrMsg_Shake msg;
|
|
msg.set_command( eCommand ); // shake command (SHAKE_START, STOP, FREQUENCY, AMPLITUDE)
|
|
msg.set_local_amplitude( localAmplitude ); // shake magnitude/amplitude
|
|
msg.set_frequency( frequency ); // shake noise frequency
|
|
msg.set_duration( duration ); // shake lasts this long
|
|
|
|
SendUserMessage( user, CS_UM_Shake, msg );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 )
|
|
{
|
|
// Gurjeets - Not used in csgo
|
|
//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();
|
|
|
|
CCSUsrMsg_Fade msg;
|
|
|
|
msg.set_duration( fade.duration ); // fade lasts this long
|
|
msg.set_hold_time( fade.holdTime ); // fade lasts this long
|
|
msg.set_flags( fade.fadeFlags ); // fade type (in / out)
|
|
msg.mutable_clr()->set_r( fade.r ); // fade red
|
|
msg.mutable_clr()->set_g( fade.g ); // fade green
|
|
msg.mutable_clr()->set_b( fade.b ); // fade blue
|
|
msg.mutable_clr()->set_a( fade.a ); // fade blue
|
|
|
|
SendUserMessage( user, CS_UM_Fade, msg );
|
|
}
|
|
|
|
|
|
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();
|
|
|
|
CCSUsrMsg_HudMsg msg;
|
|
msg.set_channel( textparms.channel & 0xFF );
|
|
msg.mutable_pos()->set_x( textparms.x );
|
|
msg.mutable_pos()->set_y( textparms.y );
|
|
msg.mutable_clr1()->set_r( textparms.r1 );
|
|
msg.mutable_clr1()->set_g( textparms.g1 );
|
|
msg.mutable_clr1()->set_b( textparms.b1 );
|
|
msg.mutable_clr1()->set_a( textparms.a1 );
|
|
msg.mutable_clr2()->set_r( textparms.r2 );
|
|
msg.mutable_clr2()->set_g( textparms.g2 );
|
|
msg.mutable_clr2()->set_b( textparms.b2 );
|
|
msg.mutable_clr2()->set_a( textparms.a2 );
|
|
msg.set_effect ( textparms.effect );
|
|
msg.set_fade_in_time( textparms.fadeinTime );
|
|
msg.set_fade_out_time( textparms.fadeoutTime );
|
|
msg.set_hold_time( textparms.holdTime );
|
|
msg.set_fx_time( textparms.fxTime );
|
|
msg.set_text( pMessage );
|
|
SendUserMessage( filter, CS_UM_HudMsg, msg );
|
|
}
|
|
|
|
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();
|
|
|
|
CCSUsrMsg_KeyHintText msg;
|
|
msg.add_hints( pMessage );
|
|
SendUserMessage( user, CS_UM_KeyHintText, msg );
|
|
}
|
|
|
|
void UTIL_ClientPrintFilter( IRecipientFilter& filter, int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 )
|
|
{
|
|
CCSUsrMsg_TextMsg msg;
|
|
|
|
msg.set_msg_dst( msg_dest );
|
|
msg.add_params( msg_name );
|
|
|
|
if ( param1 )
|
|
msg.add_params( param1 );
|
|
else
|
|
msg.add_params( "" );
|
|
|
|
if ( param2 )
|
|
msg.add_params( param2 );
|
|
else
|
|
msg.add_params( "" );
|
|
|
|
if ( param3 )
|
|
msg.add_params( param3 );
|
|
else
|
|
msg.add_params( "" );
|
|
|
|
if ( param4 )
|
|
msg.add_params( param4 );
|
|
else
|
|
msg.add_params( "" );
|
|
|
|
SendUserMessage( filter, CS_UM_TextMsg, msg );
|
|
}
|
|
|
|
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, EUtilSayTextMessageType_t eMessageType )
|
|
{
|
|
CCSUsrMsg_SayText msg;
|
|
|
|
if ( pPlayer )
|
|
{
|
|
msg.set_ent_idx( pPlayer->entindex() );
|
|
}
|
|
else
|
|
{
|
|
msg.set_ent_idx( 0 ); // world, dedicated server says
|
|
}
|
|
msg.set_text( pText );
|
|
msg.set_chat( ( eMessageType == kEUtilSayTextMessageType_TeamonlyChat ) || ( eMessageType == kEUtilSayTextMessageType_AllChat ) );
|
|
msg.set_textallchat( eMessageType == kEUtilSayTextMessageType_AllChat );
|
|
|
|
SendUserMessage( filter, CS_UM_SayText, msg );
|
|
}
|
|
|
|
void UTIL_SayText2Filter( IRecipientFilter& filter, CBasePlayer *pEntity, EUtilSayTextMessageType_t eMessageType, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 )
|
|
{
|
|
CCSUsrMsg_SayText2 msg;
|
|
|
|
if ( pEntity )
|
|
{
|
|
msg.set_ent_idx( pEntity->entindex() );
|
|
}
|
|
else
|
|
{
|
|
msg.set_ent_idx( 0 ); // world, dedicated server says
|
|
}
|
|
|
|
msg.set_chat( ( eMessageType == kEUtilSayTextMessageType_TeamonlyChat ) || ( eMessageType == kEUtilSayTextMessageType_AllChat ) );
|
|
msg.set_textallchat( eMessageType == kEUtilSayTextMessageType_AllChat );
|
|
msg.set_msg_name( msg_name );
|
|
|
|
if ( param1 )
|
|
msg.add_params( param1 );
|
|
else
|
|
msg.add_params( "" );
|
|
|
|
if ( param2 )
|
|
msg.add_params( param2 );
|
|
else
|
|
msg.add_params( "" );
|
|
|
|
if ( param3 )
|
|
msg.add_params( param3 );
|
|
else
|
|
msg.add_params( "" );
|
|
|
|
if ( param4 )
|
|
msg.add_params( param4 );
|
|
else
|
|
msg.add_params( "" );
|
|
|
|
SendUserMessage( filter, CS_UM_SayText2, msg );
|
|
}
|
|
|
|
void UTIL_SayText( const char *pText, CBasePlayer *pToPlayer )
|
|
{
|
|
if ( !pToPlayer->IsNetClient() )
|
|
return;
|
|
|
|
CSingleUserRecipientFilter user( pToPlayer );
|
|
user.MakeReliable();
|
|
|
|
UTIL_SayTextFilter( user, pText, pToPlayer, kEUtilSayTextMessageType_Default );
|
|
}
|
|
|
|
void UTIL_SayTextAll( const char *pText, CBasePlayer *pPlayer, EUtilSayTextMessageType_t eMessageType )
|
|
{
|
|
CReliableBroadcastRecipientFilter filter;
|
|
UTIL_SayTextFilter( filter, pText, pPlayer, eMessageType );
|
|
}
|
|
|
|
void UTIL_ShowMessage( const char *pString, CBasePlayer *pPlayer )
|
|
{
|
|
CRecipientFilter filter;
|
|
|
|
if ( pPlayer )
|
|
{
|
|
filter.AddRecipient( pPlayer );
|
|
}
|
|
else
|
|
{
|
|
filter.AddAllPlayers();
|
|
}
|
|
|
|
filter.MakeReliable();
|
|
|
|
CCSUsrMsg_HudText msg;
|
|
msg.set_text( pString );
|
|
SendUserMessage( filter, CS_UM_HudText, msg );
|
|
}
|
|
|
|
|
|
void UTIL_ShowMessageAll( const char *pString )
|
|
{
|
|
UTIL_ShowMessage( pString, NULL );
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
// HudMessagePanel helper
|
|
void UTIL_MessageTextAll( const char *text, Color color )
|
|
{
|
|
// Gurjeets - Not used in CSGO
|
|
//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 )
|
|
{
|
|
// Gurjeets - Not used in CSGO
|
|
//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 )
|
|
{
|
|
// Gurjeets - Not used in CSGO
|
|
//CReliableBroadcastRecipientFilter filter;
|
|
//UserMessageBegin( filter, "MessageText" );
|
|
//MessageEnd();
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
// HudMessagePanel helper
|
|
void UTIL_ResetMessageText( CBasePlayer *player )
|
|
{
|
|
// Gurjeets - Not used in CSGO
|
|
//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;
|
|
}
|
|
}
|
|
}
|
|
|
|
const 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, Vector const &right, int playernum )
|
|
{
|
|
if (pTrace->fraction == 1.0)
|
|
return;
|
|
|
|
CBroadcastRecipientFilter filter;
|
|
filter.SetIgnorePredictionCull( true ); // need to ignore prediction cull for the person who is spraying
|
|
|
|
te->PlayerDecal( filter, 0.0,
|
|
&pTrace->endpos, &pTrace->startpos, &right, playernum, pTrace->m_pEnt->entindex(), pTrace->hitbox );
|
|
}
|
|
|
|
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];
|
|
}
|
|
}
|
|
|
|
|
|
|
|
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( const 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: 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 );
|
|
|
|
#if defined ( PORTAL )
|
|
// Add in any clusters seen by portals.
|
|
int iPortalCount = CPortal_Base2D_Shared::AllPortals.Count();
|
|
if( iPortalCount > 0 )
|
|
{
|
|
CPortal_Base2D **pPortals = CPortal_Base2D_Shared::AllPortals.Base();
|
|
for( int i = 0; i != iPortalCount; ++i )
|
|
{
|
|
CPortal_Base2D *pPortal = pPortals[i];
|
|
if ( pPortal && pPortal->IsActivedAndLinked() )
|
|
{
|
|
// add only portals visible in the new cluster the client check ent just moved into.
|
|
if ( pPortal->NetworkProp()->IsInPVS( ent, g_CheckClient.m_checkPVS , sizeof( g_CheckClient.m_checkPVS ) ) )
|
|
{
|
|
// add what it can see to the check pvs
|
|
AddPortalVisibilityToPVS( pPortal, sizeof( g_CheckClient.m_checkPVS ), g_CheckClient.m_checkPVS );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // PORTAL
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#if defined ( PORTAL )
|
|
void UTIL_SetClientCheckPVS( edict_t *pClient, const unsigned char *pvs, int pvssize )
|
|
{
|
|
Assert( (pClient == UTIL_GetCurrentCheckClient()) || g_pGameRules->IsMultiplayer() ); //double check that the code is nondivergent from existing behavior in single player
|
|
if( pClient == UTIL_GetCurrentCheckClient() )
|
|
{
|
|
// this sets whatever client check is the current g_ClientCheck
|
|
Assert( pvssize <= sizeof(g_CheckClient.m_checkPVS) );
|
|
|
|
memcpy( g_CheckClient.m_checkPVS, pvs, pvssize );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
void UTIL_SendClientCommandKVToPlayer( KeyValues *pKV, CBasePlayer *pPlayer )
|
|
{
|
|
KeyValues::AutoDelete autodelete( pKV );
|
|
if ( pPlayer )
|
|
{
|
|
engine->ClientCommandKeyValues( pPlayer->edict(), pKV->MakeCopy() );
|
|
}
|
|
else
|
|
{
|
|
// On server, send to all players
|
|
for (int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
|
|
if ( pPlayer )
|
|
{
|
|
engine->ClientCommandKeyValues( pPlayer->edict(), pKV->MakeCopy() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CEG_NOINLINE void UTIL_RecordAchievementEvent( const char *pszAchievementname, CBasePlayer *pPlayer /*= NULL*/ )
|
|
{
|
|
bool bIsWriteStat = ( strchr( pszAchievementname, '@' ) || strchr( pszAchievementname, '[' ) || strchr( pszAchievementname, '(' ) ); // Achievements cannot have @[( symbols in names - it's a stat
|
|
KeyValues *pKV;
|
|
if ( bIsWriteStat )
|
|
pKV = new KeyValues( "write_stats", pszAchievementname, 1 );
|
|
else
|
|
pKV = new KeyValues( "write_awards", pszAchievementname, 1 );
|
|
|
|
UTIL_SendClientCommandKVToPlayer( pKV, pPlayer );
|
|
}
|
|
|
|
CEG_PROTECT_FUNCTION( UTIL_RecordAchievementEvent );
|
|
|
|
//=============================================================================
|
|
//
|
|
// 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
|
|
}
|
|
|
|
void UTIL_EnsureInstanceBaseline( const char *szClassname )
|
|
{
|
|
CBaseEntity *pEnt = CreateEntityByName( szClassname );
|
|
if ( pEnt )
|
|
{
|
|
engine->EnsureInstanceBaseline( pEnt->entindex() );
|
|
UTIL_RemoveImmediate( pEnt );
|
|
}
|
|
}
|
|
|
|
static ConCommand collision_test("collision_test", CC_CollisionTest, "Tests collision system", FCVAR_CHEAT );
|
|
|
|
|
|
|
|
|