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