summaryrefslogtreecommitdiff
path: root/game/server/cstrike/bot/cs_bot_manager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/cstrike/bot/cs_bot_manager.cpp')
-rw-r--r--game/server/cstrike/bot/cs_bot_manager.cpp2390
1 files changed, 2390 insertions, 0 deletions
diff --git a/game/server/cstrike/bot/cs_bot_manager.cpp b/game/server/cstrike/bot/cs_bot_manager.cpp
new file mode 100644
index 0000000..aec0ef3
--- /dev/null
+++ b/game/server/cstrike/bot/cs_bot_manager.cpp
@@ -0,0 +1,2390 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#pragma warning( disable : 4530 ) // STL uses exceptions, but we are not compiling with them - ignore warning
+
+#include "cbase.h"
+
+#include "cs_bot.h"
+#include "nav_area.h"
+#include "cs_gamerules.h"
+#include "shared_util.h"
+#include "KeyValues.h"
+#include "tier0/icommandline.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#ifdef _WIN32
+#pragma warning (disable:4701) // disable warning that variable *may* not be initialized
+#endif
+
+CBotManager *TheBots = NULL;
+
+bool CCSBotManager::m_isMapDataLoaded = false;
+
+int g_nClientPutInServerOverrides = 0;
+
+
+void DrawOccupyTime( void );
+ConVar bot_show_occupy_time( "bot_show_occupy_time", "0", FCVAR_GAMEDLL | FCVAR_CHEAT, "Show when each nav area can first be reached by each team." );
+
+void DrawBattlefront( void );
+ConVar bot_show_battlefront( "bot_show_battlefront", "0", FCVAR_GAMEDLL | FCVAR_CHEAT, "Show areas where rushing players will initially meet." );
+
+int UTIL_CSSBotsInGame( void );
+
+ConVar bot_join_delay( "bot_join_delay", "0", FCVAR_GAMEDLL, "Prevents bots from joining the server for this many seconds after a map change." );
+
+/**
+ * Determine whether bots can be used or not
+ */
+inline bool AreBotsAllowed()
+{
+ // If they pass in -nobots, don't allow bots. This is for people who host servers, to
+ // allow them to disallow bots to enforce CPU limits.
+ const char *nobots = CommandLine()->CheckParm( "-nobots" );
+ if ( nobots )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void InstallBotControl( void )
+{
+ if ( TheBots != NULL )
+ delete TheBots;
+
+ TheBots = new CCSBotManager;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void RemoveBotControl( void )
+{
+ if ( TheBots != NULL )
+ delete TheBots;
+
+ TheBots = NULL;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+CBasePlayer* ClientPutInServerOverride_Bot( edict_t *pEdict, const char *playername )
+{
+ CBasePlayer *pPlayer = TheBots->AllocateAndBindBotEntity( pEdict );
+ if ( pPlayer )
+ {
+ pPlayer->SetPlayerName( playername );
+ }
+ ++g_nClientPutInServerOverrides;
+
+ return pPlayer;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+// Constructor
+CCSBotManager::CCSBotManager()
+{
+ m_zoneCount = 0;
+ SetLooseBomb( NULL );
+ m_serverActive = false;
+
+ m_isBombPlanted = false;
+ m_bombDefuser = NULL;
+ m_roundStartTimestamp = 0.0f;
+
+ m_eventListenersEnabled = true;
+ m_commonEventListeners.AddToTail( &m_PlayerFootstepEvent );
+ m_commonEventListeners.AddToTail( &m_PlayerRadioEvent );
+ m_commonEventListeners.AddToTail( &m_PlayerFallDamageEvent );
+ m_commonEventListeners.AddToTail( &m_BombBeepEvent );
+ m_commonEventListeners.AddToTail( &m_DoorMovingEvent );
+ m_commonEventListeners.AddToTail( &m_BreakPropEvent );
+ m_commonEventListeners.AddToTail( &m_BreakBreakableEvent );
+ m_commonEventListeners.AddToTail( &m_WeaponFireEvent );
+ m_commonEventListeners.AddToTail( &m_WeaponFireOnEmptyEvent );
+ m_commonEventListeners.AddToTail( &m_WeaponReloadEvent );
+ m_commonEventListeners.AddToTail( &m_WeaponZoomEvent );
+ m_commonEventListeners.AddToTail( &m_BulletImpactEvent );
+ m_commonEventListeners.AddToTail( &m_GrenadeBounceEvent );
+ m_commonEventListeners.AddToTail( &m_NavBlockedEvent );
+
+ TheBotPhrases = new BotPhraseManager;
+ TheBotProfiles = new BotProfileManager;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Invoked when a new round begins
+ */
+void CCSBotManager::RestartRound( void )
+{
+ // extend
+ CBotManager::RestartRound();
+
+ SetLooseBomb( NULL );
+ m_isBombPlanted = false;
+ m_earliestBombPlantTimestamp = gpGlobals->curtime + RandomFloat( 10.0f, 30.0f ); // 60
+ m_bombDefuser = NULL;
+
+ ResetRadioMessageTimestamps();
+
+ m_lastSeenEnemyTimestamp = -9999.9f;
+
+ m_roundStartTimestamp = gpGlobals->curtime + mp_freezetime.GetFloat();
+
+ // randomly decide if defensive team wants to "rush" as a whole
+ const float defenseRushChance = 33.3f; // 25.0f;
+ m_isDefenseRushing = (RandomFloat( 0.0f, 100.0f ) <= defenseRushChance) ? true : false;
+
+ TheBotPhrases->OnRoundRestart();
+
+ m_isRoundOver = false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+
+void UTIL_DrawBox( Extent *extent, int lifetime, int red, int green, int blue )
+{
+ int darkRed = red/2;
+ int darkGreen = green/2;
+ int darkBlue = blue/2;
+
+ Vector v[8];
+ v[0].x = extent->lo.x; v[0].y = extent->lo.y; v[0].z = extent->lo.z;
+ v[1].x = extent->hi.x; v[1].y = extent->lo.y; v[1].z = extent->lo.z;
+ v[2].x = extent->hi.x; v[2].y = extent->hi.y; v[2].z = extent->lo.z;
+ v[3].x = extent->lo.x; v[3].y = extent->hi.y; v[3].z = extent->lo.z;
+ v[4].x = extent->lo.x; v[4].y = extent->lo.y; v[4].z = extent->hi.z;
+ v[5].x = extent->hi.x; v[5].y = extent->lo.y; v[5].z = extent->hi.z;
+ v[6].x = extent->hi.x; v[6].y = extent->hi.y; v[6].z = extent->hi.z;
+ v[7].x = extent->lo.x; v[7].y = extent->hi.y; v[7].z = extent->hi.z;
+
+ static int edge[] =
+ {
+ 1, 2, 3, 4, -1,
+ 5, 6, 7, 8, -5,
+ 1, -5,
+ 2, -6,
+ 3, -7,
+ 4, -8,
+ 0
+ };
+
+ Vector from, to;
+ bool restart = true;
+ for( int i=0; edge[i] != 0; ++i )
+ {
+ if (restart)
+ {
+ to = v[ edge[i]-1 ];
+ restart = false;
+ continue;
+ }
+
+ from = to;
+
+ int index = edge[i];
+ if (index < 0)
+ {
+ restart = true;
+ index = -index;
+ }
+
+ to = v[ index-1 ];
+
+ NDebugOverlay::Line( from, to, darkRed, darkGreen, darkBlue, true, 0.1f );
+ NDebugOverlay::Line( from, to, red, green, blue, false, 0.15f );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::EnableEventListeners( bool enable )
+{
+ if ( m_eventListenersEnabled == enable )
+ {
+ return;
+ }
+
+ m_eventListenersEnabled = enable;
+
+ // enable/disable the most frequent event listeners, to improve performance when no bots are present.
+ for ( int i=0; i<m_commonEventListeners.Count(); ++i )
+ {
+ if ( enable )
+ {
+ gameeventmanager->AddListener( m_commonEventListeners[i], m_commonEventListeners[i]->GetEventName(), true );
+ }
+ else
+ {
+ gameeventmanager->RemoveListener( m_commonEventListeners[i] );
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Called each frame
+ */
+void CCSBotManager::StartFrame( void )
+{
+ if ( !AreBotsAllowed() )
+ {
+ EnableEventListeners( false );
+ return;
+ }
+
+ // EXTEND
+ CBotManager::StartFrame();
+
+ MaintainBotQuota();
+ EnableEventListeners( UTIL_CSSBotsInGame() > 0 );
+
+ // debug zone extent visualization
+ if (cv_bot_debug.GetInt() == 5)
+ {
+ for( int z=0; z<m_zoneCount; ++z )
+ {
+ Zone *zone = &m_zone[z];
+
+ if ( zone->m_isBlocked )
+ {
+ UTIL_DrawBox( &zone->m_extent, 1, 255, 0, 200 );
+ }
+ else
+ {
+ UTIL_DrawBox( &zone->m_extent, 1, 255, 100, 0 );
+ }
+ }
+ }
+
+ if (bot_show_occupy_time.GetBool())
+ {
+ DrawOccupyTime();
+ }
+
+ if (bot_show_battlefront.GetBool())
+ {
+ DrawBattlefront();
+ }
+
+ if ( m_checkTransientAreasTimer.IsElapsed() && !nav_edit.GetBool() )
+ {
+ CUtlVector< CNavArea * >& transientAreas = TheNavMesh->GetTransientAreas();
+ for ( int i=0; i<transientAreas.Count(); ++i )
+ {
+ CNavArea *area = transientAreas[i];
+ if ( area->GetAttributes() & NAV_MESH_TRANSIENT )
+ {
+ area->UpdateBlocked();
+ }
+ }
+
+ m_checkTransientAreasTimer.Start( 2.0f );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if the bot can use this weapon
+ */
+bool CCSBotManager::IsWeaponUseable( const CWeaponCSBase *weapon ) const
+{
+ if (weapon == NULL)
+ return false;
+
+ if (weapon->IsA( WEAPON_C4 ))
+ return true;
+
+ if ((!AllowShotguns() && weapon->IsKindOf( WEAPONTYPE_SHOTGUN )) ||
+ (!AllowMachineGuns() && weapon->IsKindOf( WEAPONTYPE_MACHINEGUN )) ||
+ (!AllowRifles() && weapon->IsKindOf( WEAPONTYPE_RIFLE )) ||
+ (!AllowShotguns() && weapon->IsKindOf( WEAPONTYPE_SHOTGUN )) ||
+ (!AllowSnipers() && weapon->IsKindOf( WEAPONTYPE_SNIPER_RIFLE )) ||
+ (!AllowSubMachineGuns() && weapon->IsKindOf( WEAPONTYPE_SUBMACHINEGUN )) ||
+ (!AllowPistols() && weapon->IsKindOf( WEAPONTYPE_PISTOL )) ||
+ (!AllowGrenades() && weapon->IsKindOf( WEAPONTYPE_GRENADE )))
+ {
+ return false;
+ }
+
+ return true;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if this player is on "defense"
+ */
+bool CCSBotManager::IsOnDefense( const CCSPlayer *player ) const
+{
+ switch (GetScenario())
+ {
+ case SCENARIO_DEFUSE_BOMB:
+ return (player->GetTeamNumber() == TEAM_CT);
+
+ case SCENARIO_RESCUE_HOSTAGES:
+ return (player->GetTeamNumber() == TEAM_TERRORIST);
+
+ case SCENARIO_ESCORT_VIP:
+ return (player->GetTeamNumber() == TEAM_TERRORIST);
+ }
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if this player is on "offense"
+ */
+bool CCSBotManager::IsOnOffense( const CCSPlayer *player ) const
+{
+ return !IsOnDefense( player );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Invoked when a map has just been loaded
+ */
+void CCSBotManager::ServerActivate( void )
+{
+ m_isMapDataLoaded = false;
+
+ // load the database of bot radio chatter
+ TheBotPhrases->Reset();
+ TheBotPhrases->Initialize( "BotChatter.db", 0 );
+
+ TheBotProfiles->Reset();
+ TheBotProfiles->FindVoiceBankIndex( "BotChatter.db" ); // make sure default voice bank is first
+ const char *filename;
+ if ( false ) // g_engfuncs.pfnIsCareerMatch() )
+ {
+ filename = "MissionPacks/BotPackList.db";
+ }
+ else
+ {
+ filename = "BotPackList.db";
+ }
+
+ // read in the list of bot profile DBs
+ FileHandle_t file = filesystem->Open( filename, "r" );
+
+ if ( !file )
+ {
+ TheBotProfiles->Init( "BotProfile.db" );
+ }
+ else
+ {
+ int dataLength = filesystem->Size( filename );
+ char *dataPointer = new char[ dataLength ];
+
+ filesystem->Read( dataPointer, dataLength, file );
+ filesystem->Close( file );
+
+ const char *dataFile = SharedParse( dataPointer );
+ const char *token;
+
+ while ( dataFile )
+ {
+ token = SharedGetToken();
+ char *clone = CloneString( token );
+ TheBotProfiles->Init( clone );
+ delete[] clone;
+ dataFile = SharedParse( dataFile );
+ }
+
+ delete [] dataPointer;
+ }
+
+ // Now that we've parsed all the profiles, we have a list of the voice banks they're using.
+ // Go back and parse the custom voice speakables.
+ const BotProfileManager::VoiceBankList *voiceBanks = TheBotProfiles->GetVoiceBanks();
+ for ( int i=1; i<voiceBanks->Count(); ++i )
+ {
+ TheBotPhrases->Initialize( (*voiceBanks)[i], i );
+ }
+
+ // tell the Navigation Mesh system what CS spawn points are named
+ TheNavMesh->SetPlayerSpawnName( "info_player_terrorist" );
+
+ ExtractScenarioData();
+
+ RestartRound();
+
+ TheBotPhrases->OnMapChange();
+
+ m_serverActive = true;
+}
+
+
+void CCSBotManager::ServerDeactivate( void )
+{
+ m_serverActive = false;
+}
+
+void CCSBotManager::ClientDisconnect( CBaseEntity *entity )
+{
+/*
+ if ( FBitSet( entity->GetFlags(), FL_FAKECLIENT ) )
+ {
+ FREE_PRIVATE( entity );
+ }
+*/
+
+ /*
+ // make sure voice feedback is turned off
+ CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance( pEntity );
+ if ( pPlayer && pPlayer->IsBot() )
+ {
+ CCSBot *pBot = static_cast<CCSBot *>(pPlayer);
+ if (pBot)
+ {
+ pBot->EndVoiceFeedback( true );
+ }
+ }
+ */
+}
+
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+* Parses out bot name/template/etc params from the current ConCommand
+*/
+void BotArgumentsFromArgv( const CCommand &args, const char **name, CSWeaponType *weaponType, BotDifficultyType *difficulty, int *team = NULL, bool *all = NULL )
+{
+ static char s_name[MAX_PLAYER_NAME_LENGTH];
+
+ s_name[0] = 0;
+ *name = s_name;
+ *difficulty = NUM_DIFFICULTY_LEVELS;
+ if ( team )
+ {
+ *team = TEAM_UNASSIGNED;
+ }
+ if ( all )
+ {
+ *all = false;
+ }
+
+ *weaponType = WEAPONTYPE_UNKNOWN;
+
+ for ( int arg=1; arg<args.ArgC(); ++arg )
+ {
+ bool found = false;
+
+ const char *token = args[arg];
+ if ( all && FStrEq( token, "all" ) )
+ {
+ *all = true;
+ found = true;
+ }
+ else if ( team && FStrEq( token, "t" ) )
+ {
+ *team = TEAM_TERRORIST;
+ found = true;
+ }
+ else if ( team && FStrEq( token, "ct" ) )
+ {
+ *team = TEAM_CT;
+ found = true;
+ }
+
+ for( int i=0; i<NUM_DIFFICULTY_LEVELS && !found; ++i )
+ {
+ if (!stricmp( BotDifficultyName[i], token ))
+ {
+ *difficulty = (BotDifficultyType)i;
+ found = true;
+ }
+ }
+
+ if ( !found )
+ {
+ *weaponType = WeaponClassFromString( token );
+ if ( *weaponType != WEAPONTYPE_UNKNOWN )
+ {
+ found = true;
+ }
+ }
+
+ if ( !found )
+ {
+ Q_strncpy( s_name, token, sizeof( s_name ) );
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+CON_COMMAND_F( bot_add, "bot_add <t|ct> <type> <difficulty> <name> - Adds a bot matching the given criteria.", FCVAR_GAMEDLL )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ const char *name;
+ BotDifficultyType difficulty;
+ CSWeaponType weaponType;
+ int team;
+ BotArgumentsFromArgv( args, &name, &weaponType, &difficulty, &team );
+ TheCSBots()->BotAddCommand( team, FROM_CONSOLE, name, weaponType, difficulty );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+CON_COMMAND_F( bot_add_t, "bot_add_t <type> <difficulty> <name> - Adds a terrorist bot matching the given criteria.", FCVAR_GAMEDLL )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ const char *name;
+ BotDifficultyType difficulty;
+ CSWeaponType weaponType;
+ BotArgumentsFromArgv( args, &name, &weaponType, &difficulty );
+ TheCSBots()->BotAddCommand( TEAM_TERRORIST, FROM_CONSOLE, name, weaponType, difficulty );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+CON_COMMAND_F( bot_add_ct, "bot_add_ct <type> <difficulty> <name> - Adds a Counter-Terrorist bot matching the given criteria.", FCVAR_GAMEDLL )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ const char *name;
+ BotDifficultyType difficulty;
+ CSWeaponType weaponType;
+ BotArgumentsFromArgv( args, &name, &weaponType, &difficulty );
+ TheCSBots()->BotAddCommand( TEAM_CT, FROM_CONSOLE, name, weaponType, difficulty );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Collects all bots matching the given criteria (player name, profile template name, difficulty, and team)
+ */
+class CollectBots
+{
+public:
+ CollectBots( const char *name, CSWeaponType weaponType, BotDifficultyType difficulty, int team )
+ {
+ m_name = name;
+ m_difficulty = difficulty;
+ m_team = team;
+ m_weaponType = weaponType;
+ }
+
+ bool operator() ( CBasePlayer *player )
+ {
+ if ( !player->IsBot() )
+ {
+ return true;
+ }
+
+ CCSBot *bot = dynamic_cast< CCSBot * >(player);
+ if ( !bot || !bot->GetProfile() )
+ {
+ return true;
+ }
+
+ if ( m_name && *m_name )
+ {
+ // accept based on name
+ if ( FStrEq( m_name, bot->GetProfile()->GetName() ) )
+ {
+ m_bots.RemoveAll();
+ m_bots.AddToTail( bot );
+ return false;
+ }
+
+ // Reject based on profile template name
+ if ( !bot->GetProfile()->InheritsFrom( m_name ) )
+ {
+ return true;
+ }
+ }
+
+ // reject based on difficulty
+ if ( m_difficulty != NUM_DIFFICULTY_LEVELS )
+ {
+ if ( !bot->GetProfile()->IsDifficulty( m_difficulty ) )
+ {
+ return true;
+ }
+ }
+
+ // reject based on team
+ if ( m_team == TEAM_CT || m_team == TEAM_TERRORIST )
+ {
+ if ( bot->GetTeamNumber() != m_team )
+ {
+ return true;
+ }
+ }
+
+ // reject based on weapon preference
+ if ( m_weaponType != WEAPONTYPE_UNKNOWN )
+ {
+ if ( !bot->GetProfile()->GetWeaponPreferenceCount() )
+ {
+ return true;
+ }
+
+ if ( m_weaponType != WeaponClassFromWeaponID( (CSWeaponID)bot->GetProfile()->GetWeaponPreference( 0 ) ) )
+ {
+ return true;
+ }
+ }
+
+ // A match!
+ m_bots.AddToTail( bot );
+
+ return true;
+ }
+
+ CUtlVector< CCSBot * > m_bots;
+
+private:
+ const char *m_name;
+ CSWeaponType m_weaponType;
+ BotDifficultyType m_difficulty;
+ int m_team;
+};
+
+//--------------------------------------------------------------------------------------------------------------
+CON_COMMAND_F( bot_kill, "bot_kill <all> <t|ct> <type> <difficulty> <name> - Kills a specific bot, or all bots, matching the given criteria.", FCVAR_GAMEDLL )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ const char *name;
+ BotDifficultyType difficulty;
+ CSWeaponType weaponType;
+ int team;
+ bool all;
+
+ BotArgumentsFromArgv( args, &name, &weaponType, &difficulty, &team, &all );
+ if ( (!name || !*name) && team == TEAM_UNASSIGNED && difficulty == NUM_DIFFICULTY_LEVELS )
+ {
+ all = true;
+ }
+
+ CollectBots collector( name, weaponType, difficulty, team );
+ ForEachPlayer( collector );
+
+ for ( int i=0; i<collector.m_bots.Count(); ++i )
+ {
+ CCSBot *bot = collector.m_bots[i];
+ if ( !bot->IsAlive() )
+ continue;
+
+ bot->CommitSuicide();
+
+ if ( !all )
+ {
+ return;
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+CON_COMMAND_F( bot_kick, "bot_kick <all> <t|ct> <type> <difficulty> <name> - Kicks a specific bot, or all bots, matching the given criteria.", FCVAR_GAMEDLL )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ const char *name;
+ BotDifficultyType difficulty;
+ CSWeaponType weaponType;
+ int team;
+ bool all;
+
+ BotArgumentsFromArgv( args, &name, &weaponType, &difficulty, &team, &all );
+ if ( (!name || !*name) && team == TEAM_UNASSIGNED && difficulty == NUM_DIFFICULTY_LEVELS )
+ {
+ all = true;
+ }
+
+ CollectBots collector( name, weaponType, difficulty, team );
+ ForEachPlayer( collector );
+
+ for ( int i=0; i<collector.m_bots.Count(); ++i )
+ {
+ CCSBot *bot = collector.m_bots[i];
+ engine->ServerCommand( UTIL_VarArgs( "kick \"%s\"\n", bot->GetPlayerName() ) );
+ if ( !all )
+ {
+ // adjust bot quota so kicked bot is not immediately added back in
+ int newQuota = cv_bot_quota.GetInt() - 1;
+ cv_bot_quota.SetValue( clamp( newQuota, 0, cv_bot_quota.GetInt() ) );
+ return;
+ }
+ }
+
+ // adjust bot quota so kicked bot is not immediately added back in
+ if ( all && (!name || !*name) && team == TEAM_UNASSIGNED && difficulty == NUM_DIFFICULTY_LEVELS )
+ {
+ cv_bot_quota.SetValue( 0 );
+ }
+ else
+ {
+ int newQuota = cv_bot_quota.GetInt() - collector.m_bots.Count();
+ cv_bot_quota.SetValue( clamp( newQuota, 0, cv_bot_quota.GetInt() ) );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+CON_COMMAND_F( bot_knives_only, "Restricts the bots to only using knives", FCVAR_GAMEDLL )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ cv_bot_allow_pistols.SetValue( 0 );
+ cv_bot_allow_shotguns.SetValue( 0 );
+ cv_bot_allow_sub_machine_guns.SetValue( 0 );
+ cv_bot_allow_rifles.SetValue( 0 );
+ cv_bot_allow_machine_guns.SetValue( 0 );
+ cv_bot_allow_grenades.SetValue( 0 );
+ cv_bot_allow_snipers.SetValue( 0 );
+#ifdef CS_SHIELD_ENABLED
+ cv_bot_allow_shield.SetValue( 0 );
+#endif // CS_SHIELD_ENABLED
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+CON_COMMAND_F( bot_pistols_only, "Restricts the bots to only using pistols", FCVAR_GAMEDLL )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ cv_bot_allow_pistols.SetValue( 1 );
+ cv_bot_allow_shotguns.SetValue( 0 );
+ cv_bot_allow_sub_machine_guns.SetValue( 0 );
+ cv_bot_allow_rifles.SetValue( 0 );
+ cv_bot_allow_machine_guns.SetValue( 0 );
+ cv_bot_allow_grenades.SetValue( 0 );
+ cv_bot_allow_snipers.SetValue( 0 );
+#ifdef CS_SHIELD_ENABLED
+ cv_bot_allow_shield.SetValue( 0 );
+#endif // CS_SHIELD_ENABLED
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+CON_COMMAND_F( bot_snipers_only, "Restricts the bots to only using sniper rifles", FCVAR_GAMEDLL )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ cv_bot_allow_pistols.SetValue( 0 );
+ cv_bot_allow_shotguns.SetValue( 0 );
+ cv_bot_allow_sub_machine_guns.SetValue( 0 );
+ cv_bot_allow_rifles.SetValue( 0 );
+ cv_bot_allow_machine_guns.SetValue( 0 );
+ cv_bot_allow_grenades.SetValue( 0 );
+ cv_bot_allow_snipers.SetValue( 1 );
+#ifdef CS_SHIELD_ENABLED
+ cv_bot_allow_shield.SetValue( 0 );
+#endif // CS_SHIELD_ENABLED
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+CON_COMMAND_F( bot_all_weapons, "Allows the bots to use all weapons", FCVAR_GAMEDLL )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ cv_bot_allow_pistols.SetValue( 1 );
+ cv_bot_allow_shotguns.SetValue( 1 );
+ cv_bot_allow_sub_machine_guns.SetValue( 1 );
+ cv_bot_allow_rifles.SetValue( 1 );
+ cv_bot_allow_machine_guns.SetValue( 1 );
+ cv_bot_allow_grenades.SetValue( 1 );
+ cv_bot_allow_snipers.SetValue( 1 );
+#ifdef CS_SHIELD_ENABLED
+ cv_bot_allow_shield.SetValue( 1 );
+#endif // CS_SHIELD_ENABLED
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+CON_COMMAND_F( bot_goto_mark, "Sends a bot to the selected nav area (useful for testing navigation meshes)", FCVAR_GAMEDLL | FCVAR_CHEAT )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ // tell the first bot we find to go to our marked area
+ CNavArea *area = TheNavMesh->GetMarkedArea();
+ if (area)
+ {
+ for ( int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
+
+ if (player == NULL)
+ continue;
+
+ if (player->IsBot())
+ {
+ CCSBot *bot = dynamic_cast<CCSBot *>( player );
+
+ if ( bot )
+ {
+ bot->MoveTo( area->GetCenter(), FASTEST_ROUTE );
+ }
+
+ break;
+ }
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+#if 0
+CON_COMMAND_F( bot_memory_usage, "Reports on the bots' memory usage", FCVAR_GAMEDLL )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ Msg( "Memory usage:\n" );
+
+ Msg( " %d bytes per bot\n", sizeof(CCSBot) );
+
+ Msg( " %d Navigation Areas @ %d bytes each = %d bytes\n",
+ TheNavMesh->GetNavAreaCount(),
+ sizeof( CNavArea ),
+ TheNavMesh->GetNavAreaCount() * sizeof( CNavArea ) );
+
+ Msg( " %d Hiding Spots @ %d bytes each = %d bytes\n",
+ TheHidingSpotList.Count(),
+ sizeof( HidingSpot ),
+ TheHidingSpotList.Count() * sizeof( HidingSpot ) );
+
+/*
+ unsigned int encounterMem = 0;
+ FOR_EACH_LL( TheNavAreaList, it )
+ {
+ CNavArea *area = TheNavAreaList[ it ];
+
+ FOR_EACH_LL( area->m_spotEncounterList, it )
+ {
+ SpotEncounter *se = area->m_spotEncounterList[ it ];
+
+ encounterMem += sizeof( SpotEncounter );
+ encounterMem += se->spotList.Count() * sizeof( SpotOrder );
+ }
+ }
+
+ Msg( " Encounter Spot data = %d bytes\n", encounterMem );
+*/
+}
+#endif
+
+
+bool CCSBotManager::ServerCommand( const char *cmd )
+{
+ return false;
+}
+
+
+bool CCSBotManager::ClientCommand( CBasePlayer *player, const CCommand &args )
+{
+ return false;
+}
+
+
+/**
+ * Process the "bot_add" console command
+ */
+bool CCSBotManager::BotAddCommand( int team, bool isFromConsole, const char *profileName, CSWeaponType weaponType, BotDifficultyType difficulty )
+{
+ if ( !TheNavMesh->IsLoaded() )
+ {
+ // If there isn't a Navigation Mesh in memory, create one
+ if ( !TheNavMesh->IsGenerating() )
+ {
+ if ( !m_isMapDataLoaded )
+ {
+ TheNavMesh->BeginGeneration();
+ m_isMapDataLoaded = true;
+ }
+ return false;
+ }
+ }
+
+ // dont allow bots to join if the Navigation Mesh is being generated
+ if (TheNavMesh->IsGenerating())
+ return false;
+
+ const BotProfile *profile = NULL;
+
+ if ( !isFromConsole )
+ {
+ profileName = NULL;
+ difficulty = GetDifficultyLevel();
+ }
+ else
+ {
+ if ( difficulty == NUM_DIFFICULTY_LEVELS )
+ {
+ difficulty = GetDifficultyLevel();
+ }
+
+ // if team not specified, check bot_join_team cvar for preference
+ if (team == TEAM_UNASSIGNED)
+ {
+ if (!stricmp( cv_bot_join_team.GetString(), "T" ))
+ team = TEAM_TERRORIST;
+ else if (!stricmp( cv_bot_join_team.GetString(), "CT" ))
+ team = TEAM_CT;
+ else
+ team = CSGameRules()->SelectDefaultTeam();
+ }
+ }
+
+ if ( profileName && *profileName )
+ {
+ // in career, ignore humans, since we want to add anyway
+ bool ignoreHumans = CSGameRules()->IsCareer();
+ if (UTIL_IsNameTaken( profileName, ignoreHumans ))
+ {
+ if ( isFromConsole )
+ {
+ Msg( "Error - %s is already in the game.\n", profileName );
+ }
+ return true;
+ }
+
+ // try to add a bot by name
+ profile = TheBotProfiles->GetProfile( profileName, team );
+ if ( !profile )
+ {
+ // try to add a bot by template
+ profile = TheBotProfiles->GetProfileMatchingTemplate( profileName, team, difficulty );
+ if ( !profile )
+ {
+ if ( isFromConsole )
+ {
+ Msg( "Error - no profile for '%s' exists.\n", profileName );
+ }
+ return true;
+ }
+ }
+ }
+ else
+ {
+ // if team not specified, check bot_join_team cvar for preference
+ if (team == TEAM_UNASSIGNED)
+ {
+ if (!stricmp( cv_bot_join_team.GetString(), "T" ))
+ team = TEAM_TERRORIST;
+ else if (!stricmp( cv_bot_join_team.GetString(), "CT" ))
+ team = TEAM_CT;
+ else
+ team = CSGameRules()->SelectDefaultTeam();
+ }
+
+ profile = TheBotProfiles->GetRandomProfile( difficulty, team, weaponType );
+ if (profile == NULL)
+ {
+ if ( isFromConsole )
+ {
+ Msg( "All bot profiles at this difficulty level are in use.\n" );
+ }
+ return true;
+ }
+ }
+
+ if (team == TEAM_UNASSIGNED || team == TEAM_SPECTATOR)
+ {
+ if ( isFromConsole )
+ {
+ Msg( "Could not add bot to the game: The game is full\n" );
+ }
+ return false;
+ }
+
+ if (CSGameRules()->TeamFull( team ))
+ {
+ if ( isFromConsole )
+ {
+ Msg( "Could not add bot to the game: Team is full\n" );
+ }
+ return false;
+ }
+
+ if (CSGameRules()->TeamStacked( team, TEAM_UNASSIGNED ))
+ {
+ if ( isFromConsole )
+ {
+ Msg( "Could not add bot to the game: Team is stacked (to disable this check, set mp_autoteambalance to zero, increase mp_limitteams, and restart the round).\n" );
+ }
+ return false;
+ }
+
+ // create the actual bot
+ CCSBot *bot = CreateBot<CCSBot>( profile, team );
+
+ if (bot == NULL)
+ {
+ if ( isFromConsole )
+ {
+ Msg( "Error: CreateBot() failed.\n" );
+ }
+ return false;
+ }
+
+ if (isFromConsole)
+ {
+ // increase the bot quota to account for manually added bot
+ cv_bot_quota.SetValue( cv_bot_quota.GetInt() + 1 );
+ }
+
+ return true;
+}
+
+int UTIL_CSSBotsInGame()
+{
+ int count = 0;
+
+ for (int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CCSBot *player = dynamic_cast<CCSBot *>(UTIL_PlayerByIndex( i ));
+
+ if ( player == NULL )
+ continue;
+
+ count++;
+ }
+
+ return count;
+}
+
+bool UTIL_CSSKickBotFromTeam( int kickTeam )
+{
+ int i;
+
+ // try to kick a dead bot first
+ for ( i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CCSBot *player = dynamic_cast<CCSBot *>( UTIL_PlayerByIndex( i ) );
+
+ if (player == NULL)
+ continue;
+
+ if (!player->IsAlive() && player->GetTeamNumber() == kickTeam)
+ {
+ // its a bot on the right team - kick it
+ engine->ServerCommand( UTIL_VarArgs( "kick \"%s\"\n", player->GetPlayerName() ) );
+
+ return true;
+ }
+ }
+
+ // no dead bots, kick any bot on the given team
+ for ( i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CCSBot *player = dynamic_cast<CCSBot *>( UTIL_PlayerByIndex( i ) );
+
+ if (player == NULL)
+ continue;
+
+ if (player->GetTeamNumber() == kickTeam)
+ {
+ // its a bot on the right team - kick it
+ engine->ServerCommand( UTIL_VarArgs( "kick \"%s\"\n", player->GetPlayerName() ) );
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Keep a minimum quota of bots in the game
+ */
+void CCSBotManager::MaintainBotQuota( void )
+{
+ if ( !AreBotsAllowed() )
+ return;
+
+ if (TheNavMesh->IsGenerating())
+ return;
+
+ int totalHumansInGame = UTIL_HumansInGame();
+ int humanPlayersInGame = UTIL_HumansInGame( IGNORE_SPECTATORS );
+
+ // don't add bots until local player has been registered, to make sure he's player ID #1
+ if (!engine->IsDedicatedServer() && totalHumansInGame == 0)
+ return;
+
+ // new players can't spawn immediately after the round has been going for some time
+ if ( !CSGameRules() || !TheCSBots() )
+ {
+ return;
+ }
+
+ int desiredBotCount = cv_bot_quota.GetInt();
+ int botsInGame = UTIL_CSSBotsInGame();
+
+ /// isRoundInProgress is true if the round has progressed far enough that new players will join as dead.
+ bool isRoundInProgress = CSGameRules()->m_bFirstConnected &&
+ !TheCSBots()->IsRoundOver() &&
+ ( CSGameRules()->GetRoundElapsedTime() >= 20.0f );
+
+ if ( FStrEq( cv_bot_quota_mode.GetString(), "fill" ) )
+ {
+ // If bot_quota_mode is 'fill', we want the number of bots and humans together to equal bot_quota
+ // unless the round is already in progress, in which case we play with what we've been dealt
+ if ( !isRoundInProgress )
+ {
+ desiredBotCount = MAX( 0, desiredBotCount - humanPlayersInGame );
+ }
+ else
+ {
+ desiredBotCount = botsInGame;
+ }
+ }
+ else if ( FStrEq( cv_bot_quota_mode.GetString(), "match" ) )
+ {
+ // If bot_quota_mode is 'match', we want the number of bots to be bot_quota * total humans
+ // unless the round is already in progress, in which case we play with what we've been dealt
+ if ( !isRoundInProgress )
+ {
+ desiredBotCount = (int)MAX( 0, cv_bot_quota.GetFloat() * humanPlayersInGame );
+ }
+ else
+ {
+ desiredBotCount = botsInGame;
+ }
+ }
+
+ // wait for a player to join, if necessary
+ if (cv_bot_join_after_player.GetBool())
+ {
+ if (humanPlayersInGame == 0)
+ desiredBotCount = 0;
+ }
+
+ // wait until the map has been loaded for a bit, to allow players to transition across
+ // the transition without missing the pistol round
+ if ( bot_join_delay.GetInt() > CSGameRules()->GetMapElapsedTime() )
+ {
+ desiredBotCount = 0;
+ }
+
+ // if bots will auto-vacate, we need to keep one slot open to allow players to join
+ if (cv_bot_auto_vacate.GetBool())
+ desiredBotCount = MIN( desiredBotCount, gpGlobals->maxClients - (humanPlayersInGame + 1) );
+ else
+ desiredBotCount = MIN( desiredBotCount, gpGlobals->maxClients - humanPlayersInGame );
+
+ // Try to balance teams, if we are in the first 20 seconds of a round and bots can join either team.
+ if ( botsInGame > 0 && desiredBotCount == botsInGame && CSGameRules()->m_bFirstConnected )
+ {
+ if ( CSGameRules()->GetRoundElapsedTime() < 20.0f ) // new bots can still spawn during this time
+ {
+ if ( mp_autoteambalance.GetBool() )
+ {
+ int numAliveTerrorist;
+ int numAliveCT;
+ int numDeadTerrorist;
+ int numDeadCT;
+ CSGameRules()->InitializePlayerCounts( numAliveTerrorist, numAliveCT, numDeadTerrorist, numDeadCT );
+
+ if ( !FStrEq( cv_bot_join_team.GetString(), "T" ) &&
+ !FStrEq( cv_bot_join_team.GetString(), "CT" ) )
+ {
+ if ( numAliveTerrorist > CSGameRules()->m_iNumCT + 1 )
+ {
+ if ( UTIL_KickBotFromTeam( TEAM_TERRORIST ) )
+ return;
+ }
+ else if ( numAliveCT > CSGameRules()->m_iNumTerrorist + 1 )
+ {
+ if ( UTIL_KickBotFromTeam( TEAM_CT ) )
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ // add bots if necessary
+ if (desiredBotCount > botsInGame)
+ {
+ // don't try to add a bot if all teams are full
+ if (!CSGameRules()->TeamFull( TEAM_TERRORIST ) || !CSGameRules()->TeamFull( TEAM_CT ))
+ TheCSBots()->BotAddCommand( TEAM_UNASSIGNED );
+ }
+ else if (desiredBotCount < botsInGame)
+ {
+ // kick a bot to maintain quota
+
+ // first remove any unassigned bots
+ if (UTIL_CSSKickBotFromTeam( TEAM_UNASSIGNED ))
+ return;
+
+ int kickTeam;
+
+ // remove from the team that has more players
+ if (CSGameRules()->m_iNumTerrorist > CSGameRules()->m_iNumCT)
+ {
+ kickTeam = TEAM_TERRORIST;
+ }
+ else if (CSGameRules()->m_iNumTerrorist < CSGameRules()->m_iNumCT)
+ {
+ kickTeam = TEAM_CT;
+ }
+
+ // remove from the team that's winning
+ else if (CSGameRules()->m_iNumTerroristWins > CSGameRules()->m_iNumCTWins)
+ {
+ kickTeam = TEAM_TERRORIST;
+ }
+ else if (CSGameRules()->m_iNumCTWins > CSGameRules()->m_iNumTerroristWins)
+ {
+ kickTeam = TEAM_CT;
+ }
+ else
+ {
+ // teams and scores are equal, pick a team at random
+ kickTeam = (RandomInt( 0, 1 ) == 0) ? TEAM_CT : TEAM_TERRORIST;
+ }
+
+ // attempt to kick a bot from the given team
+ if (UTIL_CSSKickBotFromTeam( kickTeam ))
+ return;
+
+ // if there were no bots on the team, kick a bot from the other team
+ if (kickTeam == TEAM_TERRORIST)
+ UTIL_CSSKickBotFromTeam( TEAM_CT );
+ else
+ UTIL_CSSKickBotFromTeam( TEAM_TERRORIST );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Collect all nav areas that overlap the given zone
+ */
+class CollectOverlappingAreas
+{
+public:
+ CollectOverlappingAreas( CCSBotManager::Zone *zone )
+ {
+ m_zone = zone;
+
+ zone->m_areaCount = 0;
+ }
+
+ bool operator() ( CNavArea *area )
+ {
+ Extent areaExtent;
+ area->GetExtent(&areaExtent);
+
+ if (areaExtent.hi.x >= m_zone->m_extent.lo.x && areaExtent.lo.x <= m_zone->m_extent.hi.x &&
+ areaExtent.hi.y >= m_zone->m_extent.lo.y && areaExtent.lo.y <= m_zone->m_extent.hi.y &&
+ areaExtent.hi.z >= m_zone->m_extent.lo.z && areaExtent.lo.z <= m_zone->m_extent.hi.z)
+ {
+ // area overlaps m_zone
+ m_zone->m_area[ m_zone->m_areaCount++ ] = area;
+ if (m_zone->m_areaCount == CCSBotManager::MAX_ZONE_NAV_AREAS)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+private:
+ CCSBotManager::Zone *m_zone;
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Search the map entities to determine the game scenario and define important zones.
+ */
+void CCSBotManager::ExtractScenarioData( void )
+{
+ if (!TheNavMesh->IsLoaded())
+ return;
+
+ m_zoneCount = 0;
+ m_gameScenario = SCENARIO_DEATHMATCH;
+
+
+ //
+ // Search all entities in the map and set the game type and
+ // store all zones (bomb target, etc).
+ //
+ CBaseEntity *entity;
+ int i;
+ for( i=1; i<gpGlobals->maxEntities; ++i )
+ {
+ entity = CBaseEntity::Instance( engine->PEntityOfEntIndex( i ) );
+
+ if (entity == NULL)
+ continue;
+
+ bool found = false;
+ bool isLegacy = false;
+
+ if (FClassnameIs( entity, "func_bomb_target" ))
+ {
+ m_gameScenario = SCENARIO_DEFUSE_BOMB;
+ found = true;
+ isLegacy = false;
+ }
+ else if (FClassnameIs( entity, "info_bomb_target" ))
+ {
+ m_gameScenario = SCENARIO_DEFUSE_BOMB;
+ found = true;
+ isLegacy = true;
+ }
+ else if (FClassnameIs( entity, "func_hostage_rescue" ))
+ {
+ m_gameScenario = SCENARIO_RESCUE_HOSTAGES;
+ found = true;
+ isLegacy = false;
+ }
+ else if (FClassnameIs( entity, "info_hostage_rescue" ))
+ {
+ m_gameScenario = SCENARIO_RESCUE_HOSTAGES;
+ found = true;
+ isLegacy = true;
+ }
+ else if (FClassnameIs( entity, "hostage_entity" ))
+ {
+ // some very old maps (ie: cs_assault) use info_player_start
+ // as rescue zones, so set the scenario if there are hostages
+ // in the map
+ m_gameScenario = SCENARIO_RESCUE_HOSTAGES;
+ }
+ else if (FClassnameIs( entity, "func_vip_safetyzone" ))
+ {
+ m_gameScenario = SCENARIO_ESCORT_VIP;
+ found = true;
+ isLegacy = false;
+ }
+
+ if (found)
+ {
+ if (m_zoneCount < MAX_ZONES)
+ {
+ Vector absmin, absmax;
+ entity->CollisionProp()->WorldSpaceAABB( &absmin, &absmax );
+
+ m_zone[ m_zoneCount ].m_isBlocked = false;
+ m_zone[ m_zoneCount ].m_center = (isLegacy) ? entity->GetAbsOrigin() : (absmin + absmax)/2.0f;
+ m_zone[ m_zoneCount ].m_isLegacy = isLegacy;
+ m_zone[ m_zoneCount ].m_index = m_zoneCount;
+ m_zone[ m_zoneCount++ ].m_entity = entity;
+ }
+ else
+ Msg( "Warning: Too many zones, some will be ignored.\n" );
+ }
+ }
+
+ //
+ // If there are no zones and the scenario is hostage rescue,
+ // use the info_player_start entities as rescue zones.
+ //
+ if (m_zoneCount == 0 && m_gameScenario == SCENARIO_RESCUE_HOSTAGES)
+ {
+ for( entity = gEntList.FindEntityByClassname( NULL, "info_player_start" );
+ entity && !FNullEnt( entity->edict() );
+ entity = gEntList.FindEntityByClassname( entity, "info_player_start" ) )
+ {
+ if (m_zoneCount < MAX_ZONES)
+ {
+ m_zone[ m_zoneCount ].m_isBlocked = false;
+ m_zone[ m_zoneCount ].m_center = entity->GetAbsOrigin();
+ m_zone[ m_zoneCount ].m_isLegacy = true;
+ m_zone[ m_zoneCount ].m_index = m_zoneCount;
+ m_zone[ m_zoneCount++ ].m_entity = entity;
+ }
+ else
+ {
+ Msg( "Warning: Too many zones, some will be ignored.\n" );
+ }
+ }
+ }
+
+ //
+ // Collect nav areas that overlap each zone
+ //
+ for( i=0; i<m_zoneCount; ++i )
+ {
+ Zone *zone = &m_zone[i];
+
+ if (zone->m_isLegacy)
+ {
+ const float legacyRange = 256.0f;
+ zone->m_extent.lo.x = zone->m_center.x - legacyRange;
+ zone->m_extent.lo.y = zone->m_center.y - legacyRange;
+ zone->m_extent.lo.z = zone->m_center.z - legacyRange;
+ zone->m_extent.hi.x = zone->m_center.x + legacyRange;
+ zone->m_extent.hi.y = zone->m_center.y + legacyRange;
+ zone->m_extent.hi.z = zone->m_center.z + legacyRange;
+ }
+ else
+ {
+ Vector absmin, absmax;
+ zone->m_entity->CollisionProp()->WorldSpaceAABB( &absmin, &absmax );
+
+ zone->m_extent.lo = absmin;
+ zone->m_extent.hi = absmax;
+ }
+
+ // ensure Z overlap
+ const float zFudge = 50.0f;
+ zone->m_extent.lo.z -= zFudge;
+ zone->m_extent.hi.z += zFudge;
+
+ // build a list of nav areas that overlap this zone
+ CollectOverlappingAreas collector( zone );
+ TheNavMesh->ForAllAreas( collector );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the zone that contains the given position
+ */
+const CCSBotManager::Zone *CCSBotManager::GetZone( const Vector &pos ) const
+{
+ for( int z=0; z<m_zoneCount; ++z )
+ {
+ if (m_zone[z].m_extent.Contains( pos ))
+ {
+ return &m_zone[z];
+ }
+ }
+
+ return NULL;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the closest zone to the given position
+ */
+const CCSBotManager::Zone *CCSBotManager::GetClosestZone( const Vector &pos ) const
+{
+ const Zone *close = NULL;
+ float closeRangeSq = 999999999.9f;
+
+ for( int z=0; z<m_zoneCount; ++z )
+ {
+ if ( m_zone[z].m_isBlocked )
+ continue;
+
+ float rangeSq = (m_zone[z].m_center - pos).LengthSqr();
+
+ if (rangeSq < closeRangeSq)
+ {
+ closeRangeSq = rangeSq;
+ close = &m_zone[z];
+ }
+ }
+
+ return close;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return a random position inside the given zone
+ */
+const Vector *CCSBotManager::GetRandomPositionInZone( const Zone *zone ) const
+{
+ static Vector pos;
+
+ if (zone == NULL)
+ return NULL;
+
+ if (zone->m_areaCount == 0)
+ return NULL;
+
+ // pick a random overlapping area
+ CNavArea *area = GetRandomAreaInZone(zone);
+
+ // pick a location inside both the nav area and the zone
+ /// @todo Randomize this
+
+ if (zone->m_isLegacy)
+ {
+ /// @todo It is possible that the radius might not overlap this area at all...
+ area->GetClosestPointOnArea( zone->m_center, &pos );
+ }
+ else
+ {
+ Extent areaExtent;
+ area->GetExtent(&areaExtent);
+ Extent overlap;
+ overlap.lo.x = MAX( areaExtent.lo.x, zone->m_extent.lo.x );
+ overlap.lo.y = MAX( areaExtent.lo.y, zone->m_extent.lo.y );
+ overlap.hi.x = MIN( areaExtent.hi.x, zone->m_extent.hi.x );
+ overlap.hi.y = MIN( areaExtent.hi.y, zone->m_extent.hi.y );
+
+ pos.x = (overlap.lo.x + overlap.hi.x)/2.0f;
+ pos.y = (overlap.lo.y + overlap.hi.y)/2.0f;
+ pos.z = area->GetZ( pos );
+ }
+
+ return &pos;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return a random area inside the given zone
+ */
+CNavArea *CCSBotManager::GetRandomAreaInZone( const Zone *zone ) const
+{
+ int areaCount = zone->m_areaCount;
+ if( areaCount == 0 )
+ {
+ assert( false && "CCSBotManager::GetRandomAreaInZone: No areas for this zone" );
+ return NULL;
+ }
+
+ // Random, but weighted. Jump areas score zero, since you aren't ever meant to stop on one of those.
+ // Avoid areas score 1 to a normal area's 20 because pathfinding treats Avoid as a 20x penalty.
+ int totalWeight = 0;
+ for( int areaIndex = 0; areaIndex < areaCount; areaIndex++ )
+ {
+ CNavArea *currentArea = zone->m_area[areaIndex];
+ if( currentArea->GetAttributes() & NAV_MESH_JUMP )
+ totalWeight += 0;
+ else if( currentArea->GetAttributes() & NAV_MESH_AVOID )
+ totalWeight += 1;
+ else
+ totalWeight += 20;
+ }
+
+ if( totalWeight == 0 )
+ {
+ assert( false && "CCSBotManager::GetRandomAreaInZone: No real areas for this zone" );
+ return NULL;
+ }
+
+ int randomPick = RandomInt( 1, totalWeight );
+
+ for( int areaIndex = 0; areaIndex < areaCount; areaIndex++ )
+ {
+ CNavArea *currentArea = zone->m_area[areaIndex];
+ if( currentArea->GetAttributes() & NAV_MESH_JUMP )
+ randomPick -= 0;
+ else if( currentArea->GetAttributes() & NAV_MESH_AVOID )
+ randomPick -= 1;
+ else
+ randomPick -= 20;
+
+ if( randomPick <= 0 )
+ return currentArea;
+ }
+
+ // Won't ever get here, but the compiler will cry without it.
+ return zone->m_area[0];
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnServerShutdown( IGameEvent *event )
+{
+ if ( !engine->IsDedicatedServer() )
+ {
+ // Since we're a listenserver, save some config info for the next time we start up
+ static const char *botVars[] =
+ {
+ "bot_quota",
+ "bot_difficulty",
+ "bot_chatter",
+ "bot_prefix",
+ "bot_join_team",
+ "bot_defer_to_human",
+#ifdef CS_SHIELD_ENABLED
+ "bot_allow_shield",
+#endif // CS_SHIELD_ENABLED
+ "bot_join_after_player",
+ "bot_allow_rogues",
+ "bot_allow_pistols",
+ "bot_allow_shotguns",
+ "bot_allow_sub_machine_guns",
+ "bot_allow_machine_guns",
+ "bot_allow_rifles",
+ "bot_allow_snipers",
+ "bot_allow_grenades"
+ };
+
+ KeyValues *data = new KeyValues( "ServerConfig" );
+
+ // load the config data
+ if (data)
+ {
+ data->LoadFromFile( filesystem, "ServerConfig.vdf", "GAME" );
+ for ( int i=0; i<sizeof(botVars)/sizeof(botVars[0]); ++i )
+ {
+ const char *varName = botVars[i];
+ if ( varName )
+ {
+ ConVar *var = cvar->FindVar( varName );
+ if ( var )
+ {
+ data->SetString( varName, var->GetString() );
+ }
+ }
+ }
+ data->SaveToFile( filesystem, "ServerConfig.vdf", "GAME" );
+ data->deleteThis();
+ }
+ return;
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnPlayerFootstep( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnPlayerFootstep, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnPlayerRadio( IGameEvent *event )
+{
+ // if it's an Enemy Spotted radio, update our enemy spotted timestamp
+ if ( event->GetInt( "slot" ) == RADIO_ENEMY_SPOTTED )
+ {
+ // to have some idea of when a human Player has seen an enemy
+ SetLastSeenEnemyTimestamp();
+ }
+
+ CCSBOTMANAGER_ITERATE_BOTS( OnPlayerRadio, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnPlayerDeath( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnPlayerDeath, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnPlayerFallDamage( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnPlayerFallDamage, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnBombPickedUp( IGameEvent *event )
+{
+ // bomb no longer loose
+ SetLooseBomb( NULL );
+
+ CCSBOTMANAGER_ITERATE_BOTS( OnBombPickedUp, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnBombPlanted( IGameEvent *event )
+{
+ m_isBombPlanted = true;
+ m_bombPlantTimestamp = gpGlobals->curtime;
+
+ CCSBOTMANAGER_ITERATE_BOTS( OnBombPlanted, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnBombBeep( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnBombBeep, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnBombDefuseBegin( IGameEvent *event )
+{
+ m_bombDefuser = static_cast<CCSPlayer *>( UTIL_PlayerByUserId( event->GetInt( "userid" ) ) );
+
+ CCSBOTMANAGER_ITERATE_BOTS( OnBombDefuseBegin, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnBombDefused( IGameEvent *event )
+{
+ m_isBombPlanted = false;
+ m_bombDefuser = NULL;
+
+ CCSBOTMANAGER_ITERATE_BOTS( OnBombDefused, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnBombDefuseAbort( IGameEvent *event )
+{
+ m_bombDefuser = NULL;
+
+ CCSBOTMANAGER_ITERATE_BOTS( OnBombDefuseAbort, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnBombExploded( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnBombExploded, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnRoundEnd( IGameEvent *event )
+{
+ m_isRoundOver = true;
+
+ CCSBOTMANAGER_ITERATE_BOTS( OnRoundEnd, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnRoundStart( IGameEvent *event )
+{
+ RestartRound();
+
+ CCSBOTMANAGER_ITERATE_BOTS( OnRoundStart, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+static CBaseEntity * SelectSpawnSpot( const char *pEntClassName )
+{
+ CBaseEntity* pSpot = NULL;
+
+ // Find the next spawn spot.
+ pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName );
+
+ if ( pSpot == NULL ) // skip over the null point
+ pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName );
+
+ CBaseEntity *pFirstSpot = pSpot;
+ do
+ {
+ if ( pSpot )
+ {
+ // check if pSpot is valid
+ if ( pSpot->GetAbsOrigin() == Vector( 0, 0, 0 ) )
+ {
+ pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName );
+ continue;
+ }
+
+ // if so, go to pSpot
+ return pSpot;
+ }
+ // increment pSpot
+ pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName );
+ } while ( pSpot != pFirstSpot ); // loop if we're not back to the start
+
+ return NULL;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Pathfind from each zone to a spawn point to ensure it is valid. Assumes that every spawn can pathfind to
+ * every other spawn.
+ */
+void CCSBotManager::CheckForBlockedZones( void )
+{
+ CBaseEntity *pSpot = SelectSpawnSpot( "info_player_counterterrorist" );
+ if ( !pSpot )
+ pSpot = SelectSpawnSpot( "info_player_terrorist" );
+
+ if ( !pSpot )
+ return;
+
+ Vector spawnPos = pSpot->GetAbsOrigin();
+ CNavArea *spawnArea = TheNavMesh->GetNearestNavArea( spawnPos );
+ if ( !spawnArea )
+ return;
+
+ ShortestPathCost costFunc;
+
+ for( int i=0; i<m_zoneCount; ++i )
+ {
+ if (m_zone[i].m_areaCount == 0)
+ continue;
+
+ // just use the first overlapping nav area as a reasonable approximation
+ float dist = NavAreaTravelDistance( spawnArea, m_zone[i].m_area[0], costFunc );
+ m_zone[i].m_isBlocked = (dist < 0.0f );
+
+ if ( cv_bot_debug.GetInt() == 5 )
+ {
+ if ( m_zone[i].m_isBlocked )
+ DevMsg( "%.1f: Zone %d, area %d (%.0f %.0f %.0f) is blocked from spawn area %d (%.0f %.0f %.0f)\n",
+ gpGlobals->curtime, i, m_zone[i].m_area[0]->GetID(),
+ m_zone[i].m_area[0]->GetCenter().x, m_zone[i].m_area[0]->GetCenter().y, m_zone[i].m_area[0]->GetCenter().z,
+ spawnArea->GetID(),
+ spawnPos.x, spawnPos.y, spawnPos.z );
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnRoundFreezeEnd( IGameEvent *event )
+{
+ bool reenableEvents = m_NavBlockedEvent.IsEnabled();
+
+ m_NavBlockedEvent.Enable( false ); // don't listen to nav_blocked events - there could be several, and we don't have bots pathing
+ CUtlVector< CNavArea * >& transientAreas = TheNavMesh->GetTransientAreas();
+ for ( int i=0; i<transientAreas.Count(); ++i )
+ {
+ CNavArea *area = transientAreas[i];
+ if ( area->GetAttributes() & NAV_MESH_TRANSIENT )
+ {
+ area->UpdateBlocked();
+ }
+ }
+ if ( reenableEvents )
+ {
+ m_NavBlockedEvent.Enable( true );
+ }
+
+ CheckForBlockedZones();
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnNavBlocked( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnNavBlocked, event );
+ CheckForBlockedZones();
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnDoorMoving( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnDoorMoving, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Check all nav areas inside the breakable's extent to see if players would now fall through
+ */
+class CheckAreasOverlappingBreakable
+{
+public:
+ CheckAreasOverlappingBreakable( CBaseEntity *breakable )
+ {
+ m_breakable = breakable;
+ ICollideable *collideable = breakable->GetCollideable();
+ collideable->WorldSpaceSurroundingBounds( &m_breakableExtent.lo, &m_breakableExtent.hi );
+
+ const float expand = 10.0f;
+ m_breakableExtent.lo += Vector( -expand, -expand, -expand );
+ m_breakableExtent.hi += Vector( expand, expand, expand );
+ }
+
+ bool operator() ( CNavArea *area )
+ {
+ Extent areaExtent;
+ area->GetExtent(&areaExtent);
+
+ if (areaExtent.hi.x >= m_breakableExtent.lo.x && areaExtent.lo.x <= m_breakableExtent.hi.x &&
+ areaExtent.hi.y >= m_breakableExtent.lo.y && areaExtent.lo.y <= m_breakableExtent.hi.y &&
+ areaExtent.hi.z >= m_breakableExtent.lo.z && areaExtent.lo.z <= m_breakableExtent.hi.z)
+ {
+ // area overlaps the breakable
+ area->CheckFloor( m_breakable );
+ }
+
+ return true;
+ }
+
+private:
+ Extent m_breakableExtent;
+ CBaseEntity *m_breakable;
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnBreakBreakable( IGameEvent *event )
+{
+ CheckAreasOverlappingBreakable collector( UTIL_EntityByIndex( event->GetInt( "entindex" ) ) );
+ TheNavMesh->ForAllAreas( collector );
+
+ CCSBOTMANAGER_ITERATE_BOTS( OnBreakBreakable, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnBreakProp( IGameEvent *event )
+{
+ CheckAreasOverlappingBreakable collector( UTIL_EntityByIndex( event->GetInt( "entindex" ) ) );
+ TheNavMesh->ForAllAreas( collector );
+
+ CCSBOTMANAGER_ITERATE_BOTS( OnBreakProp, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnHostageFollows( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnHostageFollows, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnHostageRescuedAll( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnHostageRescuedAll, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnWeaponFire( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnWeaponFire, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnWeaponFireOnEmpty( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnWeaponFireOnEmpty, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnWeaponReload( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnWeaponReload, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnWeaponZoom( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnWeaponZoom, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnBulletImpact( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnBulletImpact, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnHEGrenadeDetonate( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnHEGrenadeDetonate, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnFlashbangDetonate( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnFlashbangDetonate, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnSmokeGrenadeDetonate( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnSmokeGrenadeDetonate, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnGrenadeBounce( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnGrenadeBounce, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Get the time remaining before the planted bomb explodes
+ */
+float CCSBotManager::GetBombTimeLeft( void ) const
+{
+ return (mp_c4timer.GetFloat() - (gpGlobals->curtime - m_bombPlantTimestamp));
+}
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::SetLooseBomb( CBaseEntity *bomb )
+{
+ m_looseBomb = bomb;
+
+ if (bomb)
+ {
+ m_looseBombArea = TheNavMesh->GetNearestNavArea( bomb->GetAbsOrigin() );
+ }
+ else
+ {
+ m_looseBombArea = NULL;
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if player is important to scenario (VIP, bomb carrier, etc)
+ */
+bool CCSBotManager::IsImportantPlayer( CCSPlayer *player ) const
+{
+ switch (GetScenario())
+ {
+ case SCENARIO_DEFUSE_BOMB:
+ {
+ if (player->GetTeamNumber() == TEAM_TERRORIST && player->HasC4())
+ return true;
+
+ /// @todo TEAM_CT's defusing the bomb are important
+
+ return false;
+ }
+
+ case SCENARIO_ESCORT_VIP:
+ {
+ if (player->GetTeamNumber() == TEAM_CT && player->IsVIP())
+ return true;
+
+ return false;
+ }
+
+ case SCENARIO_RESCUE_HOSTAGES:
+ {
+ /// @todo TEAM_CT's escorting hostages are important
+ return false;
+ }
+ }
+
+ // everyone is equally important in a deathmatch
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return priority of player (0 = max pri)
+ */
+unsigned int CCSBotManager::GetPlayerPriority( CBasePlayer *player ) const
+{
+ const unsigned int lowestPriority = 0xFFFFFFFF;
+
+ if (!player->IsPlayer())
+ return lowestPriority;
+
+ // human players have highest priority
+ if (!player->IsBot())
+ return 0;
+
+ CCSBot *bot = dynamic_cast<CCSBot *>( player );
+
+ if ( !bot )
+ return 0;
+
+ // bots doing something important for the current scenario have high priority
+ switch (GetScenario())
+ {
+ case SCENARIO_DEFUSE_BOMB:
+ {
+ // the bomb carrier has high priority
+ if (bot->GetTeamNumber() == TEAM_TERRORIST && bot->HasC4())
+ return 1;
+
+ break;
+ }
+
+ case SCENARIO_ESCORT_VIP:
+ {
+ // the VIP has high priority
+ if (bot->GetTeamNumber() == TEAM_CT && bot->m_bIsVIP)
+ return 1;
+
+ break;
+ }
+
+ case SCENARIO_RESCUE_HOSTAGES:
+ {
+ // TEAM_CT's rescuing hostages have high priority
+ if (bot->GetTeamNumber() == TEAM_CT && bot->GetHostageEscortCount())
+ return 1;
+
+ break;
+ }
+ }
+
+ // everyone else is ranked by their unique ID (which cannot be zero)
+ return 1 + bot->GetID();
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Returns a random spawn point for the given team (no arg means use both team spawnpoints)
+ */
+CBaseEntity *CCSBotManager::GetRandomSpawn( int team ) const
+{
+ CUtlVector< CBaseEntity * > spawnSet;
+ CBaseEntity *spot;
+
+ if (team == TEAM_TERRORIST || team == TEAM_MAXCOUNT)
+ {
+ // collect T spawns
+ for( spot = gEntList.FindEntityByClassname( NULL, "info_player_terrorist" );
+ spot;
+ spot = gEntList.FindEntityByClassname( spot, "info_player_terrorist" ) )
+ {
+ spawnSet.AddToTail( spot );
+ }
+ }
+
+ if (team == TEAM_CT || team == TEAM_MAXCOUNT)
+ {
+ // collect CT spawns
+ for( spot = gEntList.FindEntityByClassname( NULL, "info_player_counterterrorist" );
+ spot;
+ spot = gEntList.FindEntityByClassname( spot, "info_player_counterterrorist" ) )
+ {
+ spawnSet.AddToTail( spot );
+ }
+ }
+
+ if (spawnSet.Count() == 0)
+ {
+ return NULL;
+ }
+
+ // select one at random
+ int which = RandomInt( 0, spawnSet.Count()-1 );
+ return spawnSet[ which ];
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the last time the given radio message was sent for given team
+ * 'teamID' can be TEAM_CT or TEAM_TERRORIST
+ */
+float CCSBotManager::GetRadioMessageTimestamp( RadioType event, int teamID ) const
+{
+ int i = (teamID == TEAM_TERRORIST) ? 0 : 1;
+
+ if (event > RADIO_START_1 && event < RADIO_END)
+ return m_radioMsgTimestamp[ event - RADIO_START_1 ][ i ];
+
+ return 0.0f;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the interval since the last time this message was sent
+ */
+float CCSBotManager::GetRadioMessageInterval( RadioType event, int teamID ) const
+{
+ int i = (teamID == TEAM_TERRORIST) ? 0 : 1;
+
+ if (event > RADIO_START_1 && event < RADIO_END)
+ return gpGlobals->curtime - m_radioMsgTimestamp[ event - RADIO_START_1 ][ i ];
+
+ return 99999999.9f;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Set the given radio message timestamp.
+ * 'teamID' can be TEAM_CT or TEAM_TERRORIST
+ */
+void CCSBotManager::SetRadioMessageTimestamp( RadioType event, int teamID )
+{
+ int i = (teamID == TEAM_TERRORIST) ? 0 : 1;
+
+ if (event > RADIO_START_1 && event < RADIO_END)
+ m_radioMsgTimestamp[ event - RADIO_START_1 ][ i ] = gpGlobals->curtime;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Reset all radio message timestamps
+ */
+void CCSBotManager::ResetRadioMessageTimestamps( void )
+{
+ for( int t=0; t<2; ++t )
+ {
+ for( int m=0; m<(RADIO_END - RADIO_START_1); ++m )
+ m_radioMsgTimestamp[ m ][ t ] = 0.0f;
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Display nav areas as they become reachable by each team
+ */
+void DrawOccupyTime( void )
+{
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ CNavArea *area = TheNavAreas[ it ];
+
+ int r, g, b;
+
+ if (TheCSBots()->GetElapsedRoundTime() > area->GetEarliestOccupyTime( TEAM_TERRORIST ))
+ {
+ if (TheCSBots()->GetElapsedRoundTime() > area->GetEarliestOccupyTime( TEAM_CT ))
+ {
+ r = 255; g = 0; b = 255;
+ }
+ else
+ {
+ r = 255; g = 0; b = 0;
+ }
+ }
+ else if (TheCSBots()->GetElapsedRoundTime() > area->GetEarliestOccupyTime( TEAM_CT ))
+ {
+ r = 0; g = 0; b = 255;
+ }
+ else
+ {
+ continue;
+ }
+
+ const Vector &nw = area->GetCorner( NORTH_WEST );
+ const Vector &ne = area->GetCorner( NORTH_EAST );
+ const Vector &sw = area->GetCorner( SOUTH_WEST );
+ const Vector &se = area->GetCorner( SOUTH_EAST );
+
+ NDebugOverlay::Line( nw, ne, r, g, b, true, 0.1f );
+ NDebugOverlay::Line( nw, sw, r, g, b, true, 0.1f );
+ NDebugOverlay::Line( se, sw, r, g, b, true, 0.1f );
+ NDebugOverlay::Line( se, ne, r, g, b, true, 0.1f );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Display areas where players will likely have initial battles
+ */
+void DrawBattlefront( void )
+{
+ const float epsilon = 1.0f;
+ int r = 255, g = 50, b = 0;
+
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ CNavArea *area = TheNavAreas[ it ];
+
+ if ( fabs(area->GetEarliestOccupyTime( TEAM_TERRORIST ) - area->GetEarliestOccupyTime( TEAM_CT )) > epsilon )
+ {
+ continue;
+ }
+
+
+ const Vector &nw = area->GetCorner( NORTH_WEST );
+ const Vector &ne = area->GetCorner( NORTH_EAST );
+ const Vector &sw = area->GetCorner( SOUTH_WEST );
+ const Vector &se = area->GetCorner( SOUTH_EAST );
+
+ NDebugOverlay::Line( nw, ne, r, g, b, true, 0.1f );
+ NDebugOverlay::Line( nw, sw, r, g, b, true, 0.1f );
+ NDebugOverlay::Line( se, sw, r, g, b, true, 0.1f );
+ NDebugOverlay::Line( se, ne, r, g, b, true, 0.1f );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+static bool CheckAreaAgainstAllZoneAreas(CNavArea *queryArea)
+{
+ // A marked area means they just want to double check this one spot
+ int goalZoneCount = TheCSBots()->GetZoneCount();
+
+ for( int zoneIndex = 0; zoneIndex < goalZoneCount; zoneIndex++ )
+ {
+ const CCSBotManager::Zone *currentZone = TheCSBots()->GetZone(zoneIndex);
+
+ int zoneAreaCount = currentZone->m_areaCount;
+ for( int areaIndex = 0; areaIndex < zoneAreaCount; areaIndex++ )
+ {
+ CNavArea *zoneArea = currentZone->m_area[areaIndex];
+ // We need to be connected to every area in the zone, since we don't know what other code might pick for an area
+ ShortestPathCost cost;
+ if( NavAreaTravelDistance(queryArea, zoneArea, cost) == -1.0f )
+ {
+ Msg( "Area #%d is disconnected from goal area #%d.\n",
+ queryArea->GetID(),
+ zoneArea->GetID()
+ );
+ return false;
+ }
+ }
+
+ }
+ return true;
+}
+
+CON_COMMAND_F( nav_check_connectivity, "Checks to be sure every (or just the marked) nav area can get to every goal area for the map (hostages or bomb site).", FCVAR_CHEAT )
+{
+ //Nav command in here since very CS specific.
+
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ if ( TheNavMesh->GetMarkedArea() )
+ {
+ CNavArea *markedArea = TheNavMesh->GetMarkedArea();
+ bool fine = CheckAreaAgainstAllZoneAreas( markedArea );
+ if( fine )
+ {
+ Msg( "Area #%d is connected to all goal areas.\n", markedArea->GetID() );
+ }
+ }
+ else
+ {
+ // Otherwise, loop through every area, and make sure they can all get to the goal.
+ float start = engine->Time();
+ FOR_EACH_VEC( TheNavAreas, nit )
+ {
+ CheckAreaAgainstAllZoneAreas(TheNavAreas[ nit ]);
+ }
+
+ float end = engine->Time();
+ float time = (end - start) * 1000.0f;
+ Msg( "nav_check_connectivity took %2.2f ms\n", time );
+ }
+}
+
+
+
+