//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// // Author: Michael S. Booth (mike@turtlerockstudios.com), 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; iAddListener( 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; zm_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; iGetAttributes() & 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; iCount(); ++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(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 - 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 - 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 - 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 - 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; iIsAlive() ) continue; bot->CommitSuicide(); if ( !all ) { return; } } } //-------------------------------------------------------------------------------------------------------------- CON_COMMAND_F( bot_kick, "bot_kick - 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; iServerCommand( 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( UTIL_PlayerByIndex( i ) ); if (player == NULL) continue; if (player->IsBot()) { CCSBot *bot = dynamic_cast( 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( 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(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( 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( 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; imaxEntities; ++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; im_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; zm_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; iFindVar( 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( 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; icurtime, 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; iGetAttributes() & 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( 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 ); } }