Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3221 lines
90 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Utility code.
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "saverestore.h"
  9. #include "globalstate.h"
  10. #include <stdarg.h>
  11. #include "shake.h"
  12. #include "decals.h"
  13. #include "player.h"
  14. #include "gamerules.h"
  15. #include "entitylist.h"
  16. #include "bspfile.h"
  17. #include "mathlib/mathlib.h"
  18. #include "IEffects.h"
  19. #include "vstdlib/random.h"
  20. #include "soundflags.h"
  21. #include "ispatialpartition.h"
  22. #include "igamesystem.h"
  23. #include "saverestoretypes.h"
  24. #include "checksum_crc.h"
  25. #include "hierarchy.h"
  26. #include "iservervehicle.h"
  27. #include "te_effect_dispatch.h"
  28. #include "utldict.h"
  29. #include "collisionutils.h"
  30. #include "movevars_shared.h"
  31. #include "inetchannelinfo.h"
  32. #include "tier0/vprof.h"
  33. #include "ndebugoverlay.h"
  34. #include "engine/ivdebugoverlay.h"
  35. #include "datacache/imdlcache.h"
  36. #include "util.h"
  37. #include "cdll_int.h"
  38. #ifdef PORTAL
  39. #include "PortalSimulation.h"
  40. //#include "Portal_PhysicsEnvironmentMgr.h"
  41. #endif
  42. // memdbgon must be the last include file in a .cpp file!!!
  43. #include "tier0/memdbgon.h"
  44. extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud
  45. extern short g_sModelIndexBloodDrop; // (in combatweapon.cpp) holds the sprite index for the initial blood
  46. extern short g_sModelIndexBloodSpray; // (in combatweapon.cpp) holds the sprite index for splattered blood
  47. #ifdef DEBUG
  48. void DBG_AssertFunction( bool fExpr, const char *szExpr, const char *szFile, int szLine, const char *szMessage )
  49. {
  50. if (fExpr)
  51. return;
  52. char szOut[512];
  53. if (szMessage != NULL)
  54. Q_snprintf(szOut,sizeof(szOut), "ASSERT FAILED:\n %s \n(%s@%d)\n%s", szExpr, szFile, szLine, szMessage);
  55. else
  56. Q_snprintf(szOut,sizeof(szOut), "ASSERT FAILED:\n %s \n(%s@%d)\n", szExpr, szFile, szLine);
  57. Warning( "%s", szOut);
  58. }
  59. #endif // DEBUG
  60. //-----------------------------------------------------------------------------
  61. // Entity creation factory
  62. //-----------------------------------------------------------------------------
  63. class CEntityFactoryDictionary : public IEntityFactoryDictionary
  64. {
  65. public:
  66. CEntityFactoryDictionary();
  67. virtual void InstallFactory( IEntityFactory *pFactory, const char *pClassName );
  68. virtual IServerNetworkable *Create( const char *pClassName );
  69. virtual void Destroy( const char *pClassName, IServerNetworkable *pNetworkable );
  70. virtual const char *GetCannonicalName( const char *pClassName );
  71. void ReportEntitySizes();
  72. private:
  73. IEntityFactory *FindFactory( const char *pClassName );
  74. public:
  75. CUtlDict< IEntityFactory *, unsigned short > m_Factories;
  76. };
  77. //-----------------------------------------------------------------------------
  78. // Singleton accessor
  79. //-----------------------------------------------------------------------------
  80. IEntityFactoryDictionary *EntityFactoryDictionary()
  81. {
  82. static CEntityFactoryDictionary s_EntityFactory;
  83. return &s_EntityFactory;
  84. }
  85. void DumpEntityFactories_f()
  86. {
  87. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  88. return;
  89. CEntityFactoryDictionary *dict = ( CEntityFactoryDictionary * )EntityFactoryDictionary();
  90. if ( dict )
  91. {
  92. for ( int i = dict->m_Factories.First(); i != dict->m_Factories.InvalidIndex(); i = dict->m_Factories.Next( i ) )
  93. {
  94. Warning( "%s\n", dict->m_Factories.GetElementName( i ) );
  95. }
  96. }
  97. }
  98. static ConCommand dumpentityfactories( "dumpentityfactories", DumpEntityFactories_f, "Lists all entity factory names.", FCVAR_GAMEDLL );
  99. //-----------------------------------------------------------------------------
  100. //
  101. //-----------------------------------------------------------------------------
  102. CON_COMMAND( dump_entity_sizes, "Print sizeof(entclass)" )
  103. {
  104. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  105. return;
  106. ((CEntityFactoryDictionary*)EntityFactoryDictionary())->ReportEntitySizes();
  107. }
  108. //-----------------------------------------------------------------------------
  109. // Constructor
  110. //-----------------------------------------------------------------------------
  111. CEntityFactoryDictionary::CEntityFactoryDictionary() : m_Factories( true, 0, 128 )
  112. {
  113. }
  114. //-----------------------------------------------------------------------------
  115. // Finds a new factory
  116. //-----------------------------------------------------------------------------
  117. IEntityFactory *CEntityFactoryDictionary::FindFactory( const char *pClassName )
  118. {
  119. unsigned short nIndex = m_Factories.Find( pClassName );
  120. if ( nIndex == m_Factories.InvalidIndex() )
  121. return NULL;
  122. return m_Factories[nIndex];
  123. }
  124. //-----------------------------------------------------------------------------
  125. // Install a new factory
  126. //-----------------------------------------------------------------------------
  127. void CEntityFactoryDictionary::InstallFactory( IEntityFactory *pFactory, const char *pClassName )
  128. {
  129. Assert( FindFactory( pClassName ) == NULL );
  130. m_Factories.Insert( pClassName, pFactory );
  131. }
  132. //-----------------------------------------------------------------------------
  133. // Instantiate something using a factory
  134. //-----------------------------------------------------------------------------
  135. IServerNetworkable *CEntityFactoryDictionary::Create( const char *pClassName )
  136. {
  137. IEntityFactory *pFactory = FindFactory( pClassName );
  138. if ( !pFactory )
  139. {
  140. #ifdef STAGING_ONLY
  141. static ConVarRef tf_bot_use_items( "tf_bot_use_items" );
  142. if ( tf_bot_use_items.IsValid() && tf_bot_use_items.GetInt() )
  143. return NULL;
  144. #endif
  145. Warning("Attempted to create unknown entity type %s!\n", pClassName );
  146. return NULL;
  147. }
  148. #if defined(TRACK_ENTITY_MEMORY) && defined(USE_MEM_DEBUG)
  149. MEM_ALLOC_CREDIT_( m_Factories.GetElementName( m_Factories.Find( pClassName ) ) );
  150. #endif
  151. return pFactory->Create( pClassName );
  152. }
  153. //-----------------------------------------------------------------------------
  154. //
  155. //-----------------------------------------------------------------------------
  156. const char *CEntityFactoryDictionary::GetCannonicalName( const char *pClassName )
  157. {
  158. return m_Factories.GetElementName( m_Factories.Find( pClassName ) );
  159. }
  160. //-----------------------------------------------------------------------------
  161. // Destroy a networkable
  162. //-----------------------------------------------------------------------------
  163. void CEntityFactoryDictionary::Destroy( const char *pClassName, IServerNetworkable *pNetworkable )
  164. {
  165. IEntityFactory *pFactory = FindFactory( pClassName );
  166. if ( !pFactory )
  167. {
  168. Warning("Attempted to destroy unknown entity type %s!\n", pClassName );
  169. return;
  170. }
  171. pFactory->Destroy( pNetworkable );
  172. }
  173. //-----------------------------------------------------------------------------
  174. //
  175. //-----------------------------------------------------------------------------
  176. void CEntityFactoryDictionary::ReportEntitySizes()
  177. {
  178. for ( int i = m_Factories.First(); i != m_Factories.InvalidIndex(); i = m_Factories.Next( i ) )
  179. {
  180. Msg( " %s: %llu", m_Factories.GetElementName( i ), (uint64)(m_Factories[i]->GetEntitySize()) );
  181. }
  182. }
  183. //-----------------------------------------------------------------------------
  184. // class CFlaggedEntitiesEnum
  185. //-----------------------------------------------------------------------------
  186. CFlaggedEntitiesEnum::CFlaggedEntitiesEnum( CBaseEntity **pList, int listMax, int flagMask )
  187. {
  188. m_pList = pList;
  189. m_listMax = listMax;
  190. m_flagMask = flagMask;
  191. m_count = 0;
  192. }
  193. bool CFlaggedEntitiesEnum::AddToList( CBaseEntity *pEntity )
  194. {
  195. if ( m_count >= m_listMax )
  196. {
  197. AssertMsgOnce( 0, "reached enumerated list limit. Increase limit, decrease radius, or make it so entity flags will work for you" );
  198. return false;
  199. }
  200. m_pList[m_count] = pEntity;
  201. m_count++;
  202. return true;
  203. }
  204. IterationRetval_t CFlaggedEntitiesEnum::EnumElement( IHandleEntity *pHandleEntity )
  205. {
  206. CBaseEntity *pEntity = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() );
  207. if ( pEntity )
  208. {
  209. if ( m_flagMask && !(pEntity->GetFlags() & m_flagMask) ) // Does it meet the criteria?
  210. return ITERATION_CONTINUE;
  211. if ( !AddToList( pEntity ) )
  212. return ITERATION_STOP;
  213. }
  214. return ITERATION_CONTINUE;
  215. }
  216. //-----------------------------------------------------------------------------
  217. // Purpose:
  218. //-----------------------------------------------------------------------------
  219. int UTIL_PrecacheDecal( const char *name, bool preload )
  220. {
  221. // If this is out of order, make sure to warn.
  222. if ( !CBaseEntity::IsPrecacheAllowed() )
  223. {
  224. if ( !engine->IsDecalPrecached( name ) )
  225. {
  226. Assert( !"UTIL_PrecacheDecal: too late" );
  227. Warning( "Late precache of %s\n", name );
  228. }
  229. }
  230. return engine->PrecacheDecal( name, preload );
  231. }
  232. //-----------------------------------------------------------------------------
  233. // Purpose:
  234. //-----------------------------------------------------------------------------
  235. float UTIL_GetSimulationInterval()
  236. {
  237. if ( CBaseEntity::IsSimulatingOnAlternateTicks() )
  238. return ( TICK_INTERVAL * 2.0 );
  239. return TICK_INTERVAL;
  240. }
  241. //-----------------------------------------------------------------------------
  242. // Purpose:
  243. //-----------------------------------------------------------------------------
  244. int UTIL_EntitiesInBox( const Vector &mins, const Vector &maxs, CFlaggedEntitiesEnum *pEnum )
  245. {
  246. ::partition->EnumerateElementsInBox( PARTITION_ENGINE_NON_STATIC_EDICTS, mins, maxs, false, pEnum );
  247. return pEnum->GetCount();
  248. }
  249. int UTIL_EntitiesAlongRay( const Ray_t &ray, CFlaggedEntitiesEnum *pEnum )
  250. {
  251. ::partition->EnumerateElementsAlongRay( PARTITION_ENGINE_NON_STATIC_EDICTS, ray, false, pEnum );
  252. return pEnum->GetCount();
  253. }
  254. int UTIL_EntitiesInSphere( const Vector &center, float radius, CFlaggedEntitiesEnum *pEnum )
  255. {
  256. ::partition->EnumerateElementsInSphere( PARTITION_ENGINE_NON_STATIC_EDICTS, center, radius, false, pEnum );
  257. return pEnum->GetCount();
  258. }
  259. CEntitySphereQuery::CEntitySphereQuery( const Vector &center, float radius, int flagMask )
  260. {
  261. m_listIndex = 0;
  262. m_listCount = UTIL_EntitiesInSphere( m_pList, ARRAYSIZE(m_pList), center, radius, flagMask );
  263. }
  264. CBaseEntity *CEntitySphereQuery::GetCurrentEntity()
  265. {
  266. if ( m_listIndex < m_listCount )
  267. return m_pList[m_listIndex];
  268. return NULL;
  269. }
  270. //-----------------------------------------------------------------------------
  271. // Simple trace filter
  272. //-----------------------------------------------------------------------------
  273. class CTracePassFilter : public CTraceFilter
  274. {
  275. public:
  276. CTracePassFilter( IHandleEntity *pPassEnt ) : m_pPassEnt( pPassEnt ) {}
  277. bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
  278. {
  279. if ( !StandardFilterRules( pHandleEntity, contentsMask ) )
  280. return false;
  281. if (!PassServerEntityFilter( pHandleEntity, m_pPassEnt ))
  282. return false;
  283. return true;
  284. }
  285. private:
  286. IHandleEntity *m_pPassEnt;
  287. };
  288. //-----------------------------------------------------------------------------
  289. // Drops an entity onto the floor
  290. //-----------------------------------------------------------------------------
  291. int UTIL_DropToFloor( CBaseEntity *pEntity, unsigned int mask, CBaseEntity *pIgnore )
  292. {
  293. // Assume no ground
  294. pEntity->SetGroundEntity( NULL );
  295. Assert( pEntity );
  296. trace_t trace;
  297. #ifndef HL2MP
  298. // HACK: is this really the only sure way to detect crossing a terrain boundry?
  299. UTIL_TraceEntity( pEntity, pEntity->GetAbsOrigin(), pEntity->GetAbsOrigin(), mask, pIgnore, pEntity->GetCollisionGroup(), &trace );
  300. if (trace.fraction == 0.0)
  301. return -1;
  302. #endif // HL2MP
  303. UTIL_TraceEntity( pEntity, pEntity->GetAbsOrigin(), pEntity->GetAbsOrigin() - Vector(0,0,256), mask, pIgnore, pEntity->GetCollisionGroup(), &trace );
  304. if (trace.allsolid)
  305. return -1;
  306. if (trace.fraction == 1)
  307. return 0;
  308. pEntity->SetAbsOrigin( trace.endpos );
  309. pEntity->SetGroundEntity( trace.m_pEnt );
  310. return 1;
  311. }
  312. //-----------------------------------------------------------------------------
  313. // Returns false if any part of the bottom of the entity is off an edge that
  314. // is not a staircase.
  315. //-----------------------------------------------------------------------------
  316. bool UTIL_CheckBottom( CBaseEntity *pEntity, ITraceFilter *pTraceFilter, float flStepSize )
  317. {
  318. Vector mins, maxs, start, stop;
  319. trace_t trace;
  320. int x, y;
  321. float mid, bottom;
  322. Assert( pEntity );
  323. CTracePassFilter traceFilter(pEntity);
  324. if ( !pTraceFilter )
  325. {
  326. pTraceFilter = &traceFilter;
  327. }
  328. unsigned int mask = pEntity->PhysicsSolidMaskForEntity();
  329. VectorAdd (pEntity->GetAbsOrigin(), pEntity->WorldAlignMins(), mins);
  330. VectorAdd (pEntity->GetAbsOrigin(), pEntity->WorldAlignMaxs(), maxs);
  331. // if all of the points under the corners are solid world, don't bother
  332. // with the tougher checks
  333. // the corners must be within 16 of the midpoint
  334. start[2] = mins[2] - 1;
  335. for (x=0 ; x<=1 ; x++)
  336. {
  337. for (y=0 ; y<=1 ; y++)
  338. {
  339. start[0] = x ? maxs[0] : mins[0];
  340. start[1] = y ? maxs[1] : mins[1];
  341. if (enginetrace->GetPointContents(start) != CONTENTS_SOLID)
  342. goto realcheck;
  343. }
  344. }
  345. return true; // we got out easy
  346. realcheck:
  347. // check it for real...
  348. start[2] = mins[2] + flStepSize; // seems to help going up/down slopes.
  349. // the midpoint must be within 16 of the bottom
  350. start[0] = stop[0] = (mins[0] + maxs[0])*0.5;
  351. start[1] = stop[1] = (mins[1] + maxs[1])*0.5;
  352. stop[2] = start[2] - 2*flStepSize;
  353. UTIL_TraceLine( start, stop, mask, pTraceFilter, &trace );
  354. if (trace.fraction == 1.0)
  355. return false;
  356. mid = bottom = trace.endpos[2];
  357. // the corners must be within 16 of the midpoint
  358. for (x=0 ; x<=1 ; x++)
  359. {
  360. for (y=0 ; y<=1 ; y++)
  361. {
  362. start[0] = stop[0] = x ? maxs[0] : mins[0];
  363. start[1] = stop[1] = y ? maxs[1] : mins[1];
  364. UTIL_TraceLine( start, stop, mask, pTraceFilter, &trace );
  365. if (trace.fraction != 1.0 && trace.endpos[2] > bottom)
  366. bottom = trace.endpos[2];
  367. if (trace.fraction == 1.0 || mid - trace.endpos[2] > flStepSize)
  368. return false;
  369. }
  370. }
  371. return true;
  372. }
  373. bool g_bDisableEhandleAccess = false;
  374. bool g_bReceivedChainedUpdateOnRemove = false;
  375. //-----------------------------------------------------------------------------
  376. // Purpose: Sets the entity up for deletion. Entity will not actually be deleted
  377. // until the next frame, so there can be no pointer errors.
  378. // Input : *oldObj - object to delete
  379. //-----------------------------------------------------------------------------
  380. void UTIL_Remove( IServerNetworkable *oldObj )
  381. {
  382. CServerNetworkProperty* pProp = static_cast<CServerNetworkProperty*>( oldObj );
  383. if ( !pProp || pProp->IsMarkedForDeletion() )
  384. return;
  385. if ( PhysIsInCallback() )
  386. {
  387. // This assert means that someone is deleting an entity inside a callback. That isn't supported so
  388. // this code will defer the deletion of that object until the end of the current physics simulation frame
  389. // Since this is hidden from the calling code it's preferred to call PhysCallbackRemove() directly from the caller
  390. // in case the deferred delete will have unwanted results (like continuing to receive callbacks). That will make it
  391. // obvious why the unwanted results are happening so the caller can handle them appropriately. (some callbacks can be masked
  392. // or the calling entity can be flagged to filter them in most cases)
  393. Assert(0);
  394. PhysCallbackRemove(oldObj);
  395. return;
  396. }
  397. // mark it for deletion
  398. pProp->MarkForDeletion( );
  399. CBaseEntity *pBaseEnt = oldObj->GetBaseEntity();
  400. if ( pBaseEnt )
  401. {
  402. #ifdef PORTAL //make sure entities are in the primary physics environment for the portal mod, this code should be safe even if the entity is in neither extra environment
  403. CPortalSimulator::Pre_UTIL_Remove( pBaseEnt );
  404. #endif
  405. g_bReceivedChainedUpdateOnRemove = false;
  406. pBaseEnt->UpdateOnRemove();
  407. Assert( g_bReceivedChainedUpdateOnRemove );
  408. // clear oldObj targetname / other flags now
  409. pBaseEnt->SetName( NULL_STRING );
  410. #ifdef PORTAL
  411. CPortalSimulator::Post_UTIL_Remove( pBaseEnt );
  412. #endif
  413. }
  414. gEntList.AddToDeleteList( oldObj );
  415. }
  416. void UTIL_Remove( CBaseEntity *oldObj )
  417. {
  418. if ( !oldObj )
  419. return;
  420. UTIL_Remove( oldObj->NetworkProp() );
  421. }
  422. static int s_RemoveImmediateSemaphore = 0;
  423. void UTIL_DisableRemoveImmediate()
  424. {
  425. s_RemoveImmediateSemaphore++;
  426. }
  427. void UTIL_EnableRemoveImmediate()
  428. {
  429. s_RemoveImmediateSemaphore--;
  430. Assert(s_RemoveImmediateSemaphore>=0);
  431. }
  432. //-----------------------------------------------------------------------------
  433. // Purpose: deletes an entity, without any delay. WARNING! Only use this when sure
  434. // no pointers rely on this entity.
  435. // Input : *oldObj - the entity to delete
  436. //-----------------------------------------------------------------------------
  437. void UTIL_RemoveImmediate( CBaseEntity *oldObj )
  438. {
  439. // valid pointer or already removed?
  440. if ( !oldObj || oldObj->IsEFlagSet(EFL_KILLME) )
  441. return;
  442. if ( s_RemoveImmediateSemaphore )
  443. {
  444. UTIL_Remove(oldObj);
  445. return;
  446. }
  447. #ifdef PORTAL //make sure entities are in the primary physics environment for the portal mod, this code should be safe even if the entity is in neither extra environment
  448. CPortalSimulator::Pre_UTIL_Remove( oldObj );
  449. #endif
  450. oldObj->AddEFlags( EFL_KILLME ); // Make sure to ignore further calls into here or UTIL_Remove.
  451. g_bReceivedChainedUpdateOnRemove = false;
  452. oldObj->UpdateOnRemove();
  453. Assert( g_bReceivedChainedUpdateOnRemove );
  454. // Entities shouldn't reference other entities in their destructors
  455. // that type of code should only occur in an UpdateOnRemove call
  456. g_bDisableEhandleAccess = true;
  457. delete oldObj;
  458. g_bDisableEhandleAccess = false;
  459. #ifdef PORTAL
  460. CPortalSimulator::Post_UTIL_Remove( oldObj );
  461. #endif
  462. }
  463. // returns a CBaseEntity pointer to a player by index. Only returns if the player is spawned and connected
  464. // otherwise returns NULL
  465. // Index is 1 based
  466. CBasePlayer *UTIL_PlayerByIndex( int playerIndex )
  467. {
  468. CBasePlayer *pPlayer = NULL;
  469. if ( playerIndex > 0 && playerIndex <= gpGlobals->maxClients )
  470. {
  471. edict_t *pPlayerEdict = INDEXENT( playerIndex );
  472. if ( pPlayerEdict && !pPlayerEdict->IsFree() )
  473. {
  474. pPlayer = (CBasePlayer*)GetContainingEntity( pPlayerEdict );
  475. }
  476. }
  477. return pPlayer;
  478. }
  479. //
  480. // Return the local player.
  481. // If this is a multiplayer game, return NULL.
  482. //
  483. CBasePlayer *UTIL_GetLocalPlayer( void )
  484. {
  485. if ( gpGlobals->maxClients > 1 )
  486. {
  487. if ( developer.GetBool() )
  488. {
  489. Assert( !"UTIL_GetLocalPlayer" );
  490. #ifdef DEBUG
  491. Warning( "UTIL_GetLocalPlayer() called in multiplayer game.\n" );
  492. #endif
  493. }
  494. return NULL;
  495. }
  496. return UTIL_PlayerByIndex( 1 );
  497. }
  498. //
  499. // Get the local player on a listen server - this is for multiplayer use only
  500. //
  501. CBasePlayer *UTIL_GetListenServerHost( void )
  502. {
  503. // no "local player" if this is a dedicated server or a single player game
  504. if (engine->IsDedicatedServer())
  505. {
  506. Assert( !"UTIL_GetListenServerHost" );
  507. Warning( "UTIL_GetListenServerHost() called from a dedicated server or single-player game.\n" );
  508. return NULL;
  509. }
  510. return UTIL_PlayerByIndex( 1 );
  511. }
  512. //--------------------------------------------------------------------------------------------------------------
  513. /**
  514. * Returns true if the command was issued by the listenserver host, or by the dedicated server, via rcon or the server console.
  515. * This is valid during ConCommand execution.
  516. */
  517. bool UTIL_IsCommandIssuedByServerAdmin( void )
  518. {
  519. int issuingPlayerIndex = UTIL_GetCommandClientIndex();
  520. if ( engine->IsDedicatedServer() && issuingPlayerIndex > 0 )
  521. return false;
  522. #if defined( REPLAY_ENABLED )
  523. // entity 1 is replay?
  524. player_info_t pi;
  525. bool bPlayerIsReplay = engine->GetPlayerInfo( 1, &pi ) && pi.isreplay;
  526. #else
  527. bool bPlayerIsReplay = false;
  528. #endif
  529. if ( bPlayerIsReplay )
  530. {
  531. if ( issuingPlayerIndex > 2 )
  532. return false;
  533. }
  534. else if ( issuingPlayerIndex > 1 )
  535. {
  536. return false;
  537. }
  538. return true;
  539. }
  540. //--------------------------------------------------------------------------------------------------------------
  541. /**
  542. * Returns a CBaseEntity pointer by entindex. Index is 1 based.
  543. */
  544. CBaseEntity *UTIL_EntityByIndex( int entityIndex )
  545. {
  546. CBaseEntity *entity = NULL;
  547. if ( entityIndex > 0 )
  548. {
  549. edict_t *edict = INDEXENT( entityIndex );
  550. if ( edict && !edict->IsFree() )
  551. {
  552. entity = GetContainingEntity( edict );
  553. }
  554. }
  555. return entity;
  556. }
  557. int ENTINDEX( CBaseEntity *pEnt )
  558. {
  559. // This works just like ENTINDEX for edicts.
  560. if ( pEnt )
  561. return pEnt->entindex();
  562. else
  563. return 0;
  564. }
  565. //-----------------------------------------------------------------------------
  566. // Purpose:
  567. // Input : playerIndex -
  568. // ping -
  569. // packetloss -
  570. //-----------------------------------------------------------------------------
  571. void UTIL_GetPlayerConnectionInfo( int playerIndex, int& ping, int &packetloss )
  572. {
  573. CBasePlayer *player = UTIL_PlayerByIndex( playerIndex );
  574. INetChannelInfo *nci = engine->GetPlayerNetInfo(playerIndex);
  575. if ( nci && player && !player->IsBot() )
  576. {
  577. float latency = nci->GetAvgLatency( FLOW_OUTGOING ); // in seconds
  578. // that should be the correct latency, we assume that cmdrate is higher
  579. // then updaterate, what is the case for default settings
  580. const char * szCmdRate = engine->GetClientConVarValue( playerIndex, "cl_cmdrate" );
  581. int nCmdRate = MAX( 1, Q_atoi( szCmdRate ) );
  582. latency -= (0.5f/nCmdRate) + TICKS_TO_TIME( 1.0f ); // correct latency
  583. // in GoldSrc we had a different, not fixed tickrate. so we have to adjust
  584. // Source pings by half a tick to match the old GoldSrc pings.
  585. latency -= TICKS_TO_TIME( 0.5f );
  586. ping = latency * 1000.0f; // as msecs
  587. ping = clamp( ping, 5, 1000 ); // set bounds, dont show pings under 5 msecs
  588. packetloss = 100.0f * nci->GetAvgLoss( FLOW_INCOMING ); // loss in percentage
  589. packetloss = clamp( packetloss, 0, 100 );
  590. }
  591. else
  592. {
  593. ping = 0;
  594. packetloss = 0;
  595. }
  596. }
  597. static unsigned short FixedUnsigned16( float value, float scale )
  598. {
  599. int output;
  600. output = value * scale;
  601. if ( output < 0 )
  602. output = 0;
  603. if ( output > 0xFFFF )
  604. output = 0xFFFF;
  605. return (unsigned short)output;
  606. }
  607. //-----------------------------------------------------------------------------
  608. // Compute shake amplitude
  609. //-----------------------------------------------------------------------------
  610. inline float ComputeShakeAmplitude( const Vector &center, const Vector &shakePt, float amplitude, float radius )
  611. {
  612. if ( radius <= 0 )
  613. return amplitude;
  614. float localAmplitude = -1;
  615. Vector delta = center - shakePt;
  616. float distance = delta.Length();
  617. if ( distance <= radius )
  618. {
  619. // Make the amplitude fall off over distance
  620. float flPerc = 1.0 - (distance / radius);
  621. localAmplitude = amplitude * flPerc;
  622. }
  623. return localAmplitude;
  624. }
  625. //-----------------------------------------------------------------------------
  626. // Transmits the actual shake event
  627. //-----------------------------------------------------------------------------
  628. inline void TransmitShakeEvent( CBasePlayer *pPlayer, float localAmplitude, float frequency, float duration, ShakeCommand_t eCommand )
  629. {
  630. if (( localAmplitude > 0 ) || ( eCommand == SHAKE_STOP ))
  631. {
  632. if ( eCommand == SHAKE_STOP )
  633. localAmplitude = 0;
  634. CSingleUserRecipientFilter user( pPlayer );
  635. user.MakeReliable();
  636. UserMessageBegin( user, "Shake" );
  637. WRITE_BYTE( eCommand ); // shake command (SHAKE_START, STOP, FREQUENCY, AMPLITUDE)
  638. WRITE_FLOAT( localAmplitude ); // shake magnitude/amplitude
  639. WRITE_FLOAT( frequency ); // shake noise frequency
  640. WRITE_FLOAT( duration ); // shake lasts this long
  641. MessageEnd();
  642. }
  643. }
  644. //-----------------------------------------------------------------------------
  645. // Purpose: Shake the screen of all clients within radius.
  646. // radius == 0, shake all clients
  647. // UNDONE: Fix falloff model (disabled)?
  648. // UNDONE: Affect user controls?
  649. // Input : center - Center of screen shake, radius is measured from here.
  650. // amplitude - Amplitude of shake
  651. // frequency -
  652. // duration - duration of shake in seconds.
  653. // radius - Radius of effect, 0 shakes all clients.
  654. // command - One of the following values:
  655. // SHAKE_START - starts the screen shake for all players within the radius
  656. // SHAKE_STOP - stops the screen shake for all players within the radius
  657. // SHAKE_AMPLITUDE - modifies the amplitude of the screen shake
  658. // for all players within the radius
  659. // SHAKE_FREQUENCY - modifies the frequency of the screen shake
  660. // for all players within the radius
  661. // bAirShake - if this is false, then it will only shake players standing on the ground.
  662. //-----------------------------------------------------------------------------
  663. const float MAX_SHAKE_AMPLITUDE = 16.0f;
  664. void UTIL_ScreenShake( const Vector &center, float amplitude, float frequency, float duration, float radius, ShakeCommand_t eCommand, bool bAirShake )
  665. {
  666. int i;
  667. float localAmplitude;
  668. if ( amplitude > MAX_SHAKE_AMPLITUDE )
  669. {
  670. amplitude = MAX_SHAKE_AMPLITUDE;
  671. }
  672. for ( i = 1; i <= gpGlobals->maxClients; i++ )
  673. {
  674. CBaseEntity *pPlayer = UTIL_PlayerByIndex( i );
  675. //
  676. // Only start shakes for players that are on the ground unless doing an air shake.
  677. //
  678. if ( !pPlayer || (!bAirShake && (eCommand == SHAKE_START) && !(pPlayer->GetFlags() & FL_ONGROUND)) )
  679. {
  680. continue;
  681. }
  682. localAmplitude = ComputeShakeAmplitude( center, pPlayer->WorldSpaceCenter(), amplitude, radius );
  683. // This happens if the player is outside the radius, in which case we should ignore
  684. // all commands
  685. if (localAmplitude < 0)
  686. continue;
  687. TransmitShakeEvent( (CBasePlayer *)pPlayer, localAmplitude, frequency, duration, eCommand );
  688. }
  689. }
  690. //-----------------------------------------------------------------------------
  691. // Purpose: Shake an object and all players on or near it
  692. //-----------------------------------------------------------------------------
  693. void UTIL_ScreenShakeObject( CBaseEntity *pEnt, const Vector &center, float amplitude, float frequency, float duration, float radius, ShakeCommand_t eCommand, bool bAirShake )
  694. {
  695. int i;
  696. float localAmplitude;
  697. CBaseEntity *pHighestParent = pEnt->GetRootMoveParent();
  698. for ( i = 1; i <= gpGlobals->maxClients; i++ )
  699. {
  700. CBaseEntity *pPlayer = UTIL_PlayerByIndex( i );
  701. if (!pPlayer)
  702. continue;
  703. // Shake the object, or anything hierarchically attached to it at maximum amplitude
  704. localAmplitude = 0;
  705. if (pHighestParent == pPlayer->GetRootMoveParent())
  706. {
  707. localAmplitude = amplitude;
  708. }
  709. else if ((pPlayer->GetFlags() & FL_ONGROUND) && (pPlayer->GetGroundEntity()->GetRootMoveParent() == pHighestParent))
  710. {
  711. // If the player is standing on the object, use maximum amplitude
  712. localAmplitude = amplitude;
  713. }
  714. else
  715. {
  716. // Only shake players that are on the ground.
  717. if ( !bAirShake && !(pPlayer->GetFlags() & FL_ONGROUND) )
  718. {
  719. continue;
  720. }
  721. if ( radius > 0 )
  722. {
  723. localAmplitude = ComputeShakeAmplitude( center, pPlayer->WorldSpaceCenter(), amplitude, radius );
  724. }
  725. else
  726. {
  727. // If using a 0 radius, apply to everyone with no falloff
  728. localAmplitude = amplitude;
  729. }
  730. // This happens if the player is outside the radius,
  731. // in which case we should ignore all commands
  732. if (localAmplitude < 0)
  733. continue;
  734. }
  735. TransmitShakeEvent( (CBasePlayer *)pPlayer, localAmplitude, frequency, duration, eCommand );
  736. }
  737. }
  738. //-----------------------------------------------------------------------------
  739. // Purpose: Punches the view of all clients within radius.
  740. // If radius is 0, punches all clients.
  741. // Input : center - Center of punch, radius is measured from here.
  742. // radius - Radius of effect, 0 punches all clients.
  743. // bInAir - if this is false, then it will only punch players standing on the ground.
  744. //-----------------------------------------------------------------------------
  745. void UTIL_ViewPunch( const Vector &center, QAngle angPunch, float radius, bool bInAir )
  746. {
  747. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  748. {
  749. CBaseEntity *pPlayer = UTIL_PlayerByIndex( i );
  750. //
  751. // Only apply the punch to players that are on the ground unless doing an air punch.
  752. //
  753. if ( !pPlayer || (!bInAir && !(pPlayer->GetFlags() & FL_ONGROUND)) )
  754. {
  755. continue;
  756. }
  757. QAngle angTemp = angPunch;
  758. if ( radius > 0 )
  759. {
  760. Vector delta = center - pPlayer->GetAbsOrigin();
  761. float distance = delta.Length();
  762. if ( distance <= radius )
  763. {
  764. // Make the punch amplitude fall off over distance.
  765. float flPerc = 1.0 - (distance / radius);
  766. angTemp *= flPerc;
  767. }
  768. else
  769. {
  770. continue;
  771. }
  772. }
  773. pPlayer->ViewPunch( angTemp );
  774. }
  775. }
  776. void UTIL_ScreenFadeBuild( ScreenFade_t &fade, const color32 &color, float fadeTime, float fadeHold, int flags )
  777. {
  778. fade.duration = FixedUnsigned16( fadeTime, 1<<SCREENFADE_FRACBITS ); // 7.9 fixed
  779. fade.holdTime = FixedUnsigned16( fadeHold, 1<<SCREENFADE_FRACBITS ); // 7.9 fixed
  780. fade.r = color.r;
  781. fade.g = color.g;
  782. fade.b = color.b;
  783. fade.a = color.a;
  784. fade.fadeFlags = flags;
  785. }
  786. void UTIL_ScreenFadeWrite( const ScreenFade_t &fade, CBaseEntity *pEntity )
  787. {
  788. if ( !pEntity || !pEntity->IsNetClient() )
  789. return;
  790. CSingleUserRecipientFilter user( (CBasePlayer *)pEntity );
  791. user.MakeReliable();
  792. UserMessageBegin( user, "Fade" ); // use the magic #1 for "one client"
  793. WRITE_SHORT( fade.duration ); // fade lasts this long
  794. WRITE_SHORT( fade.holdTime ); // fade lasts this long
  795. WRITE_SHORT( fade.fadeFlags ); // fade type (in / out)
  796. WRITE_BYTE( fade.r ); // fade red
  797. WRITE_BYTE( fade.g ); // fade green
  798. WRITE_BYTE( fade.b ); // fade blue
  799. WRITE_BYTE( fade.a ); // fade blue
  800. MessageEnd();
  801. }
  802. void UTIL_ScreenFadeAll( const color32 &color, float fadeTime, float fadeHold, int flags )
  803. {
  804. int i;
  805. ScreenFade_t fade;
  806. UTIL_ScreenFadeBuild( fade, color, fadeTime, fadeHold, flags );
  807. for ( i = 1; i <= gpGlobals->maxClients; i++ )
  808. {
  809. CBaseEntity *pPlayer = UTIL_PlayerByIndex( i );
  810. UTIL_ScreenFadeWrite( fade, pPlayer );
  811. }
  812. }
  813. void UTIL_ScreenFade( CBaseEntity *pEntity, const color32 &color, float fadeTime, float fadeHold, int flags )
  814. {
  815. ScreenFade_t fade;
  816. UTIL_ScreenFadeBuild( fade, color, fadeTime, fadeHold, flags );
  817. UTIL_ScreenFadeWrite( fade, pEntity );
  818. }
  819. void UTIL_HudMessage( CBasePlayer *pToPlayer, const hudtextparms_t &textparms, const char *pMessage )
  820. {
  821. CRecipientFilter filter;
  822. if( pToPlayer )
  823. {
  824. filter.AddRecipient( pToPlayer );
  825. }
  826. else
  827. {
  828. filter.AddAllPlayers();
  829. }
  830. filter.MakeReliable();
  831. UserMessageBegin( filter, "HudMsg" );
  832. WRITE_BYTE ( textparms.channel & 0xFF );
  833. WRITE_FLOAT( textparms.x );
  834. WRITE_FLOAT( textparms.y );
  835. WRITE_BYTE ( textparms.r1 );
  836. WRITE_BYTE ( textparms.g1 );
  837. WRITE_BYTE ( textparms.b1 );
  838. WRITE_BYTE ( textparms.a1 );
  839. WRITE_BYTE ( textparms.r2 );
  840. WRITE_BYTE ( textparms.g2 );
  841. WRITE_BYTE ( textparms.b2 );
  842. WRITE_BYTE ( textparms.a2 );
  843. WRITE_BYTE ( textparms.effect );
  844. WRITE_FLOAT( textparms.fadeinTime );
  845. WRITE_FLOAT( textparms.fadeoutTime );
  846. WRITE_FLOAT( textparms.holdTime );
  847. WRITE_FLOAT( textparms.fxTime );
  848. WRITE_STRING( pMessage );
  849. MessageEnd();
  850. }
  851. void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage )
  852. {
  853. UTIL_HudMessage( NULL, textparms, pMessage );
  854. }
  855. void UTIL_HudHintText( CBaseEntity *pEntity, const char *pMessage )
  856. {
  857. if ( !pEntity )
  858. return;
  859. CSingleUserRecipientFilter user( (CBasePlayer *)pEntity );
  860. user.MakeReliable();
  861. UserMessageBegin( user, "KeyHintText" );
  862. WRITE_BYTE( 1 ); // one string
  863. WRITE_STRING( pMessage );
  864. MessageEnd();
  865. }
  866. void UTIL_ClientPrintFilter( IRecipientFilter& filter, int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 )
  867. {
  868. UserMessageBegin( filter, "TextMsg" );
  869. WRITE_BYTE( msg_dest );
  870. WRITE_STRING( msg_name );
  871. if ( param1 )
  872. WRITE_STRING( param1 );
  873. else
  874. WRITE_STRING( "" );
  875. if ( param2 )
  876. WRITE_STRING( param2 );
  877. else
  878. WRITE_STRING( "" );
  879. if ( param3 )
  880. WRITE_STRING( param3 );
  881. else
  882. WRITE_STRING( "" );
  883. if ( param4 )
  884. WRITE_STRING( param4 );
  885. else
  886. WRITE_STRING( "" );
  887. MessageEnd();
  888. }
  889. void UTIL_ClientPrintAll( int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 )
  890. {
  891. CReliableBroadcastRecipientFilter filter;
  892. UTIL_ClientPrintFilter( filter, msg_dest, msg_name, param1, param2, param3, param4 );
  893. }
  894. void ClientPrint( CBasePlayer *player, int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 )
  895. {
  896. if ( !player )
  897. return;
  898. CSingleUserRecipientFilter user( player );
  899. user.MakeReliable();
  900. UTIL_ClientPrintFilter( user, msg_dest, msg_name, param1, param2, param3, param4 );
  901. }
  902. void UTIL_SayTextFilter( IRecipientFilter& filter, const char *pText, CBasePlayer *pPlayer, bool bChat )
  903. {
  904. UserMessageBegin( filter, "SayText" );
  905. if ( pPlayer )
  906. {
  907. WRITE_BYTE( pPlayer->entindex() );
  908. }
  909. else
  910. {
  911. WRITE_BYTE( 0 ); // world, dedicated server says
  912. }
  913. WRITE_STRING( pText );
  914. WRITE_BYTE( bChat );
  915. MessageEnd();
  916. }
  917. 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 )
  918. {
  919. UserMessageBegin( filter, "SayText2" );
  920. if ( pEntity )
  921. {
  922. WRITE_BYTE( pEntity->entindex() );
  923. }
  924. else
  925. {
  926. WRITE_BYTE( 0 ); // world, dedicated server says
  927. }
  928. WRITE_BYTE( bChat );
  929. WRITE_STRING( msg_name );
  930. if ( param1 )
  931. WRITE_STRING( param1 );
  932. else
  933. WRITE_STRING( "" );
  934. if ( param2 )
  935. WRITE_STRING( param2 );
  936. else
  937. WRITE_STRING( "" );
  938. if ( param3 )
  939. WRITE_STRING( param3 );
  940. else
  941. WRITE_STRING( "" );
  942. if ( param4 )
  943. WRITE_STRING( param4 );
  944. else
  945. WRITE_STRING( "" );
  946. MessageEnd();
  947. }
  948. void UTIL_SayText( const char *pText, CBasePlayer *pToPlayer )
  949. {
  950. if ( !pToPlayer->IsNetClient() )
  951. return;
  952. CSingleUserRecipientFilter user( pToPlayer );
  953. user.MakeReliable();
  954. UTIL_SayTextFilter( user, pText, pToPlayer, false );
  955. }
  956. void UTIL_SayTextAll( const char *pText, CBasePlayer *pPlayer, bool bChat )
  957. {
  958. CReliableBroadcastRecipientFilter filter;
  959. UTIL_SayTextFilter( filter, pText, pPlayer, bChat );
  960. }
  961. void UTIL_ShowMessage( const char *pString, CBasePlayer *pPlayer )
  962. {
  963. CRecipientFilter filter;
  964. if ( pPlayer )
  965. {
  966. filter.AddRecipient( pPlayer );
  967. }
  968. else
  969. {
  970. filter.AddAllPlayers();
  971. }
  972. filter.MakeReliable();
  973. UserMessageBegin( filter, "HudText" );
  974. WRITE_STRING( pString );
  975. MessageEnd();
  976. }
  977. void UTIL_ShowMessageAll( const char *pString )
  978. {
  979. UTIL_ShowMessage( pString, NULL );
  980. }
  981. // So we always return a valid surface
  982. static csurface_t g_NullSurface = { "**empty**", 0 };
  983. void UTIL_SetTrace(trace_t& trace, const Ray_t &ray, edict_t *ent, float fraction,
  984. int hitgroup, unsigned int contents, const Vector& normal, float intercept )
  985. {
  986. trace.startsolid = (fraction == 0.0f);
  987. trace.fraction = fraction;
  988. VectorCopy( ray.m_Start, trace.startpos );
  989. VectorMA( ray.m_Start, fraction, ray.m_Delta, trace.endpos );
  990. VectorCopy( normal, trace.plane.normal );
  991. trace.plane.dist = intercept;
  992. trace.m_pEnt = CBaseEntity::Instance( ent );
  993. trace.hitgroup = hitgroup;
  994. trace.surface = g_NullSurface;
  995. trace.contents = contents;
  996. }
  997. void UTIL_ClearTrace( trace_t &trace )
  998. {
  999. memset( &trace, 0, sizeof(trace));
  1000. trace.fraction = 1.f;
  1001. trace.fractionleftsolid = 0;
  1002. trace.surface = g_NullSurface;
  1003. }
  1004. //-----------------------------------------------------------------------------
  1005. // Sets the entity size
  1006. //-----------------------------------------------------------------------------
  1007. static void SetMinMaxSize (CBaseEntity *pEnt, const Vector& mins, const Vector& maxs )
  1008. {
  1009. for ( int i=0 ; i<3 ; i++ )
  1010. {
  1011. if ( mins[i] > maxs[i] )
  1012. {
  1013. Error( "%s: backwards mins/maxs", ( pEnt ) ? pEnt->GetDebugName() : "<NULL>" );
  1014. }
  1015. }
  1016. Assert( pEnt );
  1017. pEnt->SetCollisionBounds( mins, maxs );
  1018. }
  1019. //-----------------------------------------------------------------------------
  1020. // Sets the model size
  1021. //-----------------------------------------------------------------------------
  1022. void UTIL_SetSize( CBaseEntity *pEnt, const Vector &vecMin, const Vector &vecMax )
  1023. {
  1024. SetMinMaxSize (pEnt, vecMin, vecMax);
  1025. }
  1026. //-----------------------------------------------------------------------------
  1027. // Sets the model to be associated with an entity
  1028. //-----------------------------------------------------------------------------
  1029. void UTIL_SetModel( CBaseEntity *pEntity, const char *pModelName )
  1030. {
  1031. // check to see if model was properly precached
  1032. int i = modelinfo->GetModelIndex( pModelName );
  1033. if ( i == -1 )
  1034. {
  1035. Error("%i/%s - %s: UTIL_SetModel: not precached: %s\n", pEntity->entindex(),
  1036. STRING( pEntity->GetEntityName() ),
  1037. pEntity->GetClassname(), pModelName);
  1038. }
  1039. CBaseAnimating *pAnimating = pEntity->GetBaseAnimating();
  1040. if ( pAnimating )
  1041. {
  1042. pAnimating->m_nForceBone = 0;
  1043. }
  1044. pEntity->SetModelName( AllocPooledString( pModelName ) );
  1045. pEntity->SetModelIndex( i ) ;
  1046. SetMinMaxSize(pEntity, vec3_origin, vec3_origin);
  1047. pEntity->SetCollisionBoundsFromModel();
  1048. }
  1049. void UTIL_SetOrigin( CBaseEntity *entity, const Vector &vecOrigin, bool bFireTriggers )
  1050. {
  1051. entity->SetLocalOrigin( vecOrigin );
  1052. if ( bFireTriggers )
  1053. {
  1054. entity->PhysicsTouchTriggers();
  1055. }
  1056. }
  1057. void UTIL_ParticleEffect( const Vector &vecOrigin, const Vector &vecDirection, ULONG ulColor, ULONG ulCount )
  1058. {
  1059. Msg( "UTIL_ParticleEffect: Disabled\n" );
  1060. }
  1061. void UTIL_Smoke( const Vector &origin, const float scale, const float framerate )
  1062. {
  1063. g_pEffects->Smoke( origin, g_sModelIndexSmoke, scale, framerate );
  1064. }
  1065. // snaps a vector to the nearest axis vector (if within epsilon)
  1066. void UTIL_SnapDirectionToAxis( Vector &direction, float epsilon )
  1067. {
  1068. float proj = 1 - epsilon;
  1069. for ( int i = 0; i < 3; i ++ )
  1070. {
  1071. if ( fabs(direction[i]) > proj )
  1072. {
  1073. // snap to axis unit vector
  1074. if ( direction[i] < 0 )
  1075. direction[i] = -1.0f;
  1076. else
  1077. direction[i] = 1.0f;
  1078. direction[(i+1)%3] = 0;
  1079. direction[(i+2)%3] = 0;
  1080. return;
  1081. }
  1082. }
  1083. }
  1084. const char *UTIL_VarArgs( const char *format, ... )
  1085. {
  1086. va_list argptr;
  1087. static char string[1024];
  1088. va_start (argptr, format);
  1089. Q_vsnprintf(string, sizeof(string), format,argptr);
  1090. va_end (argptr);
  1091. return string;
  1092. }
  1093. bool UTIL_IsMasterTriggered(string_t sMaster, CBaseEntity *pActivator)
  1094. {
  1095. if (sMaster != NULL_STRING)
  1096. {
  1097. CBaseEntity *pMaster = gEntList.FindEntityByName( NULL, sMaster, NULL, pActivator );
  1098. if ( pMaster && (pMaster->ObjectCaps() & FCAP_MASTER) )
  1099. {
  1100. return pMaster->IsTriggered( pActivator );
  1101. }
  1102. Warning( "Master was null or not a master!\n");
  1103. }
  1104. // if this isn't a master entity, just say yes.
  1105. return true;
  1106. }
  1107. void UTIL_BloodStream( const Vector &origin, const Vector &direction, int color, int amount )
  1108. {
  1109. if ( !UTIL_ShouldShowBlood( color ) )
  1110. return;
  1111. if ( g_Language.GetInt() == LANGUAGE_GERMAN && color == BLOOD_COLOR_RED )
  1112. color = 0;
  1113. CPVSFilter filter( origin );
  1114. te->BloodStream( filter, 0.0, &origin, &direction, 247, 63, 14, 255, MIN( amount, 255 ) );
  1115. }
  1116. Vector UTIL_RandomBloodVector( void )
  1117. {
  1118. Vector direction;
  1119. direction.x = random->RandomFloat ( -1, 1 );
  1120. direction.y = random->RandomFloat ( -1, 1 );
  1121. direction.z = random->RandomFloat ( 0, 1 );
  1122. return direction;
  1123. }
  1124. //------------------------------------------------------------------------------
  1125. // Purpose : Creates both an decal and any associated impact effects (such
  1126. // as flecks) for the given iDamageType and the trace's end position
  1127. // Input :
  1128. // Output :
  1129. //------------------------------------------------------------------------------
  1130. void UTIL_ImpactTrace( trace_t *pTrace, int iDamageType, const char *pCustomImpactName )
  1131. {
  1132. CBaseEntity *pEntity = pTrace->m_pEnt;
  1133. // Is the entity valid, is the surface sky?
  1134. if ( !pEntity || !UTIL_IsValidEntity( pEntity ) || (pTrace->surface.flags & SURF_SKY) )
  1135. return;
  1136. if ( pTrace->fraction == 1.0 )
  1137. return;
  1138. pEntity->ImpactTrace( pTrace, iDamageType, pCustomImpactName );
  1139. }
  1140. /*
  1141. ==============
  1142. UTIL_PlayerDecalTrace
  1143. A player is trying to apply his custom decal for the spray can.
  1144. Tell connected clients to display it, or use the default spray can decal
  1145. if the custom can't be loaded.
  1146. ==============
  1147. */
  1148. void UTIL_PlayerDecalTrace( trace_t *pTrace, int playernum )
  1149. {
  1150. if (pTrace->fraction == 1.0)
  1151. return;
  1152. CBroadcastRecipientFilter filter;
  1153. te->PlayerDecal( filter, 0.0,
  1154. &pTrace->endpos, playernum, pTrace->m_pEnt->entindex() );
  1155. }
  1156. bool UTIL_TeamsMatch( const char *pTeamName1, const char *pTeamName2 )
  1157. {
  1158. // Everyone matches unless it's teamplay
  1159. if ( !g_pGameRules->IsTeamplay() )
  1160. return true;
  1161. // Both on a team?
  1162. if ( *pTeamName1 != 0 && *pTeamName2 != 0 )
  1163. {
  1164. if ( !stricmp( pTeamName1, pTeamName2 ) ) // Same Team?
  1165. return true;
  1166. }
  1167. return false;
  1168. }
  1169. void UTIL_AxisStringToPointPoint( Vector &start, Vector &end, const char *pString )
  1170. {
  1171. char tmpstr[256];
  1172. Q_strncpy( tmpstr, pString, sizeof(tmpstr) );
  1173. char *pVec = strtok( tmpstr, "," );
  1174. int i = 0;
  1175. while ( pVec != NULL && *pVec )
  1176. {
  1177. if ( i == 0 )
  1178. {
  1179. UTIL_StringToVector( start.Base(), pVec );
  1180. i++;
  1181. }
  1182. else
  1183. {
  1184. UTIL_StringToVector( end.Base(), pVec );
  1185. }
  1186. pVec = strtok( NULL, "," );
  1187. }
  1188. }
  1189. void UTIL_AxisStringToPointDir( Vector &start, Vector &dir, const char *pString )
  1190. {
  1191. Vector end;
  1192. UTIL_AxisStringToPointPoint( start, end, pString );
  1193. dir = end - start;
  1194. VectorNormalize(dir);
  1195. }
  1196. void UTIL_AxisStringToUnitDir( Vector &dir, const char *pString )
  1197. {
  1198. Vector start;
  1199. UTIL_AxisStringToPointDir( start, dir, pString );
  1200. }
  1201. /*
  1202. ==================================================
  1203. UTIL_ClipPunchAngleOffset
  1204. ==================================================
  1205. */
  1206. void UTIL_ClipPunchAngleOffset( QAngle &in, const QAngle &punch, const QAngle &clip )
  1207. {
  1208. QAngle final = in + punch;
  1209. //Clip each component
  1210. for ( int i = 0; i < 3; i++ )
  1211. {
  1212. if ( final[i] > clip[i] )
  1213. {
  1214. final[i] = clip[i];
  1215. }
  1216. else if ( final[i] < -clip[i] )
  1217. {
  1218. final[i] = -clip[i];
  1219. }
  1220. //Return the result
  1221. in[i] = final[i] - punch[i];
  1222. }
  1223. }
  1224. float UTIL_WaterLevel( const Vector &position, float minz, float maxz )
  1225. {
  1226. Vector midUp = position;
  1227. midUp.z = minz;
  1228. if ( !(UTIL_PointContents(midUp) & MASK_WATER) )
  1229. return minz;
  1230. midUp.z = maxz;
  1231. if ( UTIL_PointContents(midUp) & MASK_WATER )
  1232. return maxz;
  1233. float diff = maxz - minz;
  1234. while (diff > 1.0)
  1235. {
  1236. midUp.z = minz + diff/2.0;
  1237. if ( UTIL_PointContents(midUp) & MASK_WATER )
  1238. {
  1239. minz = midUp.z;
  1240. }
  1241. else
  1242. {
  1243. maxz = midUp.z;
  1244. }
  1245. diff = maxz - minz;
  1246. }
  1247. return midUp.z;
  1248. }
  1249. //-----------------------------------------------------------------------------
  1250. // Like UTIL_WaterLevel, but *way* less expensive.
  1251. // I didn't replace UTIL_WaterLevel everywhere to avoid breaking anything.
  1252. //-----------------------------------------------------------------------------
  1253. class CWaterTraceFilter : public CTraceFilter
  1254. {
  1255. public:
  1256. bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
  1257. {
  1258. CBaseEntity *pCollide = EntityFromEntityHandle( pHandleEntity );
  1259. // Static prop case...
  1260. if ( !pCollide )
  1261. return false;
  1262. // Only impact water stuff...
  1263. if ( pCollide->GetSolidFlags() & FSOLID_VOLUME_CONTENTS )
  1264. return true;
  1265. return false;
  1266. }
  1267. };
  1268. float UTIL_FindWaterSurface( const Vector &position, float minz, float maxz )
  1269. {
  1270. Vector vecStart, vecEnd;
  1271. vecStart.Init( position.x, position.y, maxz );
  1272. vecEnd.Init( position.x, position.y, minz );
  1273. Ray_t ray;
  1274. trace_t tr;
  1275. CWaterTraceFilter waterTraceFilter;
  1276. ray.Init( vecStart, vecEnd );
  1277. enginetrace->TraceRay( ray, MASK_WATER, &waterTraceFilter, &tr );
  1278. return tr.endpos.z;
  1279. }
  1280. extern short g_sModelIndexBubbles;// holds the index for the bubbles model
  1281. void UTIL_Bubbles( const Vector& mins, const Vector& maxs, int count )
  1282. {
  1283. Vector mid = (mins + maxs) * 0.5;
  1284. float flHeight = UTIL_WaterLevel( mid, mid.z, mid.z + 1024 );
  1285. flHeight = flHeight - mins.z;
  1286. CPASFilter filter( mid );
  1287. te->Bubbles( filter, 0.0,
  1288. &mins, &maxs, flHeight, g_sModelIndexBubbles, count, 8.0 );
  1289. }
  1290. void UTIL_BubbleTrail( const Vector& from, const Vector& to, int count )
  1291. {
  1292. // Find water surface will return from.z if the from point is above water
  1293. float flStartHeight = UTIL_FindWaterSurface( from, from.z, from.z + 256 );
  1294. flStartHeight = flStartHeight - from.z;
  1295. float flEndHeight = UTIL_FindWaterSurface( to, to.z, to.z + 256 );
  1296. flEndHeight = flEndHeight - to.z;
  1297. if ( ( flStartHeight == 0 ) && ( flEndHeight == 0 ) )
  1298. return;
  1299. float flWaterZ = flStartHeight + from.z;
  1300. const Vector *pFrom = &from;
  1301. const Vector *pTo = &to;
  1302. Vector vecWaterPoint;
  1303. if ( ( flStartHeight == 0 ) || ( flEndHeight == 0 ) )
  1304. {
  1305. if ( flStartHeight == 0 )
  1306. {
  1307. flWaterZ = flEndHeight + to.z;
  1308. }
  1309. float t = IntersectRayWithAAPlane( from, to, 2, 1.0f, flWaterZ );
  1310. Assert( (t >= -1e-3f) && ( t <= 1.0f ) );
  1311. VectorLerp( from, to, t, vecWaterPoint );
  1312. if ( flStartHeight == 0 )
  1313. {
  1314. pFrom = &vecWaterPoint;
  1315. // Reduce the count by the actual length
  1316. count = (int)( count * ( 1.0f - t ) );
  1317. }
  1318. else
  1319. {
  1320. pTo = &vecWaterPoint;
  1321. // Reduce the count by the actual length
  1322. count = (int)( count * t );
  1323. }
  1324. }
  1325. CBroadcastRecipientFilter filter;
  1326. te->BubbleTrail( filter, 0.0, pFrom, pTo, flWaterZ, g_sModelIndexBubbles, count, 8.0 );
  1327. }
  1328. //-----------------------------------------------------------------------------
  1329. // Purpose:
  1330. // Input : Start -
  1331. // End -
  1332. // ModelIndex -
  1333. // FrameStart -
  1334. // FrameRate -
  1335. // Life -
  1336. // Width -
  1337. // Noise -
  1338. // Red -
  1339. // Green -
  1340. // Brightness -
  1341. // Speed -
  1342. //-----------------------------------------------------------------------------
  1343. void UTIL_Beam( Vector &Start, Vector &End, int nModelIndex, int nHaloIndex, unsigned char FrameStart, unsigned char FrameRate,
  1344. float Life, unsigned char Width, unsigned char EndWidth, unsigned char FadeLength, unsigned char Noise, unsigned char Red, unsigned char Green,
  1345. unsigned char Blue, unsigned char Brightness, unsigned char Speed)
  1346. {
  1347. CBroadcastRecipientFilter filter;
  1348. te->BeamPoints( filter, 0.0,
  1349. &Start,
  1350. &End,
  1351. nModelIndex,
  1352. nHaloIndex,
  1353. FrameStart,
  1354. FrameRate,
  1355. Life,
  1356. Width,
  1357. EndWidth,
  1358. FadeLength,
  1359. Noise,
  1360. Red,
  1361. Green,
  1362. Blue,
  1363. Brightness,
  1364. Speed );
  1365. }
  1366. bool UTIL_IsValidEntity( CBaseEntity *pEnt )
  1367. {
  1368. edict_t *pEdict = pEnt->edict();
  1369. if ( !pEdict || pEdict->IsFree() )
  1370. return false;
  1371. return true;
  1372. }
  1373. #define PRECACHE_OTHER_ONCE
  1374. // UNDONE: Do we need this to avoid doing too much of this? Measure startup times and see
  1375. #if defined( PRECACHE_OTHER_ONCE )
  1376. #include "utlsymbol.h"
  1377. class CPrecacheOtherList : public CAutoGameSystem
  1378. {
  1379. public:
  1380. CPrecacheOtherList( char const *name ) : CAutoGameSystem( name )
  1381. {
  1382. }
  1383. virtual void LevelInitPreEntity();
  1384. virtual void LevelShutdownPostEntity();
  1385. bool AddOrMarkPrecached( const char *pClassname );
  1386. private:
  1387. CUtlSymbolTable m_list;
  1388. };
  1389. void CPrecacheOtherList::LevelInitPreEntity()
  1390. {
  1391. m_list.RemoveAll();
  1392. }
  1393. void CPrecacheOtherList::LevelShutdownPostEntity()
  1394. {
  1395. m_list.RemoveAll();
  1396. }
  1397. //-----------------------------------------------------------------------------
  1398. // Purpose: mark or add
  1399. // Input : *pEntity -
  1400. // Output : Returns true on success, false on failure.
  1401. //-----------------------------------------------------------------------------
  1402. bool CPrecacheOtherList::AddOrMarkPrecached( const char *pClassname )
  1403. {
  1404. CUtlSymbol sym = m_list.Find( pClassname );
  1405. if ( sym.IsValid() )
  1406. return false;
  1407. m_list.AddString( pClassname );
  1408. return true;
  1409. }
  1410. CPrecacheOtherList g_PrecacheOtherList( "CPrecacheOtherList" );
  1411. #endif
  1412. //-----------------------------------------------------------------------------
  1413. // Purpose:
  1414. // Input : *szClassname -
  1415. // *modelName -
  1416. //-----------------------------------------------------------------------------
  1417. void UTIL_PrecacheOther( const char *szClassname, const char *modelName )
  1418. {
  1419. #if defined( PRECACHE_OTHER_ONCE )
  1420. // already done this one?, if not, mark as done
  1421. if ( !g_PrecacheOtherList.AddOrMarkPrecached( szClassname ) )
  1422. return;
  1423. #endif
  1424. CBaseEntity *pEntity = CreateEntityByName( szClassname );
  1425. if ( !pEntity )
  1426. {
  1427. Warning( "NULL Ent in UTIL_PrecacheOther\n" );
  1428. return;
  1429. }
  1430. // If we have a specified model, set it before calling precache
  1431. if ( modelName && modelName[0] )
  1432. {
  1433. pEntity->SetModelName( AllocPooledString( modelName ) );
  1434. }
  1435. if (pEntity)
  1436. pEntity->Precache( );
  1437. UTIL_RemoveImmediate( pEntity );
  1438. }
  1439. //=========================================================
  1440. // UTIL_LogPrintf - Prints a logged message to console.
  1441. // Preceded by LOG: ( timestamp ) < message >
  1442. //=========================================================
  1443. void UTIL_LogPrintf( const char *fmt, ... )
  1444. {
  1445. va_list argptr;
  1446. char tempString[1024];
  1447. va_start ( argptr, fmt );
  1448. Q_vsnprintf( tempString, sizeof(tempString), fmt, argptr );
  1449. va_end ( argptr );
  1450. // Print to server console
  1451. engine->LogPrint( tempString );
  1452. }
  1453. //=========================================================
  1454. // UTIL_DotPoints - returns the dot product of a line from
  1455. // src to check and vecdir.
  1456. //=========================================================
  1457. float UTIL_DotPoints ( const Vector &vecSrc, const Vector &vecCheck, const Vector &vecDir )
  1458. {
  1459. Vector2D vec2LOS;
  1460. vec2LOS = ( vecCheck - vecSrc ).AsVector2D();
  1461. Vector2DNormalize( vec2LOS );
  1462. return DotProduct2D(vec2LOS, vecDir.AsVector2D());
  1463. }
  1464. //=========================================================
  1465. // UTIL_StripToken - for redundant keynames
  1466. //=========================================================
  1467. void UTIL_StripToken( const char *pKey, char *pDest, int nDestLength )
  1468. {
  1469. int i = 0;
  1470. while ( ( i < nDestLength - 1 ) && pKey[i] && pKey[i] != '#' )
  1471. {
  1472. pDest[i] = pKey[i];
  1473. ++ i;
  1474. }
  1475. pDest[i] = 0;
  1476. }
  1477. // computes gravity scale for an absolute gravity. Pass the result into CBaseEntity::SetGravity()
  1478. float UTIL_ScaleForGravity( float desiredGravity )
  1479. {
  1480. float worldGravity = GetCurrentGravity();
  1481. return worldGravity > 0 ? desiredGravity / worldGravity : 0;
  1482. }
  1483. //-----------------------------------------------------------------------------
  1484. // Purpose: Implemented for mathlib.c error handling
  1485. // Input : *error -
  1486. //-----------------------------------------------------------------------------
  1487. extern "C" void Sys_Error( char *error, ... )
  1488. {
  1489. va_list argptr;
  1490. char string[1024];
  1491. va_start( argptr, error );
  1492. Q_vsnprintf( string, sizeof(string), error, argptr );
  1493. va_end( argptr );
  1494. Warning( "%s", string );
  1495. Assert(0);
  1496. }
  1497. //-----------------------------------------------------------------------------
  1498. // Purpose: Spawns an entity into the game, initializing it with the map ent data block
  1499. // Input : *pEntity - the newly created entity
  1500. // *mapData - pointer a block of entity map data
  1501. // Output : -1 if the entity was not successfully created; 0 on success
  1502. //-----------------------------------------------------------------------------
  1503. int DispatchSpawn( CBaseEntity *pEntity )
  1504. {
  1505. if ( pEntity )
  1506. {
  1507. MDLCACHE_CRITICAL_SECTION();
  1508. // keep a smart pointer that will now if the object gets deleted
  1509. EHANDLE pEntSafe;
  1510. pEntSafe = pEntity;
  1511. // Initialize these or entities who don't link to the world won't have anything in here
  1512. // is this necessary?
  1513. //pEntity->SetAbsMins( pEntity->GetOrigin() - Vector(1,1,1) );
  1514. //pEntity->SetAbsMaxs( pEntity->GetOrigin() + Vector(1,1,1) );
  1515. #if defined(TRACK_ENTITY_MEMORY) && defined(USE_MEM_DEBUG)
  1516. const char *pszClassname = NULL;
  1517. int iClassname = ((CEntityFactoryDictionary*)EntityFactoryDictionary())->m_Factories.Find( pEntity->GetClassname() );
  1518. if ( iClassname != ((CEntityFactoryDictionary*)EntityFactoryDictionary())->m_Factories.InvalidIndex() )
  1519. pszClassname = ((CEntityFactoryDictionary*)EntityFactoryDictionary())->m_Factories.GetElementName( iClassname );
  1520. if ( pszClassname )
  1521. {
  1522. MemAlloc_PushAllocDbgInfo( pszClassname, __LINE__ );
  1523. }
  1524. #endif
  1525. bool bAsyncAnims = mdlcache->SetAsyncLoad( MDLCACHE_ANIMBLOCK, false );
  1526. CBaseAnimating *pAnimating = pEntity->GetBaseAnimating();
  1527. if (!pAnimating)
  1528. {
  1529. pEntity->Spawn();
  1530. }
  1531. else
  1532. {
  1533. // Don't allow the PVS check to skip animation setup during spawning
  1534. pAnimating->SetBoneCacheFlags( BCF_IS_IN_SPAWN );
  1535. pEntity->Spawn();
  1536. if ( pEntSafe != NULL )
  1537. pAnimating->ClearBoneCacheFlags( BCF_IS_IN_SPAWN );
  1538. }
  1539. mdlcache->SetAsyncLoad( MDLCACHE_ANIMBLOCK, bAsyncAnims );
  1540. #if defined(TRACK_ENTITY_MEMORY) && defined(USE_MEM_DEBUG)
  1541. if ( pszClassname )
  1542. {
  1543. MemAlloc_PopAllocDbgInfo();
  1544. }
  1545. #endif
  1546. // Try to get the pointer again, in case the spawn function deleted the entity.
  1547. // UNDONE: Spawn() should really return a code to ask that the entity be deleted, but
  1548. // that would touch too much code for me to do that right now.
  1549. if ( pEntSafe == NULL || pEntity->IsMarkedForDeletion() )
  1550. return -1;
  1551. if ( pEntity->m_iGlobalname != NULL_STRING )
  1552. {
  1553. // Handle global stuff here
  1554. int globalIndex = GlobalEntity_GetIndex( pEntity->m_iGlobalname );
  1555. if ( globalIndex >= 0 )
  1556. {
  1557. // Already dead? delete
  1558. if ( GlobalEntity_GetState(globalIndex) == GLOBAL_DEAD )
  1559. {
  1560. pEntity->Remove();
  1561. return -1;
  1562. }
  1563. else if ( !FStrEq(STRING(gpGlobals->mapname), GlobalEntity_GetMap(globalIndex)) )
  1564. {
  1565. pEntity->MakeDormant(); // Hasn't been moved to this level yet, wait but stay alive
  1566. }
  1567. // In this level & not dead, continue on as normal
  1568. }
  1569. else
  1570. {
  1571. // Spawned entities default to 'On'
  1572. GlobalEntity_Add( pEntity->m_iGlobalname, gpGlobals->mapname, GLOBAL_ON );
  1573. // Msg( "Added global entity %s (%s)\n", pEntity->GetClassname(), STRING(pEntity->m_iGlobalname) );
  1574. }
  1575. }
  1576. gEntList.NotifySpawn( pEntity );
  1577. }
  1578. return 0;
  1579. }
  1580. // UNDONE: This could be a better test - can we run the absbox through the bsp and see
  1581. // if it contains any solid space? or would that eliminate some entities we want to keep?
  1582. int UTIL_EntityInSolid( CBaseEntity *ent )
  1583. {
  1584. Vector point;
  1585. CBaseEntity *pParent = ent->GetMoveParent();
  1586. // HACKHACK -- If you're attached to a client, always go through
  1587. if ( pParent )
  1588. {
  1589. if ( pParent->IsPlayer() )
  1590. return 0;
  1591. ent = ent->GetRootMoveParent();
  1592. }
  1593. point = ent->WorldSpaceCenter();
  1594. return ( enginetrace->GetPointContents( point ) & MASK_SOLID );
  1595. }
  1596. //-----------------------------------------------------------------------------
  1597. // Purpose: Initialize the matrix from an entity
  1598. // Input : *pEntity -
  1599. //-----------------------------------------------------------------------------
  1600. void EntityMatrix::InitFromEntity( CBaseEntity *pEntity, int iAttachment )
  1601. {
  1602. if ( !pEntity )
  1603. {
  1604. Identity();
  1605. return;
  1606. }
  1607. // Get an attachment's matrix?
  1608. if ( iAttachment != 0 )
  1609. {
  1610. CBaseAnimating *pAnimating = pEntity->GetBaseAnimating();
  1611. if ( pAnimating && pAnimating->GetModelPtr() )
  1612. {
  1613. Vector vOrigin;
  1614. QAngle vAngles;
  1615. if ( pAnimating->GetAttachment( iAttachment, vOrigin, vAngles ) )
  1616. {
  1617. ((VMatrix *)this)->SetupMatrixOrgAngles( vOrigin, vAngles );
  1618. return;
  1619. }
  1620. }
  1621. }
  1622. ((VMatrix *)this)->SetupMatrixOrgAngles( pEntity->GetAbsOrigin(), pEntity->GetAbsAngles() );
  1623. }
  1624. void EntityMatrix::InitFromEntityLocal( CBaseEntity *entity )
  1625. {
  1626. if ( !entity || !entity->edict() )
  1627. {
  1628. Identity();
  1629. return;
  1630. }
  1631. ((VMatrix *)this)->SetupMatrixOrgAngles( entity->GetLocalOrigin(), entity->GetLocalAngles() );
  1632. }
  1633. //==================================================
  1634. // Purpose:
  1635. // Input:
  1636. // Output:
  1637. //==================================================
  1638. void UTIL_ValidateSoundName( string_t &name, const char *defaultStr )
  1639. {
  1640. if ( ( !name ||
  1641. strlen( (char*) STRING( name ) ) < 1 ) ||
  1642. !Q_stricmp( (char *)STRING(name), "0" ) )
  1643. {
  1644. name = AllocPooledString( defaultStr );
  1645. }
  1646. }
  1647. //-----------------------------------------------------------------------------
  1648. // Purpose: Helper for UTIL_FindClientInPVS
  1649. // Input : check - last checked client
  1650. // Output : static int UTIL_GetNewCheckClient
  1651. //-----------------------------------------------------------------------------
  1652. // FIXME: include bspfile.h here?
  1653. class CCheckClient : public CAutoGameSystem
  1654. {
  1655. public:
  1656. CCheckClient( char const *name ) : CAutoGameSystem( name )
  1657. {
  1658. }
  1659. void LevelInitPreEntity()
  1660. {
  1661. m_checkCluster = -1;
  1662. m_lastcheck = 1;
  1663. m_lastchecktime = -1;
  1664. m_bClientPVSIsExpanded = false;
  1665. }
  1666. byte m_checkPVS[MAX_MAP_LEAFS/8];
  1667. byte m_checkVisibilityPVS[MAX_MAP_LEAFS/8];
  1668. int m_checkCluster;
  1669. int m_lastcheck;
  1670. float m_lastchecktime;
  1671. bool m_bClientPVSIsExpanded;
  1672. };
  1673. CCheckClient g_CheckClient( "CCheckClient" );
  1674. static int UTIL_GetNewCheckClient( int check )
  1675. {
  1676. int i;
  1677. edict_t *ent;
  1678. Vector org;
  1679. // cycle to the next one
  1680. if (check < 1)
  1681. check = 1;
  1682. if (check > gpGlobals->maxClients)
  1683. check = gpGlobals->maxClients;
  1684. if (check == gpGlobals->maxClients)
  1685. i = 1;
  1686. else
  1687. i = check + 1;
  1688. for ( ; ; i++)
  1689. {
  1690. if ( i > gpGlobals->maxClients )
  1691. {
  1692. i = 1;
  1693. }
  1694. ent = engine->PEntityOfEntIndex( i );
  1695. if ( !ent )
  1696. continue;
  1697. // Looped but didn't find anything else
  1698. if ( i == check )
  1699. break;
  1700. if ( !ent->GetUnknown() )
  1701. continue;
  1702. CBaseEntity *entity = GetContainingEntity( ent );
  1703. if ( !entity )
  1704. continue;
  1705. if ( entity->GetFlags() & FL_NOTARGET )
  1706. continue;
  1707. // anything that is a client, or has a client as an enemy
  1708. break;
  1709. }
  1710. if ( i != check )
  1711. {
  1712. memset( g_CheckClient.m_checkVisibilityPVS, 0, sizeof(g_CheckClient.m_checkVisibilityPVS) );
  1713. g_CheckClient.m_bClientPVSIsExpanded = false;
  1714. }
  1715. if ( ent )
  1716. {
  1717. // get the PVS for the entity
  1718. CBaseEntity *pce = GetContainingEntity( ent );
  1719. if ( !pce )
  1720. return i;
  1721. org = pce->EyePosition();
  1722. int clusterIndex = engine->GetClusterForOrigin( org );
  1723. if ( clusterIndex != g_CheckClient.m_checkCluster )
  1724. {
  1725. g_CheckClient.m_checkCluster = clusterIndex;
  1726. engine->GetPVSForCluster( clusterIndex, sizeof(g_CheckClient.m_checkPVS), g_CheckClient.m_checkPVS );
  1727. }
  1728. }
  1729. return i;
  1730. }
  1731. //-----------------------------------------------------------------------------
  1732. // Gets the current check client....
  1733. //-----------------------------------------------------------------------------
  1734. static edict_t *UTIL_GetCurrentCheckClient()
  1735. {
  1736. edict_t *ent;
  1737. // find a new check if on a new frame
  1738. float delta = gpGlobals->curtime - g_CheckClient.m_lastchecktime;
  1739. if ( delta >= 0.1 || delta < 0 )
  1740. {
  1741. g_CheckClient.m_lastcheck = UTIL_GetNewCheckClient( g_CheckClient.m_lastcheck );
  1742. g_CheckClient.m_lastchecktime = gpGlobals->curtime;
  1743. }
  1744. // return check if it might be visible
  1745. ent = engine->PEntityOfEntIndex( g_CheckClient.m_lastcheck );
  1746. // Allow dead clients -- JAY
  1747. // Our monsters know the difference, and this function gates alot of behavior
  1748. // It's annoying to die and see monsters stop thinking because you're no longer
  1749. // "in" their PVS
  1750. if ( !ent || ent->IsFree() || !ent->GetUnknown())
  1751. {
  1752. return NULL;
  1753. }
  1754. return ent;
  1755. }
  1756. void UTIL_SetClientVisibilityPVS( edict_t *pClient, const unsigned char *pvs, int pvssize )
  1757. {
  1758. if ( pClient == UTIL_GetCurrentCheckClient() )
  1759. {
  1760. Assert( pvssize <= sizeof(g_CheckClient.m_checkVisibilityPVS) );
  1761. g_CheckClient.m_bClientPVSIsExpanded = false;
  1762. unsigned *pFrom = (unsigned *)pvs;
  1763. unsigned *pMask = (unsigned *)g_CheckClient.m_checkPVS;
  1764. unsigned *pTo = (unsigned *)g_CheckClient.m_checkVisibilityPVS;
  1765. int limit = pvssize / 4;
  1766. int i;
  1767. for ( i = 0; i < limit; i++ )
  1768. {
  1769. pTo[i] = pFrom[i] & ~pMask[i];
  1770. if ( pFrom[i] )
  1771. {
  1772. g_CheckClient.m_bClientPVSIsExpanded = true;
  1773. }
  1774. }
  1775. int remainder = pvssize % 4;
  1776. for ( i = 0; i < remainder; i++ )
  1777. {
  1778. ((unsigned char *)&pTo[limit])[i] = ((unsigned char *)&pFrom[limit])[i] & !((unsigned char *)&pMask[limit])[i];
  1779. if ( ((unsigned char *)&pFrom[limit])[i] != 0)
  1780. {
  1781. g_CheckClient.m_bClientPVSIsExpanded = true;
  1782. }
  1783. }
  1784. }
  1785. }
  1786. bool UTIL_ClientPVSIsExpanded()
  1787. {
  1788. return g_CheckClient.m_bClientPVSIsExpanded;
  1789. }
  1790. //-----------------------------------------------------------------------------
  1791. // Purpose: Returns a client (or object that has a client enemy) that would be a valid target.
  1792. // If there are more than one valid options, they are cycled each frame
  1793. // If (self.origin + self.viewofs) is not in the PVS of the current target, it is not returned at all.
  1794. // Input : *pEdict -
  1795. // Output : edict_t*
  1796. //-----------------------------------------------------------------------------
  1797. CBaseEntity *UTIL_FindClientInPVS( const Vector &vecBoxMins, const Vector &vecBoxMaxs )
  1798. {
  1799. edict_t *ent = UTIL_GetCurrentCheckClient();
  1800. if ( !ent )
  1801. {
  1802. return NULL;
  1803. }
  1804. if ( !engine->CheckBoxInPVS( vecBoxMins, vecBoxMaxs, g_CheckClient.m_checkPVS, sizeof( g_CheckClient.m_checkPVS ) ) )
  1805. {
  1806. return NULL;
  1807. }
  1808. // might be able to see it
  1809. return GetContainingEntity( ent );
  1810. }
  1811. //-----------------------------------------------------------------------------
  1812. // Purpose: Returns a client (or object that has a client enemy) that would be a valid target.
  1813. // If there are more than one valid options, they are cycled each frame
  1814. // If (self.origin + self.viewofs) is not in the PVS of the current target, it is not returned at all.
  1815. // Input : *pEdict -
  1816. // Output : edict_t*
  1817. //-----------------------------------------------------------------------------
  1818. ConVar sv_strict_notarget( "sv_strict_notarget", "0", 0, "If set, notarget will cause entities to never think they are in the pvs" );
  1819. static edict_t *UTIL_FindClientInPVSGuts(edict_t *pEdict, unsigned char *pvs, unsigned pvssize )
  1820. {
  1821. Vector view;
  1822. edict_t *ent = UTIL_GetCurrentCheckClient();
  1823. if ( !ent )
  1824. {
  1825. return NULL;
  1826. }
  1827. CBaseEntity *pPlayerEntity = GetContainingEntity( ent );
  1828. if( (!pPlayerEntity || (pPlayerEntity->GetFlags() & FL_NOTARGET)) && sv_strict_notarget.GetBool() )
  1829. {
  1830. return NULL;
  1831. }
  1832. // if current entity can't possibly see the check entity, return 0
  1833. // UNDONE: Build a box for this and do it over that box
  1834. // UNDONE: Use CM_BoxLeafnums()
  1835. CBaseEntity *pe = GetContainingEntity( pEdict );
  1836. if ( pe )
  1837. {
  1838. view = pe->EyePosition();
  1839. if ( !engine->CheckOriginInPVS( view, pvs, pvssize ) )
  1840. {
  1841. return NULL;
  1842. }
  1843. }
  1844. // might be able to see it
  1845. return ent;
  1846. }
  1847. //-----------------------------------------------------------------------------
  1848. // Purpose: Returns a client that could see the entity directly
  1849. //-----------------------------------------------------------------------------
  1850. edict_t *UTIL_FindClientInPVS(edict_t *pEdict)
  1851. {
  1852. return UTIL_FindClientInPVSGuts( pEdict, g_CheckClient.m_checkPVS, sizeof( g_CheckClient.m_checkPVS ) );
  1853. }
  1854. //-----------------------------------------------------------------------------
  1855. // Purpose: Returns a client that could see the entity, including through a camera
  1856. //-----------------------------------------------------------------------------
  1857. edict_t *UTIL_FindClientInVisibilityPVS( edict_t *pEdict )
  1858. {
  1859. return UTIL_FindClientInPVSGuts( pEdict, g_CheckClient.m_checkVisibilityPVS, sizeof( g_CheckClient.m_checkVisibilityPVS ) );
  1860. }
  1861. //-----------------------------------------------------------------------------
  1862. // Purpose: Returns a chain of entities within the PVS of another entity (client)
  1863. // starting_ent is the ent currently at in the list
  1864. // a starting_ent of NULL signifies the beginning of a search
  1865. // Input : *pplayer -
  1866. // *starting_ent -
  1867. // Output : edict_t
  1868. //-----------------------------------------------------------------------------
  1869. CBaseEntity *UTIL_EntitiesInPVS( CBaseEntity *pPVSEntity, CBaseEntity *pStartingEntity )
  1870. {
  1871. Vector org;
  1872. static byte pvs[ MAX_MAP_CLUSTERS/8 ];
  1873. static Vector lastOrg( 0, 0, 0 );
  1874. static int lastCluster = -1;
  1875. if ( !pPVSEntity )
  1876. return NULL;
  1877. // NOTE: These used to be caching code here to prevent this from
  1878. // being called over+over which breaks when you go back + forth
  1879. // across level transitions
  1880. // So, we'll always get the PVS each time we start a new EntitiesInPVS iteration.
  1881. // Given that weapon_binocs + leveltransition code is the only current clients
  1882. // of this, this seems safe.
  1883. if ( !pStartingEntity )
  1884. {
  1885. org = pPVSEntity->EyePosition();
  1886. int clusterIndex = engine->GetClusterForOrigin( org );
  1887. Assert( clusterIndex >= 0 );
  1888. engine->GetPVSForCluster( clusterIndex, sizeof(pvs), pvs );
  1889. }
  1890. for ( CBaseEntity *pEntity = gEntList.NextEnt(pStartingEntity); pEntity; pEntity = gEntList.NextEnt(pEntity) )
  1891. {
  1892. // Only return attached ents.
  1893. if ( !pEntity->edict() )
  1894. continue;
  1895. CBaseEntity *pParent = pEntity->GetRootMoveParent();
  1896. Vector vecSurroundMins, vecSurroundMaxs;
  1897. pParent->CollisionProp()->WorldSpaceSurroundingBounds( &vecSurroundMins, &vecSurroundMaxs );
  1898. if ( !engine->CheckBoxInPVS( vecSurroundMins, vecSurroundMaxs, pvs, sizeof( pvs ) ) )
  1899. continue;
  1900. return pEntity;
  1901. }
  1902. return NULL;
  1903. }
  1904. //-----------------------------------------------------------------------------
  1905. // Purpose: Get the predicted postion of an entity of a certain number of seconds
  1906. // Use this function with caution, it has great potential for annoying the player, especially
  1907. // if used for target firing predition
  1908. // Input : *pTarget - target entity to predict
  1909. // timeDelta - amount of time to predict ahead (in seconds)
  1910. // &vecPredictedPosition - output
  1911. //-----------------------------------------------------------------------------
  1912. void UTIL_PredictedPosition( CBaseEntity *pTarget, float flTimeDelta, Vector *vecPredictedPosition )
  1913. {
  1914. if ( ( pTarget == NULL ) || ( vecPredictedPosition == NULL ) )
  1915. return;
  1916. Vector vecPredictedVel;
  1917. //FIXME: Should we look at groundspeed or velocity for non-clients??
  1918. //Get the proper velocity to predict with
  1919. CBasePlayer *pPlayer = ToBasePlayer( pTarget );
  1920. //Player works differently than other entities
  1921. if ( pPlayer != NULL )
  1922. {
  1923. if ( pPlayer->IsInAVehicle() )
  1924. {
  1925. //Calculate the predicted position in this vehicle
  1926. vecPredictedVel = pPlayer->GetVehicleEntity()->GetSmoothedVelocity();
  1927. }
  1928. else
  1929. {
  1930. //Get the player's stored velocity
  1931. vecPredictedVel = pPlayer->GetSmoothedVelocity();
  1932. }
  1933. }
  1934. else
  1935. {
  1936. // See if we're a combat character in a vehicle
  1937. CBaseCombatCharacter *pCCTarget = pTarget->MyCombatCharacterPointer();
  1938. if ( pCCTarget != NULL && pCCTarget->IsInAVehicle() )
  1939. {
  1940. //Calculate the predicted position in this vehicle
  1941. vecPredictedVel = pCCTarget->GetVehicleEntity()->GetSmoothedVelocity();
  1942. }
  1943. else
  1944. {
  1945. // See if we're an animating entity
  1946. CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating *>(pTarget);
  1947. if ( pAnimating != NULL )
  1948. {
  1949. vecPredictedVel = pAnimating->GetGroundSpeedVelocity();
  1950. }
  1951. else
  1952. {
  1953. // Otherwise we're a vanilla entity
  1954. vecPredictedVel = pTarget->GetSmoothedVelocity();
  1955. }
  1956. }
  1957. }
  1958. //Get the result
  1959. (*vecPredictedPosition) = pTarget->GetAbsOrigin() + ( vecPredictedVel * flTimeDelta );
  1960. }
  1961. //-----------------------------------------------------------------------------
  1962. // Purpose: Points the destination entity at the target entity
  1963. // Input : *pDest - entity to be pointed at the target
  1964. // *pTarget - target to point at
  1965. //-----------------------------------------------------------------------------
  1966. bool UTIL_PointAtEntity( CBaseEntity *pDest, CBaseEntity *pTarget )
  1967. {
  1968. if ( ( pDest == NULL ) || ( pTarget == NULL ) )
  1969. {
  1970. return false;
  1971. }
  1972. Vector dir = (pTarget->GetAbsOrigin() - pDest->GetAbsOrigin());
  1973. VectorNormalize( dir );
  1974. //Store off as angles
  1975. QAngle angles;
  1976. VectorAngles( dir, angles );
  1977. pDest->SetLocalAngles( angles );
  1978. pDest->SetAbsAngles( angles );
  1979. return true;
  1980. }
  1981. //-----------------------------------------------------------------------------
  1982. // Purpose: Points the destination entity at the target entity by name
  1983. // Input : *pDest - entity to be pointed at the target
  1984. // strTarget - name of entity to target (will only choose the first!)
  1985. //-----------------------------------------------------------------------------
  1986. void UTIL_PointAtNamedEntity( CBaseEntity *pDest, string_t strTarget )
  1987. {
  1988. //Attempt to find the entity
  1989. if ( !UTIL_PointAtEntity( pDest, gEntList.FindEntityByName( NULL, strTarget ) ) )
  1990. {
  1991. DevMsg( 1, "%s (%s) was unable to point at an entity named: %s\n", pDest->GetClassname(), pDest->GetDebugName(), STRING( strTarget ) );
  1992. }
  1993. }
  1994. //-----------------------------------------------------------------------------
  1995. // Purpose: Copy the pose parameter values from one entity to the other
  1996. // Input : *pSourceEntity - entity to copy from
  1997. // *pDestEntity - entity to copy to
  1998. //-----------------------------------------------------------------------------
  1999. bool UTIL_TransferPoseParameters( CBaseEntity *pSourceEntity, CBaseEntity *pDestEntity )
  2000. {
  2001. CBaseAnimating *pSourceBaseAnimating = dynamic_cast<CBaseAnimating*>( pSourceEntity );
  2002. CBaseAnimating *pDestBaseAnimating = dynamic_cast<CBaseAnimating*>( pDestEntity );
  2003. if ( !pSourceBaseAnimating || !pDestBaseAnimating )
  2004. return false;
  2005. for ( int iPose = 0; iPose < MAXSTUDIOPOSEPARAM; ++iPose )
  2006. {
  2007. pDestBaseAnimating->SetPoseParameter( iPose, pSourceBaseAnimating->GetPoseParameter( iPose ) );
  2008. }
  2009. return true;
  2010. }
  2011. //-----------------------------------------------------------------------------
  2012. // Purpose: Make a muzzle flash appear
  2013. // Input : &origin - position of the muzzle flash
  2014. // &angles - angles of the fire direction
  2015. // scale - scale of the muzzle flash
  2016. // type - type of muzzle flash
  2017. //-----------------------------------------------------------------------------
  2018. void UTIL_MuzzleFlash( const Vector &origin, const QAngle &angles, int scale, int type )
  2019. {
  2020. CPASFilter filter( origin );
  2021. te->MuzzleFlash( filter, 0.0f, origin, angles, scale, type );
  2022. }
  2023. //-----------------------------------------------------------------------------
  2024. // Purpose:
  2025. // Input : vStartPos - start of the line
  2026. // vEndPos - end of the line
  2027. // vPoint - point to find nearest point to on specified line
  2028. // clampEnds - clamps returned points to being on the line segment specified
  2029. // Output : Vector - nearest point on the specified line
  2030. //-----------------------------------------------------------------------------
  2031. Vector UTIL_PointOnLineNearestPoint(const Vector& vStartPos, const Vector& vEndPos, const Vector& vPoint, bool clampEnds )
  2032. {
  2033. Vector vEndToStart = (vEndPos - vStartPos);
  2034. Vector vOrgToStart = (vPoint - vStartPos);
  2035. float fNumerator = DotProduct(vEndToStart,vOrgToStart);
  2036. float fDenominator = vEndToStart.Length() * vOrgToStart.Length();
  2037. float fIntersectDist = vOrgToStart.Length()*(fNumerator/fDenominator);
  2038. float flLineLength = VectorNormalize( vEndToStart );
  2039. if ( clampEnds )
  2040. {
  2041. fIntersectDist = clamp( fIntersectDist, 0.0f, flLineLength );
  2042. }
  2043. Vector vIntersectPos = vStartPos + vEndToStart * fIntersectDist;
  2044. return vIntersectPos;
  2045. }
  2046. //-----------------------------------------------------------------------------
  2047. //-----------------------------------------------------------------------------
  2048. AngularImpulse WorldToLocalRotation( const VMatrix &localToWorld, const Vector &worldAxis, float rotation )
  2049. {
  2050. // fix axes of rotation to match axes of vector
  2051. Vector rot = worldAxis * rotation;
  2052. // since the matrix maps local to world, do a transpose rotation to get world to local
  2053. AngularImpulse ang = localToWorld.VMul3x3Transpose( rot );
  2054. return ang;
  2055. }
  2056. //-----------------------------------------------------------------------------
  2057. // Purpose:
  2058. // Input : *filename -
  2059. // *pLength -
  2060. // Output : byte
  2061. //-----------------------------------------------------------------------------
  2062. byte *UTIL_LoadFileForMe( const char *filename, int *pLength )
  2063. {
  2064. void *buffer = NULL;
  2065. int length = filesystem->ReadFileEx( filename, "GAME", &buffer, true, true );
  2066. if ( pLength )
  2067. {
  2068. *pLength = length;
  2069. }
  2070. return (byte *)buffer;
  2071. }
  2072. //-----------------------------------------------------------------------------
  2073. // Purpose:
  2074. // Input : *buffer -
  2075. //-----------------------------------------------------------------------------
  2076. void UTIL_FreeFile( byte *buffer )
  2077. {
  2078. filesystem->FreeOptimalReadBuffer( buffer );
  2079. }
  2080. //-----------------------------------------------------------------------------
  2081. // Purpose: Determines whether an entity is within a certain angular tolerance to viewer
  2082. // Input : *pEntity - entity which is the "viewer"
  2083. // vecPosition - position to test against
  2084. // flTolerance - tolerance (as dot-product)
  2085. // *pflDot - if not NULL, holds the
  2086. // Output : Returns true on success, false on failure.
  2087. //-----------------------------------------------------------------------------
  2088. bool UTIL_IsFacingWithinTolerance( CBaseEntity *pViewer, const Vector &vecPosition, float flDotTolerance, float *pflDot /*= NULL*/ )
  2089. {
  2090. if ( pflDot )
  2091. {
  2092. *pflDot = 0.0f;
  2093. }
  2094. // Required elements
  2095. if ( pViewer == NULL )
  2096. return false;
  2097. Vector forward;
  2098. pViewer->GetVectors( &forward, NULL, NULL );
  2099. Vector dir = vecPosition - pViewer->GetAbsOrigin();
  2100. VectorNormalize( dir );
  2101. // Larger dot product corresponds to a smaller angle
  2102. float flDot = dir.Dot( forward );
  2103. // Return the result
  2104. if ( pflDot )
  2105. {
  2106. *pflDot = flDot;
  2107. }
  2108. // Within the goal tolerance
  2109. if ( flDot >= flDotTolerance )
  2110. return true;
  2111. return false;
  2112. }
  2113. //-----------------------------------------------------------------------------
  2114. // Purpose: Determines whether an entity is within a certain angular tolerance to viewer
  2115. // Input : *pEntity - entity which is the "viewer"
  2116. // *pTarget - entity to test against
  2117. // flTolerance - tolerance (as dot-product)
  2118. // *pflDot - if not NULL, holds the
  2119. // Output : Returns true on success, false on failure.
  2120. //-----------------------------------------------------------------------------
  2121. bool UTIL_IsFacingWithinTolerance( CBaseEntity *pViewer, CBaseEntity *pTarget, float flDotTolerance, float *pflDot /*= NULL*/ )
  2122. {
  2123. if ( pViewer == NULL || pTarget == NULL )
  2124. return false;
  2125. return UTIL_IsFacingWithinTolerance( pViewer, pTarget->GetAbsOrigin(), flDotTolerance, pflDot );
  2126. }
  2127. //-----------------------------------------------------------------------------
  2128. // Purpose: Fills in color for debug purposes based on a relationship
  2129. // Input : nRelationship - relationship to test
  2130. // *pR, *pG, *pB - colors to fill
  2131. //-----------------------------------------------------------------------------
  2132. void UTIL_GetDebugColorForRelationship( int nRelationship, int &r, int &g, int &b )
  2133. {
  2134. switch ( nRelationship )
  2135. {
  2136. case D_LI:
  2137. r = 0;
  2138. g = 255;
  2139. b = 0;
  2140. break;
  2141. case D_NU:
  2142. r = 0;
  2143. g = 0;
  2144. b = 255;
  2145. break;
  2146. case D_HT:
  2147. r = 255;
  2148. g = 0;
  2149. b = 0;
  2150. break;
  2151. case D_FR:
  2152. r = 255;
  2153. g = 255;
  2154. b = 0;
  2155. break;
  2156. default:
  2157. r = 255;
  2158. g = 255;
  2159. b = 255;
  2160. break;
  2161. }
  2162. }
  2163. void LoadAndSpawnEntities_ParseEntKVBlockHelper( CBaseEntity *pNode, KeyValues *pkvNode )
  2164. {
  2165. KeyValues *pkvNodeData = pkvNode->GetFirstSubKey();
  2166. while ( pkvNodeData )
  2167. {
  2168. // Handle the connections block
  2169. if ( !Q_strcmp(pkvNodeData->GetName(), "connections") )
  2170. {
  2171. LoadAndSpawnEntities_ParseEntKVBlockHelper( pNode, pkvNodeData );
  2172. }
  2173. else
  2174. {
  2175. pNode->KeyValue( pkvNodeData->GetName(), pkvNodeData->GetString() );
  2176. }
  2177. pkvNodeData = pkvNodeData->GetNextKey();
  2178. }
  2179. }
  2180. //-----------------------------------------------------------------------------
  2181. // Purpose: Loads and parses a file and spawns entities defined in it.
  2182. //-----------------------------------------------------------------------------
  2183. bool UTIL_LoadAndSpawnEntitiesFromScript( CUtlVector <CBaseEntity*> &entities, const char *pScriptFile, const char *pBlock, bool bActivate )
  2184. {
  2185. KeyValues *pkvFile = new KeyValues( pBlock );
  2186. if ( pkvFile->LoadFromFile( filesystem, pScriptFile, "MOD" ) )
  2187. {
  2188. // Load each block, and spawn the entities
  2189. KeyValues *pkvNode = pkvFile->GetFirstSubKey();
  2190. while ( pkvNode )
  2191. {
  2192. // Get name
  2193. const char *pNodeName = pkvNode->GetName();
  2194. if ( stricmp( pNodeName, "entity" ) )
  2195. {
  2196. pkvNode = pkvNode->GetNextKey();
  2197. continue;
  2198. }
  2199. KeyValues *pClassname = pkvNode->FindKey( "classname" );
  2200. if ( pClassname )
  2201. {
  2202. // Use the classname instead
  2203. pNodeName = pClassname->GetString();
  2204. }
  2205. // Spawn the entity
  2206. CBaseEntity *pNode = CreateEntityByName( pNodeName );
  2207. if ( pNode )
  2208. {
  2209. LoadAndSpawnEntities_ParseEntKVBlockHelper( pNode, pkvNode );
  2210. DispatchSpawn( pNode );
  2211. entities.AddToTail( pNode );
  2212. }
  2213. else
  2214. {
  2215. Warning( "UTIL_LoadAndSpawnEntitiesFromScript: Failed to spawn entity, type: '%s'\n", pNodeName );
  2216. }
  2217. // Move to next entity
  2218. pkvNode = pkvNode->GetNextKey();
  2219. }
  2220. if ( bActivate == true )
  2221. {
  2222. bool bAsyncAnims = mdlcache->SetAsyncLoad( MDLCACHE_ANIMBLOCK, false );
  2223. // Then activate all the entities
  2224. for ( int i = 0; i < entities.Count(); i++ )
  2225. {
  2226. entities[i]->Activate();
  2227. }
  2228. mdlcache->SetAsyncLoad( MDLCACHE_ANIMBLOCK, bAsyncAnims );
  2229. }
  2230. }
  2231. else
  2232. return false;
  2233. return true;
  2234. }
  2235. //-----------------------------------------------------------------------------
  2236. // Purpose: Convert a vector an angle from worldspace to the entity's parent's local space
  2237. // Input : *pEntity - Entity whose parent we're concerned with
  2238. //-----------------------------------------------------------------------------
  2239. void UTIL_ParentToWorldSpace( CBaseEntity *pEntity, Vector &vecPosition, QAngle &vecAngles )
  2240. {
  2241. if ( pEntity == NULL )
  2242. return;
  2243. // Construct the entity-to-world matrix
  2244. // Start with making an entity-to-parent matrix
  2245. matrix3x4_t matEntityToParent;
  2246. AngleMatrix( vecAngles, matEntityToParent );
  2247. MatrixSetColumn( vecPosition, 3, matEntityToParent );
  2248. // concatenate with our parent's transform
  2249. matrix3x4_t matScratch, matResult;
  2250. matrix3x4_t matParentToWorld;
  2251. if ( pEntity->GetParent() != NULL )
  2252. {
  2253. matParentToWorld = pEntity->GetParentToWorldTransform( matScratch );
  2254. }
  2255. else
  2256. {
  2257. matParentToWorld = pEntity->EntityToWorldTransform();
  2258. }
  2259. ConcatTransforms( matParentToWorld, matEntityToParent, matResult );
  2260. // pull our absolute position out of the matrix
  2261. MatrixGetColumn( matResult, 3, vecPosition );
  2262. MatrixAngles( matResult, vecAngles );
  2263. }
  2264. //-----------------------------------------------------------------------------
  2265. // Purpose: Convert a vector and quaternion from worldspace to the entity's parent's local space
  2266. // Input : *pEntity - Entity whose parent we're concerned with
  2267. //-----------------------------------------------------------------------------
  2268. void UTIL_ParentToWorldSpace( CBaseEntity *pEntity, Vector &vecPosition, Quaternion &quat )
  2269. {
  2270. if ( pEntity == NULL )
  2271. return;
  2272. QAngle vecAngles;
  2273. QuaternionAngles( quat, vecAngles );
  2274. UTIL_ParentToWorldSpace( pEntity, vecPosition, vecAngles );
  2275. AngleQuaternion( vecAngles, quat );
  2276. }
  2277. //-----------------------------------------------------------------------------
  2278. // Purpose: Convert a vector an angle from worldspace to the entity's parent's local space
  2279. // Input : *pEntity - Entity whose parent we're concerned with
  2280. //-----------------------------------------------------------------------------
  2281. void UTIL_WorldToParentSpace( CBaseEntity *pEntity, Vector &vecPosition, QAngle &vecAngles )
  2282. {
  2283. if ( pEntity == NULL )
  2284. return;
  2285. // Construct the entity-to-world matrix
  2286. // Start with making an entity-to-parent matrix
  2287. matrix3x4_t matEntityToParent;
  2288. AngleMatrix( vecAngles, matEntityToParent );
  2289. MatrixSetColumn( vecPosition, 3, matEntityToParent );
  2290. // concatenate with our parent's transform
  2291. matrix3x4_t matScratch, matResult;
  2292. matrix3x4_t matWorldToParent;
  2293. if ( pEntity->GetParent() != NULL )
  2294. {
  2295. matScratch = pEntity->GetParentToWorldTransform( matScratch );
  2296. }
  2297. else
  2298. {
  2299. matScratch = pEntity->EntityToWorldTransform();
  2300. }
  2301. MatrixInvert( matScratch, matWorldToParent );
  2302. ConcatTransforms( matWorldToParent, matEntityToParent, matResult );
  2303. // pull our absolute position out of the matrix
  2304. MatrixGetColumn( matResult, 3, vecPosition );
  2305. MatrixAngles( matResult, vecAngles );
  2306. }
  2307. //-----------------------------------------------------------------------------
  2308. // Purpose: Convert a vector and quaternion from worldspace to the entity's parent's local space
  2309. // Input : *pEntity - Entity whose parent we're concerned with
  2310. //-----------------------------------------------------------------------------
  2311. void UTIL_WorldToParentSpace( CBaseEntity *pEntity, Vector &vecPosition, Quaternion &quat )
  2312. {
  2313. if ( pEntity == NULL )
  2314. return;
  2315. QAngle vecAngles;
  2316. QuaternionAngles( quat, vecAngles );
  2317. UTIL_WorldToParentSpace( pEntity, vecPosition, vecAngles );
  2318. AngleQuaternion( vecAngles, quat );
  2319. }
  2320. //-----------------------------------------------------------------------------
  2321. // Purpose: Given a vector, clamps the scalar axes to MAX_COORD_FLOAT ranges from worldsize.h
  2322. // Input : *pVecPos -
  2323. //-----------------------------------------------------------------------------
  2324. void UTIL_BoundToWorldSize( Vector *pVecPos )
  2325. {
  2326. Assert( pVecPos );
  2327. for ( int i = 0; i < 3; ++i )
  2328. {
  2329. (*pVecPos)[ i ] = clamp( (*pVecPos)[ i ], MIN_COORD_FLOAT, MAX_COORD_FLOAT );
  2330. }
  2331. }
  2332. //=============================================================================
  2333. //
  2334. // Tests!
  2335. //
  2336. #define NUM_KDTREE_TESTS 2500
  2337. #define NUM_KDTREE_ENTITY_SIZE 256
  2338. void CC_KDTreeTest( const CCommand &args )
  2339. {
  2340. Msg( "Testing kd-tree entity queries." );
  2341. // Get the testing spot.
  2342. // CBaseEntity *pSpot = gEntList.FindEntityByClassname( NULL, "info_player_start" );
  2343. // Vector vecStart = pSpot->GetAbsOrigin();
  2344. CBasePlayer *pPlayer = static_cast<CBasePlayer*>( UTIL_GetLocalPlayer() );
  2345. Vector vecStart = pPlayer->GetAbsOrigin();
  2346. static Vector *vecTargets = NULL;
  2347. static bool bFirst = true;
  2348. // Generate the targets - rays (1K long).
  2349. if ( bFirst )
  2350. {
  2351. vecTargets = new Vector [NUM_KDTREE_TESTS];
  2352. double flRadius = 0;
  2353. double flTheta = 0;
  2354. double flPhi = 0;
  2355. for ( int i = 0; i < NUM_KDTREE_TESTS; ++i )
  2356. {
  2357. flRadius += NUM_KDTREE_TESTS * 123.123;
  2358. flRadius = fmod( flRadius, 128.0 );
  2359. flRadius = fabs( flRadius );
  2360. flTheta += NUM_KDTREE_TESTS * 76.76;
  2361. flTheta = fmod( flTheta, (double) DEG2RAD( 360 ) );
  2362. flTheta = fabs( flTheta );
  2363. flPhi += NUM_KDTREE_TESTS * 1997.99;
  2364. flPhi = fmod( flPhi, (double) DEG2RAD( 180 ) );
  2365. flPhi = fabs( flPhi );
  2366. float st, ct, sp, cp;
  2367. SinCos( flTheta, &st, &ct );
  2368. SinCos( flPhi, &sp, &cp );
  2369. vecTargets[i].x = flRadius * ct * sp;
  2370. vecTargets[i].y = flRadius * st * sp;
  2371. vecTargets[i].z = flRadius * cp;
  2372. // Make the trace 1024 units long.
  2373. Vector vecDir = vecTargets[i] - vecStart;
  2374. VectorNormalize( vecDir );
  2375. vecTargets[i] = vecStart + vecDir * 1024;
  2376. }
  2377. bFirst = false;
  2378. }
  2379. int nTestType = 0;
  2380. if ( args.ArgC() >= 2 )
  2381. {
  2382. nTestType = atoi( args[ 1 ] );
  2383. }
  2384. vtune( true );
  2385. #ifdef VPROF_ENABLED
  2386. g_VProfCurrentProfile.Resume();
  2387. g_VProfCurrentProfile.Start();
  2388. g_VProfCurrentProfile.Reset();
  2389. g_VProfCurrentProfile.MarkFrame();
  2390. #endif
  2391. switch ( nTestType )
  2392. {
  2393. case 0:
  2394. {
  2395. VPROF( "TraceTotal" );
  2396. trace_t trace;
  2397. for ( int iTest = 0; iTest < NUM_KDTREE_TESTS; ++iTest )
  2398. {
  2399. UTIL_TraceLine( vecStart, vecTargets[iTest], MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &trace );
  2400. }
  2401. break;
  2402. }
  2403. case 1:
  2404. {
  2405. VPROF( "TraceTotal" );
  2406. trace_t trace;
  2407. for ( int iTest = 0; iTest < NUM_KDTREE_TESTS; ++iTest )
  2408. {
  2409. UTIL_TraceHull( vecStart, vecTargets[iTest], VEC_HULL_MIN_SCALED( pPlayer ), VEC_HULL_MAX_SCALED( pPlayer ), MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &trace );
  2410. }
  2411. break;
  2412. }
  2413. case 2:
  2414. {
  2415. Vector vecMins[NUM_KDTREE_TESTS];
  2416. Vector vecMaxs[NUM_KDTREE_TESTS];
  2417. int iTest;
  2418. for ( iTest = 0; iTest < NUM_KDTREE_TESTS; ++iTest )
  2419. {
  2420. vecMins[iTest] = vecStart;
  2421. vecMaxs[iTest] = vecStart;
  2422. for ( int iAxis = 0; iAxis < 3; ++iAxis )
  2423. {
  2424. if ( vecTargets[iTest].x < vecMins[iTest].x ) { vecMins[iTest].x = vecTargets[iTest].x; }
  2425. if ( vecTargets[iTest].y < vecMins[iTest].y ) { vecMins[iTest].y = vecTargets[iTest].y; }
  2426. if ( vecTargets[iTest].z < vecMins[iTest].z ) { vecMins[iTest].z = vecTargets[iTest].z; }
  2427. if ( vecTargets[iTest].x > vecMaxs[iTest].x ) { vecMaxs[iTest].x = vecTargets[iTest].x; }
  2428. if ( vecTargets[iTest].y > vecMaxs[iTest].y ) { vecMaxs[iTest].y = vecTargets[iTest].y; }
  2429. if ( vecTargets[iTest].z > vecMaxs[iTest].z ) { vecMaxs[iTest].z = vecTargets[iTest].z; }
  2430. }
  2431. }
  2432. VPROF( "TraceTotal" );
  2433. int nCount = 0;
  2434. Vector vecDelta;
  2435. trace_t trace;
  2436. CBaseEntity *pList[1024];
  2437. for ( iTest = 0; iTest < NUM_KDTREE_TESTS; ++iTest )
  2438. {
  2439. nCount += UTIL_EntitiesInBox( pList, 1024, vecMins[iTest], vecMaxs[iTest], 0 );
  2440. }
  2441. Msg( "Count = %d\n", nCount );
  2442. break;
  2443. }
  2444. case 3:
  2445. {
  2446. Vector vecDelta;
  2447. float flRadius[NUM_KDTREE_TESTS];
  2448. int iTest;
  2449. for ( iTest = 0; iTest < NUM_KDTREE_TESTS; ++iTest )
  2450. {
  2451. VectorSubtract( vecTargets[iTest], vecStart, vecDelta );
  2452. flRadius[iTest] = vecDelta.Length() * 0.5f;
  2453. }
  2454. VPROF( "TraceTotal" );
  2455. int nCount = 0;
  2456. trace_t trace;
  2457. CBaseEntity *pList[1024];
  2458. for ( iTest = 0; iTest < NUM_KDTREE_TESTS; ++iTest )
  2459. {
  2460. nCount += UTIL_EntitiesInSphere( pList, 1024, vecStart, flRadius[iTest], 0 );
  2461. }
  2462. Msg( "Count = %d\n", nCount );
  2463. break;
  2464. }
  2465. default:
  2466. {
  2467. break;
  2468. }
  2469. }
  2470. #ifdef VPROF_ENABLED
  2471. g_VProfCurrentProfile.MarkFrame();
  2472. g_VProfCurrentProfile.Pause();
  2473. g_VProfCurrentProfile.OutputReport( VPRT_FULL );
  2474. #endif
  2475. vtune( false );
  2476. }
  2477. static ConCommand kdtree_test( "kdtree_test", CC_KDTreeTest, "Tests spatial partition for entities queries.", FCVAR_CHEAT );
  2478. void CC_VoxelTreeView( void )
  2479. {
  2480. Msg( "VoxelTreeView\n" );
  2481. ::partition->RenderAllObjectsInTree( 10.0f );
  2482. }
  2483. static ConCommand voxeltree_view( "voxeltree_view", CC_VoxelTreeView, "View entities in the voxel-tree.", FCVAR_CHEAT );
  2484. void CC_VoxelTreePlayerView( void )
  2485. {
  2486. Msg( "VoxelTreePlayerView\n" );
  2487. CBasePlayer *pPlayer = static_cast<CBasePlayer*>( UTIL_GetLocalPlayer() );
  2488. Vector vecStart = pPlayer->GetAbsOrigin();
  2489. ::partition->RenderObjectsInPlayerLeafs( vecStart - VEC_HULL_MIN_SCALED( pPlayer ), vecStart + VEC_HULL_MAX_SCALED( pPlayer ), 3.0f );
  2490. }
  2491. static ConCommand voxeltree_playerview( "voxeltree_playerview", CC_VoxelTreePlayerView, "View entities in the voxel-tree at the player position.", FCVAR_CHEAT );
  2492. void CC_VoxelTreeBox( const CCommand &args )
  2493. {
  2494. Vector vecMin, vecMax;
  2495. if ( args.ArgC() >= 6 )
  2496. {
  2497. vecMin.x = atof( args[ 1 ] );
  2498. vecMin.y = atof( args[ 2 ] );
  2499. vecMin.z = atof( args[ 3 ] );
  2500. vecMax.x = atof( args[ 4 ] );
  2501. vecMax.y = atof( args[ 5 ] );
  2502. vecMax.z = atof( args[ 6 ] );
  2503. }
  2504. else
  2505. {
  2506. return;
  2507. }
  2508. float flTime = 10.0f;
  2509. Vector vecPoints[8];
  2510. vecPoints[0].Init( vecMin.x, vecMin.y, vecMin.z );
  2511. vecPoints[1].Init( vecMin.x, vecMax.y, vecMin.z );
  2512. vecPoints[2].Init( vecMax.x, vecMax.y, vecMin.z );
  2513. vecPoints[3].Init( vecMax.x, vecMin.y, vecMin.z );
  2514. vecPoints[4].Init( vecMin.x, vecMin.y, vecMax.z );
  2515. vecPoints[5].Init( vecMin.x, vecMax.y, vecMax.z );
  2516. vecPoints[6].Init( vecMax.x, vecMax.y, vecMax.z );
  2517. vecPoints[7].Init( vecMax.x, vecMin.y, vecMax.z );
  2518. if ( debugoverlay )
  2519. {
  2520. debugoverlay->AddLineOverlay( vecPoints[0], vecPoints[1], 255, 0, 0, true, flTime );
  2521. debugoverlay->AddLineOverlay( vecPoints[1], vecPoints[2], 255, 0, 0, true, flTime );
  2522. debugoverlay->AddLineOverlay( vecPoints[2], vecPoints[3], 255, 0, 0, true, flTime );
  2523. debugoverlay->AddLineOverlay( vecPoints[3], vecPoints[0], 255, 0, 0, true, flTime );
  2524. debugoverlay->AddLineOverlay( vecPoints[4], vecPoints[5], 255, 0, 0, true, flTime );
  2525. debugoverlay->AddLineOverlay( vecPoints[5], vecPoints[6], 255, 0, 0, true, flTime );
  2526. debugoverlay->AddLineOverlay( vecPoints[6], vecPoints[7], 255, 0, 0, true, flTime );
  2527. debugoverlay->AddLineOverlay( vecPoints[7], vecPoints[4], 255, 0, 0, true, flTime );
  2528. debugoverlay->AddLineOverlay( vecPoints[0], vecPoints[4], 255, 0, 0, true, flTime );
  2529. debugoverlay->AddLineOverlay( vecPoints[3], vecPoints[7], 255, 0, 0, true, flTime );
  2530. debugoverlay->AddLineOverlay( vecPoints[1], vecPoints[5], 255, 0, 0, true, flTime );
  2531. debugoverlay->AddLineOverlay( vecPoints[2], vecPoints[6], 255, 0, 0, true, flTime );
  2532. }
  2533. Msg( "VoxelTreeBox - (%f %f %f) to (%f %f %f)\n", vecMin.x, vecMin.y, vecMin.z, vecMax.x, vecMax.y, vecMax.z );
  2534. ::partition->RenderObjectsInBox( vecMin, vecMax, flTime );
  2535. }
  2536. static ConCommand voxeltree_box( "voxeltree_box", CC_VoxelTreeBox, "View entities in the voxel-tree inside box <Vector(min), Vector(max)>.", FCVAR_CHEAT );
  2537. void CC_VoxelTreeSphere( const CCommand &args )
  2538. {
  2539. Vector vecCenter;
  2540. float flRadius;
  2541. if ( args.ArgC() >= 4 )
  2542. {
  2543. vecCenter.x = atof( args[ 1 ] );
  2544. vecCenter.y = atof( args[ 2 ] );
  2545. vecCenter.z = atof( args[ 3 ] );
  2546. flRadius = atof( args[ 3 ] );
  2547. }
  2548. else
  2549. {
  2550. return;
  2551. }
  2552. float flTime = 3.0f;
  2553. Vector vecMin, vecMax;
  2554. vecMin.Init( vecCenter.x - flRadius, vecCenter.y - flRadius, vecCenter.z - flRadius );
  2555. vecMax.Init( vecCenter.x + flRadius, vecCenter.y + flRadius, vecCenter.z + flRadius );
  2556. Vector vecPoints[8];
  2557. vecPoints[0].Init( vecMin.x, vecMin.y, vecMin.z );
  2558. vecPoints[1].Init( vecMin.x, vecMax.y, vecMin.z );
  2559. vecPoints[2].Init( vecMax.x, vecMax.y, vecMin.z );
  2560. vecPoints[3].Init( vecMax.x, vecMin.y, vecMin.z );
  2561. vecPoints[4].Init( vecMin.x, vecMin.y, vecMax.z );
  2562. vecPoints[5].Init( vecMin.x, vecMax.y, vecMax.z );
  2563. vecPoints[6].Init( vecMax.x, vecMax.y, vecMax.z );
  2564. vecPoints[7].Init( vecMax.x, vecMin.y, vecMax.z );
  2565. if ( debugoverlay )
  2566. {
  2567. debugoverlay->AddLineOverlay( vecPoints[0], vecPoints[1], 255, 0, 0, true, flTime );
  2568. debugoverlay->AddLineOverlay( vecPoints[1], vecPoints[2], 255, 0, 0, true, flTime );
  2569. debugoverlay->AddLineOverlay( vecPoints[2], vecPoints[3], 255, 0, 0, true, flTime );
  2570. debugoverlay->AddLineOverlay( vecPoints[3], vecPoints[0], 255, 0, 0, true, flTime );
  2571. debugoverlay->AddLineOverlay( vecPoints[4], vecPoints[5], 255, 0, 0, true, flTime );
  2572. debugoverlay->AddLineOverlay( vecPoints[5], vecPoints[6], 255, 0, 0, true, flTime );
  2573. debugoverlay->AddLineOverlay( vecPoints[6], vecPoints[7], 255, 0, 0, true, flTime );
  2574. debugoverlay->AddLineOverlay( vecPoints[7], vecPoints[4], 255, 0, 0, true, flTime );
  2575. debugoverlay->AddLineOverlay( vecPoints[0], vecPoints[4], 255, 0, 0, true, flTime );
  2576. debugoverlay->AddLineOverlay( vecPoints[3], vecPoints[7], 255, 0, 0, true, flTime );
  2577. debugoverlay->AddLineOverlay( vecPoints[1], vecPoints[5], 255, 0, 0, true, flTime );
  2578. debugoverlay->AddLineOverlay( vecPoints[2], vecPoints[6], 255, 0, 0, true, flTime );
  2579. }
  2580. Msg( "VoxelTreeSphere - (%f %f %f), %f\n", vecCenter.x, vecCenter.y, vecCenter.z, flRadius );
  2581. ::partition->RenderObjectsInSphere( vecCenter, flRadius, flTime );
  2582. }
  2583. static ConCommand voxeltree_sphere( "voxeltree_sphere", CC_VoxelTreeSphere, "View entities in the voxel-tree inside sphere <Vector(center), float(radius)>.", FCVAR_CHEAT );
  2584. #define NUM_COLLISION_TESTS 2500
  2585. void CC_CollisionTest( const CCommand &args )
  2586. {
  2587. if ( !physenv )
  2588. return;
  2589. Msg( "Testing collision system\n" );
  2590. ::partition->ReportStats( "" );
  2591. int i;
  2592. CBaseEntity *pSpot = gEntList.FindEntityByClassname( NULL, "info_player_start");
  2593. Vector start = pSpot->GetAbsOrigin();
  2594. static Vector *targets = NULL;
  2595. static bool first = true;
  2596. static float test[2] = {1,1};
  2597. if ( first )
  2598. {
  2599. targets = new Vector[NUM_COLLISION_TESTS];
  2600. float radius = 0;
  2601. float theta = 0;
  2602. float phi = 0;
  2603. for ( i = 0; i < NUM_COLLISION_TESTS; i++ )
  2604. {
  2605. radius += NUM_COLLISION_TESTS * 123.123;
  2606. radius = fabs(fmod(radius, 128));
  2607. theta += NUM_COLLISION_TESTS * 76.76;
  2608. theta = fabs(fmod(theta, DEG2RAD(360)));
  2609. phi += NUM_COLLISION_TESTS * 1997.99;
  2610. phi = fabs(fmod(phi, DEG2RAD(180)));
  2611. float st, ct, sp, cp;
  2612. SinCos( theta, &st, &ct );
  2613. SinCos( phi, &sp, &cp );
  2614. targets[i].x = radius * ct * sp;
  2615. targets[i].y = radius * st * sp;
  2616. targets[i].z = radius * cp;
  2617. // make the trace 1024 units long
  2618. Vector dir = targets[i] - start;
  2619. VectorNormalize(dir);
  2620. targets[i] = start + dir * 1024;
  2621. }
  2622. first = false;
  2623. }
  2624. //Vector results[NUM_COLLISION_TESTS];
  2625. int testType = 0;
  2626. if ( args.ArgC() >= 2 )
  2627. {
  2628. testType = atoi(args[1]);
  2629. }
  2630. float duration = 0;
  2631. Vector size[2];
  2632. size[0].Init(0,0,0);
  2633. size[1].Init(16,16,16);
  2634. unsigned int dots = 0;
  2635. int nMask = MASK_ALL & ~(CONTENTS_MONSTER | CONTENTS_HITBOX );
  2636. for ( int j = 0; j < 2; j++ )
  2637. {
  2638. float startTime = engine->Time();
  2639. if ( testType == 1 )
  2640. {
  2641. trace_t tr;
  2642. for ( i = 0; i < NUM_COLLISION_TESTS; i++ )
  2643. {
  2644. UTIL_TraceHull( start, targets[i], -size[1], size[1], nMask, NULL, COLLISION_GROUP_NONE, &tr );
  2645. }
  2646. }
  2647. else
  2648. {
  2649. testType = 0;
  2650. trace_t tr;
  2651. for ( i = 0; i < NUM_COLLISION_TESTS; i++ )
  2652. {
  2653. if ( i == 0 )
  2654. {
  2655. ::partition->RenderLeafsForRayTraceStart( 10.0f );
  2656. }
  2657. UTIL_TraceLine( start, targets[i], nMask, NULL, COLLISION_GROUP_NONE, &tr );
  2658. if ( i == 0 )
  2659. {
  2660. ::partition->RenderLeafsForRayTraceEnd( );
  2661. }
  2662. }
  2663. }
  2664. duration += engine->Time() - startTime;
  2665. }
  2666. test[testType] = duration;
  2667. Msg("%d collisions in %.2f ms (%u dots)\n", NUM_COLLISION_TESTS, duration*1000, dots );
  2668. ::partition->ReportStats( "" );
  2669. #if 1
  2670. int red = 255, green = 0, blue = 0;
  2671. for ( i = 0; i < 1 /*NUM_COLLISION_TESTS*/; i++ )
  2672. {
  2673. NDebugOverlay::Line( start, targets[i], red, green, blue, false, 2 );
  2674. }
  2675. #endif
  2676. }
  2677. static ConCommand collision_test("collision_test", CC_CollisionTest, "Tests collision system", FCVAR_CHEAT );