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.

496 lines
13 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. // bot_hide.cpp
  9. // Mechanisms for using Hiding Spots in the Navigation Mesh
  10. // Author: Michael Booth, 2003-2004
  11. #include "cbase.h"
  12. #include "bot.h"
  13. #include "cs_nav_pathfind.h"
  14. // memdbgon must be the last include file in a .cpp file!!!
  15. #include "tier0/memdbgon.h"
  16. //--------------------------------------------------------------------------------------------------------------
  17. /**
  18. * If a player is at the given spot, return true
  19. */
  20. bool IsSpotOccupied( CBaseEntity *me, const Vector &pos )
  21. {
  22. const float closeRange = 75.0f; // 50
  23. // is there a player in this spot
  24. float range;
  25. CBasePlayer *player = UTIL_GetClosestPlayer( pos, &range );
  26. if (player != me)
  27. {
  28. if (player && range < closeRange)
  29. return true;
  30. }
  31. // is there is a hostage in this spot
  32. // BOTPORT: Implement hostage manager
  33. /*
  34. if (g_pHostages)
  35. {
  36. CHostage *hostage = g_pHostages->GetClosestHostage( *pos, &range );
  37. if (hostage && hostage != me && range < closeRange)
  38. return true;
  39. }
  40. */
  41. return false;
  42. }
  43. //--------------------------------------------------------------------------------------------------------------
  44. class CollectHidingSpotsFunctor
  45. {
  46. public:
  47. CollectHidingSpotsFunctor( CBaseEntity *me, const Vector &origin, float range, int flags, Place place = UNDEFINED_PLACE ) : m_origin( origin )
  48. {
  49. m_me = me;
  50. m_count = 0;
  51. m_range = range;
  52. m_flags = (unsigned char)flags;
  53. m_place = place;
  54. m_totalWeight = 0;
  55. }
  56. enum { MAX_SPOTS = 256 };
  57. bool operator() ( CNavArea *area )
  58. {
  59. // if a place is specified, only consider hiding spots from areas in that place
  60. if (m_place != UNDEFINED_PLACE && area->GetPlace() != m_place)
  61. return true;
  62. // collect all the hiding spots in this area
  63. const HidingSpotVector *pSpots = area->GetHidingSpots();
  64. FOR_EACH_VEC( (*pSpots), it )
  65. {
  66. const HidingSpot *spot = (*pSpots)[ it ];
  67. // if we've filled up, stop searching
  68. if (m_count == MAX_SPOTS)
  69. {
  70. return false;
  71. }
  72. // make sure hiding spot is in range
  73. if (m_range > 0.0f)
  74. {
  75. if ((spot->GetPosition() - m_origin).IsLengthGreaterThan( m_range ))
  76. {
  77. continue;
  78. }
  79. }
  80. // if a Player is using this hiding spot, don't consider it
  81. if (IsSpotOccupied( m_me, spot->GetPosition() ))
  82. {
  83. // player is in hiding spot
  84. /// @todo Check if player is moving or sitting still
  85. continue;
  86. }
  87. if (spot->GetArea() && (spot->GetArea()->GetAttributes() & NAV_MESH_DONT_HIDE))
  88. {
  89. // the area has been marked as DONT_HIDE since the last analysis, so let's ignore it
  90. continue;
  91. }
  92. // only collect hiding spots with matching flags
  93. if (m_flags & spot->GetFlags())
  94. {
  95. m_hidingSpot[ m_count ] = &spot->GetPosition();
  96. m_hidingSpotWeight[ m_count ] = m_totalWeight;
  97. // if it's an 'avoid' area, give it a low weight
  98. if ( spot->GetArea() && ( spot->GetArea()->GetAttributes() & NAV_MESH_AVOID ) )
  99. {
  100. m_totalWeight += 1;
  101. }
  102. else
  103. {
  104. m_totalWeight += 2;
  105. }
  106. ++m_count;
  107. }
  108. }
  109. return (m_count < MAX_SPOTS);
  110. }
  111. /**
  112. * Remove the spot at index "i"
  113. */
  114. void RemoveSpot( int i )
  115. {
  116. if (m_count == 0)
  117. return;
  118. for( int j=i+1; j<m_count; ++j )
  119. m_hidingSpot[j-1] = m_hidingSpot[j];
  120. --m_count;
  121. }
  122. int GetRandomHidingSpot( void )
  123. {
  124. int weight = RandomInt( 0, m_totalWeight-1 );
  125. for ( int i=0; i<m_count-1; ++i )
  126. {
  127. // if the next spot's starting weight is over the target weight, this spot is the one
  128. if ( m_hidingSpotWeight[i+1] >= weight )
  129. {
  130. return i;
  131. }
  132. }
  133. // if we didn't find any, it's the last one
  134. return m_count - 1;
  135. }
  136. CBaseEntity *m_me;
  137. const Vector &m_origin;
  138. float m_range;
  139. const Vector *m_hidingSpot[ MAX_SPOTS ];
  140. int m_hidingSpotWeight[ MAX_SPOTS ];
  141. int m_totalWeight;
  142. int m_count;
  143. unsigned char m_flags;
  144. Place m_place;
  145. };
  146. /**
  147. * Do a breadth-first search to find a nearby hiding spot and return it.
  148. * Don't pick a hiding spot that a Player is currently occupying.
  149. * @todo Clean up this mess
  150. */
  151. const Vector *FindNearbyHidingSpot( CBaseEntity *me, const Vector &pos, float maxRange, bool isSniper, bool useNearest )
  152. {
  153. CNavArea *startArea = TheNavMesh->GetNearestNavArea( pos );
  154. if (startArea == NULL)
  155. return NULL;
  156. // collect set of nearby hiding spots
  157. if (isSniper)
  158. {
  159. CollectHidingSpotsFunctor collector( me, pos, maxRange, HidingSpot::IDEAL_SNIPER_SPOT );
  160. SearchSurroundingAreas( startArea, pos, collector, maxRange );
  161. if (collector.m_count)
  162. {
  163. int which = collector.GetRandomHidingSpot();
  164. return collector.m_hidingSpot[ which ];
  165. }
  166. else
  167. {
  168. // no ideal sniping spots, look for "good" sniping spots
  169. CollectHidingSpotsFunctor collector( me, pos, maxRange, HidingSpot::GOOD_SNIPER_SPOT );
  170. SearchSurroundingAreas( startArea, pos, collector, maxRange );
  171. if (collector.m_count)
  172. {
  173. int which = collector.GetRandomHidingSpot();
  174. return collector.m_hidingSpot[ which ];
  175. }
  176. // no sniping spots at all.. fall through and pick a normal hiding spot
  177. }
  178. }
  179. // collect hiding spots with decent "cover"
  180. CollectHidingSpotsFunctor collector( me, pos, maxRange, HidingSpot::IN_COVER );
  181. SearchSurroundingAreas( startArea, pos, collector, maxRange );
  182. if (collector.m_count == 0)
  183. {
  184. // no hiding spots at all - if we're not a sniper, try to find a sniper spot to use instead
  185. if (!isSniper)
  186. {
  187. return FindNearbyHidingSpot( me, pos, maxRange, true, useNearest );
  188. }
  189. return NULL;
  190. }
  191. if (useNearest)
  192. {
  193. // return closest hiding spot
  194. const Vector *closest = NULL;
  195. float closeRangeSq = 9999999999.9f;
  196. for( int i=0; i<collector.m_count; ++i )
  197. {
  198. float rangeSq = (*collector.m_hidingSpot[i] - pos).LengthSqr();
  199. if (rangeSq < closeRangeSq)
  200. {
  201. closeRangeSq = rangeSq;
  202. closest = collector.m_hidingSpot[i];
  203. }
  204. }
  205. return closest;
  206. }
  207. // select a hiding spot at random
  208. int which = collector.GetRandomHidingSpot();
  209. return collector.m_hidingSpot[ which ];
  210. }
  211. //--------------------------------------------------------------------------------------------------------------
  212. /**
  213. * Select a random hiding spot among the nav areas that are tagged with the given place
  214. */
  215. const Vector *FindRandomHidingSpot( CBaseEntity *me, Place place, bool isSniper )
  216. {
  217. // collect set of nearby hiding spots
  218. if (isSniper)
  219. {
  220. CollectHidingSpotsFunctor collector( me, me->GetAbsOrigin(), -1.0f, HidingSpot::IDEAL_SNIPER_SPOT, place );
  221. TheNavMesh->ForAllAreas( collector );
  222. if (collector.m_count)
  223. {
  224. int which = RandomInt( 0, collector.m_count-1 );
  225. return collector.m_hidingSpot[ which ];
  226. }
  227. else
  228. {
  229. // no ideal sniping spots, look for "good" sniping spots
  230. CollectHidingSpotsFunctor collector( me, me->GetAbsOrigin(), -1.0f, HidingSpot::GOOD_SNIPER_SPOT, place );
  231. TheNavMesh->ForAllAreas( collector );
  232. if (collector.m_count)
  233. {
  234. int which = RandomInt( 0, collector.m_count-1 );
  235. return collector.m_hidingSpot[ which ];
  236. }
  237. // no sniping spots at all.. fall through and pick a normal hiding spot
  238. }
  239. }
  240. // collect hiding spots with decent "cover"
  241. CollectHidingSpotsFunctor collector( me, me->GetAbsOrigin(), -1.0f, HidingSpot::IN_COVER, place );
  242. TheNavMesh->ForAllAreas( collector );
  243. if (collector.m_count == 0)
  244. return NULL;
  245. // select a hiding spot at random
  246. int which = RandomInt( 0, collector.m_count-1 );
  247. return collector.m_hidingSpot[ which ];
  248. }
  249. //--------------------------------------------------------------------------------------------------------------------
  250. /**
  251. * Select a nearby retreat spot.
  252. * Don't pick a hiding spot that a Player is currently occupying.
  253. * If "avoidTeam" is nonzero, avoid getting close to members of that team.
  254. */
  255. const Vector *FindNearbyRetreatSpot( CBaseEntity *me, const Vector &start, float maxRange, int avoidTeam )
  256. {
  257. CNavArea *startArea = TheNavMesh->GetNearestNavArea( start );
  258. if (startArea == NULL)
  259. return NULL;
  260. // collect hiding spots with decent "cover"
  261. CollectHidingSpotsFunctor collector( me, start, maxRange, HidingSpot::IN_COVER );
  262. SearchSurroundingAreas( startArea, start, collector, maxRange );
  263. if (collector.m_count == 0)
  264. return NULL;
  265. // find the closest unoccupied hiding spot that crosses the least lines of fire and has the best cover
  266. for( int i=0; i<collector.m_count; ++i )
  267. {
  268. // check if we would have to cross a line of fire to reach this hiding spot
  269. if (IsCrossingLineOfFire( start, *collector.m_hidingSpot[i], me ))
  270. {
  271. collector.RemoveSpot( i );
  272. // back up a step, so iteration won't skip a spot
  273. --i;
  274. continue;
  275. }
  276. // check if there is someone on the avoidTeam near this hiding spot
  277. if (avoidTeam)
  278. {
  279. float range;
  280. if (UTIL_GetClosestPlayer( *collector.m_hidingSpot[i], avoidTeam, &range ))
  281. {
  282. const float dangerRange = 150.0f;
  283. if (range < dangerRange)
  284. {
  285. // there is an avoidable player too near this spot - remove it
  286. collector.RemoveSpot( i );
  287. // back up a step, so iteration won't skip a spot
  288. --i;
  289. continue;
  290. }
  291. }
  292. }
  293. }
  294. if (collector.m_count <= 0)
  295. return NULL;
  296. // all remaining spots are ok - pick one at random
  297. int which = RandomInt( 0, collector.m_count-1 );
  298. return collector.m_hidingSpot[ which ];
  299. }
  300. //--------------------------------------------------------------------------------------------------------------------
  301. /**
  302. * Functor to collect all hiding spots in range that we can reach before the enemy arrives.
  303. * NOTE: This only works for the initial rush.
  304. */
  305. class CollectArriveFirstSpotsFunctor
  306. {
  307. public:
  308. CollectArriveFirstSpotsFunctor( CBaseEntity *me, const Vector &searchOrigin, float enemyArriveTime, float range, int flags ) : m_searchOrigin( searchOrigin )
  309. {
  310. m_me = me;
  311. m_count = 0;
  312. m_range = range;
  313. m_flags = (unsigned char)flags;
  314. m_enemyArriveTime = enemyArriveTime;
  315. }
  316. enum { MAX_SPOTS = 256 };
  317. bool operator() ( CNavArea *area )
  318. {
  319. const HidingSpotVector *pSpots = area->GetHidingSpots();
  320. FOR_EACH_VEC( (*pSpots), it )
  321. {
  322. const HidingSpot *spot = (*pSpots)[ it ];
  323. // make sure hiding spot is in range
  324. if (m_range > 0.0f)
  325. {
  326. if ((spot->GetPosition() - m_searchOrigin).IsLengthGreaterThan( m_range ))
  327. {
  328. continue;
  329. }
  330. }
  331. // if a Player is using this hiding spot, don't consider it
  332. if (IsSpotOccupied( m_me, spot->GetPosition() ))
  333. {
  334. // player is in hiding spot
  335. /// @todo Check if player is moving or sitting still
  336. continue;
  337. }
  338. // only collect hiding spots with matching flags
  339. if (!(m_flags & spot->GetFlags()))
  340. {
  341. continue;
  342. }
  343. // only collect this hiding spot if we can reach it before the enemy arrives
  344. // NOTE: This assumes the area is fairly small and the difference of moving to the corner vs the center is small
  345. const float settleTime = 1.0f;
  346. if(!spot->GetArea())
  347. {
  348. AssertMsg(false, "Check console spew for Hiding Spot off the Nav Mesh errors.");
  349. continue;
  350. }
  351. if (spot->GetArea()->GetEarliestOccupyTime( m_me->GetTeamNumber() ) + settleTime < m_enemyArriveTime)
  352. {
  353. m_hidingSpot[ m_count++ ] = spot;
  354. }
  355. }
  356. // if we've filled up, stop searching
  357. if (m_count == MAX_SPOTS)
  358. return false;
  359. return true;
  360. }
  361. CBaseEntity *m_me;
  362. const Vector &m_searchOrigin;
  363. float m_range;
  364. float m_enemyArriveTime;
  365. unsigned char m_flags;
  366. const HidingSpot *m_hidingSpot[ MAX_SPOTS ];
  367. int m_count;
  368. };
  369. /**
  370. * Select a hiding spot that we can reach before the enemy arrives.
  371. * NOTE: This only works for the initial rush.
  372. */
  373. const HidingSpot *FindInitialEncounterSpot( CBaseEntity *me, const Vector &searchOrigin, float enemyArriveTime, float maxRange, bool isSniper )
  374. {
  375. CNavArea *startArea = TheNavMesh->GetNearestNavArea( searchOrigin );
  376. if (startArea == NULL)
  377. return NULL;
  378. // collect set of nearby hiding spots
  379. if (isSniper)
  380. {
  381. CollectArriveFirstSpotsFunctor collector( me, searchOrigin, enemyArriveTime, maxRange, HidingSpot::IDEAL_SNIPER_SPOT );
  382. SearchSurroundingAreas( startArea, searchOrigin, collector, maxRange );
  383. if (collector.m_count)
  384. {
  385. int which = RandomInt( 0, collector.m_count-1 );
  386. return collector.m_hidingSpot[ which ];
  387. }
  388. else
  389. {
  390. // no ideal sniping spots, look for "good" sniping spots
  391. CollectArriveFirstSpotsFunctor collector( me, searchOrigin, enemyArriveTime, maxRange, HidingSpot::GOOD_SNIPER_SPOT );
  392. SearchSurroundingAreas( startArea, searchOrigin, collector, maxRange );
  393. if (collector.m_count)
  394. {
  395. int which = RandomInt( 0, collector.m_count-1 );
  396. return collector.m_hidingSpot[ which ];
  397. }
  398. // no sniping spots at all.. fall through and pick a normal hiding spot
  399. }
  400. }
  401. // collect hiding spots with decent "cover"
  402. CollectArriveFirstSpotsFunctor collector( me, searchOrigin, enemyArriveTime, maxRange, HidingSpot::IN_COVER | HidingSpot::EXPOSED );
  403. SearchSurroundingAreas( startArea, searchOrigin, collector, maxRange );
  404. if (collector.m_count == 0)
  405. return NULL;
  406. // select a hiding spot at random
  407. int which = RandomInt( 0, collector.m_count-1 );
  408. return collector.m_hidingSpot[ which ];
  409. }