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.

872 lines
24 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //----------------------------------------------------------------------------------------------------------------
  3. // tf_bot_manager.cpp
  4. // Team Fortress NextBotManager
  5. // Tom Bui, February 2010
  6. //----------------------------------------------------------------------------------------------------------------
  7. #include "cbase.h"
  8. #include "tf_bot_manager.h"
  9. #include "Player/NextBotPlayer.h"
  10. #include "team.h"
  11. #include "tf_bot.h"
  12. #include "tf_gamerules.h"
  13. #include "bot/map_entities/tf_bot_hint.h"
  14. #include "bot/map_entities/tf_bot_hint_sentrygun.h"
  15. #include "bot/map_entities/tf_bot_hint_teleporter_exit.h"
  16. //----------------------------------------------------------------------------------------------------------------
  17. // Creates and sets CTFBotManager as the NextBotManager singleton
  18. static CTFBotManager sTFBotManager;
  19. extern ConVar tf_bot_force_class;
  20. ConVar tf_bot_difficulty( "tf_bot_difficulty", "1", FCVAR_NONE, "Defines the skill of bots joining the game. Values are: 0=easy, 1=normal, 2=hard, 3=expert." );
  21. ConVar tf_bot_quota( "tf_bot_quota", "0", FCVAR_NONE, "Determines the total number of tf bots in the game." );
  22. ConVar tf_bot_quota_mode( "tf_bot_quota_mode", "normal", FCVAR_NONE, "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." );
  23. ConVar tf_bot_join_after_player( "tf_bot_join_after_player", "1", FCVAR_NONE, "If nonzero, bots wait until a player joins before entering the game." );
  24. ConVar tf_bot_auto_vacate( "tf_bot_auto_vacate", "1", FCVAR_NONE, "If nonzero, bots will automatically leave to make room for human players." );
  25. ConVar tf_bot_offline_practice( "tf_bot_offline_practice", "0", FCVAR_NONE, "Tells the server that it is in offline practice mode." );
  26. ConVar tf_bot_melee_only( "tf_bot_melee_only", "0", FCVAR_GAMEDLL, "If nonzero, TFBots will only use melee weapons" );
  27. extern const char *GetRandomBotName( void );
  28. extern void CreateBotName( int iTeam, int iClassIndex, CTFBot::DifficultyType skill, char* pBuffer, int iBufferSize );
  29. static bool UTIL_KickBotFromTeam( int kickTeam )
  30. {
  31. int i;
  32. // try to kick a dead bot first
  33. for ( i = 1; i <= gpGlobals->maxClients; ++i )
  34. {
  35. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  36. CTFBot* pBot = dynamic_cast<CTFBot*>(pPlayer);
  37. if (pBot == NULL)
  38. continue;
  39. if ( pBot->HasAttribute( CTFBot::QUOTA_MANANGED ) == false )
  40. continue;
  41. if ( ( pPlayer->GetFlags() & FL_FAKECLIENT ) == 0 )
  42. continue;
  43. if ( !pPlayer->IsAlive() && pPlayer->GetTeamNumber() == kickTeam )
  44. {
  45. // its a bot on the right team - kick it
  46. engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", pPlayer->GetUserID() ) );
  47. return true;
  48. }
  49. }
  50. // no dead bots, kick any bot on the given team
  51. for ( i = 1; i <= gpGlobals->maxClients; ++i )
  52. {
  53. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  54. CTFBot* pBot = dynamic_cast<CTFBot*>(pPlayer);
  55. if (pBot == NULL)
  56. continue;
  57. if ( pBot->HasAttribute( CTFBot::QUOTA_MANANGED ) == false )
  58. continue;
  59. if ( ( pPlayer->GetFlags() & FL_FAKECLIENT ) == 0 )
  60. continue;
  61. if (pPlayer->GetTeamNumber() == kickTeam)
  62. {
  63. // its a bot on the right team - kick it
  64. engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", pPlayer->GetUserID() ) );
  65. return true;
  66. }
  67. }
  68. return false;
  69. }
  70. //----------------------------------------------------------------------------------------------------------------
  71. CTFBotManager::CTFBotManager()
  72. : NextBotManager()
  73. , m_flNextPeriodicThink( 0 )
  74. {
  75. NextBotManager::SetInstance( this );
  76. }
  77. //----------------------------------------------------------------------------------------------------------------
  78. CTFBotManager::~CTFBotManager()
  79. {
  80. NextBotManager::SetInstance( NULL );
  81. }
  82. //----------------------------------------------------------------------------------------------------------------
  83. void CTFBotManager::OnMapLoaded( void )
  84. {
  85. NextBotManager::OnMapLoaded();
  86. ClearStuckBotData();
  87. }
  88. //----------------------------------------------------------------------------------------------------------------
  89. void CTFBotManager::OnRoundRestart( void )
  90. {
  91. NextBotManager::OnRoundRestart();
  92. // clear all hint ownership
  93. CTFBotHint *hint = NULL;
  94. while( ( hint = (CTFBotHint *)( gEntList.FindEntityByClassname( hint, "func_tfbot_hint" ) ) ) != NULL )
  95. {
  96. hint->SetOwnerEntity( NULL );
  97. }
  98. CTFBotHintSentrygun *sentryHint = NULL;
  99. while( ( sentryHint = (CTFBotHintSentrygun *)( gEntList.FindEntityByClassname( sentryHint, "bot_hint_sentrygun" ) ) ) != NULL )
  100. {
  101. sentryHint->SetOwnerEntity( NULL );
  102. }
  103. CTFBotHintTeleporterExit *teleporterHint = NULL;
  104. while( ( teleporterHint = (CTFBotHintTeleporterExit *)( gEntList.FindEntityByClassname( teleporterHint, "bot_hint_teleporter_exit" ) ) ) != NULL )
  105. {
  106. teleporterHint->SetOwnerEntity( NULL );
  107. }
  108. #ifdef TF_CREEP_MODE
  109. m_creepExperience[ TF_TEAM_RED ] = 0;
  110. m_creepExperience[ TF_TEAM_BLUE ] = 0;
  111. #endif
  112. m_isMedeivalBossScenarioSetup = false;
  113. }
  114. //----------------------------------------------------------------------------------------------------------------
  115. void CTFBotManager::Update()
  116. {
  117. MaintainBotQuota();
  118. DrawStuckBotData();
  119. #ifdef TF_CREEP_MODE
  120. UpdateCreepWaves();
  121. #endif
  122. NextBotManager::Update();
  123. }
  124. #ifdef TF_CREEP_MODE
  125. ConVar tf_creep_initial_delay( "tf_creep_initial_delay", "30" );
  126. ConVar tf_creep_wave_interval( "tf_creep_wave_interval", "30" );
  127. ConVar tf_creep_wave_count( "tf_creep_wave_count", "3" );
  128. ConVar tf_creep_class( "tf_creep_class", "heavyweapons" );
  129. ConVar tf_creep_level_up( "tf_creep_level_up", "6" );
  130. //----------------------------------------------------------------------------------------------------------------
  131. void CTFBotManager::UpdateCreepWaves()
  132. {
  133. if ( !TFGameRules()->IsCreepWaveMode() )
  134. return;
  135. if ( TFGameRules()->RoundHasBeenWon() )
  136. {
  137. // no more creep waves - game is over
  138. return;
  139. }
  140. if ( TFGameRules()->InSetup() || TFGameRules()->State_Get() == GR_STATE_STARTGAME || TFGameRules()->State_Get() == GR_STATE_PREROUND )
  141. {
  142. // no creeps at start of round
  143. m_creepWaveTimer.Start( tf_creep_initial_delay.GetFloat() );
  144. // delete all creeps
  145. for( int i=1; i<=gpGlobals->maxClients; ++i )
  146. {
  147. CBasePlayer *player = static_cast< CBasePlayer * >( UTIL_PlayerByIndex( i ) );
  148. if ( !player )
  149. continue;
  150. if ( FNullEnt( player->edict() ) )
  151. continue;
  152. CTFBot *creep = ToTFBot( player );
  153. if ( !creep || !creep->HasAttribute( CTFBot::IS_NPC ) )
  154. continue;
  155. engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", player->GetUserID() ) );
  156. }
  157. return;
  158. }
  159. if ( m_creepWaveTimer.IsElapsed() )
  160. {
  161. m_creepWaveTimer.Start( tf_creep_wave_interval.GetFloat() );
  162. SpawnCreepWave( TF_TEAM_RED );
  163. SpawnCreepWave( TF_TEAM_BLUE );
  164. }
  165. }
  166. //----------------------------------------------------------------------------------------------------------------
  167. void CTFBotManager::SpawnCreepWave( int team )
  168. {
  169. CTFBotSquad *squad = new CTFBotSquad;
  170. for( int i=0; i<tf_creep_wave_count.GetInt(); ++i )
  171. {
  172. SpawnCreep( team, squad );
  173. }
  174. }
  175. //----------------------------------------------------------------------------------------------------------------
  176. void CTFBotManager::SpawnCreep( int team, CTFBotSquad *squad )
  177. {
  178. CTFBot *bot = NextBotCreatePlayerBot< CTFBot >( "Creep" );
  179. if ( !bot )
  180. return;
  181. bot->SetAttribute( CTFBot::IS_NPC );
  182. bot->HandleCommand_JoinTeam( team == TF_TEAM_RED ? "red" : "blue" );
  183. bot->SetDifficulty( CTFBot::NORMAL );
  184. bot->HandleCommand_JoinClass( tf_creep_class.GetString() );
  185. bot->JoinSquad( squad );
  186. bot->AddGlowEffect();
  187. //BotGenerateAndWearItem( bot, "Honest Halo" );
  188. }
  189. //----------------------------------------------------------------------------------------------------------------
  190. void CTFBotManager::OnCreepKilled( CTFPlayer *killer )
  191. {
  192. CTFBot *bot = ToTFBot( killer );
  193. if ( bot && bot->HasAttribute( CTFBot::IS_NPC ) )
  194. return;
  195. ++m_creepExperience[ killer->GetTeamNumber() ];
  196. /*
  197. int xp = m_creepExperience[ killer->GetTeamNumber() ];
  198. int level = xp / tf_creep_level_up.GetInt();
  199. int left = xp % tf_creep_level_up.GetInt();
  200. char text[256];
  201. Q_snprintf( text, sizeof(text), "%s killed a creep. %s team LVL = %d+%d/%d\n",
  202. killer->GetPlayerName(),
  203. killer->GetTeamNumber() == TF_TEAM_RED ? "Red" : "Blue",
  204. level+1, left, tf_creep_level_up.GetInt() );
  205. UTIL_ClientPrintAll( HUD_PRINTTALK, text );
  206. */
  207. UTIL_ClientPrintAll( HUD_PRINTTALK, "%s killed a creep" );
  208. }
  209. #endif // TF_CREEP_MODE
  210. //----------------------------------------------------------------------------------------------------------------
  211. bool CTFBotManager::RemoveBotFromTeamAndKick( int nTeam )
  212. {
  213. CUtlVector< CTFPlayer* > vecCandidates;
  214. // Gather potential candidates
  215. for ( int i = 1; i <= gpGlobals->maxClients; ++i )
  216. {
  217. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  218. if ( pPlayer == NULL )
  219. continue;
  220. if ( FNullEnt( pPlayer->edict() ) )
  221. continue;
  222. if ( !pPlayer->IsConnected() )
  223. continue;
  224. CTFBot* pBot = dynamic_cast<CTFBot*>( pPlayer );
  225. if ( pBot && pBot->HasAttribute( CTFBot::QUOTA_MANANGED ) )
  226. {
  227. if ( pBot->GetTeamNumber() == nTeam )
  228. {
  229. vecCandidates.AddToTail( pPlayer );
  230. }
  231. }
  232. }
  233. CTFPlayer *pVictim = NULL;
  234. if ( vecCandidates.Count() > 0 )
  235. {
  236. // first look for bots that are currently dead
  237. FOR_EACH_VEC( vecCandidates, i )
  238. {
  239. CTFPlayer *pPlayer = vecCandidates[i];
  240. if ( pPlayer && !pPlayer->IsAlive() )
  241. {
  242. pVictim = pPlayer;
  243. break;
  244. }
  245. }
  246. // if we didn't fine one, try to kick anyone on the team
  247. if ( !pVictim )
  248. {
  249. FOR_EACH_VEC( vecCandidates, i )
  250. {
  251. CTFPlayer *pPlayer = vecCandidates[i];
  252. if ( pPlayer )
  253. {
  254. pVictim = pPlayer;
  255. break;
  256. }
  257. }
  258. }
  259. }
  260. if ( pVictim )
  261. {
  262. if ( pVictim->IsAlive() )
  263. {
  264. pVictim->CommitSuicide();
  265. }
  266. pVictim->ForceChangeTeam( TEAM_UNASSIGNED ); // skipping TEAM_SPECTATOR because some servers don't allow spectators
  267. UTIL_KickBotFromTeam( TEAM_UNASSIGNED );
  268. return true;
  269. }
  270. return false;
  271. }
  272. //----------------------------------------------------------------------------------------------------------------
  273. void CTFBotManager::MaintainBotQuota()
  274. {
  275. if ( TheNavMesh->IsGenerating() )
  276. return;
  277. if ( g_fGameOver )
  278. return;
  279. // new players can't spawn immediately after the round has been going for some time
  280. if ( !TFGameRules() )
  281. return;
  282. // training mode controls the bots
  283. if ( TFGameRules()->IsInTraining() )
  284. return;
  285. // if it is not time to do anything...
  286. if ( gpGlobals->curtime < m_flNextPeriodicThink )
  287. return;
  288. // think every quarter second
  289. m_flNextPeriodicThink = gpGlobals->curtime + 0.25f;
  290. // don't add bots until local player has been registered, to make sure he's player ID #1
  291. if ( !engine->IsDedicatedServer() )
  292. {
  293. CBasePlayer *pPlayer = UTIL_GetListenServerHost();
  294. if ( !pPlayer )
  295. return;
  296. }
  297. // We want to balance based on who's playing on game teams not necessary who's on team spectator, etc.
  298. int nConnectedClients = 0;
  299. int nTFBots = 0;
  300. int nTFBotsOnGameTeams = 0;
  301. int nNonTFBotsOnGameTeams = 0;
  302. int nSpectators = 0;
  303. for ( int i = 1; i <= gpGlobals->maxClients; ++i )
  304. {
  305. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  306. if ( pPlayer == NULL )
  307. continue;
  308. if ( FNullEnt( pPlayer->edict() ) )
  309. continue;
  310. if ( !pPlayer->IsConnected() )
  311. continue;
  312. CTFBot* pBot = dynamic_cast<CTFBot*>( pPlayer );
  313. if ( pBot && pBot->HasAttribute( CTFBot::QUOTA_MANANGED ) )
  314. {
  315. nTFBots++;
  316. if ( pPlayer->GetTeamNumber() == TF_TEAM_RED || pPlayer->GetTeamNumber() == TF_TEAM_BLUE )
  317. {
  318. nTFBotsOnGameTeams++;
  319. }
  320. }
  321. else
  322. {
  323. if ( pPlayer->GetTeamNumber() == TF_TEAM_RED || pPlayer->GetTeamNumber() == TF_TEAM_BLUE )
  324. {
  325. nNonTFBotsOnGameTeams++;
  326. }
  327. else if ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR )
  328. {
  329. nSpectators++;
  330. }
  331. }
  332. nConnectedClients++;
  333. }
  334. int desiredBotCount = tf_bot_quota.GetInt();
  335. int nTotalNonTFBots = nConnectedClients - nTFBots;
  336. if ( FStrEq( tf_bot_quota_mode.GetString(), "fill" ) )
  337. {
  338. desiredBotCount = MAX( 0, desiredBotCount - nNonTFBotsOnGameTeams );
  339. }
  340. else if ( FStrEq( tf_bot_quota_mode.GetString(), "match" ) )
  341. {
  342. // If bot_quota_mode is 'match', we want the number of bots to be bot_quota * total humans
  343. desiredBotCount = (int)MAX( 0, tf_bot_quota.GetFloat() * nNonTFBotsOnGameTeams );
  344. }
  345. // wait for a player to join, if necessary
  346. if ( tf_bot_join_after_player.GetBool() )
  347. {
  348. if ( ( nNonTFBotsOnGameTeams == 0 ) && ( nSpectators == 0 ) )
  349. {
  350. desiredBotCount = 0;
  351. }
  352. }
  353. // if bots will auto-vacate, we need to keep one slot open to allow players to join
  354. if ( tf_bot_auto_vacate.GetBool() )
  355. {
  356. desiredBotCount = MIN( desiredBotCount, gpGlobals->maxClients - nTotalNonTFBots - 1 );
  357. }
  358. else
  359. {
  360. desiredBotCount = MIN( desiredBotCount, gpGlobals->maxClients - nTotalNonTFBots );
  361. }
  362. // add bots if necessary
  363. if ( desiredBotCount > nTFBotsOnGameTeams )
  364. {
  365. // don't try to add a bot if it would unbalance
  366. if ( !TFGameRules()->WouldChangeUnbalanceTeams( TF_TEAM_BLUE, TEAM_UNASSIGNED ) ||
  367. !TFGameRules()->WouldChangeUnbalanceTeams( TF_TEAM_RED, TEAM_UNASSIGNED ) )
  368. {
  369. CTFBot *pBot = GetAvailableBotFromPool();
  370. if ( pBot == NULL )
  371. {
  372. pBot = NextBotCreatePlayerBot< CTFBot >( GetRandomBotName() );
  373. }
  374. if ( pBot )
  375. {
  376. pBot->SetAttribute( CTFBot::QUOTA_MANANGED );
  377. // join a team before we pick our class, since we use our teammates to decide what class to be
  378. pBot->HandleCommand_JoinTeam( "auto" );
  379. const char *classname = FStrEq( tf_bot_force_class.GetString(), "" ) ? pBot->GetNextSpawnClassname() : tf_bot_force_class.GetString();
  380. pBot->HandleCommand_JoinClass( classname );
  381. // give the bot a proper name
  382. char name[256];
  383. CTFBot::DifficultyType skill = pBot->GetDifficulty();
  384. CreateBotName( pBot->GetTeamNumber(), pBot->GetPlayerClass()->GetClassIndex(), skill, name, sizeof( name ) );
  385. engine->SetFakeClientConVarValue( pBot->edict(), "name", name );
  386. // Keep track of any bots we add during a match
  387. CMatchInfo *pMatchInfo = GTFGCClientSystem()->GetMatch();
  388. if ( pMatchInfo )
  389. {
  390. pMatchInfo->m_nBotsAdded++;
  391. }
  392. }
  393. }
  394. }
  395. else if ( desiredBotCount < nTFBotsOnGameTeams )
  396. {
  397. // kick a bot to maintain quota
  398. // first remove any unassigned bots
  399. if ( UTIL_KickBotFromTeam( TEAM_UNASSIGNED ) )
  400. return;
  401. int kickTeam;
  402. CTeam *pRed = GetGlobalTeam( TF_TEAM_RED );
  403. CTeam *pBlue = GetGlobalTeam( TF_TEAM_BLUE );
  404. // remove from the team that has more players
  405. if ( pBlue->GetNumPlayers() > pRed->GetNumPlayers() )
  406. {
  407. kickTeam = TF_TEAM_BLUE;
  408. }
  409. else if ( pBlue->GetNumPlayers() < pRed->GetNumPlayers() )
  410. {
  411. kickTeam = TF_TEAM_RED;
  412. }
  413. // remove from the team that's winning
  414. else if ( pBlue->GetScore() > pRed->GetScore() )
  415. {
  416. kickTeam = TF_TEAM_BLUE;
  417. }
  418. else if ( pBlue->GetScore() < pRed->GetScore() )
  419. {
  420. kickTeam = TF_TEAM_RED;
  421. }
  422. else
  423. {
  424. // teams and scores are equal, pick a team at random
  425. kickTeam = (RandomInt( 0, 1 ) == 0) ? TF_TEAM_BLUE : TF_TEAM_RED;
  426. }
  427. // attempt to kick a bot from the given team
  428. if ( UTIL_KickBotFromTeam( kickTeam ) )
  429. return;
  430. // if there were no bots on the team, kick a bot from the other team
  431. UTIL_KickBotFromTeam( kickTeam == TF_TEAM_BLUE ? TF_TEAM_RED : TF_TEAM_BLUE );
  432. }
  433. }
  434. //----------------------------------------------------------------------------------------------------------------
  435. bool CTFBotManager::IsAllBotTeam( int iTeam )
  436. {
  437. CTeam *pTeam = GetGlobalTeam( iTeam );
  438. if ( pTeam == NULL )
  439. {
  440. return false;
  441. }
  442. // check to see if any players on the team are humans
  443. for ( int i = 0, n = pTeam->GetNumPlayers(); i < n; ++i )
  444. {
  445. CTFPlayer *pPlayer = ToTFPlayer( pTeam->GetPlayer( i ) );
  446. if ( pPlayer == NULL )
  447. {
  448. continue;
  449. }
  450. if ( pPlayer->IsBot() == false )
  451. {
  452. return false;
  453. }
  454. }
  455. // if we made it this far, then they must all be bots!
  456. if ( pTeam->GetNumPlayers() != 0 )
  457. {
  458. return true;
  459. }
  460. // okay, this is a bit trickier...
  461. // if there are no people on this team, then we need to check the "assigned" human team
  462. return TFGameRules()->GetAssignedHumanTeam() != iTeam;
  463. }
  464. //----------------------------------------------------------------------------------------------------------------
  465. void CTFBotManager::SetIsInOfflinePractice(bool bIsInOfflinePractice)
  466. {
  467. tf_bot_offline_practice.SetValue( bIsInOfflinePractice ? 1 : 0 );
  468. }
  469. //----------------------------------------------------------------------------------------------------------------
  470. bool CTFBotManager::IsInOfflinePractice() const
  471. {
  472. return tf_bot_offline_practice.GetInt() != 0;
  473. }
  474. //----------------------------------------------------------------------------------------------------------------
  475. bool CTFBotManager::IsMeleeOnly() const
  476. {
  477. return tf_bot_melee_only.GetBool();
  478. }
  479. //----------------------------------------------------------------------------------------------------------------
  480. void CTFBotManager::RevertOfflinePracticeConvars()
  481. {
  482. tf_bot_quota.Revert();
  483. tf_bot_quota_mode.Revert();
  484. tf_bot_auto_vacate.Revert();
  485. tf_bot_difficulty.Revert();
  486. tf_bot_offline_practice.Revert();
  487. }
  488. //----------------------------------------------------------------------------------------------------------------
  489. void CTFBotManager::LevelShutdown()
  490. {
  491. m_flNextPeriodicThink = 0.0f;
  492. if ( IsInOfflinePractice() )
  493. {
  494. RevertOfflinePracticeConvars();
  495. SetIsInOfflinePractice( false );
  496. }
  497. }
  498. //----------------------------------------------------------------------------------------------------------------
  499. CTFBot* CTFBotManager::GetAvailableBotFromPool()
  500. {
  501. for ( int i = 1; i <= gpGlobals->maxClients; ++i )
  502. {
  503. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  504. CTFBot* pBot = dynamic_cast<CTFBot*>(pPlayer);
  505. if (pBot == NULL)
  506. continue;
  507. if ( ( pBot->GetFlags() & FL_FAKECLIENT ) == 0 )
  508. continue;
  509. if ( pBot->GetTeamNumber() == TEAM_SPECTATOR || pBot->GetTeamNumber() == TEAM_UNASSIGNED )
  510. {
  511. pBot->ClearAttribute( CTFBot::QUOTA_MANANGED );
  512. return pBot;
  513. }
  514. }
  515. return NULL;
  516. }
  517. //----------------------------------------------------------------------------------------------------------------
  518. void CTFBotManager::OnForceAddedBots( int iNumAdded )
  519. {
  520. tf_bot_quota.SetValue( tf_bot_quota.GetInt() + iNumAdded );
  521. m_flNextPeriodicThink = gpGlobals->curtime + 1.0f;
  522. }
  523. //----------------------------------------------------------------------------------------------------------------
  524. void CTFBotManager::OnForceKickedBots( int iNumKicked )
  525. {
  526. tf_bot_quota.SetValue( MAX( tf_bot_quota.GetInt() - iNumKicked, 0 ) );
  527. // allow time for the bots to be kicked
  528. m_flNextPeriodicThink = gpGlobals->curtime + 2.0f;
  529. }
  530. //----------------------------------------------------------------------------------------------------------------
  531. CTFBotManager &TheTFBots( void )
  532. {
  533. return static_cast<CTFBotManager&>( TheNextBots() );
  534. }
  535. //----------------------------------------------------------------------------------------------------------------
  536. //----------------------------------------------------------------------------------------------------------------
  537. CON_COMMAND_F( tf_bot_debug_stuck_log, "Given a server logfile, visually display bot stuck locations.", FCVAR_GAMEDLL | FCVAR_CHEAT )
  538. {
  539. // Listenserver host or rcon access only!
  540. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  541. return;
  542. if ( args.ArgC() < 2 )
  543. {
  544. DevMsg( "%s <logfilename>\n", args.Arg(0) );
  545. return;
  546. }
  547. FileHandle_t file = filesystem->Open( args.Arg(1), "r", "GAME" );
  548. const int maxBufferSize = 1024;
  549. char buffer[ maxBufferSize ];
  550. char logMapName[ maxBufferSize ];
  551. logMapName[0] = '\000';
  552. TheTFBots().ClearStuckBotData();
  553. if ( file )
  554. {
  555. int line = 0;
  556. while( !filesystem->EndOfFile( file ) )
  557. {
  558. filesystem->ReadLine( buffer, maxBufferSize, file );
  559. ++line;
  560. strtok( buffer, ":" );
  561. strtok( NULL, ":" );
  562. strtok( NULL, ":" );
  563. char *first = strtok( NULL, " " );
  564. if ( !first )
  565. continue;
  566. if ( !strcmp( first, "Loading" ) )
  567. {
  568. // L 08/08/2012 - 15:10:47: Loading map "mvm_coaltown"
  569. strtok( NULL, " " );
  570. char *mapname = strtok( NULL, "\"" );
  571. if ( mapname )
  572. {
  573. strcpy( logMapName, mapname );
  574. Warning( "*** Log file from map '%s'\n", mapname );
  575. }
  576. }
  577. else if ( first[0] == '\"' )
  578. {
  579. // might be a player ID
  580. char *playerClassname = &first[1];
  581. char *nameEnd = playerClassname;
  582. while( *nameEnd != '\000' && *nameEnd != '<' )
  583. ++nameEnd;
  584. *nameEnd = '\000';
  585. char *botIDString = ++nameEnd;
  586. char *IDEnd = botIDString;
  587. while( *IDEnd != '\000' && *IDEnd != '>' )
  588. ++IDEnd;
  589. *IDEnd = '\000';
  590. int botID = atoi( botIDString );
  591. char *second = strtok( NULL, " " );
  592. if ( second && !strcmp( second, "stuck" ) )
  593. {
  594. CStuckBot *stuckBot = TheTFBots().FindOrCreateStuckBot( botID, playerClassname );
  595. CStuckBotEvent *stuckEvent = new CStuckBotEvent;
  596. // L 08/08/2012 - 15:15:05: "Scout<53><BOT><Blue>" stuck (position "-180.61 2471.29 216.04") (duration "2.52") L 08/08/2012 - 15:15:05: path_goal ( "-180.61 2471.29 216.04" )
  597. strtok( NULL, " (\"" ); // (position
  598. stuckEvent->m_stuckSpot.x = (float)atof( strtok( NULL, " )\"" ) );
  599. stuckEvent->m_stuckSpot.y = (float)atof( strtok( NULL, " )\"" ) );
  600. stuckEvent->m_stuckSpot.z = (float)atof( strtok( NULL, " )\"" ) );
  601. strtok( NULL, ") (\"" );
  602. stuckEvent->m_stuckDuration = (float)atof( strtok( NULL, "\"" ) );
  603. strtok( NULL, ") (\"-L0123456789/:" ); // path_goal
  604. char *goal = strtok( NULL, ") (\"" );
  605. if ( goal && strcmp( goal, "NULL" ) )
  606. {
  607. stuckEvent->m_isGoalValid = true;
  608. stuckEvent->m_goalSpot.x = (float)atof( goal );
  609. stuckEvent->m_goalSpot.y = (float)atof( strtok( NULL, ") (\"" ) );
  610. stuckEvent->m_goalSpot.z = (float)atof( strtok( NULL, ") (\"" ) );
  611. }
  612. else
  613. {
  614. stuckEvent->m_isGoalValid = false;
  615. }
  616. stuckBot->m_stuckEventVector.AddToTail( stuckEvent );
  617. }
  618. }
  619. }
  620. filesystem->Close( file );
  621. }
  622. else
  623. {
  624. Warning( "Can't open file '%s'\n", args.Arg(1) );
  625. }
  626. //TheTFBots().DrawStuckBotData();
  627. }
  628. //----------------------------------------------------------------------------------------------------------------
  629. //----------------------------------------------------------------------------------------------------------------
  630. CON_COMMAND_F( tf_bot_debug_stuck_log_clear, "Clear currently loaded bot stuck data", FCVAR_GAMEDLL | FCVAR_CHEAT )
  631. {
  632. // Listenserver host or rcon access only!
  633. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  634. return;
  635. TheTFBots().ClearStuckBotData();
  636. }
  637. //----------------------------------------------------------------------------------------------------------------
  638. // for parsing and debugging stuck bot server logs
  639. void CTFBotManager::ClearStuckBotData()
  640. {
  641. m_stuckBotVector.PurgeAndDeleteElements();
  642. }
  643. //----------------------------------------------------------------------------------------------------------------
  644. // for parsing and debugging stuck bot server logs
  645. CStuckBot *CTFBotManager::FindOrCreateStuckBot( int id, const char *playerClass )
  646. {
  647. for( int i=0; i<m_stuckBotVector.Count(); ++i )
  648. {
  649. CStuckBot *stuckBot = m_stuckBotVector[i];
  650. if ( stuckBot->IsMatch( id, playerClass ) )
  651. {
  652. return stuckBot;
  653. }
  654. }
  655. // new instance of a stuck bot
  656. CStuckBot *newStuckBot = new CStuckBot( id, playerClass );
  657. m_stuckBotVector.AddToHead( newStuckBot );
  658. return newStuckBot;
  659. }
  660. //----------------------------------------------------------------------------------------------------------------
  661. void CTFBotManager::DrawStuckBotData( float deltaT )
  662. {
  663. if ( engine->IsDedicatedServer() )
  664. return;
  665. if ( !m_stuckDisplayTimer.IsElapsed() )
  666. return;
  667. m_stuckDisplayTimer.Start( deltaT );
  668. CBasePlayer *player = UTIL_GetListenServerHost();
  669. if ( player == NULL )
  670. return;
  671. // Vector forward;
  672. // AngleVectors( player->EyeAngles(), &forward );
  673. for( int i=0; i<m_stuckBotVector.Count(); ++i )
  674. {
  675. for( int j=0; j<m_stuckBotVector[i]->m_stuckEventVector.Count(); ++j )
  676. {
  677. m_stuckBotVector[i]->m_stuckEventVector[j]->Draw( deltaT );
  678. }
  679. for( int j=0; j<m_stuckBotVector[i]->m_stuckEventVector.Count()-1; ++j )
  680. {
  681. NDebugOverlay::HorzArrow( m_stuckBotVector[i]->m_stuckEventVector[j]->m_stuckSpot,
  682. m_stuckBotVector[i]->m_stuckEventVector[j+1]->m_stuckSpot,
  683. 3, 100, 0, 255, 255, true, deltaT );
  684. }
  685. NDebugOverlay::Text( m_stuckBotVector[i]->m_stuckEventVector[0]->m_stuckSpot, CFmtStr( "%s(#%d)", m_stuckBotVector[i]->m_name, m_stuckBotVector[i]->m_id ), false, deltaT );
  686. }
  687. }