Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1326 lines
33 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. // Author: Michael S. Booth ([email protected]), 2003
  8. #include "cbase.h"
  9. #include "cs_gamerules.h"
  10. #include "cs_bot.h"
  11. #include "fmtstr.h"
  12. #include "cs_simple_hostage.h"
  13. // memdbgon must be the last include file in a .cpp file!!!
  14. #include "tier0/memdbgon.h"
  15. //-----------------------------------------------------------------------------------------------------------
  16. float CCSBot::GetMoveSpeed( void )
  17. {
  18. return 250.0f;
  19. }
  20. //--------------------------------------------------------------------------------------------------------------
  21. /**
  22. * Lightweight maintenance, invoked frequently
  23. */
  24. void CCSBot::Upkeep( void )
  25. {
  26. VPROF_BUDGET( "CCSBot::Upkeep", VPROF_BUDGETGROUP_NPCS );
  27. if (TheNavMesh->IsGenerating() || !IsAlive())
  28. return;
  29. // If bot_flipout is on, then generate some random commands.
  30. if ( cv_bot_flipout.GetBool() )
  31. {
  32. int val = RandomInt( 0, 2 );
  33. if ( val == 0 )
  34. MoveForward();
  35. else if ( val == 1 )
  36. MoveBackward();
  37. val = RandomInt( 0, 2 );
  38. if ( val == 0 )
  39. StrafeLeft();
  40. else if ( val == 1 )
  41. StrafeRight();
  42. if ( RandomInt( 0, 5 ) == 0 )
  43. Jump( true );
  44. val = RandomInt( 0, 2 );
  45. if ( val == 0 )
  46. Crouch();
  47. else ( val == 1 );
  48. StandUp();
  49. return;
  50. }
  51. // BOTPORT: Remove this nasty hack
  52. m_eyePosition = EyePosition();
  53. Vector myOrigin = GetCentroid( this );
  54. if ( m_bIsSleeping )
  55. return;
  56. // aiming must be smooth - update often
  57. if ( IsAimingAtEnemy() )
  58. {
  59. if (gpGlobals->curtime >= m_aimFocusNextUpdate)
  60. {
  61. PickNewAimSpot();
  62. }
  63. UpdateAimPrediction();
  64. SetLookAngles( m_aimGoal[YAW], m_aimGoal[PITCH] );
  65. }
  66. else
  67. {
  68. if (m_lookAtSpotClearIfClose)
  69. {
  70. // don't look at spots just in front of our face - it causes erratic view rotation
  71. const float tooCloseRange = 100.0f;
  72. if ((m_lookAtSpot - myOrigin).IsLengthLessThan( tooCloseRange ))
  73. m_lookAtSpotState = NOT_LOOKING_AT_SPOT;
  74. }
  75. switch( m_lookAtSpotState )
  76. {
  77. case NOT_LOOKING_AT_SPOT:
  78. {
  79. // look ahead
  80. SetLookAngles( m_lookAheadAngle, 0.0f );
  81. break;
  82. }
  83. case LOOK_TOWARDS_SPOT:
  84. {
  85. UpdateLookAt();
  86. if (IsLookingAtPosition( m_lookAtSpot, m_lookAtSpotAngleTolerance ))
  87. {
  88. m_lookAtSpotState = LOOK_AT_SPOT;
  89. m_lookAtSpotTimestamp = gpGlobals->curtime;
  90. }
  91. break;
  92. }
  93. case LOOK_AT_SPOT:
  94. {
  95. UpdateLookAt();
  96. if (m_lookAtSpotDuration >= 0.0f && gpGlobals->curtime - m_lookAtSpotTimestamp > m_lookAtSpotDuration)
  97. {
  98. m_lookAtSpotState = NOT_LOOKING_AT_SPOT;
  99. m_lookAtSpotDuration = 0.0f;
  100. }
  101. break;
  102. }
  103. }
  104. // have view "drift" very slowly, so view looks "alive"
  105. if (!IsUsingSniperRifle())
  106. {
  107. float driftAmplitude = 2.0f;
  108. if (IsBlind())
  109. {
  110. driftAmplitude = 5.0f;
  111. }
  112. m_lookYaw += driftAmplitude * BotCOS( 33.0f * gpGlobals->curtime );
  113. m_lookPitch += driftAmplitude * BotSIN( 13.0f * gpGlobals->curtime );
  114. }
  115. }
  116. // view angles can change quickly
  117. UpdateLookAngles();
  118. }
  119. void CCSBot::CoopUpdateChecks()
  120. {
  121. // check to see if the bot hasn't seen anyone in a long time and if so, kill them and recycle their spot
  122. if ( CSGameRules() && CSGameRules()->IsPlayingCoopGuardian() &&
  123. IsAlive() &&
  124. !CSGameRules()->IsFreezePeriod() &&
  125. !CSGameRules()->IsWarmupPeriod() &&
  126. !CSGameRules()->IsRoundOver() )
  127. {
  128. float flStart = CSGameRules()->GetRoundStartTime();
  129. if ( m_spawnedTime > flStart )
  130. flStart = m_spawnedTime;
  131. // this waits 70 seconds from the last time they spawned or the round start
  132. // and sees if they've seen an enemy in the last 15 seconds
  133. // if not, they get removed
  134. // TODO: put the 70 and 15 on convars
  135. if ( m_nextCleanupCheckTimestamp < gpGlobals->curtime && flStart > 0
  136. && IsEnemyVisible() == false && GetTimeSinceLastSawEnemy() > 20 )
  137. {
  138. if ( CSGameRules()->IsHostageRescueMap() && (gpGlobals->curtime - flStart) > 50
  139. && GetTask() != CCSBot::COLLECT_HOSTAGES )
  140. {
  141. CBaseEntity *hostage = static_cast<CBaseEntity*>( GetGameState()->GetNearestFreeHostage() );
  142. if ( hostage )
  143. {
  144. // here we want to force them to run for the hostages, no questions asked
  145. // go get hostages
  146. SetTask( CCSBot::COLLECT_HOSTAGES );
  147. ForceRun( 3.0 );
  148. SetGoalEntity( hostage );
  149. ResetWaitForHostagePatience();
  150. SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
  151. MoveTo( GetCentroid( hostage ), FASTEST_ROUTE );
  152. PrintIfWatched( "I'm collecting hostages\n" );
  153. }
  154. }
  155. else if ( (gpGlobals->curtime - flStart) > 140 )
  156. {
  157. bool bSeen = false;
  158. int nTeam = CSGameRules()->IsHostageRescueMap() ? TEAM_TERRORIST : TEAM_CT;
  159. // last check is to make sure a player can't see them
  160. for ( int i = 1; i <= MAX_PLAYERS; i++ )
  161. {
  162. CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
  163. if ( pPlayer && pPlayer->IsAlive() && pPlayer->GetTeamNumber() == nTeam )
  164. {
  165. if ( IsVisible( pPlayer->GetAbsOrigin() + Vector( 0, 0, 64 ), false, pPlayer ) )
  166. {
  167. bSeen = true;
  168. break;
  169. }
  170. }
  171. }
  172. const Vector *vecGoal = GetGameState()->GetRandomFreeHostagePosition();
  173. if ( CSGameRules()->IsHostageRescueMap() == false )
  174. {
  175. int zoneIndex = GetGameState()->GetNextBombsiteToSearch();
  176. // move to bombsite - if we reach it, we'll update its cleared status, causing us to select another
  177. vecGoal = TheCSBots()->GetRandomPositionInZone( TheCSBots()->GetZone( zoneIndex ) );
  178. }
  179. if ( vecGoal && IsVisible( *vecGoal, false, NULL ) )
  180. {
  181. bSeen = true;
  182. }
  183. if ( bSeen == false )
  184. CommitSuicide();
  185. }
  186. m_nextCleanupCheckTimestamp = gpGlobals->curtime + 0.5f;
  187. }
  188. // Make sure bots know the bomb state in guardian. Don't require them to actually see it.
  189. if ( CBaseEntity* pBomb = TheCSBots()->GetLooseBomb() )
  190. {
  191. GetGameState()->UpdateLooseBomb( pBomb->GetAbsOrigin() );
  192. }
  193. }
  194. }
  195. //--------------------------------------------------------------------------------------------------------------
  196. /**
  197. * Heavyweight processing, invoked less often
  198. */
  199. void CCSBot::Update( void )
  200. {
  201. VPROF_BUDGET( "CCSBot::Update", VPROF_BUDGETGROUP_NPCS );
  202. // If bot_flipout is on, then we only do stuff in Upkeep().
  203. if ( cv_bot_flipout.GetBool() )
  204. return;
  205. Vector myOrigin = GetCentroid( this );
  206. // if we're sleeping and essentially dormant, check to see if there is a CT near us to wake us up
  207. if ( m_bIsSleeping && CSGameRules()->IsPlayingCooperativeGametype() )
  208. {
  209. for ( int i = 1; i <= MAX_PLAYERS; i++ )
  210. {
  211. CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
  212. if ( pPlayer && pPlayer->GetTeamNumber() == TEAM_CT )
  213. {
  214. Vector vecPlayer = pPlayer->GetAbsOrigin() + Vector( 0, 0, 64 );
  215. if ( !IsBeyondBotMaxVisionDistance( vecPlayer ) && IsVisible( vecPlayer, false, pPlayer ) )
  216. {
  217. // get us going ASAP
  218. m_bIsSleeping = false;
  219. m_lookAtSpotState = NOT_LOOKING_AT_SPOT;
  220. // if we are a heavy, shout a taunt!
  221. if ( HasHeavyArmor() )
  222. {
  223. GetChatter()->DoPhoenixHeavyWakeTaunt();
  224. }
  225. break;
  226. }
  227. }
  228. }
  229. if ( m_bIsSleeping )
  230. return;
  231. }
  232. // if we are spectating, get into the game
  233. if (GetTeamNumber() == 0)
  234. {
  235. HandleCommand_JoinTeam( m_desiredTeam );
  236. HandleCommand_JoinClass();
  237. return;
  238. }
  239. // update our radio chatter
  240. // need to allow bots to finish their chatter even if they are dead
  241. GetChatter()->Update();
  242. // check if we are dead
  243. if (!IsAlive())
  244. {
  245. // remember that we died
  246. m_diedLastRound = true;
  247. BotDeathThink();
  248. return;
  249. }
  250. // the bot is alive and in the game at this point
  251. m_hasJoined = true;
  252. //
  253. // Debug beam rendering
  254. //
  255. if (cv_bot_debug.GetBool() && IsLocalPlayerWatchingMe())
  256. {
  257. DebugDisplay();
  258. }
  259. if (cv_bot_stop.GetBool())
  260. return;
  261. // check if we are stuck
  262. StuckCheck();
  263. // Check for physics props and other breakables in our way that we can break
  264. BreakablesCheck();
  265. // Check for useable doors in our way that we need to open
  266. DoorCheck();
  267. CoopUpdateChecks();
  268. // update travel distance to all players (this is an optimization)
  269. // EDIT: actually this is really slow and only used to detect audible events; we're using straight line dist instead
  270. //UpdateTravelDistanceToAllPlayers();
  271. // if our current 'noise' was heard a long time ago, forget it
  272. const float rememberNoiseDuration = 20.0f;
  273. if (m_noiseTimestamp > 0.0f && gpGlobals->curtime - m_noiseTimestamp > rememberNoiseDuration)
  274. {
  275. ForgetNoise();
  276. }
  277. // where are we
  278. if (!m_currentArea || !m_currentArea->Contains( myOrigin ))
  279. {
  280. m_currentArea = (CCSNavArea *)TheNavMesh->GetNavArea( myOrigin );
  281. }
  282. // track the last known area we were in
  283. if (m_currentArea && m_currentArea != m_lastKnownArea)
  284. {
  285. m_lastKnownArea = m_currentArea;
  286. OnEnteredNavArea( m_currentArea );
  287. }
  288. // keep track of how long we have been motionless
  289. const float stillSpeed = 10.0f;
  290. if (GetAbsVelocity().IsLengthLessThan( stillSpeed ))
  291. {
  292. m_stillTimer.Start();
  293. }
  294. else
  295. {
  296. m_stillTimer.Invalidate();
  297. }
  298. // if we're blind, retreat!
  299. if (IsBlind())
  300. {
  301. if (m_blindFire)
  302. {
  303. PrimaryAttack();
  304. }
  305. }
  306. UpdatePanicLookAround();
  307. //
  308. // Enemy acquisition and attack initiation
  309. //
  310. // take a snapshot and update our reaction time queue
  311. UpdateReactionQueue();
  312. // "threat" may be the same as our current enemy
  313. CCSPlayer *threat = GetRecognizedEnemy();
  314. if (threat)
  315. {
  316. SNPROF("(threat)");
  317. Vector threatOrigin = GetCentroid( threat );
  318. // adjust our personal "safe" time
  319. AdjustSafeTime();
  320. BecomeAlert();
  321. const float selfDefenseRange = 500.0f; // 750.0f;
  322. const float farAwayRange = 2000.0f;
  323. //
  324. // Decide if we should attack
  325. //
  326. bool doAttack = false;
  327. switch( GetDisposition() )
  328. {
  329. case IGNORE_ENEMIES:
  330. {
  331. // never attack
  332. doAttack = false;
  333. break;
  334. }
  335. case SELF_DEFENSE:
  336. {
  337. // attack if fired on
  338. doAttack = (IsPlayerLookingAtMe( threat, 0.99f ) && DidPlayerJustFireWeapon( threat ));
  339. // attack if enemy very close
  340. if (!doAttack)
  341. {
  342. doAttack = (myOrigin - threatOrigin).IsLengthLessThan( selfDefenseRange );
  343. }
  344. if ( CSGameRules()->IsPlayingCoopMission() && IsSniper() )
  345. {
  346. // snipers love far away targets
  347. doAttack = true;
  348. }
  349. break;
  350. }
  351. case ENGAGE_AND_INVESTIGATE:
  352. case OPPORTUNITY_FIRE:
  353. {
  354. if ((myOrigin - threatOrigin).IsLengthGreaterThan( farAwayRange ))
  355. {
  356. // enemy is very far away - wait to take our shot until he is closer
  357. // unless we are a sniper or he is shooting at us
  358. if (IsSniper())
  359. {
  360. // snipers love far away targets
  361. doAttack = true;
  362. }
  363. else
  364. {
  365. // attack if fired on
  366. doAttack = (IsPlayerLookingAtMe( threat, 0.99f ) && DidPlayerJustFireWeapon( threat ));
  367. }
  368. }
  369. else
  370. {
  371. // normal combat range
  372. doAttack = true;
  373. }
  374. break;
  375. }
  376. }
  377. // if we aren't attacking but we are being attacked, retaliate
  378. if (!doAttack && !IsAttacking() && GetDisposition() != IGNORE_ENEMIES)
  379. {
  380. const float recentAttackDuration = 1.0f;
  381. if (GetTimeSinceAttacked() < recentAttackDuration)
  382. {
  383. // we may not be attacking our attacker, but at least we're not just taking it
  384. // (since m_attacker isn't reaction-time delayed, we can't directly use it)
  385. doAttack = true;
  386. PrintIfWatched( "Ouch! Retaliating!\n" );
  387. }
  388. }
  389. if (doAttack)
  390. {
  391. if (!IsAttacking() || threat != GetBotEnemy())
  392. {
  393. if (IsUsingKnife() && IsHiding())
  394. {
  395. // if hiding with a knife, wait until threat is close
  396. const float knifeAttackRange = 250.0f;
  397. if ((GetAbsOrigin() - threat->GetAbsOrigin()).IsLengthLessThan( knifeAttackRange ))
  398. {
  399. Attack( threat );
  400. }
  401. }
  402. else
  403. {
  404. Attack( threat );
  405. }
  406. }
  407. }
  408. else
  409. {
  410. // dont attack, but keep track of nearby enemies
  411. SetBotEnemy( threat );
  412. m_isEnemyVisible = true;
  413. }
  414. TheCSBots()->SetLastSeenEnemyTimestamp();
  415. }
  416. //
  417. // Validate existing enemy, if any
  418. //
  419. if (m_enemy != NULL)
  420. {
  421. SNPROF("Validate existing enemy, if any");
  422. if (IsAwareOfEnemyDeath())
  423. {
  424. // we have noticed that our enemy has died
  425. m_enemy = NULL;
  426. m_isEnemyVisible = false;
  427. }
  428. else
  429. {
  430. // check LOS to current enemy (chest & head), in case he's dead (GetNearestEnemy() only returns live players)
  431. // note we're not checking FOV - once we've acquired an enemy (which does check FOV), assume we know roughly where he is
  432. if (IsVisible( m_enemy, false, &m_visibleEnemyParts ))
  433. {
  434. m_isEnemyVisible = true;
  435. m_lastSawEnemyTimestamp = gpGlobals->curtime;
  436. m_lastEnemyPosition = GetCentroid( m_enemy );
  437. }
  438. else
  439. {
  440. m_isEnemyVisible = false;
  441. if ( (gpGlobals->curtime - m_lastSawEnemyTimestamp ) > 1.0f && EquipGrenade() && CSGameRules()->IsPlayingCooperativeGametype() )
  442. {
  443. ThrowGrenade( m_lastEnemyPosition );
  444. }
  445. }
  446. // check if enemy died
  447. if (m_enemy->IsAlive())
  448. {
  449. m_enemyDeathTimestamp = 0.0f;
  450. m_isLastEnemyDead = false;
  451. }
  452. else if (m_enemyDeathTimestamp == 0.0f)
  453. {
  454. // note time of death (to allow bots to overshoot for a time)
  455. m_enemyDeathTimestamp = gpGlobals->curtime;
  456. m_isLastEnemyDead = true;
  457. }
  458. }
  459. }
  460. else
  461. {
  462. m_isEnemyVisible = false;
  463. }
  464. // if we have seen an enemy recently, keep an eye on him if we can
  465. {
  466. SNPROF("1");
  467. if (!IsBlind() && !IsLookingAtSpot(PRIORITY_UNINTERRUPTABLE) )
  468. {
  469. const float seenRecentTime = 3.0f;
  470. if (m_enemy != NULL && GetTimeSinceLastSawEnemy() < seenRecentTime)
  471. {
  472. AimAtEnemy();
  473. }
  474. else
  475. {
  476. StopAiming();
  477. }
  478. }
  479. else if( IsAimingAtEnemy() )
  480. {
  481. StopAiming();
  482. }
  483. }
  484. //
  485. // Hack to fire while retreating
  486. /// @todo Encapsulate aiming and firing on enemies separately from current task
  487. //
  488. if (GetDisposition() == IGNORE_ENEMIES)
  489. {
  490. SNPROF("Fire weapon at enemy");
  491. FireWeaponAtEnemy();
  492. }
  493. {
  494. SNPROF("2");
  495. // toss grenades
  496. LookForGrenadeTargets();
  497. // process grenade throw state machine
  498. UpdateGrenadeThrow();
  499. // avoid enemy grenades
  500. AvoidEnemyGrenades();
  501. }
  502. // check if our weapon is totally out of ammo
  503. // or if we no longer feel "safe", equip our weapon
  504. if (!IsSafe() && !IsUsingGrenade() && IsActiveWeaponOutOfAmmo())
  505. {
  506. EquipBestWeapon();
  507. }
  508. /// @todo This doesn't work if we are restricted to just knives and sniper rifles because we cant use the rifle at close range
  509. if (!IsSafe() && !IsUsingGrenade() && IsUsingKnife() && !IsEscapingFromBomb())
  510. {
  511. SNPROF("3");
  512. EquipBestWeapon();
  513. }
  514. // if we haven't seen an enemy in awhile, and we switched to our pistol during combat,
  515. // switch back to our primary weapon (if it still has ammo left)
  516. const float safeRearmTime = 5.0f;
  517. if (!IsReloading() && IsUsingPistol() && !IsPrimaryWeaponEmpty() && GetTimeSinceLastSawEnemy() > safeRearmTime)
  518. {
  519. SNPROF("4");
  520. EquipBestWeapon();
  521. }
  522. {
  523. SNPROF("5");
  524. // reload our weapon if we must
  525. ReloadCheck();
  526. // equip silencer
  527. SilencerCheck();
  528. // listen to the radio
  529. RespondToRadioCommands();
  530. }
  531. // make way
  532. const float avoidTime = 0.33f;
  533. if (gpGlobals->curtime - m_avoidTimestamp < avoidTime && m_avoid != NULL)
  534. {
  535. StrafeAwayFromPosition( GetCentroid( m_avoid ) );
  536. }
  537. else
  538. {
  539. m_avoid = NULL;
  540. }
  541. // if we're using a sniper rifle and are no longer attacking, stop looking thru scope
  542. if (!IsAtHidingSpot() && !IsAttacking() && IsUsingSniperRifle() && IsUsingScope())
  543. {
  544. SecondaryAttack();
  545. }
  546. if (!IsBlind())
  547. {
  548. SNPROF("6");
  549. // check encounter spots
  550. UpdatePeripheralVision();
  551. // watch for snipers
  552. if (CanSeeSniper() && !HasSeenSniperRecently())
  553. {
  554. SNPROF("watch for smipers");
  555. GetChatter()->SpottedSniper();
  556. const float sniperRecentInterval = 20.0f;
  557. m_sawEnemySniperTimer.Start( sniperRecentInterval );
  558. }
  559. //
  560. // Update gamestate
  561. //
  562. if (m_bomber != NULL)
  563. GetChatter()->SpottedBomber( GetBomber() );
  564. if (CanSeeLooseBomb())
  565. GetChatter()->SpottedLooseBomb( TheCSBots()->GetLooseBomb() );
  566. }
  567. // if we're burning, escape from the flames
  568. if ( GetTimeSinceBurnedByFlames() < 1.0f && !IsEscapingFromFlames() )
  569. {
  570. EscapeFromFlames();
  571. return;
  572. }
  573. //
  574. // Scenario interrupts
  575. //
  576. {
  577. SNPROF("Scenario interrupts");
  578. switch (TheCSBots()->GetScenario())
  579. {
  580. case CCSBotManager::SCENARIO_DEFUSE_BOMB:
  581. {
  582. // flee if the bomb is ready to blow and we aren't defusing it or attacking and we know where the bomb is
  583. // (aggressive players wait until its almost too late)
  584. float gonnaBlowTime = 8.0f - (2.0f * GetProfile()->GetAggression());
  585. // if we have a defuse kit, can wait longer
  586. if (m_bHasDefuser)
  587. gonnaBlowTime *= 0.66f;
  588. if (!IsEscapingFromBomb() && // we aren't already escaping the bomb
  589. TheCSBots()->IsBombPlanted() && // is the bomb planted
  590. GetGameState()->IsPlantedBombLocationKnown() && // we know where the bomb is
  591. TheCSBots()->GetBombTimeLeft() < gonnaBlowTime && // is the bomb about to explode
  592. !IsDefusingBomb() && // we aren't defusing the bomb
  593. !IsAttacking()) // we aren't in the midst of a firefight
  594. {
  595. EscapeFromBomb();
  596. break;
  597. }
  598. break;
  599. }
  600. case CCSBotManager::SCENARIO_RESCUE_HOSTAGES:
  601. {
  602. if (GetTeamNumber() == TEAM_CT)
  603. {
  604. UpdateHostageEscortCount();
  605. }
  606. else
  607. {
  608. // Terrorists have imperfect information on status of hostages
  609. unsigned char status = GetGameState()->ValidateHostagePositions();
  610. if (status & CSGameState::HOSTAGES_ALL_GONE)
  611. {
  612. GetChatter()->HostagesTaken();
  613. Idle();
  614. }
  615. else if (status & CSGameState::HOSTAGE_GONE)
  616. {
  617. GetGameState()->HostageWasTaken();
  618. Idle();
  619. }
  620. }
  621. break;
  622. }
  623. }
  624. }
  625. //
  626. // Follow nearby humans if our co-op is high and we have nothing else to do
  627. // If we were just following someone, don't auto-follow again for a short while to
  628. // give us a chance to do something else.
  629. //
  630. const float earliestAutoFollowTime = 5.0f;
  631. const float minAutoFollowTeamwork = 0.4f;
  632. if (cv_bot_auto_follow.GetBool() &&
  633. TheCSBots()->GetElapsedRoundTime() > earliestAutoFollowTime &&
  634. GetProfile()->GetTeamwork() > minAutoFollowTeamwork &&
  635. CanAutoFollow() &&
  636. !IsBusy() &&
  637. !IsFollowing() &&
  638. !IsBlind() &&
  639. !GetGameState()->IsAtPlantedBombsite())
  640. {
  641. SNPROF("7");
  642. // chance of following is proportional to teamwork attribute
  643. if (GetProfile()->GetTeamwork() > RandomFloat( 0.0f, 1.0f ))
  644. {
  645. CCSPlayer *leader = GetClosestVisibleHumanFriend();
  646. if (leader && leader->IsAutoFollowAllowed())
  647. {
  648. // count how many bots are already following this player
  649. const float maxFollowCount = 2;
  650. if (GetBotFollowCount( leader ) < maxFollowCount)
  651. {
  652. const float autoFollowRange = 300.0f;
  653. Vector leaderOrigin = GetCentroid( leader );
  654. if ((leaderOrigin - myOrigin).IsLengthLessThan( autoFollowRange ))
  655. {
  656. CNavArea *leaderArea = TheNavMesh->GetNavArea( leaderOrigin );
  657. if (leaderArea)
  658. {
  659. PathCost cost( this, FASTEST_ROUTE );
  660. float travelRange = NavAreaTravelDistance( GetLastKnownArea(), leaderArea, cost );
  661. if (travelRange >= 0.0f && travelRange < autoFollowRange)
  662. {
  663. // follow this human
  664. Follow( leader );
  665. PrintIfWatched( "Auto-Following %s\n", leader->GetPlayerName() );
  666. if (CSGameRules()->IsCareer())
  667. {
  668. GetChatter()->Say( "FollowingCommander", 10.0f );
  669. }
  670. else
  671. {
  672. GetChatter()->Say( "FollowingSir", 10.0f );
  673. }
  674. }
  675. }
  676. }
  677. }
  678. }
  679. }
  680. else
  681. {
  682. SNPROF("8");
  683. // we decided not to follow, don't re-check for a duration
  684. m_allowAutoFollowTime = gpGlobals->curtime + 15.0f + (1.0f - GetProfile()->GetTeamwork()) * 30.0f;
  685. }
  686. }
  687. if (IsFollowing())
  688. {
  689. SNPROF("9");
  690. // if we are following someone, make sure they are still alive
  691. CBaseEntity *leader = m_leader;
  692. if (leader == NULL || !leader->IsAlive())
  693. {
  694. StopFollowing();
  695. }
  696. // decide whether to continue following them
  697. const float highTeamwork = 0.85f;
  698. if (GetProfile()->GetTeamwork() < highTeamwork)
  699. {
  700. float minFollowDuration = 15.0f;
  701. if (GetFollowDuration() > minFollowDuration + 40.0f * GetProfile()->GetTeamwork())
  702. {
  703. // we are bored of following our leader
  704. StopFollowing();
  705. PrintIfWatched( "Stopping following - bored\n" );
  706. }
  707. }
  708. }
  709. //
  710. // Execute state machine
  711. //
  712. {
  713. SNPROF("9b");
  714. if (m_isOpeningDoor)
  715. {
  716. SNPROF("m_isOpeningDoor");
  717. // opening doors takes precedence over attacking because DoorCheck() will stop opening doors if
  718. // we don't have a knife out.
  719. m_openDoorState.OnUpdate( this );
  720. if (m_openDoorState.IsDone())
  721. {
  722. // open door behavior is finished, return to previous behavior context
  723. m_openDoorState.OnExit( this );
  724. m_isOpeningDoor = false;
  725. }
  726. }
  727. else if (m_isAttacking)
  728. {
  729. SNPROF("m_attackState.OnUpdate");
  730. m_attackState.OnUpdate( this );
  731. }
  732. else
  733. {
  734. TM_ZONE_FILTERED(TELEMETRY_LEVEL1, 50, TMZF_NONE, "%s", tmDynamicString(TELEMETRY_LEVEL0, m_state->GetName()));
  735. //SNPROF(m_state->GetName());
  736. m_state->OnUpdate( this );
  737. }
  738. // do wait behavior
  739. if (!IsAttacking() && IsWaiting())
  740. {
  741. SNPROF("do wait behavior1");
  742. ResetStuckMonitor();
  743. ClearMovement();
  744. }
  745. // don't move while reloading unless we see an enemy
  746. if (IsReloading() && !m_isEnemyVisible)
  747. {
  748. SNPROF("do wait behavior2");
  749. ResetStuckMonitor();
  750. ClearMovement();
  751. }
  752. }
  753. // if we get too far ahead of the hostages we are escorting, wait for them
  754. if (!IsAttacking() && m_inhibitWaitingForHostageTimer.IsElapsed())
  755. {
  756. SNPROF("10");
  757. const float waitForHostageRange = 500.0f;
  758. if ((GetTask() == COLLECT_HOSTAGES || GetTask() == RESCUE_HOSTAGES) && GetRangeToFarthestEscortedHostage() > waitForHostageRange)
  759. {
  760. if (!m_isWaitingForHostage)
  761. {
  762. // just started waiting
  763. m_isWaitingForHostage = true;
  764. m_waitForHostageTimer.Start( 10.0f );
  765. }
  766. else
  767. {
  768. // we've been waiting
  769. if (m_waitForHostageTimer.IsElapsed())
  770. {
  771. // give up waiting for awhile
  772. m_isWaitingForHostage = false;
  773. m_inhibitWaitingForHostageTimer.Start( 3.0f );
  774. }
  775. else
  776. {
  777. // keep waiting
  778. ResetStuckMonitor();
  779. ClearMovement();
  780. }
  781. }
  782. }
  783. }
  784. // remember our prior safe time status
  785. m_wasSafe = IsSafe();
  786. }
  787. //--------------------------------------------------------------------------------------------------------------
  788. class DrawTravelTime
  789. {
  790. public:
  791. DrawTravelTime( const CCSBot *me )
  792. {
  793. m_me = me;
  794. }
  795. bool operator() ( CBasePlayer *player )
  796. {
  797. if (player->IsAlive() && !m_me->InSameTeam( player ))
  798. {
  799. CFmtStr msg;
  800. player->EntityText( 0,
  801. msg.sprintf( "%3.0f", m_me->GetTravelDistanceToPlayer( (CCSPlayer *)player ) ),
  802. 0.1f );
  803. if (m_me->DidPlayerJustFireWeapon( ToCSPlayer( player ) ))
  804. {
  805. player->EntityText( 1, "BANG!", 0.1f );
  806. }
  807. }
  808. return true;
  809. }
  810. const CCSBot *m_me;
  811. };
  812. //--------------------------------------------------------------------------------------------------------------
  813. /**
  814. * Render bot debug info
  815. */
  816. void CCSBot::DebugDisplay( void ) const
  817. {
  818. const float duration = 0.15f;
  819. CFmtStr msg;
  820. NDebugOverlay::ScreenText( 0.5f, 0.34f, msg.sprintf( "Skill: %d%%", (int)(100.0f * GetProfile()->GetSkill()) ), 255, 255, 255, 150, duration );
  821. if ( m_pathLadder )
  822. {
  823. NDebugOverlay::ScreenText( 0.5f, 0.36f, msg.sprintf( "Ladder: %d", m_pathLadder->GetID() ), 255, 255, 255, 150, duration );
  824. }
  825. // show safe time
  826. float safeTime = GetSafeTimeRemaining();
  827. if (safeTime > 0.0f)
  828. {
  829. NDebugOverlay::ScreenText( 0.5f, 0.38f, msg.sprintf( "SafeTime: %3.2f", safeTime ), 255, 255, 255, 150, duration );
  830. }
  831. // show if blind
  832. if (IsBlind())
  833. {
  834. NDebugOverlay::ScreenText( 0.5f, 0.38f, msg.sprintf( "<<< BLIND >>>" ), 255, 255, 255, 255, duration );
  835. }
  836. // show if alert
  837. if (IsAlert())
  838. {
  839. NDebugOverlay::ScreenText( 0.5f, 0.38f, msg.sprintf( "ALERT" ), 255, 0, 0, 255, duration );
  840. }
  841. // show if panicked
  842. if (IsPanicking())
  843. {
  844. NDebugOverlay::ScreenText( 0.5f, 0.36f, msg.sprintf( "PANIC" ), 255, 255, 0, 255, duration );
  845. }
  846. // show behavior variables
  847. if (m_isAttacking)
  848. {
  849. NDebugOverlay::ScreenText( 0.5f, 0.4f, msg.sprintf( "ATTACKING: %s", GetBotEnemy()->GetPlayerName() ), 255, 0, 0, 255, duration );
  850. }
  851. else
  852. {
  853. NDebugOverlay::ScreenText( 0.5f, 0.4f, msg.sprintf( "State: %s", m_state->GetName() ), 255, 255, 0, 255, duration );
  854. NDebugOverlay::ScreenText( 0.5f, 0.42f, msg.sprintf( "Task: %s", GetTaskName() ), 0, 255, 0, 255, duration );
  855. NDebugOverlay::ScreenText( 0.5f, 0.44f, msg.sprintf( "Disposition: %s", GetDispositionName() ), 100, 100, 255, 255, duration );
  856. NDebugOverlay::ScreenText( 0.5f, 0.46f, msg.sprintf( "Morale: %s", GetMoraleName() ), 0, 200, 200, 255, duration );
  857. }
  858. // show look at status
  859. if (m_lookAtSpotState != NOT_LOOKING_AT_SPOT)
  860. {
  861. const char *string = msg.sprintf( "LookAt: %s (%s)", m_lookAtDesc, (m_lookAtSpotState == LOOK_TOWARDS_SPOT) ? "LOOK_TOWARDS_SPOT" : "LOOK_AT_SPOT" );
  862. NDebugOverlay::ScreenText( 0.5f, 0.60f, string, 255, 255, 0, 150, duration );
  863. }
  864. NDebugOverlay::ScreenText( 0.5f, 0.62f, msg.sprintf( "Steady view = %s", HasViewBeenSteady( 0.2f ) ? "YES" : "NO" ), 255, 255, 0, 150, duration );
  865. // show friend/foes I know of
  866. NDebugOverlay::ScreenText( 0.5f, 0.64f, msg.sprintf( "Nearby friends = %d", m_nearbyFriendCount ), 100, 255, 100, 150, duration );
  867. NDebugOverlay::ScreenText( 0.5f, 0.66f, msg.sprintf( "Nearby enemies = %d", m_nearbyEnemyCount ), 255, 100, 100, 150, duration );
  868. if ( m_lastNavArea )
  869. {
  870. NDebugOverlay::ScreenText( 0.5f, 0.68f, msg.sprintf( "Nav Area: %d (%s)", m_lastNavArea->GetID(), m_szLastPlaceName.Get() ), 255, 255, 255, 150, duration );
  871. /*
  872. if ( cv_bot_traceview.GetBool() )
  873. {
  874. if ( m_currentArea )
  875. {
  876. NDebugOverlay::Line( GetAbsOrigin(), m_currentArea->GetCenter(), 0, 255, 0, true, 0.1f );
  877. }
  878. else if ( m_lastKnownArea )
  879. {
  880. NDebugOverlay::Line( GetAbsOrigin(), m_lastKnownArea->GetCenter(), 255, 0, 0, true, 0.1f );
  881. }
  882. else if ( m_lastNavArea )
  883. {
  884. NDebugOverlay::Line( GetAbsOrigin(), m_lastNavArea->GetCenter(), 0, 0, 255, true, 0.1f );
  885. }
  886. }
  887. */
  888. }
  889. // show debug message history
  890. float y = 0.8f;
  891. const float lineHeight = 0.02f;
  892. const float fadeAge = 7.0f;
  893. const float maxAge = 10.0f;
  894. for( int i=0; i<TheBots->GetDebugMessageCount(); ++i )
  895. {
  896. const CBotManager::DebugMessage *msg = TheBots->GetDebugMessage( i );
  897. if (msg->m_age.GetElapsedTime() < maxAge)
  898. {
  899. int alpha = 255;
  900. if (msg->m_age.GetElapsedTime() > fadeAge)
  901. {
  902. alpha *= (1.0f - (msg->m_age.GetElapsedTime() - fadeAge) / (maxAge - fadeAge));
  903. }
  904. NDebugOverlay::ScreenText( 0.5f, y, msg->m_string, 255, 255, 255, alpha, duration );
  905. y += lineHeight;
  906. }
  907. }
  908. // show noises
  909. const Vector *noisePos = GetNoisePosition();
  910. if (noisePos)
  911. {
  912. const float size = 25.0f;
  913. NDebugOverlay::VertArrow( *noisePos + Vector( 0, 0, size ), *noisePos, size/4.0f, 255, 255, 0, 0, true, duration );
  914. }
  915. // show aim spot
  916. if (IsAimingAtEnemy())
  917. {
  918. // since this is executed on the server, we don't have a way of rendering something view relative,
  919. // so project out the aim vector to a distance corresponding to the target distance
  920. Vector toCurrent = m_targetSpot - EyePositionConst();
  921. float fDistance = toCurrent.Length();
  922. Vector aimVector;
  923. AngleVectors( m_aimGoal, &aimVector );
  924. Vector aimTarget = EyePositionConst() + aimVector * fDistance;
  925. NDebugOverlay::Cross3D( aimTarget, 8.0f, 255, 255, 0, true, duration );
  926. /*
  927. vgui::surface()->DrawSetColor( r, g, b, alpha );
  928. float fHalfFov = DEG2RAD( pPlayer->GetFOV() ) * 0.5f;
  929. float fSpreadDistance = ( GetInaccuracy() + GetSpread() ) * 320.0f / tanf( fHalfFov );
  930. int iSpreadDistance = RoundFloatToInt( YRES( fSpreadDistance ));
  931. vgui::surface()->DrawFilledRect( x0, y0, x1, y1 );
  932. */
  933. }
  934. if (IsHiding())
  935. {
  936. // show approach points
  937. DrawApproachPoints();
  938. }
  939. else
  940. {
  941. // show encounter spot data
  942. if (false && m_spotEncounter)
  943. {
  944. NDebugOverlay::Line( m_spotEncounter->path.from, m_spotEncounter->path.to, 0, 150, 150, true, 0.1f );
  945. Vector dir = m_spotEncounter->path.to - m_spotEncounter->path.from;
  946. float length = dir.NormalizeInPlace();
  947. const SpotOrder *order;
  948. Vector along;
  949. FOR_EACH_VEC( m_spotEncounter->spots, it )
  950. {
  951. order = &m_spotEncounter->spots[ it ];
  952. // ignore spots the enemy could not have possibly reached yet
  953. if (order->spot->GetArea())
  954. {
  955. if (TheCSBots()->GetElapsedRoundTime() < order->spot->GetArea()->GetEarliestOccupyTime( OtherTeam( GetTeamNumber() ) ))
  956. {
  957. continue;
  958. }
  959. }
  960. along = m_spotEncounter->path.from + order->t * length * dir;
  961. NDebugOverlay::Line( along, order->spot->GetPosition(), 0, 255, 255, true, 0.1f );
  962. }
  963. }
  964. }
  965. // show aim targets
  966. if (false)
  967. {
  968. CStudioHdr *pStudioHdr = const_cast< CCSBot *>( this )->GetModelPtr();
  969. if ( !pStudioHdr )
  970. return;
  971. mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( const_cast< CCSBot *>( this )->GetHitboxSet() );
  972. if ( !set )
  973. return;
  974. Vector position, forward, right, up;
  975. QAngle angles;
  976. char buffer[16];
  977. for ( int i = 0; i < set->numhitboxes; i++ )
  978. {
  979. mstudiobbox_t *pbox = set->pHitbox( i );
  980. const_cast< CCSBot *>( this )->GetBonePosition( pbox->bone, position, angles );
  981. AngleVectors( angles, &forward, &right, &up );
  982. NDebugOverlay::BoxAngles( position, pbox->bbmin, pbox->bbmax, angles, 255, 0, 0, 0, 0.1f );
  983. const float size = 5.0f;
  984. NDebugOverlay::Line( position, position + size * forward, 255, 255, 0, true, 0.1f );
  985. NDebugOverlay::Line( position, position + size * right, 255, 0, 0, true, 0.1f );
  986. NDebugOverlay::Line( position, position + size * up, 0, 255, 0, true, 0.1f );
  987. Q_snprintf( buffer, 16, "%d", i );
  988. if (i == 12)
  989. {
  990. // in local bone space
  991. const float headForwardOffset = 4.0f;
  992. const float headRightOffset = 2.0f;
  993. position += headForwardOffset * forward + headRightOffset * right;
  994. }
  995. NDebugOverlay::Text( position, buffer, true, 0.1f );
  996. }
  997. }
  998. /*
  999. const QAngle &angles = const_cast< CCSBot *>( this )->GetPunchAngle();
  1000. NDebugOverlay::ScreenText( 0.3f, 0.66f, msg.sprintf( "Punch angle pitch = %3.2f", angles.x ), 255, 255, 0, 150, duration );
  1001. */
  1002. DrawTravelTime drawTravelTime( this );
  1003. ForEachPlayer( drawTravelTime );
  1004. /*
  1005. // show line of fire
  1006. if ((cv_bot_traceview.GetInt() == 100 && IsLocalPlayerWatchingMe()) || cv_bot_traceview.GetInt() == 101)
  1007. {
  1008. if (!IsFriendInLineOfFire())
  1009. {
  1010. Vector vecAiming = GetViewVector();
  1011. Vector vecSrc = EyePositionConst();
  1012. if (GetTeamNumber() == TEAM_TERRORIST)
  1013. UTIL_DrawBeamPoints( vecSrc, vecSrc + 2000.0f * vecAiming, 1, 255, 0, 0 );
  1014. else
  1015. UTIL_DrawBeamPoints( vecSrc, vecSrc + 2000.0f * vecAiming, 1, 0, 50, 255 );
  1016. }
  1017. }
  1018. // show path navigation data
  1019. if (cv_bot_traceview.GetInt() == 1 && IsLocalPlayerWatchingMe())
  1020. {
  1021. Vector from = EyePositionConst();
  1022. const float size = 50.0f;
  1023. //Vector arrow( size * cos( m_forwardAngle * M_PI/180.0f ), size * sin( m_forwardAngle * M_PI/180.0f ), 0.0f );
  1024. Vector arrow( size * (float)cos( m_lookAheadAngle * M_PI/180.0f ),
  1025. size * (float)sin( m_lookAheadAngle * M_PI/180.0f ),
  1026. 0.0f );
  1027. UTIL_DrawBeamPoints( from, from + arrow, 1, 0, 255, 255 );
  1028. }
  1029. if (cv_bot_show_nav.GetBool() && m_lastKnownArea)
  1030. {
  1031. m_lastKnownArea->DrawConnectedAreas();
  1032. }
  1033. */
  1034. if (IsAttacking())
  1035. {
  1036. const float crossSize = 2.0f;
  1037. CCSPlayer *enemy = GetBotEnemy();
  1038. if (enemy)
  1039. {
  1040. NDebugOverlay::Cross3D( GetPartPosition( enemy, GUT ), crossSize, 0, 255, 0, true, 0.1f );
  1041. NDebugOverlay::Cross3D( GetPartPosition( enemy, HEAD ), crossSize, 0, 255, 0, true, 0.1f );
  1042. NDebugOverlay::Cross3D( GetPartPosition( enemy, FEET ), crossSize, 0, 255, 0, true, 0.1f );
  1043. NDebugOverlay::Cross3D( GetPartPosition( enemy, LEFT_SIDE ), crossSize, 0, 255, 0, true, 0.1f );
  1044. NDebugOverlay::Cross3D( GetPartPosition( enemy, RIGHT_SIDE ), crossSize, 0, 255, 0, true, 0.1f );
  1045. }
  1046. }
  1047. }
  1048. //--------------------------------------------------------------------------------------------------------------
  1049. /**
  1050. * Periodically compute shortest path distance to each player.
  1051. * NOTE: Travel distance is NOT symmetric between players A and B. Each much be computed separately.
  1052. */
  1053. void CCSBot::UpdateTravelDistanceToAllPlayers( void )
  1054. {
  1055. SNPROF("UpdateTravelDistanceToAllPlayers");
  1056. const unsigned char numPhases = 3;
  1057. if (m_updateTravelDistanceTimer.IsElapsed())
  1058. {
  1059. ShortestPathCost pathCost;
  1060. for( int i=1; i<=gpGlobals->maxClients; ++i )
  1061. {
  1062. CCSPlayer *player = static_cast< CCSPlayer * >( UTIL_PlayerByIndex( i ) );
  1063. if (player == NULL)
  1064. continue;
  1065. if (FNullEnt( player->edict() ))
  1066. continue;
  1067. if (!player->IsPlayer())
  1068. continue;
  1069. if (!player->IsAlive())
  1070. continue;
  1071. // skip friends for efficiency
  1072. if ( !IsOtherEnemy( player ) )
  1073. continue;
  1074. int which = player->entindex() % MAX_PLAYERS;
  1075. // if player is very far away, update every third time (on phase 0)
  1076. const float veryFarAway = 4000.0f;
  1077. if (m_playerTravelDistance[ which ] < 0.0f || m_playerTravelDistance[ which ] > veryFarAway)
  1078. {
  1079. if (m_travelDistancePhase != 0)
  1080. continue;
  1081. }
  1082. else
  1083. {
  1084. // if player is far away, update two out of three times (on phases 1 and 2)
  1085. const float farAway = 2000.0f;
  1086. if (m_playerTravelDistance[ which ] > farAway && m_travelDistancePhase == 0)
  1087. continue;
  1088. }
  1089. // if player is fairly close, update often
  1090. m_playerTravelDistance[ which ] = NavAreaTravelDistance( EyePosition(), player->EyePosition(), pathCost );
  1091. }
  1092. // throttle the computation frequency
  1093. const float checkInterval = 1.0f;
  1094. m_updateTravelDistanceTimer.Start( checkInterval );
  1095. // round-robin the phases
  1096. ++m_travelDistancePhase;
  1097. if (m_travelDistancePhase >= numPhases)
  1098. {
  1099. m_travelDistancePhase = 0;
  1100. }
  1101. }
  1102. }