From 39ed87570bdb2f86969d4be821c94b722dc71179 Mon Sep 17 00:00:00 2001 From: Joe Ludwig Date: Wed, 26 Jun 2013 15:22:04 -0700 Subject: First version of the SOurce SDK 2013 --- mp/src/game/server/util.cpp | 3290 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3290 insertions(+) create mode 100644 mp/src/game/server/util.cpp (limited to 'mp/src/game/server/util.cpp') diff --git a/mp/src/game/server/util.cpp b/mp/src/game/server/util.cpp new file mode 100644 index 00000000..8b2365f7 --- /dev/null +++ b/mp/src/game/server/util.cpp @@ -0,0 +1,3290 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Utility code. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "saverestore.h" +#include "globalstate.h" +#include +#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 "cdll_int.h" + +#ifdef PORTAL +#include "PortalSimulation.h" +//#include "Portal_PhysicsEnvironmentMgr.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud +extern short g_sModelIndexBloodDrop; // (in combatweapon.cpp) holds the sprite index for the initial blood +extern short g_sModelIndexBloodSpray; // (in combatweapon.cpp) holds the sprite index for splattered blood + +#ifdef DEBUG +void DBG_AssertFunction( bool fExpr, const char *szExpr, const char *szFile, int szLine, const char *szMessage ) +{ + if (fExpr) + return; + char szOut[512]; + if (szMessage != NULL) + Q_snprintf(szOut,sizeof(szOut), "ASSERT FAILED:\n %s \n(%s@%d)\n%s", szExpr, szFile, szLine, szMessage); + else + Q_snprintf(szOut,sizeof(szOut), "ASSERT FAILED:\n %s \n(%s@%d)\n", szExpr, szFile, szLine); + Warning( szOut); +} +#endif // DEBUG + + +//----------------------------------------------------------------------------- +// Entity creation factory +//----------------------------------------------------------------------------- +class CEntityFactoryDictionary : public IEntityFactoryDictionary +{ +public: + CEntityFactoryDictionary(); + + virtual void InstallFactory( IEntityFactory *pFactory, const char *pClassName ); + virtual IServerNetworkable *Create( const char *pClassName ); + virtual void Destroy( const char *pClassName, IServerNetworkable *pNetworkable ); + virtual const char *GetCannonicalName( const char *pClassName ); + void ReportEntitySizes(); + +private: + IEntityFactory *FindFactory( const char *pClassName ); +public: + CUtlDict< IEntityFactory *, unsigned short > m_Factories; +}; + +//----------------------------------------------------------------------------- +// Singleton accessor +//----------------------------------------------------------------------------- +IEntityFactoryDictionary *EntityFactoryDictionary() +{ + static CEntityFactoryDictionary s_EntityFactory; + return &s_EntityFactory; +} + +void DumpEntityFactories_f() +{ + 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 ) +{ + Assert( FindFactory( pClassName ) == NULL ); + 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() ); + } +} + + +//----------------------------------------------------------------------------- +// class CFlaggedEntitiesEnum +//----------------------------------------------------------------------------- + +CFlaggedEntitiesEnum::CFlaggedEntitiesEnum( CBaseEntity **pList, int listMax, int flagMask ) +{ + m_pList = pList; + m_listMax = listMax; + m_flagMask = flagMask; + m_count = 0; +} + +bool CFlaggedEntitiesEnum::AddToList( CBaseEntity *pEntity ) +{ + if ( m_count >= m_listMax ) + { + AssertMsgOnce( 0, "reached enumerated list limit. Increase limit, decrease radius, or make it so entity flags will work for you" ); + return false; + } + m_pList[m_count] = pEntity; + m_count++; + return true; +} + +IterationRetval_t CFlaggedEntitiesEnum::EnumElement( IHandleEntity *pHandleEntity ) +{ + CBaseEntity *pEntity = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() ); + if ( pEntity ) + { + if ( m_flagMask && !(pEntity->GetFlags() & m_flagMask) ) // Does it meet the criteria? + return ITERATION_CONTINUE; + + if ( !AddToList( pEntity ) ) + return ITERATION_STOP; + } + + return ITERATION_CONTINUE; +} + + +//----------------------------------------------------------------------------- +// 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_EntitiesAlongRay( const Ray_t &ray, CFlaggedEntitiesEnum *pEnum ) +{ + partition->EnumerateElementsAlongRay( PARTITION_ENGINE_NON_STATIC_EDICTS, ray, 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; + +#ifndef HL2MP + // 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; +#endif // HL2MP + + 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( 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; +} + +// +// Return the local player. +// If this is a multiplayer game, return NULL. +// +CBasePlayer *UTIL_GetLocalPlayer( void ) +{ + if ( gpGlobals->maxClients > 1 ) + { + if ( developer.GetBool() ) + { + Assert( !"UTIL_GetLocalPlayer" ); + +#ifdef DEBUG + Warning( "UTIL_GetLocalPlayer() called in multiplayer game.\n" ); +#endif + } + + return NULL; + } + + return UTIL_PlayerByIndex( 1 ); +} + +// +// Get the local player on a listen server - this is for multiplayer use only +// +CBasePlayer *UTIL_GetListenServerHost( void ) +{ + // no "local player" if this is a dedicated server or a single player game + if (engine->IsDedicatedServer()) + { + Assert( !"UTIL_GetListenServerHost" ); + Warning( "UTIL_GetListenServerHost() called from a dedicated server or single-player game.\n" ); + return NULL; + } + + return UTIL_PlayerByIndex( 1 ); +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Returns true if the command was issued by the listenserver host, or by the dedicated server, via rcon or the server console. + * This is valid during ConCommand execution. + */ +bool UTIL_IsCommandIssuedByServerAdmin( void ) +{ + int issuingPlayerIndex = UTIL_GetCommandClientIndex(); + + if ( engine->IsDedicatedServer() && issuingPlayerIndex > 0 ) + return false; + +#if defined( REPLAY_ENABLED ) + // entity 1 is replay? + player_info_t pi; + bool bPlayerIsReplay = engine->GetPlayerInfo( 1, &pi ) && pi.isreplay; +#else + bool bPlayerIsReplay = false; +#endif + + if ( bPlayerIsReplay ) + { + if ( issuingPlayerIndex > 2 ) + return false; + } + else if ( issuingPlayerIndex > 1 ) + { + return false; + } + + return true; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Returns a CBaseEntity pointer by entindex. Index is 1 based. + */ +CBaseEntity *UTIL_EntityByIndex( int entityIndex ) +{ + CBaseEntity *entity = NULL; + + if ( entityIndex > 0 ) + { + edict_t *edict = INDEXENT( entityIndex ); + if ( edict && !edict->IsFree() ) + { + entity = GetContainingEntity( edict ); + } + } + + return entity; +} + + +int ENTINDEX( CBaseEntity *pEnt ) +{ + // This works just like ENTINDEX for edicts. + if ( pEnt ) + return pEnt->entindex(); + else + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : playerIndex - +// ping - +// packetloss - +//----------------------------------------------------------------------------- +void UTIL_GetPlayerConnectionInfo( int playerIndex, int& ping, int &packetloss ) +{ + CBasePlayer *player = UTIL_PlayerByIndex( playerIndex ); + + INetChannelInfo *nci = engine->GetPlayerNetInfo(playerIndex); + + if ( nci && player && !player->IsBot() ) + { + float latency = nci->GetAvgLatency( FLOW_OUTGOING ); // in seconds + + // that should be the correct latency, we assume that cmdrate is higher + // then updaterate, what is the case for default settings + const char * szCmdRate = engine->GetClientConVarValue( playerIndex, "cl_cmdrate" ); + + int nCmdRate = MAX( 1, Q_atoi( szCmdRate ) ); + latency -= (0.5f/nCmdRate) + TICKS_TO_TIME( 1.0f ); // correct latency + + // in GoldSrc we had a different, not fixed tickrate. so we have to adjust + // Source pings by half a tick to match the old GoldSrc pings. + latency -= TICKS_TO_TIME( 0.5f ); + + ping = latency * 1000.0f; // as msecs + ping = clamp( ping, 5, 1000 ); // set bounds, dont show pings under 5 msecs + + packetloss = 100.0f * nci->GetAvgLoss( FLOW_INCOMING ); // loss in percentage + packetloss = clamp( packetloss, 0, 100 ); + } + else + { + ping = 0; + packetloss = 0; + } +} + +static unsigned short FixedUnsigned16( float value, float scale ) +{ + int output; + + output = value * scale; + if ( output < 0 ) + output = 0; + if ( output > 0xFFFF ) + output = 0xFFFF; + + return (unsigned short)output; +} + + +//----------------------------------------------------------------------------- +// Compute shake amplitude +//----------------------------------------------------------------------------- +inline float ComputeShakeAmplitude( const Vector ¢er, const Vector &shakePt, float amplitude, float radius ) +{ + if ( radius <= 0 ) + return amplitude; + + float localAmplitude = -1; + Vector delta = center - shakePt; + float distance = delta.Length(); + + if ( distance <= radius ) + { + // Make the amplitude fall off over distance + float flPerc = 1.0 - (distance / radius); + localAmplitude = amplitude * flPerc; + } + + return localAmplitude; +} + + +//----------------------------------------------------------------------------- +// Transmits the actual shake event +//----------------------------------------------------------------------------- +inline void TransmitShakeEvent( CBasePlayer *pPlayer, float localAmplitude, float frequency, float duration, ShakeCommand_t eCommand ) +{ + if (( localAmplitude > 0 ) || ( eCommand == SHAKE_STOP )) + { + if ( eCommand == SHAKE_STOP ) + localAmplitude = 0; + + CSingleUserRecipientFilter user( pPlayer ); + user.MakeReliable(); + UserMessageBegin( user, "Shake" ); + WRITE_BYTE( eCommand ); // shake command (SHAKE_START, STOP, FREQUENCY, AMPLITUDE) + WRITE_FLOAT( localAmplitude ); // shake magnitude/amplitude + WRITE_FLOAT( frequency ); // shake noise frequency + WRITE_FLOAT( duration ); // shake lasts this long + MessageEnd(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Shake the screen of all clients within radius. +// radius == 0, shake all clients +// UNDONE: Fix falloff model (disabled)? +// UNDONE: Affect user controls? +// Input : center - Center of screen shake, radius is measured from here. +// amplitude - Amplitude of shake +// frequency - +// duration - duration of shake in seconds. +// radius - Radius of effect, 0 shakes all clients. +// command - One of the following values: +// SHAKE_START - starts the screen shake for all players within the radius +// SHAKE_STOP - stops the screen shake for all players within the radius +// SHAKE_AMPLITUDE - modifies the amplitude of the screen shake +// for all players within the radius +// SHAKE_FREQUENCY - modifies the frequency of the screen shake +// for all players within the radius +// bAirShake - if this is false, then it will only shake players standing on the ground. +//----------------------------------------------------------------------------- +const float MAX_SHAKE_AMPLITUDE = 16.0f; +void UTIL_ScreenShake( const Vector ¢er, float amplitude, float frequency, float duration, float radius, ShakeCommand_t eCommand, bool bAirShake ) +{ + int i; + float localAmplitude; + + if ( amplitude > MAX_SHAKE_AMPLITUDE ) + { + amplitude = MAX_SHAKE_AMPLITUDE; + } + 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 || (!bAirShake && (eCommand == SHAKE_START) && !(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 ); + } +} + + +//----------------------------------------------------------------------------- +// 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()->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 ); + } +} + + +//----------------------------------------------------------------------------- +// 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<IsNetClient() ) + return; + + CSingleUserRecipientFilter user( (CBasePlayer *)pEntity ); + user.MakeReliable(); + + UserMessageBegin( user, "Fade" ); // use the magic #1 for "one client" + WRITE_SHORT( fade.duration ); // fade lasts this long + WRITE_SHORT( fade.holdTime ); // fade lasts this long + WRITE_SHORT( fade.fadeFlags ); // fade type (in / out) + WRITE_BYTE( fade.r ); // fade red + WRITE_BYTE( fade.g ); // fade green + WRITE_BYTE( fade.b ); // fade blue + WRITE_BYTE( fade.a ); // fade blue + MessageEnd(); +} + + +void UTIL_ScreenFadeAll( const color32 &color, float fadeTime, float fadeHold, int flags ) +{ + int i; + ScreenFade_t fade; + + + UTIL_ScreenFadeBuild( fade, color, fadeTime, fadeHold, flags ); + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + UTIL_ScreenFadeWrite( fade, pPlayer ); + } +} + + +void UTIL_ScreenFade( CBaseEntity *pEntity, const color32 &color, float fadeTime, float fadeHold, int flags ) +{ + ScreenFade_t fade; + + UTIL_ScreenFadeBuild( fade, color, fadeTime, fadeHold, flags ); + UTIL_ScreenFadeWrite( fade, pEntity ); +} + + +void UTIL_HudMessage( CBasePlayer *pToPlayer, const hudtextparms_t &textparms, const char *pMessage ) +{ + CRecipientFilter filter; + + if( pToPlayer ) + { + filter.AddRecipient( pToPlayer ); + } + else + { + filter.AddAllPlayers(); + } + + filter.MakeReliable(); + + UserMessageBegin( filter, "HudMsg" ); + WRITE_BYTE ( textparms.channel & 0xFF ); + WRITE_FLOAT( textparms.x ); + WRITE_FLOAT( textparms.y ); + WRITE_BYTE ( textparms.r1 ); + WRITE_BYTE ( textparms.g1 ); + WRITE_BYTE ( textparms.b1 ); + WRITE_BYTE ( textparms.a1 ); + WRITE_BYTE ( textparms.r2 ); + WRITE_BYTE ( textparms.g2 ); + WRITE_BYTE ( textparms.b2 ); + WRITE_BYTE ( textparms.a2 ); + WRITE_BYTE ( textparms.effect ); + WRITE_FLOAT( textparms.fadeinTime ); + WRITE_FLOAT( textparms.fadeoutTime ); + WRITE_FLOAT( textparms.holdTime ); + WRITE_FLOAT( textparms.fxTime ); + WRITE_STRING( pMessage ); + MessageEnd(); +} + +void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage ) +{ + UTIL_HudMessage( NULL, textparms, pMessage ); +} + +void UTIL_HudHintText( CBaseEntity *pEntity, const char *pMessage ) +{ + if ( !pEntity ) + return; + + CSingleUserRecipientFilter user( (CBasePlayer *)pEntity ); + user.MakeReliable(); + UserMessageBegin( user, "KeyHintText" ); + WRITE_BYTE( 1 ); // one string + WRITE_STRING( pMessage ); + MessageEnd(); +} + +void UTIL_ClientPrintFilter( IRecipientFilter& filter, int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) +{ + UserMessageBegin( filter, "TextMsg" ); + WRITE_BYTE( msg_dest ); + WRITE_STRING( msg_name ); + + if ( param1 ) + WRITE_STRING( param1 ); + else + WRITE_STRING( "" ); + + if ( param2 ) + WRITE_STRING( param2 ); + else + WRITE_STRING( "" ); + + if ( param3 ) + WRITE_STRING( param3 ); + else + WRITE_STRING( "" ); + + if ( param4 ) + WRITE_STRING( param4 ); + else + WRITE_STRING( "" ); + + MessageEnd(); +} + +void UTIL_ClientPrintAll( int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) +{ + CReliableBroadcastRecipientFilter filter; + + UTIL_ClientPrintFilter( filter, msg_dest, msg_name, param1, param2, param3, param4 ); +} + +void ClientPrint( CBasePlayer *player, int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) +{ + if ( !player ) + return; + + CSingleUserRecipientFilter user( player ); + user.MakeReliable(); + + UTIL_ClientPrintFilter( user, msg_dest, msg_name, param1, param2, param3, param4 ); +} + +void UTIL_SayTextFilter( IRecipientFilter& filter, const char *pText, CBasePlayer *pPlayer, bool bChat ) +{ + UserMessageBegin( filter, "SayText" ); + if ( pPlayer ) + { + WRITE_BYTE( pPlayer->entindex() ); + } + else + { + WRITE_BYTE( 0 ); // world, dedicated server says + } + WRITE_STRING( pText ); + WRITE_BYTE( bChat ); + MessageEnd(); +} + +void UTIL_SayText2Filter( IRecipientFilter& filter, CBasePlayer *pEntity, bool bChat, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) +{ + UserMessageBegin( filter, "SayText2" ); + if ( pEntity ) + { + WRITE_BYTE( pEntity->entindex() ); + } + else + { + WRITE_BYTE( 0 ); // world, dedicated server says + } + + WRITE_BYTE( bChat ); + + WRITE_STRING( msg_name ); + + if ( param1 ) + WRITE_STRING( param1 ); + else + WRITE_STRING( "" ); + + if ( param2 ) + WRITE_STRING( param2 ); + else + WRITE_STRING( "" ); + + if ( param3 ) + WRITE_STRING( param3 ); + else + WRITE_STRING( "" ); + + if ( param4 ) + WRITE_STRING( param4 ); + else + WRITE_STRING( "" ); + + MessageEnd(); +} + +void UTIL_SayText( const char *pText, CBasePlayer *pToPlayer ) +{ + if ( !pToPlayer->IsNetClient() ) + return; + + CSingleUserRecipientFilter user( pToPlayer ); + user.MakeReliable(); + + UTIL_SayTextFilter( user, pText, pToPlayer, false ); +} + +void UTIL_SayTextAll( const char *pText, CBasePlayer *pPlayer, bool bChat ) +{ + CReliableBroadcastRecipientFilter filter; + UTIL_SayTextFilter( filter, pText, pPlayer, bChat ); +} + +void UTIL_ShowMessage( const char *pString, CBasePlayer *pPlayer ) +{ + CRecipientFilter filter; + + if ( pPlayer ) + { + filter.AddRecipient( pPlayer ); + } + else + { + filter.AddAllPlayers(); + } + + filter.MakeReliable(); + + UserMessageBegin( filter, "HudText" ); + WRITE_STRING( pString ); + MessageEnd(); +} + + +void UTIL_ShowMessageAll( const char *pString ) +{ + UTIL_ShowMessage( pString, NULL ); +} + +// 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() : "" ); + } + } + + 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 == -1 ) + { + Error("%i/%s - %s: UTIL_SetModel: not precached: %s\n", pEntity->entindex(), + STRING( pEntity->GetEntityName() ), + pEntity->GetClassname(), pModelName); + } + + CBaseAnimating *pAnimating = pEntity->GetBaseAnimating(); + if ( pAnimating ) + { + pAnimating->m_nForceBone = 0; + } + + pEntity->SetModelName( AllocPooledString( pModelName ) ); + pEntity->SetModelIndex( i ) ; + SetMinMaxSize(pEntity, vec3_origin, vec3_origin); + pEntity->SetCollisionBoundsFromModel(); +} + + +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, ULONG ulColor, ULONG ulCount ) +{ + Msg( "UTIL_ParticleEffect: Disabled\n" ); +} + +void UTIL_Smoke( const Vector &origin, const float scale, const float framerate ) +{ + g_pEffects->Smoke( origin, g_sModelIndexSmoke, scale, framerate ); +} + +// snaps a vector to the nearest axis vector (if within epsilon) +void UTIL_SnapDirectionToAxis( Vector &direction, float epsilon ) +{ + float proj = 1 - epsilon; + for ( int i = 0; i < 3; i ++ ) + { + if ( fabs(direction[i]) > proj ) + { + // snap to axis unit vector + if ( direction[i] < 0 ) + direction[i] = -1.0f; + else + direction[i] = 1.0f; + direction[(i+1)%3] = 0; + direction[(i+2)%3] = 0; + return; + } + } +} + +char *UTIL_VarArgs( const char *format, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start (argptr, format); + Q_vsnprintf(string, sizeof(string), format,argptr); + va_end (argptr); + + return string; +} + +bool UTIL_IsMasterTriggered(string_t sMaster, CBaseEntity *pActivator) +{ + if (sMaster != NULL_STRING) + { + CBaseEntity *pMaster = gEntList.FindEntityByName( NULL, sMaster, NULL, pActivator ); + + if ( pMaster && (pMaster->ObjectCaps() & FCAP_MASTER) ) + { + return pMaster->IsTriggered( pActivator ); + } + + Warning( "Master was null or not a master!\n"); + } + + // if this isn't a master entity, just say yes. + return true; +} + +void UTIL_BloodStream( const Vector &origin, const Vector &direction, int color, int amount ) +{ + if ( !UTIL_ShouldShowBlood( color ) ) + return; + + if ( g_Language.GetInt() == LANGUAGE_GERMAN && color == BLOOD_COLOR_RED ) + color = 0; + + CPVSFilter filter( origin ); + te->BloodStream( filter, 0.0, &origin, &direction, 247, 63, 14, 255, MIN( amount, 255 ) ); +} + + +Vector UTIL_RandomBloodVector( void ) +{ + Vector direction; + + direction.x = random->RandomFloat ( -1, 1 ); + direction.y = random->RandomFloat ( -1, 1 ); + direction.z = random->RandomFloat ( 0, 1 ); + + return direction; +} + + +//------------------------------------------------------------------------------ +// Purpose : Creates both an decal and any associated impact effects (such +// as flecks) for the given iDamageType and the trace's end position +// Input : +// Output : +//------------------------------------------------------------------------------ +void UTIL_ImpactTrace( trace_t *pTrace, int iDamageType, const char *pCustomImpactName ) +{ + CBaseEntity *pEntity = pTrace->m_pEnt; + + // Is the entity valid, is the surface sky? + if ( !pEntity || !UTIL_IsValidEntity( pEntity ) || (pTrace->surface.flags & SURF_SKY) ) + return; + + if ( pTrace->fraction == 1.0 ) + return; + + pEntity->ImpactTrace( pTrace, iDamageType, pCustomImpactName ); +} + +/* +============== +UTIL_PlayerDecalTrace + +A player is trying to apply his custom decal for the spray can. +Tell connected clients to display it, or use the default spray can decal +if the custom can't be loaded. +============== +*/ +void UTIL_PlayerDecalTrace( trace_t *pTrace, int playernum ) +{ + if (pTrace->fraction == 1.0) + return; + + CBroadcastRecipientFilter filter; + + te->PlayerDecal( filter, 0.0, + &pTrace->endpos, playernum, pTrace->m_pEnt->entindex() ); +} + +bool UTIL_TeamsMatch( const char *pTeamName1, const char *pTeamName2 ) +{ + // Everyone matches unless it's teamplay + if ( !g_pGameRules->IsTeamplay() ) + return true; + + // Both on a team? + if ( *pTeamName1 != 0 && *pTeamName2 != 0 ) + { + if ( !stricmp( pTeamName1, pTeamName2 ) ) // Same Team? + return true; + } + + return false; +} + + +void UTIL_AxisStringToPointPoint( Vector &start, Vector &end, const char *pString ) +{ + char tmpstr[256]; + + Q_strncpy( tmpstr, pString, sizeof(tmpstr) ); + char *pVec = strtok( tmpstr, "," ); + int i = 0; + while ( pVec != NULL && *pVec ) + { + if ( i == 0 ) + { + UTIL_StringToVector( start.Base(), pVec ); + i++; + } + else + { + UTIL_StringToVector( end.Base(), pVec ); + } + pVec = strtok( NULL, "," ); + } +} + +void UTIL_AxisStringToPointDir( Vector &start, Vector &dir, const char *pString ) +{ + Vector end; + UTIL_AxisStringToPointPoint( start, end, pString ); + dir = end - start; + VectorNormalize(dir); +} + +void UTIL_AxisStringToUnitDir( Vector &dir, const char *pString ) +{ + Vector start; + UTIL_AxisStringToPointDir( start, dir, pString ); +} + +/* +================================================== +UTIL_ClipPunchAngleOffset +================================================== +*/ + +void UTIL_ClipPunchAngleOffset( QAngle &in, const QAngle &punch, const QAngle &clip ) +{ + QAngle final = in + punch; + + //Clip each component + for ( int i = 0; i < 3; i++ ) + { + if ( final[i] > clip[i] ) + { + final[i] = clip[i]; + } + else if ( final[i] < -clip[i] ) + { + final[i] = -clip[i]; + } + + //Return the result + in[i] = final[i] - punch[i]; + } +} + +float UTIL_WaterLevel( const Vector &position, float minz, float maxz ) +{ + Vector midUp = position; + midUp.z = minz; + + if ( !(UTIL_PointContents(midUp) & MASK_WATER) ) + return minz; + + midUp.z = maxz; + if ( UTIL_PointContents(midUp) & MASK_WATER ) + return maxz; + + float diff = maxz - minz; + while (diff > 1.0) + { + midUp.z = minz + diff/2.0; + if ( UTIL_PointContents(midUp) & MASK_WATER ) + { + minz = midUp.z; + } + else + { + maxz = midUp.z; + } + diff = maxz - minz; + } + + return midUp.z; +} + + +//----------------------------------------------------------------------------- +// Like UTIL_WaterLevel, but *way* less expensive. +// I didn't replace UTIL_WaterLevel everywhere to avoid breaking anything. +//----------------------------------------------------------------------------- +class CWaterTraceFilter : public CTraceFilter +{ +public: + bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) + { + CBaseEntity *pCollide = EntityFromEntityHandle( pHandleEntity ); + + // Static prop case... + if ( !pCollide ) + return false; + + // Only impact water stuff... + if ( pCollide->GetSolidFlags() & FSOLID_VOLUME_CONTENTS ) + return true; + + return false; + } +}; + +float UTIL_FindWaterSurface( const Vector &position, float minz, float maxz ) +{ + Vector vecStart, vecEnd; + vecStart.Init( position.x, position.y, maxz ); + vecEnd.Init( position.x, position.y, minz ); + + Ray_t ray; + trace_t tr; + CWaterTraceFilter waterTraceFilter; + ray.Init( vecStart, vecEnd ); + enginetrace->TraceRay( ray, MASK_WATER, &waterTraceFilter, &tr ); + + return tr.endpos.z; +} + + +extern short 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, ... ) +{ + 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 = GetCurrentGravity(); + 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 ) +{ + 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 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 ); + } + + return 0; +} + +// UNDONE: This could be a better test - can we run the absbox through the bsp and see +// if it contains any solid space? or would that eliminate some entities we want to keep? +int UTIL_EntityInSolid( CBaseEntity *ent ) +{ + Vector point; + + CBaseEntity *pParent = ent->GetMoveParent(); + // HACKHACK -- If you're attached to a client, always go through + if ( pParent ) + { + if ( pParent->IsPlayer() ) + return 0; + + ent = ent->GetRootMoveParent(); + } + + point = ent->WorldSpaceCenter(); + return ( enginetrace->GetPointContents( point ) & MASK_SOLID ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Initialize the matrix from an entity +// Input : *pEntity - +//----------------------------------------------------------------------------- +void EntityMatrix::InitFromEntity( CBaseEntity *pEntity, int iAttachment ) +{ + if ( !pEntity ) + { + Identity(); + return; + } + + // Get an attachment's matrix? + if ( iAttachment != 0 ) + { + CBaseAnimating *pAnimating = pEntity->GetBaseAnimating(); + if ( pAnimating && pAnimating->GetModelPtr() ) + { + Vector vOrigin; + QAngle vAngles; + if ( pAnimating->GetAttachment( iAttachment, vOrigin, vAngles ) ) + { + ((VMatrix *)this)->SetupMatrixOrgAngles( vOrigin, vAngles ); + return; + } + } + } + + ((VMatrix *)this)->SetupMatrixOrgAngles( pEntity->GetAbsOrigin(), pEntity->GetAbsAngles() ); +} + + +void EntityMatrix::InitFromEntityLocal( CBaseEntity *entity ) +{ + if ( !entity || !entity->edict() ) + { + Identity(); + return; + } + ((VMatrix *)this)->SetupMatrixOrgAngles( entity->GetLocalOrigin(), entity->GetLocalAngles() ); +} + +//================================================== +// Purpose: +// Input: +// Output: +//================================================== + +void UTIL_ValidateSoundName( string_t &name, const char *defaultStr ) +{ + if ( ( !name || + strlen( (char*) STRING( name ) ) < 1 ) || + !Q_stricmp( (char *)STRING(name), "0" ) ) + { + name = AllocPooledString( defaultStr ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Slightly modified strtok. Does not modify the input string. Does +// not skip over more than one separator at a time. This allows parsing +// strings where tokens between separators may or may not be present: +// +// Door01,,,0 would be parsed as "Door01" "" "" "0" +// Door01,Open,,0 would be parsed as "Door01" "Open" "" "0" +// +// Input : token - Returns with a token, or zero length if the token was missing. +// str - String to parse. +// sep - Character to use as separator. UNDONE: allow multiple separator chars +// Output : Returns a pointer to the next token to be parsed. +//----------------------------------------------------------------------------- +const char *nexttoken(char *token, const char *str, char sep) +{ + if ((str == NULL) || (*str == '\0')) + { + *token = '\0'; + return(NULL); + } + + // + // Copy everything up to the first separator into the return buffer. + // Do not include separators in the return buffer. + // + while ((*str != sep) && (*str != '\0')) + { + *token++ = *str++; + } + *token = '\0'; + + // + // Advance the pointer unless we hit the end of the input string. + // + if (*str == '\0') + { + return(str); + } + + return(++str); +} + +//----------------------------------------------------------------------------- +// Purpose: Helper for UTIL_FindClientInPVS +// Input : check - last checked client +// Output : static int UTIL_GetNewCheckClient +//----------------------------------------------------------------------------- +// FIXME: include bspfile.h here? +class CCheckClient : public CAutoGameSystem +{ +public: + CCheckClient( char const *name ) : CAutoGameSystem( name ) + { + } + + void LevelInitPreEntity() + { + m_checkCluster = -1; + m_lastcheck = 1; + m_lastchecktime = -1; + m_bClientPVSIsExpanded = false; + } + + byte m_checkPVS[MAX_MAP_LEAFS/8]; + byte m_checkVisibilityPVS[MAX_MAP_LEAFS/8]; + int m_checkCluster; + int m_lastcheck; + float m_lastchecktime; + bool m_bClientPVSIsExpanded; +}; + +CCheckClient g_CheckClient( "CCheckClient" ); + + +static int UTIL_GetNewCheckClient( int check ) +{ + int i; + edict_t *ent; + Vector org; + +// cycle to the next one + + if (check < 1) + check = 1; + if (check > gpGlobals->maxClients) + check = gpGlobals->maxClients; + + if (check == gpGlobals->maxClients) + i = 1; + else + i = check + 1; + + for ( ; ; i++) + { + if ( i > gpGlobals->maxClients ) + { + i = 1; + } + + ent = engine->PEntityOfEntIndex( i ); + if ( !ent ) + continue; + + // Looped but didn't find anything else + if ( i == check ) + break; + + if ( !ent->GetUnknown() ) + continue; + + CBaseEntity *entity = GetContainingEntity( ent ); + if ( !entity ) + continue; + + if ( entity->GetFlags() & FL_NOTARGET ) + continue; + + // anything that is a client, or has a client as an enemy + break; + } + + if ( i != check ) + { + memset( g_CheckClient.m_checkVisibilityPVS, 0, sizeof(g_CheckClient.m_checkVisibilityPVS) ); + g_CheckClient.m_bClientPVSIsExpanded = false; + } + + if ( ent ) + { + // get the PVS for the entity + CBaseEntity *pce = GetContainingEntity( ent ); + if ( !pce ) + return i; + + org = pce->EyePosition(); + + int clusterIndex = engine->GetClusterForOrigin( org ); + if ( clusterIndex != g_CheckClient.m_checkCluster ) + { + g_CheckClient.m_checkCluster = clusterIndex; + engine->GetPVSForCluster( clusterIndex, sizeof(g_CheckClient.m_checkPVS), g_CheckClient.m_checkPVS ); + } + } + + return i; +} + + +//----------------------------------------------------------------------------- +// Gets the current check client.... +//----------------------------------------------------------------------------- +static edict_t *UTIL_GetCurrentCheckClient() +{ + edict_t *ent; + + // find a new check if on a new frame + float delta = gpGlobals->curtime - g_CheckClient.m_lastchecktime; + if ( delta >= 0.1 || delta < 0 ) + { + g_CheckClient.m_lastcheck = UTIL_GetNewCheckClient( g_CheckClient.m_lastcheck ); + g_CheckClient.m_lastchecktime = gpGlobals->curtime; + } + + // return check if it might be visible + ent = engine->PEntityOfEntIndex( g_CheckClient.m_lastcheck ); + + // Allow dead clients -- JAY + // Our monsters know the difference, and this function gates alot of behavior + // It's annoying to die and see monsters stop thinking because you're no longer + // "in" their PVS + if ( !ent || ent->IsFree() || !ent->GetUnknown()) + { + return NULL; + } + + return ent; +} + +void UTIL_SetClientVisibilityPVS( edict_t *pClient, const unsigned char *pvs, int pvssize ) +{ + if ( pClient == UTIL_GetCurrentCheckClient() ) + { + Assert( pvssize <= sizeof(g_CheckClient.m_checkVisibilityPVS) ); + + g_CheckClient.m_bClientPVSIsExpanded = false; + + unsigned *pFrom = (unsigned *)pvs; + unsigned *pMask = (unsigned *)g_CheckClient.m_checkPVS; + unsigned *pTo = (unsigned *)g_CheckClient.m_checkVisibilityPVS; + + int limit = pvssize / 4; + int i; + + for ( i = 0; i < limit; i++ ) + { + pTo[i] = pFrom[i] & ~pMask[i]; + + if ( pFrom[i] ) + { + g_CheckClient.m_bClientPVSIsExpanded = true; + } + } + + int remainder = pvssize % 4; + for ( i = 0; i < remainder; i++ ) + { + ((unsigned char *)&pTo[limit])[i] = ((unsigned char *)&pFrom[limit])[i] & !((unsigned char *)&pMask[limit])[i]; + + if ( ((unsigned char *)&pFrom[limit])[i] != 0) + { + g_CheckClient.m_bClientPVSIsExpanded = true; + } + } + } +} + +bool UTIL_ClientPVSIsExpanded() +{ + return g_CheckClient.m_bClientPVSIsExpanded; +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns a client (or object that has a client enemy) that would be a valid target. +// If there are more than one valid options, they are cycled each frame +// If (self.origin + self.viewofs) is not in the PVS of the current target, it is not returned at all. +// Input : *pEdict - +// Output : edict_t* +//----------------------------------------------------------------------------- +CBaseEntity *UTIL_FindClientInPVS( const Vector &vecBoxMins, const Vector &vecBoxMaxs ) +{ + edict_t *ent = UTIL_GetCurrentCheckClient(); + if ( !ent ) + { + return NULL; + } + + if ( !engine->CheckBoxInPVS( vecBoxMins, vecBoxMaxs, g_CheckClient.m_checkPVS, sizeof( g_CheckClient.m_checkPVS ) ) ) + { + return NULL; + } + + // might be able to see it + return GetContainingEntity( ent ); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns a client (or object that has a client enemy) that would be a valid target. +// If there are more than one valid options, they are cycled each frame +// If (self.origin + self.viewofs) is not in the PVS of the current target, it is not returned at all. +// Input : *pEdict - +// Output : edict_t* +//----------------------------------------------------------------------------- +ConVar sv_strict_notarget( "sv_strict_notarget", "0", 0, "If set, notarget will cause entities to never think they are in the pvs" ); + +static 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 UTIL_FindClientInPVSGuts( 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 UTIL_FindClientInPVSGuts( 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(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( pSourceEntity ); + CBaseAnimating *pDestBaseAnimating = dynamic_cast( 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 &entities, const char *pScriptFile, const char *pBlock, bool bActivate ) +{ + KeyValues *pkvFile = new KeyValues( pBlock ); + + if ( pkvFile->LoadFromFile( filesystem, pScriptFile, "MOD" ) ) + { + // 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 ); + } +} + +//============================================================================= +// +// 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( 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_SCALED( pPlayer ), VEC_HULL_MAX_SCALED( pPlayer ), 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( UTIL_GetLocalPlayer() ); + Vector vecStart = pPlayer->GetAbsOrigin(); + partition->RenderObjectsInPlayerLeafs( vecStart - VEC_HULL_MIN_SCALED( pPlayer ), vecStart + VEC_HULL_MAX_SCALED( pPlayer ), 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 .", 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 .", 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 = engine->Time(); + 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 += engine->Time() - startTime; + } + test[testType] = duration; + Msg("%d collisions in %.2f ms (%u dots)\n", NUM_COLLISION_TESTS, duration*1000, dots ); + partition->ReportStats( "" ); +#if 1 + int red = 255, green = 0, blue = 0; + for ( i = 0; i < 1 /*NUM_COLLISION_TESTS*/; i++ ) + { + NDebugOverlay::Line( start, targets[i], red, green, blue, false, 2 ); + } +#endif +} +static ConCommand collision_test("collision_test", CC_CollisionTest, "Tests collision system", FCVAR_CHEAT ); + + + + -- cgit v1.2.3