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.

545 lines
17 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_bot.h"
  10. #include "cs_shareddefs.h"
  11. #include "mathlib/mathlib.h"
  12. // memdbgon must be the last include file in a .cpp file!!!
  13. #include "tier0/memdbgon.h"
  14. #pragma warning( disable : 4355 ) // warning 'this' used in base member initializer list - we're using it safely
  15. ConVar mp_coopmission_bot_difficulty_offset(
  16. "mp_coopmission_bot_difficulty_offset",
  17. "0",
  18. FCVAR_REPLICATED | FCVAR_RELEASE,
  19. "The difficulty offset modifier for bots during coop missions." );
  20. //--------------------------------------------------------------------------------------------------------------
  21. static void PrefixChanged( IConVar *c, const char *oldPrefix, float flOldValue )
  22. {
  23. if ( TheCSBots() && TheCSBots()->IsServerActive() )
  24. {
  25. for( int i = 1; i <= gpGlobals->maxClients; ++i )
  26. {
  27. CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
  28. if ( !player )
  29. continue;
  30. if ( !player->IsBot() || !IsEntityValid( player ) )
  31. continue;
  32. CCSBot *bot = dynamic_cast< CCSBot * >( player );
  33. if ( !bot )
  34. continue;
  35. // set the bot's name
  36. char botName[MAX_PLAYER_NAME_LENGTH];
  37. UTIL_ConstructBotNetName( botName, MAX_PLAYER_NAME_LENGTH, bot->GetProfile() );
  38. engine->SetFakeClientConVarValue( bot->edict(), "name", botName );
  39. }
  40. }
  41. }
  42. ConVar cv_bot_traceview( "bot_traceview", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "For internal testing purposes." );
  43. ConVar cv_bot_stop( "bot_stop", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "If nonzero, immediately stops all bot processing." );
  44. ConVar cv_bot_show_nav( "bot_show_nav", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "For internal testing purposes." );
  45. ConVar cv_bot_walk( "bot_walk", "0", FCVAR_REPLICATED, "If nonzero, bots can only walk, not run." );
  46. ConVar cv_bot_difficulty( "bot_difficulty", "1", FCVAR_REPLICATED | FCVAR_RELEASE, "Defines the skill of bots joining the game. Values are: 0=easy, 1=normal, 2=hard, 3=expert." );
  47. ConVar cv_bot_debug( "bot_debug", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "For internal testing purposes." );
  48. ConVar cv_bot_debug_target( "bot_debug_target", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "For internal testing purposes." );
  49. ConVar cv_bot_quota( "bot_quota", "10", FCVAR_REPLICATED | FCVAR_RELEASE, "Determines the total number of bots in the game." );
  50. ConVar cv_bot_quota_mode( "bot_quota_mode", "normal", FCVAR_REPLICATED | FCVAR_RELEASE, "Determines the type of quota.\nAllowed values: 'normal', 'fill', and 'match'.\nIf 'fill', the server will adjust bots to keep N players in the game, where N is bot_quota.\nIf 'match', the server will maintain a 1:N ratio of humans to bots, where N is bot_quota." );
  51. ConVar cv_bot_prefix( "bot_prefix", "", FCVAR_REPLICATED, "This string is prefixed to the name of all bots that join the game.\n<difficulty> will be replaced with the bot's difficulty.\n<weaponclass> will be replaced with the bot's desired weapon class.\n<skill> will be replaced with a 0-100 representation of the bot's skill.", PrefixChanged );
  52. ConVar cv_bot_allow_rogues( "bot_allow_rogues", "1", FCVAR_RELEASE|FCVAR_REPLICATED, "If nonzero, bots may occasionally go 'rogue'. Rogue bots do not obey radio commands, nor pursue scenario goals." );
  53. ConVar cv_bot_allow_pistols( "bot_allow_pistols", "1", FCVAR_RELEASE|FCVAR_REPLICATED, "If nonzero, bots may use pistols." );
  54. ConVar cv_bot_allow_shotguns( "bot_allow_shotguns", "1", FCVAR_RELEASE|FCVAR_REPLICATED, "If nonzero, bots may use shotguns." );
  55. ConVar cv_bot_allow_sub_machine_guns( "bot_allow_sub_machine_guns", "1", FCVAR_RELEASE|FCVAR_REPLICATED, "If nonzero, bots may use sub-machine guns." );
  56. ConVar cv_bot_allow_rifles( "bot_allow_rifles", "1", FCVAR_RELEASE|FCVAR_REPLICATED, "If nonzero, bots may use rifles." );
  57. ConVar cv_bot_allow_machine_guns( "bot_allow_machine_guns", "1", FCVAR_RELEASE|FCVAR_REPLICATED, "If nonzero, bots may use the machine gun." );
  58. ConVar cv_bot_allow_grenades( "bot_allow_grenades", "1", FCVAR_RELEASE|FCVAR_REPLICATED, "If nonzero, bots may use grenades." );
  59. ConVar cv_bot_allow_snipers( "bot_allow_snipers", "1", FCVAR_RELEASE|FCVAR_REPLICATED, "If nonzero, bots may use sniper rifles." );
  60. #ifdef CS_SHIELD_ENABLED
  61. ConVar cv_bot_allow_shield( "bot_allow_shield", "1", FCVAR_REPLICATED );
  62. #endif // CS_SHIELD_ENABLED
  63. ConVar cv_bot_join_team( "bot_join_team", "any", FCVAR_REPLICATED | FCVAR_RELEASE, "Determines the team bots will join into. Allowed values: 'any', 'T', or 'CT'." );
  64. ConVar cv_bot_join_after_player( "bot_join_after_player", "1", FCVAR_REPLICATED | FCVAR_RELEASE, "If nonzero, bots wait until a player joins before entering the game." );
  65. ConVar cv_bot_auto_vacate( "bot_auto_vacate", "1", FCVAR_REPLICATED, "If nonzero, bots will automatically leave to make room for human players." );
  66. ConVar cv_bot_zombie( "bot_zombie", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "If nonzero, bots will stay in idle mode and not attack." );
  67. ConVar cv_bot_defer_to_human_goals( "bot_defer_to_human_goals", "0", FCVAR_REPLICATED | FCVAR_RELEASE, "If nonzero and there is a human on the team, the bots will not do the scenario tasks." );
  68. ConVar cv_bot_defer_to_human_items( "bot_defer_to_human_items", "1", FCVAR_REPLICATED | FCVAR_RELEASE, "If nonzero and there is a human on the team, the bots will not get scenario items." );
  69. ConVar cv_bot_chatter( "bot_chatter", "normal", FCVAR_REPLICATED | FCVAR_RELEASE, "Control how bots talk. Allowed values: 'off', 'radio', 'minimal', or 'normal'." );
  70. ConVar cv_bot_profile_db( "bot_profile_db", "BotProfile.db", FCVAR_REPLICATED, "The filename from which bot profiles will be read." );
  71. ConVar cv_bot_dont_shoot( "bot_dont_shoot", "0", FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_CHEAT, "If nonzero, bots will not fire weapons (for debugging)." );
  72. ConVar cv_bot_eco_limit( "bot_eco_limit", "2000", FCVAR_REPLICATED, "If nonzero, bots will not buy if their money falls below this amount." );
  73. ConVar cv_bot_auto_follow( "bot_auto_follow", "0", FCVAR_REPLICATED, "If nonzero, bots with high co-op may automatically follow a nearby human player." );
  74. ConVar cv_bot_flipout( "bot_flipout", "0", FCVAR_REPLICATED, "If nonzero, bots use no CPU for AI. Instead, they run around randomly." );
  75. #if CS_CONTROLLABLE_BOTS_ENABLED
  76. ConVar cv_bot_controllable( "bot_controllable", "1", FCVAR_REPLICATED, "Determines whether bots can be controlled by players" );
  77. #endif
  78. extern void FinishClientPutInServer( CCSPlayer *pPlayer );
  79. //--------------------------------------------------------------------------------------------------------------
  80. // Engine callback for custom server commands
  81. void Bot_ServerCommand( void )
  82. {
  83. }
  84. //--------------------------------------------------------------------------------------------------------------
  85. /**
  86. * Constructor
  87. */
  88. CCSBot::CCSBot( void ) :
  89. m_gameState( this ),
  90. m_hasJoined( false ),
  91. m_pLocalProfile( NULL )
  92. {
  93. if ( CSGameRules()->IsPlayingCoopMission() )
  94. {
  95. m_pChatter = new BotChatterCoop( this );
  96. }
  97. else
  98. {
  99. m_pChatter = new BotChatterInterface( this );
  100. }
  101. }
  102. //--------------------------------------------------------------------------------------------------------------
  103. /**
  104. * Destructor
  105. */
  106. CCSBot::~CCSBot()
  107. {
  108. if ( m_pLocalProfile )
  109. {
  110. delete m_pLocalProfile;
  111. }
  112. }
  113. //--------------------------------------------------------------------------------------------------------------
  114. /**
  115. * Prepare bot for action
  116. */
  117. bool CCSBot::Initialize( const BotProfile *profile, int team )
  118. {
  119. int preserved_voice_pitch = -1;
  120. char preserved_name[256];
  121. preserved_name[0] = 0;
  122. AssertMsg( profile != NULL, "You cannot pass in null for a bot profile." );
  123. if ( NULL == profile ) return false;
  124. if ( m_pLocalProfile )
  125. {
  126. // Preserve the voice pitch and name from the existing profile
  127. preserved_voice_pitch = m_pLocalProfile->GetVoicePitch();
  128. if ( CSGameRules() && CSGameRules()->IsPlayingCooperativeGametype() )
  129. {
  130. // change names everytime in coop
  131. Q_snprintf( preserved_name, 256, "%s", profile->GetName() );
  132. }
  133. else
  134. Q_snprintf( preserved_name, 256, "%s", m_pLocalProfile->GetName() );
  135. delete m_pLocalProfile;
  136. }
  137. m_pLocalProfile = new BotProfile;
  138. if ( m_pLocalProfile )
  139. {
  140. // Copy the profile into the local profile
  141. m_pLocalProfile->Clone( profile );
  142. // Restore the name
  143. if ( Q_strlen( preserved_name ) > 0 )
  144. {
  145. m_pLocalProfile->SetName( preserved_name );
  146. }
  147. // Restore the voice pitch
  148. if ( preserved_voice_pitch > -1 )
  149. {
  150. m_pLocalProfile->SetVoicePitch( preserved_voice_pitch );
  151. }
  152. }
  153. // extend
  154. BaseClass::Initialize( m_pLocalProfile, team );
  155. // CS bot initialization
  156. m_diedLastRound = false;
  157. if ( CSGameRules()->IsPlayingGunGameTRBomb() )
  158. {
  159. // in demolition, start the CT's off with terrible morale, so they try to camp the bombsite
  160. if ( team == TEAM_CT )
  161. {
  162. m_morale = TERRIBLE;
  163. }
  164. else
  165. {
  166. m_morale = POSITIVE; // starting a new round makes everyone a little happy
  167. }
  168. }
  169. else
  170. {
  171. m_morale = POSITIVE; // starting a new round makes everyone a little happy
  172. }
  173. m_combatRange = RandomFloat( 325.0f, 425.0f );
  174. // set initial safe time guess for this map
  175. m_safeTime = 15.0f + 5.0f * GetProfile()->GetAggression( );
  176. m_name[0] = '\000';
  177. ResetValues();
  178. m_desiredTeam = team;
  179. if (GetTeamNumber() == 0)
  180. {
  181. HandleCommand_JoinTeam( m_desiredTeam );
  182. // join class is already called within JoinTeam
  183. //HandleCommand_JoinClass();
  184. }
  185. return true;
  186. }
  187. void CCSBot::CoopInitialize( void )
  188. {
  189. if ( CSGameRules() && CSGameRules()->IsPlayingCoopMission() && GetTeamNumber() == TEAM_TERRORIST )
  190. {
  191. // bots start asleep when they spawn and wake up when players come close to them
  192. SpawnPointCoopEnemy *pEnemySpawnSpot = GetLastCoopSpawnPoint();
  193. if ( pEnemySpawnSpot )
  194. {
  195. SetAbsAngles( pEnemySpawnSpot->GetAbsAngles() );
  196. int nDifficulty = pEnemySpawnSpot->GetBotDifficulty();
  197. int nOffset = mp_coopmission_bot_difficulty_offset.GetInt();
  198. nDifficulty = nDifficulty + nOffset;
  199. BotDifficultyType botDifficultyType = BOT_EASY;
  200. if ( nDifficulty >= 6 )
  201. botDifficultyType = BOT_EXPERT;
  202. else if ( nDifficulty >= 3 )
  203. botDifficultyType = BOT_HARD;
  204. else if ( nDifficulty >= 1 )
  205. botDifficultyType = BOT_NORMAL;
  206. bool bIsAgressive = pEnemySpawnSpot->IsBotAgressive();
  207. char profileName[128];
  208. Q_snprintf( profileName, sizeof( profileName ), "Level_%d%s", ( int )nDifficulty, bIsAgressive ? "_Agressive" : "" );
  209. const BotProfile* pNewProfileData = TheBotProfiles->GetProfileMatchingTemplate( profileName, TEAM_TERRORIST, botDifficultyType, CSGameRules()->GetMatchDevice(), true );
  210. if ( pNewProfileData )
  211. {
  212. char szHeavy[64] = {};
  213. int nArmor = pEnemySpawnSpot->GetArmorToSpawnWith();
  214. if ( nArmor == 1 )
  215. Q_snprintf( szHeavy, sizeof( szHeavy ), "%s", "" );
  216. else if ( nArmor == 2 )
  217. Q_snprintf( szHeavy, sizeof( szHeavy ), "%s", "Heavy " );
  218. char szName[128] = {};
  219. char szDifficulty[128] = {};
  220. if ( nDifficulty >= 6 )
  221. Q_snprintf( szDifficulty, sizeof( szDifficulty ), "%s", "Elite " );
  222. else if ( nDifficulty >= 4 )
  223. Q_snprintf( szDifficulty, sizeof( szDifficulty ), "%s", "Expert " );
  224. else
  225. Q_snprintf( szDifficulty, sizeof( szDifficulty ), "%s", "" );
  226. Q_snprintf( szName, sizeof( szName ), "%s%sPhoenix", szHeavy, szDifficulty );
  227. Initialize( pNewProfileData, GetTeamNumber() );
  228. SetPlayerName( szName );
  229. // have to inform the engine that the bot name has been updated
  230. engine->SetFakeClientConVarValue( edict(), "name", szName );
  231. }
  232. // sleeping needs to be set after the initialize because we reset values in the init
  233. m_bIsSleeping = pEnemySpawnSpot->ShouldStartAsleep();
  234. }
  235. }
  236. }
  237. //--------------------------------------------------------------------------------------------------------------
  238. /**
  239. * Reset internal data to initial state
  240. */
  241. void CCSBot::ResetValues( void )
  242. {
  243. m_pChatter->Reset();
  244. m_gameState.Reset();
  245. m_avoid = NULL;
  246. m_avoidTimestamp = 0.0f;
  247. m_hurryTimer.Invalidate();
  248. m_alertTimer.Invalidate();
  249. m_sneakTimer.Invalidate();
  250. m_noiseBendTimer.Invalidate();
  251. m_bendNoisePositionValid = false;
  252. m_isStuck = false;
  253. m_stuckTimestamp = 0.0f;
  254. m_wiggleTimer.Invalidate();
  255. m_stuckJumpTimer.Invalidate();
  256. m_nextCleanupCheckTimestamp = 0.0f;
  257. m_pathLength = 0;
  258. m_pathIndex = 0;
  259. m_areaEnteredTimestamp = 0.0f;
  260. m_currentArea = NULL;
  261. m_lastKnownArea = NULL;
  262. m_isStopping = false;
  263. m_avoidFriendTimer.Invalidate();
  264. m_isFriendInTheWay = false;
  265. m_isWaitingBehindFriend = false;
  266. m_isAvoidingGrenade.Invalidate();
  267. StopPanicking();
  268. m_disposition = ENGAGE_AND_INVESTIGATE;
  269. m_enemy = NULL;
  270. m_grenadeTossState = NOT_THROWING;
  271. m_initialEncounterArea = NULL;
  272. m_wasSafe = true;
  273. m_nearbyEnemyCount = 0;
  274. m_enemyPlace = 0;
  275. m_nearbyFriendCount = 0;
  276. m_closestVisibleFriend = NULL;
  277. m_closestVisibleHumanFriend = NULL;
  278. for( int w=0; w<MAX_PLAYERS; ++w )
  279. {
  280. m_watchInfo[w].timestamp = 0.0f;
  281. m_watchInfo[w].isEnemy = false;
  282. m_playerTravelDistance[ w ] = -1.0f;
  283. }
  284. // randomly offset each bot's timer to spread computation out
  285. m_updateTravelDistanceTimer.Start( RandomFloat( 0.0f, 0.9f ) );
  286. m_travelDistancePhase = 0;
  287. m_isEnemyVisible = false;
  288. m_visibleEnemyParts = NONE;
  289. m_lastSawEnemyTimestamp = -999.9f;
  290. m_firstSawEnemyTimestamp = 0.0f;
  291. m_currentEnemyAcquireTimestamp = 0.0f;
  292. m_isLastEnemyDead = true;
  293. m_attacker = NULL;
  294. m_attackedTimestamp = 0.0f;
  295. m_enemyDeathTimestamp = 0.0f;
  296. m_friendDeathTimestamp = 0.0f;
  297. m_lastVictimID = 0;
  298. m_isAimingAtEnemy = false;
  299. m_fireWeaponTimestamp = 0.0f;
  300. m_equipTimer.Invalidate();
  301. m_zoomTimer.Invalidate();
  302. m_isFollowing = false;
  303. m_leader = NULL;
  304. m_followTimestamp = 0.0f;
  305. m_allowAutoFollowTime = 0.0f;
  306. m_enemyQueueIndex = 0;
  307. m_enemyQueueCount = 0;
  308. m_enemyQueueAttendIndex = 0;
  309. m_bomber = NULL;
  310. m_bIsSleeping = false;
  311. m_isEnemySniperVisible = false;
  312. m_sawEnemySniperTimer.Invalidate();
  313. m_lookAroundStateTimestamp = 0.0f;
  314. m_inhibitLookAroundTimestamp = 0.0f;
  315. m_lookPitch = 0.0f;
  316. m_lookPitchVel = 0.0f;
  317. m_lookYaw = 0.0f;
  318. m_lookYawVel = 0.0f;
  319. m_lookAtSpotState = NOT_LOOKING_AT_SPOT;
  320. m_targetSpot.Zero();
  321. m_targetSpotVelocity.Zero();
  322. m_targetSpotPredicted.Zero();
  323. m_aimError.Init();
  324. m_aimGoal.Init();
  325. m_targetSpotTime = 0.0f;
  326. m_aimFocus = 0.0f;
  327. m_aimFocusInterval = 0.0f;
  328. m_aimFocusNextUpdate = 0.0f;
  329. for( int p=0; p<MAX_PLAYERS; ++p )
  330. {
  331. m_partInfo[p].m_validFrame = 0;
  332. }
  333. m_spotEncounter = NULL;
  334. m_spotCheckTimestamp = 0.0f;
  335. m_peripheralTimestamp = 0.0f;
  336. m_avgVelIndex = 0;
  337. m_avgVelCount = 0;
  338. m_lastOrigin = GetCentroid( this );
  339. m_lastRadioCommand = RADIO_INVALID;
  340. m_lastRadioRecievedTimestamp = 0.0f;
  341. m_lastRadioSentTimestamp = 0.0f;
  342. m_radioSubject = NULL;
  343. m_voiceEndTimestamp = 0.0f;
  344. m_hostageEscortCount = 0;
  345. m_hostageEscortCountTimestamp = 0.0f;
  346. m_noisePosition = Vector( 0, 0, 0 );
  347. m_noiseTimestamp = 0.0f;
  348. m_stateTimestamp = 0.0f;
  349. m_task = SEEK_AND_DESTROY;
  350. m_taskEntity = NULL;
  351. m_approachPointCount = 0;
  352. m_approachPointViewPosition.x = 99999999999.9f;
  353. m_approachPointViewPosition.y = 0.0f;
  354. m_approachPointViewPosition.z = 0.0f;
  355. m_checkedHidingSpotCount = 0;
  356. StandUp();
  357. Run();
  358. m_mustRunTimer.Invalidate();
  359. m_waitTimer.Invalidate();
  360. m_pathLadder = NULL;
  361. m_repathTimer.Invalidate();
  362. m_huntState.ClearHuntArea();
  363. m_hasVisitedEnemySpawn = false;
  364. m_stillTimer.Invalidate();
  365. // adjust morale - if we died, our morale decreased,
  366. // but if we live, no adjustement (round win/loss also adjusts morale)
  367. if (m_diedLastRound)
  368. DecreaseMorale();
  369. m_diedLastRound = false;
  370. // IsRogue() randomly changes this
  371. m_isRogue = false;
  372. m_surpriseTimer.Invalidate();
  373. // even though these are EHANDLEs, they need to be NULL-ed
  374. m_goalEntity = NULL;
  375. m_avoid = NULL;
  376. m_enemy = NULL;
  377. for ( int i=0; i<MAX_ENEMY_QUEUE; ++i )
  378. {
  379. m_enemyQueue[i].player = NULL;
  380. m_enemyQueue[i].isReloading = false;
  381. m_enemyQueue[i].isProtectedByShield = false;
  382. }
  383. m_burnedByFlamesTimer.Invalidate();
  384. #ifdef OPT_VIS_CSGO
  385. V_memset( m_bVis, 0, sizeof(m_bVis) );
  386. V_memset( m_aVisParts, 0, sizeof(m_aVisParts) );
  387. #endif
  388. // start in idle state
  389. m_isOpeningDoor = false;
  390. StopAttacking();
  391. Idle();
  392. }
  393. //--------------------------------------------------------------------------------------------------------------
  394. /**
  395. * Called when bot is placed in map, and when bots are reset after a round ends.
  396. * NOTE: For some reason, this can be called twice when a bot is added.
  397. */
  398. void CCSBot::Spawn( void )
  399. {
  400. if ( CSGameRules() && CSGameRules()->IsPlayingCoopMission() && GetTeamNumber() == TEAM_TERRORIST )
  401. SetLastCoopSpawnPoint( NULL );
  402. // do the normal player spawn process
  403. BaseClass::Spawn();
  404. ResetValues();
  405. Buy();
  406. if ( CSGameRules()->IsPlayingCoopMission() )
  407. {
  408. if ( GetTeamNumber() == TEAM_CT )
  409. {
  410. SetDisposition( CCSBot::SELF_DEFENSE );
  411. for ( int i = 1; i <= MAX_PLAYERS; i++ )
  412. {
  413. CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
  414. if ( pPlayer && !pPlayer->IsBot() && pPlayer->GetTeamNumber() == TEAM_CT )
  415. {
  416. //m_isFollowing = true;
  417. //m_leader = pPlayer;
  418. //SetTask( CCSBot::FOLLOW );
  419. Follow( pPlayer );
  420. m_lastRadioCommand = RADIO_FOLLOW_ME;
  421. m_lastRadioRecievedTimestamp = gpGlobals->curtime;
  422. m_radioSubject = pPlayer;
  423. m_radioPosition = GetCentroid( pPlayer );
  424. break;
  425. }
  426. }
  427. }
  428. else if ( GetTeamNumber() == TEAM_TERRORIST )
  429. {
  430. CoopInitialize();
  431. }
  432. }
  433. // set the bot name here (after we've had a chance to initialize a new profile
  434. V_strcpy_safe( m_name, GetPlayerName() );
  435. }