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.

1363 lines
34 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 "basecsgrenade_projectile.h"
  11. // memdbgon must be the last include file in a .cpp file!!!
  12. #include "tier0/memdbgon.h"
  13. //--------------------------------------------------------------------------------------------------------------
  14. /**
  15. * Fire our active weapon towards our current enemy
  16. * NOTE: Aiming our weapon is handled in RunBotUpkeep()
  17. */
  18. void CCSBot::FireWeaponAtEnemy( void )
  19. {
  20. if (cv_bot_dont_shoot.GetBool())
  21. {
  22. return;
  23. }
  24. CBasePlayer *enemy = GetBotEnemy();
  25. if (enemy == NULL)
  26. {
  27. return;
  28. }
  29. Vector myOrigin = GetCentroid( this );
  30. if (IsUsingSniperRifle())
  31. {
  32. // if we're using a sniper rifle, don't fire until we are standing still, are zoomed in, and not rapidly moving our view
  33. if (!IsNotMoving() || IsWaitingForZoom() || !HasViewBeenSteady( GetProfile()->GetReactionTime() ) )
  34. {
  35. return;
  36. }
  37. }
  38. if (gpGlobals->curtime > m_fireWeaponTimestamp &&
  39. GetTimeSinceAcquiredCurrentEnemy() >= GetProfile()->GetAttackDelay() &&
  40. !IsSurprised())
  41. {
  42. if (!(IsRecognizedEnemyProtectedByShield() && IsPlayerFacingMe( enemy )) && // don't shoot at enemies behind shields
  43. !IsReloading() &&
  44. !IsActiveWeaponClipEmpty() &&
  45. //gpGlobals->curtime > m_reacquireTimestamp &&
  46. IsEnemyVisible())
  47. {
  48. // we have a clear shot - pull trigger if we are aiming at enemy
  49. Vector toAimSpot = m_aimSpot - EyePosition();
  50. float rangeToEnemy = toAimSpot.NormalizeInPlace();
  51. if ( IsUsingSniperRifle() )
  52. {
  53. // check our accuracy versus our target distance
  54. float fProjectedSpread = rangeToEnemy * GetActiveCSWeapon()->GetInaccuracy();
  55. float fRequiredSpread = IsUsing( WEAPON_AWP ) ? 50.0f : 25.0f; // AWP will kill with any hit
  56. if ( fProjectedSpread > fRequiredSpread )
  57. return;
  58. }
  59. // get actual view direction vector
  60. Vector aimDir = GetViewVector();
  61. float onTarget = DotProduct( toAimSpot, aimDir );
  62. // aim more precisely with a sniper rifle
  63. // because rifles' bullets spray, don't have to be very precise
  64. const float halfSize = (IsUsingSniperRifle()) ? HalfHumanWidth : 2.0f * HalfHumanWidth;
  65. // aiming tolerance depends on how close the target is - closer targets subtend larger angles
  66. float aimTolerance = (float)cos( atan( halfSize / rangeToEnemy ) );
  67. if (onTarget > aimTolerance)
  68. {
  69. bool doAttack;
  70. // if friendly fire is on, don't fire if a teammate is blocking our line of fire
  71. if (TheCSBots()->AllowFriendlyFireDamage())
  72. {
  73. if (IsFriendInLineOfFire())
  74. doAttack = false;
  75. else
  76. doAttack = true;
  77. }
  78. else
  79. {
  80. // fire freely
  81. doAttack = true;
  82. }
  83. if (doAttack)
  84. {
  85. // if we are using a knife, only swing it if we're close
  86. if (IsUsingKnife())
  87. {
  88. const float knifeRange = 75.0f; // 50
  89. if (rangeToEnemy < knifeRange)
  90. {
  91. // since we've given ourselves away - run!
  92. ForceRun( 5.0f );
  93. // if our prey is facing away, backstab him!
  94. if (!IsPlayerFacingMe( enemy ))
  95. {
  96. SecondaryAttack();
  97. }
  98. else
  99. {
  100. // randomly choose primary and secondary attacks with knife
  101. const float knifeStabChance = 33.3f;
  102. if (RandomFloat( 0, 100 ) < knifeStabChance)
  103. SecondaryAttack();
  104. else
  105. PrimaryAttack();
  106. }
  107. }
  108. }
  109. else
  110. {
  111. PrimaryAttack();
  112. }
  113. }
  114. if (IsUsingPistol())
  115. {
  116. // high-skill bots fire their pistols quickly at close range
  117. const float closePistolRange = 360.0f;
  118. if (GetProfile()->GetSkill() > 0.75f && rangeToEnemy < closePistolRange)
  119. {
  120. // fire as fast as possible
  121. m_fireWeaponTimestamp = 0.0f;
  122. }
  123. else
  124. {
  125. // fire somewhat quickly
  126. m_fireWeaponTimestamp = RandomFloat( 0.15f, 0.4f );
  127. }
  128. }
  129. else // not using a pistol
  130. {
  131. const float sprayRange = 400.0f;
  132. if (GetProfile()->GetSkill() < 0.5f || rangeToEnemy < sprayRange || IsUsingMachinegun())
  133. {
  134. // spray 'n pray if enemy is close, or we're not that good, or we're using the big machinegun
  135. m_fireWeaponTimestamp = 0.0f;
  136. }
  137. else
  138. {
  139. const float distantTargetRange = 800.0f;
  140. if (!IsUsingSniperRifle() && rangeToEnemy > distantTargetRange)
  141. {
  142. // if very far away, fire slowly for better accuracy
  143. m_fireWeaponTimestamp = RandomFloat( 0.3f, 0.7f );
  144. }
  145. else
  146. {
  147. // fire short bursts for accuracy
  148. m_fireWeaponTimestamp = RandomFloat( 0.15f, 0.25f ); // 0.15, 0.5
  149. }
  150. }
  151. }
  152. // subtract system latency
  153. m_fireWeaponTimestamp -= g_BotUpdateInterval;
  154. m_fireWeaponTimestamp += gpGlobals->curtime;
  155. }
  156. }
  157. }
  158. }
  159. //--------------------------------------------------------------------------------------------------------------
  160. /**
  161. * Set the current aim offset using given accuracy (1.0 = perfect aim, 0.0f = terrible aim)
  162. */
  163. void CCSBot::SetAimOffset( float accuracy )
  164. {
  165. // if our accuracy is less than perfect, it will improve as we "focus in" while not rotating our view
  166. if (accuracy < 1.0f)
  167. {
  168. // if we moved our view, reset our "focus" mechanism
  169. if (IsViewMoving( 100.0f ))
  170. m_aimSpreadTimestamp = gpGlobals->curtime;
  171. // focusTime is the time it takes for a bot to "focus in" for very good aim, from 2 to 5 seconds
  172. const float focusTime = MAX( 5.0f * (1.0f - accuracy), 2.0f );
  173. float focusInterval = gpGlobals->curtime - m_aimSpreadTimestamp;
  174. float focusAccuracy = focusInterval / focusTime;
  175. // limit how much "focus" will help
  176. const float maxFocusAccuracy = 0.75f;
  177. if (focusAccuracy > maxFocusAccuracy)
  178. focusAccuracy = maxFocusAccuracy;
  179. accuracy = MAX( accuracy, focusAccuracy );
  180. }
  181. //PrintIfWatched( "Accuracy = %4.3f\n", accuracy );
  182. // aim error increases with distance, such that actual crosshair error stays about the same
  183. float range = (m_lastEnemyPosition - EyePosition()).Length();
  184. float maxOffset = (GetFOV()/GetDefaultFOV()) * 0.05f * range; // 0.1
  185. float error = maxOffset * (1.0f - accuracy);
  186. m_aimOffsetGoal.x = RandomFloat( -error, error );
  187. m_aimOffsetGoal.y = RandomFloat( -error, error );
  188. m_aimOffsetGoal.z = RandomFloat( -error, error );
  189. // define time when aim offset will automatically be updated
  190. m_aimOffsetTimestamp = gpGlobals->curtime + RandomFloat( 0.25f, 1.0f ); // 0.25, 1.5f
  191. }
  192. //--------------------------------------------------------------------------------------------------------------
  193. /**
  194. * Wiggle aim error based on GetProfile()->GetSkill()
  195. */
  196. void CCSBot::UpdateAimOffset( void )
  197. {
  198. if (gpGlobals->curtime >= m_aimOffsetTimestamp)
  199. {
  200. SetAimOffset( GetProfile()->GetSkill() );
  201. }
  202. // move current offset towards goal offset
  203. Vector d = m_aimOffsetGoal - m_aimOffset;
  204. const float stiffness = 0.1f;
  205. m_aimOffset.x += stiffness * d.x;
  206. m_aimOffset.y += stiffness * d.y;
  207. m_aimOffset.z += stiffness * d.z;
  208. }
  209. //--------------------------------------------------------------------------------------------------------------
  210. /**
  211. * Change our zoom level to be appropriate for the given range.
  212. * Return true if the zoom level changed.
  213. */
  214. bool CCSBot::AdjustZoom( float range )
  215. {
  216. bool adjustZoom = false;
  217. if (IsUsingSniperRifle())
  218. {
  219. const float sniperZoomRange = 150.0f; // NOTE: This must be less than sniperMinRange in AttackState
  220. const float sniperFarZoomRange = 1500.0f;
  221. // if range is too close, don't zoom
  222. if (range <= sniperZoomRange)
  223. {
  224. // zoom out
  225. if (GetZoomLevel() != NO_ZOOM)
  226. {
  227. adjustZoom = true;
  228. }
  229. }
  230. else if (range < sniperFarZoomRange)
  231. {
  232. // maintain low zoom
  233. if (GetZoomLevel() != LOW_ZOOM)
  234. {
  235. adjustZoom = true;
  236. }
  237. }
  238. else
  239. {
  240. // maintain high zoom
  241. if (GetZoomLevel() != HIGH_ZOOM)
  242. {
  243. adjustZoom = true;
  244. }
  245. }
  246. }
  247. else
  248. {
  249. // zoom out
  250. if (GetZoomLevel() != NO_ZOOM)
  251. {
  252. adjustZoom = true;
  253. }
  254. }
  255. if (adjustZoom)
  256. {
  257. SecondaryAttack();
  258. // pause after zoom to allow "eyes" to refocus
  259. // m_zoomTimer.Start( 0.25f + (1.0f - GetProfile()->GetSkill()) );
  260. m_zoomTimer.Start( 0.25f );
  261. }
  262. return adjustZoom;
  263. }
  264. //--------------------------------------------------------------------------------------------------------------
  265. /**
  266. * Returns true if using the specific weapon
  267. */
  268. bool CCSBot::IsUsing( CSWeaponID weaponID ) const
  269. {
  270. CWeaponCSBase *weapon = GetActiveCSWeapon();
  271. if (weapon == NULL)
  272. return false;
  273. if (weapon->IsA( weaponID ))
  274. return true;
  275. return false;
  276. }
  277. //--------------------------------------------------------------------------------------------------------------
  278. /**
  279. * Returns true if we are using a weapon with a removable silencer
  280. */
  281. bool CCSBot::DoesActiveWeaponHaveSilencer( void ) const
  282. {
  283. CWeaponCSBase *weapon = GetActiveCSWeapon();
  284. if (weapon == NULL)
  285. return false;
  286. if (weapon->IsA( WEAPON_M4A1 ) || weapon->IsA( WEAPON_USP ))
  287. return true;
  288. return false;
  289. }
  290. //--------------------------------------------------------------------------------------------------------------
  291. /**
  292. * Return true if we are using a sniper rifle
  293. */
  294. bool CCSBot::IsUsingSniperRifle( void ) const
  295. {
  296. CWeaponCSBase *weapon = GetActiveCSWeapon();
  297. if (weapon && IsSniperRifle( weapon ))
  298. return true;
  299. return false;
  300. }
  301. //--------------------------------------------------------------------------------------------------------------
  302. /**
  303. * Return true if we have a sniper rifle in our inventory
  304. */
  305. bool CCSBot::IsSniper( void ) const
  306. {
  307. CWeaponCSBase *weapon = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_RIFLE ) );
  308. if (weapon && IsSniperRifle( weapon ))
  309. return true;
  310. return false;
  311. }
  312. //--------------------------------------------------------------------------------------------------------------
  313. /**
  314. * Return true if we are actively sniping (moving to sniper spot or settled in)
  315. */
  316. bool CCSBot::IsSniping( void ) const
  317. {
  318. if (GetTask() == MOVE_TO_SNIPER_SPOT || GetTask() == SNIPING)
  319. return true;
  320. return false;
  321. }
  322. //--------------------------------------------------------------------------------------------------------------
  323. /**
  324. * Return true if we are using a shotgun
  325. */
  326. bool CCSBot::IsUsingShotgun( void ) const
  327. {
  328. CWeaponCSBase *weapon = GetActiveCSWeapon();
  329. if (weapon == NULL)
  330. return false;
  331. return weapon->IsKindOf(WEAPONTYPE_SHOTGUN);
  332. }
  333. //--------------------------------------------------------------------------------------------------------------
  334. /**
  335. * Returns true if using the big 'ol machinegun
  336. */
  337. bool CCSBot::IsUsingMachinegun( void ) const
  338. {
  339. CWeaponCSBase *weapon = GetActiveCSWeapon();
  340. if (weapon && weapon->IsA( WEAPON_M249 ))
  341. return true;
  342. return false;
  343. }
  344. //--------------------------------------------------------------------------------------------------------------
  345. /**
  346. * Return true if primary weapon doesn't exist or is totally out of ammo
  347. */
  348. bool CCSBot::IsPrimaryWeaponEmpty( void ) const
  349. {
  350. CWeaponCSBase *weapon = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_RIFLE ) );
  351. if (weapon == NULL)
  352. return true;
  353. // check if gun has any ammo left
  354. if (weapon->HasAnyAmmo())
  355. return false;
  356. return true;
  357. }
  358. //--------------------------------------------------------------------------------------------------------------
  359. /**
  360. * Return true if pistol doesn't exist or is totally out of ammo
  361. */
  362. bool CCSBot::IsPistolEmpty( void ) const
  363. {
  364. CWeaponCSBase *weapon = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_PISTOL ) );
  365. if (weapon == NULL)
  366. return true;
  367. // check if gun has any ammo left
  368. if (weapon->HasAnyAmmo())
  369. return false;
  370. return true;
  371. }
  372. //--------------------------------------------------------------------------------------------------------------
  373. /**
  374. * Equip the given item
  375. */
  376. bool CCSBot::DoEquip( CWeaponCSBase *weapon )
  377. {
  378. if (weapon == NULL)
  379. return false;
  380. // check if weapon has any ammo left
  381. if (!weapon->HasAnyAmmo())
  382. return false;
  383. // equip it
  384. SelectItem( weapon->GetClassname() );
  385. m_equipTimer.Start();
  386. return true;
  387. }
  388. // throttle how often equipping is allowed
  389. const float minEquipInterval = 5.0f;
  390. //--------------------------------------------------------------------------------------------------------------
  391. /**
  392. * Equip the best weapon we are carrying that has ammo
  393. */
  394. void CCSBot::EquipBestWeapon( bool mustEquip )
  395. {
  396. // throttle how often equipping is allowed
  397. if (!mustEquip && m_equipTimer.GetElapsedTime() < minEquipInterval)
  398. return;
  399. CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheBots );
  400. CWeaponCSBase *primary = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_RIFLE ) );
  401. if (primary)
  402. {
  403. CSWeaponType weaponClass = primary->GetCSWpnData().m_WeaponType;
  404. if ((ctrl->AllowShotguns() && weaponClass == WEAPONTYPE_SHOTGUN) ||
  405. (ctrl->AllowMachineGuns() && weaponClass == WEAPONTYPE_MACHINEGUN) ||
  406. (ctrl->AllowRifles() && weaponClass == WEAPONTYPE_RIFLE) ||
  407. (ctrl->AllowShotguns() && weaponClass == WEAPONTYPE_SHOTGUN) ||
  408. (ctrl->AllowSnipers() && weaponClass == WEAPONTYPE_SNIPER_RIFLE) ||
  409. (ctrl->AllowSubMachineGuns() && weaponClass == WEAPONTYPE_SUBMACHINEGUN))
  410. {
  411. if (DoEquip( primary ))
  412. return;
  413. }
  414. }
  415. if (ctrl->AllowPistols())
  416. {
  417. if (DoEquip( static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_PISTOL ) ) ))
  418. return;
  419. }
  420. // always have a knife
  421. EquipKnife();
  422. }
  423. //--------------------------------------------------------------------------------------------------------------
  424. /**
  425. * Equip our pistol
  426. */
  427. void CCSBot::EquipPistol( void )
  428. {
  429. // throttle how often equipping is allowed
  430. if (m_equipTimer.GetElapsedTime() < minEquipInterval)
  431. return;
  432. if (TheCSBots()->AllowPistols() && !IsUsingPistol())
  433. {
  434. CWeaponCSBase *pistol = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_PISTOL ) );
  435. DoEquip( pistol );
  436. }
  437. }
  438. //--------------------------------------------------------------------------------------------------------------
  439. /**
  440. * Equip the knife
  441. */
  442. void CCSBot::EquipKnife( void )
  443. {
  444. if (!IsUsingKnife())
  445. {
  446. SelectItem( "weapon_knife" );
  447. }
  448. }
  449. //--------------------------------------------------------------------------------------------------------------
  450. /**
  451. * Return true if we have a grenade in our inventory
  452. */
  453. bool CCSBot::HasGrenade( void ) const
  454. {
  455. CWeaponCSBase *grenade = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_GRENADES ) );
  456. return (grenade) ? true : false;
  457. }
  458. //--------------------------------------------------------------------------------------------------------------
  459. /**
  460. * Equip a grenade, return false if we cant
  461. */
  462. bool CCSBot::EquipGrenade( bool noSmoke )
  463. {
  464. // snipers don't use grenades
  465. if (IsSniper())
  466. return false;
  467. if (IsUsingGrenade())
  468. return true;
  469. if (HasGrenade())
  470. {
  471. CWeaponCSBase *grenade = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_GRENADES ) );
  472. if (noSmoke && grenade->IsA( WEAPON_SMOKEGRENADE ))
  473. return false;
  474. SelectItem( grenade->GetClassname() );
  475. return true;
  476. }
  477. return false;
  478. }
  479. //--------------------------------------------------------------------------------------------------------------
  480. /**
  481. * Returns true if we have knife equipped
  482. */
  483. bool CCSBot::IsUsingKnife( void ) const
  484. {
  485. CWeaponCSBase *weapon = GetActiveCSWeapon();
  486. if (weapon && weapon->IsA( WEAPON_KNIFE ))
  487. return true;
  488. return false;
  489. }
  490. //--------------------------------------------------------------------------------------------------------------
  491. /**
  492. * Returns true if we have pistol equipped
  493. */
  494. bool CCSBot::IsUsingPistol( void ) const
  495. {
  496. CWeaponCSBase *weapon = GetActiveCSWeapon();
  497. if (weapon && weapon->IsPistol())
  498. return true;
  499. return false;
  500. }
  501. //--------------------------------------------------------------------------------------------------------------
  502. /**
  503. * Returns true if we have a grenade equipped
  504. */
  505. bool CCSBot::IsUsingGrenade( void ) const
  506. {
  507. CWeaponCSBase *weapon = GetActiveCSWeapon();
  508. if (!weapon)
  509. return false;
  510. if (weapon->IsA( WEAPON_FLASHBANG ) ||
  511. weapon->IsA( WEAPON_SMOKEGRENADE ) ||
  512. weapon->IsA( WEAPON_HEGRENADE ))
  513. return true;
  514. return false;
  515. }
  516. //--------------------------------------------------------------------------------------------------------------
  517. /**
  518. * Begin the process of throwing the grenade
  519. */
  520. void CCSBot::ThrowGrenade( const Vector &target )
  521. {
  522. if (IsUsingGrenade() && m_grenadeTossState == NOT_THROWING && !IsOnLadder())
  523. {
  524. m_grenadeTossState = START_THROW;
  525. m_tossGrenadeTimer.Start( 2.0f );
  526. const float angleTolerance = 3.0f;
  527. SetLookAt( "GrenadeThrow", target, PRIORITY_UNINTERRUPTABLE, 4.0f, false, angleTolerance );
  528. Wait( RandomFloat( 2.0f, 4.0f ) );
  529. if (cv_bot_debug.GetBool() && IsLocalPlayerWatchingMe())
  530. {
  531. NDebugOverlay::Cross3D( target, 25.0f, 255, 125, 0, true, 3.0f );
  532. }
  533. PrintIfWatched( "%3.2f: Grenade: START_THROW\n", gpGlobals->curtime );
  534. }
  535. }
  536. //--------------------------------------------------------------------------------------------------------------
  537. /**
  538. * Returns true if our weapon can attack
  539. */
  540. bool CCSBot::CanActiveWeaponFire( void ) const
  541. {
  542. return ( GetActiveWeapon() && GetActiveWeapon()->m_flNextPrimaryAttack <= gpGlobals->curtime );
  543. }
  544. //--------------------------------------------------------------------------------------------------------------
  545. /**
  546. * Find spot to throw grenade ahead of us and "around the corner" along our path
  547. */
  548. bool CCSBot::FindGrenadeTossPathTarget( Vector *pos )
  549. {
  550. if (!HasPath())
  551. return false;
  552. // find farthest point we can see on the path
  553. int i;
  554. for( i=m_pathIndex; i<m_pathLength; ++i )
  555. {
  556. if (!FVisible( m_path[i].pos + Vector( 0, 0, HalfHumanHeight ) ))
  557. break;
  558. }
  559. if (i == m_pathIndex)
  560. return false;
  561. // find exact spot where we lose sight
  562. Vector dir = m_path[i].pos - m_path[i-1].pos;
  563. float length = dir.NormalizeInPlace();
  564. const float inc = 25.0f;
  565. Vector p;
  566. Vector visibleSpot = m_path[i-1].pos;
  567. for( float t = 0.0f; t<length; t += inc )
  568. {
  569. p = m_path[i-1].pos + t * dir;
  570. p.z += HalfHumanHeight;
  571. if (!FVisible( p ))
  572. break;
  573. visibleSpot = p;
  574. }
  575. // massage the location a bit
  576. visibleSpot.z += 10.0f;
  577. const float bufferRange = 50.0f;
  578. trace_t result;
  579. Vector check;
  580. // check +X
  581. check = visibleSpot + Vector( 999.9f, 0, 0 );
  582. UTIL_TraceLine( visibleSpot, check, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
  583. if (result.fraction < 1.0f)
  584. {
  585. float range = result.endpos.x - visibleSpot.x;
  586. if (range < bufferRange)
  587. {
  588. visibleSpot.x = result.endpos.x - bufferRange;
  589. }
  590. }
  591. // check -X
  592. check = visibleSpot + Vector( -999.9f, 0, 0 );
  593. UTIL_TraceLine( visibleSpot, check, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
  594. if (result.fraction < 1.0f)
  595. {
  596. float range = visibleSpot.x - result.endpos.x;
  597. if (range < bufferRange)
  598. {
  599. visibleSpot.x = result.endpos.x + bufferRange;
  600. }
  601. }
  602. // check +Y
  603. check = visibleSpot + Vector( 0, 999.9f, 0 );
  604. UTIL_TraceLine( visibleSpot, check, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
  605. if (result.fraction < 1.0f)
  606. {
  607. float range = result.endpos.y - visibleSpot.y;
  608. if (range < bufferRange)
  609. {
  610. visibleSpot.y = result.endpos.y - bufferRange;
  611. }
  612. }
  613. // check -Y
  614. check = visibleSpot + Vector( 0, -999.9f, 0 );
  615. UTIL_TraceLine( visibleSpot, check, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
  616. if (result.fraction < 1.0f)
  617. {
  618. float range = visibleSpot.y - result.endpos.y;
  619. if (range < bufferRange)
  620. {
  621. visibleSpot.y = result.endpos.y + bufferRange;
  622. }
  623. }
  624. *pos = visibleSpot;
  625. return true;
  626. }
  627. //--------------------------------------------------------------------------------------------------------------
  628. /**
  629. * Look for grenade throw targets and throw the grenade
  630. */
  631. void CCSBot::LookForGrenadeTargets( void )
  632. {
  633. if (!IsUsingGrenade() || IsThrowingGrenade())
  634. {
  635. return;
  636. }
  637. const CNavArea *tossArea = GetInitialEncounterArea();
  638. if (tossArea == NULL)
  639. {
  640. return;
  641. }
  642. int enemyTeam = OtherTeam( GetTeamNumber() );
  643. // check if we should put our grenade away
  644. if (tossArea->GetEarliestOccupyTime( enemyTeam ) > gpGlobals->curtime)
  645. {
  646. EquipBestWeapon( MUST_EQUIP );
  647. return;
  648. }
  649. // throw grenades at initial encounter area
  650. Vector tossTarget = Vector( 0, 0, 0 );
  651. if (!tossArea->IsVisible( EyePosition(), &tossTarget ))
  652. {
  653. return;
  654. }
  655. CWeaponCSBase *weapon = GetActiveCSWeapon();
  656. if (weapon && weapon->IsA( WEAPON_SMOKEGRENADE ))
  657. {
  658. // don't worry so much about smokes
  659. ThrowGrenade( tossTarget );
  660. PrintIfWatched( "Throwing smoke grenade!" );
  661. SetInitialEncounterArea( NULL );
  662. return;
  663. }
  664. else // explosive and flashbang grenades
  665. {
  666. // initial encounter area is visible, wait to throw until timing is right
  667. const float leadTime = 1.5f;
  668. float enemyTime = tossArea->GetEarliestOccupyTime( enemyTeam );
  669. if (enemyTime - TheCSBots()->GetElapsedRoundTime() > leadTime)
  670. {
  671. // don't throw yet
  672. return;
  673. }
  674. Vector to = tossTarget - EyePosition();
  675. float range = to.Length();
  676. const float slope = 0.2f; // 0.25f;
  677. float tossHeight = slope * range;
  678. trace_t result;
  679. CTraceFilterNoNPCsOrPlayer traceFilter( this, COLLISION_GROUP_NONE );
  680. const float heightInc = tossHeight / 10.0f;
  681. Vector target;
  682. float safeSpace = tossHeight / 2.0f;
  683. // Build a box to sweep along the ray when looking for obstacles
  684. const Vector& eyePosition = EyePosition();
  685. Vector mins = VEC_HULL_MIN;
  686. Vector maxs = VEC_HULL_MAX;
  687. mins.z = 0;
  688. maxs.z = heightInc;
  689. // find low and high bounds of toss window
  690. float low = 0.0f;
  691. float high = tossHeight + safeSpace;
  692. bool gotLow = false;
  693. float lastH = 0.0f;
  694. for( float h = 0.0f; h < 3.0f * tossHeight; h += heightInc )
  695. {
  696. target = tossTarget + Vector( 0, 0, h );
  697. // make sure toss line is clear
  698. QAngle angles( 0, 0, 0 );
  699. Ray_t ray;
  700. ray.Init( eyePosition, target, mins, maxs );
  701. enginetrace->TraceRay( ray, MASK_VISIBLE_AND_NPCS | CONTENTS_GRATE, &traceFilter, &result );
  702. if (result.fraction == 1.0f)
  703. {
  704. //NDebugOverlay::SweptBox( eyePosition, target, mins, maxs, angles, 0, 0, 255, 40, 10.0f );
  705. // line is clear
  706. if (!gotLow)
  707. {
  708. low = h;
  709. gotLow = true;
  710. }
  711. }
  712. else
  713. {
  714. //NDebugOverlay::SweptBox( eyePosition, target, mins, maxs, angles, 255, 0, 0, 5, 10.0f );
  715. // line is blocked
  716. if (gotLow)
  717. {
  718. high = lastH;
  719. break;
  720. }
  721. }
  722. lastH = h;
  723. }
  724. if (gotLow)
  725. {
  726. // throw grenade into toss window
  727. if (tossHeight < low)
  728. {
  729. if (low + safeSpace > high)
  730. {
  731. // narrow window
  732. tossHeight = (high + low)/2.0f;
  733. }
  734. else
  735. {
  736. tossHeight = low + safeSpace;
  737. }
  738. }
  739. else if (tossHeight > high - safeSpace)
  740. {
  741. if (high - safeSpace < low)
  742. {
  743. // narrow window
  744. tossHeight = (high + low)/2.0f;
  745. }
  746. else
  747. {
  748. tossHeight = high - safeSpace;
  749. }
  750. }
  751. ThrowGrenade( tossTarget + Vector( 0, 0, tossHeight ) );
  752. SetInitialEncounterArea( NULL );
  753. return;
  754. }
  755. }
  756. }
  757. //--------------------------------------------------------------------------------------------------------------
  758. class FOVClearOfFriends
  759. {
  760. public:
  761. FOVClearOfFriends( CCSBot *me )
  762. {
  763. m_me = me;
  764. }
  765. bool operator() ( CBasePlayer *player )
  766. {
  767. if (player == m_me || !player->IsAlive())
  768. return true;
  769. if (m_me->InSameTeam( player ))
  770. {
  771. Vector to = player->EyePosition() - m_me->EyePosition();
  772. to.NormalizeInPlace();
  773. Vector forward;
  774. m_me->EyeVectors( &forward );
  775. if (DotProduct( to, forward ) > 0.95f)
  776. {
  777. if (m_me->IsVisible( (CCSPlayer *)player ))
  778. {
  779. // we see a friend in our FOV
  780. return false;
  781. }
  782. }
  783. }
  784. return true;
  785. }
  786. CCSBot *m_me;
  787. };
  788. //--------------------------------------------------------------------------------------------------------------
  789. /**
  790. * Process the grenade throw state machine
  791. */
  792. void CCSBot::UpdateGrenadeThrow( void )
  793. {
  794. switch( m_grenadeTossState )
  795. {
  796. case START_THROW:
  797. {
  798. if (m_tossGrenadeTimer.IsElapsed())
  799. {
  800. // something prevented the throw - give up
  801. EquipBestWeapon( MUST_EQUIP );
  802. ClearLookAt();
  803. m_grenadeTossState = NOT_THROWING;
  804. PrintIfWatched( "%3.2f: Grenade: THROW FAILED\n", gpGlobals->curtime );
  805. return;
  806. }
  807. if (m_lookAtSpotState == LOOK_AT_SPOT)
  808. {
  809. // don't throw if there are friends ahead of us
  810. FOVClearOfFriends fovClear( this );
  811. if (ForEachPlayer( fovClear ))
  812. {
  813. m_grenadeTossState = FINISH_THROW;
  814. m_tossGrenadeTimer.Start( 1.0f );
  815. PrintIfWatched( "%3.2f: Grenade: FINISH_THROW\n", gpGlobals->curtime );
  816. }
  817. else
  818. {
  819. PrintIfWatched( "%3.2f: Grenade: Friend is in the way...\n", gpGlobals->curtime );
  820. }
  821. }
  822. // hold in the trigger and be ready to throw
  823. PrimaryAttack();
  824. break;
  825. }
  826. case FINISH_THROW:
  827. {
  828. // throw the grenade and hold our aiming line for a moment
  829. if (m_tossGrenadeTimer.IsElapsed())
  830. {
  831. ClearLookAt();
  832. m_grenadeTossState = NOT_THROWING;
  833. PrintIfWatched( "%3.2f: Grenade: THROW COMPLETE\n", gpGlobals->curtime );
  834. }
  835. break;
  836. }
  837. default:
  838. {
  839. if (IsUsingGrenade())
  840. {
  841. // pull the pin
  842. PrimaryAttack();
  843. }
  844. break;
  845. }
  846. }
  847. }
  848. //--------------------------------------------------------------------------------------------------------------
  849. class GrenadeResponse
  850. {
  851. public:
  852. GrenadeResponse( CCSBot *me )
  853. {
  854. m_me = me;
  855. }
  856. bool operator() ( ActiveGrenade *ag ) const
  857. {
  858. const float retreatRange = 300.0f;
  859. const float hideTime = 1.0f;
  860. // do we see this grenade
  861. if (m_me->IsVisible( ag->GetPosition(), CHECK_FOV, (CBaseEntity *)ag->GetEntity() ))
  862. {
  863. // we see it
  864. if (ag->IsSmoke())
  865. {
  866. // ignore smokes
  867. return true;
  868. }
  869. Vector velDir = ag->GetEntity()->GetAbsVelocity();
  870. float grenadeSpeed = velDir.NormalizeInPlace();
  871. const float atRestSpeed = 50.0f;
  872. const float aboutToBlow = 0.5f;
  873. if (ag->IsFlashbang() && ag->GetEntity()->m_flDetonateTime - gpGlobals->curtime < aboutToBlow)
  874. {
  875. // turn away from flashbangs about to explode
  876. QAngle eyeAngles = m_me->EyeAngles();
  877. float yaw = RandomFloat( 100.0f, 135.0f );
  878. eyeAngles.y += (RandomFloat( -1.0f, 1.0f ) < 0.0f) ? (-yaw) : yaw;
  879. Vector forward;
  880. AngleVectors( eyeAngles, &forward );
  881. Vector away = m_me->EyePosition() - 1000.0f * forward;
  882. const float duration = 2.0f;
  883. m_me->ClearLookAt();
  884. m_me->SetLookAt( "Avoid Flashbang", away, PRIORITY_UNINTERRUPTABLE, duration );
  885. m_me->StopAiming();
  886. return false;
  887. }
  888. // flee from grenades if close by or thrown towards us
  889. const float throwDangerRange = 750.0f;
  890. const float nearDangerRange = 300.0f;
  891. Vector to = ag->GetPosition() - m_me->GetAbsOrigin();
  892. float range = to.NormalizeInPlace();
  893. if (range > throwDangerRange)
  894. {
  895. return true;
  896. }
  897. if (grenadeSpeed > atRestSpeed)
  898. {
  899. // grenade is moving
  900. if (DotProduct( to, velDir ) >= -0.5f)
  901. {
  902. // going away from us
  903. return true;
  904. }
  905. m_me->PrintIfWatched( "Retreating from a grenade thrown towards me!\n" );
  906. }
  907. else if (range < nearDangerRange)
  908. {
  909. // grenade has come to rest near us
  910. m_me->PrintIfWatched( "Retreating from a grenade that landed near me!\n" );
  911. }
  912. // retreat!
  913. m_me->TryToRetreat( retreatRange, hideTime );
  914. return false;
  915. }
  916. return true;
  917. }
  918. CCSBot *m_me;
  919. };
  920. /**
  921. * React to enemy grenades we see
  922. */
  923. void CCSBot::AvoidEnemyGrenades( void )
  924. {
  925. // low skill bots dont avoid grenades
  926. if (GetProfile()->GetSkill() < 0.5)
  927. {
  928. return;
  929. }
  930. if (IsAvoidingGrenade())
  931. {
  932. // already avoiding one
  933. return;
  934. }
  935. // low skill bots don't avoid grenades
  936. if (GetProfile()->GetSkill() < 0.6f)
  937. {
  938. return;
  939. }
  940. GrenadeResponse respond( this );
  941. if (TheBots->ForEachGrenade( respond ) == false)
  942. {
  943. const float avoidTime = 4.0f;
  944. m_isAvoidingGrenade.Start( avoidTime );
  945. }
  946. }
  947. //--------------------------------------------------------------------------------------------------------------
  948. /**
  949. * Reload our weapon if we must
  950. */
  951. void CCSBot::ReloadCheck( void )
  952. {
  953. const float safeReloadWaitTime = 3.0f;
  954. const float reloadAmmoRatio = 0.6f;
  955. // don't bother to reload if there are no enemies left
  956. if (GetEnemiesRemaining() == 0)
  957. return;
  958. if (IsDefusingBomb() || IsReloading())
  959. return;
  960. if (IsActiveWeaponClipEmpty())
  961. {
  962. // high-skill players switch to pistol instead of reloading during combat
  963. if (GetProfile()->GetSkill() > 0.5f && IsAttacking())
  964. {
  965. if (!GetActiveCSWeapon()->IsPistol() && !IsPistolEmpty())
  966. {
  967. // switch to pistol instead of reloading
  968. EquipPistol();
  969. return;
  970. }
  971. }
  972. }
  973. else if (GetTimeSinceLastSawEnemy() > safeReloadWaitTime && GetActiveWeaponAmmoRatio() <= reloadAmmoRatio)
  974. {
  975. // high-skill players use all their ammo and switch to pistol instead of reloading during combat
  976. if (GetProfile()->GetSkill() > 0.5f && IsAttacking())
  977. return;
  978. }
  979. else
  980. {
  981. // do not need to reload
  982. return;
  983. }
  984. // don't reload the AWP until it is totally out of ammo
  985. if (IsUsing( WEAPON_AWP ) && !IsActiveWeaponClipEmpty())
  986. return;
  987. Reload();
  988. // move to cover to reload if there are enemies nearby
  989. if (GetNearbyEnemyCount())
  990. {
  991. // avoid enemies while reloading (above 0.75 skill always hide to reload)
  992. const float hideChance = 25.0f + 100.0f * GetProfile()->GetSkill();
  993. if (!IsHiding() && RandomFloat( 0, 100 ) < hideChance)
  994. {
  995. const float safeTime = 5.0f;
  996. if (GetTimeSinceLastSawEnemy() < safeTime)
  997. {
  998. PrintIfWatched( "Retreating to a safe spot to reload!\n" );
  999. const Vector *spot = FindNearbyRetreatSpot( this, 1000.0f );
  1000. if (spot)
  1001. {
  1002. // ignore enemies for a second to give us time to hide
  1003. // reaching our hiding spot clears our disposition
  1004. IgnoreEnemies( 10.0f );
  1005. Run();
  1006. StandUp();
  1007. Hide( *spot, 0.0f );
  1008. }
  1009. }
  1010. }
  1011. }
  1012. }
  1013. //--------------------------------------------------------------------------------------------------------------
  1014. /**
  1015. * Silence/unsilence our weapon if we must
  1016. */
  1017. void CCSBot::SilencerCheck( void )
  1018. {
  1019. const float safeSilencerWaitTime = 3.5f; // longer than reload check because reloading should take precedence
  1020. if (IsDefusingBomb() || IsReloading() || IsAttacking())
  1021. return;
  1022. // M4A1 and USP are the only weapons with removable silencers
  1023. if (!DoesActiveWeaponHaveSilencer())
  1024. return;
  1025. if (GetTimeSinceLastSawEnemy() < safeSilencerWaitTime)
  1026. return;
  1027. // don't touch the silencer if there are enemies nearby
  1028. if (GetNearbyEnemyCount() == 0)
  1029. {
  1030. CWeaponCSBase *weapon = GetActiveCSWeapon();
  1031. if (weapon == NULL)
  1032. return;
  1033. bool isSilencerOn = weapon->IsSilenced();
  1034. if ( weapon->m_flNextSecondaryAttack >= gpGlobals->curtime )
  1035. return;
  1036. // equip silencer if we want to and we don't have a shield.
  1037. if ( isSilencerOn != (GetProfile()->PrefersSilencer() || GetProfile()->GetSkill() > 0.7f) && !HasShield() )
  1038. {
  1039. PrintIfWatched( "%s silencer!\n", (isSilencerOn) ? "Unequipping" : "Equipping" );
  1040. weapon->SecondaryAttack();
  1041. }
  1042. }
  1043. }
  1044. //--------------------------------------------------------------------------------------------------------------
  1045. /**
  1046. * Invoked when in contact with a CBaseCombatWeapon
  1047. */
  1048. bool CCSBot::BumpWeapon( CBaseCombatWeapon *pWeapon )
  1049. {
  1050. CWeaponCSBase *droppedGun = dynamic_cast< CWeaponCSBase* >( pWeapon );
  1051. // right now we only care about primary weapons on the ground
  1052. if ( droppedGun && droppedGun->GetSlot() == WEAPON_SLOT_RIFLE )
  1053. {
  1054. CWeaponCSBase *myGun = dynamic_cast< CWeaponCSBase* >( Weapon_GetSlot( WEAPON_SLOT_RIFLE ) );
  1055. // if the gun on the ground is the same one we have, dont bother
  1056. if ( myGun && droppedGun->GetWeaponID() != myGun->GetWeaponID() )
  1057. {
  1058. // if we don't have a weapon preference, give up
  1059. if ( GetProfile()->HasPrimaryPreference() )
  1060. {
  1061. // don't change weapons if we've seen enemies recently
  1062. const float safeTime = 2.5f;
  1063. if ( GetTimeSinceLastSawEnemy() >= safeTime )
  1064. {
  1065. // we have a primary weapon - drop it if the one on the ground is better
  1066. for( int i = 0; i < GetProfile()->GetWeaponPreferenceCount(); ++i )
  1067. {
  1068. CSWeaponID prefID = GetProfile()->GetWeaponPreference( i );
  1069. if (!IsPrimaryWeapon( prefID ))
  1070. continue;
  1071. // if the gun we are using is more desirable, give up
  1072. if ( prefID == myGun->GetWeaponID() )
  1073. break;
  1074. if ( prefID == droppedGun->GetWeaponID() )
  1075. {
  1076. // the gun on the ground is better than the one we have - drop our gun
  1077. DropRifle();
  1078. break;
  1079. }
  1080. }
  1081. }
  1082. }
  1083. }
  1084. }
  1085. return BaseClass::BumpWeapon( droppedGun );
  1086. }
  1087. //--------------------------------------------------------------------------------------------------------------
  1088. /**
  1089. * Return true if a friend is in our weapon's way
  1090. * @todo Check more rays for safety.
  1091. */
  1092. bool CCSBot::IsFriendInLineOfFire( void )
  1093. {
  1094. // compute the unit vector along our view
  1095. Vector aimDir = GetViewVector();
  1096. // trace the bullet's path
  1097. trace_t result;
  1098. UTIL_TraceLine( EyePosition(), EyePosition() + 10000.0f * aimDir, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
  1099. if (result.DidHitNonWorldEntity())
  1100. {
  1101. CBaseEntity *victim = result.m_pEnt;
  1102. if (victim && victim->IsPlayer() && victim->IsAlive())
  1103. {
  1104. CBasePlayer *player = static_cast<CBasePlayer *>( victim );
  1105. if (player->InSameTeam( this ))
  1106. return true;
  1107. }
  1108. }
  1109. return false;
  1110. }
  1111. //--------------------------------------------------------------------------------------------------------------
  1112. /**
  1113. * Return line-of-sight distance to obstacle along weapon fire ray
  1114. * @todo Re-use this computation with IsFriendInLineOfFire()
  1115. */
  1116. float CCSBot::ComputeWeaponSightRange( void )
  1117. {
  1118. // compute the unit vector along our view
  1119. Vector aimDir = GetViewVector();
  1120. // trace the bullet's path
  1121. trace_t result;
  1122. UTIL_TraceLine( EyePosition(), EyePosition() + 10000.0f * aimDir, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
  1123. return (EyePosition() - result.endpos).Length();
  1124. }
  1125. //--------------------------------------------------------------------------------------------------------------
  1126. /**
  1127. * Return true if the given player just fired their weapon
  1128. */
  1129. bool CCSBot::DidPlayerJustFireWeapon( const CCSPlayer *player ) const
  1130. {
  1131. // if this player has just fired his weapon, we notice him
  1132. CWeaponCSBase *weapon = player->GetActiveCSWeapon();
  1133. return (weapon && !weapon->IsSilenced() && weapon->m_flNextPrimaryAttack > gpGlobals->curtime);
  1134. }