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.

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