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.

1956 lines
50 KiB

  1. //========= Copyright � 1996-2005, 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 "cs_bot.h"
  10. #include "datacache/imdlcache.h"
  11. #include "../../../shared/cstrike15/cs_gamerules.h"
  12. #include "mapinfo.h"
  13. // memdbgon must be the last include file in a .cpp file!!!
  14. #include "tier0/memdbgon.h"
  15. ConVar bot_max_vision_distance_override( "bot_max_vision_distance_override", "-1", FCVAR_CHEAT | FCVAR_REPLICATED, "Max distance bots can see targets.", true, -1, false, 0 );
  16. ConVar bot_ignore_players( "bot_ignore_players", "0", FCVAR_CHEAT, "Bots will not see non-bot players." );
  17. ConVar bot_coop_idle_max_vision_distance( "bot_coop_idle_max_vision_distance", "1400", FCVAR_CHEAT | FCVAR_REPLICATED, "Max distance bots can see targets (in coop) when they are idle, dormant, hiding or asleep.", true, -1, false, 0 );
  18. //--------------------------------------------------------------------------------------------------------------
  19. /**
  20. * Used to update view angles to stay on a ladder
  21. */
  22. inline float StayOnLadderLine( CCSBot *me, const CNavLadder *ladder )
  23. {
  24. // determine our facing
  25. NavDirType faceDir = AngleToDirection( me->EyeAngles().y );
  26. const float stiffness = 1.0f;
  27. // move toward ladder mount point
  28. switch( faceDir )
  29. {
  30. case NORTH:
  31. return stiffness * (ladder->m_top.x - me->GetAbsOrigin().x);
  32. case SOUTH:
  33. return -stiffness * (ladder->m_top.x - me->GetAbsOrigin().x);
  34. case WEST:
  35. return -stiffness * (ladder->m_top.y - me->GetAbsOrigin().y);
  36. case EAST:
  37. return stiffness * (ladder->m_top.y - me->GetAbsOrigin().y);
  38. }
  39. return 0.0f;
  40. }
  41. //--------------------------------------------------------------------------------------------------------------
  42. void CCSBot::ComputeLadderAngles( float *yaw, float *pitch )
  43. {
  44. if ( !yaw || !pitch )
  45. return;
  46. Vector myOrigin = GetCentroid( this );
  47. // set yaw to aim at ladder
  48. Vector to = m_pathLadder->GetPosAtHeight(myOrigin.z) - myOrigin;
  49. float idealYaw = UTIL_VecToYaw( to );
  50. Vector faceDir = (m_pathLadderFaceIn) ? -m_pathLadder->GetNormal() : m_pathLadder->GetNormal();
  51. QAngle faceAngles;
  52. VectorAngles( faceDir, faceAngles );
  53. const float lookAlongLadderRange = 50.0f;
  54. const float ladderPitchUpApproach = -30.0f;
  55. const float ladderPitchUpTraverse = -60.0f; // -80
  56. const float ladderPitchDownApproach = 0.0f;
  57. const float ladderPitchDownTraverse = 80.0f;
  58. // adjust pitch to look up/down ladder as we ascend/descend
  59. switch( m_pathLadderState )
  60. {
  61. case APPROACH_ASCENDING_LADDER:
  62. {
  63. Vector to = m_goalPosition - myOrigin;
  64. *yaw = idealYaw;
  65. if (to.IsLengthLessThan( lookAlongLadderRange ))
  66. *pitch = ladderPitchUpApproach;
  67. break;
  68. }
  69. case APPROACH_DESCENDING_LADDER:
  70. {
  71. Vector to = m_goalPosition - myOrigin;
  72. *yaw = idealYaw;
  73. if (to.IsLengthLessThan( lookAlongLadderRange ))
  74. *pitch = ladderPitchDownApproach;
  75. break;
  76. }
  77. case FACE_ASCENDING_LADDER:
  78. if ( m_pathLadderDismountDir == LEFT )
  79. {
  80. *yaw = AngleNormalizePositive( idealYaw + 90.0f );
  81. }
  82. else if ( m_pathLadderDismountDir == RIGHT )
  83. {
  84. *yaw = AngleNormalizePositive( idealYaw - 90.0f );
  85. }
  86. else
  87. {
  88. *yaw = idealYaw;
  89. }
  90. *pitch = ladderPitchUpApproach;
  91. break;
  92. case FACE_DESCENDING_LADDER:
  93. *yaw = idealYaw;
  94. *pitch = ladderPitchDownApproach;
  95. break;
  96. case MOUNT_ASCENDING_LADDER:
  97. case ASCEND_LADDER:
  98. if ( m_pathLadderDismountDir == LEFT )
  99. {
  100. *yaw = AngleNormalizePositive( idealYaw + 90.0f );
  101. }
  102. else if ( m_pathLadderDismountDir == RIGHT )
  103. {
  104. *yaw = AngleNormalizePositive( idealYaw - 90.0f );
  105. }
  106. else
  107. {
  108. *yaw = faceAngles[ YAW ] + StayOnLadderLine( this, m_pathLadder );
  109. }
  110. *pitch = ( m_pathLadderState == ASCEND_LADDER ) ? ladderPitchUpTraverse : ladderPitchUpApproach;
  111. break;
  112. case MOUNT_DESCENDING_LADDER:
  113. case DESCEND_LADDER:
  114. *yaw = faceAngles[ YAW ] + StayOnLadderLine( this, m_pathLadder );
  115. *pitch = ( m_pathLadderState == DESCEND_LADDER ) ? ladderPitchDownTraverse : ladderPitchDownApproach;
  116. break;
  117. case DISMOUNT_ASCENDING_LADDER:
  118. if ( m_pathLadderDismountDir == LEFT )
  119. {
  120. *yaw = AngleNormalizePositive( faceAngles[ YAW ] + 90.0f );
  121. }
  122. else if ( m_pathLadderDismountDir == RIGHT )
  123. {
  124. *yaw = AngleNormalizePositive( faceAngles[ YAW ] - 90.0f );
  125. }
  126. else
  127. {
  128. *yaw = faceAngles[ YAW ];
  129. }
  130. break;
  131. case DISMOUNT_DESCENDING_LADDER:
  132. *yaw = faceAngles[ YAW ];
  133. break;
  134. }
  135. }
  136. //--------------------------------------------------------------------------------------------------------------
  137. /**
  138. * Move actual view angles towards desired ones.
  139. * This is the only place v_angle is altered.
  140. * @todo Make stiffness and turn rate constants timestep invariant.
  141. */
  142. void CCSBot::UpdateLookAngles( void )
  143. {
  144. VPROF_BUDGET( "CCSBot::UpdateLookAngles", VPROF_BUDGETGROUP_NPCS );
  145. const float deltaT = g_BotUpkeepInterval;
  146. float maxAccel;
  147. float stiffness;
  148. float damping;
  149. // If mimicing the player, don't modify the view angles.
  150. if ( bot_mimic.GetInt() )
  151. return;
  152. // springs are stiffer when attacking, so we can track and move between targets better
  153. if (IsAttacking())
  154. {
  155. stiffness = GetProfile()->GetLookAngleStiffnessAttacking();
  156. damping = GetProfile()->GetLookAngleDampingAttacking();
  157. maxAccel = GetProfile()->GetLookAngleMaxAccelerationAttacking();
  158. }
  159. else
  160. {
  161. stiffness = GetProfile()->GetLookAngleStiffnessNormal();
  162. damping = GetProfile()->GetLookAngleDampingNormal();
  163. maxAccel = GetProfile()->GetLookAngleMaxAccelerationNormal();
  164. }
  165. // these may be overridden by ladder logic
  166. float useYaw = m_lookYaw;
  167. float usePitch = m_lookPitch;
  168. //
  169. // Ladders require precise movement, therefore we need to look at the
  170. // ladder as we approach and ascend/descend it.
  171. // If we are on a ladder, we need to look up or down to traverse it - override pitch in this case.
  172. //
  173. // If we're trying to break something, though, we actually need to look at it before we can
  174. // look at the ladder
  175. //
  176. if ( IsUsingLadder() && !(IsLookingAtSpot( PRIORITY_HIGH ) && m_lookAtSpotAttack) )
  177. {
  178. ComputeLadderAngles( &useYaw, &usePitch );
  179. }
  180. // get current view angles
  181. QAngle viewAngles = EyeAngles();
  182. //
  183. // Yaw
  184. //
  185. float angleDiff = AngleNormalize( useYaw - viewAngles.y );
  186. /*
  187. * m_forwardAngle is unreliable. Need to simulate mouse sliding & centering
  188. if (!IsAttacking())
  189. {
  190. // do not allow rotation through our reverse facing angle - go the "long" way instead
  191. float toCurrent = AngleNormalize( pev->v_angle.y - m_forwardAngle );
  192. float toDesired = AngleNormalize( useYaw - m_forwardAngle );
  193. // if angle differences are different signs, they cross the forward facing
  194. if (toCurrent * toDesired < 0.0f)
  195. {
  196. // if the sum of the angles is greater than 180, turn the "long" way around
  197. if (abs( toCurrent - toDesired ) >= 180.0f)
  198. {
  199. if (angleDiff > 0.0f)
  200. angleDiff -= 360.0f;
  201. else
  202. angleDiff += 360.0f;
  203. }
  204. }
  205. }
  206. */
  207. // if almost at target angle, snap to it
  208. const float onTargetTolerance = 0; // 1.0f; // 3
  209. if ( fabsf(angleDiff) < onTargetTolerance )
  210. {
  211. m_lookYawVel = 0.0f;
  212. viewAngles.y = useYaw;
  213. }
  214. else
  215. {
  216. // simple angular spring/damper
  217. float accel = stiffness * angleDiff - damping * m_lookYawVel;
  218. // limit rate
  219. if (accel > maxAccel)
  220. accel = maxAccel;
  221. else if (accel < -maxAccel)
  222. accel = -maxAccel;
  223. m_lookYawVel += deltaT * accel;
  224. viewAngles.y += deltaT * m_lookYawVel;
  225. // keep track of how long our view remains steady
  226. const float steadyYaw = 1000.0f;
  227. if (fabs( accel ) > steadyYaw)
  228. {
  229. m_viewSteadyTimer.Start();
  230. }
  231. }
  232. //
  233. // Pitch
  234. // Actually, this is negative pitch.
  235. //
  236. angleDiff = usePitch - viewAngles.x;
  237. angleDiff = AngleNormalize( angleDiff );
  238. if ( false && fabsf(angleDiff) < onTargetTolerance )
  239. {
  240. m_lookPitchVel = 0.0f;
  241. viewAngles.x = usePitch;
  242. }
  243. else
  244. {
  245. // simple angular spring/damper
  246. // double the stiffness since pitch is only +/- 90 and yaw is +/- 180
  247. float accel = 2.0f * stiffness * angleDiff - damping * m_lookPitchVel;
  248. // limit rate
  249. if (accel > maxAccel)
  250. accel = maxAccel;
  251. else if (accel < -maxAccel)
  252. accel = -maxAccel;
  253. m_lookPitchVel += deltaT * accel;
  254. viewAngles.x += deltaT * m_lookPitchVel;
  255. // keep track of how long our view remains steady
  256. const float steadyPitch = 1000.0f;
  257. if (fabs( accel ) > steadyPitch)
  258. {
  259. m_viewSteadyTimer.Start();
  260. }
  261. }
  262. //PrintIfWatched( "yawVel = %g, pitchVel = %g\n", m_lookYawVel, m_lookPitchVel );
  263. // limit range - avoid gimbal lock
  264. if (viewAngles.x < -89.0f)
  265. viewAngles.x = -89.0f;
  266. else if (viewAngles.x > 89.0f)
  267. viewAngles.x = 89.0f;
  268. // update view angles
  269. SnapEyeAngles( viewAngles );
  270. // if our weapon is zooming, our view is not steady
  271. if (IsWaitingForZoom())
  272. {
  273. m_viewSteadyTimer.Start();
  274. }
  275. }
  276. //--------------------------------------------------------------------------------------------------------------
  277. /**
  278. * Return true if we can see the point
  279. */
  280. bool CCSBot::IsVisible( const Vector &pos, bool testFOV, const CBaseEntity *ignore ) const
  281. {
  282. VPROF_BUDGET( "CCSBot::IsVisible( pos )", VPROF_BUDGETGROUP_NPCS );
  283. SNPROF( "CCSBot::IsVisible( pos )");
  284. // we can't see anything if we're blind
  285. if (IsBlind())
  286. return false;
  287. // is it in my general viewcone?
  288. if (testFOV && !(const_cast<CCSBot *>(this)->FInViewCone( pos )))
  289. return false;
  290. // check line of sight against smoke
  291. if (TheCSBots()->IsLineBlockedBySmoke( EyePositionConst(), pos ))
  292. return false;
  293. // check line of sight
  294. // Must include CONTENTS_MONSTER to pick up all non-brush objects like barrels
  295. trace_t result;
  296. CTraceFilterNoNPCsOrPlayer traceFilter( ignore, COLLISION_GROUP_NONE );
  297. UTIL_TraceLine( EyePositionConst(), pos, MASK_VISIBLE_AND_NPCS|CONTENTS_BLOCKLOS, &traceFilter, &result );
  298. if (result.fraction != 1.0f)
  299. return false;
  300. return true;
  301. }
  302. bool CCSBot::IsBeyondBotMaxVisionDistance(const Vector &vecTargetPosition) const
  303. {
  304. if ( CSGameRules() && CSGameRules()->IsPlayingCoopMission() )
  305. {
  306. bool bIsIdling = m_bIsSleeping || IsHiding() || IsIdling();
  307. if ( GetTask() == CCSBot::SNIPING )
  308. bIsIdling = false; // Snipers need longer vision distance
  309. if ( bIsIdling || ( g_pMapInfo && g_pMapInfo->m_flBotMaxVisionDistance >= 0 ) )
  310. {
  311. float flBotMaxVisionDistance = 3600;
  312. float flMapMaxDist = g_pMapInfo ? g_pMapInfo->m_flBotMaxVisionDistance : 0;
  313. float flIdleMaxDist = bot_coop_idle_max_vision_distance.GetFloat();
  314. if ( bIsIdling )
  315. {
  316. // use the lower one
  317. if ( flMapMaxDist > 0 )
  318. flBotMaxVisionDistance = (flMapMaxDist < flIdleMaxDist) ? flMapMaxDist : flIdleMaxDist;
  319. else
  320. flBotMaxVisionDistance = flIdleMaxDist;
  321. }
  322. else if ( g_pMapInfo && g_pMapInfo->m_flBotMaxVisionDistance >= 0 )
  323. {
  324. flBotMaxVisionDistance = g_pMapInfo->m_flBotMaxVisionDistance;
  325. }
  326. float flEnemyDistance = ( EyePositionConst() - vecTargetPosition ).Length();
  327. return ( flEnemyDistance > flBotMaxVisionDistance );
  328. }
  329. return false;
  330. }
  331. else
  332. {
  333. if ( !g_pMapInfo || g_pMapInfo->m_flBotMaxVisionDistance < 0 )
  334. return false;
  335. const float flBotMaxVisionDistance = ( bot_max_vision_distance_override.GetFloat() > 0 ) ? bot_max_vision_distance_override.GetFloat() : g_pMapInfo->m_flBotMaxVisionDistance;
  336. float flEnemyDistance = ( EyePositionConst() - vecTargetPosition ).Length();
  337. return ( flEnemyDistance > flBotMaxVisionDistance );
  338. }
  339. }
  340. //--------------------------------------------------------------------------------------------------------------
  341. /**
  342. * Return true if we can see any part of the player
  343. * Check parts in order of importance. Return the first part seen in "visPart" if it is non-NULL.
  344. */
  345. // We only send back new data every 5 upsates (ie every 1/3 second)
  346. bool CCSBot::IsVisible( CCSPlayer *player, bool testFOV, unsigned char *visParts ) const
  347. {
  348. VPROF_BUDGET( "CCSBot::IsVisible( player )", VPROF_BUDGETGROUP_NPCS );
  349. SNPROF( "CCSBot::IsVisible( player )");
  350. if ( bot_ignore_players.GetBool() && !player->IsBot() )
  351. return false;
  352. // optimization - assume if center is not in FOV, nothing is
  353. // we're using WorldSpaceCenter instead of GUT so we can skip GetPartPosition below - that's
  354. // the most expensive part of this, and if we can skip it, so much the better.
  355. if (testFOV && !(const_cast<CCSBot *>(this)->FInViewCone( player->WorldSpaceCenter() )))
  356. {
  357. return false;
  358. }
  359. //don't let bots see farther than the map-defined bot max vision distance
  360. if ( IsBeyondBotMaxVisionDistance( player->WorldSpaceCenter() ) )
  361. {
  362. return false;
  363. }
  364. unsigned char testVisParts = NONE;
  365. // check gut
  366. Vector partPos = GetPartPosition( player, GUT );
  367. // finish gut check
  368. if (IsVisible( partPos, testFOV ))
  369. {
  370. if (visParts == NULL)
  371. return true;
  372. testVisParts |= GUT;
  373. }
  374. // check top of head
  375. partPos = GetPartPosition( player, HEAD );
  376. if (IsVisible( partPos, testFOV ))
  377. {
  378. if (visParts == NULL)
  379. return true;
  380. testVisParts |= HEAD;
  381. }
  382. // check feet
  383. partPos = GetPartPosition( player, FEET );
  384. if (IsVisible( partPos, testFOV ))
  385. {
  386. if (visParts == NULL)
  387. return true;
  388. testVisParts |= FEET;
  389. }
  390. // check "edges"
  391. partPos = GetPartPosition( player, LEFT_SIDE );
  392. if (IsVisible( partPos, testFOV ))
  393. {
  394. if (visParts == NULL)
  395. return true;
  396. testVisParts |= LEFT_SIDE;
  397. }
  398. partPos = GetPartPosition( player, RIGHT_SIDE );
  399. if (IsVisible( partPos, testFOV ))
  400. {
  401. if (visParts == NULL)
  402. return true;
  403. testVisParts |= RIGHT_SIDE;
  404. }
  405. if (visParts)
  406. *visParts = testVisParts;
  407. if (testVisParts)
  408. return true;
  409. return false;
  410. }
  411. //--------------------------------------------------------------------------------------------------------------
  412. /**
  413. * Interesting part positions
  414. */
  415. CCSBot::PartInfo CCSBot::m_partInfo[ MAX_PLAYERS ];
  416. //--------------------------------------------------------------------------------------------------------------
  417. /**
  418. * Compute part positions from bone location.
  419. */
  420. void CCSBot::ComputePartPositions( CCSPlayer *player )
  421. {
  422. const int headBox = 12;
  423. const int gutBox = 9;
  424. const int leftElbowBox = 14;
  425. const int rightElbowBox = 17;
  426. //const int hipBox = 0;
  427. //const int leftFootBox = 4;
  428. //const int rightFootBox = 8;
  429. const int maxBoxIndex = rightElbowBox;
  430. VPROF_BUDGET( "CCSBot::ComputePartPositions", VPROF_BUDGETGROUP_NPCS );
  431. SNPROF( "CCSBot::ComputePartPositions" );
  432. // which PartInfo corresponds to the given player
  433. PartInfo *info = &m_partInfo[ player->entindex() % MAX_PLAYERS ];
  434. // always compute feet, since it doesn't rely on bones
  435. info->m_feetPos = player->GetAbsOrigin();
  436. info->m_feetPos.z += 5.0f;
  437. // get bone positions for interesting points on the player
  438. MDLCACHE_CRITICAL_SECTION();
  439. CStudioHdr *studioHdr = player->GetModelPtr();
  440. if (studioHdr)
  441. {
  442. mstudiohitboxset_t *set = studioHdr->pHitboxSet( player->GetHitboxSet() );
  443. if (set && maxBoxIndex < set->numhitboxes)
  444. {
  445. QAngle angles;
  446. mstudiobbox_t *box;
  447. // gut
  448. box = set->pHitbox( gutBox );
  449. player->GetBonePosition( box->bone, info->m_gutPos, angles );
  450. // head
  451. box = set->pHitbox( headBox );
  452. player->GetBonePosition( box->bone, info->m_headPos, angles );
  453. Vector forward, right;
  454. AngleVectors( angles, &forward, &right, NULL );
  455. // in local bone space
  456. const float headForwardOffset = 4.0f;
  457. const float headRightOffset = 2.0f;
  458. info->m_headPos += headForwardOffset * forward + headRightOffset * right;
  459. /// @todo Fix this hack - lower the head target because it's a bit too high for the current T model
  460. info->m_headPos.z -= 2.0f;
  461. // left side
  462. box = set->pHitbox( leftElbowBox );
  463. player->GetBonePosition( box->bone, info->m_leftSidePos, angles );
  464. // right side
  465. box = set->pHitbox( rightElbowBox );
  466. player->GetBonePosition( box->bone, info->m_rightSidePos, angles );
  467. return;
  468. }
  469. }
  470. // default values if bones are not available
  471. info->m_headPos = GetCentroid( player );
  472. info->m_gutPos = info->m_headPos;
  473. info->m_leftSidePos = info->m_headPos;
  474. info->m_rightSidePos = info->m_headPos;
  475. }
  476. //--------------------------------------------------------------------------------------------------------------
  477. /**
  478. * Return world space position of given part on player.
  479. * Uses hitboxes to get accurate positions.
  480. * @todo Optimize by computing once for each player and storing.
  481. */
  482. const Vector &CCSBot::GetPartPosition( CCSPlayer *player, VisiblePartType part ) const
  483. {
  484. VPROF_BUDGET( "CCSBot::GetPartPosition", VPROF_BUDGETGROUP_NPCS );
  485. SNPROF( "CCSBot::GetPartPosition" );
  486. // which PartInfo corresponds to the given player
  487. PartInfo *info = &m_partInfo[ player->entindex() % MAX_PLAYERS ];
  488. if (gpGlobals->framecount > info->m_validFrame)
  489. {
  490. // update part positions
  491. const_cast< CCSBot * >( this )->ComputePartPositions( player );
  492. info->m_validFrame = gpGlobals->framecount;
  493. }
  494. // return requested part position
  495. switch( part )
  496. {
  497. default:
  498. {
  499. AssertMsg( false, "GetPartPosition: Invalid part" );
  500. // fall thru to GUT
  501. }
  502. case GUT:
  503. return info->m_gutPos;
  504. case HEAD:
  505. return info->m_headPos;
  506. case FEET:
  507. return info->m_feetPos;
  508. case LEFT_SIDE:
  509. return info->m_leftSidePos;
  510. case RIGHT_SIDE:
  511. return info->m_rightSidePos;
  512. }
  513. }
  514. //--------------------------------------------------------------------------------------------------------------
  515. /**
  516. * Update desired view angles to point towards m_lookAtSpot
  517. */
  518. void CCSBot::UpdateLookAt( void )
  519. {
  520. Vector to = m_lookAtSpot - EyePositionConst();
  521. QAngle idealAngle;
  522. VectorAngles( to, idealAngle );
  523. //Vector idealAngle = UTIL_VecToAngles( to );
  524. //idealAngle.x = 360.0f - idealAngle.x;
  525. SetLookAngles( idealAngle.y, idealAngle.x );
  526. }
  527. //--------------------------------------------------------------------------------------------------------------
  528. /**
  529. * Look at the given point in space for the given duration (-1 means forever)
  530. */
  531. void CCSBot::SetLookAt( const char *desc, const Vector &pos, PriorityType pri, float duration, bool clearIfClose, float angleTolerance, bool attack )
  532. {
  533. if (IsBlind())
  534. return;
  535. // if currently looking at a point in space with higher priority, ignore this request
  536. if (m_lookAtSpotState != NOT_LOOKING_AT_SPOT && m_lookAtSpotPriority > pri)
  537. return;
  538. // if already looking at this spot, just extend the time
  539. const float tolerance = 10.0f;
  540. if (m_lookAtSpotState != NOT_LOOKING_AT_SPOT && VectorsAreEqual( pos, m_lookAtSpot, tolerance ))
  541. {
  542. m_lookAtSpotDuration = duration;
  543. if (m_lookAtSpotPriority < pri)
  544. m_lookAtSpotPriority = pri;
  545. }
  546. else
  547. {
  548. // look at new spot
  549. m_lookAtSpot = pos;
  550. m_lookAtSpotState = LOOK_TOWARDS_SPOT;
  551. m_lookAtSpotDuration = duration;
  552. m_lookAtSpotPriority = pri;
  553. }
  554. m_lookAtSpotAngleTolerance = angleTolerance;
  555. m_lookAtSpotClearIfClose = clearIfClose;
  556. m_lookAtDesc = desc;
  557. m_lookAtSpotAttack = attack;
  558. PrintIfWatched( "%3.1f SetLookAt( %s ), duration = %f\n", gpGlobals->curtime, desc, duration );
  559. }
  560. //--------------------------------------------------------------------------------------------------------------
  561. /**
  562. * Block all "look at" and "look around" behavior for given duration - just look ahead
  563. */
  564. void CCSBot::InhibitLookAround( float duration )
  565. {
  566. m_inhibitLookAroundTimestamp = gpGlobals->curtime + duration;
  567. }
  568. //--------------------------------------------------------------------------------------------------------------
  569. /**
  570. * Update enounter spot timestamps, etc
  571. */
  572. void CCSBot::UpdatePeripheralVision()
  573. {
  574. VPROF_BUDGET( "CCSBot::UpdatePeripheralVision", VPROF_BUDGETGROUP_NPCS );
  575. const float peripheralUpdateInterval = 0.29f; // if we update at 10Hz, this ensures we test once every three
  576. if (gpGlobals->curtime - m_peripheralTimestamp < peripheralUpdateInterval)
  577. return;
  578. m_peripheralTimestamp = gpGlobals->curtime;
  579. if (m_spotEncounter)
  580. {
  581. // check LOS to all spots in case we see them with our "peripheral vision"
  582. const SpotOrder *spotOrder;
  583. Vector pos;
  584. FOR_EACH_VEC( m_spotEncounter->spots, it )
  585. {
  586. spotOrder = &m_spotEncounter->spots[ it ];
  587. const Vector &spotPos = spotOrder->spot->GetPosition();
  588. pos.x = spotPos.x;
  589. pos.y = spotPos.y;
  590. pos.z = spotPos.z + HalfHumanHeight;
  591. if (!IsVisible( pos, CHECK_FOV ))
  592. continue;
  593. // can see hiding spot, remember when we saw it last
  594. SetHidingSpotCheckTimestamp( spotOrder->spot );
  595. }
  596. }
  597. }
  598. //--------------------------------------------------------------------------------------------------------------
  599. /**
  600. * Update the "looking around" behavior.
  601. */
  602. void CCSBot::UpdateLookAround( bool updateNow )
  603. {
  604. VPROF_BUDGET( "CCSBot::UpdateLookAround", VPROF_BUDGETGROUP_NPCS );
  605. SNPROF( "CCSBot::UpdateLookAround" );
  606. //
  607. // If we recently saw an enemy, look towards where we last saw them
  608. // Unless we can hear them moving, in which case look towards the noise
  609. //
  610. const float closeRange = 500.0f;
  611. if (!IsNoiseHeard() || GetNoiseRange() > closeRange)
  612. {
  613. const float recentThreatTime = 1.0f; // 0.25f;
  614. if (!IsLookingAtSpot( PRIORITY_MEDIUM ) && gpGlobals->curtime - m_lastSawEnemyTimestamp < recentThreatTime)
  615. {
  616. ClearLookAt();
  617. Vector spot = m_lastEnemyPosition;
  618. // find enemy position on the ground
  619. if (TheNavMesh->GetSimpleGroundHeight( m_lastEnemyPosition, &spot.z ))
  620. {
  621. spot.z += HalfHumanHeight;
  622. SetLookAt( "Last Enemy Position", spot, PRIORITY_MEDIUM, RandomFloat( 2.0f, 3.0f ), true );
  623. return;
  624. }
  625. }
  626. }
  627. //
  628. // Look at nearby enemy noises
  629. //
  630. if (UpdateLookAtNoise())
  631. return;
  632. // check if looking around has been inhibited
  633. // Moved inhibit to allow high priority enemy lookats to still occur
  634. if (gpGlobals->curtime < m_inhibitLookAroundTimestamp)
  635. return;
  636. //
  637. // If we are hiding (or otherwise standing still), watch all approach points leading into this region
  638. //
  639. const float minStillTime = 2.0f;
  640. if (IsAtHidingSpot() || IsNotMoving( minStillTime ))
  641. {
  642. // update approach points
  643. const float recomputeApproachPointTolerance = 50.0f;
  644. if ((m_approachPointViewPosition - GetAbsOrigin()).IsLengthGreaterThan( recomputeApproachPointTolerance ))
  645. {
  646. ComputeApproachPoints();
  647. m_approachPointViewPosition = GetAbsOrigin();
  648. }
  649. // if we're sniping, zoom in to watch our approach points
  650. if (IsUsingSniperRifle())
  651. {
  652. // low skill bots don't pre-zoom
  653. if (GetProfile()->GetSkill() > 0.4f)
  654. {
  655. if (!IsViewMoving())
  656. {
  657. float range = ComputeWeaponSightRange();
  658. AdjustZoom( range );
  659. }
  660. else
  661. {
  662. // zoom out
  663. if (GetZoomLevel() != NO_ZOOM)
  664. SecondaryAttack();
  665. }
  666. }
  667. }
  668. if (m_lastKnownArea == NULL)
  669. return;
  670. if (gpGlobals->curtime < m_lookAroundStateTimestamp)
  671. return;
  672. // if we're sniping, switch look-at spots less often
  673. if (IsUsingSniperRifle())
  674. m_lookAroundStateTimestamp = gpGlobals->curtime + RandomFloat( 5.0f, 10.0f );
  675. else
  676. m_lookAroundStateTimestamp = gpGlobals->curtime + RandomFloat( 1.0f, 2.0f ); // 0.5, 1.0
  677. #define MAX_APPROACHES 16
  678. Vector validSpot[ MAX_APPROACHES ];
  679. int validSpotCount = 0;
  680. Vector *earlySpot = NULL;
  681. float earliest = 999999.9f;
  682. for ( int i = 0; i < m_approachPointCount; ++i )
  683. {
  684. float spotTime = m_approachPoint[ i ].m_area->GetEarliestOccupyTime( OtherTeam( GetTeamNumber() ) );
  685. // ignore approach areas the enemy could not have possibly reached yet
  686. if ( TheCSBots()->GetElapsedRoundTime() >= spotTime )
  687. {
  688. validSpot[ validSpotCount++ ] = m_approachPoint[ i ].m_pos;
  689. }
  690. else
  691. {
  692. // keep track of earliest spot we can see in case we get there very early
  693. if ( spotTime < earliest )
  694. {
  695. earlySpot = &m_approachPoint[ i ].m_pos;
  696. earliest = spotTime;
  697. }
  698. }
  699. }
  700. Vector spot;
  701. if (validSpotCount)
  702. {
  703. int which = RandomInt( 0, validSpotCount-1 );
  704. spot = validSpot[ which ];
  705. }
  706. else if (earlySpot)
  707. {
  708. // all of the spots we can see can't be reached yet by the enemy - look at the earliest spot
  709. spot = *earlySpot;
  710. }
  711. else
  712. {
  713. return;
  714. }
  715. // don't look at the floor, look roughly at chest level
  716. /// @todo If this approach point is very near, this will cause us to aim up in the air if were crouching
  717. spot.z += HalfHumanHeight;
  718. SetLookAt( "Approach Point (Hiding)", spot, PRIORITY_LOW );
  719. return;
  720. }
  721. //
  722. // Glance at "encouter spots" as we move past them
  723. //
  724. if (m_spotEncounter)
  725. {
  726. //
  727. // Check encounter spots
  728. //
  729. if (!IsSafe() && !IsLookingAtSpot( PRIORITY_LOW ))
  730. {
  731. // allow a short time to look where we're going
  732. if (gpGlobals->curtime < m_spotCheckTimestamp)
  733. return;
  734. /// @todo Use skill parameter instead of accuracy
  735. // lower skills have exponentially longer delays
  736. float asleep = (1.0f - GetProfile()->GetSkill());
  737. asleep *= asleep;
  738. asleep *= asleep;
  739. m_spotCheckTimestamp = gpGlobals->curtime + asleep * RandomFloat( 10.0f, 30.0f );
  740. // figure out how far along the path segment we are
  741. Vector delta = m_spotEncounter->path.to - m_spotEncounter->path.from;
  742. float length = delta.Length();
  743. float adx = (float)fabs(delta.x);
  744. float ady = (float)fabs(delta.y);
  745. float t;
  746. Vector myOrigin = GetCentroid( this );
  747. if (adx > ady)
  748. t = (myOrigin.x - m_spotEncounter->path.from.x) / delta.x;
  749. else
  750. t = (myOrigin.y - m_spotEncounter->path.from.y) / delta.y;
  751. // advance parameter a bit so we "lead" our checks
  752. const float leadCheckRange = 50.0f;
  753. t += leadCheckRange / length;
  754. if (t < 0.0f)
  755. t = 0.0f;
  756. else if (t > 1.0f)
  757. t = 1.0f;
  758. // collect the unchecked spots so far
  759. #define MAX_DANGER_SPOTS 16
  760. HidingSpot *dangerSpot[MAX_DANGER_SPOTS];
  761. int dangerSpotCount = 0;
  762. int dangerIndex = 0;
  763. const float checkTime = 10.0f;
  764. const SpotOrder *spotOrder;
  765. FOR_EACH_VEC( m_spotEncounter->spots, it )
  766. {
  767. spotOrder = &(m_spotEncounter->spots[ it ]);
  768. // if we have seen this spot recently, we don't need to look at it
  769. if (gpGlobals->curtime - GetHidingSpotCheckTimestamp( spotOrder->spot ) <= checkTime)
  770. continue;
  771. if (spotOrder->t > t)
  772. break;
  773. // ignore spots the enemy could not have possibly reached yet
  774. if (spotOrder->spot->GetArea())
  775. {
  776. if (TheCSBots()->GetElapsedRoundTime() < spotOrder->spot->GetArea()->GetEarliestOccupyTime( OtherTeam( GetTeamNumber() ) ))
  777. {
  778. continue;
  779. }
  780. }
  781. dangerSpot[ dangerIndex++ ] = spotOrder->spot;
  782. if (dangerIndex >= MAX_DANGER_SPOTS)
  783. dangerIndex = 0;
  784. if (dangerSpotCount < MAX_DANGER_SPOTS)
  785. ++dangerSpotCount;
  786. }
  787. if (dangerSpotCount)
  788. {
  789. // pick one of the spots at random
  790. int which = RandomInt( 0, dangerSpotCount-1 );
  791. // glance at the spot for minimum time
  792. SetLookAt( "Encounter Spot", dangerSpot[which]->GetPosition() + Vector( 0, 0, HalfHumanHeight ), PRIORITY_LOW, 0.2f, true, 10.0f );
  793. // immediately mark it as "checked", so we don't check it again
  794. // if we get distracted before we check it - that's the way it goes
  795. SetHidingSpotCheckTimestamp( dangerSpot[which] );
  796. }
  797. }
  798. }
  799. }
  800. //--------------------------------------------------------------------------------------------------------------
  801. /**
  802. * "Bend" our line of sight around corners until we can "see" the point.
  803. */
  804. bool CCSBot::BendLineOfSight( const Vector &eye, const Vector &target, Vector *bend, float angleLimit ) const
  805. {
  806. VPROF_BUDGET( "CCSBot::BendLineOfSight", VPROF_BUDGETGROUP_NPCS );
  807. bool doDebug = false;
  808. const float debugDuration = 0.04f;
  809. if (doDebug && cv_bot_debug.GetBool() && IsLocalPlayerWatchingMe())
  810. NDebugOverlay::Line( eye, target, 255, 255, 255, true, debugDuration );
  811. // if we can directly see the point, use it
  812. trace_t result;
  813. CTraceFilterNoNPCsOrPlayer traceFilter( this, COLLISION_GROUP_NONE );
  814. UTIL_TraceLine( eye, target, MASK_VISIBLE_AND_NPCS, &traceFilter, &result );
  815. if (result.fraction == 1.0f && !result.startsolid)
  816. {
  817. // can directly see point, no bending needed
  818. *bend = target;
  819. return true;
  820. }
  821. // "bend" our line of sight until we can see the approach point
  822. Vector to = target - eye;
  823. float startAngle = UTIL_VecToYaw( to );
  824. float length = to.Length2D();
  825. to.NormalizeInPlace();
  826. struct Color3
  827. {
  828. int r, g, b;
  829. };
  830. const int colorCount = 6;
  831. Color3 colorSet[ colorCount ] =
  832. {
  833. { 255, 0, 0 },
  834. { 0, 255, 0 },
  835. { 0, 0, 255 },
  836. { 255, 255, 0 },
  837. { 0, 255, 255 },
  838. { 255, 0, 255 },
  839. };
  840. int color = 0;
  841. // optiming assumption - previous rays cast "shadow" on subsequent rays since they already
  842. // enumerated visible space along their length.
  843. // We should do a dot product and compute the exact length, but since the angular changes
  844. // are incremental, using the direct length should be close enough.
  845. float priorVisibleLength[2] = { 0.0f, 0.0f };
  846. float angleInc = 5.0f;
  847. for( float angle = angleInc; angle <= angleLimit; angle += angleInc )
  848. {
  849. // check both sides at this angle offset
  850. for( int side=0; side<2; ++side )
  851. {
  852. float actualAngle = (side) ? (startAngle + angle) : (startAngle - angle);
  853. float dx = cos( 3.141592f * actualAngle / 180.0f );
  854. float dy = sin( 3.141592f * actualAngle / 180.0f );
  855. // compute rotated point ray endpoint
  856. Vector rotPoint( eye.x + length * dx, eye.y + length * dy, target.z );
  857. // check LOS to find length to test along ray
  858. UTIL_TraceLine( eye, rotPoint, MASK_VISIBLE_AND_NPCS, &traceFilter, &result );
  859. // if this ray started in an obstacle, skip it
  860. if (result.startsolid)
  861. {
  862. continue;
  863. }
  864. Vector ray = rotPoint - eye;
  865. float rayLength = ray.NormalizeInPlace();
  866. float visibleLength = rayLength * result.fraction;
  867. if (doDebug && cv_bot_debug.GetBool() && IsLocalPlayerWatchingMe())
  868. {
  869. NDebugOverlay::Line( eye, eye + visibleLength * ray, colorSet[color].r, colorSet[color].g, colorSet[color].b, true, debugDuration );
  870. }
  871. // step along ray, checking if point is visible from ray point
  872. const float bendStepSize = 50.0f;
  873. // start from point that prior rays couldn't see
  874. float startLength = priorVisibleLength[ side ];
  875. for( float bendLength=startLength; bendLength <= visibleLength; bendLength += bendStepSize )
  876. {
  877. // compute point along ray
  878. Vector bendPoint = eye + bendLength * ray;
  879. // check if we can see approach point from this bend point
  880. UTIL_TraceLine( bendPoint, target, MASK_VISIBLE_AND_NPCS, &traceFilter, &result );
  881. if (doDebug && cv_bot_debug.GetBool() && IsLocalPlayerWatchingMe())
  882. {
  883. NDebugOverlay::Line( bendPoint, result.endpos, colorSet[color].r/2, colorSet[color].g/2, colorSet[color].b/2, true, debugDuration );
  884. }
  885. if (result.fraction == 1.0f && !result.startsolid)
  886. {
  887. // target is visible from this bend point on the ray - use this point on the ray as our point
  888. // keep "bent" point at correct height along line of sight
  889. bendPoint.z = eye.z + bendLength * to.z;
  890. *bend = bendPoint;
  891. return true;
  892. }
  893. }
  894. priorVisibleLength[ side ] = visibleLength;
  895. ++color;
  896. if (color >= colorCount)
  897. {
  898. color = 0;
  899. }
  900. } // side
  901. }
  902. // bending rays didn't help - still can't see the point
  903. return false;
  904. }
  905. //--------------------------------------------------------------------------------------------------------------
  906. /**
  907. * Return true if we "notice" given player
  908. * @todo Increase chance if player is rotating
  909. * @todo Decrease chance as nears edge of FOV
  910. */
  911. bool CCSBot::IsNoticable( const CCSPlayer *player, unsigned char visParts ) const
  912. {
  913. // if this player has just fired his weapon, we notice him
  914. if (DidPlayerJustFireWeapon( player ))
  915. {
  916. return true;
  917. }
  918. //don't let bots see farther than the map-defined bot max vision distance
  919. if ( IsBeyondBotMaxVisionDistance( player->WorldSpaceCenter() ) )
  920. {
  921. return false;
  922. }
  923. float deltaT = m_attentionInterval.GetElapsedTime();
  924. // all chances are specified in terms of a standard "quantum" of time
  925. // in which a normal person would notice something
  926. const float noticeQuantum = 0.25f;
  927. // determine percentage of player that is visible
  928. float coverRatio = 0.0f;
  929. if (visParts & GUT)
  930. {
  931. const float chance = 40.0f;
  932. coverRatio += chance;
  933. }
  934. if (visParts & HEAD)
  935. {
  936. const float chance = 10.0f;
  937. coverRatio += chance;
  938. }
  939. if (visParts & LEFT_SIDE)
  940. {
  941. const float chance = 20.0f;
  942. coverRatio += chance;
  943. }
  944. if (visParts & RIGHT_SIDE)
  945. {
  946. const float chance = 20.0f;
  947. coverRatio += chance;
  948. }
  949. if (visParts & FEET)
  950. {
  951. const float chance = 10.0f;
  952. coverRatio += chance;
  953. }
  954. // compute range modifier - farther away players are harder to notice, depeding on what they are doing
  955. float range = (player->GetAbsOrigin() - GetAbsOrigin()).Length();
  956. const float closeRange = 300.0f;
  957. const float farRange = 1000.0f;
  958. float rangeModifier;
  959. if (range < closeRange)
  960. {
  961. rangeModifier = 0.0f;
  962. }
  963. else if (range > farRange)
  964. {
  965. rangeModifier = 1.0f;
  966. }
  967. else
  968. {
  969. rangeModifier = (range - closeRange)/(farRange - closeRange);
  970. }
  971. // harder to notice when crouched
  972. bool isCrouching = (player->GetFlags() & FL_DUCKING) != 0;
  973. // moving players are easier to spot
  974. float playerSpeedSq = player->GetAbsVelocity().LengthSqr();
  975. const float runSpeed = 200.0f;
  976. const float walkSpeed = 30.0f;
  977. float farChance, closeChance;
  978. if (playerSpeedSq > runSpeed * runSpeed)
  979. {
  980. // running players are always easy to spot (must be standing to run)
  981. return true;
  982. }
  983. else if (playerSpeedSq > walkSpeed * walkSpeed)
  984. {
  985. // walking players are less noticable far away
  986. if (isCrouching)
  987. {
  988. closeChance = 90.0f;
  989. farChance = 60.0f;
  990. }
  991. else // standing
  992. {
  993. closeChance = 100.0f;
  994. farChance = 75.0f;
  995. }
  996. }
  997. else
  998. {
  999. // motionless players are hard to notice
  1000. if (isCrouching)
  1001. {
  1002. // crouching and motionless - very tough to notice
  1003. closeChance = 80.0f;
  1004. farChance = 5.0f; // takes about three seconds to notice (50% chance)
  1005. }
  1006. else // standing
  1007. {
  1008. closeChance = 100.0f;
  1009. farChance = 10.0f;
  1010. }
  1011. }
  1012. // combine posture, speed, and range chances
  1013. float dispositionChance = closeChance + (farChance - closeChance) * rangeModifier;
  1014. // determine actual chance of noticing player
  1015. float noticeChance = dispositionChance * coverRatio/100.0f;
  1016. // scale by skill level
  1017. noticeChance *= (0.5f + 0.5f * GetProfile()->GetSkill());
  1018. // if we are alert, our chance of noticing is much higher
  1019. if (IsAlert())
  1020. {
  1021. const float alertBonus = 50.0f;
  1022. noticeChance += alertBonus;
  1023. }
  1024. // scale by time quantum
  1025. noticeChance *= deltaT / noticeQuantum;
  1026. // there must always be a chance of detecting the enemy
  1027. const float minChance = 0.1f;
  1028. if (noticeChance < minChance)
  1029. {
  1030. noticeChance = minChance;
  1031. }
  1032. //PrintIfWatched( "Notice chance = %3.2f\n", noticeChance );
  1033. return (RandomFloat( 0.0f, 100.0f ) < noticeChance);
  1034. }
  1035. //--------------------------------------------------------------------------------------------------------------
  1036. /**
  1037. * Return most dangerous threat in my field of view (feeds into reaction time queue).
  1038. * @todo Account for lighting levels, cover, and distance to see if we notice enemy
  1039. */
  1040. CCSPlayer *CCSBot::FindMostDangerousThreat( void )
  1041. {
  1042. VPROF_BUDGET( "CCSBot::FindMostDangerousThreat", VPROF_BUDGETGROUP_NPCS );
  1043. SNPROF("CCSBot::FindMostDangerousThreat");
  1044. if (IsBlind())
  1045. {
  1046. return NULL;
  1047. }
  1048. enum { MAX_THREATS = 16 }; // maximum number of simulataneously attendable threats
  1049. struct CloseInfo
  1050. {
  1051. CCSPlayer *enemy;
  1052. float range;
  1053. }
  1054. threat[ MAX_THREATS ];
  1055. int threatCount = 0;
  1056. int prevIndex = m_enemyQueueIndex - 1;
  1057. if ( prevIndex < 0 )
  1058. prevIndex = MAX_ENEMY_QUEUE - 1;
  1059. CCSPlayer *currentThreat = m_enemyQueue[ prevIndex ].player;
  1060. m_bomber = NULL;
  1061. m_isEnemySniperVisible = false;
  1062. m_closestVisibleFriend = NULL;
  1063. float closeFriendRange = 99999999999.9f;
  1064. m_closestVisibleHumanFriend = NULL;
  1065. float closeHumanFriendRange = 99999999999.9f;
  1066. CCSPlayer *sniperThreat = NULL;
  1067. float sniperThreatRange = 99999999999.9f;
  1068. bool sniperThreatIsFacingMe = false;
  1069. const float lookingAtMeTolerance = 0.7071f;
  1070. int i;
  1071. {
  1072. VPROF_BUDGET( "CCSBot::Collect Threats", VPROF_BUDGETGROUP_NPCS );
  1073. for( i = 1; i <= gpGlobals->maxClients; ++i )
  1074. {
  1075. CBaseEntity *entity = UTIL_PlayerByIndex( i );
  1076. if (entity == NULL)
  1077. continue;
  1078. // is it a player?
  1079. if (!entity->IsPlayer())
  1080. continue;
  1081. CCSPlayer *player = static_cast<CCSPlayer *>( entity );
  1082. #ifdef OPT_VIS_CSGO
  1083. int thisIdx = entindex();
  1084. int playerIdx = player->entindex();
  1085. #endif
  1086. // ignore self
  1087. if (player->entindex() == entindex())
  1088. continue;
  1089. // is it alive?
  1090. if (!player->IsAlive())
  1091. continue;
  1092. // is it an enemy?
  1093. if ( !IsOtherEnemy( player ) )
  1094. {
  1095. #ifdef OPT_VIS_CSGO
  1096. if ( ((thisIdx>>1) + gpGlobals->tickcount) % 5 ) continue;
  1097. #endif
  1098. // keep track of nearby friends - use less exact visibility check
  1099. if (IsVisible( entity->WorldSpaceCenter(), false, this ))
  1100. {
  1101. // update watch timestamp
  1102. int idx = player->entindex();
  1103. m_watchInfo[idx].timestamp = gpGlobals->curtime;
  1104. m_watchInfo[idx].isEnemy = false;
  1105. // keep track of our closest friend
  1106. Vector to = GetAbsOrigin() - player->GetAbsOrigin();
  1107. float rangeSq = to.LengthSqr();
  1108. if (rangeSq < closeFriendRange)
  1109. {
  1110. m_closestVisibleFriend = player;
  1111. closeFriendRange = rangeSq;
  1112. }
  1113. // keep track of our closest human friend
  1114. if (!player->IsBot() && rangeSq < closeHumanFriendRange)
  1115. {
  1116. m_closestVisibleHumanFriend = player;
  1117. closeHumanFriendRange = rangeSq;
  1118. }
  1119. }
  1120. continue;
  1121. }
  1122. // check if this enemy is fully or partially visible
  1123. #ifdef OPT_VIS_CSGO
  1124. // We don't update the vis every other frame on PS3 for perf
  1125. bool bVis;
  1126. unsigned char visParts;
  1127. if ( ( ((thisIdx>>1) + gpGlobals->tickcount) % 5 ) == 0 )
  1128. {
  1129. bVis = IsVisible( player, CHECK_FOV, &visParts );
  1130. m_bVis[playerIdx] = bVis;
  1131. m_aVisParts[playerIdx] = visParts;
  1132. }
  1133. else
  1134. {
  1135. bVis = m_bVis[playerIdx];
  1136. visParts = m_aVisParts[playerIdx];
  1137. }
  1138. if (!bVis) continue;
  1139. #else
  1140. unsigned char visParts;
  1141. if (!IsVisible( player, CHECK_FOV, &visParts ))
  1142. continue;
  1143. #endif
  1144. // do we notice this enemy? (always notice current enemy)
  1145. if (player != currentThreat)
  1146. {
  1147. if (!IsNoticable( player, visParts ))
  1148. {
  1149. continue;
  1150. }
  1151. }
  1152. // update watch timestamp
  1153. int idx = player->entindex();
  1154. m_watchInfo[idx].timestamp = gpGlobals->curtime;
  1155. m_watchInfo[idx].isEnemy = true;
  1156. // note if we see the bomber
  1157. if (player->HasC4())
  1158. {
  1159. m_bomber = player;
  1160. }
  1161. // keep track of all visible threats
  1162. Vector d = GetAbsOrigin() - player->GetAbsOrigin();
  1163. float distSq = d.LengthSqr();
  1164. // track enemy sniper threats
  1165. if (IsSniperRifle( player->GetActiveCSWeapon() ))
  1166. {
  1167. m_isEnemySniperVisible = true;
  1168. // keep track of the most dangerous sniper we see
  1169. if (sniperThreat)
  1170. {
  1171. if (IsPlayerLookingAtMe( player, lookingAtMeTolerance ))
  1172. {
  1173. if (sniperThreatIsFacingMe)
  1174. {
  1175. // several snipers are facing us - keep closest
  1176. if (distSq < sniperThreatRange)
  1177. {
  1178. sniperThreat = player;
  1179. sniperThreatRange = distSq;
  1180. sniperThreatIsFacingMe = true;
  1181. }
  1182. }
  1183. else
  1184. {
  1185. // even if this sniper is farther away, keep it because he's aiming at us
  1186. sniperThreat = player;
  1187. sniperThreatRange = distSq;
  1188. sniperThreatIsFacingMe = true;
  1189. }
  1190. }
  1191. else
  1192. {
  1193. // this sniper is not looking at us, only consider it if we dont have a sniper facing us
  1194. if (!sniperThreatIsFacingMe && distSq < sniperThreatRange)
  1195. {
  1196. sniperThreat = player;
  1197. sniperThreatRange = distSq;
  1198. }
  1199. }
  1200. }
  1201. else
  1202. {
  1203. // first sniper we see
  1204. sniperThreat = player;
  1205. sniperThreatRange = distSq;
  1206. sniperThreatIsFacingMe = IsPlayerLookingAtMe( player, lookingAtMeTolerance );
  1207. }
  1208. }
  1209. {
  1210. VPROF_BUDGET( "CCSBot::Sort Threats", VPROF_BUDGETGROUP_NPCS );
  1211. // maintain set of visible threats, sorted by increasing distance
  1212. if (threatCount == 0)
  1213. {
  1214. threat[0].enemy = player;
  1215. threat[0].range = distSq;
  1216. threatCount = 1;
  1217. }
  1218. else
  1219. {
  1220. // find insertion point
  1221. int j;
  1222. for( j=0; j<threatCount; ++j )
  1223. {
  1224. if (distSq < threat[j].range)
  1225. break;
  1226. }
  1227. // shift lower half down a notch
  1228. for( int k=threatCount-1; k>=j; --k )
  1229. threat[k+1] = threat[k];
  1230. // insert threat into sorted list
  1231. threat[j].enemy = player;
  1232. threat[j].range = distSq;
  1233. if (threatCount < MAX_THREATS)
  1234. ++threatCount;
  1235. }
  1236. }
  1237. }
  1238. }
  1239. {
  1240. VPROF_BUDGET( "CCSBot::Count nearby Friends & Enemies", VPROF_BUDGETGROUP_NPCS );
  1241. // track the maximum enemy and friend counts we've seen recently
  1242. int prevEnemies = m_nearbyEnemyCount;
  1243. m_nearbyEnemyCount = 0;
  1244. m_nearbyFriendCount = 0;
  1245. for( i=0; i<MAX_PLAYERS; ++i )
  1246. {
  1247. if (m_watchInfo[i].timestamp <= 0.0f)
  1248. continue;
  1249. const float recentTime = 3.0f;
  1250. if (gpGlobals->curtime - m_watchInfo[i].timestamp < recentTime)
  1251. {
  1252. if (m_watchInfo[i].isEnemy)
  1253. ++m_nearbyEnemyCount;
  1254. else
  1255. ++m_nearbyFriendCount;
  1256. }
  1257. }
  1258. // note when we saw this batch of enemies
  1259. if (prevEnemies == 0 && m_nearbyEnemyCount > 0)
  1260. {
  1261. m_firstSawEnemyTimestamp = gpGlobals->curtime;
  1262. }
  1263. }
  1264. {
  1265. VPROF_BUDGET( "CCSBot::Track enemy Place", VPROF_BUDGETGROUP_NPCS );
  1266. //
  1267. // Track the place where we saw most of our enemies
  1268. //
  1269. struct PlaceRank
  1270. {
  1271. unsigned int place;
  1272. int count;
  1273. };
  1274. static PlaceRank placeRank[ MAX_PLACES_PER_MAP ];
  1275. int locCount = 0;
  1276. PlaceRank common;
  1277. common.place = 0;
  1278. common.count = 0;
  1279. for( i=0; i<threatCount; ++i )
  1280. {
  1281. // find the area the player/bot is standing on
  1282. CNavArea *area;
  1283. CCSBot *bot = dynamic_cast<CCSBot *>(threat[i].enemy);
  1284. if (bot && bot->IsBot())
  1285. {
  1286. area = bot->GetLastKnownArea();
  1287. }
  1288. else
  1289. {
  1290. Vector enemyOrigin = GetCentroid( threat[i].enemy );
  1291. area = TheNavMesh->GetNearestNavArea( enemyOrigin );
  1292. }
  1293. if (area == NULL)
  1294. continue;
  1295. unsigned int threatLoc = area->GetPlace();
  1296. if (!threatLoc)
  1297. continue;
  1298. // if place is already in set, increment count
  1299. int j;
  1300. for( j=0; j<locCount; ++j )
  1301. if (placeRank[j].place == threatLoc)
  1302. break;
  1303. if (j == locCount)
  1304. {
  1305. // new place
  1306. if (locCount < MAX_PLACES_PER_MAP)
  1307. {
  1308. placeRank[ locCount ].place = threatLoc;
  1309. placeRank[ locCount ].count = 1;
  1310. if (common.count == 0)
  1311. common = placeRank[locCount];
  1312. ++locCount;
  1313. }
  1314. }
  1315. else
  1316. {
  1317. // others are in that place, increment
  1318. ++placeRank[j].count;
  1319. // keep track of the most common place
  1320. if (placeRank[j].count > common.count)
  1321. common = placeRank[j];
  1322. }
  1323. }
  1324. // remember most common place
  1325. m_enemyPlace = common.place;
  1326. }
  1327. {
  1328. VPROF_BUDGET( "CCSBot::Select Threat", VPROF_BUDGETGROUP_NPCS );
  1329. if (threatCount == 0)
  1330. return NULL;
  1331. // if we can still see our current threat, keep it
  1332. // unless a new one is much closer
  1333. bool sawCloserThreat = false;
  1334. bool sawCurrentThreat = false;
  1335. int t;
  1336. for( t=0; t<threatCount; ++t )
  1337. {
  1338. if ( threat[t].enemy == currentThreat )
  1339. {
  1340. sawCurrentThreat = true;
  1341. }
  1342. else if ( threat[t].enemy != currentThreat &&
  1343. IsSignificantlyCloser( threat[t].enemy, currentThreat ) )
  1344. {
  1345. sawCloserThreat = true;
  1346. }
  1347. }
  1348. if ( sawCurrentThreat && !sawCloserThreat )
  1349. {
  1350. return currentThreat;
  1351. }
  1352. // if we are a sniper and we see a sniper threat, attack it unless
  1353. // there are other close enemies facing me
  1354. if (IsSniper() && sniperThreat)
  1355. {
  1356. const float closeCombatRange = 500.0f;
  1357. for( t=0; t<threatCount; ++t )
  1358. {
  1359. if (threat[t].range < closeCombatRange && IsPlayerLookingAtMe( threat[t].enemy, lookingAtMeTolerance ))
  1360. {
  1361. return threat[t].enemy;
  1362. }
  1363. }
  1364. return sniperThreat;
  1365. }
  1366. // otherwise, find the closest threat that is looking at me
  1367. for( t=0; t<threatCount; ++t )
  1368. {
  1369. if (IsPlayerLookingAtMe( threat[t].enemy, lookingAtMeTolerance ))
  1370. {
  1371. return threat[t].enemy;
  1372. }
  1373. }
  1374. }
  1375. // return closest threat
  1376. return threat[0].enemy;
  1377. }
  1378. //--------------------------------------------------------------------------------------------------------------
  1379. /**
  1380. * Update our reaction time queue
  1381. */
  1382. void CCSBot::UpdateReactionQueue( void )
  1383. {
  1384. VPROF_BUDGET( "CCSBot::UpdateReactionQueue", VPROF_BUDGETGROUP_NPCS );
  1385. SNPROF( "CCSBot::UpdateReactionQueue" );
  1386. // zombies dont see any threats
  1387. if (cv_bot_zombie.GetBool())
  1388. return;
  1389. // find biggest threat at this instant
  1390. CCSPlayer *threat = FindMostDangerousThreat();
  1391. // reset timer
  1392. m_attentionInterval.Start();
  1393. int now = m_enemyQueueIndex;
  1394. // store a snapshot of its state at the end of the reaction time queue
  1395. if (threat)
  1396. {
  1397. m_enemyQueue[ now ].player = threat;
  1398. m_enemyQueue[ now ].isReloading = threat->IsReloading();
  1399. m_enemyQueue[ now ].isProtectedByShield = threat->IsProtectedByShield();
  1400. }
  1401. else
  1402. {
  1403. m_enemyQueue[ now ].player = NULL;
  1404. m_enemyQueue[ now ].isReloading = false;
  1405. m_enemyQueue[ now ].isProtectedByShield = false;
  1406. }
  1407. // queue is round-robin
  1408. ++m_enemyQueueIndex;
  1409. if (m_enemyQueueIndex >= MAX_ENEMY_QUEUE)
  1410. m_enemyQueueIndex = 0;
  1411. if (m_enemyQueueCount < MAX_ENEMY_QUEUE)
  1412. ++m_enemyQueueCount;
  1413. // clamp reaction time to enemy queue size
  1414. float reactionTime = GetProfile()->GetReactionTime() - g_BotUpdateInterval;
  1415. float maxReactionTime = (MAX_ENEMY_QUEUE * g_BotUpdateInterval) - 0.01f;
  1416. if (reactionTime > maxReactionTime)
  1417. reactionTime = maxReactionTime;
  1418. // "rewind" time back to our reaction time
  1419. int reactionTimeSteps = (int)((reactionTime / g_BotUpdateInterval) + 0.5f);
  1420. int i = now - reactionTimeSteps;
  1421. if (i < 0)
  1422. i += MAX_ENEMY_QUEUE;
  1423. m_enemyQueueAttendIndex = (unsigned char)i;
  1424. }
  1425. //--------------------------------------------------------------------------------------------------------------
  1426. /**
  1427. * Return the most dangerous threat we are "conscious" of
  1428. */
  1429. CCSPlayer *CCSBot::GetRecognizedEnemy( void )
  1430. {
  1431. if (m_enemyQueueAttendIndex >= m_enemyQueueCount || IsBlind())
  1432. {
  1433. return NULL;
  1434. }
  1435. return m_enemyQueue[ m_enemyQueueAttendIndex ].player;
  1436. }
  1437. //--------------------------------------------------------------------------------------------------------------
  1438. /**
  1439. * Return true if the enemy we are "conscious" of is reloading
  1440. */
  1441. bool CCSBot::IsRecognizedEnemyReloading( void )
  1442. {
  1443. if (m_enemyQueueAttendIndex >= m_enemyQueueCount)
  1444. return false;
  1445. return m_enemyQueue[ m_enemyQueueAttendIndex ].isReloading;
  1446. }
  1447. //--------------------------------------------------------------------------------------------------------------
  1448. /**
  1449. * Return true if the enemy we are "conscious" of is hiding behind a shield
  1450. */
  1451. bool CCSBot::IsRecognizedEnemyProtectedByShield( void )
  1452. {
  1453. if (m_enemyQueueAttendIndex >= m_enemyQueueCount)
  1454. return false;
  1455. return m_enemyQueue[ m_enemyQueueAttendIndex ].isProtectedByShield;
  1456. }
  1457. //--------------------------------------------------------------------------------------------------------------
  1458. /**
  1459. * Return distance to closest enemy we are "conscious" of
  1460. */
  1461. float CCSBot::GetRangeToNearestRecognizedEnemy( void )
  1462. {
  1463. const CCSPlayer *enemy = GetRecognizedEnemy();
  1464. if (enemy)
  1465. return (GetAbsOrigin() - enemy->GetAbsOrigin()).Length();
  1466. return 99999999.9f;
  1467. }
  1468. //--------------------------------------------------------------------------------------------------------------
  1469. /**
  1470. * Blind the bot for the given duration
  1471. */
  1472. void CCSBot::Blind( float holdTime, float fadeTime, float startingAlpha )
  1473. {
  1474. PrintIfWatched( "Blinded: holdTime = %3.2f, fadeTime = %3.2f, alpha = %3.2f\n", holdTime, fadeTime, startingAlpha );
  1475. // if we were only blinded a little bit, shake it off
  1476. const float mildBlindTime = 3.0f;
  1477. if (holdTime < mildBlindTime)
  1478. {
  1479. Wait( 0.75f * holdTime );
  1480. BecomeAlert();
  1481. BaseClass::Blind( holdTime, fadeTime, startingAlpha );
  1482. return;
  1483. }
  1484. // if blinded while in combat - then spray and pray!
  1485. if ( CSGameRules()->IsPlayingOffline() && CSGameRules()->GetCustomBotDifficulty() == CUSTOM_BOT_DIFFICULTY_DUMB )
  1486. {
  1487. // For Offline games: Bots in dumb mode should not fire their weapons when flash-banged
  1488. m_blindFire = false;
  1489. }
  1490. else
  1491. {
  1492. m_blindFire = IsAttacking();
  1493. }
  1494. // retreat
  1495. // do this first, so spot selection happens before IsBlind() is set
  1496. const float hideRange = 400.0f;
  1497. TryToRetreat( hideRange );
  1498. PrintIfWatched( "I'm blind!\n" );
  1499. if (RandomFloat( 0.0f, 100.0f ) < 33.3f)
  1500. {
  1501. GetChatter()->Say( "Blinded", 1.0f );
  1502. }
  1503. // no longer safe
  1504. AdjustSafeTime();
  1505. // decide which way to move while blind
  1506. m_blindMoveDir = static_cast<NavRelativeDirType>( RandomInt( 1, NUM_RELATIVE_DIRECTIONS-1 ) );
  1507. // if we're defusing, don't give up
  1508. if (IsDefusingBomb())
  1509. {
  1510. return;
  1511. }
  1512. // can't see to aim at enemy
  1513. StopAiming();
  1514. // dont override "facing away" behavior unless we are going to spray and pray
  1515. if (m_blindFire)
  1516. {
  1517. ClearLookAt();
  1518. // just look straight ahead while blind
  1519. Vector forward;
  1520. EyeVectors( &forward );
  1521. SetLookAt( "Blind", EyePosition() + 10000.0f * forward, PRIORITY_UNINTERRUPTABLE, holdTime + 0.5f * fadeTime );
  1522. }
  1523. StopWaiting();
  1524. BecomeAlert();
  1525. BaseClass::Blind( holdTime, fadeTime, startingAlpha );
  1526. }
  1527. //--------------------------------------------------------------------------------------------------------------
  1528. class CheckLookAt
  1529. {
  1530. public:
  1531. CheckLookAt( const CCSBot *me, bool testFOV )
  1532. {
  1533. m_me = me;
  1534. m_testFOV = testFOV;
  1535. }
  1536. bool operator() ( CBasePlayer *player )
  1537. {
  1538. if (!m_me->IsEnemy( player ))
  1539. return true;
  1540. if (m_testFOV && !(const_cast< CCSBot * >(m_me)->FInViewCone( player->WorldSpaceCenter() )))
  1541. return true;
  1542. if (!m_me->IsPlayerLookingAtMe( player ))
  1543. return true;
  1544. if (m_me->IsVisible( (CCSPlayer *)player ))
  1545. return false;
  1546. return true;
  1547. }
  1548. const CCSBot *m_me;
  1549. bool m_testFOV;
  1550. };
  1551. /**
  1552. * Return true if any enemy I have LOS to is looking directly at me
  1553. * @todo Use reaction time pipeline
  1554. */
  1555. bool CCSBot::IsAnyVisibleEnemyLookingAtMe( bool testFOV ) const
  1556. {
  1557. CheckLookAt checkLookAt( this, testFOV );
  1558. return (ForEachPlayer( checkLookAt ) == false) ? true : false;
  1559. }
  1560. //--------------------------------------------------------------------------------------------------------------
  1561. /**
  1562. * Do panic behavior
  1563. */
  1564. void CCSBot::UpdatePanicLookAround( void )
  1565. {
  1566. if (m_panicTimer.IsElapsed())
  1567. {
  1568. return;
  1569. }
  1570. if (IsEnemyVisible())
  1571. {
  1572. StopPanicking();
  1573. return;
  1574. }
  1575. if (HasLookAtTarget())
  1576. {
  1577. // wait until we finish our current look at
  1578. return;
  1579. }
  1580. // select a spot somewhere behind us to look at as we search for our attacker
  1581. const QAngle &eyeAngles = EyeAngles();
  1582. QAngle newAngles;
  1583. newAngles.x = RandomFloat( -30.0f, 30.0f );
  1584. // Look directly behind at a random offset in a 90 window.
  1585. newAngles.y = eyeAngles.y + 180.0f + RandomFloat(-45.0f, +45.0f );
  1586. newAngles.z = 0.0f;
  1587. Vector forward;
  1588. AngleVectors( newAngles, &forward );
  1589. Vector spot;
  1590. spot = EyePosition() + 1000.0f * forward;
  1591. SetLookAt( "Panic", spot, PRIORITY_HIGH, 0.0f );
  1592. PrintIfWatched( "Panic yaw angle = %3.2f\n", newAngles.y );
  1593. }