diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/tf/bot/tf_bot_manager.cpp | |
| download | archived-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.cpp | 872 |
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 ); + } +} + + |