Counter Strike : Global Offensive Source Code
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.

1735 lines
49 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Hint node utilities and functions
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. // @TODO (toml 03-04-03): there is far too much duplicate code in here
  8. #include "cbase.h"
  9. #include "ai_hint.h"
  10. #include "ai_network.h"
  11. #include "ai_node.h"
  12. #include "ai_basenpc.h"
  13. #include "ai_networkmanager.h"
  14. #include "ndebugoverlay.h"
  15. #include "animation.h"
  16. #include "tier1/strtools.h"
  17. #include "mapentities_shared.h"
  18. // memdbgon must be the last include file in a .cpp file!!!
  19. #include "tier0/memdbgon.h"
  20. #define REPORTFAILURE(text) if ( hintCriteria.HasFlag( bits_HINT_NODE_REPORT_FAILURES ) ) \
  21. NDebugOverlay::Text( GetAbsOrigin(), text, false, 60 )
  22. //==================================================
  23. // CHintCriteria
  24. //==================================================
  25. CHintCriteria::CHintCriteria( void )
  26. {
  27. m_iFirstHintType = HINT_NONE;
  28. m_iLastHintType = HINT_NONE;
  29. m_strGroup = NULL_STRING;
  30. m_strGenericType = NULL_STRING;
  31. m_iFlags = 0;
  32. m_HintTypes.Purge();
  33. m_pfnFilter = NULL;
  34. m_pFilterContext = NULL;
  35. }
  36. //-----------------------------------------------------------------------------
  37. // Purpose: Destructor
  38. //-----------------------------------------------------------------------------
  39. CHintCriteria::~CHintCriteria( void )
  40. {
  41. m_zoneInclude.Purge();
  42. m_zoneExclude.Purge();
  43. m_HintTypes.Purge();
  44. }
  45. //-----------------------------------------------------------------------------
  46. // Purpose: Sets the hint type for this search criteria
  47. // Input : nHintType - the hint type for this search criteria
  48. //-----------------------------------------------------------------------------
  49. void CHintCriteria::SetHintType( int nHintType )
  50. {
  51. m_iFirstHintType = nHintType;
  52. m_iLastHintType = HINT_NONE;
  53. m_HintTypes.Purge();
  54. }
  55. //-----------------------------------------------------------------------------
  56. // Purpose: Add another type of hint that matches the search criteria
  57. //-----------------------------------------------------------------------------
  58. void CHintCriteria::AddHintType( int hintType )
  59. {
  60. m_HintTypes.AddToTail( hintType );
  61. }
  62. int CHintCriteria::NumHintTypes() const
  63. {
  64. return m_HintTypes.Count();
  65. }
  66. int CHintCriteria::GetHintType( int idx ) const
  67. {
  68. return m_HintTypes[ idx ];
  69. }
  70. bool CHintCriteria::MatchesSingleHintType() const
  71. {
  72. if ( m_HintTypes.Count() != 0 )
  73. {
  74. return false;
  75. }
  76. if ( m_iFirstHintType != HINT_ANY &&
  77. m_iLastHintType == HINT_NONE )
  78. {
  79. return true;
  80. }
  81. return false;
  82. }
  83. //-----------------------------------------------------------------------------
  84. // Purpose:
  85. //-----------------------------------------------------------------------------
  86. bool CHintCriteria::MatchesHintType( int hintType, string_t iszGenericType ) const
  87. {
  88. int c = m_HintTypes.Count();
  89. for ( int i = 0; i < c; ++i )
  90. {
  91. if ( m_HintTypes[i] == hintType )
  92. return true;
  93. }
  94. // See if we're trying to filter the nodes
  95. if ( GetFirstHintType() != HINT_ANY )
  96. {
  97. if( GetLastHintType() == HINT_NONE )
  98. {
  99. // Searching for a single type of hint.
  100. if( GetFirstHintType() != hintType )
  101. return false;
  102. }
  103. else
  104. {
  105. // This search is for a range of hint types.
  106. if( hintType < GetFirstHintType() || hintType > GetLastHintType() )
  107. return false;
  108. if ( hintType == HINT_GENERIC && iszGenericType != m_strGenericType )
  109. {
  110. return false;
  111. }
  112. }
  113. return true;
  114. }
  115. return false;
  116. }
  117. //-----------------------------------------------------------------------------
  118. // Allows us to search for nodes within a range of consecutive types.
  119. //-----------------------------------------------------------------------------
  120. void CHintCriteria::SetHintTypeRange( int firstType, int lastType )
  121. {
  122. if( lastType < firstType )
  123. {
  124. DevMsg( 2, "Hint Type Range is backwards - Fixing up.\n" );
  125. int temp;
  126. temp = firstType;
  127. firstType = lastType;
  128. lastType = temp;
  129. }
  130. m_iFirstHintType = firstType;
  131. m_iLastHintType = lastType;
  132. m_HintTypes.Purge();
  133. }
  134. //-----------------------------------------------------------------------------
  135. // Purpose:
  136. // Input : bitmask -
  137. //-----------------------------------------------------------------------------
  138. void CHintCriteria::SetFlag( int bitmask )
  139. {
  140. m_iFlags |= bitmask;
  141. }
  142. //-----------------------------------------------------------------------------
  143. // Purpose:
  144. // Input : bitmask -
  145. //-----------------------------------------------------------------------------
  146. void CHintCriteria::ClearFlag( int bitmask )
  147. {
  148. m_iFlags &= ~bitmask;
  149. }
  150. //-----------------------------------------------------------------------------
  151. // Purpose:
  152. // Input : group -
  153. //-----------------------------------------------------------------------------
  154. void CHintCriteria::SetGroup( string_t group )
  155. {
  156. m_strGroup = group;
  157. }
  158. //-----------------------------------------------------------------------------
  159. // Purpose: Adds a zone to a zone list
  160. // Input : list - the list of zones to add the new zone to
  161. // &position - the origin point of the zone
  162. // radius - the radius of the zone
  163. //-----------------------------------------------------------------------------
  164. void CHintCriteria::AddZone( zoneList_t &list, const Vector &position, float radius )
  165. {
  166. int id = list.AddToTail();
  167. list[id].position = position;
  168. list[id].radiussqr = radius*radius;
  169. }
  170. //-----------------------------------------------------------------------------
  171. // Purpose: Adds an include zone to the search criteria
  172. // Input : &position - the origin point of the zone
  173. // radius - the radius of the zone
  174. //-----------------------------------------------------------------------------
  175. void CHintCriteria::AddIncludePosition( const Vector &position, float radius )
  176. {
  177. AddZone( m_zoneInclude, position, radius );
  178. }
  179. //-----------------------------------------------------------------------------
  180. // Purpose: Adds an exclude zone to the search criteria
  181. // Input : &position - the origin point of the zone
  182. // radius - the radius of the zone
  183. //-----------------------------------------------------------------------------
  184. void CHintCriteria::AddExcludePosition( const Vector &position, float radius )
  185. {
  186. AddZone( m_zoneExclude, position, radius );
  187. }
  188. //-----------------------------------------------------------------------------
  189. // Purpose: Test to see if this position falls within any of the zones in the list
  190. // Input : *zone - list of zones to test against
  191. // &testPosition - position to test with
  192. // Output : Returns true on success, false on failure.
  193. //-----------------------------------------------------------------------------
  194. inline bool CHintCriteria::InZone( const zoneList_t &zone, const Vector &testPosition ) const
  195. {
  196. int numZones = zone.Count();
  197. //Iterate through all zones in the list
  198. for ( int i = 0; i < numZones; i++ )
  199. {
  200. if ( ((zone[i].position) - testPosition).LengthSqr() < (zone[i].radiussqr) )
  201. return true;
  202. }
  203. return false;
  204. }
  205. //-----------------------------------------------------------------------------
  206. // Purpose: Determine if a point within our include list
  207. // Input : &testPosition - position to test with
  208. // Output : Returns true on success, false on failure.
  209. //-----------------------------------------------------------------------------
  210. bool CHintCriteria::InIncludedZone( const Vector &testPosition ) const
  211. {
  212. return InZone( m_zoneInclude, testPosition );
  213. }
  214. //-----------------------------------------------------------------------------
  215. // Purpose: Determine if a point within our exclude list
  216. // Input : &testPosition - position to test with
  217. // Output : Returns true on success, false on failure.
  218. //-----------------------------------------------------------------------------
  219. bool CHintCriteria::InExcludedZone( const Vector &testPosition ) const
  220. {
  221. return InZone( m_zoneExclude, testPosition );
  222. }
  223. //-----------------------------------------------------------------------------
  224. // Init static variables
  225. //-----------------------------------------------------------------------------
  226. CAIHintVector CAI_HintManager::gm_AllHints;
  227. CUtlMap< int, CAIHintVector > CAI_HintManager::gm_TypedHints( 0, 0, DefLessFunc( int ) );
  228. CAI_Hint* CAI_HintManager::gm_pLastFoundHints[ CAI_HintManager::HINT_HISTORY ];
  229. int CAI_HintManager::gm_nFoundHintIndex = 0;
  230. CAI_Hint *CAI_HintManager::AddFoundHint( CAI_Hint *hint )
  231. {
  232. if ( hint )
  233. {
  234. CAI_HintManager::gm_nFoundHintIndex = ( CAI_HintManager::gm_nFoundHintIndex + 1 ) & CAI_HintManager::HINT_HISTORY_MASK;
  235. gm_pLastFoundHints[ CAI_HintManager::gm_nFoundHintIndex ] = hint;
  236. }
  237. return hint;
  238. }
  239. int CAI_HintManager::GetFoundHintCount()
  240. {
  241. return CAI_HintManager::HINT_HISTORY;
  242. }
  243. CAI_Hint *CAI_HintManager::GetFoundHint( int index )
  244. {
  245. return gm_pLastFoundHints[ ( CAI_HintManager::gm_nFoundHintIndex + index ) & CAI_HintManager::HINT_HISTORY_MASK ];
  246. }
  247. CAI_Hint *CAI_HintManager::GetLastFoundHint()
  248. {
  249. for ( int i = 0; i < CAI_HintManager::HINT_HISTORY; ++i )
  250. {
  251. // Walk backward
  252. int slot = ( ( CAI_HintManager::gm_nFoundHintIndex - i ) & CAI_HintManager::HINT_HISTORY_MASK );
  253. if ( gm_pLastFoundHints[ slot ] )
  254. return gm_pLastFoundHints[ slot ];
  255. }
  256. return NULL;
  257. }
  258. void CAI_HintManager::ResetFoundHints()
  259. {
  260. Q_memset( gm_pLastFoundHints, 0, sizeof( gm_pLastFoundHints ) );
  261. CAI_HintManager::gm_nFoundHintIndex = 0;
  262. }
  263. bool CAI_HintManager::IsInFoundHintList( CAI_Hint *hint )
  264. {
  265. for ( int i = 0; i < CAI_HintManager::HINT_HISTORY; ++i )
  266. {
  267. if ( gm_pLastFoundHints[ i ] == hint )
  268. return true;
  269. }
  270. return false;
  271. }
  272. //-----------------------------------------------------------------------------
  273. int CAI_HintManager::FindAllHints( CAI_BaseNPC *pNPC, const Vector &position, const CHintCriteria &hintCriteria, CUtlVector<CAI_Hint *> *pResult )
  274. {
  275. // If we have no hints, bail
  276. int c = CAI_HintManager::gm_AllHints.Count();
  277. if ( !c )
  278. return NULL;
  279. // Remove the nearest flag. It makes now sense with random.
  280. bool hadNearest = hintCriteria.HasFlag( bits_HINT_NODE_NEAREST );
  281. (const_cast<CHintCriteria &>(hintCriteria)).ClearFlag( bits_HINT_NODE_NEAREST );
  282. // Now loop till we find a valid hint or return to the start
  283. CAI_Hint *pTestHint;
  284. for ( int i = 0; i < c; ++i )
  285. {
  286. pTestHint = CAI_HintManager::gm_AllHints[ i ];
  287. Assert( pTestHint );
  288. if ( pTestHint->HintMatchesCriteria( pNPC, hintCriteria, position, NULL ) )
  289. pResult->AddToTail( pTestHint );
  290. }
  291. if ( hadNearest )
  292. (const_cast<CHintCriteria &>(hintCriteria)).SetFlag( bits_HINT_NODE_NEAREST );
  293. return pResult->Count();
  294. }
  295. //-----------------------------------------------------------------------------
  296. // Purpose: Finds a random hint within the requested radious of the npc
  297. // Builds a list of all suitable hints and chooses randomly from amongst them.
  298. // Input : *pNPC -
  299. // nHintType -
  300. // nFlags -
  301. // flMaxDist -
  302. // Output : CAI_Hint
  303. //-----------------------------------------------------------------------------
  304. CAI_Hint *CAI_HintManager::FindHintRandom( CAI_BaseNPC *pNPC, const Vector &position, const CHintCriteria &hintCriteria )
  305. {
  306. CUtlVector<CAI_Hint *> hintList;
  307. if ( FindAllHints( pNPC, position, hintCriteria, &hintList ) > 0 )
  308. {
  309. // Pick one randomly
  310. return ( CAI_HintManager::AddFoundHint( hintList[ random->RandomInt( 0, hintList.Count() - 1 ) ] ) );
  311. }
  312. // start at the top of the list for the next search
  313. CAI_HintManager::ResetFoundHints();
  314. return NULL;
  315. }
  316. // #define HINT_PROFILING 1
  317. #if defined( HINT_PROFILING )
  318. static void AppendTimer( int idx, char *buf, size_t bufsize, CFastTimer& timer )
  319. {
  320. char s[ 32 ];
  321. Q_snprintf( s, sizeof( s ), "%d %6.3f ms", idx, timer.GetDuration().GetMillisecondsF() );
  322. Q_strncat( buf, s, bufsize );
  323. }
  324. #endif
  325. //-----------------------------------------------------------------------------
  326. // Purpose:
  327. // Input : *hintCriteria -
  328. // Output : CAI_Hint
  329. //-----------------------------------------------------------------------------
  330. CAI_Hint *CAI_HintManager::FindHint( CAI_BaseNPC *pNPC, const Vector &position, const CHintCriteria &hintCriteria )
  331. {
  332. #if defined( HINT_PROFILING )
  333. CFastTimer timer;
  334. timer.Start();
  335. #endif
  336. bool singleType = hintCriteria.MatchesSingleHintType();
  337. bool lookingForNearest = hintCriteria.HasFlag( bits_HINT_NODE_NEAREST );
  338. bool bIgnoreHintType = true;
  339. CUtlVector< CAIHintVector * > lists;
  340. if ( singleType )
  341. {
  342. int slot = CAI_HintManager::gm_TypedHints.Find( hintCriteria.GetFirstHintType() );
  343. if ( slot != CAI_HintManager::gm_TypedHints.InvalidIndex() )
  344. {
  345. lists.AddToTail( &CAI_HintManager::gm_TypedHints[ slot ] );
  346. }
  347. }
  348. else
  349. {
  350. int typeCount = hintCriteria.NumHintTypes();
  351. if ( typeCount > 0 )
  352. {
  353. for ( int listType = 0; listType < typeCount; ++listType )
  354. {
  355. int slot = CAI_HintManager::gm_TypedHints.Find( hintCriteria.GetHintType( listType ) );
  356. if ( slot != CAI_HintManager::gm_TypedHints.InvalidIndex() )
  357. {
  358. lists.AddToTail( &CAI_HintManager::gm_TypedHints[ slot ] );
  359. }
  360. }
  361. }
  362. else
  363. {
  364. // Still need to check hint type in this case
  365. lists.AddToTail( &CAI_HintManager::gm_AllHints );
  366. bIgnoreHintType = false;
  367. }
  368. }
  369. CAI_Hint *pBestHint = NULL;
  370. int visited = 0;
  371. int listCount = lists.Count();
  372. if ( listCount == 0 )
  373. return NULL;
  374. // Try the fast match path
  375. int i, count;
  376. // Start with hint after the last one used
  377. CAI_Hint *pTestHint = NULL;
  378. float flBestDistance = MAX_TRACE_LENGTH;
  379. if ( !lookingForNearest )
  380. {
  381. // Fast check of previous results
  382. count = CAI_HintManager::GetFoundHintCount();
  383. for ( i = 0; i < count; ++i )
  384. {
  385. pTestHint = CAI_HintManager::GetFoundHint( i );
  386. if ( pTestHint )
  387. {
  388. Assert( dynamic_cast<CAI_Hint *>(pTestHint) != NULL );
  389. ++visited;
  390. if ( pTestHint->HintMatchesCriteria( pNPC, hintCriteria, position, &flBestDistance ) )
  391. {
  392. #if defined( HINT_PROFILING )
  393. Msg( "fast result visited %d\n", visited );
  394. #endif
  395. return pTestHint;
  396. }
  397. }
  398. }
  399. }
  400. // Longer search, reset best distance
  401. flBestDistance = MAX_TRACE_LENGTH;
  402. for ( int listNum = 0; listNum < listCount; ++listNum )
  403. {
  404. CAIHintVector *list = lists[ listNum ];
  405. count = list->Count();
  406. // -------------------------------------------
  407. // If we have no hints, bail
  408. // -------------------------------------------
  409. if ( !count )
  410. continue;
  411. // Now loop till we find a valid hint or return to the start
  412. for ( i = 0 ; i < count; ++i )
  413. {
  414. pTestHint = list->Element( i );
  415. Assert( pTestHint );
  416. ++visited;
  417. Assert( dynamic_cast<CAI_Hint *>(pTestHint) != NULL );
  418. if ( pTestHint->HintMatchesCriteria( pNPC, hintCriteria, position, &flBestDistance, false, bIgnoreHintType ) )
  419. {
  420. // If we were searching for the nearest, just note that this is now the nearest node
  421. if ( lookingForNearest )
  422. {
  423. pBestHint = pTestHint;
  424. }
  425. else
  426. {
  427. // If we're not looking for the nearest, we're done
  428. CAI_HintManager::AddFoundHint( pTestHint );
  429. #if defined( HINT_PROFILING )
  430. Msg( "visited %d\n", visited );
  431. #endif
  432. return pTestHint;
  433. }
  434. }
  435. }
  436. }
  437. // Return the nearest node that we found
  438. if ( pBestHint )
  439. {
  440. CAI_HintManager::AddFoundHint( pBestHint );
  441. }
  442. #if defined( HINT_PROFILING )
  443. timer.End();
  444. Msg( "visited %d\n", visited );
  445. if ( !pBestHint )
  446. {
  447. Msg( "%i search failed for [%d] at pos %.3f %.3f %.3f [%.4f msec ~ %.4f msec per node]\n",
  448. gpGlobals->tickcount,
  449. pNPC ? pNPC->entindex() : -1,
  450. position.x, position.y, position.z,
  451. timer.GetDuration().GetMillisecondsF(),
  452. timer.GetDuration().GetMillisecondsF()/MAX( (float)visited, 1.0f ) );
  453. }
  454. #endif
  455. return pBestHint;
  456. }
  457. //-----------------------------------------------------------------------------
  458. // Purpose: Searches for a hint node that this NPC cares about. If one is
  459. // claims that hint node for this NPC so that no other NPCs
  460. // try to use it.
  461. //
  462. // Input : nFlags - Search criterea. Can currently be one or more of the following:
  463. // bits_HINT_NODE_VISIBLE - searches for visible hint nodes.
  464. // bits_HINT_NODE_RANDOM - calls through the FindHintRandom and builds list of all matching
  465. // nodes and picks randomly from among them. Note: Depending on number of hint nodes, this
  466. // could be slower, so use with care.
  467. //
  468. // Output : Returns pointer to hint node if available hint node was found that matches the
  469. // given criterea that this NPC also cares about. Otherwise, returns NULL
  470. //-----------------------------------------------------------------------------
  471. CAI_Hint* CAI_HintManager::FindHint( CAI_BaseNPC *pNPC, Hint_e nHintType, int nFlags, float flMaxDist, const Vector *pMaxDistFrom )
  472. {
  473. assert( pNPC != NULL );
  474. if ( pNPC == NULL )
  475. return NULL;
  476. CHintCriteria hintCriteria;
  477. hintCriteria.SetHintType( nHintType );
  478. hintCriteria.SetFlag( nFlags );
  479. // Using the NPC's hint group?
  480. if ( nFlags & bits_HINT_NODE_USE_GROUP )
  481. {
  482. hintCriteria.SetGroup( pNPC->GetHintGroup() );
  483. }
  484. // Add the search position
  485. Vector vecPosition = ( pMaxDistFrom != NULL ) ? (*pMaxDistFrom) : pNPC->GetAbsOrigin();
  486. hintCriteria.AddIncludePosition( vecPosition, flMaxDist );
  487. // If asking for a random node, use random logic instead
  488. if ( nFlags & bits_HINT_NODE_RANDOM )
  489. return FindHintRandom( pNPC, vecPosition, hintCriteria );
  490. return FindHint( pNPC, vecPosition, hintCriteria );
  491. }
  492. //-----------------------------------------------------------------------------
  493. // Purpose: Position only search
  494. // Output : CAI_Hint
  495. //-----------------------------------------------------------------------------
  496. CAI_Hint *CAI_HintManager::FindHint( const Vector &position, const CHintCriteria &hintCriteria )
  497. {
  498. return FindHint( NULL, position, hintCriteria );
  499. }
  500. //-----------------------------------------------------------------------------
  501. // Purpose: NPC only search
  502. // Output : CAI_Hint
  503. //-----------------------------------------------------------------------------
  504. CAI_Hint *CAI_HintManager::FindHint( CAI_BaseNPC *pNPC, const CHintCriteria &hintCriteria )
  505. {
  506. assert( pNPC != NULL );
  507. if ( pNPC == NULL )
  508. return NULL;
  509. return FindHint( pNPC, pNPC->GetAbsOrigin(), hintCriteria );
  510. }
  511. //------------------------------------------------------------------------------
  512. // Purpose :
  513. // Input :
  514. // Output :
  515. //------------------------------------------------------------------------------
  516. CAI_Hint* CAI_HintManager::CreateHint( HintNodeData *pNodeData, const char *pMapData )
  517. {
  518. // Reset last found hint if new node is added
  519. CAI_HintManager::ResetFoundHints();
  520. CAI_Hint *pHint = (CAI_Hint*)CreateEntityByName("ai_hint");
  521. if ( pHint )
  522. {
  523. // First, parse the mapdata chunk we were passed
  524. if ( pMapData )
  525. {
  526. CEntityMapData entData( (char*)pMapData );
  527. pHint->ParseMapData( &entData );
  528. // Restore the desired classname (parsing the mapdata stomps it)
  529. pHint->SetClassname( "ai_hint" );
  530. }
  531. pHint->SetName( pNodeData->strEntityName );
  532. pHint->SetAbsOrigin( pNodeData->vecPosition );
  533. memcpy( &(pHint->m_NodeData), pNodeData, sizeof(HintNodeData) );
  534. DispatchSpawn( pHint );
  535. return pHint;
  536. }
  537. return NULL;
  538. }
  539. //------------------------------------------------------------------------------
  540. void CAI_HintManager::AddHint( CAI_Hint *pHint )
  541. {
  542. // ---------------------------------
  543. // Add to linked list of hints
  544. // ---------------------------------
  545. CAI_HintManager::gm_AllHints.AddToTail( pHint );
  546. CAI_HintManager::AddHintByType( pHint );
  547. }
  548. void CAI_Hint::SetHintType( int hintType, bool force /*= false*/ )
  549. {
  550. if ( !force && hintType == m_NodeData.nHintType )
  551. return;
  552. CAI_HintManager::RemoveHintByType( this );
  553. m_NodeData.nHintType = hintType;
  554. CAI_HintManager::AddHintByType( this );
  555. }
  556. void CAI_HintManager::AddHintByType( CAI_Hint *pHint )
  557. {
  558. Hint_e type = pHint->HintType();
  559. int slot = CAI_HintManager::gm_TypedHints.Find( type );
  560. if ( slot == CAI_HintManager::gm_TypedHints.InvalidIndex() )
  561. {
  562. slot = CAI_HintManager::gm_TypedHints.Insert( type);
  563. }
  564. CAI_HintManager::gm_TypedHints[ slot ].AddToTail( pHint );
  565. }
  566. void CAI_HintManager::RemoveHintByType( CAI_Hint *pHintToRemove )
  567. {
  568. int slot = CAI_HintManager::gm_TypedHints.Find( pHintToRemove->HintType() );
  569. if ( slot != CAI_HintManager::gm_TypedHints.InvalidIndex() )
  570. {
  571. CAI_HintManager::gm_TypedHints[ slot ].FindAndRemove( pHintToRemove );
  572. }
  573. }
  574. //------------------------------------------------------------------------------
  575. void CAI_HintManager::RemoveHint( CAI_Hint *pHintToRemove )
  576. {
  577. // --------------------------------------
  578. // Remove from linked list of hints
  579. // --------------------------------------
  580. gm_AllHints.FindAndRemove( pHintToRemove );
  581. RemoveHintByType( pHintToRemove );
  582. if ( CAI_HintManager::IsInFoundHintList( pHintToRemove ) )
  583. {
  584. CAI_HintManager::ResetFoundHints();
  585. }
  586. }
  587. //-----------------------------------------------------------------------------
  588. // Purpose:
  589. // Input : *token -
  590. // Output : int
  591. //-----------------------------------------------------------------------------
  592. int CAI_HintManager::GetFlags( const char *token )
  593. {
  594. int len = strlen( token );
  595. if ( len <= 0 )
  596. {
  597. return bits_HINT_NODE_NONE;
  598. }
  599. char *lowercase = (char *)_alloca( len + 1 );
  600. Q_strncpy( lowercase, token, len+1 );
  601. strlwr( lowercase );
  602. if ( strstr( "none", lowercase ) )
  603. {
  604. return bits_HINT_NODE_NONE;
  605. }
  606. int bits = 0;
  607. if ( strstr( "visible", lowercase ) )
  608. {
  609. bits |= bits_HINT_NODE_VISIBLE;
  610. }
  611. if ( strstr( "nearest", lowercase ) )
  612. {
  613. bits |= bits_HINT_NODE_NEAREST;
  614. }
  615. if ( strstr( "random", lowercase ) )
  616. {
  617. bits |= bits_HINT_NODE_RANDOM;
  618. }
  619. // Can't be nearest and random, defer to nearest
  620. if ( ( bits & bits_HINT_NODE_NEAREST ) &&
  621. ( bits & bits_HINT_NODE_RANDOM ) )
  622. {
  623. // Remove random
  624. bits &= ~bits_HINT_NODE_RANDOM;
  625. DevMsg( "HINTFLAGS:%s, inconsistent, the nearest node is never a random hint node, treating as nearest request!\n",
  626. token );
  627. }
  628. return bits;
  629. }
  630. //-----------------------------------------------------------------------------
  631. // Purpose:
  632. //-----------------------------------------------------------------------------
  633. CAI_Hint *CAI_HintManager::GetFirstHint( AIHintIter_t *pIter )
  634. {
  635. if ( !gm_AllHints.Count() )
  636. {
  637. *pIter = (AIHintIter_t)(intp)gm_AllHints.InvalidIndex();
  638. return NULL;
  639. }
  640. *pIter = (AIHintIter_t)0;
  641. return gm_AllHints[0];
  642. }
  643. //-----------------------------------------------------------------------------
  644. // Purpose:
  645. //-----------------------------------------------------------------------------
  646. CAI_Hint *CAI_HintManager::GetNextHint( AIHintIter_t *pIter )
  647. {
  648. int iterAsInt = size_cast< int >( (intp )*pIter );
  649. if ( iterAsInt != gm_AllHints.InvalidIndex() )
  650. {
  651. intp i = iterAsInt + 1;
  652. if ( gm_AllHints.Count() <= i )
  653. {
  654. *pIter = (AIHintIter_t)(intp)gm_AllHints.InvalidIndex();
  655. return NULL;
  656. }
  657. *pIter = (AIHintIter_t)i;
  658. return gm_AllHints[i];
  659. }
  660. return NULL;
  661. }
  662. //-----------------------------------------------------------------------------
  663. // Purpose:
  664. //-----------------------------------------------------------------------------
  665. void CAI_HintManager::DumpHints()
  666. {
  667. AIHintIter_t iter = (AIHintIter_t)0;
  668. CAI_Hint *pCurHint = GetFirstHint( &iter );
  669. while (pCurHint)
  670. {
  671. const Vector &v = pCurHint->GetAbsOrigin();
  672. Msg( "(%.1f, %.1f, %.1f) -- Node ID: %d; WC id %d; type %d\n",
  673. v.x, v.y, v.z,
  674. pCurHint->GetNodeId(),
  675. pCurHint->GetWCId(),
  676. pCurHint->HintType() );
  677. pCurHint = GetNextHint( &iter );
  678. }
  679. }
  680. //-----------------------------------------------------------------------------
  681. // Purpose:
  682. //-----------------------------------------------------------------------------
  683. void CAI_HintManager::ValidateHints()
  684. {
  685. #ifdef _DEBUG
  686. int nTyped = 0;
  687. FOR_EACH_VEC( gm_AllHints, i )
  688. {
  689. Assert( dynamic_cast<CAI_Hint *>(gm_AllHints[i]) != NULL );
  690. }
  691. for ( int i = gm_TypedHints.FirstInorder(); i != gm_TypedHints.InvalidIndex(); i = gm_TypedHints.NextInorder( i ) )
  692. {
  693. FOR_EACH_VEC( gm_TypedHints[i], j )
  694. {
  695. nTyped++;
  696. Assert( dynamic_cast<CAI_Hint *>(gm_TypedHints[i][j]) != NULL );
  697. }
  698. }
  699. Assert( gm_AllHints.Count() == nTyped );
  700. #endif
  701. }
  702. //------------------------------------------------------------------------------
  703. // Purpose :
  704. // Input :
  705. // Output :
  706. //------------------------------------------------------------------------------
  707. void CAI_HintManager::DrawHintOverlays(float flDrawDuration)
  708. {
  709. int c = gm_AllHints.Count();
  710. for ( int i = 0; i < c; ++i )
  711. {
  712. CAI_Hint *pHint = gm_AllHints[ i ];
  713. int r = 0;
  714. int g = 0;
  715. int b = 255;
  716. Vector vHintPos;
  717. if (pHint->m_NodeData.nNodeID != NO_NODE)
  718. {
  719. vHintPos = g_pBigAINet->GetNode(pHint->m_NodeData.nNodeID)->GetPosition(g_pAINetworkManager->GetEditOps()->m_iHullDrawNum);
  720. }
  721. else
  722. {
  723. vHintPos = pHint->GetAbsOrigin();
  724. }
  725. if ( pHint->GetNodeId() != NO_NODE )
  726. NDebugOverlay::Text( vHintPos + Vector(0,6,8), CFmtStr("(%d), (%d)", pHint->HintType(), pHint->GetNodeId()), true, flDrawDuration );
  727. else
  728. NDebugOverlay::Text( vHintPos + Vector(0,6,8), CFmtStr("(%d)", pHint->HintType()), true, flDrawDuration );
  729. // If node is currently locked
  730. if (pHint->m_NodeData.iDisabled)
  731. {
  732. r = 100;
  733. g = 100;
  734. b = 100;
  735. }
  736. else if (pHint->m_hHintOwner != NULL)
  737. {
  738. r = 255;
  739. g = 0;
  740. b = 0;
  741. CBaseEntity* pOwner = pHint->User();
  742. if (pOwner)
  743. {
  744. char owner[255];
  745. Q_strncpy(owner,pOwner->GetDebugName(),sizeof(owner));
  746. Vector loc = vHintPos;
  747. loc.x+=6;
  748. loc.y+=6;
  749. loc.z+=6;
  750. NDebugOverlay::Text( loc, owner, true, flDrawDuration );
  751. NDebugOverlay::Line( vHintPos, pOwner->WorldSpaceCenter(), 128, 128, 128, false, 0);
  752. }
  753. }
  754. else if (pHint->IsLocked())
  755. {
  756. r = 200;
  757. g = 150;
  758. b = 10;
  759. }
  760. NDebugOverlay::Box(vHintPos, Vector(-3,-3,-3), Vector(3,3,3), r,g,b,0,flDrawDuration);
  761. // Draw line in facing direction
  762. Vector offsetDir = 12.0 * Vector(cos(DEG2RAD(pHint->Yaw())),sin(DEG2RAD(pHint->Yaw())),0);
  763. NDebugOverlay::Line(vHintPos, vHintPos+offsetDir, r,g,b,false,flDrawDuration);
  764. }
  765. }
  766. //##################################################################
  767. // > CAI_Hint
  768. //##################################################################
  769. LINK_ENTITY_TO_CLASS( ai_hint, CAI_Hint );
  770. BEGIN_DATADESC( CAI_Hint )
  771. DEFINE_EMBEDDED( m_NodeData ),
  772. // m_nTargetNodeID (reset on load)
  773. DEFINE_FIELD( m_hHintOwner, FIELD_EHANDLE),
  774. DEFINE_FIELD( m_flNextUseTime, FIELD_TIME),
  775. DEFINE_FIELD( m_vecForward, FIELD_VECTOR),
  776. DEFINE_KEYFIELD( m_nodeFOV, FIELD_FLOAT, "nodeFOV" ),
  777. DEFINE_THINKFUNC( EnableThink ),
  778. // Inputs
  779. DEFINE_INPUTFUNC( FIELD_VOID, "EnableHint", InputEnableHint ),
  780. DEFINE_INPUTFUNC( FIELD_VOID, "DisableHint", InputDisableHint ),
  781. // Outputs
  782. DEFINE_OUTPUT( m_OnNPCStartedUsing, "OnNPCStartedUsing" ),
  783. DEFINE_OUTPUT( m_OnNPCStoppedUsing, "OnNPCStoppedUsing" ),
  784. END_DATADESC( );
  785. //------------------------------------------------------------------------------
  786. // Purpose :
  787. //------------------------------------------------------------------------------
  788. void CAI_Hint::InputEnableHint( inputdata_t &inputdata )
  789. {
  790. m_NodeData.iDisabled = false;
  791. }
  792. //------------------------------------------------------------------------------
  793. // Purpose :
  794. //------------------------------------------------------------------------------
  795. void CAI_Hint::InputDisableHint( inputdata_t &inputdata )
  796. {
  797. m_NodeData.iDisabled = true;
  798. }
  799. //------------------------------------------------------------------------------
  800. // Purpose :
  801. // Input :
  802. // Output :
  803. //------------------------------------------------------------------------------
  804. void CAI_Hint::Spawn( void )
  805. {
  806. // Cache off the forward vector
  807. GetVectors( &m_vecForward, NULL, NULL );
  808. if( m_nodeFOV != 360 )
  809. {
  810. // As a micro-optimization, leave the FOV at 360 to save us
  811. // a dot product later when checking node FOV.
  812. m_nodeFOV = cos( DEG2RAD(m_nodeFOV/2) );
  813. }
  814. SetSolid( SOLID_NONE );
  815. }
  816. void CAI_Hint::Activate()
  817. {
  818. BaseClass::Activate();
  819. CAI_HintManager::AddHint( this );
  820. }
  821. void CAI_Hint::UpdateOnRemove( void )
  822. {
  823. CAI_HintManager::RemoveHint( this );
  824. BaseClass::UpdateOnRemove();
  825. }
  826. //------------------------------------------------------------------------------
  827. // Purpose : If connected to a node returns node position, otherwise
  828. // returns local hint position
  829. //
  830. // NOTE: Assumes not using multiple AI networks
  831. // Input :
  832. // Output :
  833. //------------------------------------------------------------------------------
  834. void CAI_Hint::GetPosition(CBaseCombatCharacter *pBCC, Vector *vPosition)
  835. {
  836. if ( m_NodeData.nNodeID != NO_NODE )
  837. {
  838. *vPosition = g_pBigAINet->GetNodePosition( pBCC, m_NodeData.nNodeID );
  839. }
  840. else
  841. {
  842. *vPosition = GetAbsOrigin();
  843. }
  844. }
  845. //-----------------------------------------------------------------------------
  846. // Purpose:
  847. // Input : hull -
  848. // *vPosition -
  849. //-----------------------------------------------------------------------------
  850. void CAI_Hint::GetPosition( Hull_t hull, Vector *vPosition )
  851. {
  852. if ( m_NodeData.nNodeID != NO_NODE )
  853. {
  854. *vPosition = g_pBigAINet->GetNodePosition( hull, m_NodeData.nNodeID );
  855. }
  856. else
  857. {
  858. *vPosition = GetAbsOrigin();
  859. }
  860. }
  861. //------------------------------------------------------------------------------
  862. // Purpose : If connected to a node returns node direction, otherwise
  863. // returns local hint direction
  864. //
  865. // NOTE: Assumes not using multiple AI networks
  866. // Input :
  867. // Output :
  868. //------------------------------------------------------------------------------
  869. Vector CAI_Hint::GetDirection( )
  870. {
  871. return UTIL_YawToVector( Yaw() );
  872. }
  873. //------------------------------------------------------------------------------
  874. // Purpose : If connected to a node returns node yaw, otherwise
  875. // returns local hint yaw
  876. //
  877. // NOTE: Assumes not using multiple AI networks
  878. // Input :
  879. // Output :
  880. //------------------------------------------------------------------------------
  881. float CAI_Hint::Yaw(void)
  882. {
  883. if (m_NodeData.nNodeID != NO_NODE)
  884. {
  885. return g_pBigAINet->GetNodeYaw(m_NodeData.nNodeID );
  886. }
  887. else
  888. {
  889. return GetLocalAngles().y;
  890. }
  891. }
  892. //------------------------------------------------------------------------------
  893. // Purpose : Returns if this is something that's interesting to look at
  894. //
  895. // NOTE: Assumes not using multiple AI networks
  896. // Input :
  897. // Output :
  898. //------------------------------------------------------------------------------
  899. bool CAI_Hint::IsViewable(void)
  900. {
  901. if (m_NodeData.iDisabled)
  902. {
  903. return false;
  904. }
  905. switch( HintType() )
  906. {
  907. case HINT_WORLD_VISUALLY_INTERESTING:
  908. case HINT_WORLD_VISUALLY_INTERESTING_DONT_AIM:
  909. case HINT_WORLD_VISUALLY_INTERESTING_STEALTH:
  910. return true;
  911. }
  912. return false;
  913. }
  914. //-----------------------------------------------------------------------------
  915. //-----------------------------------------------------------------------------
  916. bool CAI_Hint::IsInNodeFOV( CBaseEntity *pOther )
  917. {
  918. if( m_nodeFOV == 360 )
  919. {
  920. return true;
  921. }
  922. #if 0
  923. NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + m_vecForward * 16, 255, 255, 0, false, 1 );
  924. #endif
  925. Vector vecToNPC = pOther->GetAbsOrigin() - GetAbsOrigin();
  926. VectorNormalize( vecToNPC );
  927. float flDot = DotProduct( vecToNPC, m_vecForward );
  928. if( flDot > m_nodeFOV )
  929. {
  930. #if 0
  931. NDebugOverlay::Line( GetAbsOrigin(), pOther->GetAbsOrigin(), 0, 255, 0, false, 1 );
  932. #endif
  933. return true;
  934. }
  935. #if 0
  936. NDebugOverlay::Line( GetAbsOrigin(), pOther->GetAbsOrigin(), 255, 0, 0, false, 1 );
  937. #endif
  938. return false;
  939. }
  940. //-----------------------------------------------------------------------------
  941. // Purpose: Locks the node for use by an AI for hints
  942. // Output : Returns true if the node was available for locking, false on failure.
  943. //-----------------------------------------------------------------------------
  944. bool CAI_Hint::Lock( CBaseEntity* pNPC )
  945. {
  946. if ( m_hHintOwner != pNPC && m_hHintOwner != NULL )
  947. return false;
  948. m_hHintOwner = pNPC;
  949. return true;
  950. }
  951. //-----------------------------------------------------------------------------
  952. // Purpose: Unlocks the node, making it available for hint use by other AIs.
  953. // after the given delay time
  954. //-----------------------------------------------------------------------------
  955. void CAI_Hint::Unlock( float delay )
  956. {
  957. m_hHintOwner = NULL;
  958. m_flNextUseTime = gpGlobals->curtime + delay;
  959. }
  960. //-----------------------------------------------------------------------------
  961. // Purpose: Returns true is hint node is open for use
  962. // Input :
  963. // Output :
  964. //-----------------------------------------------------------------------------
  965. bool CAI_Hint::IsLockedBy( CBaseEntity *pNPC )
  966. {
  967. return (m_hHintOwner == pNPC);
  968. };
  969. //-----------------------------------------------------------------------------
  970. // Purpose: Returns true is hint node is open for use
  971. // Input :
  972. // Output :
  973. //-----------------------------------------------------------------------------
  974. bool CAI_Hint::IsLocked( void )
  975. {
  976. if (m_NodeData.iDisabled)
  977. {
  978. return true;
  979. }
  980. if (gpGlobals->curtime < m_flNextUseTime)
  981. {
  982. return true;
  983. }
  984. if (m_hHintOwner != NULL)
  985. {
  986. return true;
  987. }
  988. return false;
  989. };
  990. //-----------------------------------------------------------------------------
  991. // Purpose: Return true if pTestHint passes the criteria specified in hintCriteria
  992. //-----------------------------------------------------------------------------
  993. bool CAI_Hint::HintMatchesCriteria( CAI_BaseNPC *pNPC, const CHintCriteria &hintCriteria, const Vector &position, float *flNearestDistance, bool bIgnoreLock, bool bIgnoreHintType )
  994. {
  995. // Cannot be locked
  996. if ( !bIgnoreLock && IsLocked() )
  997. {
  998. REPORTFAILURE( "Node is locked." );
  999. return false;
  1000. }
  1001. if ( !bIgnoreHintType && !hintCriteria.MatchesHintType( HintType() ) )
  1002. {
  1003. return false;
  1004. }
  1005. if ( GetMinState() > NPC_STATE_IDLE || GetMaxState() < NPC_STATE_COMBAT )
  1006. {
  1007. if ( pNPC && ( pNPC->GetState() < GetMinState() || pNPC->GetState() > GetMaxState() ) )
  1008. {
  1009. REPORTFAILURE( "NPC not in correct state." );
  1010. return false;
  1011. }
  1012. }
  1013. // See if we're filtering by group name
  1014. if ( hintCriteria.GetGroup() != NULL_STRING )
  1015. {
  1016. AssertIsValidString( GetGroup() );
  1017. AssertIsValidString( hintCriteria.GetGroup() );
  1018. if ( GetGroup() == NULL_STRING || GetGroup() != hintCriteria.GetGroup() )
  1019. {
  1020. Assert(GetGroup() == NULL_STRING || strcmp( STRING(GetGroup()), STRING(hintCriteria.GetGroup())) != 0 );
  1021. REPORTFAILURE( "Doesn't match NPC hint group." );
  1022. return false;
  1023. }
  1024. }
  1025. // If we're watching for include zones, test it
  1026. if ( ( hintCriteria.HasIncludeZones() ) && ( hintCriteria.InIncludedZone( GetAbsOrigin() ) == false ) )
  1027. {
  1028. REPORTFAILURE( "Not inside include zones." );
  1029. return false;
  1030. }
  1031. // If we're watching for exclude zones, test it
  1032. if ( ( hintCriteria.HasExcludeZones() ) && ( hintCriteria.InExcludedZone( GetAbsOrigin() ) ) )
  1033. {
  1034. REPORTFAILURE( "Inside exclude zones." );
  1035. return false;
  1036. }
  1037. // See if the class handles this hint type
  1038. if ( ( pNPC != NULL ) && ( pNPC->FValidateHintType( this ) == false ) )
  1039. {
  1040. REPORTFAILURE( "NPC doesn't know how to handle that type." );
  1041. return false;
  1042. }
  1043. // Test against generic filter
  1044. if ( !hintCriteria.PassesFilter( this ) )
  1045. {
  1046. REPORTFAILURE( "Failed filter test" );
  1047. return false;
  1048. }
  1049. int nRadius = GetRadius();
  1050. if ( nRadius != 0 )
  1051. {
  1052. // Calculate our distance
  1053. float distance = (GetAbsOrigin() - position).LengthSqr();
  1054. if ( distance > nRadius * nRadius )
  1055. {
  1056. REPORTFAILURE( "NPC is not within the node's radius." );
  1057. return false;
  1058. }
  1059. }
  1060. if ( hintCriteria.HasFlag(bits_HINT_NPC_IN_NODE_FOV) )
  1061. {
  1062. if ( pNPC == NULL )
  1063. {
  1064. AssertMsg(0,"Hint node attempted to verify NPC in node FOV without NPC!\n");
  1065. }
  1066. else
  1067. {
  1068. if( !IsInNodeFOV(pNPC) )
  1069. {
  1070. REPORTFAILURE( "NPC Not in hint's FOV" );
  1071. return false;
  1072. }
  1073. }
  1074. }
  1075. if ( hintCriteria.HasFlag( bits_HINT_NODE_IN_AIMCONE ) )
  1076. {
  1077. if ( pNPC == NULL )
  1078. {
  1079. AssertMsg( 0, "Hint node attempted to find node in aimcone without specifying NPC!\n" );
  1080. }
  1081. else
  1082. {
  1083. if( !pNPC->FInAimCone( GetAbsOrigin() ) )
  1084. {
  1085. REPORTFAILURE( "Hint isn't in NPC's aimcone" );
  1086. return false;
  1087. }
  1088. }
  1089. }
  1090. if ( hintCriteria.HasFlag( bits_HINT_NODE_IN_VIEWCONE ) )
  1091. {
  1092. if ( pNPC == NULL )
  1093. {
  1094. AssertMsg( 0, "Hint node attempted to find node in viewcone without specifying NPC!\n" );
  1095. }
  1096. else
  1097. {
  1098. if( !pNPC->FInViewCone( this ) )
  1099. {
  1100. REPORTFAILURE( "Hint isn't in NPC's viewcone" );
  1101. return false;
  1102. }
  1103. }
  1104. }
  1105. if ( hintCriteria.HasFlag( bits_HINT_NOT_CLOSE_TO_ENEMY ) )
  1106. {
  1107. if ( pNPC == NULL )
  1108. {
  1109. AssertMsg( 0, "Hint node attempted to find node not close to enemy without specifying NPC!\n" );
  1110. }
  1111. else
  1112. {
  1113. if( pNPC->GetEnemy() )
  1114. {
  1115. float flDistHintToEnemySqr = GetAbsOrigin().DistToSqr( pNPC->GetEnemy()->GetAbsOrigin() ) ;
  1116. if( flDistHintToEnemySqr < Square( 30.0f * 12.0f ) )
  1117. {
  1118. REPORTFAILURE( "Hint takes NPC close to Enemy" );
  1119. return false;
  1120. }
  1121. }
  1122. }
  1123. }
  1124. {
  1125. AI_PROFILE_SCOPE( HINT_FVisible );
  1126. // See if we're requesting a visible node
  1127. if ( hintCriteria.HasFlag( bits_HINT_NODE_VISIBLE ) )
  1128. {
  1129. if ( pNPC == NULL )
  1130. {
  1131. //NOTENOTE: If you're hitting this, you've asked for a visible node without specifing an NPC!
  1132. AssertMsg( 0, "Hint node attempted to find visible node without specifying NPC!\n" );
  1133. }
  1134. else
  1135. {
  1136. if( m_NodeData.nNodeID == NO_NODE )
  1137. {
  1138. // This is just an info_hint, not a node.
  1139. if( !pNPC->FVisible( this ) )
  1140. {
  1141. REPORTFAILURE( "Hint isn't visible to NPC." );
  1142. return false;
  1143. }
  1144. }
  1145. else
  1146. {
  1147. // This hint associated with a node.
  1148. trace_t tr;
  1149. Vector vHintPos;
  1150. GetPosition(pNPC,&vHintPos);
  1151. AI_TraceLine ( pNPC->EyePosition(), vHintPos + pNPC->GetViewOffset(), pNPC->GetAITraceMask_BrushOnly(), pNPC, COLLISION_GROUP_NONE, &tr );
  1152. if ( tr.fraction != 1.0f )
  1153. {
  1154. REPORTFAILURE( "Node isn't visible to NPC." );
  1155. return false;
  1156. }
  1157. }
  1158. }
  1159. }
  1160. }
  1161. // Check for clear if requested
  1162. if ( hintCriteria.HasFlag( bits_HINT_NODE_CLEAR ) )
  1163. {
  1164. if ( pNPC == NULL )
  1165. {
  1166. //NOTENOTE: If you're hitting this, you've asked for a clear node without specifing an NPC!
  1167. AssertMsg( 0, "Hint node attempted to find clear node without specifying NPC!\n" );
  1168. }
  1169. else
  1170. {
  1171. trace_t tr;
  1172. // Can my bounding box fit there?
  1173. Vector vStep( 0, 0, pNPC->StepHeight() );
  1174. AI_TraceHull ( GetAbsOrigin() + vStep, GetAbsOrigin(), pNPC->WorldAlignMins(), pNPC->WorldAlignMaxs() - vStep,
  1175. MASK_SOLID, pNPC, COLLISION_GROUP_NONE, &tr );
  1176. if ( tr.fraction < 0.95 )
  1177. {
  1178. REPORTFAILURE( "Node isn't clear." );
  1179. return false;
  1180. }
  1181. }
  1182. }
  1183. // See if this is our next, closest node
  1184. if ( hintCriteria.HasFlag( bits_HINT_NODE_NEAREST ) )
  1185. {
  1186. Assert( flNearestDistance );
  1187. // Calculate our distance
  1188. float distance = (GetAbsOrigin() - position).Length();
  1189. // Must be closer than the current best
  1190. if ( distance > *flNearestDistance )
  1191. {
  1192. REPORTFAILURE( "Not the nearest node." );
  1193. return false;
  1194. }
  1195. // Remember the distance
  1196. *flNearestDistance = distance;
  1197. }
  1198. if ( hintCriteria.HasFlag(bits_HINT_HAS_LOS_TO_PLAYER|bits_HAS_EYEPOSITION_LOS_TO_PLAYER) )
  1199. {
  1200. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  1201. if( pPlayer != NULL )
  1202. {
  1203. Vector vecDest = GetAbsOrigin();
  1204. if( hintCriteria.HasFlag(bits_HAS_EYEPOSITION_LOS_TO_PLAYER) )
  1205. {
  1206. vecDest += pNPC->GetDefaultEyeOffset();
  1207. }
  1208. if( !pPlayer->FVisible(vecDest) )
  1209. {
  1210. REPORTFAILURE( "Do not have LOS to player" );
  1211. return false;
  1212. }
  1213. }
  1214. }
  1215. if ( hintCriteria.HasFlag( bits_HINT_HAS_NO_EYEPOSITION_LOS_TO_ENEMY ) )
  1216. {
  1217. CBaseEntity *pEnemy = pNPC->GetEnemy();
  1218. if( pEnemy != NULL )
  1219. {
  1220. Vector vecDest = GetAbsOrigin();
  1221. vecDest += pNPC->GetNodeViewOffset();
  1222. if( pEnemy->FVisible(vecDest) )
  1223. {
  1224. REPORTFAILURE( "Has LOS to enemy" );
  1225. return false;
  1226. }
  1227. }
  1228. }
  1229. // Must either be visible or not if requested
  1230. if ( hintCriteria.HasFlag( bits_HINT_NODE_NOT_VISIBLE_TO_PLAYER|bits_HINT_NODE_VISIBLE_TO_PLAYER ) )
  1231. {
  1232. bool bWasSeen = false;
  1233. // Test all potential seers
  1234. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  1235. {
  1236. CBasePlayer *pPlayer = UTIL_PlayerByIndex(i);
  1237. if ( pPlayer )
  1238. {
  1239. // Only spawn if the player's looking away from me
  1240. Vector vLookDir = pPlayer->EyeDirection3D();
  1241. Vector vTargetDir = GetAbsOrigin() - pPlayer->EyePosition();
  1242. VectorNormalize(vTargetDir);
  1243. float fDotPr = DotProduct(vLookDir,vTargetDir);
  1244. if ( fDotPr > 0 )
  1245. {
  1246. trace_t tr;
  1247. UTIL_TraceLine( pPlayer->EyePosition(), GetAbsOrigin(), MASK_SOLID_BRUSHONLY, pPlayer, COLLISION_GROUP_NONE, &tr);
  1248. if ( tr.fraction == 1.0 )
  1249. {
  1250. if ( hintCriteria.HasFlag( bits_HINT_NODE_NOT_VISIBLE_TO_PLAYER ) )
  1251. {
  1252. REPORTFAILURE( "Node is visible to player." );
  1253. return false;
  1254. }
  1255. bWasSeen = true;
  1256. }
  1257. }
  1258. }
  1259. }
  1260. if ( !bWasSeen && hintCriteria.HasFlag( bits_HINT_NODE_VISIBLE_TO_PLAYER ) )
  1261. {
  1262. REPORTFAILURE( "Node isn't visible to player." );
  1263. return false;
  1264. }
  1265. }
  1266. return true;
  1267. }
  1268. //-----------------------------------------------------------------------------
  1269. // Purpose: Draw any debug text overlays
  1270. // Input :
  1271. // Output : Current text offset from the top
  1272. //-----------------------------------------------------------------------------
  1273. int CAI_Hint::DrawDebugTextOverlays(void)
  1274. {
  1275. int text_offset = BaseClass::DrawDebugTextOverlays();
  1276. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  1277. {
  1278. char tempstr[512];
  1279. Q_snprintf(tempstr,sizeof(tempstr),"%s (%i)", GetHintTypeDescription( HintType() ), HintType());
  1280. EntityText(text_offset,tempstr,0);
  1281. text_offset++;
  1282. Q_snprintf(tempstr,sizeof(tempstr),"delay %f", MAX( 0.0f, m_flNextUseTime - gpGlobals->curtime ) ) ;
  1283. EntityText(text_offset,tempstr,0);
  1284. text_offset++;
  1285. if ( m_NodeData.iDisabled )
  1286. {
  1287. Q_snprintf(tempstr,sizeof(tempstr),"DISABLED" );
  1288. EntityText(text_offset,tempstr,0);
  1289. text_offset++;
  1290. }
  1291. }
  1292. return text_offset;
  1293. }
  1294. //-----------------------------------------------------------------------------
  1295. // Purpose: Constructor
  1296. // Input :
  1297. // Output :
  1298. //-----------------------------------------------------------------------------
  1299. CAI_Hint::CAI_Hint(void)
  1300. {
  1301. m_flNextUseTime = 0;
  1302. m_nTargetNodeID = NO_NODE;
  1303. }
  1304. //-----------------------------------------------------------------------------
  1305. // Purpose: Destructor
  1306. // Input :
  1307. // Output :
  1308. //-----------------------------------------------------------------------------
  1309. CAI_Hint::~CAI_Hint(void)
  1310. {
  1311. }
  1312. //-----------------------------------------------------------------------------
  1313. // Purpose: Sometimes FValidateHint, etc. will want to examine the underlying node to
  1314. // see if it's truly suitable ( e.g., in the same air/ground network of nodes? )
  1315. // Output : C_AINode *
  1316. //-----------------------------------------------------------------------------
  1317. CAI_Node *CAI_Hint::GetNode( void )
  1318. {
  1319. if ( m_NodeData.nNodeID != NO_NODE )
  1320. {
  1321. return g_pBigAINet->GetNode( m_NodeData.nNodeID, false );
  1322. }
  1323. return NULL;
  1324. }
  1325. //-----------------------------------------------------------------------------
  1326. //-----------------------------------------------------------------------------
  1327. void CAI_Hint::DisableForSeconds( float flSeconds )
  1328. {
  1329. Unlock( flSeconds );
  1330. }
  1331. //-----------------------------------------------------------------------------
  1332. //-----------------------------------------------------------------------------
  1333. void CAI_Hint::EnableThink()
  1334. {
  1335. SetDisabled( false );
  1336. SetThink( NULL );
  1337. }
  1338. void CAI_Hint::FixupTargetNode()
  1339. {
  1340. if ( m_NodeData.nTargetWCNodeID != -1 )
  1341. m_nTargetNodeID = g_pAINetworkManager->GetEditOps()->GetNodeIdFromWCId( m_NodeData.nTargetWCNodeID );
  1342. else
  1343. m_nTargetNodeID = NO_NODE;
  1344. }
  1345. void CAI_Hint::OnRestore()
  1346. {
  1347. BaseClass::OnRestore();
  1348. m_NodeData.nNodeID = g_pAINetworkManager->GetEditOps()->GetNodeIdFromWCId( m_NodeData.nWCNodeID );
  1349. FixupTargetNode();
  1350. CAI_Node *pNode = GetNode();
  1351. if ( !pNode )
  1352. {
  1353. if ( m_NodeData.nWCNodeID > 0 )
  1354. DevMsg("Warning: AI hint has incorrect or no AI node\n");
  1355. }
  1356. else
  1357. {
  1358. m_NodeData.vecPosition = pNode->GetOrigin();
  1359. Teleport( &m_NodeData.vecPosition, NULL, NULL );
  1360. pNode->SetHint( this );
  1361. }
  1362. }
  1363. //-----------------------------------------------------------------------------
  1364. // Purpose:
  1365. //-----------------------------------------------------------------------------
  1366. void CAI_Hint::NPCStartedUsing( CAI_BaseNPC *pNPC )
  1367. {
  1368. m_OnNPCStartedUsing.Set( pNPC, pNPC, this );
  1369. }
  1370. //-----------------------------------------------------------------------------
  1371. // Purpose:
  1372. //-----------------------------------------------------------------------------
  1373. void CAI_Hint::NPCStoppedUsing( CAI_BaseNPC *pNPC )
  1374. {
  1375. m_OnNPCStoppedUsing.Set( pNPC, pNPC, this );
  1376. }
  1377. CON_COMMAND(ai_dump_hints, "")
  1378. {
  1379. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  1380. return;
  1381. CAI_HintManager::ValidateHints();
  1382. CAI_HintManager::DumpHints();
  1383. }
  1384. //-----------------------------------------------------------------------------
  1385. //
  1386. // hints - these MUST coincide with the HINTS listed under Hint_e
  1387. //
  1388. //-----------------------------------------------------------------------------
  1389. struct hinttypedescs_t
  1390. {
  1391. Hint_e iType;
  1392. const char *pszDesc;
  1393. };
  1394. hinttypedescs_t g_pszHintDescriptions[] =
  1395. {
  1396. { HINT_NONE, "None" },
  1397. //{ HINT_NOT_USED_WORLD_DOOR, "Obsolete / Unused" },
  1398. { HINT_WORLD_WINDOW, "World: Window" },
  1399. //{ HINT_NOT_USED_WORLD_BUTTON, "Obsolete / Unused" },
  1400. //{ HINT_NOT_USED_WORLD_MACHINERY, "Obsolete / Unused" },
  1401. //{ HINT_NOT_USED_WORLD_LEDGE, "Obsolete / Unused" },
  1402. //{ HINT_NOT_USED_WORLD_LIGHT_SOURCE, "Obsolete / Unused" },
  1403. //{ HINT_NOT_USED_WORLD_HEAT_SOURCE, "Obsolete / Unused" },
  1404. //{ HINT_NOT_USED_WORLD_BLINKING_LIGHT, "Obsolete / Unused" },
  1405. //{ HINT_NOT_USED_WORLD_BRIGHT_COLORS, "Obsolete / Unused" },
  1406. //{ HINT_NOT_USED_WORLD_HUMAN_BLOOD, "Obsolete / Unused" },
  1407. //{ HINT_NOT_USED_WORLD_ALIEN_BLOOD, "Obsolete / Unused" },
  1408. { HINT_WORLD_WORK_POSITION, "Act Busy" },
  1409. { HINT_WORLD_VISUALLY_INTERESTING, "World: Visually Interesting" },
  1410. { HINT_WORLD_VISUALLY_INTERESTING_DONT_AIM, "World: Visually Interesting (Don't Aim)" },
  1411. { HINT_WORLD_INHIBIT_COMBINE_MINES, "World: Inhibit Combine Mines" },
  1412. { HINT_WORLD_VISUALLY_INTERESTING_STEALTH, "World: Visually Interesting (Stealth)" },
  1413. { HINT_TACTICAL_COVER_MED, "Tactical: Cover Medium" },
  1414. { HINT_TACTICAL_COVER_LOW, "Tactical: Cover Low" },
  1415. //{ HINT_NOT_USED_TACTICAL_SPAWN, "Tactical: Spawn" },
  1416. { HINT_TACTICAL_PINCH, "Tactical: Pinch" },
  1417. //{ HINT_NOT_USED_TACTICAL_GUARD, "Obsolete / Unused" },
  1418. { HINT_TACTICAL_ENEMY_DISADVANTAGED, "Tactical: Enemy Disadvantage" },
  1419. //{ HINT_NOT_USED_HEALTH_KIT, "Obsolete / Unused" },
  1420. { HINT_TACTICAL_HIGH_GROUND, "Tactical: High Ground" },
  1421. //{ HINT_NOT_USED_URBAN_STREETCORNER, "Obsolete / Unused" },
  1422. //{ HINT_NOT_USED_URBAN_STREETLAMP, "Obsolete / Unused" },
  1423. //{ HINT_NOT_USED_URBAN_DARK_SPOT, "Obsolete / Unused" },
  1424. //{ HINT_NOT_USED_URBAN_POSTER, "Obsolete / Unused" },
  1425. //{ HINT_NOT_USED_URBAN_SHELTER, "Obsolete / Unused" },
  1426. //{ HINT_NOT_USED_ASSASSIN_SECLUDED, "Obsolete / Unused" },
  1427. //{ HINT_NOT_USED_ASSASSIN_RAFTERS, "Obsolete / Unused" },
  1428. //{ HINT_NOT_USED_ASSASSIN_GROUND, "Obsolete / Unused" },
  1429. //{ HINT_NOT_USED_ASSASSIN_MONKEYBARS, "Obsolete / Unused" },
  1430. { HINT_ANTLION_BURROW_POINT, "Antlion: Burrow Point" },
  1431. { HINT_ANTLION_THUMPER_FLEE_POINT, "Antlion: Thumper Flee Point" },
  1432. //{ HINT_HEADCRAB_BURROW_POINT, "Obsolete / Unused" },
  1433. //{ HINT_NOT_USED_ROLLER_PATROL_POINT, "Obsolete / Unused" },
  1434. //{ HINT_NOT_USED_ROLLER_CLEANUP_POINT, "Obsolete / Unused" },
  1435. //{ HINT_NOT_USED_PSTORM_ROCK_SPAWN, "Obsolete / Unused" },
  1436. { HINT_CROW_FLYTO_POINT, "Crow: Flyto Point" },
  1437. //{ HINT_BUG_PATROL_POINT, "Obsolete / Unused" },
  1438. { HINT_FOLLOW_WAIT_POINT, "Follow: Wait Point" },
  1439. { HINT_JUMP_OVERRIDE, "Jump Override" },
  1440. { HINT_PLAYER_SQUAD_TRANSITON_POINT, "Squad Transition Point" },
  1441. { HINT_NPC_EXIT_POINT, "Act Busy: Exit Point" },
  1442. { HINT_STRIDER_NODE, "Strider" },
  1443. { HINT_PLAYER_ALLY_MOVE_AWAY_DEST, "Ally MoveAway Point" },
  1444. { HINT_CSTRIKE_HOSTAGE_ESCAPE, "CS Port: Hostage Escape" },
  1445. #ifdef PORTAL2
  1446. { HINT_PORTAL2_NEST, "Portal 2: Nest" },
  1447. #endif // PORTAL2
  1448. };
  1449. //-----------------------------------------------------------------------------
  1450. // Purpose:
  1451. //-----------------------------------------------------------------------------
  1452. const char *GetHintTypeDescription( Hint_e iHintType )
  1453. {
  1454. for ( int i = 0; i < ARRAYSIZE(g_pszHintDescriptions); i++ )
  1455. {
  1456. if ( g_pszHintDescriptions[i].iType == iHintType )
  1457. return g_pszHintDescriptions[i].pszDesc;
  1458. }
  1459. return "Obsolete / Unused";
  1460. }
  1461. //-----------------------------------------------------------------------------
  1462. // Purpose:
  1463. //-----------------------------------------------------------------------------
  1464. const char *GetHintTypeDescription( CAI_Hint *pHint )
  1465. {
  1466. return GetHintTypeDescription( pHint->HintType() );
  1467. }
  1468. //-----------------------------------------------------------------------------
  1469. // Purpose: Debug command to drop hints into the world
  1470. //-----------------------------------------------------------------------------
  1471. void CC_ai_drop_hint( const CCommand &args )
  1472. {
  1473. CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() );
  1474. if ( !pPlayer )
  1475. return;
  1476. if ( args.ArgC() < 2 )
  1477. {
  1478. Msg("Invalid hint type specified. Format: ai_drop_hint <hint type>\nValid hint types:\n");
  1479. for ( int i = 0; i < ARRAYSIZE(g_pszHintDescriptions); i++ )
  1480. {
  1481. Msg("%d : %s\n", g_pszHintDescriptions[i].iType, g_pszHintDescriptions[i].pszDesc );
  1482. }
  1483. return;
  1484. }
  1485. HintNodeData nodeData;
  1486. nodeData.strEntityName = MAKE_STRING("ai_drop_hint");
  1487. nodeData.vecPosition = pPlayer->EyePosition();
  1488. nodeData.nHintType = atoi( args[1] );
  1489. nodeData.nNodeID = NO_NODE;
  1490. nodeData.strGroup = NULL_STRING;
  1491. nodeData.iDisabled = false;
  1492. nodeData.iszActivityName = NULL_STRING;
  1493. nodeData.fIgnoreFacing = HIF_DEFAULT;
  1494. nodeData.minState = NPC_STATE_IDLE;
  1495. nodeData.maxState = NPC_STATE_COMBAT;
  1496. nodeData.nRadius = 0;
  1497. CAI_Hint *pHint = CAI_HintManager::CreateHint( &nodeData, NULL );
  1498. if ( pHint )
  1499. {
  1500. ((CBaseEntity *)pHint)->Activate();
  1501. pHint->KeyValue( "nodeFOV", "360" );
  1502. pHint->m_debugOverlays |= (OVERLAY_TEXT_BIT | OVERLAY_BBOX_BIT);
  1503. }
  1504. }
  1505. ConCommand ai_drop_hint( "ai_drop_hint", CC_ai_drop_hint, "Drop an ai_hint at the player's current eye position.", FCVAR_CHEAT );