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.

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