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.

1687 lines
48 KiB

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