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.

711 lines
17 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. // nav_entities.cpp
  9. // AI Navigation entities
  10. // Author: Michael S. Booth ([email protected]), January 2003
  11. #include "cbase.h"
  12. #include "nav_mesh.h"
  13. #include "nav_node.h"
  14. #include "nav_pathfind.h"
  15. #include "nav_colors.h"
  16. #include "fmtstr.h"
  17. #include "props_shared.h"
  18. #include "func_breakablesurf.h"
  19. #ifdef TERROR
  20. #include "func_elevator.h"
  21. #include "AmbientLight.h"
  22. #endif
  23. #ifdef TF_DLL
  24. #include "tf_player.h"
  25. #include "bot/tf_bot.h"
  26. #endif
  27. #include "Color.h"
  28. #include "collisionutils.h"
  29. #include "functorutils.h"
  30. #include "team.h"
  31. #include "nav_entities.h"
  32. // memdbgon must be the last include file in a .cpp file!!!
  33. #include "tier0/memdbgon.h"
  34. //--------------------------------------------------------------------------------------------------------
  35. //--------------------------------------------------------------------------------------------------------
  36. BEGIN_DATADESC( CFuncNavCost )
  37. // Inputs
  38. DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
  39. DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
  40. DEFINE_KEYFIELD( m_iszTags, FIELD_STRING, "tags" ),
  41. DEFINE_KEYFIELD( m_team, FIELD_INTEGER, "team" ),
  42. DEFINE_KEYFIELD( m_isDisabled, FIELD_BOOLEAN, "start_disabled" ),
  43. DEFINE_THINKFUNC( CostThink ),
  44. END_DATADESC()
  45. LINK_ENTITY_TO_CLASS( func_nav_avoid, CFuncNavAvoid );
  46. LINK_ENTITY_TO_CLASS( func_nav_prefer, CFuncNavPrefer );
  47. CUtlVector< CHandle< CFuncNavCost > > CFuncNavCost::gm_masterCostVector;
  48. CountdownTimer CFuncNavCost::gm_dirtyTimer;
  49. #define UPDATE_DIRTY_TIME 0.2f
  50. //--------------------------------------------------------------------------------------------------------
  51. void CFuncNavCost::Spawn( void )
  52. {
  53. BaseClass::Spawn();
  54. gm_masterCostVector.AddToTail( this );
  55. gm_dirtyTimer.Start( UPDATE_DIRTY_TIME );
  56. SetSolid( SOLID_BSP );
  57. AddSolidFlags( FSOLID_NOT_SOLID );
  58. SetMoveType( MOVETYPE_NONE );
  59. SetModel( STRING( GetModelName() ) );
  60. AddEffects( EF_NODRAW );
  61. SetCollisionGroup( COLLISION_GROUP_NONE );
  62. VPhysicsInitShadow( false, false );
  63. SetThink( &CFuncNavCost::CostThink );
  64. SetNextThink( gpGlobals->curtime + UPDATE_DIRTY_TIME );
  65. m_tags.RemoveAll();
  66. const char *tags = STRING( m_iszTags );
  67. // chop space-delimited string into individual tokens
  68. if ( tags )
  69. {
  70. char *buffer = V_strdup ( tags );
  71. for( char *token = strtok( buffer, " " ); token; token = strtok( NULL, " " ) )
  72. {
  73. m_tags.AddToTail( CFmtStr( "%s", token ) );
  74. }
  75. delete [] buffer;
  76. }
  77. }
  78. //--------------------------------------------------------------------------------------------------------
  79. void CFuncNavCost::UpdateOnRemove( void )
  80. {
  81. gm_masterCostVector.FindAndFastRemove( this );
  82. BaseClass::UpdateOnRemove();
  83. gm_dirtyTimer.Start( UPDATE_DIRTY_TIME );
  84. }
  85. //--------------------------------------------------------------------------------------------------------
  86. void CFuncNavCost::InputEnable( inputdata_t &inputdata )
  87. {
  88. m_isDisabled = false;
  89. gm_dirtyTimer.Start( UPDATE_DIRTY_TIME );
  90. }
  91. //--------------------------------------------------------------------------------------------------------
  92. void CFuncNavCost::InputDisable( inputdata_t &inputdata )
  93. {
  94. m_isDisabled = true;
  95. gm_dirtyTimer.Start( UPDATE_DIRTY_TIME );
  96. }
  97. //--------------------------------------------------------------------------------------------------------
  98. void CFuncNavCost::CostThink( void )
  99. {
  100. SetNextThink( gpGlobals->curtime + UPDATE_DIRTY_TIME );
  101. if ( gm_dirtyTimer.HasStarted() && gm_dirtyTimer.IsElapsed() )
  102. {
  103. // one or more avoid entities have changed - update nav decoration
  104. gm_dirtyTimer.Invalidate();
  105. UpdateAllNavCostDecoration();
  106. }
  107. }
  108. //--------------------------------------------------------------------------------------------------------
  109. bool CFuncNavCost::HasTag( const char *groupname ) const
  110. {
  111. for( int i=0; i<m_tags.Count(); ++i )
  112. {
  113. if ( FStrEq( m_tags[i], groupname ) )
  114. {
  115. return true;
  116. }
  117. }
  118. return false;
  119. }
  120. //--------------------------------------------------------------------------------------------------------
  121. // Return true if this cost applies to the given actor
  122. bool CFuncNavCost::IsApplicableTo( CBaseCombatCharacter *who ) const
  123. {
  124. if ( !who )
  125. {
  126. return false;
  127. }
  128. if ( m_team > 0 )
  129. {
  130. if ( who->GetTeamNumber() != m_team )
  131. {
  132. return false;
  133. }
  134. }
  135. #ifdef TF_DLL
  136. // TODO: Make group comparison efficient and move to base combat character
  137. CTFBot *bot = ToTFBot( who );
  138. if ( bot )
  139. {
  140. if ( bot->HasTheFlag() )
  141. {
  142. if ( HasTag( "bomb_carrier" ) )
  143. {
  144. return true;
  145. }
  146. // check custom bomb_carrier tags for this bot
  147. for( int i=0; i<m_tags.Count(); ++i )
  148. {
  149. const char* pszTag = m_tags[i];
  150. if ( V_stristr( pszTag, "bomb_carrier" ) )
  151. {
  152. if ( bot->HasTag( pszTag ) )
  153. {
  154. return true;
  155. }
  156. }
  157. }
  158. // the bomb carrier only pays attention to bomb_carrier costs
  159. return false;
  160. }
  161. if ( bot->HasMission( CTFBot::MISSION_DESTROY_SENTRIES ) )
  162. {
  163. if ( HasTag( "mission_sentry_buster" ) )
  164. {
  165. return true;
  166. }
  167. }
  168. if ( bot->HasMission( CTFBot::MISSION_SNIPER ) )
  169. {
  170. if ( HasTag( "mission_sniper" ) )
  171. {
  172. return true;
  173. }
  174. }
  175. if ( bot->HasMission( CTFBot::MISSION_SPY ) )
  176. {
  177. if ( HasTag( "mission_spy" ) )
  178. {
  179. return true;
  180. }
  181. }
  182. if ( bot->HasMission( CTFBot::MISSION_REPROGRAMMED ) )
  183. {
  184. return false;
  185. }
  186. if ( !bot->IsOnAnyMission() )
  187. {
  188. if ( HasTag( "common" ) )
  189. {
  190. return true;
  191. }
  192. }
  193. if ( HasTag( bot->GetPlayerClass()->GetName() ) )
  194. {
  195. return true;
  196. }
  197. // check custom tags for this bot
  198. for( int i=0; i<m_tags.Count(); ++i )
  199. {
  200. if ( bot->HasTag( m_tags[i] ) )
  201. {
  202. return true;
  203. }
  204. }
  205. // this cost doesn't apply to me
  206. return false;
  207. }
  208. #endif
  209. return false;
  210. }
  211. //--------------------------------------------------------------------------------------------------------
  212. // Reevaluate all func_nav_cost entities and update the nav decoration accordingly.
  213. // This is required to handle overlapping func_nav_cost entities.
  214. void CFuncNavCost::UpdateAllNavCostDecoration( void )
  215. {
  216. int i, j;
  217. // first, clear all avoid decoration from the mesh
  218. for( i=0; i<TheNavAreas.Count(); ++i )
  219. {
  220. TheNavAreas[i]->ClearAllNavCostEntities();
  221. }
  222. // now, mark all areas with active cost entities overlapping them
  223. for( i=0; i<gm_masterCostVector.Count(); ++i )
  224. {
  225. CFuncNavCost *cost = gm_masterCostVector[i];
  226. if ( !cost || !cost->IsEnabled() )
  227. {
  228. continue;
  229. }
  230. Extent extent;
  231. extent.Init( cost );
  232. CUtlVector< CNavArea * > overlapVector;
  233. TheNavMesh->CollectAreasOverlappingExtent( extent, &overlapVector );
  234. Ray_t ray;
  235. trace_t tr;
  236. ICollideable *pCollide = cost->CollisionProp();
  237. for( j=0; j<overlapVector.Count(); ++j )
  238. {
  239. ray.Init( overlapVector[j]->GetCenter(), overlapVector[j]->GetCenter() );
  240. enginetrace->ClipRayToCollideable( ray, MASK_ALL, pCollide, &tr );
  241. if ( tr.startsolid )
  242. {
  243. overlapVector[j]->AddFuncNavCostEntity( cost );
  244. }
  245. }
  246. }
  247. }
  248. //--------------------------------------------------------------------------------------------------------
  249. //--------------------------------------------------------------------------------------------------------
  250. // Return pathfind cost multiplier for the given actor
  251. float CFuncNavAvoid::GetCostMultiplier( CBaseCombatCharacter *who ) const
  252. {
  253. if ( IsApplicableTo( who ) )
  254. {
  255. return 25.0f;
  256. }
  257. return 1.0f;
  258. }
  259. //--------------------------------------------------------------------------------------------------------
  260. //--------------------------------------------------------------------------------------------------------
  261. // Return pathfind cost multiplier for the given actor
  262. float CFuncNavPrefer::GetCostMultiplier( CBaseCombatCharacter *who ) const
  263. {
  264. if ( IsApplicableTo( who ) )
  265. {
  266. return 0.04f; // 1/25th
  267. }
  268. return 1.0f;
  269. }
  270. //--------------------------------------------------------------------------------------------------------
  271. //--------------------------------------------------------------------------------------------------------
  272. BEGIN_DATADESC( CFuncNavBlocker )
  273. // Inputs
  274. DEFINE_INPUTFUNC( FIELD_VOID, "BlockNav", InputBlockNav ),
  275. DEFINE_INPUTFUNC( FIELD_VOID, "UnblockNav", InputUnblockNav ),
  276. DEFINE_KEYFIELD( m_blockedTeamNumber, FIELD_INTEGER, "teamToBlock" ),
  277. DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ),
  278. END_DATADESC()
  279. LINK_ENTITY_TO_CLASS( func_nav_blocker, CFuncNavBlocker );
  280. CUtlLinkedList<CFuncNavBlocker *> CFuncNavBlocker::gm_NavBlockers;
  281. //-----------------------------------------------------------------------------------------------------
  282. int CFuncNavBlocker::DrawDebugTextOverlays( void )
  283. {
  284. int offset = BaseClass::DrawDebugTextOverlays();
  285. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  286. {
  287. CFmtStr str;
  288. // FIRST_GAME_TEAM skips TEAM_SPECTATOR and TEAM_UNASSIGNED, so we can print
  289. // useful team names in a non-game-specific fashion.
  290. for ( int i=FIRST_GAME_TEAM; i<FIRST_GAME_TEAM + MAX_NAV_TEAMS; ++i )
  291. {
  292. if ( IsBlockingNav( i ) )
  293. {
  294. CTeam *team = GetGlobalTeam( i );
  295. if ( team )
  296. {
  297. EntityText( offset++, str.sprintf( "blocking team %s", team->GetName() ), 0 );
  298. }
  299. else
  300. {
  301. EntityText( offset++, str.sprintf( "blocking team %d", i ), 0 );
  302. }
  303. }
  304. }
  305. NavAreaCollector collector( true );
  306. Extent extent;
  307. extent.Init( this );
  308. TheNavMesh->ForAllAreasOverlappingExtent( collector, extent );
  309. for ( int i=0; i<collector.m_area.Count(); ++i )
  310. {
  311. CNavArea *area = collector.m_area[i];
  312. Extent areaExtent;
  313. area->GetExtent( &areaExtent );
  314. if ( debugoverlay )
  315. {
  316. debugoverlay->AddBoxOverlay( vec3_origin, areaExtent.lo, areaExtent.hi, vec3_angle, 0, 255, 0, 10, NDEBUG_PERSIST_TILL_NEXT_SERVER );
  317. }
  318. }
  319. }
  320. return offset;
  321. }
  322. //--------------------------------------------------------------------------------------------------------
  323. void CFuncNavBlocker::UpdateBlocked()
  324. {
  325. NavAreaCollector collector( true );
  326. Extent extent;
  327. extent.Init( this );
  328. TheNavMesh->ForAllAreasOverlappingExtent( collector, extent );
  329. for ( int i=0; i<collector.m_area.Count(); ++i )
  330. {
  331. CNavArea *area = collector.m_area[i];
  332. area->UpdateBlocked( true );
  333. }
  334. }
  335. //--------------------------------------------------------------------------------------------------------
  336. // Forces nav areas to unblock when the nav blocker is deleted (round restart) so flow can compute properly
  337. void CFuncNavBlocker::UpdateOnRemove( void )
  338. {
  339. UnblockNav();
  340. gm_NavBlockers.FindAndRemove( this );
  341. BaseClass::UpdateOnRemove();
  342. }
  343. //--------------------------------------------------------------------------------------------------------
  344. void CFuncNavBlocker::Spawn( void )
  345. {
  346. gm_NavBlockers.AddToTail( this );
  347. if ( !m_blockedTeamNumber )
  348. m_blockedTeamNumber = TEAM_ANY;
  349. SetMoveType( MOVETYPE_NONE );
  350. SetModel( STRING( GetModelName() ) );
  351. AddEffects( EF_NODRAW );
  352. SetCollisionGroup( COLLISION_GROUP_NONE );
  353. SetSolid( SOLID_NONE );
  354. AddSolidFlags( FSOLID_NOT_SOLID );
  355. CollisionProp()->WorldSpaceAABB( &m_CachedMins, &m_CachedMaxs );
  356. if ( m_bDisabled )
  357. {
  358. UnblockNav();
  359. }
  360. else
  361. {
  362. BlockNav();
  363. }
  364. }
  365. //--------------------------------------------------------------------------------------------------------
  366. void CFuncNavBlocker::InputBlockNav( inputdata_t &inputdata )
  367. {
  368. BlockNav();
  369. }
  370. //--------------------------------------------------------------------------------------------------------
  371. void CFuncNavBlocker::InputUnblockNav( inputdata_t &inputdata )
  372. {
  373. UnblockNav();
  374. }
  375. //--------------------------------------------------------------------------------------------------------
  376. void CFuncNavBlocker::BlockNav( void )
  377. {
  378. if ( m_blockedTeamNumber == TEAM_ANY )
  379. {
  380. for ( int i=0; i<MAX_NAV_TEAMS; ++i )
  381. {
  382. m_isBlockingNav[ i ] = true;
  383. }
  384. }
  385. else
  386. {
  387. int teamNumber = m_blockedTeamNumber % MAX_NAV_TEAMS;
  388. m_isBlockingNav[ teamNumber ] = true;
  389. }
  390. Extent extent;
  391. extent.Init( this );
  392. TheNavMesh->ForAllAreasOverlappingExtent( *this, extent );
  393. }
  394. //--------------------------------------------------------------------------------------------------------
  395. void CFuncNavBlocker::UnblockNav( void )
  396. {
  397. if ( m_blockedTeamNumber == TEAM_ANY )
  398. {
  399. for ( int i=0; i<MAX_NAV_TEAMS; ++i )
  400. {
  401. m_isBlockingNav[ i ] = false;
  402. }
  403. }
  404. else
  405. {
  406. int teamNumber = m_blockedTeamNumber % MAX_NAV_TEAMS;
  407. m_isBlockingNav[ teamNumber ] = false;
  408. }
  409. UpdateBlocked();
  410. }
  411. //--------------------------------------------------------------------------------------------------------
  412. // functor that blocks areas in our extent
  413. bool CFuncNavBlocker::operator()( CNavArea *area )
  414. {
  415. area->MarkAsBlocked( m_blockedTeamNumber, this );
  416. return true;
  417. }
  418. //--------------------------------------------------------------------------------------------------------
  419. bool CFuncNavBlocker::CalculateBlocked( bool *pResultByTeam, const Vector &vecMins, const Vector &vecMaxs )
  420. {
  421. int nTeamsBlocked = 0;
  422. int i;
  423. bool bBlocked = false;
  424. for ( i=0; i<MAX_NAV_TEAMS; ++i )
  425. {
  426. pResultByTeam[i] = false;
  427. }
  428. FOR_EACH_LL( gm_NavBlockers, iBlocker )
  429. {
  430. CFuncNavBlocker *pBlocker = gm_NavBlockers[iBlocker];
  431. bool bIsIntersecting = false;
  432. for ( i=0; i<MAX_NAV_TEAMS; ++i )
  433. {
  434. if ( pBlocker->m_isBlockingNav[i] )
  435. {
  436. if ( !pResultByTeam[i] )
  437. {
  438. if ( bIsIntersecting || ( bIsIntersecting = IsBoxIntersectingBox( pBlocker->m_CachedMins, pBlocker->m_CachedMaxs, vecMins, vecMaxs ) ) != false )
  439. {
  440. bBlocked = true;
  441. pResultByTeam[i] = true;
  442. nTeamsBlocked++;
  443. }
  444. else
  445. {
  446. continue;
  447. }
  448. }
  449. }
  450. }
  451. if ( nTeamsBlocked == MAX_NAV_TEAMS )
  452. {
  453. break;
  454. }
  455. }
  456. return bBlocked;
  457. }
  458. //-----------------------------------------------------------------------------------------------------
  459. /**
  460. * An entity that can obstruct nav areas. This is meant for semi-transient areas that obstruct
  461. * pathfinding but can be ignored for longer-term queries like computing L4D flow distances and
  462. * escape routes.
  463. */
  464. class CFuncNavObstruction : public CBaseEntity, public INavAvoidanceObstacle
  465. {
  466. DECLARE_DATADESC();
  467. DECLARE_CLASS( CFuncNavObstruction, CBaseEntity );
  468. public:
  469. void Spawn();
  470. virtual void UpdateOnRemove( void );
  471. void InputEnable( inputdata_t &inputdata );
  472. void InputDisable( inputdata_t &inputdata );
  473. virtual bool IsPotentiallyAbleToObstructNavAreas( void ) const { return true; } // could we at some future time obstruct nav?
  474. virtual float GetNavObstructionHeight( void ) const { return JumpCrouchHeight; } // height at which to obstruct nav areas
  475. virtual bool CanObstructNavAreas( void ) const { return !m_bDisabled; } // can we obstruct nav right this instant?
  476. virtual CBaseEntity *GetObstructingEntity( void ) { return this; }
  477. virtual void OnNavMeshLoaded( void )
  478. {
  479. if ( !m_bDisabled )
  480. {
  481. ObstructNavAreas();
  482. }
  483. }
  484. int DrawDebugTextOverlays( void );
  485. bool operator()( CNavArea *area ); // functor that obstructs areas in our extent
  486. private:
  487. void ObstructNavAreas( void );
  488. bool m_bDisabled;
  489. };
  490. //--------------------------------------------------------------------------------------------------------
  491. BEGIN_DATADESC( CFuncNavObstruction )
  492. DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ),
  493. END_DATADESC()
  494. LINK_ENTITY_TO_CLASS( func_nav_avoidance_obstacle, CFuncNavObstruction );
  495. //-----------------------------------------------------------------------------------------------------
  496. int CFuncNavObstruction::DrawDebugTextOverlays( void )
  497. {
  498. int offset = BaseClass::DrawDebugTextOverlays();
  499. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  500. {
  501. if ( CanObstructNavAreas() )
  502. {
  503. EntityText( offset++, "Obstructing nav", NDEBUG_PERSIST_TILL_NEXT_SERVER );
  504. }
  505. else
  506. {
  507. EntityText( offset++, "Not obstructing nav", NDEBUG_PERSIST_TILL_NEXT_SERVER );
  508. }
  509. }
  510. return offset;
  511. }
  512. //--------------------------------------------------------------------------------------------------------
  513. void CFuncNavObstruction::UpdateOnRemove( void )
  514. {
  515. TheNavMesh->UnregisterAvoidanceObstacle( this );
  516. BaseClass::UpdateOnRemove();
  517. }
  518. //--------------------------------------------------------------------------------------------------------
  519. void CFuncNavObstruction::Spawn( void )
  520. {
  521. SetMoveType( MOVETYPE_NONE );
  522. SetModel( STRING( GetModelName() ) );
  523. AddEffects( EF_NODRAW );
  524. SetCollisionGroup( COLLISION_GROUP_NONE );
  525. SetSolid( SOLID_NONE );
  526. AddSolidFlags( FSOLID_NOT_SOLID );
  527. if ( !m_bDisabled )
  528. {
  529. ObstructNavAreas();
  530. TheNavMesh->RegisterAvoidanceObstacle( this );
  531. }
  532. }
  533. //--------------------------------------------------------------------------------------------------------
  534. void CFuncNavObstruction::InputEnable( inputdata_t &inputdata )
  535. {
  536. m_bDisabled = false;
  537. ObstructNavAreas();
  538. TheNavMesh->RegisterAvoidanceObstacle( this );
  539. }
  540. //--------------------------------------------------------------------------------------------------------
  541. void CFuncNavObstruction::InputDisable( inputdata_t &inputdata )
  542. {
  543. m_bDisabled = true;
  544. TheNavMesh->UnregisterAvoidanceObstacle( this );
  545. }
  546. //--------------------------------------------------------------------------------------------------------
  547. void CFuncNavObstruction::ObstructNavAreas( void )
  548. {
  549. Extent extent;
  550. extent.Init( this );
  551. TheNavMesh->ForAllAreasOverlappingExtent( *this, extent );
  552. }
  553. //--------------------------------------------------------------------------------------------------------
  554. // functor that blocks areas in our extent
  555. bool CFuncNavObstruction::operator()( CNavArea *area )
  556. {
  557. area->MarkObstacleToAvoid( GetNavObstructionHeight() );
  558. return true;
  559. }
  560. //--------------------------------------------------------------------------------------------------------------