summaryrefslogtreecommitdiff
path: root/game/server/tf/bot/tf_bot_manager.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/tf/bot/tf_bot_manager.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/server/tf/bot/tf_bot_manager.cpp')
-rw-r--r--game/server/tf/bot/tf_bot_manager.cpp872
1 files changed, 872 insertions, 0 deletions
diff --git a/game/server/tf/bot/tf_bot_manager.cpp b/game/server/tf/bot/tf_bot_manager.cpp
new file mode 100644
index 0000000..6ead184
--- /dev/null
+++ b/game/server/tf/bot/tf_bot_manager.cpp
@@ -0,0 +1,872 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//----------------------------------------------------------------------------------------------------------------
+// tf_bot_manager.cpp
+// Team Fortress NextBotManager
+// Tom Bui, February 2010
+//----------------------------------------------------------------------------------------------------------------
+
+#include "cbase.h"
+#include "tf_bot_manager.h"
+
+#include "Player/NextBotPlayer.h"
+#include "team.h"
+#include "tf_bot.h"
+#include "tf_gamerules.h"
+#include "bot/map_entities/tf_bot_hint.h"
+#include "bot/map_entities/tf_bot_hint_sentrygun.h"
+#include "bot/map_entities/tf_bot_hint_teleporter_exit.h"
+
+
+//----------------------------------------------------------------------------------------------------------------
+
+// Creates and sets CTFBotManager as the NextBotManager singleton
+static CTFBotManager sTFBotManager;
+
+extern ConVar tf_bot_force_class;
+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." );
+ConVar tf_bot_quota( "tf_bot_quota", "0", FCVAR_NONE, "Determines the total number of tf bots in the game." );
+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." );
+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." );
+ConVar tf_bot_auto_vacate( "tf_bot_auto_vacate", "1", FCVAR_NONE, "If nonzero, bots will automatically leave to make room for human players." );
+ConVar tf_bot_offline_practice( "tf_bot_offline_practice", "0", FCVAR_NONE, "Tells the server that it is in offline practice mode." );
+ConVar tf_bot_melee_only( "tf_bot_melee_only", "0", FCVAR_GAMEDLL, "If nonzero, TFBots will only use melee weapons" );
+
+extern const char *GetRandomBotName( void );
+extern void CreateBotName( int iTeam, int iClassIndex, CTFBot::DifficultyType skill, char* pBuffer, int iBufferSize );
+
+static bool UTIL_KickBotFromTeam( int kickTeam )
+{
+ int i;
+
+ // try to kick a dead bot first
+ for ( i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
+ CTFBot* pBot = dynamic_cast<CTFBot*>(pPlayer);
+
+ if (pBot == NULL)
+ continue;
+
+ if ( pBot->HasAttribute( CTFBot::QUOTA_MANANGED ) == false )
+ continue;
+
+ if ( ( pPlayer->GetFlags() & FL_FAKECLIENT ) == 0 )
+ continue;
+
+ if ( !pPlayer->IsAlive() && pPlayer->GetTeamNumber() == kickTeam )
+ {
+ // its a bot on the right team - kick it
+ engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", pPlayer->GetUserID() ) );
+
+ return true;
+ }
+ }
+
+ // no dead bots, kick any bot on the given team
+ for ( i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
+ CTFBot* pBot = dynamic_cast<CTFBot*>(pPlayer);
+
+ if (pBot == NULL)
+ continue;
+
+ if ( pBot->HasAttribute( CTFBot::QUOTA_MANANGED ) == false )
+ continue;
+
+ if ( ( pPlayer->GetFlags() & FL_FAKECLIENT ) == 0 )
+ continue;
+
+ if (pPlayer->GetTeamNumber() == kickTeam)
+ {
+ // its a bot on the right team - kick it
+ engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", pPlayer->GetUserID() ) );
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+//----------------------------------------------------------------------------------------------------------------
+
+CTFBotManager::CTFBotManager()
+ : NextBotManager()
+ , m_flNextPeriodicThink( 0 )
+{
+ NextBotManager::SetInstance( this );
+}
+
+
+//----------------------------------------------------------------------------------------------------------------
+CTFBotManager::~CTFBotManager()
+{
+ NextBotManager::SetInstance( NULL );
+}
+
+
+//----------------------------------------------------------------------------------------------------------------
+void CTFBotManager::OnMapLoaded( void )
+{
+ NextBotManager::OnMapLoaded();
+
+ ClearStuckBotData();
+}
+
+
+//----------------------------------------------------------------------------------------------------------------
+void CTFBotManager::OnRoundRestart( void )
+{
+ NextBotManager::OnRoundRestart();
+
+ // clear all hint ownership
+ CTFBotHint *hint = NULL;
+ while( ( hint = (CTFBotHint *)( gEntList.FindEntityByClassname( hint, "func_tfbot_hint" ) ) ) != NULL )
+ {
+ hint->SetOwnerEntity( NULL );
+ }
+
+ CTFBotHintSentrygun *sentryHint = NULL;
+ while( ( sentryHint = (CTFBotHintSentrygun *)( gEntList.FindEntityByClassname( sentryHint, "bot_hint_sentrygun" ) ) ) != NULL )
+ {
+ sentryHint->SetOwnerEntity( NULL );
+ }
+
+ CTFBotHintTeleporterExit *teleporterHint = NULL;
+ while( ( teleporterHint = (CTFBotHintTeleporterExit *)( gEntList.FindEntityByClassname( teleporterHint, "bot_hint_teleporter_exit" ) ) ) != NULL )
+ {
+ teleporterHint->SetOwnerEntity( NULL );
+ }
+
+
+#ifdef TF_CREEP_MODE
+ m_creepExperience[ TF_TEAM_RED ] = 0;
+ m_creepExperience[ TF_TEAM_BLUE ] = 0;
+#endif
+
+ m_isMedeivalBossScenarioSetup = false;
+}
+
+
+//----------------------------------------------------------------------------------------------------------------
+void CTFBotManager::Update()
+{
+ MaintainBotQuota();
+
+ DrawStuckBotData();
+
+#ifdef TF_CREEP_MODE
+ UpdateCreepWaves();
+#endif
+
+ NextBotManager::Update();
+}
+
+
+#ifdef TF_CREEP_MODE
+ConVar tf_creep_initial_delay( "tf_creep_initial_delay", "30" );
+ConVar tf_creep_wave_interval( "tf_creep_wave_interval", "30" );
+ConVar tf_creep_wave_count( "tf_creep_wave_count", "3" );
+ConVar tf_creep_class( "tf_creep_class", "heavyweapons" );
+ConVar tf_creep_level_up( "tf_creep_level_up", "6" );
+
+
+//----------------------------------------------------------------------------------------------------------------
+void CTFBotManager::UpdateCreepWaves()
+{
+ if ( !TFGameRules()->IsCreepWaveMode() )
+ return;
+
+ if ( TFGameRules()->RoundHasBeenWon() )
+ {
+ // no more creep waves - game is over
+ return;
+ }
+
+ if ( TFGameRules()->InSetup() || TFGameRules()->State_Get() == GR_STATE_STARTGAME || TFGameRules()->State_Get() == GR_STATE_PREROUND )
+ {
+ // no creeps at start of round
+ m_creepWaveTimer.Start( tf_creep_initial_delay.GetFloat() );
+
+ // delete all creeps
+ for( int i=1; i<=gpGlobals->maxClients; ++i )
+ {
+ CBasePlayer *player = static_cast< CBasePlayer * >( UTIL_PlayerByIndex( i ) );
+
+ if ( !player )
+ continue;
+
+ if ( FNullEnt( player->edict() ) )
+ continue;
+
+ CTFBot *creep = ToTFBot( player );
+ if ( !creep || !creep->HasAttribute( CTFBot::IS_NPC ) )
+ continue;
+
+ engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", player->GetUserID() ) );
+ }
+
+ return;
+ }
+
+ if ( m_creepWaveTimer.IsElapsed() )
+ {
+ m_creepWaveTimer.Start( tf_creep_wave_interval.GetFloat() );
+
+ SpawnCreepWave( TF_TEAM_RED );
+ SpawnCreepWave( TF_TEAM_BLUE );
+ }
+}
+
+
+//----------------------------------------------------------------------------------------------------------------
+void CTFBotManager::SpawnCreepWave( int team )
+{
+ CTFBotSquad *squad = new CTFBotSquad;
+
+ for( int i=0; i<tf_creep_wave_count.GetInt(); ++i )
+ {
+ SpawnCreep( team, squad );
+ }
+}
+
+
+//----------------------------------------------------------------------------------------------------------------
+void CTFBotManager::SpawnCreep( int team, CTFBotSquad *squad )
+{
+ CTFBot *bot = NextBotCreatePlayerBot< CTFBot >( "Creep" );
+
+ if ( !bot )
+ return;
+
+ bot->SetAttribute( CTFBot::IS_NPC );
+ bot->HandleCommand_JoinTeam( team == TF_TEAM_RED ? "red" : "blue" );
+ bot->SetDifficulty( CTFBot::NORMAL );
+ bot->HandleCommand_JoinClass( tf_creep_class.GetString() );
+ bot->JoinSquad( squad );
+ bot->AddGlowEffect();
+ //BotGenerateAndWearItem( bot, "Honest Halo" );
+}
+
+
+//----------------------------------------------------------------------------------------------------------------
+void CTFBotManager::OnCreepKilled( CTFPlayer *killer )
+{
+ CTFBot *bot = ToTFBot( killer );
+ if ( bot && bot->HasAttribute( CTFBot::IS_NPC ) )
+ return;
+
+ ++m_creepExperience[ killer->GetTeamNumber() ];
+
+/*
+ int xp = m_creepExperience[ killer->GetTeamNumber() ];
+ int level = xp / tf_creep_level_up.GetInt();
+ int left = xp % tf_creep_level_up.GetInt();
+
+ char text[256];
+ Q_snprintf( text, sizeof(text), "%s killed a creep. %s team LVL = %d+%d/%d\n",
+ killer->GetPlayerName(),
+ killer->GetTeamNumber() == TF_TEAM_RED ? "Red" : "Blue",
+ level+1, left, tf_creep_level_up.GetInt() );
+
+ UTIL_ClientPrintAll( HUD_PRINTTALK, text );
+*/
+
+ UTIL_ClientPrintAll( HUD_PRINTTALK, "%s killed a creep" );
+}
+
+#endif // TF_CREEP_MODE
+
+//----------------------------------------------------------------------------------------------------------------
+bool CTFBotManager::RemoveBotFromTeamAndKick( int nTeam )
+{
+ CUtlVector< CTFPlayer* > vecCandidates;
+
+ // Gather potential candidates
+ for ( int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
+
+ if ( pPlayer == NULL )
+ continue;
+
+ if ( FNullEnt( pPlayer->edict() ) )
+ continue;
+
+ if ( !pPlayer->IsConnected() )
+ continue;
+
+ CTFBot* pBot = dynamic_cast<CTFBot*>( pPlayer );
+ if ( pBot && pBot->HasAttribute( CTFBot::QUOTA_MANANGED ) )
+ {
+ if ( pBot->GetTeamNumber() == nTeam )
+ {
+ vecCandidates.AddToTail( pPlayer );
+ }
+ }
+ }
+
+ CTFPlayer *pVictim = NULL;
+ if ( vecCandidates.Count() > 0 )
+ {
+ // first look for bots that are currently dead
+ FOR_EACH_VEC( vecCandidates, i )
+ {
+ CTFPlayer *pPlayer = vecCandidates[i];
+ if ( pPlayer && !pPlayer->IsAlive() )
+ {
+ pVictim = pPlayer;
+ break;
+ }
+ }
+
+ // if we didn't fine one, try to kick anyone on the team
+ if ( !pVictim )
+ {
+ FOR_EACH_VEC( vecCandidates, i )
+ {
+ CTFPlayer *pPlayer = vecCandidates[i];
+ if ( pPlayer )
+ {
+ pVictim = pPlayer;
+ break;
+ }
+ }
+ }
+ }
+
+ if ( pVictim )
+ {
+ if ( pVictim->IsAlive() )
+ {
+ pVictim->CommitSuicide();
+ }
+ pVictim->ForceChangeTeam( TEAM_UNASSIGNED ); // skipping TEAM_SPECTATOR because some servers don't allow spectators
+ UTIL_KickBotFromTeam( TEAM_UNASSIGNED );
+ return true;
+ }
+
+ return false;
+}
+
+//----------------------------------------------------------------------------------------------------------------
+void CTFBotManager::MaintainBotQuota()
+{
+ if ( TheNavMesh->IsGenerating() )
+ return;
+
+ if ( g_fGameOver )
+ return;
+
+ // new players can't spawn immediately after the round has been going for some time
+ if ( !TFGameRules() )
+ return;
+
+ // training mode controls the bots
+ if ( TFGameRules()->IsInTraining() )
+ return;
+
+ // if it is not time to do anything...
+ if ( gpGlobals->curtime < m_flNextPeriodicThink )
+ return;
+
+ // think every quarter second
+ m_flNextPeriodicThink = gpGlobals->curtime + 0.25f;
+
+ // don't add bots until local player has been registered, to make sure he's player ID #1
+ if ( !engine->IsDedicatedServer() )
+ {
+ CBasePlayer *pPlayer = UTIL_GetListenServerHost();
+ if ( !pPlayer )
+ return;
+ }
+
+ // We want to balance based on who's playing on game teams not necessary who's on team spectator, etc.
+ int nConnectedClients = 0;
+ int nTFBots = 0;
+ int nTFBotsOnGameTeams = 0;
+ int nNonTFBotsOnGameTeams = 0;
+ int nSpectators = 0;
+ for ( int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
+
+ if ( pPlayer == NULL )
+ continue;
+
+ if ( FNullEnt( pPlayer->edict() ) )
+ continue;
+
+ if ( !pPlayer->IsConnected() )
+ continue;
+
+ CTFBot* pBot = dynamic_cast<CTFBot*>( pPlayer );
+ if ( pBot && pBot->HasAttribute( CTFBot::QUOTA_MANANGED ) )
+ {
+ nTFBots++;
+ if ( pPlayer->GetTeamNumber() == TF_TEAM_RED || pPlayer->GetTeamNumber() == TF_TEAM_BLUE )
+ {
+ nTFBotsOnGameTeams++;
+ }
+ }
+ else
+ {
+ if ( pPlayer->GetTeamNumber() == TF_TEAM_RED || pPlayer->GetTeamNumber() == TF_TEAM_BLUE )
+ {
+ nNonTFBotsOnGameTeams++;
+ }
+ else if ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR )
+ {
+ nSpectators++;
+ }
+ }
+
+ nConnectedClients++;
+ }
+
+ int desiredBotCount = tf_bot_quota.GetInt();
+ int nTotalNonTFBots = nConnectedClients - nTFBots;
+
+ if ( FStrEq( tf_bot_quota_mode.GetString(), "fill" ) )
+ {
+ desiredBotCount = MAX( 0, desiredBotCount - nNonTFBotsOnGameTeams );
+ }
+ else if ( FStrEq( tf_bot_quota_mode.GetString(), "match" ) )
+ {
+ // If bot_quota_mode is 'match', we want the number of bots to be bot_quota * total humans
+ desiredBotCount = (int)MAX( 0, tf_bot_quota.GetFloat() * nNonTFBotsOnGameTeams );
+ }
+
+ // wait for a player to join, if necessary
+ if ( tf_bot_join_after_player.GetBool() )
+ {
+ if ( ( nNonTFBotsOnGameTeams == 0 ) && ( nSpectators == 0 ) )
+ {
+ desiredBotCount = 0;
+ }
+ }
+
+ // if bots will auto-vacate, we need to keep one slot open to allow players to join
+ if ( tf_bot_auto_vacate.GetBool() )
+ {
+ desiredBotCount = MIN( desiredBotCount, gpGlobals->maxClients - nTotalNonTFBots - 1 );
+ }
+ else
+ {
+ desiredBotCount = MIN( desiredBotCount, gpGlobals->maxClients - nTotalNonTFBots );
+ }
+
+ // add bots if necessary
+ if ( desiredBotCount > nTFBotsOnGameTeams )
+ {
+ // don't try to add a bot if it would unbalance
+ if ( !TFGameRules()->WouldChangeUnbalanceTeams( TF_TEAM_BLUE, TEAM_UNASSIGNED ) ||
+ !TFGameRules()->WouldChangeUnbalanceTeams( TF_TEAM_RED, TEAM_UNASSIGNED ) )
+ {
+ CTFBot *pBot = GetAvailableBotFromPool();
+ if ( pBot == NULL )
+ {
+ pBot = NextBotCreatePlayerBot< CTFBot >( GetRandomBotName() );
+ }
+ if ( pBot )
+ {
+ pBot->SetAttribute( CTFBot::QUOTA_MANANGED );
+
+ // join a team before we pick our class, since we use our teammates to decide what class to be
+ pBot->HandleCommand_JoinTeam( "auto" );
+
+ const char *classname = FStrEq( tf_bot_force_class.GetString(), "" ) ? pBot->GetNextSpawnClassname() : tf_bot_force_class.GetString();
+ pBot->HandleCommand_JoinClass( classname );
+
+ // give the bot a proper name
+ char name[256];
+ CTFBot::DifficultyType skill = pBot->GetDifficulty();
+ CreateBotName( pBot->GetTeamNumber(), pBot->GetPlayerClass()->GetClassIndex(), skill, name, sizeof( name ) );
+ engine->SetFakeClientConVarValue( pBot->edict(), "name", name );
+
+ // Keep track of any bots we add during a match
+ CMatchInfo *pMatchInfo = GTFGCClientSystem()->GetMatch();
+ if ( pMatchInfo )
+ {
+ pMatchInfo->m_nBotsAdded++;
+ }
+ }
+ }
+ }
+ else if ( desiredBotCount < nTFBotsOnGameTeams )
+ {
+ // kick a bot to maintain quota
+
+ // first remove any unassigned bots
+ if ( UTIL_KickBotFromTeam( TEAM_UNASSIGNED ) )
+ return;
+
+ int kickTeam;
+
+ CTeam *pRed = GetGlobalTeam( TF_TEAM_RED );
+ CTeam *pBlue = GetGlobalTeam( TF_TEAM_BLUE );
+
+ // remove from the team that has more players
+ if ( pBlue->GetNumPlayers() > pRed->GetNumPlayers() )
+ {
+ kickTeam = TF_TEAM_BLUE;
+ }
+ else if ( pBlue->GetNumPlayers() < pRed->GetNumPlayers() )
+ {
+ kickTeam = TF_TEAM_RED;
+ }
+ // remove from the team that's winning
+ else if ( pBlue->GetScore() > pRed->GetScore() )
+ {
+ kickTeam = TF_TEAM_BLUE;
+ }
+ else if ( pBlue->GetScore() < pRed->GetScore() )
+ {
+ kickTeam = TF_TEAM_RED;
+ }
+ else
+ {
+ // teams and scores are equal, pick a team at random
+ kickTeam = (RandomInt( 0, 1 ) == 0) ? TF_TEAM_BLUE : TF_TEAM_RED;
+ }
+
+ // attempt to kick a bot from the given team
+ if ( UTIL_KickBotFromTeam( kickTeam ) )
+ return;
+
+ // if there were no bots on the team, kick a bot from the other team
+ UTIL_KickBotFromTeam( kickTeam == TF_TEAM_BLUE ? TF_TEAM_RED : TF_TEAM_BLUE );
+ }
+}
+
+
+//----------------------------------------------------------------------------------------------------------------
+bool CTFBotManager::IsAllBotTeam( int iTeam )
+{
+ CTeam *pTeam = GetGlobalTeam( iTeam );
+ if ( pTeam == NULL )
+ {
+ return false;
+ }
+
+ // check to see if any players on the team are humans
+ for ( int i = 0, n = pTeam->GetNumPlayers(); i < n; ++i )
+ {
+ CTFPlayer *pPlayer = ToTFPlayer( pTeam->GetPlayer( i ) );
+ if ( pPlayer == NULL )
+ {
+ continue;
+ }
+ if ( pPlayer->IsBot() == false )
+ {
+ return false;
+ }
+ }
+
+ // if we made it this far, then they must all be bots!
+ if ( pTeam->GetNumPlayers() != 0 )
+ {
+ return true;
+ }
+
+ // okay, this is a bit trickier...
+ // if there are no people on this team, then we need to check the "assigned" human team
+ return TFGameRules()->GetAssignedHumanTeam() != iTeam;
+}
+
+
+//----------------------------------------------------------------------------------------------------------------
+void CTFBotManager::SetIsInOfflinePractice(bool bIsInOfflinePractice)
+{
+ tf_bot_offline_practice.SetValue( bIsInOfflinePractice ? 1 : 0 );
+}
+
+
+//----------------------------------------------------------------------------------------------------------------
+bool CTFBotManager::IsInOfflinePractice() const
+{
+ return tf_bot_offline_practice.GetInt() != 0;
+}
+
+
+//----------------------------------------------------------------------------------------------------------------
+bool CTFBotManager::IsMeleeOnly() const
+{
+ return tf_bot_melee_only.GetBool();
+}
+
+
+//----------------------------------------------------------------------------------------------------------------
+void CTFBotManager::RevertOfflinePracticeConvars()
+{
+ tf_bot_quota.Revert();
+ tf_bot_quota_mode.Revert();
+ tf_bot_auto_vacate.Revert();
+ tf_bot_difficulty.Revert();
+ tf_bot_offline_practice.Revert();
+}
+
+
+//----------------------------------------------------------------------------------------------------------------
+void CTFBotManager::LevelShutdown()
+{
+ m_flNextPeriodicThink = 0.0f;
+ if ( IsInOfflinePractice() )
+ {
+ RevertOfflinePracticeConvars();
+ SetIsInOfflinePractice( false );
+ }
+}
+
+
+//----------------------------------------------------------------------------------------------------------------
+CTFBot* CTFBotManager::GetAvailableBotFromPool()
+{
+ for ( int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
+ CTFBot* pBot = dynamic_cast<CTFBot*>(pPlayer);
+
+ if (pBot == NULL)
+ continue;
+
+ if ( ( pBot->GetFlags() & FL_FAKECLIENT ) == 0 )
+ continue;
+
+ if ( pBot->GetTeamNumber() == TEAM_SPECTATOR || pBot->GetTeamNumber() == TEAM_UNASSIGNED )
+ {
+ pBot->ClearAttribute( CTFBot::QUOTA_MANANGED );
+ return pBot;
+ }
+ }
+ return NULL;
+}
+
+
+//----------------------------------------------------------------------------------------------------------------
+void CTFBotManager::OnForceAddedBots( int iNumAdded )
+{
+ tf_bot_quota.SetValue( tf_bot_quota.GetInt() + iNumAdded );
+ m_flNextPeriodicThink = gpGlobals->curtime + 1.0f;
+}
+
+
+//----------------------------------------------------------------------------------------------------------------
+void CTFBotManager::OnForceKickedBots( int iNumKicked )
+{
+ tf_bot_quota.SetValue( MAX( tf_bot_quota.GetInt() - iNumKicked, 0 ) );
+ // allow time for the bots to be kicked
+ m_flNextPeriodicThink = gpGlobals->curtime + 2.0f;
+}
+
+
+//----------------------------------------------------------------------------------------------------------------
+CTFBotManager &TheTFBots( void )
+{
+ return static_cast<CTFBotManager&>( TheNextBots() );
+}
+
+
+
+//----------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------
+CON_COMMAND_F( tf_bot_debug_stuck_log, "Given a server logfile, visually display bot stuck locations.", FCVAR_GAMEDLL | FCVAR_CHEAT )
+{
+ // Listenserver host or rcon access only!
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ if ( args.ArgC() < 2 )
+ {
+ DevMsg( "%s <logfilename>\n", args.Arg(0) );
+ return;
+ }
+
+ FileHandle_t file = filesystem->Open( args.Arg(1), "r", "GAME" );
+
+ const int maxBufferSize = 1024;
+ char buffer[ maxBufferSize ];
+
+ char logMapName[ maxBufferSize ];
+ logMapName[0] = '\000';
+
+ TheTFBots().ClearStuckBotData();
+
+ if ( file )
+ {
+ int line = 0;
+ while( !filesystem->EndOfFile( file ) )
+ {
+ filesystem->ReadLine( buffer, maxBufferSize, file );
+ ++line;
+
+ strtok( buffer, ":" );
+ strtok( NULL, ":" );
+ strtok( NULL, ":" );
+ char *first = strtok( NULL, " " );
+
+ if ( !first )
+ continue;
+
+ if ( !strcmp( first, "Loading" ) )
+ {
+ // L 08/08/2012 - 15:10:47: Loading map "mvm_coaltown"
+ strtok( NULL, " " );
+ char *mapname = strtok( NULL, "\"" );
+
+ if ( mapname )
+ {
+ strcpy( logMapName, mapname );
+ Warning( "*** Log file from map '%s'\n", mapname );
+ }
+ }
+ else if ( first[0] == '\"' )
+ {
+ // might be a player ID
+
+ char *playerClassname = &first[1];
+
+ char *nameEnd = playerClassname;
+ while( *nameEnd != '\000' && *nameEnd != '<' )
+ ++nameEnd;
+ *nameEnd = '\000';
+
+ char *botIDString = ++nameEnd;
+ char *IDEnd = botIDString;
+ while( *IDEnd != '\000' && *IDEnd != '>' )
+ ++IDEnd;
+ *IDEnd = '\000';
+
+ int botID = atoi( botIDString );
+
+ char *second = strtok( NULL, " " );
+ if ( second && !strcmp( second, "stuck" ) )
+ {
+ CStuckBot *stuckBot = TheTFBots().FindOrCreateStuckBot( botID, playerClassname );
+
+ CStuckBotEvent *stuckEvent = new CStuckBotEvent;
+
+
+ // 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" )
+ strtok( NULL, " (\"" ); // (position
+
+ stuckEvent->m_stuckSpot.x = (float)atof( strtok( NULL, " )\"" ) );
+ stuckEvent->m_stuckSpot.y = (float)atof( strtok( NULL, " )\"" ) );
+ stuckEvent->m_stuckSpot.z = (float)atof( strtok( NULL, " )\"" ) );
+
+ strtok( NULL, ") (\"" );
+ stuckEvent->m_stuckDuration = (float)atof( strtok( NULL, "\"" ) );
+
+ strtok( NULL, ") (\"-L0123456789/:" ); // path_goal
+
+ char *goal = strtok( NULL, ") (\"" );
+
+ if ( goal && strcmp( goal, "NULL" ) )
+ {
+ stuckEvent->m_isGoalValid = true;
+
+ stuckEvent->m_goalSpot.x = (float)atof( goal );
+ stuckEvent->m_goalSpot.y = (float)atof( strtok( NULL, ") (\"" ) );
+ stuckEvent->m_goalSpot.z = (float)atof( strtok( NULL, ") (\"" ) );
+ }
+ else
+ {
+ stuckEvent->m_isGoalValid = false;
+ }
+
+ stuckBot->m_stuckEventVector.AddToTail( stuckEvent );
+ }
+ }
+ }
+
+ filesystem->Close( file );
+ }
+ else
+ {
+ Warning( "Can't open file '%s'\n", args.Arg(1) );
+ }
+
+ //TheTFBots().DrawStuckBotData();
+}
+
+
+//----------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------
+CON_COMMAND_F( tf_bot_debug_stuck_log_clear, "Clear currently loaded bot stuck data", FCVAR_GAMEDLL | FCVAR_CHEAT )
+{
+ // Listenserver host or rcon access only!
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ TheTFBots().ClearStuckBotData();
+}
+
+
+//----------------------------------------------------------------------------------------------------------------
+// for parsing and debugging stuck bot server logs
+void CTFBotManager::ClearStuckBotData()
+{
+ m_stuckBotVector.PurgeAndDeleteElements();
+}
+
+
+//----------------------------------------------------------------------------------------------------------------
+// for parsing and debugging stuck bot server logs
+CStuckBot *CTFBotManager::FindOrCreateStuckBot( int id, const char *playerClass )
+{
+ for( int i=0; i<m_stuckBotVector.Count(); ++i )
+ {
+ CStuckBot *stuckBot = m_stuckBotVector[i];
+
+ if ( stuckBot->IsMatch( id, playerClass ) )
+ {
+ return stuckBot;
+ }
+ }
+
+ // new instance of a stuck bot
+ CStuckBot *newStuckBot = new CStuckBot( id, playerClass );
+ m_stuckBotVector.AddToHead( newStuckBot );
+
+ return newStuckBot;
+}
+
+
+//----------------------------------------------------------------------------------------------------------------
+void CTFBotManager::DrawStuckBotData( float deltaT )
+{
+ if ( engine->IsDedicatedServer() )
+ return;
+
+ if ( !m_stuckDisplayTimer.IsElapsed() )
+ return;
+
+ m_stuckDisplayTimer.Start( deltaT );
+
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if ( player == NULL )
+ return;
+
+// Vector forward;
+// AngleVectors( player->EyeAngles(), &forward );
+
+ for( int i=0; i<m_stuckBotVector.Count(); ++i )
+ {
+ for( int j=0; j<m_stuckBotVector[i]->m_stuckEventVector.Count(); ++j )
+ {
+ m_stuckBotVector[i]->m_stuckEventVector[j]->Draw( deltaT );
+ }
+
+ for( int j=0; j<m_stuckBotVector[i]->m_stuckEventVector.Count()-1; ++j )
+ {
+ NDebugOverlay::HorzArrow( m_stuckBotVector[i]->m_stuckEventVector[j]->m_stuckSpot,
+ m_stuckBotVector[i]->m_stuckEventVector[j+1]->m_stuckSpot,
+ 3, 100, 0, 255, 255, true, deltaT );
+ }
+
+ 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 );
+ }
+}
+
+