Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

473 lines
12 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. // NavMesh.cpp
  9. // Implementation of Navigation Mesh interface
  10. // Author: Michael S. Booth, 2003-2004
  11. #include "cbase.h"
  12. #include "filesystem.h"
  13. #include "cs_nav_mesh.h"
  14. #include "cs_nav_node.h"
  15. #include "cs_nav_area.h"
  16. #include "cs_nav_pathfind.h"
  17. #include "cs_shareddefs.h"
  18. #include "cs_gamerules.h"
  19. #include "fmtstr.h"
  20. #include "utlbuffer.h"
  21. #include "bot_util.h"
  22. #include "tier0/vprof.h"
  23. #include "cs_bot_manager.h"
  24. #ifndef CLIENT_DLL
  25. #include "mapinfo.h"
  26. #endif
  27. class CFuncBlockDMSpawns : public CFuncBrush
  28. {
  29. DECLARE_CLASS( CFuncBlockDMSpawns, CFuncBrush );
  30. void Spawn( void )
  31. {
  32. SetSolid( SOLID_BSP );
  33. AddSolidFlags( FSOLID_NOT_SOLID );
  34. SetModel( STRING( GetModelName() ) ); // set size and link into world
  35. extern ConVar showtriggers;
  36. if ( !showtriggers.GetInt() )
  37. {
  38. AddEffects( EF_NODRAW );
  39. }
  40. }
  41. };
  42. LINK_ENTITY_TO_CLASS( func_block_dm_spawns, CFuncBlockDMSpawns );
  43. extern ConVar mp_randomspawn;
  44. ConVar mp_guardian_target_site( "mp_guardian_target_site", "-1", FCVAR_RELEASE | FCVAR_GAMEDLL, "If set to the index of a bombsite, will cause random spawns to be only created near that site." );
  45. //--------------------------------------------------------------------------------------------------------------
  46. CSNavMesh::CSNavMesh( void )
  47. {
  48. m_desiredChickenCount = 0;
  49. }
  50. //--------------------------------------------------------------------------------------------------------------
  51. CSNavMesh::~CSNavMesh()
  52. {
  53. }
  54. CNavArea * CSNavMesh::CreateArea( void ) const
  55. {
  56. return new CCSNavArea;
  57. }
  58. //-------------------------------------------------------------------------
  59. void CSNavMesh::BeginCustomAnalysis( bool bIncremental )
  60. {
  61. }
  62. //-------------------------------------------------------------------------
  63. // invoked when custom analysis step is complete
  64. void CSNavMesh::PostCustomAnalysis( void )
  65. {
  66. }
  67. //-------------------------------------------------------------------------
  68. void CSNavMesh::EndCustomAnalysis()
  69. {
  70. }
  71. //-------------------------------------------------------------------------
  72. /**
  73. * Returns sub-version number of data format used by derived classes
  74. */
  75. unsigned int CSNavMesh::GetSubVersionNumber( void ) const
  76. {
  77. // 1: initial implementation - added ApproachArea data
  78. return 1;
  79. }
  80. //-------------------------------------------------------------------------
  81. /**
  82. * Store custom mesh data for derived classes
  83. */
  84. void CSNavMesh::SaveCustomData( CUtlBuffer &fileBuffer ) const
  85. {
  86. }
  87. //-------------------------------------------------------------------------
  88. /**
  89. * Load custom mesh data for derived classes
  90. */
  91. void CSNavMesh::LoadCustomData( CUtlBuffer &fileBuffer, unsigned int subVersion )
  92. {
  93. }
  94. //--------------------------------------------------------------------------------------------------------------
  95. /**
  96. * Reset the Navigation Mesh to initial values
  97. */
  98. void CSNavMesh::Reset( void )
  99. {
  100. m_refreshChickenTimer.Start( 5.0f );
  101. m_refreshDMSpawnTimer.Start( 1.0f );
  102. CNavMesh::Reset();
  103. }
  104. //--------------------------------------------------------------------------------------------------------------
  105. /**
  106. * Zero player counts in all areas
  107. */
  108. void CSNavMesh::ClearPlayerCounts( void )
  109. {
  110. FOR_EACH_VEC( TheNavAreas, it )
  111. {
  112. CCSNavArea *area = (CCSNavArea*)TheNavAreas[ it ];
  113. area->ClearPlayerCount();
  114. }
  115. }
  116. //--------------------------------------------------------------------------------------------------------------
  117. // Keep desired number of chickens alive in map
  118. void CSNavMesh::MaintainChickenPopulation( void )
  119. {
  120. if ( m_desiredChickenCount > 0 && m_refreshChickenTimer.IsElapsed() )
  121. {
  122. m_refreshChickenTimer.Start( RandomFloat( 10.0f, 20.0f ) );
  123. int actualCount = 0;
  124. for( int i=0; i<m_chickenVector.Count(); ++i )
  125. {
  126. if ( m_chickenVector[i] != NULL )
  127. {
  128. ++actualCount;
  129. }
  130. }
  131. if ( actualCount < m_desiredChickenCount )
  132. {
  133. int need = m_desiredChickenCount - actualCount;
  134. for( int k=0; k<need; ++k )
  135. {
  136. // find a good spot to spawn a chicken
  137. CBaseEntity *chicken = NULL;
  138. for( int attempts=0; attempts<10; ++attempts )
  139. {
  140. int which = RandomInt( 0, TheNavAreas.Count()-1 );
  141. CNavArea *testArea = TheNavAreas[ which ];
  142. const float tooSmall = 50.0f;
  143. if ( testArea && testArea->GetSizeX() > tooSmall && testArea->GetSizeY() > tooSmall )
  144. {
  145. if ( !UTIL_IsVisibleToTeam( testArea->GetCenter(), TEAM_CT ) &&
  146. !UTIL_IsVisibleToTeam( testArea->GetCenter(), TEAM_TERRORIST ) )
  147. {
  148. // don't spawn a chicken on top of another chicken
  149. int n;
  150. for( n=0; n<m_chickenVector.Count(); ++n )
  151. {
  152. if ( m_chickenVector[n] == NULL )
  153. continue;
  154. const float tooClose = 50.0f;
  155. Vector between = m_chickenVector[n]->GetAbsOrigin() - testArea->GetCenter();
  156. if ( between.IsLengthLessThan( tooClose ) )
  157. break;
  158. }
  159. if ( n >= m_chickenVector.Count() )
  160. {
  161. // found a good spot - spawn a chicken here
  162. chicken = CreateEntityByName( "chicken" );
  163. if ( chicken )
  164. {
  165. chicken->SetAbsOrigin( testArea->GetCenter() );
  166. DispatchSpawn( chicken );
  167. m_chickenVector.AddToTail( chicken );
  168. }
  169. break;
  170. }
  171. }
  172. }
  173. }
  174. if ( !chicken )
  175. {
  176. // couldn't spawn a chicken - try again later
  177. return;
  178. }
  179. }
  180. }
  181. }
  182. }
  183. //--------------------------------------------------------------------------------------------------------------
  184. void CSNavMesh::Update( void )
  185. {
  186. MaintainChickenPopulation();
  187. MaintainDMSpawnPopulation();
  188. CNavMesh::Update();
  189. }
  190. NavErrorType CSNavMesh::Load( void )
  191. {
  192. return CNavMesh::Load();
  193. }
  194. bool CSNavMesh::Save( void ) const
  195. {
  196. return CNavMesh::Save();
  197. }
  198. NavErrorType CSNavMesh::PostLoad( unsigned int version )
  199. {
  200. if ( CSGameRules()->IsPlayingGunGameDeathmatch() )
  201. m_desiredChickenCount = 10;
  202. else
  203. m_desiredChickenCount = g_pMapInfo ? g_pMapInfo->m_iPetPopulation : 0;
  204. m_chickenVector.RemoveAll();
  205. if ( CSGameRules() && CSGameRules()->IsPlayingCooperativeGametype() )
  206. {
  207. // in Guardian mode, remove all of the mapper placed DM spawn points
  208. CBaseEntity *pEntity = NULL;
  209. while ( ( pEntity = gEntList.FindEntityByClassname( pEntity, "info_deathmatch_spawn" ) ) != NULL )
  210. {
  211. UTIL_Remove( pEntity );
  212. }
  213. }
  214. ResetDMSpawns();
  215. return CNavMesh::PostLoad(version);
  216. }
  217. CON_COMMAND( dm_reset_spawns, "" )
  218. {
  219. ( dynamic_cast< CSNavMesh* >( TheNavMesh ) )->ResetDMSpawns();
  220. DevMsg( "Manually resetting deathmatch spawns.\n");
  221. }
  222. void CSNavMesh::ResetDMSpawns( void )
  223. {
  224. FOR_EACH_VEC( m_DMSpawnVector, i )
  225. {
  226. UTIL_Remove( m_DMSpawnVector[i] );
  227. }
  228. m_desiredDMSpawns = 50;
  229. m_consecutiveFailedAttempts = 0;
  230. m_DMSpawnVector.RemoveAll();
  231. MaintainDMSpawnPopulation();
  232. }
  233. void CSNavMesh::MaintainDMSpawnPopulation( void )
  234. {
  235. if ( mp_randomspawn.GetInt() == 0 )
  236. return;
  237. bool bDisableAutoGeneratedDMSpawns = ( g_pMapInfo ? g_pMapInfo->m_bDisableAutoGeneratedDMSpawns : false );
  238. // HACK: Force this to run in guardian, even if the map explicitly placed dm spawns
  239. if ( bDisableAutoGeneratedDMSpawns && !CSGameRules()->IsPlayingCoopGuardian() )
  240. return;
  241. if ( m_desiredDMSpawns > 0 && m_refreshDMSpawnTimer.IsElapsed() )
  242. {
  243. m_refreshDMSpawnTimer.Start( 1.0f );
  244. int actualCount = 0;
  245. for( int i=0; i<m_DMSpawnVector.Count(); ++i )
  246. {
  247. if ( m_DMSpawnVector[i] != NULL )
  248. {
  249. ++actualCount;
  250. }
  251. }
  252. // If the map isn't large enough to support as many spawns as m_desiredDMSpawns then shrink m_desiredDMSpawns to whatever we can get.
  253. if ( m_consecutiveFailedAttempts >= 100 )
  254. {
  255. m_desiredDMSpawns = actualCount;
  256. m_consecutiveFailedAttempts = 0;
  257. Warning( "Giving up attempts to make more random spawn points. Current count: %i.\n", actualCount );
  258. }
  259. if ( actualCount < m_desiredDMSpawns )
  260. {
  261. // float calctime = Plat_FloatTime(); MEASURE PERF 1/2
  262. int need = m_desiredDMSpawns - actualCount;
  263. int spawnsToTry = max( 10, need );
  264. for( int k=0; k < spawnsToTry; k++ )
  265. {
  266. // find a good spot to spawn a Deathmatch spawnpoint
  267. CBaseEntity *DMSpawn = NULL;
  268. m_consecutiveFailedAttempts++;
  269. for( int attempts = 0; attempts < 10; attempts++ )
  270. {
  271. CNavArea *testArea = NULL;
  272. if ( mp_guardian_target_site.GetInt() >= 0 )
  273. {
  274. const CCSBotManager::Zone *zone = TheCSBots()->GetZone( mp_guardian_target_site.GetInt() );
  275. if ( zone )
  276. {
  277. testArea = zone->m_area[ RandomInt( 0, zone->m_areaCount - 1 ) ];
  278. }
  279. }
  280. else
  281. {
  282. testArea = TheNavAreas[ RandomInt( 0, TheNavAreas.Count() - 1 ) ];
  283. }
  284. const float tooSmall = ( mp_guardian_target_site.GetInt() >= 0 ) ? 35.0f : 100.0f;
  285. if ( testArea && testArea->GetSizeX() > tooSmall && testArea->GetSizeY() > tooSmall )
  286. {
  287. bool bBadNavArea = false;
  288. // don't spawn a DMSpawn too close to another DMSpawn
  289. for( int n = 0; n < m_DMSpawnVector.Count(); n++ )
  290. {
  291. if ( m_DMSpawnVector[n] == NULL )
  292. continue;
  293. float tooClose = ( mp_guardian_target_site.GetInt() >= 0 ) ? 50.0f : 300.0f;
  294. Vector between = m_DMSpawnVector[n]->GetAbsOrigin() - testArea->GetCenter();
  295. if ( between.IsLengthLessThan( tooClose ) )
  296. {
  297. bBadNavArea = true;
  298. break;
  299. }
  300. }
  301. if ( bBadNavArea )
  302. continue;
  303. if ( IsSpawnBlockedByTrigger( testArea->GetCenter() + Vector( 0, 0, 16 ) ) )
  304. continue;
  305. // check that we can path from the nav area to a ct spawner to confirm it isn't orphaned.
  306. CBaseEntity *CTSpawn = gEntList.FindEntityByClassname( NULL, "info_player_counterterrorist" );
  307. if ( CTSpawn )
  308. {
  309. CNavArea *CTSpawnArea = GetNearestNavArea( CTSpawn->GetAbsOrigin() );
  310. ShortestPathCost cost;
  311. bool bNotOrphaned = NavAreaBuildPath( testArea, CTSpawnArea, NULL, cost );
  312. if ( !bNotOrphaned )
  313. {
  314. const char* szTSpawnEntName = "info_player_terrorist";
  315. if ( CSGameRules()->IsPlayingCoopMission() )
  316. szTSpawnEntName = "info_enemy_terrorist_spawn";
  317. // double check that we can path from the nav area to a t spawner to confirm it isn't orphaned.
  318. CBaseEntity *TSpawn = gEntList.FindEntityByClassname( NULL, szTSpawnEntName );
  319. if ( TSpawn )
  320. {
  321. CNavArea *TSpawnArea = GetNearestNavArea( TSpawn->GetAbsOrigin() );
  322. ShortestPathCost cost2;
  323. bNotOrphaned = NavAreaBuildPath( testArea, TSpawnArea, NULL, cost2 );
  324. if ( !bNotOrphaned )
  325. continue;
  326. }
  327. }
  328. }
  329. // found a good spot - spawn a DMSpawn here
  330. DMSpawn = CreateEntityByName( "info_deathmatch_spawn" );
  331. if ( DMSpawn )
  332. {
  333. DMSpawn->SetAbsOrigin( testArea->GetCenter() + Vector( 0, 0, 16 ));
  334. DispatchSpawn( DMSpawn );
  335. m_DMSpawnVector.AddToTail( DMSpawn );
  336. m_consecutiveFailedAttempts = 0;
  337. // Msg( " %f :CREATED DM SPAWN %i.\n", gpGlobals->curtime, m_DMSpawnVector.Count() );
  338. }
  339. break;
  340. }
  341. }
  342. }
  343. // MEASURE PERF 2/2
  344. // calctime = Plat_FloatTime() - calctime;
  345. // Msg( "DM SPAWN SEARCH CALC TIME: %f\n", calctime );
  346. }
  347. }
  348. }
  349. bool CSNavMesh::IsSpawnBlockedByTrigger( Vector pos )
  350. {
  351. CBaseEntity *list[32];
  352. Ray_t ray;
  353. ray.Init( pos, pos + Vector( 1,1,1 ) );
  354. int nCount = AllEdictsAlongRay( list, 1024, ray, 0 );
  355. for ( int i = 0; i < nCount; i++ )
  356. {
  357. if ( FClassnameIs( list[i], "func_block_dm_spawns" ) )
  358. return true;
  359. }
  360. return false;
  361. }
  362. int CSNavMesh::AllEdictsAlongRay( CBaseEntity **pList, int listMax, const Ray_t &ray, int flagMask )
  363. {
  364. CFlaggedEntitiesEnum rayEnum( pList, listMax, flagMask );
  365. ::partition->EnumerateElementsAlongRay( PARTITION_ENGINE_NON_STATIC_EDICTS, ray, false, &rayEnum );
  366. return rayEnum.GetCount();
  367. }