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.

1230 lines
31 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_simple_hostage.h"
  10. #include "cs_gamerules.h"
  11. #include "func_breakablesurf.h"
  12. #include "obstacle_pushaway.h"
  13. #include "cs_bot.h"
  14. // memdbgon must be the last include file in a .cpp file!!!
  15. #include "tier0/memdbgon.h"
  16. LINK_ENTITY_TO_CLASS( cs_bot, CCSBot );
  17. BEGIN_DATADESC( CCSBot )
  18. END_DATADESC()
  19. //--------------------------------------------------------------------------------------------------------------
  20. /**
  21. * Return the number of bots following the given player
  22. */
  23. int GetBotFollowCount( CCSPlayer *leader )
  24. {
  25. int count = 0;
  26. for( int i=1; i <= gpGlobals->maxClients; ++i )
  27. {
  28. CBaseEntity *entity = UTIL_PlayerByIndex( i );
  29. if (entity == NULL)
  30. continue;
  31. CBasePlayer *player = static_cast<CBasePlayer *>( entity );
  32. if (!player->IsBot())
  33. continue;
  34. if (!player->IsAlive())
  35. continue;
  36. CCSBot *bot = dynamic_cast<CCSBot *>( player );
  37. if (bot && bot->GetFollowLeader() == leader)
  38. ++count;
  39. }
  40. return count;
  41. }
  42. //--------------------------------------------------------------------------------------------------------------
  43. /**
  44. * Change movement speed to walking
  45. */
  46. void CCSBot::Walk( void )
  47. {
  48. if (m_mustRunTimer.IsElapsed())
  49. {
  50. BaseClass::Walk();
  51. }
  52. else
  53. {
  54. // must run
  55. Run();
  56. }
  57. }
  58. //--------------------------------------------------------------------------------------------------------------
  59. /**
  60. * Return true if jump was started.
  61. * This is extended from the base jump to disallow jumping when in a crouch area.
  62. */
  63. bool CCSBot::Jump( bool mustJump )
  64. {
  65. // prevent jumping if we're crouched, unless we're in a crouchjump area - jump wins
  66. bool inCrouchJumpArea = (m_lastKnownArea &&
  67. (m_lastKnownArea->GetAttributes() & NAV_MESH_CROUCH) &&
  68. (m_lastKnownArea->GetAttributes() & NAV_MESH_JUMP));
  69. if ( !IsUsingLadder() && IsDucked() && !inCrouchJumpArea )
  70. {
  71. return false;
  72. }
  73. if ( CSGameRules()->IsPlayingCoopMission() && !mustJump )
  74. return false;
  75. return BaseClass::Jump( mustJump );
  76. }
  77. //--------------------------------------------------------------------------------------------------------------
  78. /**
  79. * Invoked when injured by something
  80. * NOTE: We dont want to directly call Attack() here, or the bots will have super-human reaction times when injured
  81. */
  82. int CCSBot::OnTakeDamage( const CTakeDamageInfo &info )
  83. {
  84. m_bIsSleeping = false;
  85. CBaseEntity *attacker = info.GetInflictor();
  86. // getting hurt makes us alert
  87. BecomeAlert();
  88. StopWaiting();
  89. if ( info.GetDamageType() == DMG_BURN )
  90. {
  91. m_burnedByFlamesTimer.Start();
  92. }
  93. // if we were attacked by a teammate, rebuke
  94. if (attacker->IsPlayer())
  95. {
  96. CCSPlayer *player = static_cast<CCSPlayer *>( attacker );
  97. if ( IsOtherSameTeam( player->GetTeamNumber() ) && !IsOtherEnemy( player ) && !player->IsBot() )
  98. {
  99. // Response rules specifically needs to know if this is bullet or knife damage, so no need to do a fully general solution at this time
  100. const char *pDmgType = "OTHER";
  101. if ( info.GetDamageType() & DMG_BULLET )
  102. {
  103. pDmgType = "DMG_BULLET";
  104. }
  105. else if ( info.GetDamageType() & DMG_SLASH )
  106. {
  107. pDmgType = "DMG_SLASH";
  108. }
  109. GetChatter()->FriendlyFire( pDmgType );
  110. }
  111. }
  112. if (attacker->IsPlayer() && IsEnemy( attacker ))
  113. {
  114. // Track previous attacker so we don't try to panic multiple times for a shotgun blast
  115. CCSPlayer *lastAttacker = m_attacker;
  116. float lastAttackedTimestamp = m_attackedTimestamp;
  117. // keep track of our last attacker
  118. m_attacker = reinterpret_cast<CCSPlayer *>( attacker );
  119. m_attackedTimestamp = gpGlobals->curtime;
  120. // no longer safe
  121. AdjustSafeTime();
  122. if ( !IsSurprised() && (m_attacker != lastAttacker || m_attackedTimestamp != lastAttackedTimestamp) )
  123. {
  124. CCSPlayer *enemy = static_cast<CCSPlayer *>( attacker );
  125. // being hurt by an enemy we can't see causes panic
  126. if (!IsVisible( enemy, CHECK_FOV ))
  127. {
  128. // if not attacking anything, look around to try to find attacker
  129. if (!IsAttacking())
  130. {
  131. Panic();
  132. }
  133. else // we are attacking
  134. {
  135. if (!IsEnemyVisible())
  136. {
  137. // can't see our current enemy, panic to acquire new attacker
  138. Panic();
  139. }
  140. }
  141. }
  142. }
  143. }
  144. // extend
  145. return BaseClass::OnTakeDamage( info );
  146. }
  147. //--------------------------------------------------------------------------------------------------------------
  148. /**
  149. * Invoked when killed
  150. */
  151. void CCSBot::Event_Killed( const CTakeDamageInfo &info )
  152. {
  153. // PrintIfWatched( "Killed( attacker = %s )\n", STRING(pevAttacker->netname) );
  154. GetChatter()->OnDeath();
  155. // increase the danger where we died
  156. const float deathDanger = 1.0f;
  157. const float deathDangerRadius = 500.0f;
  158. TheNavMesh->IncreaseDangerNearby( GetTeamNumber(), deathDanger, m_lastKnownArea, GetAbsOrigin(), deathDangerRadius );
  159. // end voice feedback
  160. m_voiceEndTimestamp = 0.0f;
  161. // extend
  162. BaseClass::Event_Killed( info );
  163. }
  164. //--------------------------------------------------------------------------------------------------------------
  165. /**
  166. * Return true if line segment intersects rectagular volume
  167. */
  168. #define HI_X 0x01
  169. #define LO_X 0x02
  170. #define HI_Y 0x04
  171. #define LO_Y 0x08
  172. #define HI_Z 0x10
  173. #define LO_Z 0x20
  174. inline bool IsIntersectingBox( const Vector& start, const Vector& end, const Vector& boxMin, const Vector& boxMax )
  175. {
  176. unsigned char startFlags = 0;
  177. unsigned char endFlags = 0;
  178. // classify start point
  179. if (start.x < boxMin.x)
  180. startFlags |= LO_X;
  181. if (start.x > boxMax.x)
  182. startFlags |= HI_X;
  183. if (start.y < boxMin.y)
  184. startFlags |= LO_Y;
  185. if (start.y > boxMax.y)
  186. startFlags |= HI_Y;
  187. if (start.z < boxMin.z)
  188. startFlags |= LO_Z;
  189. if (start.z > boxMax.z)
  190. startFlags |= HI_Z;
  191. // classify end point
  192. if (end.x < boxMin.x)
  193. endFlags |= LO_X;
  194. if (end.x > boxMax.x)
  195. endFlags |= HI_X;
  196. if (end.y < boxMin.y)
  197. endFlags |= LO_Y;
  198. if (end.y > boxMax.y)
  199. endFlags |= HI_Y;
  200. if (end.z < boxMin.z)
  201. endFlags |= LO_Z;
  202. if (end.z > boxMax.z)
  203. endFlags |= HI_Z;
  204. // trivial reject
  205. if (startFlags & endFlags)
  206. return false;
  207. /// @todo Do exact line/box intersection check
  208. return true;
  209. }
  210. extern void UTIL_DrawBox( Extent *extent, int lifetime, int red, int green, int blue );
  211. //--------------------------------------------------------------------------------------------------------------
  212. /**
  213. * When bot is touched by another entity.
  214. */
  215. void CCSBot::Touch( CBaseEntity *other )
  216. {
  217. // EXTEND
  218. BaseClass::Touch( other );
  219. // if we have touched a higher-priority player, make way
  220. /// @todo Need to account for reaction time, etc.
  221. if (other->IsPlayer())
  222. {
  223. // if we are defusing a bomb, don't move
  224. if (IsDefusingBomb())
  225. return;
  226. // if we are on a ladder, don't move
  227. if (IsUsingLadder())
  228. return;
  229. CCSPlayer *player = static_cast<CCSPlayer *>( other );
  230. // get priority of other player
  231. unsigned int otherPri = TheCSBots()->GetPlayerPriority( player );
  232. // get our priority
  233. unsigned int myPri = TheCSBots()->GetPlayerPriority( this );
  234. // if our priority is better, don't budge
  235. if (myPri < otherPri)
  236. return;
  237. // they are higher priority - make way, unless we're already making way for someone more important
  238. if (m_avoid != NULL)
  239. {
  240. unsigned int avoidPri = TheCSBots()->GetPlayerPriority( static_cast<CBasePlayer *>( static_cast<CBaseEntity *>( m_avoid ) ) );
  241. if (avoidPri < otherPri)
  242. {
  243. // ignore 'other' because we're already avoiding someone better
  244. return;
  245. }
  246. }
  247. m_avoid = other;
  248. m_avoidTimestamp = gpGlobals->curtime;
  249. }
  250. // Check for breakables we're actually touching
  251. // If we're not stuck or crouched, we don't care
  252. if ( !m_isStuck && !IsCrouching() && !IsOnLadder() )
  253. return;
  254. // See if it's breakable
  255. if ( IsBreakableEntity( other ) )
  256. {
  257. // it's breakable - try to shoot it.
  258. SetLookAt( "Breakable", other->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true );
  259. }
  260. }
  261. //--------------------------------------------------------------------------------------------------------------
  262. /**
  263. * Return true if we are busy doing something important
  264. */
  265. bool CCSBot::IsBusy( void ) const
  266. {
  267. if (IsAttacking() ||
  268. IsBuying() ||
  269. IsDefusingBomb() ||
  270. GetTask() == PLANT_BOMB ||
  271. GetTask() == RESCUE_HOSTAGES ||
  272. IsSniping())
  273. {
  274. return true;
  275. }
  276. return false;
  277. }
  278. //--------------------------------------------------------------------------------------------------------------
  279. void CCSBot::BotDeathThink( void )
  280. {
  281. }
  282. //--------------------------------------------------------------------------------------------------------------
  283. /**
  284. * Try to join the given team
  285. */
  286. void CCSBot::TryToJoinTeam( int team )
  287. {
  288. m_desiredTeam = team;
  289. }
  290. //--------------------------------------------------------------------------------------------------------------
  291. /**
  292. * Assign given player as our current enemy to attack
  293. */
  294. void CCSBot::SetBotEnemy( CCSPlayer *enemy )
  295. {
  296. if (m_enemy != enemy)
  297. {
  298. m_enemy = enemy;
  299. m_currentEnemyAcquireTimestamp = gpGlobals->curtime;
  300. PrintIfWatched( "SetBotEnemy: %s\n", (enemy) ? enemy->GetPlayerName() : "(NULL)" );
  301. }
  302. }
  303. //--------------------------------------------------------------------------------------------------------------
  304. /**
  305. * If we are not on the navigation mesh (m_currentArea == NULL),
  306. * move towards last known area.
  307. * Return false if off mesh.
  308. */
  309. bool CCSBot::StayOnNavMesh( void )
  310. {
  311. if (m_currentArea == NULL)
  312. {
  313. // move back onto the area map
  314. // if we have no lastKnownArea, we probably started off
  315. // of the nav mesh - find the closest nav area and use it
  316. CNavArea *goalArea;
  317. if (!m_currentArea && !m_lastKnownArea)
  318. {
  319. goalArea = TheNavMesh->GetNearestNavArea( GetCentroid( this ) );
  320. PrintIfWatched( "Started off the nav mesh - moving to closest nav area...\n" );
  321. }
  322. else
  323. {
  324. goalArea = m_lastKnownArea;
  325. PrintIfWatched( "Getting out of NULL area...\n" );
  326. }
  327. if (goalArea)
  328. {
  329. Vector pos;
  330. goalArea->GetClosestPointOnArea( GetCentroid( this ), &pos );
  331. // move point into area
  332. Vector to = pos - GetCentroid( this );
  333. to.NormalizeInPlace();
  334. const float stepInDist = 5.0f; // how far to "step into" an area - must be less than min area size
  335. pos = pos + (stepInDist * to);
  336. MoveTowardsPosition( pos );
  337. }
  338. // if we're stuck, try to get un-stuck
  339. // do stuck movements last, so they override normal movement
  340. if (m_isStuck)
  341. Wiggle();
  342. return false;
  343. }
  344. return true;
  345. }
  346. //--------------------------------------------------------------------------------------------------------------
  347. /**
  348. * Return true if we will do scenario-related tasks
  349. */
  350. bool CCSBot::IsDoingScenario( void ) const
  351. {
  352. // if we are deferring to humans, and there is a live human on our team, don't do the scenario
  353. if (cv_bot_defer_to_human_goals.GetBool())
  354. {
  355. if (UTIL_HumansOnTeam( GetTeamNumber(), IS_ALIVE ))
  356. return false;
  357. }
  358. return true;
  359. }
  360. //--------------------------------------------------------------------------------------------------------------
  361. /**
  362. * Return true if we noticed the bomb on the ground or on the radar (for T's only)
  363. */
  364. bool CCSBot::NoticeLooseBomb( void ) const
  365. {
  366. CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
  367. if (ctrl->GetScenario() != CCSBotManager::SCENARIO_DEFUSE_BOMB)
  368. return false;
  369. CBaseEntity *bomb = ctrl->GetLooseBomb();
  370. if (bomb)
  371. {
  372. // T's can always see bomb on their radar
  373. return true;
  374. }
  375. return false;
  376. }
  377. //--------------------------------------------------------------------------------------------------------------
  378. /**
  379. * Return true if can see the bomb lying on the ground
  380. */
  381. bool CCSBot::CanSeeLooseBomb( void ) const
  382. {
  383. CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
  384. if (ctrl->GetScenario() != CCSBotManager::SCENARIO_DEFUSE_BOMB)
  385. return false;
  386. CBaseEntity *bomb = ctrl->GetLooseBomb();
  387. if (bomb)
  388. {
  389. if (IsVisible( bomb->GetAbsOrigin(), CHECK_FOV ))
  390. return true;
  391. }
  392. return false;
  393. }
  394. //--------------------------------------------------------------------------------------------------------------
  395. /**
  396. * Return true if can see the planted bomb
  397. */
  398. bool CCSBot::CanSeePlantedBomb( void ) const
  399. {
  400. CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
  401. if (ctrl->GetScenario() != CCSBotManager::SCENARIO_DEFUSE_BOMB)
  402. return false;
  403. if (!GetGameState()->IsBombPlanted())
  404. return false;
  405. const Vector *bombPos = GetGameState()->GetBombPosition();
  406. if (bombPos && IsVisible( *bombPos, CHECK_FOV ))
  407. return true;
  408. return false;
  409. }
  410. //--------------------------------------------------------------------------------------------------------------
  411. /**
  412. * Return last enemy that hurt us
  413. */
  414. CCSPlayer *CCSBot::GetAttacker( void ) const
  415. {
  416. if (m_attacker && m_attacker->IsAlive())
  417. return m_attacker;
  418. return NULL;
  419. }
  420. //--------------------------------------------------------------------------------------------------------------
  421. /**
  422. * Immediately jump off of our ladder, if we're on one
  423. */
  424. void CCSBot::GetOffLadder( void )
  425. {
  426. if (IsUsingLadder())
  427. {
  428. Jump( MUST_JUMP );
  429. DestroyPath();
  430. }
  431. }
  432. //--------------------------------------------------------------------------------------------------------------
  433. /**
  434. * Return time when given spot was last checked
  435. */
  436. float CCSBot::GetHidingSpotCheckTimestamp( HidingSpot *spot ) const
  437. {
  438. for( int i=0; i<m_checkedHidingSpotCount; ++i )
  439. if (m_checkedHidingSpot[i].spot->GetID() == spot->GetID())
  440. return m_checkedHidingSpot[i].timestamp;
  441. return -999999.9f;
  442. }
  443. //--------------------------------------------------------------------------------------------------------------
  444. /**
  445. * Set the timestamp of the given spot to now.
  446. * If the spot is not in the set, overwrite the least recently checked spot.
  447. */
  448. void CCSBot::SetHidingSpotCheckTimestamp( HidingSpot *spot )
  449. {
  450. int leastRecent = 0;
  451. float leastRecentTime = gpGlobals->curtime + 1.0f;
  452. for( int i=0; i<m_checkedHidingSpotCount; ++i )
  453. {
  454. // if spot is in the set, just update its timestamp
  455. if (m_checkedHidingSpot[i].spot->GetID() == spot->GetID())
  456. {
  457. m_checkedHidingSpot[i].timestamp = gpGlobals->curtime;
  458. return;
  459. }
  460. // keep track of least recent spot
  461. if (m_checkedHidingSpot[i].timestamp < leastRecentTime)
  462. {
  463. leastRecentTime = m_checkedHidingSpot[i].timestamp;
  464. leastRecent = i;
  465. }
  466. }
  467. // if there is room for more spots, append this one
  468. if (m_checkedHidingSpotCount < MAX_CHECKED_SPOTS)
  469. {
  470. m_checkedHidingSpot[ m_checkedHidingSpotCount ].spot = spot;
  471. m_checkedHidingSpot[ m_checkedHidingSpotCount ].timestamp = gpGlobals->curtime;
  472. ++m_checkedHidingSpotCount;
  473. }
  474. else
  475. {
  476. // replace the least recent spot
  477. m_checkedHidingSpot[ leastRecent ].spot = spot;
  478. m_checkedHidingSpot[ leastRecent ].timestamp = gpGlobals->curtime;
  479. }
  480. }
  481. //--------------------------------------------------------------------------------------------------------------
  482. /**
  483. * Periodic check of hostage count in case we lost some
  484. */
  485. void CCSBot::UpdateHostageEscortCount( void )
  486. {
  487. const float updateInterval = 1.0f;
  488. if (m_hostageEscortCount == 0 || gpGlobals->curtime - m_hostageEscortCountTimestamp < updateInterval)
  489. return;
  490. m_hostageEscortCountTimestamp = gpGlobals->curtime;
  491. // recount the hostages in case we lost some
  492. m_hostageEscortCount = 0;
  493. for( int i=0; i<g_Hostages.Count(); ++i )
  494. {
  495. CHostage *hostage = g_Hostages[i];
  496. // skip dead or rescued hostages
  497. if ( !hostage->IsValid() || !hostage->IsAlive() )
  498. continue;
  499. // check if hostage has targeted us, and is following
  500. if ( hostage->IsFollowing( this ) )
  501. ++m_hostageEscortCount;
  502. }
  503. }
  504. //--------------------------------------------------------------------------------------------------------------
  505. /**
  506. * Return true if we are outnumbered by enemies
  507. */
  508. bool CCSBot::IsOutnumbered( void ) const
  509. {
  510. // Play panic lines more often in coop
  511. if ( CSGameRules()->IsPlayingCoopMission() )
  512. return GetNearbyFriendCount() <= GetNearbyEnemyCount();
  513. return (GetNearbyFriendCount() < GetNearbyEnemyCount()-1) ? true : false;
  514. }
  515. //--------------------------------------------------------------------------------------------------------------
  516. /**
  517. * Return number of enemies we are outnumbered by
  518. */
  519. int CCSBot::OutnumberedCount( void ) const
  520. {
  521. if (IsOutnumbered())
  522. return (GetNearbyEnemyCount()-1) - GetNearbyFriendCount();
  523. return 0;
  524. }
  525. //--------------------------------------------------------------------------------------------------------------
  526. /**
  527. * Return the closest "important" enemy for the given scenario (bomb carrier, VIP, hostage escorter)
  528. */
  529. CCSPlayer *CCSBot::GetImportantEnemy( bool checkVisibility ) const
  530. {
  531. CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
  532. CCSPlayer *nearEnemy = NULL;
  533. float nearDist = 999999999.9f;
  534. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  535. {
  536. CBaseEntity *entity = UTIL_PlayerByIndex( i );
  537. if (entity == NULL)
  538. continue;
  539. // if (FNullEnt( entity->pev ))
  540. // continue;
  541. // if (FStrEq( STRING( entity->pev->netname ), "" ))
  542. // continue;
  543. // is it a player?
  544. if (!entity->IsPlayer())
  545. continue;
  546. CCSPlayer *player = static_cast<CCSPlayer *>( entity );
  547. // is it alive?
  548. if (!player->IsAlive())
  549. continue;
  550. // skip friends
  551. if ( player->IsOtherSameTeam( GetTeamNumber() ) && !player->IsOtherEnemy( entindex() ) )
  552. continue;
  553. // is it "important"
  554. if (!ctrl->IsImportantPlayer( player ))
  555. continue;
  556. // is it closest?
  557. Vector d = GetAbsOrigin() - player->GetAbsOrigin();
  558. float distSq = d.x*d.x + d.y*d.y + d.z*d.z;
  559. if (distSq < nearDist)
  560. {
  561. if (checkVisibility && !IsVisible( player, CHECK_FOV ))
  562. continue;
  563. nearEnemy = player;
  564. nearDist = distSq;
  565. }
  566. }
  567. return nearEnemy;
  568. }
  569. //--------------------------------------------------------------------------------------------------------------
  570. /**
  571. * Sets our current disposition
  572. */
  573. void CCSBot::SetDisposition( DispositionType disposition )
  574. {
  575. m_disposition = disposition;
  576. if (m_disposition != IGNORE_ENEMIES)
  577. m_ignoreEnemiesTimer.Invalidate();
  578. }
  579. //--------------------------------------------------------------------------------------------------------------
  580. /**
  581. * Return our current disposition
  582. */
  583. CCSBot::DispositionType CCSBot::GetDisposition( void ) const
  584. {
  585. if (!m_ignoreEnemiesTimer.IsElapsed())
  586. return IGNORE_ENEMIES;
  587. return m_disposition;
  588. }
  589. //--------------------------------------------------------------------------------------------------------------
  590. /**
  591. * Ignore enemies for a short durationy
  592. */
  593. void CCSBot::IgnoreEnemies( float duration )
  594. {
  595. m_ignoreEnemiesTimer.Start( duration );
  596. }
  597. //--------------------------------------------------------------------------------------------------------------
  598. /**
  599. * Increase morale one step
  600. */
  601. void CCSBot::IncreaseMorale( void )
  602. {
  603. if (m_morale < EXCELLENT)
  604. m_morale = static_cast<MoraleType>( m_morale + 1 );
  605. }
  606. //--------------------------------------------------------------------------------------------------------------
  607. /**
  608. * Decrease morale one step
  609. */
  610. void CCSBot::DecreaseMorale( void )
  611. {
  612. if (m_morale > TERRIBLE)
  613. m_morale = static_cast<MoraleType>( m_morale - 1 );
  614. }
  615. float CCSBot::GetNoiseInvestigateChance( void ) const
  616. {
  617. if ( CSGameRules()->IsPlayingCoopMission() )
  618. return 100.0f;
  619. // chance of investigating is inversely proportional to distance
  620. const float maxNoiseDist = 3000.0f;
  621. float chance = 100.0f * ( 1.0f - ( GetNoiseRange() / maxNoiseDist ) );
  622. // modify chance by number of friends remaining
  623. // if we have lots of friends, presumably one of them is closer and will check it out
  624. if ( GetFriendsRemaining() >= 3 )
  625. {
  626. float friendFactor = 5.0f * GetFriendsRemaining();
  627. if ( friendFactor > 50.0f )
  628. friendFactor = 50.0f;
  629. chance -= friendFactor;
  630. }
  631. return chance;
  632. }
  633. //--------------------------------------------------------------------------------------------------------------
  634. /**
  635. * Return true if we are acting like a rogue (not listening to teammates, not doing scenario goals)
  636. * @todo Account for morale
  637. */
  638. bool CCSBot::IsRogue( void ) const
  639. {
  640. CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
  641. if (!ctrl->AllowRogues())
  642. return false;
  643. // periodically re-evaluate our rogue status
  644. if (m_rogueTimer.IsElapsed())
  645. {
  646. m_rogueTimer.Start( RandomFloat( 10.0f, 30.0f ) );
  647. // our chance of going rogue is inversely proportional to our teamwork attribute
  648. const float rogueChance = 100.0f * (1.0f - GetProfile()->GetTeamwork());
  649. m_isRogue = (RandomFloat( 0, 100 ) < rogueChance);
  650. }
  651. return m_isRogue;
  652. }
  653. //--------------------------------------------------------------------------------------------------------------
  654. /**
  655. * Return true if we are in a hurry
  656. */
  657. bool CCSBot::IsHurrying( void ) const
  658. {
  659. if (!m_hurryTimer.IsElapsed())
  660. return true;
  661. CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
  662. // if the bomb has been planted, we are in a hurry, CT or T (they could be defusing it!)
  663. if (ctrl->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB && ctrl->IsBombPlanted())
  664. return true;
  665. // if we are a T and hostages are being rescued, we are in a hurry
  666. if (ctrl->GetScenario() == CCSBotManager::SCENARIO_RESCUE_HOSTAGES &&
  667. GetTeamNumber() == TEAM_TERRORIST &&
  668. GetGameState()->AreAllHostagesBeingRescued())
  669. return true;
  670. return false;
  671. }
  672. //--------------------------------------------------------------------------------------------------------------
  673. /**
  674. * Return true if it is the early, "safe", part of the round
  675. */
  676. bool CCSBot::IsSafe( void ) const
  677. {
  678. CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
  679. if (ctrl->GetElapsedRoundTime() < m_safeTime)
  680. return true;
  681. return false;
  682. }
  683. //--------------------------------------------------------------------------------------------------------------
  684. /**
  685. * Return true if it is well past the early, "safe", part of the round
  686. */
  687. bool CCSBot::IsWellPastSafe( void ) const
  688. {
  689. CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
  690. if (ctrl->GetElapsedRoundTime() > 2.0f * m_safeTime)
  691. return true;
  692. return false;
  693. }
  694. //--------------------------------------------------------------------------------------------------------------
  695. /**
  696. * Return true if we were in the safe time last update, but not now
  697. */
  698. bool CCSBot::IsEndOfSafeTime( void ) const
  699. {
  700. return m_wasSafe && !IsSafe();
  701. }
  702. //--------------------------------------------------------------------------------------------------------------
  703. /**
  704. * Return the amount of "safe time" we have left
  705. */
  706. float CCSBot::GetSafeTimeRemaining( void ) const
  707. {
  708. CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
  709. return m_safeTime - ctrl->GetElapsedRoundTime();
  710. }
  711. //--------------------------------------------------------------------------------------------------------------
  712. /**
  713. * Called when enemy seen to adjust safe time for this round
  714. */
  715. void CCSBot::AdjustSafeTime( void )
  716. {
  717. CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
  718. // if we spotted an enemy sooner than we thought possible, adjust our notion of "safe" time
  719. if (ctrl->GetElapsedRoundTime() < m_safeTime)
  720. {
  721. // since right now is not safe, adjust safe time to be a few seconds ago
  722. m_safeTime = ctrl->GetElapsedRoundTime() - 2.0f;
  723. }
  724. }
  725. //--------------------------------------------------------------------------------------------------------------
  726. /**
  727. * Return true if we haven't seen an enemy for "a long time"
  728. */
  729. bool CCSBot::HasNotSeenEnemyForLongTime( void ) const
  730. {
  731. const float longTime = 30.0f;
  732. return (GetTimeSinceLastSawEnemy() > longTime);
  733. }
  734. //--------------------------------------------------------------------------------------------------------------
  735. /**
  736. * Pick a random zone and hide near it
  737. */
  738. bool CCSBot::GuardRandomZone( float range )
  739. {
  740. CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
  741. const CCSBotManager::Zone *zone = ctrl->GetRandomZone();
  742. if (zone)
  743. {
  744. CNavArea *rescueArea = ctrl->GetRandomAreaInZone( zone );
  745. if (rescueArea)
  746. {
  747. Hide( rescueArea, -1.0f, range );
  748. return true;
  749. }
  750. }
  751. return false;
  752. }
  753. //--------------------------------------------------------------------------------------------------------------
  754. class CollectRetreatSpotsFunctor
  755. {
  756. public:
  757. CollectRetreatSpotsFunctor( CCSBot *me, float range )
  758. {
  759. m_me = me;
  760. m_count = 0;
  761. m_range = range;
  762. }
  763. enum { MAX_SPOTS = 256 };
  764. bool operator() ( CNavArea *area )
  765. {
  766. // collect all the hiding spots in this area
  767. const HidingSpotVector *pSpots = area->GetHidingSpots();
  768. FOR_EACH_VEC( (*pSpots), it )
  769. {
  770. const HidingSpot *spot = (*pSpots)[ it ];
  771. if (m_count >= MAX_SPOTS)
  772. break;
  773. // make sure hiding spot is in range
  774. if (m_range > 0.0f)
  775. if ((spot->GetPosition() - GetCentroid( m_me )).IsLengthGreaterThan( m_range ))
  776. continue;
  777. // if a Player is using this hiding spot, don't consider it
  778. if (IsSpotOccupied( m_me, spot->GetPosition() ))
  779. {
  780. // player is in hiding spot
  781. /// @todo Check if player is moving or sitting still
  782. continue;
  783. }
  784. // don't select spot if an enemy can see it
  785. if (UTIL_IsVisibleToTeam( spot->GetPosition() + Vector( 0, 0, HalfHumanHeight ), OtherTeam( m_me->GetTeamNumber() ) ))
  786. continue;
  787. // don't select spot if it is closest to an enemy
  788. CBasePlayer *owner = UTIL_GetClosestPlayer( spot->GetPosition() );
  789. if (owner && !m_me->InSameTeam( owner ))
  790. continue;
  791. m_spot[ m_count++ ] = &spot->GetPosition();
  792. }
  793. // if we've filled up, stop searching
  794. if (m_count == MAX_SPOTS)
  795. return false;
  796. return true;
  797. }
  798. CCSBot *m_me;
  799. float m_range;
  800. const Vector *m_spot[ MAX_SPOTS ];
  801. int m_count;
  802. };
  803. /**
  804. * Do a breadth-first search to find a good retreat spot.
  805. * Don't pick a spot that a Player is currently occupying.
  806. */
  807. const Vector *FindNearbyRetreatSpot( CCSBot *me, float maxRange )
  808. {
  809. CNavArea *area = me->GetLastKnownArea();
  810. if (area == NULL)
  811. return NULL;
  812. // collect spots that enemies cannot see
  813. CollectRetreatSpotsFunctor collector( me, maxRange );
  814. SearchSurroundingAreas( area, GetCentroid( me ), collector, maxRange );
  815. if (collector.m_count == 0)
  816. return NULL;
  817. // select a hiding spot at random
  818. int which = RandomInt( 0, collector.m_count-1 );
  819. return collector.m_spot[ which ];
  820. }
  821. //--------------------------------------------------------------------------------------------------------------
  822. class FarthestHostage
  823. {
  824. public:
  825. FarthestHostage( const CCSBot *me )
  826. {
  827. m_me = me;
  828. m_farRange = -1.0f;
  829. }
  830. bool operator() ( CHostage *hostage )
  831. {
  832. if (hostage->IsFollowing( m_me ))
  833. {
  834. float range = (hostage->GetAbsOrigin() - m_me->GetAbsOrigin()).Length();
  835. if (range > m_farRange)
  836. {
  837. m_farRange = range;
  838. }
  839. }
  840. return true;
  841. }
  842. const CCSBot *m_me;
  843. float m_farRange;
  844. };
  845. /**
  846. * Return euclidean distance to farthest escorted hostage.
  847. * Return -1 if no hostage is following us.
  848. */
  849. float CCSBot::GetRangeToFarthestEscortedHostage( void ) const
  850. {
  851. FarthestHostage away( this );
  852. ForEachHostage( away );
  853. return away.m_farRange;
  854. }
  855. //--------------------------------------------------------------------------------------------------------------
  856. /**
  857. * Return string describing current task
  858. * NOTE: This MUST be kept in sync with the CCSBot::TaskType enum
  859. */
  860. const char *CCSBot::GetTaskName( void ) const
  861. {
  862. static char *name[ NUM_TASKS ] =
  863. {
  864. "SEEK_AND_DESTROY",
  865. "PLANT_BOMB",
  866. "FIND_TICKING_BOMB",
  867. "DEFUSE_BOMB",
  868. "GUARD_TICKING_BOMB",
  869. "GUARD_BOMB_DEFUSER",
  870. "GUARD_LOOSE_BOMB",
  871. "GUARD_BOMB_ZONE",
  872. "GUARD_INITIAL_ENCOUNTER",
  873. "ESCAPE_FROM_BOMB",
  874. "HOLD_POSITION",
  875. "FOLLOW",
  876. "VIP_ESCAPE",
  877. "GUARD_VIP_ESCAPE_ZONE",
  878. "COLLECT_HOSTAGES",
  879. "RESCUE_HOSTAGES",
  880. "GUARD_HOSTAGES",
  881. "GUARD_HOSTAGE_RESCUE_ZONE",
  882. "MOVE_TO_LAST_KNOWN_ENEMY_POSITION",
  883. "MOVE_TO_SNIPER_SPOT",
  884. "SNIPING",
  885. "ESCAPE_FROM_FLAMES",
  886. };
  887. return name[ (int)GetTask() ];
  888. }
  889. //--------------------------------------------------------------------------------------------------------------
  890. /**
  891. * Return string describing current disposition
  892. * NOTE: This MUST be kept in sync with the CCSBot::DispositionType enum
  893. */
  894. const char *CCSBot::GetDispositionName( void ) const
  895. {
  896. static char *name[ NUM_DISPOSITIONS ] =
  897. {
  898. "ENGAGE_AND_INVESTIGATE",
  899. "OPPORTUNITY_FIRE",
  900. "SELF_DEFENSE",
  901. "IGNORE_ENEMIES"
  902. };
  903. return name[ (int)GetDisposition() ];
  904. }
  905. //--------------------------------------------------------------------------------------------------------------
  906. /**
  907. * Return string describing current morale
  908. * NOTE: This MUST be kept in sync with the CCSBot::MoraleType enum
  909. */
  910. const char *CCSBot::GetMoraleName( void ) const
  911. {
  912. static char *name[ EXCELLENT - TERRIBLE + 1 ] =
  913. {
  914. "TERRIBLE",
  915. "BAD",
  916. "NEGATIVE",
  917. "NEUTRAL",
  918. "POSITIVE",
  919. "GOOD",
  920. "EXCELLENT"
  921. };
  922. return name[ (int)GetMorale() + 3 ];
  923. }
  924. //--------------------------------------------------------------------------------------------------------------
  925. /**
  926. * Fill in a CUserCmd with our data
  927. */
  928. void CCSBot::BuildUserCmd( CUserCmd& cmd, const QAngle& viewangles, float forwardmove, float sidemove, float upmove, int buttons, byte impulse )
  929. {
  930. Q_memset( &cmd, 0, sizeof( cmd ) );
  931. if ( !RunMimicCommand( cmd ) )
  932. {
  933. // Don't walk when ducked - it's painfully slow
  934. if ( m_Local.m_bDucked || m_Local.m_bDucking )
  935. {
  936. buttons &= ~IN_SPEED;
  937. }
  938. cmd.command_number = gpGlobals->tickcount;
  939. cmd.forwardmove = forwardmove;
  940. cmd.sidemove = sidemove;
  941. cmd.upmove = upmove;
  942. cmd.buttons = buttons;
  943. cmd.impulse = impulse;
  944. VectorCopy( viewangles, cmd.viewangles );
  945. cmd.random_seed = random->RandomInt( 0, 0x7fffffff );
  946. }
  947. }
  948. //
  949. // Returns a value in the -1 .. +1 range based on adding some cosines together. Cheap and sloppy.
  950. float CCSBot::SlowNoise( float fTau ) const
  951. {
  952. int iUniqueOffset = HashInt(entindex()) & 0xFF;
  953. float t = (float)iUniqueOffset;
  954. t = (t + gpGlobals->curtime / fTau) * M_PI * 2.0f;
  955. return 0.25f * ( cosf( fTau ) + cosf( fTau * 29.f / 47.f ) + cosf( fTau * 59.f / 137.f ) + cosf( fTau * 151.f / 499.f ) );
  956. }
  957. //--------------------------------------------------------------------------------------------------------------
  958. // Some game types allow players to pass through each other, this method pushes them apart
  959. void CCSBot::AvoidPlayers( CUserCmd *pCmd )
  960. {
  961. Vector forward, right;
  962. EyeVectors( &forward, &right );
  963. CUtlVector< CCSPlayer * > playerVector;
  964. CollectPlayers( &playerVector, GetTeamNumber(), COLLECT_ONLY_LIVING_PLAYERS );
  965. Vector avoidVector = vec3_origin;
  966. float tooClose = 2.0f * HalfHumanWidth;
  967. for( int i=0; i<playerVector.Count(); ++i )
  968. {
  969. CCSPlayer *them = playerVector[i];
  970. if ( entindex() == them->entindex() )
  971. {
  972. continue;
  973. }
  974. Vector between = GetAbsOrigin() - them->GetAbsOrigin();
  975. if ( between.IsLengthLessThan( tooClose ) )
  976. {
  977. float range = between.NormalizeInPlace();
  978. avoidVector += ( 1.0f - ( range / tooClose ) ) * between;
  979. }
  980. }
  981. if ( avoidVector.IsZero() )
  982. {
  983. // m_Shared.SetSeparation( false );
  984. // m_Shared.SetSeparationVelocity( vec3_origin );
  985. return;
  986. }
  987. avoidVector.NormalizeInPlace();
  988. // m_Shared.SetSeparation( true );
  989. const float maxSpeed = 50.0f;
  990. // m_Shared.SetSeparationVelocity( avoidVector * maxSpeed );
  991. float ahead = maxSpeed * DotProduct( forward, avoidVector );
  992. float side = maxSpeed * DotProduct( right, avoidVector );
  993. pCmd->forwardmove += ahead;
  994. pCmd->sidemove += side;
  995. }