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.

402 lines
10 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. // Author: Michael S. Booth ([email protected]), 2003
  8. #include "cbase.h"
  9. #include "bot.h"
  10. #include "bot_manager.h"
  11. #include "nav_area.h"
  12. #include "bot_util.h"
  13. #include "basegrenade_shared.h"
  14. #include "cs_bot.h"
  15. #include "tier0/vprof.h"
  16. // memdbgon must be the last include file in a .cpp file!!!
  17. #include "tier0/memdbgon.h"
  18. float g_BotUpkeepInterval = 0.0f;
  19. float g_BotUpdateInterval = 0.0f;
  20. //--------------------------------------------------------------------------------------------------------------
  21. CBotManager::CBotManager()
  22. {
  23. InitBotTrig();
  24. }
  25. //--------------------------------------------------------------------------------------------------------------
  26. CBotManager::~CBotManager()
  27. {
  28. }
  29. //--------------------------------------------------------------------------------------------------------------
  30. /**
  31. * Invoked when the round is restarting
  32. */
  33. void CBotManager::RestartRound( void )
  34. {
  35. DestroyAllGrenades();
  36. ClearDebugMessages();
  37. }
  38. //--------------------------------------------------------------------------------------------------------------
  39. /**
  40. * Invoked at the start of each frame
  41. */
  42. void CBotManager::StartFrame( void )
  43. {
  44. VPROF_BUDGET( "CBotManager::StartFrame", VPROF_BUDGETGROUP_NPCS );
  45. ValidateActiveGrenades();
  46. // debug smoke grenade visualization
  47. if (cv_bot_debug.GetInt() == 5)
  48. {
  49. Vector edge, lastEdge;
  50. FOR_EACH_LL( m_activeGrenadeList, it )
  51. {
  52. ActiveGrenade *ag = m_activeGrenadeList[ it ];
  53. const Vector &pos = ag->GetDetonationPosition();
  54. UTIL_DrawBeamPoints( pos, pos + Vector( 0, 0, 50 ), 1, 255, 100, 0 );
  55. lastEdge = Vector( ag->GetRadius() + pos.x, pos.y, pos.z );
  56. float angle;
  57. for( angle=0.0f; angle <= 180.0f; angle += 22.5f )
  58. {
  59. edge.x = ag->GetRadius() * BotCOS( angle ) + pos.x;
  60. edge.y = pos.y;
  61. edge.z = ag->GetRadius() * BotSIN( angle ) + pos.z;
  62. UTIL_DrawBeamPoints( edge, lastEdge, 1, 255, 50, 0 );
  63. lastEdge = edge;
  64. }
  65. lastEdge = Vector( pos.x, ag->GetRadius() + pos.y, pos.z );
  66. for( angle=0.0f; angle <= 180.0f; angle += 22.5f )
  67. {
  68. edge.x = pos.x;
  69. edge.y = ag->GetRadius() * BotCOS( angle ) + pos.y;
  70. edge.z = ag->GetRadius() * BotSIN( angle ) + pos.z;
  71. UTIL_DrawBeamPoints( edge, lastEdge, 1, 255, 50, 0 );
  72. lastEdge = edge;
  73. }
  74. }
  75. }
  76. // set frame duration
  77. g_BotUpkeepInterval = m_frameTimer.GetElapsedTime();
  78. m_frameTimer.Start();
  79. g_BotUpdateInterval = (g_BotUpdateSkipCount+1) * g_BotUpkeepInterval;
  80. //
  81. // Process each active bot
  82. //
  83. for( int i = 1; i <= gpGlobals->maxClients; ++i )
  84. {
  85. CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
  86. if (!player)
  87. continue;
  88. // Hack for now so the temp bot code works. The temp bots are very useful for debugging
  89. // because they can be setup to mimic the player's usercmds.
  90. if (player->IsBot() && IsEntityValid( player ) )
  91. {
  92. // EVIL: Messes up vtables
  93. //CBot< CBasePlayer > *bot = static_cast< CBot< CBasePlayer > * >( player );
  94. CCSBot *bot = dynamic_cast< CCSBot * >( player );
  95. if ( bot )
  96. {
  97. bot->Upkeep();
  98. if (((gpGlobals->tickcount + bot->entindex()) % g_BotUpdateSkipCount) == 0)
  99. {
  100. bot->ResetCommand();
  101. bot->Update();
  102. }
  103. bot->UpdatePlayer();
  104. }
  105. }
  106. }
  107. }
  108. //--------------------------------------------------------------------------------------------------------------
  109. /**
  110. * Add an active grenade to the bot's awareness
  111. */
  112. void CBotManager::AddGrenade( CBaseGrenade *grenade )
  113. {
  114. ActiveGrenade *ag = new ActiveGrenade( grenade );
  115. m_activeGrenadeList.AddToTail( ag );
  116. }
  117. //--------------------------------------------------------------------------------------------------------------
  118. /**
  119. * The grenade entity in the world is going away
  120. */
  121. void CBotManager::RemoveGrenade( CBaseGrenade *grenade )
  122. {
  123. FOR_EACH_LL( m_activeGrenadeList, it )
  124. {
  125. ActiveGrenade *ag = m_activeGrenadeList[ it ];
  126. if (ag->IsEntity( grenade ))
  127. {
  128. ag->OnEntityGone();
  129. return;
  130. }
  131. }
  132. }
  133. //--------------------------------------------------------------------------------------------------------------
  134. /**
  135. * The grenade entity has changed its radius
  136. */
  137. void CBotManager::SetGrenadeRadius( CBaseGrenade *grenade, float radius )
  138. {
  139. FOR_EACH_LL( m_activeGrenadeList, it )
  140. {
  141. ActiveGrenade *ag = m_activeGrenadeList[ it ];
  142. if (ag->IsEntity( grenade ))
  143. {
  144. ag->SetRadius( radius );
  145. return;
  146. }
  147. }
  148. }
  149. //--------------------------------------------------------------------------------------------------------------
  150. /**
  151. * Destroy any invalid active grenades
  152. */
  153. void CBotManager::ValidateActiveGrenades( void )
  154. {
  155. int it = m_activeGrenadeList.Head();
  156. while( it != m_activeGrenadeList.InvalidIndex() )
  157. {
  158. ActiveGrenade *ag = m_activeGrenadeList[ it ];
  159. int current = it;
  160. it = m_activeGrenadeList.Next( it );
  161. // lazy validation
  162. if (!ag->IsValid())
  163. {
  164. m_activeGrenadeList.Remove( current );
  165. delete ag;
  166. continue;
  167. }
  168. else
  169. {
  170. ag->Update();
  171. }
  172. }
  173. }
  174. //--------------------------------------------------------------------------------------------------------------
  175. void CBotManager::DestroyAllGrenades( void )
  176. {
  177. m_activeGrenadeList.PurgeAndDeleteElements();
  178. }
  179. //--------------------------------------------------------------------------------------------------------------
  180. /**
  181. * Return true if position is inside a smoke cloud
  182. */
  183. bool CBotManager::IsInsideSmokeCloud( const Vector *pos )
  184. {
  185. int it = m_activeGrenadeList.Head();
  186. while( it != m_activeGrenadeList.InvalidIndex() )
  187. {
  188. ActiveGrenade *ag = m_activeGrenadeList[ it ];
  189. int current = it;
  190. it = m_activeGrenadeList.Next( it );
  191. // lazy validation
  192. if (!ag->IsValid())
  193. {
  194. m_activeGrenadeList.Remove( current );
  195. delete ag;
  196. continue;
  197. }
  198. if (ag->IsSmoke())
  199. {
  200. const Vector &smokeOrigin = ag->GetDetonationPosition();
  201. if ((smokeOrigin - *pos).IsLengthLessThan( ag->GetRadius() ))
  202. return true;
  203. }
  204. }
  205. return false;
  206. }
  207. //--------------------------------------------------------------------------------------------------------------
  208. /**
  209. * Return true if line intersects smoke volume
  210. * Determine the length of the line of sight covered by each smoke cloud,
  211. * and sum them (overlap is additive for obstruction).
  212. * If the overlap exceeds the threshold, the bot can't see through.
  213. */
  214. bool CBotManager::IsLineBlockedBySmoke( const Vector &from, const Vector &to, float grenadeBloat )
  215. {
  216. VPROF_BUDGET( "CBotManager::IsLineBlockedBySmoke", VPROF_BUDGETGROUP_NPCS );
  217. float totalSmokedLength = 0.0f; // distance along line of sight covered by smoke
  218. // compute unit vector and length of line of sight segment
  219. Vector sightDir = to - from;
  220. float sightLength = sightDir.NormalizeInPlace();
  221. FOR_EACH_LL( m_activeGrenadeList, it )
  222. {
  223. ActiveGrenade *ag = m_activeGrenadeList[ it ];
  224. const float smokeRadiusSq = ag->GetRadius() * ag->GetRadius() * grenadeBloat * grenadeBloat;
  225. if (ag->IsSmoke())
  226. {
  227. const Vector &smokeOrigin = ag->GetDetonationPosition();
  228. Vector toGrenade = smokeOrigin - from;
  229. float alongDist = DotProduct( toGrenade, sightDir );
  230. // compute closest point to grenade along line of sight ray
  231. Vector close;
  232. // constrain closest point to line segment
  233. if (alongDist < 0.0f)
  234. close = from;
  235. else if (alongDist >= sightLength)
  236. close = to;
  237. else
  238. close = from + sightDir * alongDist;
  239. // if closest point is within smoke radius, the line overlaps the smoke cloud
  240. Vector toClose = close - smokeOrigin;
  241. float lengthSq = toClose.LengthSqr();
  242. if (lengthSq < smokeRadiusSq)
  243. {
  244. // some portion of the ray intersects the cloud
  245. float fromSq = toGrenade.LengthSqr();
  246. float toSq = (smokeOrigin - to).LengthSqr();
  247. if (fromSq < smokeRadiusSq)
  248. {
  249. if (toSq < smokeRadiusSq)
  250. {
  251. // both 'from' and 'to' lie within the cloud
  252. // entire length is smoked
  253. totalSmokedLength += (to - from).Length();
  254. }
  255. else
  256. {
  257. // 'from' is inside the cloud, 'to' is outside
  258. // compute half of total smoked length as if ray crosses entire cloud chord
  259. float halfSmokedLength = (float)sqrt( smokeRadiusSq - lengthSq );
  260. if (alongDist > 0.0f)
  261. {
  262. // ray goes thru 'close'
  263. totalSmokedLength += halfSmokedLength + (close - from).Length();
  264. }
  265. else
  266. {
  267. // ray starts after 'close'
  268. totalSmokedLength += halfSmokedLength - (close - from).Length();
  269. }
  270. }
  271. }
  272. else if (toSq < smokeRadiusSq)
  273. {
  274. // 'from' is outside the cloud, 'to' is inside
  275. // compute half of total smoked length as if ray crosses entire cloud chord
  276. float halfSmokedLength = (float)sqrt( smokeRadiusSq - lengthSq );
  277. Vector v = to - smokeOrigin;
  278. if (DotProduct( v, sightDir ) > 0.0f)
  279. {
  280. // ray goes thru 'close'
  281. totalSmokedLength += halfSmokedLength + (close - to).Length();
  282. }
  283. else
  284. {
  285. // ray ends before 'close'
  286. totalSmokedLength += halfSmokedLength - (close - to).Length();
  287. }
  288. }
  289. else
  290. {
  291. // 'from' and 'to' lie outside of the cloud - the line of sight completely crosses it
  292. // determine the length of the chord that crosses the cloud
  293. float smokedLength = 2.0f * (float)sqrt( smokeRadiusSq - lengthSq );
  294. totalSmokedLength += smokedLength;
  295. }
  296. }
  297. }
  298. }
  299. // define how much smoke a bot can see thru
  300. const float maxSmokedLength = 0.7f * SmokeGrenadeRadius;
  301. // return true if the total length of smoke-covered line-of-sight is too much
  302. return (totalSmokedLength > maxSmokedLength);
  303. }
  304. //--------------------------------------------------------------------------------------------------------------
  305. void CBotManager::ClearDebugMessages( void )
  306. {
  307. m_debugMessageCount = 0;
  308. m_currentDebugMessage = -1;
  309. }
  310. //--------------------------------------------------------------------------------------------------------------
  311. /**
  312. * Add a new debug message to the message history
  313. */
  314. void CBotManager::AddDebugMessage( const char *msg )
  315. {
  316. if (++m_currentDebugMessage >= MAX_DBG_MSGS)
  317. {
  318. m_currentDebugMessage = 0;
  319. }
  320. if (m_debugMessageCount < MAX_DBG_MSGS)
  321. {
  322. ++m_debugMessageCount;
  323. }
  324. Q_strncpy( m_debugMessage[ m_currentDebugMessage ].m_string, msg, MAX_DBG_MSG_SIZE );
  325. m_debugMessage[ m_currentDebugMessage ].m_age.Start();
  326. }