You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2390 lines
62 KiB
2390 lines
62 KiB
//========= 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; 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 ); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|