diff options
Diffstat (limited to 'game/server/cstrike/bot/cs_bot_manager.cpp')
| -rw-r--r-- | game/server/cstrike/bot/cs_bot_manager.cpp | 2390 |
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 ); + } +} + + + + |