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.

2255 lines
64 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. // tf_raid_logic.cpp
  3. // Raid game mode singleton manager
  4. // Michael Booth, November 2009
  5. #include "cbase.h"
  6. #ifdef TF_RAID_MODE
  7. #include "team.h"
  8. #include "nav_pathfind.h"
  9. #include "tf_gamerules.h"
  10. #include "team_control_point_master.h"
  11. #include "bot/tf_bot.h"
  12. #include "nav_mesh/tf_nav_mesh.h"
  13. #include "raid/tf_raid_logic.h"
  14. #include "bot_npc/bot_npc_minion.h"
  15. #include "tf_obj_sentrygun.h"
  16. #include "filesystem.h"
  17. #include "func_respawnroom.h"
  18. #include "pathtrack.h"
  19. extern ConVar mp_teams_unbalance_limit;
  20. extern ConVar mp_autoteambalance;
  21. extern ConVar sv_alltalk;
  22. extern ConVar mp_timelimit;
  23. CRaidLogic *g_pRaidLogic = NULL;
  24. ConVar tf_debug_sniper_spots( "tf_debug_sniper_spots", "0"/*, FCVAR_CHEAT*/ );
  25. ConVar tf_raid_max_wanderers( "tf_raid_max_wanderers", "10"/*, FCVAR_CHEAT*/ );
  26. ConVar tf_raid_max_defense_engineers( "tf_raid_max_defense_engineers", "6"/*, FCVAR_CHEAT*/ );
  27. ConVar tf_raid_max_defense_demomen( "tf_raid_max_defense_demomen", "1"/*, FCVAR_CHEAT*/ );
  28. ConVar tf_raid_max_defense_heavies( "tf_raid_max_defense_heavies", "1"/*, FCVAR_CHEAT*/ );
  29. ConVar tf_raid_max_defense_soldiers( "tf_raid_max_defense_soldiers", "1"/*, FCVAR_CHEAT*/ );
  30. ConVar tf_raid_max_defense_pyros( "tf_raid_max_defense_pyros", "1"/*, FCVAR_CHEAT*/ );
  31. ConVar tf_raid_max_defense_spies( "tf_raid_max_defense_spies", "1"/*, FCVAR_CHEAT*/ );
  32. ConVar tf_raid_max_defense_snipers( "tf_raid_max_defense_snipers", "3"/*, FCVAR_CHEAT*/ );
  33. ConVar tf_raid_max_defense_squads( "tf_raid_max_defense_squads", "1"/*, FCVAR_CHEAT*/ );
  34. ConVar tf_raid_wandering_density( "tf_raid_wandering_density", "0.00001", FCVAR_CHEAT, "Wandering defenders per unit area" );
  35. ConVar tf_raid_spawn_wanderers( "tf_raid_spawn_wanderers", "1"/*, FCVAR_CHEAT*/ );
  36. ConVar tf_raid_defender_density( "tf_raid_defender_density", "0.000001", 0/*FCVAR_CHEAT*/, "Wandering defenders per unit area" );
  37. ConVar tf_raid_max_defender_count( "tf_raid_max_defender_count", "18", 0/*FCVAR_CHEAT*/ );
  38. ConVar tf_raid_spawn_defenders( "tf_raid_spawn_defenders", "1"/*, FCVAR_CHEAT*/ );
  39. ConVar tf_raid_sentry_density( "tf_raid_sentry_density", "0.0000005", 0/*FCVAR_CHEAT*/, "Sentry guns per unit area" );
  40. ConVar tf_raid_sentry_spacing( "tf_raid_sentry_spacing", "750", 0/*FCVAR_CHEAT*/, "Minimum travel distance between sentry gun spots" );
  41. ConVar tf_raid_debug_sentry_placement( "tf_raid_debug_sentry_placement", "0"/*, FCVAR_CHEAT*/ );
  42. ConVar tf_raid_spawn_sentries( "tf_raid_spawn_sentries", "1"/*, FCVAR_CHEAT*/ );
  43. ConVar tf_raid_spawn_engineers( "tf_raid_spawn_engineers", "0"/*, FCVAR_CHEAT*/ );
  44. ConVar tf_raid_engineer_spawn_interval( "tf_raid_engineer_spawn_interval", "20"/*, FCVAR_CHEAT*/ );
  45. ConVar tf_raid_mob_spawn_min_interval( "tf_raid_mob_spawn_min_interval", "60"/*, FCVAR_CHEAT*/ );
  46. ConVar tf_raid_mob_spawn_max_interval( "tf_raid_mob_spawn_max_interval", "90"/*, FCVAR_CHEAT*/ );
  47. ConVar tf_raid_mob_spawn_count( "tf_raid_mob_spawn_count", "15"/*, FCVAR_CHEAT*/ );
  48. ConVar tf_raid_mob_spawn_below_tolerance( "tf_raid_mob_spawn_below_tolerance", "150"/*, FCVAR_CHEAT*/ );
  49. ConVar tf_raid_mob_spawn_min_range( "tf_raid_mob_spawn_min_range", "1000"/*, FCVAR_CHEAT*/ );
  50. ConVar tf_raid_spawn_mobs( "tf_raid_spawn_mobs", "1"/*, FCVAR_CHEAT*/ );
  51. ConVar tf_raid_spawn_mob_as_squad_chance_start( "tf_raid_spawn_mob_as_squad_chance_start", "100" ); // /*, FCVAR_CHEAT*/ );
  52. ConVar tf_raid_spawn_mob_as_squad_chance_halfway( "tf_raid_spawn_mob_as_squad_chance_halfway", "100" ); // /*, FCVAR_CHEAT*/ );
  53. ConVar tf_raid_spawn_mob_as_squad_chance_final( "tf_raid_spawn_mob_as_squad_chance_final", "100" ); // /*, FCVAR_CHEAT*/ );
  54. ConVar tf_raid_squad_medic_intro_percent( "tf_raid_squad_medic_intro_percent", "0.5" ); // /*, FCVAR_CHEAT*/ );
  55. ConVar tf_raid_capture_mob_interval( "tf_raid_capture_mob_interval", "20"/*, FCVAR_CHEAT*/ );
  56. ConVar tf_raid_special_spawn_min_interval( "tf_raid_special_spawn_min_interval", "20"/*, FCVAR_CHEAT*/ );
  57. ConVar tf_raid_special_spawn_max_interval( "tf_raid_special_spawn_max_interval", "30"/*, FCVAR_CHEAT*/ );
  58. ConVar tf_raid_spawn_specials( "tf_raid_spawn_specials", "0"/*, FCVAR_CHEAT*/ );
  59. ConVar tf_raid_sniper_spawn_ahead_incursion( "tf_raid_sniper_spawn_ahead_incursion", "6000"/*, FCVAR_CHEAT*/ );
  60. ConVar tf_raid_sniper_spawn_behind_incursion( "tf_raid_sniper_spawn_behind_incursion", "6000"/*, FCVAR_CHEAT*/ );
  61. ConVar tf_raid_sniper_spawn_max_range( "tf_raid_sniper_spawn_max_range", "6000"/*, FCVAR_CHEAT*/ );
  62. ConVar tf_raid_sniper_spawn_min_range( "tf_raid_sniper_spawn_min_range", "1000"/*, FCVAR_CHEAT*/ );
  63. ConVar tf_raid_show_escape_route( "tf_raid_show_escape_route", "0"/*, FCVAR_CHEAT*/ );
  64. ConVar tf_raid_sentry_build_ahead_incursion( "tf_raid_sentry_build_ahead_incursion", "5000"/*, FCVAR_CHEAT*/ );
  65. ConVar tf_raid_sentry_build_behind_incursion( "tf_raid_sentry_build_behind_incursion", "-1000"/*, FCVAR_CHEAT*/ );
  66. ConVar tf_raid_debug( "tf_raid_debug", "0"/*, FCVAR_CHEAT*/ );
  67. ConVar tf_raid_debug_escape_route( "tf_raid_debug_escape_route", "0"/*, FCVAR_CHEAT*/ );
  68. ConVar tf_raid_debug_director( "tf_raid_debug_director", "0"/*, FCVAR_CHEAT*/ );
  69. ConVar tf_raid_spawn_enable( "tf_raid_spawn_enable", "1"/*, FCVAR_CHEAT*/ );
  70. extern ConVar tf_populator_active_buffer_range;
  71. extern bool IsSpaceToSpawnHere( const Vector &where );
  72. //--------------------------------------------------------------------------------------------------------
  73. // Check actual line of sight to team
  74. bool IsPlayerVisibleToTeam( CTFPlayer *subject, int teamIndex )
  75. {
  76. CTeam *team = GetGlobalTeam( teamIndex );
  77. for( int t=0; t<team->GetNumPlayers(); ++t )
  78. {
  79. CTFPlayer *teamMember = (CTFPlayer *)team->GetPlayer(t);
  80. if ( !teamMember->IsAlive() )
  81. continue;
  82. CTFBot *bot = ToTFBot( teamMember );
  83. if ( bot && bot->HasAttribute( CTFBot::IS_NPC ) )
  84. continue;
  85. if ( teamMember->IsInFieldOfView( subject->EyePosition() ) )
  86. {
  87. if ( teamMember->IsLineOfSightClear( subject, CBaseCombatCharacter::IGNORE_ACTORS ) )
  88. {
  89. // visible to team
  90. return true;
  91. }
  92. }
  93. }
  94. return false;
  95. }
  96. //--------------------------------------------------------------------------------------------------------------
  97. int GetAvailableRedSpawnSlots( void )
  98. {
  99. int available = 0;
  100. // count dead bots we can re-use
  101. CTeam *deadTeam = GetGlobalTeam( TEAM_SPECTATOR );
  102. for( int i=0; i<deadTeam->GetNumPlayers(); ++i )
  103. {
  104. if ( !deadTeam->GetPlayer(i)->IsBot() )
  105. continue;
  106. // reuse this guy
  107. ++available;
  108. }
  109. // count unused player slots
  110. int totalPlayerCount = 0;
  111. totalPlayerCount += GetGlobalTeam( TEAM_SPECTATOR )->GetNumPlayers();
  112. totalPlayerCount += 4; // always leave room for 4 blue players
  113. totalPlayerCount += GetGlobalTeam( TF_TEAM_RED )->GetNumPlayers();
  114. available += gpGlobals->maxClients - totalPlayerCount;
  115. return available;
  116. }
  117. //-------------------------------------------------------------------------
  118. //-------------------------------------------------------------------------
  119. BEGIN_DATADESC( CRaidLogic )
  120. DEFINE_THINKFUNC( Update ),
  121. END_DATADESC()
  122. LINK_ENTITY_TO_CLASS( tf_logic_raid, CRaidLogic );
  123. //-------------------------------------------------------------------------
  124. CRaidLogic::CRaidLogic()
  125. {
  126. ListenForGameEvent( "teamplay_point_captured" );
  127. ListenForGameEvent( "teamplay_round_win" );
  128. ListenForGameEvent( "teamplay_round_start" );
  129. m_didFailLastTime = false;
  130. }
  131. //-------------------------------------------------------------------------
  132. CRaidLogic::~CRaidLogic()
  133. {
  134. g_pRaidLogic = NULL;
  135. }
  136. //-------------------------------------------------------------------------
  137. void CRaidLogic::Spawn( void )
  138. {
  139. BaseClass::Spawn();
  140. Reset();
  141. SetThink( &CRaidLogic::Update );
  142. SetNextThink( gpGlobals->curtime );
  143. m_didFailLastTime = false;
  144. g_pRaidLogic = this;
  145. m_miniBossIndex = 0;
  146. }
  147. //-------------------------------------------------------------------------
  148. void CRaidLogic::Reset( void )
  149. {
  150. m_isWaitingForRaidersToLeaveSpawnRoom = true;
  151. m_wasCapturingPoint = false;
  152. m_mobSpawnTimer.Invalidate();
  153. m_mobLifetimeTimer.Invalidate();
  154. m_specialSpawnTimer.Invalidate();
  155. m_mobCountRemaining = 0;
  156. m_mobArea = NULL;
  157. m_mobClass = TF_CLASS_SCOUT;
  158. m_priorRaiderAliveCount = -1;
  159. m_farthestAlongRaider = NULL;
  160. m_farthestAlongEscapeRouteArea = NULL;
  161. m_incursionDistanceAtEnd = -1.0f;
  162. m_wandererCount = 0;
  163. m_engineerCount = 0;
  164. m_demomanCount = 0;
  165. m_heavyCount = 0;
  166. m_soldierCount = 0;
  167. m_pyroCount = 0;
  168. m_spyCount = 0;
  169. m_sniperCount = 0;
  170. m_squadCount = 0;
  171. m_miniBossIndex = 0;
  172. }
  173. #if 0
  174. //--------------------------------------------------------------------------------------------------------
  175. bool SpawnWanderer( const Vector &spot )
  176. {
  177. if ( !tf_raid_spawn_wanderers.GetBool() )
  178. return false;
  179. return SpawnRedTFBot( TF_CLASS_SCOUT, spot ) ? true : false;
  180. /*
  181. CBaseCombatCharacter *minion = static_cast< CBaseCombatCharacter * >( CreateEntityByName( "bot_npc_minion" ) );
  182. if ( minion )
  183. {
  184. minion->SetAbsOrigin( spot );
  185. minion->SetOwnerEntity( NULL );
  186. DispatchSpawn( minion );
  187. return true;
  188. }
  189. return false;
  190. */
  191. }
  192. #endif // 0
  193. //-------------------------------------------------------------------------
  194. void CRaidLogic::OnRoundStart( void )
  195. {
  196. if ( !TFGameRules() || !TFGameRules()->IsRaidMode() )
  197. return;
  198. Reset();
  199. // unspawn entire red team
  200. CTeam *defendingTeam = GetGlobalTeam( TF_TEAM_RED );
  201. int i;
  202. for( i=0; i<defendingTeam->GetNumPlayers(); ++i )
  203. {
  204. engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", defendingTeam->GetPlayer(i)->GetUserID() ) );
  205. }
  206. // remove all minions
  207. CBaseEntity *minion = NULL;
  208. while( ( minion = gEntList.FindEntityByClassname( minion, "bot_npc_minion" ) ) != NULL )
  209. {
  210. UTIL_Remove( minion );
  211. }
  212. // kick last round's NPCs
  213. CTeam *raidingTeam = GetGlobalTeam( TF_TEAM_BLUE );
  214. for( i=0; i<raidingTeam->GetNumPlayers(); ++i )
  215. {
  216. CTFBot *bot = ToTFBot( raidingTeam->GetPlayer(i) );
  217. if ( bot && bot->HasAttribute( CTFBot::IS_NPC ) )
  218. {
  219. engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", raidingTeam->GetPlayer(i)->GetUserID() ) );
  220. }
  221. }
  222. BuildEscapeRoute();
  223. // collect special areas
  224. m_sniperSpotVector.RemoveAll();
  225. m_sentrySpotVector.RemoveAll();
  226. m_rescueClosetVector.RemoveAll();
  227. m_miniBossHomeVector.RemoveAll();
  228. m_miniBossIndex = 0;
  229. float availableSentrySpotArea = 0.0f;
  230. for( i=0; i<TheNavAreas.Count(); ++i )
  231. {
  232. CTFNavArea *area = (CTFNavArea *)TheNavAreas[i];
  233. if ( area->HasAttributeTF( TF_NAV_SNIPER_SPOT ) )
  234. m_sniperSpotVector.AddToTail( area );
  235. if ( area->HasAttributeTF( TF_NAV_SENTRY_SPOT ) )
  236. {
  237. m_sentrySpotVector.AddToTail( area );
  238. availableSentrySpotArea += area->GetSizeX() * area->GetSizeY();
  239. }
  240. if ( area->HasAttributeTF( TF_NAV_RESCUE_CLOSET ) )
  241. m_rescueClosetVector.AddToTail( area );
  242. if ( area->HasAttributeTF( TF_NAV_RED_SETUP_GATE ) )
  243. m_miniBossHomeVector.AddToTail( area );
  244. }
  245. // compute total geometric area of entire nav mesh, and clear all wander counts
  246. float totalSpace = 0.0f;
  247. for( i=0; i<TheNavAreas.Count(); ++i )
  248. {
  249. CTFNavArea *area = (CTFNavArea *)TheNavAreas[ i ];
  250. totalSpace += area->GetSizeX() * area->GetSizeY();
  251. area->SetWanderCount( 0 );
  252. }
  253. #if 0
  254. //----------------------------------------------
  255. // fill the world with wandering defenders
  256. int totalPopulation = (int)( tf_raid_wandering_density.GetFloat() * totalSpace + 0.5f );
  257. CUtlVector< CNavArea * > minionAreaVector;
  258. SelectSeparatedShuffleSet< CNavArea >( totalPopulation, tf_raid_sentry_spacing.GetFloat(), TheNavAreas, &minionAreaVector );
  259. for( int i=0; i<minionAreaVector.Count(); ++i )
  260. {
  261. static_cast< CTFNavArea * >( minionAreaVector[i] )->AddToWanderCount( 1 );
  262. // SpawnWanderer( minionAreaVector[i]->GetRandomPoint() );
  263. }
  264. DevMsg( "RAID: Total minion population = %d\n", minionAreaVector.Count() );
  265. //----------------------------------------------
  266. // determine where sentry guns will be
  267. // the total sentry population is based on the total actual space, not just sentry areas
  268. totalPopulation = (int)( tf_raid_sentry_density.GetFloat() * totalSpace + 0.5f );
  269. SelectSeparatedShuffleSet< CTFNavArea >( totalPopulation, tf_raid_sentry_spacing.GetFloat(), m_sentrySpotVector, &m_actualSentrySpotVector );
  270. for( int i=0; i<m_actualSentrySpotVector.Count(); ++i )
  271. {
  272. SpawnSentry( m_actualSentrySpotVector[i]->GetCenter() );
  273. }
  274. DevMsg( "RAID: Total sentry population = %d\n", m_actualSentrySpotVector.Count() );
  275. //----------------------------------------------
  276. // fill the world with defending bots
  277. if ( tf_raid_spawn_defenders.GetBool() )
  278. {
  279. const int classRosterCount = 5;
  280. int classRoster[ classRosterCount ] = { TF_CLASS_SNIPER, TF_CLASS_DEMOMAN, TF_CLASS_SNIPER, TF_CLASS_DEMOMAN, TF_CLASS_PYRO };
  281. CUtlVector< CTFNavArea * > validSpawnAreaVector;
  282. for( int i=0; i<TheNavAreas.Count(); ++i )
  283. {
  284. CTFNavArea *area = (CTFNavArea *)TheNavAreas[i];
  285. if ( area->IsValidForWanderingPopulation() && IsSpaceToSpawnHere( area->GetCenter() ) )
  286. {
  287. validSpawnAreaVector.AddToTail( area );
  288. }
  289. }
  290. totalPopulation = (int)( tf_raid_defender_density.GetFloat() * totalSpace + 0.5f );
  291. totalPopulation = clamp( totalPopulation, 0, tf_raid_max_defender_count.GetInt() );
  292. CUtlVector< CTFNavArea * > defenderAreaVector;
  293. SelectSeparatedShuffleSet< CTFNavArea >( totalPopulation, tf_raid_sentry_spacing.GetFloat(), validSpawnAreaVector, &defenderAreaVector );
  294. for( int i=0; i<defenderAreaVector.Count(); ++i )
  295. {
  296. CTFNavArea *homeArea = defenderAreaVector[i];
  297. CTFBot *bot = SpawnRedTFBot( classRoster[ i % classRosterCount ], homeArea->GetCenter() + Vector( 0, 0, 10.0f ) );
  298. if ( bot )
  299. {
  300. bot->SetHomeArea( homeArea );
  301. }
  302. else
  303. {
  304. DevMsg( "RAID: Failed to spawn defender!\n" );
  305. }
  306. }
  307. DevMsg( "RAID: Total defender population = %d\n", defenderAreaVector.Count() );
  308. }
  309. #endif
  310. // collect all capture point gates
  311. m_gateVector.RemoveAll();
  312. CBaseEntity *entity = NULL;
  313. while( ( entity = gEntList.FindEntityByClassname( entity, "func_door*" ) ) != NULL )
  314. {
  315. CBaseDoor *door = (CBaseDoor *)entity;
  316. if ( door->GetEntityName() != NULL_STRING && V_stristr( STRING( door->GetEntityName() ), "raid" ) )
  317. {
  318. m_gateVector.AddToTail( door );
  319. }
  320. }
  321. }
  322. //--------------------------------------------------------------------------------------------------------
  323. void CRaidLogic::FireGameEvent( IGameEvent *event )
  324. {
  325. const char *eventName = event->GetName();
  326. if ( !Q_strcmp( eventName, "teamplay_point_captured" ) )
  327. {
  328. // they just capped - give them a break and reset the mob spawner
  329. StartMobTimer( RandomFloat( tf_raid_mob_spawn_min_interval.GetFloat(), tf_raid_mob_spawn_max_interval.GetFloat() ) );
  330. DevMsg( "RAID: %3.2f: Reset Mob timer after successful point capture\n", gpGlobals->curtime );
  331. }
  332. else if ( !Q_strcmp( eventName, "teamplay_round_win" ) )
  333. {
  334. if ( event->GetInt( "team" ) == TF_TEAM_RED )
  335. {
  336. // the raiders didn't make it
  337. m_didFailLastTime = true;
  338. }
  339. }
  340. else if ( !Q_strcmp( eventName, "teamplay_round_start" ) )
  341. {
  342. OnRoundStart();
  343. }
  344. }
  345. //--------------------------------------------------------------------------------------------------------
  346. int CompareIncursionDistances( CTFNavArea * const *area1, CTFNavArea * const *area2 )
  347. {
  348. float d1 = (*area1)->GetIncursionDistance( TF_TEAM_BLUE );
  349. float d2 = (*area2)->GetIncursionDistance( TF_TEAM_BLUE );
  350. if ( d1 < d2 )
  351. return -1;
  352. if ( d1 > d2 )
  353. return 1;
  354. return 0;
  355. }
  356. #if 0
  357. //--------------------------------------------------------------------------------------------------------
  358. class CPopulator : public ISearchSurroundingAreasFunctor
  359. {
  360. public:
  361. CPopulator( float leaderIncursionRange, int maxWanderersToSpawn )
  362. {
  363. m_leaderIncursionRange = leaderIncursionRange;
  364. m_spaceLeft = maxWanderersToSpawn;
  365. m_floor = FLT_MAX;
  366. CTeam *invaderTeam = GetGlobalTeam( TF_TEAM_BLUE );
  367. for( int i=0; i<invaderTeam->GetNumPlayers(); ++i )
  368. {
  369. if ( !invaderTeam->GetPlayer(i)->IsAlive() )
  370. continue;
  371. if ( invaderTeam->GetPlayer(i)->GetAbsOrigin().z < m_floor )
  372. {
  373. m_floor = invaderTeam->GetPlayer(i)->GetAbsOrigin().z;
  374. }
  375. }
  376. m_floor -= tf_raid_mob_spawn_below_tolerance.GetFloat();
  377. }
  378. virtual bool operator() ( CNavArea *baseArea, CNavArea *priorArea, float travelDistanceSoFar )
  379. {
  380. CTFNavArea *area = (CTFNavArea *)baseArea;
  381. if ( area->IsBlocked( TF_TEAM_RED ) )
  382. return true;
  383. if ( area->HasAttributeTF( TF_NAV_NO_SPAWNING | TF_NAV_SPAWN_ROOM_BLUE ) )
  384. return true;
  385. if ( tf_raid_debug.GetInt() > 1 )
  386. {
  387. if ( area->IsPotentiallyVisibleToTeam( TF_TEAM_BLUE ) )
  388. area->DrawFilled( 255, 100, 0, 100, 1.0f );
  389. else
  390. area->DrawFilled( 0, 100, 255, 100, 1.0f );
  391. }
  392. // require minimum size
  393. if ( area->GetSizeX() < 45.0f && area->GetSizeY() < 45.0f )
  394. return true;
  395. // don't use areas far below team
  396. // if ( area->GetCenter().z < m_floor )
  397. // return true;
  398. if ( area->IsPotentiallyVisibleToTeam( TF_TEAM_BLUE ) )
  399. {
  400. // don't spawn wanderers in view of raiders
  401. // clear any unspawned wanderers here
  402. area->SetWanderCount( 0 );
  403. return true;
  404. }
  405. // collect out-of-sight areas
  406. m_hiddenAreaVector.AddToTail( area );
  407. // collect out-of-sight areas ahead of the team for special spawns
  408. const float aheadBuffer = 500.0f;
  409. if ( area->GetIncursionDistance( TF_TEAM_BLUE ) > m_leaderIncursionRange + aheadBuffer )
  410. {
  411. m_hiddenAreaAheadVector.AddToTail( area );
  412. }
  413. if ( m_spaceLeft <= 0 )
  414. return true;
  415. if ( !tf_raid_spawn_wanderers.GetBool() )
  416. {
  417. return true;
  418. }
  419. if ( TFGameRules()->GetRaidLogic()->IsMobSpawning() )
  420. {
  421. // don't spawn wanderers if a mob is spawning to keep slots free
  422. return true;
  423. }
  424. if ( !area->IsValidForWanderingPopulation() )
  425. {
  426. return true;
  427. }
  428. int maxSpawnCount = 5;
  429. while( area->GetWanderCount() > 0 && --maxSpawnCount && m_spaceLeft )
  430. {
  431. // attempt to spawn a wanderer here
  432. if ( SpawnWanderer( area->GetRandomPoint() + Vector( 0, 0, StepHeight ) ) )
  433. {
  434. area->SetWanderCount( area->GetWanderCount() - 1 );
  435. --m_spaceLeft;
  436. }
  437. }
  438. return true;
  439. }
  440. // return true if 'adjArea' should be included in the ongoing search
  441. virtual bool ShouldSearch( CNavArea *adjArea, CNavArea *currentArea, float travelDistanceSoFar )
  442. {
  443. CTFNavArea *area = (CTFNavArea *)adjArea;
  444. float incursionDistance = area->GetIncursionDistance( TF_TEAM_BLUE );
  445. return incursionDistance > m_leaderIncursionRange - tf_populator_active_buffer_range.GetFloat() &&
  446. incursionDistance < m_leaderIncursionRange + tf_populator_active_buffer_range.GetFloat() &&
  447. !adjArea->IsBlocked( TEAM_ANY );
  448. }
  449. virtual void PostSearch( void )
  450. {
  451. // collect the highest hidden & ahead areas
  452. float minZ = 999999.9f, maxZ = -999999.9f;
  453. int i;
  454. for( i=0; i<m_hiddenAreaAheadVector.Count(); ++i )
  455. {
  456. CTFNavArea *area = m_hiddenAreaAheadVector[i];
  457. float areaZ = area->GetCenter().z;
  458. if ( areaZ < minZ )
  459. minZ = areaZ;
  460. if ( areaZ > maxZ )
  461. maxZ = areaZ;
  462. }
  463. float floorZ = minZ + 0.7f * ( maxZ - minZ );
  464. for( i=0; i<m_hiddenAreaAheadVector.Count(); ++i )
  465. {
  466. CTFNavArea *area = m_hiddenAreaAheadVector[i];
  467. float areaZ = area->GetCenter().z;
  468. if ( areaZ > floorZ )
  469. {
  470. m_hiddenAreaAheadHighVector.AddToTail( area );
  471. }
  472. }
  473. // sort hidden areas by incursion distance
  474. m_hiddenAreaVector.Sort( CompareIncursionDistances );
  475. }
  476. float m_leaderIncursionRange;
  477. float m_floor;
  478. int m_spaceLeft;
  479. CUtlVector< CTFNavArea * > m_hiddenAreaVector;
  480. CUtlVector< CTFNavArea * > m_hiddenAreaAheadVector;
  481. CUtlVector< CTFNavArea * > m_hiddenAreaAheadHighVector;
  482. };
  483. #endif // 0
  484. //--------------------------------------------------------------------------------------------------------
  485. bool CRaidLogic::Unspawn( CTFPlayer *who )
  486. {
  487. if ( who->IsAlive() && who->GetTeamNumber() == TF_TEAM_RED )
  488. {
  489. // only cull Engineers who are far behind the team
  490. if ( who->IsPlayerClass( TF_CLASS_ENGINEER ) )
  491. {
  492. CTFNavArea *area = (CTFNavArea *)who->GetLastKnownArea();
  493. if ( area && area->GetIncursionDistance( TF_TEAM_BLUE ) > GetMaximumRaiderIncursionDistance() - tf_populator_active_buffer_range.GetFloat() )
  494. return false;
  495. }
  496. else if ( !who->IsPlayerClass( TF_CLASS_SCOUT ) )
  497. {
  498. // do not unspawn these classes - they lurk at far distances
  499. return false;
  500. }
  501. }
  502. // check actual line of sight to team
  503. if ( IsPlayerVisibleToTeam( who, TF_TEAM_BLUE ) )
  504. return false;
  505. who->ChangeTeam( TEAM_SPECTATOR, false, true );
  506. // destroy all buildings (for relocated engineers)
  507. who->RemoveAllObjects();
  508. return true;
  509. }
  510. //--------------------------------------------------------------------------------------------------------
  511. CTFNavArea *CRaidLogic::FindSpawnAreaAhead( void )
  512. {
  513. CTFPlayer *leader = GetFarthestAlongRaider();
  514. if ( leader == NULL || m_escapeRouteVector.Count() == 0 )
  515. return NULL;
  516. const float minTravel = 1000.0f;
  517. float minIncursion = GetMaximumRaiderIncursionDistance() + minTravel;
  518. // find first non-visible area ahead of leader along escape path beyond a minimum distance
  519. for( int i=0; i<m_escapeRouteVector.Count(); ++i )
  520. {
  521. CTFNavArea *area = m_escapeRouteVector[i];
  522. if ( area->IsBlocked( TF_TEAM_RED ) )
  523. return NULL;
  524. if ( area->HasAttributeTF( TF_NAV_NO_SPAWNING ) )
  525. continue;
  526. if ( area->GetIncursionDistance( TF_TEAM_BLUE ) < minIncursion )
  527. continue;
  528. if ( area->IsPotentiallyVisibleToTeam( TF_TEAM_BLUE ) )
  529. continue;
  530. if ( IsSpaceToSpawnHere( area->GetCenter() ) )
  531. {
  532. // found valid squad spawn
  533. return area;
  534. }
  535. }
  536. return NULL;
  537. }
  538. //--------------------------------------------------------------------------------------------------------
  539. CTFNavArea *CRaidLogic::FindSpawnAreaBehind( void )
  540. {
  541. CTFPlayer *leader = GetFarthestAlongRaider();
  542. if ( leader == NULL || m_escapeRouteVector.Count() == 0 )
  543. return NULL;
  544. const float minTravel = 1000.0f;
  545. float maxIncursion = GetMaximumRaiderIncursionDistance() - minTravel;
  546. // find first non-visible area behind leader along escape path beyond a minimum distance
  547. for( int i=m_escapeRouteVector.Count()-1; i >= 0; --i )
  548. {
  549. CTFNavArea *area = m_escapeRouteVector[i];
  550. if ( area->HasAttributeTF( TF_NAV_NO_SPAWNING ) )
  551. continue;
  552. if ( area->GetIncursionDistance( TF_TEAM_BLUE ) > maxIncursion )
  553. continue;
  554. if ( area->IsPotentiallyVisibleToTeam( TF_TEAM_BLUE ) )
  555. continue;
  556. if ( IsSpaceToSpawnHere( area->GetCenter() ) )
  557. {
  558. // found valid squad spawn
  559. return area;
  560. }
  561. }
  562. return NULL;
  563. }
  564. #if 0
  565. //--------------------------------------------------------------------------------------------------------
  566. bool CRaidLogic::SpawnSquad( CTFNavArea *spawnArea )
  567. {
  568. if ( spawnArea == NULL )
  569. return false;
  570. int squadSize = 4;
  571. int freeSlots = GetAvailableRedSpawnSlots();
  572. if ( freeSlots < squadSize )
  573. {
  574. DevMsg( "RAID: %3.2f: *** Not enough free slots to spawn a squad\n", gpGlobals->curtime );
  575. return false;
  576. }
  577. const int squadClassMaxCount = 4;
  578. int squadClasses[ squadClassMaxCount ];
  579. int squadClassCount = 0;
  580. squadClasses[ squadClassCount++ ] = TF_CLASS_HEAVYWEAPONS;
  581. squadClasses[ squadClassCount++ ] = TF_CLASS_SOLDIER;
  582. squadClasses[ squadClassCount++ ] = TF_CLASS_PYRO;
  583. squadClasses[ squadClassCount++ ] = TF_CLASS_DEMOMAN;
  584. // randomly shuffle the class order
  585. int n = squadClassCount;
  586. while( n > 1 )
  587. {
  588. int k = RandomInt( 0, n-1 );
  589. n--;
  590. int tmp = squadClasses[n];
  591. squadClasses[n] = squadClasses[k];
  592. squadClasses[k] = tmp;
  593. }
  594. if ( GetMaximumRaiderIncursionDistance() > tf_raid_squad_medic_intro_percent.GetFloat() * GetIncursionDistanceAtEnd() )
  595. {
  596. // Medics join squads farther into the raid
  597. squadClasses[0] = TF_CLASS_MEDIC;
  598. }
  599. CTFBotSquad *squad = new CTFBotSquad;
  600. CTFBot *bot;
  601. DevMsg( "RAID: %3.2f: <<<< Spawning Squad >>>>\n", gpGlobals->curtime );
  602. for( int i=0; i<squadSize; ++i )
  603. {
  604. int which = squadClasses[ i ];
  605. bot = SpawnRedTFBot( which, spawnArea->GetCenter() );
  606. if ( !bot )
  607. return false;
  608. bot->JoinSquad( squad );
  609. DevMsg( "RAID: %3.2f: Squad member %s(%d)\n", gpGlobals->curtime, bot->GetPlayerName(), bot->entindex() );
  610. }
  611. IGameEvent* event = gameeventmanager->CreateEvent( "raid_spawn_squad" );
  612. if ( event )
  613. {
  614. gameeventmanager->FireEvent( event );
  615. }
  616. return true;
  617. }
  618. #endif // 0
  619. //--------------------------------------------------------------------------------------------------------
  620. void CRaidLogic::StartMobTimer( float duration )
  621. {
  622. if ( IsMobSpawning() )
  623. {
  624. DevMsg( "RAID: %3.2f: Skipping mob spawn because an existing mob is still in progress\n", gpGlobals->curtime );
  625. return;
  626. }
  627. m_mobSpawnTimer.Start( duration );
  628. m_mobLifetimeTimer.Invalidate();
  629. m_mobCountRemaining = 0;
  630. m_mobArea = NULL;
  631. }
  632. #if 0
  633. //--------------------------------------------------------------------------------------------------------
  634. CTFNavArea *CRaidLogic::SelectMobSpawn( CUtlVector< CTFNavArea * > *spawnAreaVector, RelativePositionType where )
  635. {
  636. if ( spawnAreaVector->Count() == 0 )
  637. {
  638. // use high-reliability locations
  639. CTFNavArea *spawnArea = FindSpawnAreaBehind();
  640. if ( spawnArea )
  641. return spawnArea;
  642. return NULL; // FindSpawnAreaAhead();
  643. }
  644. const int maxRetries = 5;
  645. CTFNavArea *spawnArea = NULL;
  646. CTFPlayer *farRaider = GetFarthestAlongRaider();
  647. if ( !farRaider )
  648. return NULL;
  649. for( int r=0; r<maxRetries; ++r )
  650. {
  651. int which = 0;
  652. switch( where )
  653. {
  654. case AHEAD:
  655. // areas are sorted from behind to ahead - weight the selection to choose ahead
  656. which = SkewedRandomValue() * spawnAreaVector->Count();
  657. break;
  658. case BEHIND:
  659. // areas are sorted from behind to ahead - weight the selection to choose behind
  660. which = ( 1.0f - SkewedRandomValue() ) * spawnAreaVector->Count();
  661. break;
  662. case ANYWHERE:
  663. // areas are sorted from behind to ahead - weight the selection to choose ahead
  664. which = RandomFloat( 0.0f, 1.0f ) * spawnAreaVector->Count();
  665. break;
  666. }
  667. if ( which == spawnAreaVector->Count() )
  668. --which;
  669. spawnArea = spawnAreaVector->Element( which );
  670. if ( ( farRaider->GetAbsOrigin() - spawnArea->GetCenter() ).IsLengthGreaterThan( tf_raid_mob_spawn_min_range.GetFloat() ) )
  671. {
  672. // well behaved spawn area
  673. return spawnArea;
  674. }
  675. }
  676. // return whatever we've found so far
  677. return spawnArea;
  678. }
  679. //--------------------------------------------------------------------------------------------------------
  680. void CRaidLogic::SpawnMobs( CUtlVector< CTFNavArea * > *spawnAreaVector )
  681. {
  682. if ( !tf_raid_spawn_mobs.GetBool() )
  683. return;
  684. if ( m_mobLifetimeTimer.HasStarted() )
  685. {
  686. if ( m_mobLifetimeTimer.IsElapsed() )
  687. {
  688. // time is up for this mob to spawn, clear the rest
  689. DevMsg( "RAID: %3.2f: Mob spawn lifetime is up. Mob count remaining unspawned: %d\n", gpGlobals->curtime, m_mobCountRemaining );
  690. m_mobLifetimeTimer.Invalidate();
  691. m_mobCountRemaining = 0;
  692. m_mobArea = NULL;
  693. // start next mob
  694. m_mobSpawnTimer.Start( RandomFloat( tf_raid_mob_spawn_min_interval.GetFloat(), tf_raid_mob_spawn_max_interval.GetFloat() ) );
  695. }
  696. // can't create another mob until this mob has expired
  697. return;
  698. }
  699. if ( m_mobSpawnTimer.HasStarted() && m_mobSpawnTimer.IsElapsed() && spawnAreaVector && spawnAreaVector->Count() > 0 )
  700. {
  701. // chance of mob changes as we progress through the map
  702. int mobChance;
  703. float progressRatio = GetMaximumRaiderIncursionDistance() / GetIncursionDistanceAtEnd();
  704. if ( progressRatio < 0.5f )
  705. {
  706. mobChance = tf_raid_spawn_mob_as_squad_chance_start.GetInt() + 2.0f * progressRatio * ( tf_raid_spawn_mob_as_squad_chance_halfway.GetInt() - tf_raid_spawn_mob_as_squad_chance_start.GetInt() );
  707. }
  708. else
  709. {
  710. mobChance = tf_raid_spawn_mob_as_squad_chance_halfway.GetInt() + 2.0f * ( progressRatio - 0.5f ) * ( tf_raid_spawn_mob_as_squad_chance_final.GetInt() - tf_raid_spawn_mob_as_squad_chance_halfway.GetInt() );
  711. }
  712. if ( RandomInt( 1, 100 ) <= mobChance )
  713. {
  714. // this mob is a squad
  715. CTFNavArea *spawnArea = SelectMobSpawn( spawnAreaVector, BEHIND ); // m_wasCapturingPoint ? ANYWHERE : BEHIND );
  716. if ( SpawnSquad( spawnArea ) )
  717. {
  718. StartMobTimer( RandomFloat( tf_raid_mob_spawn_min_interval.GetFloat(), tf_raid_mob_spawn_max_interval.GetFloat() ) );
  719. }
  720. else
  721. {
  722. // couldn't spawn - try again soon
  723. StartMobTimer( 1.0f );
  724. DevMsg( "RAID: %3.2f: No place to spawn Squad!\n", gpGlobals->curtime );
  725. }
  726. return;
  727. }
  728. // time to throw in a mob rush
  729. m_mobArea = SelectMobSpawn( spawnAreaVector, m_wasCapturingPoint ? ANYWHERE : BEHIND );
  730. if ( m_mobArea )
  731. {
  732. const int mobClassCount = 4;
  733. static int mobClassList[ mobClassCount ] =
  734. {
  735. TF_CLASS_SCOUT,
  736. TF_CLASS_HEAVYWEAPONS,
  737. TF_CLASS_PYRO,
  738. TF_CLASS_SPY,
  739. };
  740. m_mobLifetimeTimer.Start( 7.0f );
  741. m_mobCountRemaining = tf_raid_mob_spawn_count.GetInt();
  742. m_mobClass = mobClassList[ RandomInt( 0, mobClassCount-1 ) ];
  743. DevMsg( "RAID: %3.2f: <<<< Creating mob! >>>>\n", gpGlobals->curtime );
  744. IGameEvent* event = gameeventmanager->CreateEvent( "raid_spawn_mob" );
  745. if ( event )
  746. {
  747. gameeventmanager->FireEvent( event );
  748. }
  749. }
  750. else
  751. {
  752. // couldn't spawn - try again soon
  753. StartMobTimer( 1.0f );
  754. DevMsg( "RAID: %3.2f: No place to spawn Mob!\n", gpGlobals->curtime );
  755. }
  756. }
  757. }
  758. #endif // 0
  759. //--------------------------------------------------------------------------------------------------------
  760. class CNearbyHiddenScan : public ISearchSurroundingAreasFunctor
  761. {
  762. public:
  763. CNearbyHiddenScan( void )
  764. {
  765. m_hiddenArea = NULL;
  766. }
  767. virtual bool operator() ( CNavArea *baseArea, CNavArea *priorArea, float travelDistanceSoFar )
  768. {
  769. CTFNavArea *area = (CTFNavArea *)baseArea;
  770. if ( area->GetIncursionDistance( TF_TEAM_BLUE ) < TFGameRules()->GetRaidLogic()->GetMaximumRaiderIncursionDistance() )
  771. {
  772. // defenders can't spawn in region under raider's control
  773. return true;
  774. }
  775. if ( !area->IsPotentiallyVisibleToTeam( TF_TEAM_BLUE ) && area->IsValidForWanderingPopulation() )
  776. {
  777. // found a hidden spot
  778. m_hiddenArea = area;
  779. return false;
  780. }
  781. return true;
  782. }
  783. CTFNavArea *m_hiddenArea;
  784. };
  785. //--------------------------------------------------------------------------------------------------------
  786. //
  787. // Choose unpopulated sentry area nearest the invaders
  788. //
  789. CTFNavArea *CRaidLogic::SelectRaidSentryArea( void ) const
  790. {
  791. CTFNavArea *nearestSentryArea = NULL;
  792. float nearestSentryAreaIncDist = FLT_MAX;
  793. float invaderIncDist = GetMaximumRaiderIncursionDistance();
  794. float aheadLimit = invaderIncDist + tf_raid_sentry_build_ahead_incursion.GetFloat();
  795. float behindLimit = invaderIncDist - tf_raid_sentry_build_behind_incursion.GetFloat();
  796. // collect a vector of alive enemies
  797. CUtlVector< CTFPlayer * > enemyVector;
  798. CollectPlayers( &enemyVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
  799. // check for unpopulated sentry areas in the active area set
  800. for( int i=0; i<m_actualSentrySpotVector.Count(); ++i )
  801. {
  802. CTFNavArea *sentryArea = m_actualSentrySpotVector[i];
  803. // is this area in play?
  804. if ( sentryArea->GetIncursionDistance( TF_TEAM_BLUE ) > aheadLimit )
  805. continue;
  806. if ( sentryArea->GetIncursionDistance( TF_TEAM_BLUE ) < behindLimit )
  807. continue;
  808. // is this area 'owned' by another, active, engineer?
  809. int e;
  810. for( e=0; e<enemyVector.Count(); ++e )
  811. {
  812. if ( !enemyVector[e]->IsBot() )
  813. continue;
  814. if ( !enemyVector[e]->IsPlayerClass( TF_CLASS_ENGINEER ) )
  815. continue;
  816. CTFBot *engineer = (CTFBot *)enemyVector[e];
  817. if ( engineer->GetHomeArea() && engineer->GetHomeArea()->GetID() == sentryArea->GetID() )
  818. {
  819. break;
  820. }
  821. }
  822. if ( e < enemyVector.Count() )
  823. {
  824. // another engineer is using this area
  825. continue;
  826. }
  827. // this is an unreserved sentry area in the active area set, keep the nearest one that is ahead of the team
  828. float incDist = sentryArea->GetIncursionDistance( TF_TEAM_BLUE );
  829. if ( incDist < nearestSentryAreaIncDist && incDist >= behindLimit )
  830. {
  831. nearestSentryArea = sentryArea;
  832. nearestSentryAreaIncDist = incDist;
  833. }
  834. }
  835. if ( nearestSentryArea )
  836. {
  837. // find a nearby non-visible spawn spot for the engineer to enter from
  838. CNearbyHiddenScan hide;
  839. const float hideRange = 1000.0f;
  840. SearchSurroundingAreas( nearestSentryArea, hide, hideRange );
  841. if ( !hide.m_hiddenArea )
  842. {
  843. DevMsg( "RAID: %3.2f: Can't find hidden area to spawn in engineer", gpGlobals->curtime );
  844. return NULL;
  845. }
  846. // actual spawn-in, hidden area is this area's parent
  847. nearestSentryArea->SetParent( hide.m_hiddenArea );
  848. return nearestSentryArea;
  849. }
  850. return NULL;
  851. }
  852. #if 0
  853. //--------------------------------------------------------------------------------------------------------
  854. void CRaidLogic::SpawnEngineers( void )
  855. {
  856. if ( !tf_raid_spawn_engineers.GetBool() || !m_engineerSpawnTimer.IsElapsed() )
  857. return;
  858. m_engineerSpawnTimer.Start( tf_raid_engineer_spawn_interval.GetFloat() );
  859. int engineerCount = m_engineerCount;
  860. while( engineerCount < tf_raid_max_defense_engineers.GetInt() )
  861. {
  862. // parent of sentry area is hidden area where engineer spawns
  863. CTFNavArea *sentryArea = SelectRaidSentryArea();
  864. if ( !sentryArea )
  865. {
  866. // no available areas
  867. break;
  868. }
  869. const int maxTries = 10;
  870. int tryCount;
  871. for( tryCount=0; tryCount<maxTries; ++tryCount )
  872. {
  873. CTFBot *bot = SpawnRedTFBot( TF_CLASS_ENGINEER, sentryArea->GetParent()->GetCenter() + Vector( 0, 0, RandomFloat( 0.0f, 30.0f ) ) );
  874. if ( bot )
  875. {
  876. // engineer bot will move to the sentry area and build
  877. bot->SetHomeArea( sentryArea );
  878. ++engineerCount;
  879. DevMsg( "RAID: %3.2f: Spawned engineer", gpGlobals->curtime );
  880. break;
  881. }
  882. }
  883. if ( tryCount == maxTries )
  884. {
  885. DevMsg( "RAID: %3.2f: Can't spawn engineer", gpGlobals->curtime );
  886. break;
  887. }
  888. }
  889. }
  890. //--------------------------------------------------------------------------------------------------------
  891. void CRaidLogic::SpawnSpecials( CUtlVector< CTFNavArea * > *spawnAheadVector, CUtlVector< CTFNavArea * > *spawnAnywhereVector )
  892. {
  893. // spawn specials
  894. if ( tf_raid_spawn_specials.GetBool() && m_specialSpawnTimer.HasStarted() && m_specialSpawnTimer.IsElapsed() )
  895. {
  896. // time to add in a "special"
  897. m_specialSpawnTimer.Start( RandomFloat( tf_raid_special_spawn_min_interval.GetFloat(), tf_raid_special_spawn_max_interval.GetFloat() ) );
  898. DevMsg( "RAID: %3.2f: <<<< Spawning Special >>>>\n", gpGlobals->curtime );
  899. const int specialClassCount = 8;
  900. int availableSpecialClassList[ specialClassCount ];
  901. int availableCount = 0;
  902. if ( m_sniperCount < tf_raid_max_defense_snipers.GetInt() )
  903. {
  904. // increased chance of a sniper
  905. availableSpecialClassList[ availableCount++ ] = TF_CLASS_SNIPER;
  906. availableSpecialClassList[ availableCount++ ] = TF_CLASS_SNIPER;
  907. }
  908. if ( m_demomanCount < tf_raid_max_defense_demomen.GetInt() )
  909. {
  910. availableSpecialClassList[ availableCount++ ] = TF_CLASS_DEMOMAN;
  911. }
  912. if ( m_heavyCount < tf_raid_max_defense_heavies.GetInt() )
  913. {
  914. availableSpecialClassList[ availableCount++ ] = TF_CLASS_HEAVYWEAPONS;
  915. }
  916. if ( m_soldierCount < tf_raid_max_defense_soldiers.GetInt() )
  917. {
  918. availableSpecialClassList[ availableCount++ ] = TF_CLASS_SOLDIER;
  919. }
  920. if ( m_pyroCount < tf_raid_max_defense_pyros.GetInt() )
  921. {
  922. availableSpecialClassList[ availableCount++ ] = TF_CLASS_PYRO;
  923. }
  924. if ( m_spyCount < tf_raid_max_defense_spies.GetInt() )
  925. {
  926. availableSpecialClassList[ availableCount++ ] = TF_CLASS_SPY;
  927. }
  928. if ( availableCount == 0 )
  929. {
  930. // nothing to spawn
  931. DevMsg( "RAID: %3.2f: All specials in play already.\n", gpGlobals->curtime );
  932. return;
  933. }
  934. int whichClass = availableSpecialClassList[ RandomInt( 0, availableCount-1 ) ];
  935. if ( whichClass == TF_CLASS_SNIPER )
  936. {
  937. CTFNavArea *homeArea = FindSniperSpawn();
  938. if ( homeArea )
  939. {
  940. // actual spawn-in, hidden area is this area's parent
  941. for( int tryCount=0; tryCount<10; ++tryCount )
  942. {
  943. CTFBot *bot = SpawnRedTFBot( whichClass, homeArea->GetParent()->GetCenter() + Vector( 0, 0, RandomFloat( 0.0f, 30.0f ) ) );
  944. if ( bot )
  945. {
  946. // Bot will move to his home area to do his business
  947. bot->SetHomeArea( homeArea );
  948. return;
  949. }
  950. }
  951. }
  952. }
  953. else if ( spawnAheadVector && spawnAheadVector->Count() > 0 )
  954. {
  955. CTFNavArea *where = spawnAheadVector->Element( RandomInt( 0, spawnAheadVector->Count()-1 ) );
  956. CTFBot *bot = SpawnRedTFBot( whichClass, where->GetCenter() + Vector( 0, 0, StepHeight ) );
  957. if ( bot )
  958. {
  959. bot->SetHomeArea( where );
  960. return;
  961. }
  962. }
  963. // failed to create special - try again soon
  964. m_specialSpawnTimer.Start( RandomFloat( 1.0f, 2.0f ) );
  965. DevMsg( "RAID: %3.2f: Failed to spawn Special.\n", gpGlobals->curtime );
  966. }
  967. }
  968. #endif // 0
  969. //--------------------------------------------------------------------------------------------------------
  970. void CRaidLogic::CullObsoleteEnemies( float minIncursion, float maxIncursion )
  971. {
  972. return;
  973. // cull wanderers outside of the active area set - use slightly larger range to avoid thrashing
  974. CTeam *defenseTeam = GetGlobalTeam( TF_TEAM_RED );
  975. float cullMinIncursionDistance = minIncursion - 1.1f * tf_populator_active_buffer_range.GetFloat();
  976. float cullMaxIncursionDistance = maxIncursion + 1.1f * tf_populator_active_buffer_range.GetFloat();
  977. for( int i=0; i<defenseTeam->GetNumPlayers(); ++i )
  978. {
  979. if ( !defenseTeam->GetPlayer(i)->IsBot() )
  980. continue;
  981. CTFBot *defender = (CTFBot *)defenseTeam->GetPlayer(i);
  982. if ( !defender->IsAlive() )
  983. continue;
  984. CTFNavArea *defenderArea = (CTFNavArea *)defender->GetLastKnownArea();
  985. if ( defenderArea == NULL )
  986. {
  987. // bad placement, underground most likely
  988. Unspawn( defender );
  989. }
  990. else if ( !defender->IsMoving() || defender->GetLocomotionInterface()->IsStuck() )
  991. {
  992. if ( defenderArea->GetIncursionDistance( TF_TEAM_BLUE ) < cullMinIncursionDistance ||
  993. defenderArea->GetIncursionDistance( TF_TEAM_BLUE ) > cullMaxIncursionDistance )
  994. {
  995. if ( Unspawn( defender ) )
  996. {
  997. if ( !defender->HasAttribute( CTFBot::AGGRESSIVE ) )
  998. {
  999. defenderArea->AddToWanderCount( 1 );
  1000. }
  1001. }
  1002. }
  1003. }
  1004. }
  1005. // aggressively cull wanderers if we need to spawn a mob and have no room
  1006. if ( IsMobSpawning() && GetAvailableRedSpawnSlots() <= 0 )
  1007. {
  1008. // try to make room by removing an unseen wanderer
  1009. for( int i=0; i<defenseTeam->GetNumPlayers(); ++i )
  1010. {
  1011. if ( !defenseTeam->GetPlayer(i)->IsBot() )
  1012. continue;
  1013. CTFBot *defender = (CTFBot *)defenseTeam->GetPlayer(i);
  1014. if ( !defender->IsAlive() )
  1015. continue;
  1016. // only cull Scouts
  1017. if ( !defender->IsPlayerClass( TF_CLASS_SCOUT ) )
  1018. continue;
  1019. // don't cull mob rushers
  1020. if ( defender->HasAttribute( CTFBot::AGGRESSIVE ) )
  1021. continue;
  1022. // try to open up a slot
  1023. if ( Unspawn( defender ) )
  1024. {
  1025. if ( defender->GetLastKnownArea() )
  1026. {
  1027. defender->GetLastKnownArea()->AddToWanderCount( 1 );
  1028. }
  1029. break;
  1030. }
  1031. }
  1032. }
  1033. }
  1034. //--------------------------------------------------------------------------------------------------------
  1035. class CShowEscapeRoute : public ISearchSurroundingAreasFunctor
  1036. {
  1037. public:
  1038. virtual bool operator() ( CNavArea *baseArea, CNavArea *priorArea, float travelDistanceSoFar )
  1039. {
  1040. CTFNavArea *area = (CTFNavArea *)baseArea;
  1041. if ( area->HasAttributeTF( TF_NAV_ESCAPE_ROUTE ) )
  1042. {
  1043. area->DrawFilled( 100, 255, 255, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, false );
  1044. }
  1045. else if ( area->HasAttributeTF( TF_NAV_ESCAPE_ROUTE_VISIBLE ) )
  1046. {
  1047. area->DrawFilled( 100, 200, 100, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, false );
  1048. }
  1049. return true;
  1050. }
  1051. virtual bool ShouldSearch( CNavArea *adjArea, CNavArea *currentArea, float travelDistanceSoFar )
  1052. {
  1053. return travelDistanceSoFar < 3000.0f;
  1054. }
  1055. };
  1056. //--------------------------------------------------------------------------------------------------------
  1057. void CRaidLogic::DrawDebugDisplay( float deltaT )
  1058. {
  1059. // avoid Warning() spam from UTIL_GetListenServerHost when on a dedicated server
  1060. if ( engine->IsDedicatedServer() )
  1061. return;
  1062. CBasePlayer *player = UTIL_GetListenServerHost();
  1063. if ( player == NULL )
  1064. return;
  1065. if ( tf_raid_debug_director.GetBool() )
  1066. {
  1067. NDebugOverlay::ScreenText( 0.01f, 0.5f, CFmtStr( "Mob timer: %3.2f", m_mobSpawnTimer.GetRemainingTime() ), 255, 255, 0, 255, 0.33f );
  1068. NDebugOverlay::ScreenText( 0.01f, 0.51f, CFmtStr( "Wanderers: %d", m_wandererCount ), 255, 255, 0, 255, 0.33f );
  1069. NDebugOverlay::ScreenText( 0.01f, 0.52f, CFmtStr( "Engineers: %d", m_engineerCount ), 255, 255, 0, 255, 0.33f );
  1070. NDebugOverlay::ScreenText( 0.01f, 0.53f, CFmtStr( "Demomen: %d", m_demomanCount ), 255, 255, 0, 255, 0.33f );
  1071. NDebugOverlay::ScreenText( 0.01f, 0.54f, CFmtStr( "Heavies: %d", m_heavyCount ), 255, 255, 0, 255, 0.33f );
  1072. NDebugOverlay::ScreenText( 0.01f, 0.55f, CFmtStr( "Soldiers: %d", m_soldierCount ), 255, 255, 0, 255, 0.33f );
  1073. NDebugOverlay::ScreenText( 0.01f, 0.56f, CFmtStr( "Pyros: %d", m_pyroCount ), 255, 255, 0, 255, 0.33f );
  1074. NDebugOverlay::ScreenText( 0.01f, 0.57f, CFmtStr( "Spies: %d", m_spyCount ), 255, 255, 0, 255, 0.33f );
  1075. NDebugOverlay::ScreenText( 0.01f, 0.58f, CFmtStr( "Snipers: %d", m_sniperCount ), 255, 255, 0, 255, 0.33f );
  1076. NDebugOverlay::ScreenText( 0.01f, 0.59f, CFmtStr( "Squads: %d", m_squadCount ), 255, 255, 0, 255, 0.33f );
  1077. NDebugOverlay::ScreenText( 0.01f, 0.60f, CFmtStr( "Raider max inc range: %3.2f", GetMaximumRaiderIncursionDistance() ), 255, 255, 0, 255, 0.33f );
  1078. }
  1079. if ( tf_raid_show_escape_route.GetInt() == 2 )
  1080. {
  1081. if ( m_escapeRouteVector.Count() >= 2 )
  1082. {
  1083. int anchor=0;
  1084. float closeRangeSq = FLT_MAX;
  1085. for( int i=0; i<m_escapeRouteVector.Count(); ++i )
  1086. {
  1087. float rangeSq = ( m_escapeRouteVector[i]->GetCenter() - player->GetAbsOrigin() ).LengthSqr();
  1088. if ( rangeSq < closeRangeSq )
  1089. {
  1090. closeRangeSq = rangeSq;
  1091. anchor = i;
  1092. }
  1093. }
  1094. int lo = MAX( 0, anchor-20 );
  1095. int hi = MIN( m_escapeRouteVector.Count(), anchor+20 );
  1096. for( int i=lo; i<hi-1; ++i )
  1097. {
  1098. NDebugOverlay::HorzArrow( m_escapeRouteVector[i]->GetCenter(), m_escapeRouteVector[i+1]->GetCenter(), 5.0f, 255, 0, 0, 255, true, deltaT );
  1099. NDebugOverlay::Text( m_escapeRouteVector[i]->GetCenter(), CFmtStr( "%d", i ), true, deltaT );
  1100. }
  1101. }
  1102. }
  1103. else if ( tf_raid_show_escape_route.GetInt() == 1 )
  1104. {
  1105. CShowEscapeRoute show;
  1106. SearchSurroundingAreas( player->GetLastKnownArea(), show );
  1107. }
  1108. if ( tf_raid_debug_sentry_placement.GetBool() )
  1109. {
  1110. for( int i=0; i<m_actualSentrySpotVector.Count(); ++i )
  1111. {
  1112. m_actualSentrySpotVector[i]->DrawFilled( 255, 155, 0, 255, deltaT, true );
  1113. }
  1114. }
  1115. }
  1116. //--------------------------------------------------------------------------------------------------------
  1117. void CRaidLogic::Update( void )
  1118. {
  1119. VPROF_BUDGET( "CRaidLogic::Update", "Game" );
  1120. const float deltaT = 0.33f;
  1121. SetNextThink( gpGlobals->curtime + deltaT );
  1122. if ( !TFGameRules()->IsRaidMode() )
  1123. return;
  1124. DrawDebugDisplay( deltaT );
  1125. CTeam *raidingTeam = GetGlobalTeam( TF_TEAM_BLUE );
  1126. // total hack for testing mini-bosses
  1127. if ( m_miniBossIndex == 0 && m_miniBossHomeVector.Count() > 0 )
  1128. {
  1129. CTFNavArea *bossHomeArea = m_miniBossHomeVector[0];
  1130. CBaseEntity *miniBoss = CreateEntityByName( "bot_boss_mini_rockets" );
  1131. if ( miniBoss )
  1132. {
  1133. miniBoss->SetAbsOrigin( bossHomeArea->GetCenter() );
  1134. miniBoss->SetOwnerEntity( NULL );
  1135. DispatchSpawn( miniBoss );
  1136. }
  1137. ++m_miniBossIndex;
  1138. }
  1139. else if ( m_miniBossIndex == 1 && m_miniBossHomeVector.Count() > 1 )
  1140. {
  1141. if ( gEntList.FindEntityByClassname( NULL, "bot_boss_mini_rockets" ) == NULL )
  1142. {
  1143. CTFNavArea *bossHomeArea = m_miniBossHomeVector[1];
  1144. CBaseEntity *miniBoss = CreateEntityByName( "bot_boss_mini_nuker" );
  1145. if ( miniBoss )
  1146. {
  1147. miniBoss->SetAbsOrigin( bossHomeArea->GetCenter() );
  1148. miniBoss->SetOwnerEntity( NULL );
  1149. DispatchSpawn( miniBoss );
  1150. }
  1151. ++m_miniBossIndex;
  1152. }
  1153. }
  1154. if ( IsWaitingForRaidersToLeaveSafeRoom() )
  1155. {
  1156. // has anyone left?
  1157. for( int i=0; i<raidingTeam->GetNumPlayers(); ++i )
  1158. {
  1159. CTFPlayer *player = (CTFPlayer *)raidingTeam->GetPlayer(i);
  1160. if ( !player->IsAlive() || !player->GetLastKnownArea() )
  1161. continue;
  1162. // don't start until a HUMAN leaves the safe room
  1163. if ( player->IsBot() )
  1164. continue;
  1165. CTFNavArea *area = (CTFNavArea *)player->GetLastKnownArea();
  1166. if ( !area->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE ) )
  1167. {
  1168. // this Raider has left the spawn room - game on!
  1169. m_isWaitingForRaidersToLeaveSpawnRoom = false;
  1170. StartMobTimer( RandomFloat( 0.5f * tf_raid_mob_spawn_min_interval.GetFloat(), tf_raid_mob_spawn_max_interval.GetFloat() ) );
  1171. m_specialSpawnTimer.Start( RandomFloat( 0.0f, tf_raid_special_spawn_min_interval.GetFloat() ) );
  1172. DevMsg( "RAID: %3.2f: Raiders left the spawn room!\n", gpGlobals->curtime );
  1173. }
  1174. }
  1175. }
  1176. else
  1177. {
  1178. int aliveCount = 0;
  1179. for( int i=0; i<raidingTeam->GetNumPlayers(); ++i )
  1180. {
  1181. CTFPlayer *player = ToTFPlayer( raidingTeam->GetPlayer(i) );
  1182. CTFBot *bot = ToTFBot( player );
  1183. if ( bot && bot->HasAttribute( CTFBot::IS_NPC ) )
  1184. continue;
  1185. if ( player->IsAlive() )
  1186. {
  1187. CTFNavArea *area = (CTFNavArea *)player->GetLastKnownArea();
  1188. if ( !area || !area->HasAttributeTF( TF_NAV_RESCUE_CLOSET ) )
  1189. {
  1190. // only count raiders not in a rescue closet
  1191. ++aliveCount;
  1192. }
  1193. }
  1194. }
  1195. if ( m_priorRaiderAliveCount < 0 )
  1196. {
  1197. // just left the safe room
  1198. if ( m_didFailLastTime )
  1199. {
  1200. TFGameRules()->BroadcastSound( 255, "Announcer.DontFailAgain" );
  1201. }
  1202. else
  1203. {
  1204. TFGameRules()->BroadcastSound( 255, "Announcer.AM_GameStarting04" ); // "Let the games begin!"
  1205. }
  1206. }
  1207. else
  1208. {
  1209. if ( m_priorRaiderAliveCount > aliveCount )
  1210. {
  1211. // somebody died, warn the team
  1212. switch( aliveCount )
  1213. {
  1214. case 3: TFGameRules()->BroadcastSound( 255, "Announcer.RoundEnds3seconds" ); break;
  1215. case 2: TFGameRules()->BroadcastSound( 255, "Announcer.RoundEnds2seconds" ); break;
  1216. case 1: TFGameRules()->BroadcastSound( 255, "Announcer.AM_LastManAlive01" ); break;
  1217. }
  1218. }
  1219. else if ( m_priorRaiderAliveCount < aliveCount )
  1220. {
  1221. // someone has joined/respawned, let the team know
  1222. switch( aliveCount )
  1223. {
  1224. case 4: TFGameRules()->BroadcastSound( 255, "Announcer.RoundEnds4seconds" ); break;
  1225. case 3: TFGameRules()->BroadcastSound( 255, "Announcer.RoundEnds3seconds" ); break;
  1226. case 2: TFGameRules()->BroadcastSound( 255, "Announcer.RoundEnds2seconds" ); break;
  1227. }
  1228. }
  1229. }
  1230. m_priorRaiderAliveCount = aliveCount;
  1231. if ( TFGameRules() && !TFGameRules()->IsInWaitingForPlayers() )
  1232. {
  1233. // if all of the raiders die, they lose
  1234. if ( aliveCount == 0 )
  1235. {
  1236. CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );
  1237. if ( pRules )
  1238. {
  1239. pRules->SetWinningTeam( TF_TEAM_RED, WINREASON_OPPONENTS_DEAD );
  1240. }
  1241. }
  1242. }
  1243. }
  1244. // have the raiders begun capturing the next point?
  1245. CTeamControlPoint *ctrlPoint = GetContestedPoint();
  1246. bool isCapturingPoint = ( ctrlPoint && ctrlPoint->GetTeamCapPercentage( TF_TEAM_BLUE ) );
  1247. // find maximum incursion distance
  1248. if ( m_incursionDistanceAtEnd < 0.0f )
  1249. {
  1250. for( int i=0; i<TheNavAreas.Count(); ++i )
  1251. {
  1252. CTFNavArea *area = (CTFNavArea *)TheNavAreas[ i ];
  1253. if ( area->GetIncursionDistance( TF_TEAM_BLUE ) > m_incursionDistanceAtEnd )
  1254. {
  1255. m_incursionDistanceAtEnd = area->GetIncursionDistance( TF_TEAM_BLUE );
  1256. }
  1257. }
  1258. }
  1259. // find incursion range that surrounds raider team
  1260. float minIncursion = FLT_MAX, maxIncursion = -FLT_MAX;
  1261. m_farthestAlongRaider = NULL;
  1262. CTFPlayer *capturer = NULL;
  1263. int i;
  1264. for( i=0; i<raidingTeam->GetNumPlayers(); ++i )
  1265. {
  1266. CTFPlayer *player = (CTFPlayer *)raidingTeam->GetPlayer(i);
  1267. if ( !player->IsAlive() || !player->GetLastKnownArea() )
  1268. continue;
  1269. CTFBot *bot = ToTFBot( player );
  1270. if ( bot && bot->HasAttribute( CTFBot::IS_NPC ) )
  1271. continue;
  1272. if ( player->IsCapturingPoint() )
  1273. {
  1274. capturer = player;
  1275. }
  1276. CTFNavArea *area = (CTFNavArea *)player->GetLastKnownArea();
  1277. float myIncursion = area->GetIncursionDistance( TF_TEAM_BLUE );
  1278. if ( maxIncursion < myIncursion )
  1279. {
  1280. maxIncursion = myIncursion;
  1281. m_farthestAlongRaider = player;
  1282. }
  1283. if ( minIncursion > myIncursion )
  1284. {
  1285. minIncursion = myIncursion;
  1286. }
  1287. }
  1288. m_farthestAlongEscapeRouteArea = NULL;
  1289. if ( !m_farthestAlongRaider )
  1290. return;
  1291. // watch for point capture events
  1292. if ( !m_wasCapturingPoint && isCapturingPoint )
  1293. {
  1294. DevMsg( "RAID: %3.2f: Point capture STARTED!\n", gpGlobals->curtime );
  1295. // UTIL_CenterPrintAll( CFmtStr( "*** %s started capturing the point! ***", capturer->GetPlayerName() ) );
  1296. // spawn a mob immediately
  1297. StartMobTimer( 0.1f );
  1298. }
  1299. else if ( m_wasCapturingPoint && !isCapturingPoint )
  1300. {
  1301. DevMsg( "RAID: %3.2f: Point capture STOPPED!\n", gpGlobals->curtime );
  1302. }
  1303. m_wasCapturingPoint = isCapturingPoint;
  1304. // track escape route area nearest leader
  1305. for( i=0; i<m_escapeRouteVector.Count(); ++i )
  1306. {
  1307. CTFNavArea *area = m_escapeRouteVector[i];
  1308. if ( area->GetIncursionDistance( TF_TEAM_BLUE ) <= GetMaximumRaiderIncursionDistance() )
  1309. {
  1310. if ( m_farthestAlongEscapeRouteArea )
  1311. {
  1312. if ( m_farthestAlongEscapeRouteArea->GetIncursionDistance( TF_TEAM_BLUE ) < area->GetIncursionDistance( TF_TEAM_BLUE ) )
  1313. {
  1314. m_farthestAlongEscapeRouteArea = area;
  1315. }
  1316. }
  1317. else
  1318. {
  1319. m_farthestAlongEscapeRouteArea = area;
  1320. }
  1321. }
  1322. }
  1323. if ( tf_raid_debug_escape_route.GetBool() && m_farthestAlongEscapeRouteArea )
  1324. {
  1325. m_farthestAlongEscapeRouteArea->DrawFilled( 255, 100, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER );
  1326. }
  1327. CullObsoleteEnemies( minIncursion, maxIncursion );
  1328. // count defensive types
  1329. CTeam *defenseTeam = GetGlobalTeam( TF_TEAM_RED );
  1330. m_wandererCount = 0;
  1331. m_engineerCount = 0;
  1332. m_demomanCount = 0;
  1333. m_heavyCount = 0;
  1334. m_soldierCount = 0;
  1335. m_pyroCount = 0;
  1336. m_spyCount = 0;
  1337. m_sniperCount = 0;
  1338. m_squadCount = 0;
  1339. CUtlVector< CTFBotSquad * > m_squadVector;
  1340. for( i=0; i<defenseTeam->GetNumPlayers(); ++i )
  1341. {
  1342. if ( !defenseTeam->GetPlayer(i)->IsBot() )
  1343. continue;
  1344. CTFBot *defender = (CTFBot *)defenseTeam->GetPlayer(i);
  1345. if ( !defender->IsAlive() )
  1346. continue;
  1347. if ( defender->GetSquad() )
  1348. {
  1349. if ( m_squadVector.Find( defender->GetSquad() ) == m_squadVector.InvalidIndex() )
  1350. {
  1351. m_squadVector.AddToTail( defender->GetSquad() );
  1352. ++m_squadCount;
  1353. }
  1354. continue;
  1355. }
  1356. if ( defender->IsPlayerClass( TF_CLASS_ENGINEER ) )
  1357. {
  1358. ++m_engineerCount;
  1359. }
  1360. else if ( defender->IsPlayerClass( TF_CLASS_DEMOMAN ) )
  1361. {
  1362. ++m_demomanCount;
  1363. }
  1364. else if ( defender->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) )
  1365. {
  1366. ++m_heavyCount;
  1367. }
  1368. else if ( defender->IsPlayerClass( TF_CLASS_SOLDIER ) )
  1369. {
  1370. ++m_soldierCount;
  1371. }
  1372. else if ( defender->IsPlayerClass( TF_CLASS_PYRO ) )
  1373. {
  1374. ++m_pyroCount;
  1375. }
  1376. else if ( defender->IsPlayerClass( TF_CLASS_SPY ) )
  1377. {
  1378. ++m_spyCount;
  1379. }
  1380. else if ( defender->IsPlayerClass( TF_CLASS_SNIPER ) )
  1381. {
  1382. ++m_sniperCount;
  1383. }
  1384. else if ( defender->IsPlayerClass( TF_CLASS_SCOUT ) )
  1385. {
  1386. if ( defender->HasAttribute( CTFBot::AGGRESSIVE ) )
  1387. continue;
  1388. ++m_wandererCount;
  1389. }
  1390. }
  1391. if ( tf_raid_spawn_enable.GetBool() == false )
  1392. return;
  1393. #if 0
  1394. // populate wanderers
  1395. CPopulator populator( maxIncursion, tf_raid_max_wanderers.GetInt() - m_wandererCount );
  1396. SearchSurroundingAreas( m_farthestAlongRaider->GetLastKnownArea(), populator );
  1397. // if raiders are capturing a point, spawn mobs at a faster rate
  1398. if ( isCapturingPoint && m_mobSpawnTimer.GetRemainingTime() > tf_raid_capture_mob_interval.GetFloat() )
  1399. {
  1400. StartMobTimer( tf_raid_capture_mob_interval.GetFloat() - 0.1f );
  1401. }
  1402. SpawnMobs( &populator.m_hiddenAreaVector );
  1403. SpawnSpecials( &populator.m_hiddenAreaAheadVector, &populator.m_hiddenAreaVector );
  1404. SpawnEngineers();
  1405. // emit mob
  1406. if ( IsMobSpawning() && m_mobCountRemaining && m_mobArea && tf_raid_spawn_mobs.GetBool() )
  1407. {
  1408. if ( m_mobArea->HasAttributeTF( TF_NAV_NO_SPAWNING ) )
  1409. {
  1410. // the mob is trying to spawn from an area that doesn't have room to spawn in bots, pick another
  1411. DevMsg( "RAID: %3.2f: Mob attempting to spawn where there isn't room - retrying\n", gpGlobals->curtime );
  1412. m_mobArea = SelectMobSpawn( &populator.m_hiddenAreaVector, BEHIND );
  1413. if ( !m_mobArea )
  1414. {
  1415. DevMsg( "RAID: %3.2f: Can't find a mob spawn area!\n", gpGlobals->curtime );
  1416. // try again soon
  1417. StartMobTimer( 2.0f );
  1418. }
  1419. }
  1420. if ( m_mobArea )
  1421. {
  1422. if ( SpawnRedTFBot( m_mobClass, m_mobArea->GetCenter() + Vector( 0, 0, StepHeight ), IS_MOB_RUSHER ) )
  1423. {
  1424. --m_mobCountRemaining;
  1425. DevMsg( "RAID: %3.2f: Spawned mob member, %d to go\n", gpGlobals->curtime, m_mobCountRemaining );
  1426. }
  1427. }
  1428. }
  1429. #endif // 0
  1430. // block/unblock capture point gate doors
  1431. // TODO: Do this more efficiently
  1432. for( i=0; i<m_gateVector.Count(); ++i )
  1433. {
  1434. CBaseDoor *door = m_gateVector[i];
  1435. Extent doorExtent;
  1436. doorExtent.Init( door );
  1437. NavAreaCollector overlapAreas;
  1438. TheNavMesh->ForAllAreasOverlappingExtent( overlapAreas, doorExtent );
  1439. for( int b=0; b<overlapAreas.m_area.Count(); ++b )
  1440. {
  1441. CTFNavArea *area = (CTFNavArea *)overlapAreas.m_area[b];
  1442. if ( door->m_toggle_state == TS_AT_TOP )
  1443. {
  1444. // open, not blocked
  1445. area->ClearAttributeTF( TF_NAV_BLOCKED );
  1446. }
  1447. else
  1448. {
  1449. // not open, blocked
  1450. area->SetAttributeTF( TF_NAV_BLOCKED );
  1451. }
  1452. }
  1453. }
  1454. }
  1455. //--------------------------------------------------------------------------------------------------------
  1456. float CRaidLogic::GetMaximumRaiderIncursionDistance( void ) const
  1457. {
  1458. if ( m_farthestAlongRaider == NULL )
  1459. return 0.0f;
  1460. CTFNavArea *area = (CTFNavArea *)m_farthestAlongRaider->GetLastKnownArea();
  1461. return area ? area->GetIncursionDistance( TF_TEAM_BLUE ) : 0.0f;
  1462. }
  1463. //--------------------------------------------------------------------------------------------------------
  1464. /**
  1465. * Given a viewing area, return the earliest escape route area near the team that is visible
  1466. */
  1467. CTFNavArea *CRaidLogic::FindEarliestVisibleEscapeRouteAreaNearTeam( CTFNavArea *viewArea ) const
  1468. {
  1469. if ( viewArea == NULL || m_farthestAlongEscapeRouteArea == NULL )
  1470. return NULL;
  1471. const float nearIncursionRange = 1000.0f;
  1472. const float minIncursion = GetMaximumRaiderIncursionDistance() - nearIncursionRange;
  1473. const float maxIncursion = GetMaximumRaiderIncursionDistance() + nearIncursionRange;
  1474. NavAreaCollector collector;
  1475. viewArea->ForAllCompletelyVisibleAreas( collector );
  1476. CTFNavArea *firstArea = NULL;
  1477. float firstAreaIncursion = FLT_MAX;
  1478. for( int i=0; i<collector.m_area.Count(); ++i )
  1479. {
  1480. CTFNavArea *area = (CTFNavArea *)collector.m_area[i];
  1481. float areaIncursion = area->GetIncursionDistance( TF_TEAM_BLUE );
  1482. if ( area->HasAttributeTF( TF_NAV_ESCAPE_ROUTE ) && areaIncursion > minIncursion && areaIncursion < maxIncursion )
  1483. {
  1484. if ( tf_debug_sniper_spots.GetBool() )
  1485. {
  1486. area->DrawFilled( 255, 0, 255, 255, 120.0f );
  1487. }
  1488. /*
  1489. float rangeSq = ( viewArea->GetCenter() - area->GetCenter() ).LengthSqr();
  1490. if ( firstAreaIncursion > rangeSq )
  1491. {
  1492. // closest
  1493. firstAreaIncursion = rangeSq;
  1494. firstArea = area;
  1495. }
  1496. */
  1497. if ( firstAreaIncursion > areaIncursion )
  1498. {
  1499. firstArea = area;
  1500. firstAreaIncursion = areaIncursion;
  1501. }
  1502. }
  1503. }
  1504. return firstArea;
  1505. }
  1506. //--------------------------------------------------------------------------------------------------------
  1507. /**
  1508. * Return area where sniper should take up a vantage point. That area's parent will be
  1509. * the actual spawn-in area not currently visible to the team.
  1510. */
  1511. CTFNavArea *CRaidLogic::FindSniperSpawn( void )
  1512. {
  1513. CUtlVector< CTFNavArea * > validAreas;
  1514. float aheadLimit = GetMaximumRaiderIncursionDistance() + tf_raid_sniper_spawn_ahead_incursion.GetFloat();
  1515. float behindLimit = GetMaximumRaiderIncursionDistance() - tf_raid_sniper_spawn_behind_incursion.GetFloat();
  1516. float tooCloseLimitSq = tf_raid_sniper_spawn_min_range.GetFloat();
  1517. tooCloseLimitSq *= tooCloseLimitSq;
  1518. for( int i=0; i<m_sniperSpotVector.Count(); ++i )
  1519. {
  1520. CTFNavArea *sniperArea = m_sniperSpotVector[i];
  1521. if ( sniperArea->GetIncursionDistance( TF_TEAM_BLUE ) < 0.0f )
  1522. continue;
  1523. if ( sniperArea->GetIncursionDistance( TF_TEAM_BLUE ) > aheadLimit )
  1524. continue;
  1525. if ( sniperArea->GetIncursionDistance( TF_TEAM_BLUE ) < behindLimit )
  1526. continue;
  1527. // make sure no Raider is too close
  1528. CClosestTFPlayer close( sniperArea->GetCenter(), TF_TEAM_BLUE );
  1529. ForEachPlayer( close );
  1530. if ( close.m_closePlayer && close.m_closeRangeSq < tooCloseLimitSq )
  1531. continue;
  1532. // this is often too restrictive - we really want "potentially visible to region near team"
  1533. if ( !sniperArea->IsPotentiallyVisibleToTeam( TF_TEAM_BLUE ) )
  1534. {
  1535. if ( tf_debug_sniper_spots.GetBool() )
  1536. sniperArea->DrawFilled( 100, 100, 100, 255, 99999.9f );
  1537. continue;
  1538. }
  1539. validAreas.AddToTail( sniperArea );
  1540. }
  1541. if ( validAreas.Count() )
  1542. {
  1543. // choose a specific sniper spot
  1544. CTFNavArea *sniperArea = validAreas[ RandomInt( 0, validAreas.Count()-1 ) ];
  1545. // find a nearby non-visible spawn spot
  1546. CNearbyHiddenScan hide;
  1547. const float hideRange = 1000.0f;
  1548. SearchSurroundingAreas( sniperArea, hide, hideRange );
  1549. if ( hide.m_hiddenArea )
  1550. {
  1551. sniperArea->SetParent( hide.m_hiddenArea );
  1552. if ( tf_debug_sniper_spots.GetBool() )
  1553. {
  1554. const float deltaT = 999999.9f;
  1555. hide.m_hiddenArea->DrawFilled( 0, 0, 255, 255, deltaT );
  1556. NDebugOverlay::HorzArrow( hide.m_hiddenArea->GetCenter() + Vector( 0, 0, 10.0f ), sniperArea->GetCenter() + Vector( 0, 0, 10.0f ), 5.0f, 255, 255, 0, 255, true, deltaT );
  1557. sniperArea->DrawFilled( 255, 0, 0, 255, deltaT );
  1558. for( int i=0; i<validAreas.Count(); ++i )
  1559. {
  1560. if ( validAreas[i] != sniperArea && validAreas[i] != hide.m_hiddenArea )
  1561. validAreas[i]->DrawFilled( 0, 255, 0, 255, 99999.9f );
  1562. }
  1563. }
  1564. return sniperArea;
  1565. }
  1566. }
  1567. return NULL;
  1568. }
  1569. //--------------------------------------------------------------------------------------------------------
  1570. /**
  1571. * Return a good area for a Sentry Gun, and return a hidden area for the engineer
  1572. * to spawn as the parent of that area.
  1573. */
  1574. CTFNavArea *CRaidLogic::FindSentryArea( void )
  1575. {
  1576. CUtlVector< CTFNavArea * > validAreas;
  1577. float aheadLimit = GetMaximumRaiderIncursionDistance() + tf_raid_sentry_build_ahead_incursion.GetFloat();
  1578. float behindLimit = GetMaximumRaiderIncursionDistance() - tf_raid_sentry_build_behind_incursion.GetFloat();
  1579. for( int i=0; i<m_sentrySpotVector.Count(); ++i )
  1580. {
  1581. CTFNavArea *sentryArea = m_sentrySpotVector[i];
  1582. if ( sentryArea->GetIncursionDistance( TF_TEAM_BLUE ) < 0.0f )
  1583. continue;
  1584. if ( sentryArea->GetIncursionDistance( TF_TEAM_BLUE ) > aheadLimit )
  1585. continue;
  1586. if ( sentryArea->GetIncursionDistance( TF_TEAM_BLUE ) < behindLimit )
  1587. continue;
  1588. // don't use this area if it already has a sentry in it
  1589. if ( TheTFNavMesh()->IsSentryGunHere( sentryArea ) )
  1590. continue;
  1591. validAreas.AddToTail( sentryArea );
  1592. }
  1593. if ( validAreas.Count() )
  1594. {
  1595. // choose a specific sentry spot
  1596. CTFNavArea *sentryArea = validAreas[ RandomInt( 0, validAreas.Count()-1 ) ];
  1597. // find a nearby non-visible spawn spot for the engineer
  1598. CNearbyHiddenScan hide;
  1599. const float hideRange = 1000.0f;
  1600. SearchSurroundingAreas( sentryArea, hide, hideRange );
  1601. if ( hide.m_hiddenArea )
  1602. {
  1603. sentryArea->SetParent( hide.m_hiddenArea );
  1604. return sentryArea;
  1605. }
  1606. }
  1607. return NULL;
  1608. }
  1609. //---------------------------------------------------------------------------------------------
  1610. // Pick a member of the raiding (blue) team for a red defender to attack
  1611. CTFPlayer *CRaidLogic::SelectRaiderToAttack( void )
  1612. {
  1613. CTeam *invaderTeam = GetGlobalTeam( TF_TEAM_BLUE );
  1614. // attack point cappers first
  1615. CUtlVector< CTFPlayer * > victimVector;
  1616. int i;
  1617. for( i=0; i<invaderTeam->GetNumPlayers(); ++i )
  1618. {
  1619. CTFPlayer *player = (CTFPlayer *)invaderTeam->GetPlayer(i);
  1620. if ( player->IsAlive() && player->IsCapturingPoint() )
  1621. victimVector.AddToTail( player );
  1622. }
  1623. if ( victimVector.Count() == 0 )
  1624. {
  1625. // pick a random living Raider
  1626. for( i=0; i<invaderTeam->GetNumPlayers(); ++i )
  1627. {
  1628. CTFPlayer *player = (CTFPlayer *)invaderTeam->GetPlayer(i);
  1629. CTFBot *bot = ToTFBot( player );
  1630. if ( bot && bot->HasAttribute( CTFBot::IS_NPC ) )
  1631. continue;
  1632. if ( player->IsAlive() )
  1633. victimVector.AddToTail( player );
  1634. }
  1635. }
  1636. if ( victimVector.Count() )
  1637. {
  1638. return victimVector[ RandomInt( 0, victimVector.Count()-1 ) ];
  1639. }
  1640. return NULL;
  1641. }
  1642. //--------------------------------------------------------------------------------------------------------
  1643. // Return entity positioned within next valid rescue closet area for to respawn players in
  1644. CBaseEntity *CRaidLogic::GetRescueRespawn( void ) const
  1645. {
  1646. if ( g_internalSpawnPoint == NULL )
  1647. {
  1648. g_internalSpawnPoint = (CPopulatorInternalSpawnPoint *)CreateEntityByName( "populator_internal_spawn_point" );
  1649. g_internalSpawnPoint->Spawn();
  1650. }
  1651. float limit = GetMaximumRaiderIncursionDistance() - 500.0f;
  1652. CTFNavArea *rescueArea = NULL;
  1653. float rescueFlow = FLT_MAX;
  1654. for( int i=0; i<m_rescueClosetVector.Count(); ++i )
  1655. {
  1656. float flow = m_rescueClosetVector[i]->GetIncursionDistance( TF_TEAM_BLUE );
  1657. if ( flow > limit && flow < rescueFlow )
  1658. {
  1659. rescueArea = m_rescueClosetVector[i];
  1660. rescueFlow = flow;
  1661. }
  1662. }
  1663. if ( rescueArea )
  1664. {
  1665. g_internalSpawnPoint->SetAbsOrigin( rescueArea->GetCenter() );
  1666. g_internalSpawnPoint->SetLocalAngles( vec3_angle );
  1667. return g_internalSpawnPoint;
  1668. }
  1669. return NULL;
  1670. }
  1671. //--------------------------------------------------------------------------------------------------------
  1672. class CMarkEscapeRoute
  1673. {
  1674. public:
  1675. CMarkEscapeRoute( CUtlVector< CTFNavArea * > *escapeRouteVector )
  1676. {
  1677. m_escapeRouteVector = escapeRouteVector;
  1678. }
  1679. void operator() ( CNavArea *baseArea )
  1680. {
  1681. CTFNavArea *area = (CTFNavArea *)baseArea;
  1682. if ( !m_escapeRouteVector->HasElement( area ) )
  1683. {
  1684. area->SetAttributeTF( TF_NAV_ESCAPE_ROUTE );
  1685. m_escapeRouteVector->AddToTail( area );
  1686. }
  1687. }
  1688. CUtlVector< CTFNavArea * > *m_escapeRouteVector;
  1689. };
  1690. //--------------------------------------------------------------------------------------------------------
  1691. class CMarkEscapeRouteVisible
  1692. {
  1693. public:
  1694. bool operator() ( CNavArea *baseArea )
  1695. {
  1696. CTFNavArea *area = (CTFNavArea *)baseArea;
  1697. area->SetAttributeTF( TF_NAV_ESCAPE_ROUTE_VISIBLE );
  1698. return true;
  1699. }
  1700. };
  1701. //---------------------------------------------------------------------------------------------
  1702. // Locate and store escape route
  1703. void CRaidLogic::BuildEscapeRoute( void )
  1704. {
  1705. // find blue spawn room
  1706. CBaseEntity *entity = NULL;
  1707. for ( int i=0; i<IFuncRespawnRoomAutoList::AutoList().Count(); ++i )
  1708. {
  1709. CFuncRespawnRoom *respawnRoom = static_cast< CFuncRespawnRoom* >( IFuncRespawnRoomAutoList::AutoList()[i] );
  1710. entity = respawnRoom;
  1711. if ( respawnRoom->GetActive() && respawnRoom->GetTeamNumber() == TF_TEAM_BLUE )
  1712. {
  1713. break;
  1714. }
  1715. }
  1716. if ( entity )
  1717. {
  1718. // respawn room absorigin is 0,0,0 - have to use extent
  1719. Extent extent;
  1720. extent.Init( entity );
  1721. Vector safeRoomPos = ( extent.lo + extent.hi ) / 2;
  1722. DevMsg( "RAID: Blue spawn room at (%g, %g, %g)\n", safeRoomPos.x, safeRoomPos.y, safeRoomPos.z );
  1723. // assume path_track nearest the blue spawn is the start of the escape route
  1724. // according to the mappers, previous pointers do not contain useful data
  1725. CPathTrack *pathNode = NULL;
  1726. CPathTrack *firstPathNode = NULL;
  1727. float closeRangeSq = FLT_MAX;
  1728. for( pathNode = dynamic_cast< CPathTrack * >( gEntList.FindEntityByClassname( pathNode, "path_track" ) );
  1729. pathNode;
  1730. pathNode = dynamic_cast< CPathTrack * >( gEntList.FindEntityByClassname( pathNode, "path_track" ) ) )
  1731. {
  1732. float rangeSq = ( pathNode->GetAbsOrigin() - safeRoomPos ).LengthSqr();
  1733. if ( rangeSq < closeRangeSq )
  1734. {
  1735. closeRangeSq = rangeSq;
  1736. firstPathNode = pathNode;
  1737. }
  1738. }
  1739. if ( firstPathNode )
  1740. {
  1741. CUtlVector< CTFNavArea * > pathTrackAreaVector;
  1742. for( pathNode = firstPathNode; pathNode; pathNode = pathNode->GetNext() )
  1743. {
  1744. CTFNavArea *pathArea = (CTFNavArea *)TheNavMesh->GetNavArea( pathNode->GetAbsOrigin() );
  1745. if ( pathArea )
  1746. {
  1747. if ( !pathTrackAreaVector.HasElement( pathArea ) )
  1748. {
  1749. pathTrackAreaVector.AddToTail( pathArea );
  1750. }
  1751. }
  1752. }
  1753. if ( pathTrackAreaVector.Count() > 1 )
  1754. {
  1755. // build contiguous escape route area vector
  1756. m_escapeRouteVector.RemoveAll();
  1757. CMarkEscapeRoute markAsEscapeRoute( &m_escapeRouteVector );
  1758. for( int i=1; i<pathTrackAreaVector.Count(); ++i )
  1759. {
  1760. // mark all areas between as on the escape route
  1761. TheNavMesh->ForAllAreasAlongLine( markAsEscapeRoute, pathTrackAreaVector[i-1], pathTrackAreaVector[i] );
  1762. }
  1763. // flag all areas that can see the escape route as escape route 'visible'
  1764. for( int i=0; i<m_escapeRouteVector.Count(); ++i )
  1765. {
  1766. CTFNavArea *escapeArea = m_escapeRouteVector[i];
  1767. CMarkEscapeRouteVisible markAsEscapeRouteVisible;
  1768. escapeArea->ForAllCompletelyVisibleAreas( markAsEscapeRouteVisible );
  1769. }
  1770. }
  1771. }
  1772. }
  1773. }
  1774. //---------------------------------------------------------------------------------------------
  1775. //
  1776. // Return the next control point that can be captured
  1777. //
  1778. CTeamControlPoint *CRaidLogic::GetContestedPoint( void ) const
  1779. {
  1780. CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
  1781. if ( pMaster )
  1782. {
  1783. for( int i=0; i<pMaster->GetNumPoints(); ++i )
  1784. {
  1785. CTeamControlPoint *point = pMaster->GetControlPoint( i );
  1786. if ( point && pMaster->IsInRound( point ) )
  1787. {
  1788. if ( ObjectiveResource()->GetOwningTeam( point->GetPointIndex() ) == TF_TEAM_BLUE )
  1789. continue;
  1790. // blue are the invaders
  1791. if ( !TeamplayGameRules()->TeamMayCapturePoint( TF_TEAM_BLUE, point->GetPointIndex() ) )
  1792. continue;
  1793. return point;
  1794. }
  1795. }
  1796. }
  1797. return NULL;
  1798. }
  1799. #endif // TF_RAID_MODE