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.
1506 lines
35 KiB
1506 lines
35 KiB
#include "bot_common.h" |
|
|
|
/* |
|
* Globals initialization |
|
*/ |
|
CBotManager *TheBots = NULL; |
|
|
|
float CCSBotManager::m_flNextCVarCheck = 0.0f; |
|
bool CCSBotManager::m_isMapDataLoaded = false; |
|
bool CCSBotManager::m_isLearningMap = false; |
|
bool CCSBotManager::m_isAnalysisRequested = false; |
|
NavEditCmdType CCSBotManager::m_editCmd = EDIT_NONE; |
|
|
|
|
|
CCSBotManager::CCSBotManager() |
|
{ |
|
m_flNextCVarCheck = 0.0f; |
|
|
|
m_zoneCount = 0; |
|
SetLooseBomb(NULL); |
|
|
|
m_isBombPlanted = false; |
|
m_bombDefuser = NULL; |
|
|
|
m_isLearningMap = false; |
|
m_isAnalysisRequested = false; |
|
m_editCmd = EDIT_NONE; |
|
|
|
m_navPlace = false; |
|
m_roundStartTimestamp = 0.0f; |
|
|
|
m_bServerActive = false; |
|
|
|
TheBotPhrases = new BotPhraseManager; |
|
// load the database of bot radio chatter |
|
TheBotPhrases->Initialize("BotChatter.db", 0); |
|
|
|
TheBotProfiles = new BotProfileManager; |
|
// make sure default voice bank is first |
|
TheBotProfiles->FindVoiceBankIndex("BotChatter.db"); |
|
|
|
const char *filename; |
|
#if 0 |
|
if (IS_CAREER_MATCH()) |
|
{ |
|
filename = "MissionPacks/BotPackList.db"; |
|
} |
|
else |
|
#endif |
|
{ |
|
filename = "BotPackList.db"; |
|
} |
|
|
|
// read in the list of bot profile DBs |
|
int dataLength; |
|
char *dataPointer = (char *)LOAD_FILE_FOR_ME((char *)filename, &dataLength); |
|
|
|
if (dataPointer == NULL) |
|
{ |
|
TheBotProfiles->Init("BotProfile.db"); |
|
} |
|
else |
|
{ |
|
const char *dataFile = SharedParse(dataPointer); |
|
const char *token; |
|
|
|
while (dataFile != NULL) |
|
{ |
|
token = SharedGetToken(); |
|
char *clone = CloneString(token); |
|
TheBotProfiles->Init(clone); |
|
delete[] clone; |
|
dataFile = SharedParse(dataFile); |
|
} |
|
|
|
FREE_FILE(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 *pVoiceBanks = TheBotProfiles->GetVoiceBanks(); |
|
for (int i = 1; i < pVoiceBanks->Count(); ++i) |
|
{ |
|
TheBotPhrases->Initialize((*pVoiceBanks)[i], i); |
|
} |
|
} |
|
|
|
// Invoked when a new round begins |
|
|
|
void CCSBotManager::RestartRound() |
|
{ |
|
// extend |
|
CBotManager::RestartRound(); |
|
|
|
SetLooseBomb(NULL); |
|
m_isBombPlanted = false; |
|
m_earliestBombPlantTimestamp = gpGlobals->time + RANDOM_FLOAT(10.0f, 30.0f); |
|
m_bombDefuser = NULL; |
|
|
|
m_editCmd = EDIT_NONE; |
|
|
|
ResetRadioMessageTimestamps(); |
|
|
|
m_lastSeenEnemyTimestamp = -9999.9f; |
|
|
|
m_roundStartTimestamp = gpGlobals->time + CVAR_GET_FLOAT("mp_freezetime"); |
|
|
|
// randomly decide if defensive team wants to "rush" as a whole |
|
const float defenseRushChance = 33.3f; // 25.0f; |
|
m_isDefenseRushing = (RANDOM_FLOAT(0.0f, 100.0f) <= defenseRushChance) ? true : false; |
|
|
|
TheBotPhrases->OnRoundRestart(); |
|
|
|
m_isRoundOver = false; |
|
m_isRespawnStarted = false; |
|
m_canRespawn = true; |
|
} |
|
|
|
void UTIL_DrawBox(Extent *extent, int lifetime, int red, int green, int blue) |
|
{ |
|
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 // end iterator |
|
}; |
|
|
|
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 ]; |
|
|
|
UTIL_DrawBeamPoints(from, to, lifetime, red, green, blue); |
|
UTIL_DrawBeamPoints(to, from, lifetime, red, green, blue); |
|
} |
|
} |
|
|
|
// Called each frame |
|
|
|
void CCSBotManager::StartFrame() |
|
{ |
|
// EXTEND |
|
CBotManager::StartFrame(); |
|
MonitorBotCVars(); |
|
|
|
// debug zone extent visualization |
|
if (cv_bot_debug.value == 5.0f) |
|
{ |
|
for (int z = 0; z < m_zoneCount; ++z) |
|
{ |
|
Zone *zone = &m_zone[z]; |
|
UTIL_DrawBox(&zone->m_extent, 1, 255, 100, 0); |
|
} |
|
} |
|
} |
|
|
|
// Return true if the bot can use this weapon |
|
|
|
bool CCSBotManager::IsWeaponUseable(CBasePlayerItem *item) const |
|
{ |
|
if (item == NULL) |
|
{ |
|
return false; |
|
} |
|
|
|
// if (item->m_iId == WEAPON_C4) |
|
// return true; |
|
|
|
// int weaponClass = WeaponIDToWeaponClass(item->m_iId); |
|
#if 0 |
|
if ((!AllowShotguns() && weaponClass == WEAPONCLASS_SHOTGUN) |
|
|| (!AllowMachineGuns() && weaponClass == WEAPONCLASS_MACHINEGUN) |
|
|| (!AllowRifles() && weaponClass == WEAPONCLASS_RIFLE) |
|
|| (!AllowSnipers() && weaponClass == WEAPONCLASS_SNIPERRIFLE) |
|
|| (!AllowSubMachineGuns() && weaponClass == WEAPONCLASS_SUBMACHINEGUN) |
|
|| (!AllowTacticalShield() && item->m_iId == WEAPON_SHIELDGUN) |
|
|| (!AllowPistols() && weaponClass == WEAPONCLASS_PISTOL) |
|
|| (!AllowGrenades() && weaponClass == WEAPONCLASS_GRENADE)) |
|
{ |
|
return false; |
|
} |
|
#endif |
|
return true; |
|
} |
|
|
|
// Return true if this player is on "defense" |
|
|
|
bool CCSBotManager::IsOnDefense(CBasePlayer *player) const |
|
{ |
|
#if 0 |
|
switch (GetScenario()) |
|
{ |
|
case SCENARIO_DEFUSE_BOMB: |
|
return (player->m_iTeam == CT); |
|
|
|
case SCENARIO_RESCUE_HOSTAGES: |
|
return (player->m_iTeam == TERRORIST); |
|
|
|
case SCENARIO_ESCORT_VIP: |
|
return (player->m_iTeam == TERRORIST); |
|
} |
|
#endif |
|
return false; |
|
} |
|
|
|
// Return true if this player is on "offense" |
|
|
|
bool CCSBotManager::IsOnOffense(CBasePlayer *player) const |
|
{ |
|
return !IsOnDefense(player); |
|
} |
|
|
|
// Invoked when a map has just been loaded |
|
|
|
void CCSBotManager::ServerActivate() |
|
{ |
|
DestroyNavigationMap(); |
|
m_isMapDataLoaded = false; |
|
|
|
m_zoneCount = 0; |
|
m_gameScenario = SCENARIO_DEATHMATCH; |
|
|
|
ValidateMapData(); |
|
RestartRound(); |
|
|
|
m_isLearningMap = false; |
|
m_isAnalysisRequested = false; |
|
|
|
m_bServerActive = true; |
|
AddServerCommands(); |
|
|
|
TheBotPhrases->OnMapChange(); |
|
} |
|
|
|
void CCSBotManager::AddServerCommand(const char *cmd) |
|
{ |
|
ADD_SERVER_COMMAND((char *)cmd, Bot_ServerCommand); |
|
} |
|
|
|
void CCSBotManager::AddServerCommands() |
|
{ |
|
static bool fFirstTime = true; |
|
|
|
if (!fFirstTime) |
|
return; |
|
|
|
fFirstTime = false; |
|
|
|
if (true) |
|
{ |
|
AddServerCommand("bot_about"); |
|
AddServerCommand("bot_add"); |
|
AddServerCommand("bot_add_t"); |
|
AddServerCommand("bot_add_ct"); |
|
AddServerCommand("bot_kill"); |
|
AddServerCommand("bot_kick"); |
|
AddServerCommand("bot_knives_only"); |
|
AddServerCommand("bot_pistols_only"); |
|
AddServerCommand("bot_snipers_only"); |
|
AddServerCommand("bot_all_weapons"); |
|
AddServerCommand("entity_dump"); |
|
AddServerCommand("bot_nav_delete"); |
|
AddServerCommand("bot_nav_split"); |
|
AddServerCommand("bot_nav_merge"); |
|
AddServerCommand("bot_nav_mark"); |
|
AddServerCommand("bot_nav_begin_area"); |
|
AddServerCommand("bot_nav_end_area"); |
|
AddServerCommand("bot_nav_connect"); |
|
AddServerCommand("bot_nav_disconnect"); |
|
AddServerCommand("bot_nav_splice"); |
|
AddServerCommand("bot_nav_crouch"); |
|
AddServerCommand("bot_nav_jump"); |
|
AddServerCommand("bot_nav_precise"); |
|
AddServerCommand("bot_nav_no_jump"); |
|
AddServerCommand("bot_nav_analyze"); |
|
AddServerCommand("bot_nav_strip"); |
|
AddServerCommand("bot_nav_save"); |
|
AddServerCommand("bot_nav_load"); |
|
AddServerCommand("bot_nav_use_place"); |
|
AddServerCommand("bot_nav_place_floodfill"); |
|
AddServerCommand("bot_nav_place_pick"); |
|
AddServerCommand("bot_nav_toggle_place_mode"); |
|
AddServerCommand("bot_nav_toggle_place_painting"); |
|
AddServerCommand("bot_goto_mark"); |
|
AddServerCommand("bot_memory_usage"); |
|
AddServerCommand("bot_nav_mark_unnamed"); |
|
AddServerCommand("bot_nav_warp"); |
|
AddServerCommand("bot_nav_corner_select"); |
|
AddServerCommand("bot_nav_corner_raise"); |
|
AddServerCommand("bot_nav_corner_lower"); |
|
AddServerCommand("bot_nav_check_consistency"); |
|
} |
|
} |
|
|
|
void CCSBotManager::ServerDeactivate() |
|
{ |
|
m_bServerActive = false; |
|
} |
|
|
|
void CCSBotManager::ClientDisconnect(CBasePlayer *pPlayer) |
|
{ |
|
if (pPlayer != NULL && pPlayer->IsBot()) |
|
{ |
|
entvars_t *temp = VARS(pPlayer->edict()); |
|
CCSBot *pBot = static_cast<CCSBot *>(pPlayer); |
|
|
|
if (pBot != NULL) |
|
{ |
|
pBot->Disconnect(); |
|
} |
|
|
|
if (!FStringNull(pPlayer->pev->classname)) |
|
{ |
|
//RemoveEntityHashValue(pPlayer->pev, STRING(pPlayer->pev->classname), CLASSNAME); |
|
} |
|
|
|
FREE_PRIVATE(pPlayer->edict()); |
|
|
|
CBasePlayer *player = GetClassPtr((CBasePlayer *)temp); |
|
//AddEntityHashValue(player->pev, STRING(player->pev->classname), CLASSNAME); |
|
player->pev->flags = FL_DORMANT; |
|
} |
|
} |
|
|
|
void PrintAllEntities() |
|
{ |
|
for (int i = 1; i < gpGlobals->maxEntities; ++i) |
|
{ |
|
edict_t *edict = INDEXENT(i); |
|
|
|
if (!edict || FStringNull(edict->v.classname)) |
|
continue; |
|
|
|
CONSOLE_ECHO(" %s\n", STRING(edict->v.classname)); |
|
} |
|
} |
|
|
|
void CCSBotManager::ServerCommand(const char *pcmd) |
|
{ |
|
if (!m_bServerActive) |
|
return; |
|
|
|
char buffer[512]; |
|
const char *msg = CMD_ARGV(1); |
|
|
|
#if 0 // crashes on xash |
|
if (FStrEq(pcmd, "bot_about")) |
|
{ |
|
Q_snprintf(buffer, sizeof (buffer), "\n--------------------------------------------------------------------------\nThe Official Counter-Strike Bot V%d.%02d\nCreated by Michael S. Booth\nWeb: www.turtlerockstudios.com\\csbot\nE-mail: csbot@turtlerockstudios.com\n--------------------------------------------------------------------------\n\n", CSBOT_VERSION_MAJOR, CSBOT_VERSION_MINOR); |
|
g_engfuncs.pfnServerPrint(buffer); |
|
HintMessageToAllPlayers(buffer); |
|
} |
|
else |
|
#endif |
|
if (FStrEq(pcmd, "bot_add")) |
|
{ |
|
BotAddCommand(BOT_TEAM_ANY, FROM_CONSOLE); |
|
} |
|
else if (FStrEq(pcmd, "bot_add_t")) |
|
{ |
|
BotAddCommand(BOT_TEAM_T, FROM_CONSOLE); |
|
} |
|
else if (FStrEq(pcmd, "bot_add_ct")) |
|
{ |
|
BotAddCommand(BOT_TEAM_CT, FROM_CONSOLE); |
|
} |
|
else if (FStrEq(pcmd, "bot_kill")) |
|
{ |
|
bool killThemAll; |
|
if (CMD_ARGC() == 1 || FStrEq(msg, "all")) |
|
killThemAll = true; |
|
else |
|
killThemAll = false; |
|
|
|
for (int iIndex = 1; iIndex <= gpGlobals->maxClients; ++iIndex) |
|
{ |
|
CBasePlayer *pPlayer = static_cast<CBasePlayer *>(UTIL_PlayerByIndex(iIndex)); |
|
|
|
if (pPlayer == NULL) |
|
continue; |
|
|
|
if (FNullEnt(pPlayer->pev)) |
|
continue; |
|
|
|
const char *name = STRING(pPlayer->pev->netname); |
|
|
|
if (FStrEq(name, "")) |
|
continue; |
|
|
|
if (pPlayer->IsBot()) |
|
{ |
|
if (killThemAll || FStrEq(name, msg)) |
|
{ |
|
pPlayer->TakeDamage(pPlayer->pev, pPlayer->pev, 9999.9f, DMG_CRUSH); |
|
} |
|
} |
|
} |
|
} |
|
else if (FStrEq(pcmd, "bot_kick")) |
|
{ |
|
bool kickThemAll; |
|
if (CMD_ARGC() == 1 || FStrEq(msg, "all")) |
|
kickThemAll = true; |
|
else |
|
kickThemAll = false; |
|
|
|
for (int iIndex = 1; iIndex <= gpGlobals->maxClients; ++iIndex) |
|
{ |
|
CBasePlayer *pPlayer = static_cast<CBasePlayer *>(UTIL_PlayerByIndex(iIndex)); |
|
|
|
if (pPlayer == NULL) |
|
continue; |
|
|
|
if (FNullEnt(pPlayer->pev)) |
|
continue; |
|
|
|
const char *name = STRING(pPlayer->pev->netname); |
|
|
|
if (FStrEq(name, "")) |
|
continue; |
|
|
|
if (pPlayer->IsBot()) |
|
{ |
|
if (kickThemAll || FStrEq(name, msg)) |
|
{ |
|
// adjust bot quota so kicked bot is not immediately added back in |
|
int newQuota = cv_bot_quota.value - 1; |
|
SERVER_COMMAND(UTIL_VarArgs("kick \"%s\"\n", name)); |
|
CVAR_SET_FLOAT("bot_quota", clamp(newQuota, 0, (int)cv_bot_quota.value)); |
|
} |
|
} |
|
} |
|
|
|
if (kickThemAll || cv_bot_quota.value < 0.0f) |
|
{ |
|
CVAR_SET_FLOAT("bot_quota", 0); |
|
} |
|
} |
|
else if (FStrEq(pcmd, "bot_knives_only")) |
|
{ |
|
CVAR_SET_FLOAT("bot_allow_pistols", 0); |
|
CVAR_SET_FLOAT("bot_allow_shotguns", 0); |
|
CVAR_SET_FLOAT("bot_allow_sub_machine_guns", 0); |
|
CVAR_SET_FLOAT("bot_allow_rifles", 0); |
|
CVAR_SET_FLOAT("bot_allow_machine_guns", 0); |
|
CVAR_SET_FLOAT("bot_allow_grenades", 0); |
|
CVAR_SET_FLOAT("bot_allow_snipers", 0); |
|
CVAR_SET_FLOAT("bot_allow_shield", 0); |
|
} |
|
else if (FStrEq(pcmd, "bot_pistols_only")) |
|
{ |
|
CVAR_SET_FLOAT("bot_allow_pistols", 1); |
|
CVAR_SET_FLOAT("bot_allow_shotguns", 0); |
|
CVAR_SET_FLOAT("bot_allow_sub_machine_guns", 0); |
|
CVAR_SET_FLOAT("bot_allow_rifles", 0); |
|
CVAR_SET_FLOAT("bot_allow_machine_guns", 0); |
|
CVAR_SET_FLOAT("bot_allow_grenades", 0); |
|
CVAR_SET_FLOAT("bot_allow_snipers", 0); |
|
CVAR_SET_FLOAT("bot_allow_shield", 0); |
|
} |
|
else if (FStrEq(pcmd, "bot_snipers_only")) |
|
{ |
|
CVAR_SET_FLOAT("bot_allow_pistols", 1); |
|
CVAR_SET_FLOAT("bot_allow_shotguns", 0); |
|
CVAR_SET_FLOAT("bot_allow_sub_machine_guns", 0); |
|
CVAR_SET_FLOAT("bot_allow_rifles", 0); |
|
CVAR_SET_FLOAT("bot_allow_machine_guns", 0); |
|
CVAR_SET_FLOAT("bot_allow_grenades", 0); |
|
CVAR_SET_FLOAT("bot_allow_snipers", 1); |
|
CVAR_SET_FLOAT("bot_allow_shield", 0); |
|
} |
|
else if (FStrEq(pcmd, "bot_all_weapons")) |
|
{ |
|
CVAR_SET_FLOAT("bot_allow_pistols", 1); |
|
CVAR_SET_FLOAT("bot_allow_shotguns", 1); |
|
CVAR_SET_FLOAT("bot_allow_sub_machine_guns", 1); |
|
CVAR_SET_FLOAT("bot_allow_rifles", 1); |
|
CVAR_SET_FLOAT("bot_allow_machine_guns", 1); |
|
CVAR_SET_FLOAT("bot_allow_grenades", 1); |
|
CVAR_SET_FLOAT("bot_allow_snipers", 1); |
|
CVAR_SET_FLOAT("bot_allow_shield", 1); |
|
} |
|
else if (FStrEq(pcmd, "entity_dump")) |
|
{ |
|
PrintAllEntities(); |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_delete")) |
|
{ |
|
m_editCmd = EDIT_DELETE; |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_split")) |
|
{ |
|
m_editCmd = EDIT_SPLIT; |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_merge")) |
|
{ |
|
m_editCmd = EDIT_MERGE; |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_mark")) |
|
{ |
|
m_editCmd = EDIT_MARK; |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_begin_area")) |
|
{ |
|
m_editCmd = EDIT_BEGIN_AREA; |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_end_area")) |
|
{ |
|
m_editCmd = EDIT_END_AREA; |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_connect")) |
|
{ |
|
m_editCmd = EDIT_CONNECT; |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_disconnect")) |
|
{ |
|
m_editCmd = EDIT_DISCONNECT; |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_splice")) |
|
{ |
|
m_editCmd = EDIT_SPLICE; |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_crouch")) |
|
{ |
|
m_editCmd = EDIT_ATTRIB_CROUCH; |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_jump")) |
|
{ |
|
m_editCmd = EDIT_ATTRIB_JUMP; |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_precise")) |
|
{ |
|
m_editCmd = EDIT_ATTRIB_PRECISE; |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_no_jump")) |
|
{ |
|
m_editCmd = EDIT_ATTRIB_NO_JUMP; |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_analyze")) |
|
{ |
|
m_isAnalysisRequested = true; |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_strip")) |
|
{ |
|
StripNavigationAreas(); |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_save")) |
|
{ |
|
GET_GAME_DIR(buffer); |
|
Q_strcat(buffer, "\\"); |
|
Q_strcat(buffer, CBotManager::GetNavMapFilename()); |
|
|
|
if (SaveNavigationMap(buffer)) |
|
CONSOLE_ECHO("Navigation map '%s' saved.\n", buffer); |
|
else |
|
CONSOLE_ECHO("ERROR: Cannot save navigation map '%s'.\n", buffer); |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_load")) |
|
{ |
|
ValidateMapData(); |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_use_place")) |
|
{ |
|
if (CMD_ARGC() == 1) |
|
{ |
|
// no arguments = list all available places |
|
int i = 0; |
|
const BotPhraseList *placeList = TheBotPhrases->GetPlaceList(); |
|
FOR_EACH_LL ((*placeList), it) |
|
{ |
|
const BotPhrase *phrase = (*placeList)[it]; |
|
|
|
if (phrase->GetID() == GetNavPlace()) |
|
CONSOLE_ECHO("--> %-26s", phrase->GetName()); |
|
else |
|
CONSOLE_ECHO("%-30s", phrase->GetName()); |
|
|
|
if (!(i % 3)) |
|
CONSOLE_ECHO("\n"); |
|
} |
|
CONSOLE_ECHO("\n"); |
|
} |
|
else |
|
{ |
|
// single argument = set current place |
|
const BotPhraseList *placeList = TheBotPhrases->GetPlaceList(); |
|
const BotPhrase *found = NULL; |
|
bool isAmbiguous = false; |
|
FOR_EACH_LL ((*placeList), it) |
|
{ |
|
const BotPhrase *phrase = (*placeList)[it]; |
|
if (!Q_strnicmp(phrase->GetName(), msg, Q_strlen(msg))) |
|
{ |
|
// check for exact match in case of subsets of other strings |
|
if (!Q_strcmp(phrase->GetName(), msg)) |
|
{ |
|
found = phrase; |
|
isAmbiguous = false; |
|
break; |
|
} |
|
|
|
if (found != NULL) |
|
{ |
|
isAmbiguous = true; |
|
} |
|
else |
|
{ |
|
found = phrase; |
|
} |
|
} |
|
} |
|
|
|
if (isAmbiguous) |
|
{ |
|
CONSOLE_ECHO("Ambiguous\n"); |
|
} |
|
else |
|
{ |
|
CONSOLE_ECHO("Current place set to '%s'\n", found->GetName()); |
|
m_navPlace = found->GetID(); |
|
} |
|
} |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_toggle_place_mode")) |
|
{ |
|
m_editCmd = EDIT_TOGGLE_PLACE_MODE; |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_place_floodfill")) |
|
{ |
|
m_editCmd = EDIT_PLACE_FLOODFILL; |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_place_pick")) |
|
{ |
|
m_editCmd = EDIT_PLACE_PICK; |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_toggle_place_painting")) |
|
{ |
|
m_editCmd = EDIT_TOGGLE_PLACE_PAINTING; |
|
} |
|
else if (FStrEq(pcmd, "bot_goto_mark")) |
|
{ |
|
// tell the first bot we find to go to our marked area |
|
CNavArea *area = GetMarkedArea(); |
|
if (area != NULL) |
|
{ |
|
CBaseEntity *pEntity = NULL; |
|
while ((pEntity = UTIL_FindEntityByClassname(pEntity, "player")) != NULL) |
|
{ |
|
if (!pEntity->IsPlayer()) |
|
continue; |
|
|
|
if ((pEntity->pev->flags & FL_DORMANT) == FL_DORMANT) |
|
continue; |
|
|
|
CBasePlayer *playerOrBot = GetClassPtr((CBasePlayer *)pEntity->pev); |
|
|
|
if (playerOrBot->IsBot()) |
|
{ |
|
CCSBot *bot = static_cast<CCSBot *>(playerOrBot); |
|
if (bot != NULL) |
|
{ |
|
bot->MoveTo(&area->m_center, FASTEST_ROUTE); |
|
} |
|
|
|
break; |
|
} |
|
} |
|
} |
|
} |
|
else if (FStrEq(pcmd, "bot_memory_usage")) |
|
{ |
|
CONSOLE_ECHO("Memory usage:\n"); |
|
CONSOLE_ECHO(" %d bytes per bot\b", sizeof(CCSBot)); |
|
CONSOLE_ECHO(" %d Navigation Areas @ %d bytes each = %d bytes\n", |
|
TheNavAreaGrid.GetNavAreaCount(), |
|
sizeof(CNavArea), |
|
TheNavAreaGrid.GetNavAreaCount() * sizeof(CNavArea)); |
|
CONSOLE_ECHO(" %d Hiding Spots @ %d bytes each = %d bytes\n", |
|
TheHidingSpotList.Count(), |
|
sizeof(HidingSpot), |
|
sizeof(HidingSpot) * TheHidingSpotList.Count()); |
|
|
|
unsigned int encounterMem = 0; |
|
FOR_EACH_LL (TheNavAreaList, it) |
|
{ |
|
CNavArea *area = TheNavAreaList[it]; |
|
|
|
FOR_EACH_LL (area->m_spotEncounterList, it2) |
|
{ |
|
SpotEncounter *se = area->m_spotEncounterList[it2]; |
|
|
|
encounterMem += sizeof(SpotEncounter); |
|
encounterMem += sizeof(SpotOrder) * se->spotList.Count(); |
|
} |
|
} |
|
|
|
CONSOLE_ECHO(" Encounter Spot data = %d bytes\n", encounterMem); |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_mark_unnamed")) |
|
{ |
|
m_editCmd = EDIT_MARK_UNNAMED; |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_warp")) |
|
{ |
|
m_editCmd = EDIT_WARP_TO_MARK; |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_corner_select")) |
|
{ |
|
m_editCmd = EDIT_SELECT_CORNER; |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_corner_raise")) |
|
{ |
|
m_editCmd = EDIT_RAISE_CORNER; |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_corner_lower")) |
|
{ |
|
m_editCmd = EDIT_LOWER_CORNER; |
|
} |
|
else if (FStrEq(pcmd, "bot_nav_check_consistency")) |
|
{ |
|
if (CMD_ARGC() != 2) |
|
{ |
|
CONSOLE_ECHO("usage: bot_nav_check_consistency <filename>\n"); |
|
return; |
|
} |
|
|
|
SanityCheckNavigationMap(msg); |
|
} |
|
} |
|
|
|
BOOL CCSBotManager::ClientCommand(CBasePlayer *pPlayer, const char *pcmd) |
|
{ |
|
return FALSE; |
|
} |
|
|
|
// Process the "bot_add" console command |
|
|
|
bool CCSBotManager::BotAddCommand(BotProfileTeamType team, bool isFromConsole) |
|
{ |
|
// dont allow bots to join if the Navigation Area is being generated |
|
if (m_isLearningMap) |
|
return false; |
|
|
|
const BotProfile *profile = NULL; |
|
|
|
if (!isFromConsole || CMD_ARGC() < 2) |
|
{ |
|
// if team not specified, check cv_bot_join_team cvar for preference |
|
if (team == BOT_TEAM_ANY) |
|
{ |
|
if (!Q_stricmp(cv_bot_join_team.string, "T")) |
|
team = BOT_TEAM_T; |
|
|
|
else if (!Q_stricmp(cv_bot_join_team.string, "CT")) |
|
team = BOT_TEAM_CT; |
|
else |
|
{ |
|
team = BOT_TEAM_T; |
|
} |
|
} |
|
|
|
// try to add a bot by name |
|
profile = TheBotProfiles->GetRandomProfile(GetDifficultyLevel(), team); |
|
|
|
if (profile == NULL) |
|
{ |
|
CONSOLE_ECHO("All bot profiles at this difficulty level are in use.\n"); |
|
return true; |
|
} |
|
} |
|
else |
|
{ |
|
// in career, ignore humans |
|
bool ignoreHumans = false; |
|
#warning "Will crash in non-deathmatch" |
|
CHalfLifeMultiplay *mp = (CHalfLifeMultiplay*)g_pGameRules; |
|
|
|
//if (mp != NULL && mp->IsCareer()) |
|
// ignoreHumans = true; |
|
|
|
if (UTIL_IsNameTaken(CMD_ARGV(1), ignoreHumans)) |
|
{ |
|
CONSOLE_ECHO("Error - %s is already in the game.\n", CMD_ARGV(1)); |
|
return true; |
|
} |
|
|
|
profile = TheBotProfiles->GetProfile(CMD_ARGV(1), team); |
|
if (profile == NULL) |
|
{ |
|
CONSOLE_ECHO("Error - no profile for '%s' exists.\n", CMD_ARGV(1)); |
|
return true; |
|
} |
|
} |
|
|
|
// create the bot |
|
if (AddBot(profile, team)) |
|
{ |
|
if (isFromConsole) |
|
{ |
|
// increase the bot quota to account for manually added bot |
|
CVAR_SET_FLOAT("bot_quota", cv_bot_quota.value + 1); |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
// Keep a minimum quota of bots in the game |
|
|
|
void CCSBotManager::MaintainBotQuota() |
|
{ |
|
if (m_isLearningMap) |
|
return; |
|
#warning "will crash in non-deathmatch" |
|
CHalfLifeMultiplay *mp = (CHalfLifeMultiplay *) g_pGameRules; |
|
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 (!IS_DEDICATED_SERVER() && totalHumansInGame == 0) |
|
return; |
|
|
|
int desiredBotCount = (int)cv_bot_quota.value; |
|
int botsInGame = UTIL_BotsInGame(); |
|
|
|
if (cv_bot_quota_match.value > 0.0) |
|
{ |
|
desiredBotCount = (int)(humanPlayersInGame * cv_bot_quota_match.value); |
|
} |
|
|
|
// wait for a player to join, if necessary |
|
if (cv_bot_join_after_player.value > 0.0) |
|
{ |
|
if (humanPlayersInGame == 0) |
|
desiredBotCount = 0; |
|
} |
|
|
|
// if bots will auto-vacate, we need to keep one slot open to allow players to join |
|
if (cv_bot_auto_vacate.value > 0.0) |
|
desiredBotCount = Q_min(desiredBotCount, gpGlobals->maxClients - (totalHumansInGame + 1)); |
|
else |
|
desiredBotCount = Q_min(desiredBotCount, gpGlobals->maxClients - totalHumansInGame); |
|
|
|
// add bots if necessary |
|
if (desiredBotCount > botsInGame) |
|
{ |
|
// don't try to add a bot if all teams are full |
|
//if (!mp->TeamFull(TERRORIST) || !mp->TeamFull(CT)) |
|
BotAddCommand(BOT_TEAM_ANY); |
|
} |
|
else if (desiredBotCount < botsInGame) |
|
{ |
|
// kick a bot to maintain quota |
|
|
|
// first remove any unassigned bots |
|
//if (UTIL_KickBotFromTeam(0)) |
|
// return; |
|
#if 0 |
|
//TeamName kickTeam; |
|
|
|
// remove from the team that has more players |
|
if (mp->m_iNumTerrorist > mp->m_iNumCT) |
|
{ |
|
kickTeam = TERRORIST; |
|
} |
|
else if (mp->m_iNumTerrorist < mp->m_iNumCT) |
|
{ |
|
kickTeam = CT; |
|
} |
|
// remove from the team that's winning |
|
else if (mp->m_iNumTerroristWins > mp->m_iNumCTWins) |
|
{ |
|
kickTeam = TERRORIST; |
|
} |
|
else if (mp->m_iNumCTWins > mp->m_iNumTerroristWins) |
|
{ |
|
kickTeam = CT; |
|
} |
|
else |
|
{ |
|
// teams and scores are equal, pick a team at random |
|
kickTeam = (RANDOM_LONG(0, 1) == 0) ? CT : TERRORIST; |
|
} |
|
|
|
// attempt to kick a bot from the given team |
|
if (UTIL_KickBotFromTeam(kickTeam)) |
|
return; |
|
|
|
// if there were no bots on the team, kick a bot from the other team |
|
if (kickTeam == TERRORIST) |
|
UTIL_KickBotFromTeam(CT); |
|
else |
|
UTIL_KickBotFromTeam(TERRORIST); |
|
#endif |
|
} |
|
else |
|
{ |
|
#if 0 |
|
if (mp != NULL && !mp->IsCareer()) |
|
return; |
|
|
|
bool humansAreCTs = (Q_strcmp(humans_join_team.string, "CT") == 0); |
|
|
|
if (humansAreCTs) |
|
{ |
|
if (mp->m_iNumCT <= 6) |
|
return; |
|
|
|
UTIL_KickBotFromTeam(CT); |
|
} |
|
else |
|
{ |
|
if (mp->m_iNumTerrorist <= 6) |
|
return; |
|
|
|
UTIL_KickBotFromTeam(TERRORIST); |
|
} |
|
|
|
CVAR_SET_FLOAT("bot_quota", cv_bot_quota.value - 1.0f); |
|
#endif |
|
} |
|
} |
|
|
|
void CCSBotManager::MonitorBotCVars() |
|
{ |
|
if (cv_bot_nav_edit.value != 0.0f) |
|
{ |
|
EditNavAreas(m_editCmd); |
|
m_editCmd = EDIT_NONE; |
|
} |
|
|
|
if (gpGlobals->time >= m_flNextCVarCheck) |
|
{ |
|
if (cv_bot_show_danger.value != 0.0f) |
|
DrawDanger(); |
|
|
|
MaintainBotQuota(); |
|
m_flNextCVarCheck = gpGlobals->time + 0.3f; |
|
} |
|
} |
|
|
|
// 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) |
|
{ |
|
const Extent *areaExtent = area->GetExtent(); |
|
|
|
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::ValidateMapData() |
|
{ |
|
if (m_isMapDataLoaded ) |
|
return; |
|
|
|
m_isMapDataLoaded = true; |
|
|
|
if (LoadNavigationMap()) |
|
{ |
|
CONSOLE_ECHO("Failed to load navigation map.\n"); |
|
return; |
|
} |
|
|
|
CONSOLE_ECHO("Navigation map loaded.\n"); |
|
|
|
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 = NULL; |
|
int i; |
|
for (i = 1; i < gpGlobals->maxEntities; ++i) |
|
{ |
|
entity = CBaseEntity::Instance(INDEXENT(i)); |
|
|
|
if (entity == NULL) |
|
continue; |
|
|
|
bool found = false; |
|
bool isLegacy = false; |
|
|
|
if (FClassnameIs(entity->pev, "func_bomb_target")) |
|
{ |
|
m_gameScenario = SCENARIO_DEFUSE_BOMB; |
|
found = true; |
|
isLegacy = false; |
|
} |
|
else if (FClassnameIs(entity->pev, "info_bomb_target")) |
|
{ |
|
m_gameScenario = SCENARIO_DEFUSE_BOMB; |
|
found = true; |
|
isLegacy = true; |
|
} |
|
else if (FClassnameIs(entity->pev, "func_hostage_rescue")) |
|
{ |
|
m_gameScenario = SCENARIO_RESCUE_HOSTAGES; |
|
found = true; |
|
isLegacy = false; |
|
} |
|
else if (FClassnameIs(entity->pev, "info_hostage_rescue")) |
|
{ |
|
m_gameScenario = SCENARIO_RESCUE_HOSTAGES; |
|
found = true; |
|
isLegacy = true; |
|
} |
|
else if (FClassnameIs(entity->pev, "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->pev, "func_vip_safetyzone")) |
|
{ |
|
m_gameScenario = SCENARIO_ESCORT_VIP; |
|
found = true; |
|
isLegacy = false; |
|
} |
|
|
|
if (found) |
|
{ |
|
if (m_zoneCount < MAX_ZONES) |
|
{ |
|
m_zone[ m_zoneCount ].m_center = (isLegacy) ? entity->pev->origin : (entity->pev->absmax + entity->pev->absmin) / 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 |
|
CONSOLE_ECHO("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) |
|
{ |
|
entity = NULL; |
|
|
|
while ((entity = UTIL_FindEntityByClassname(entity, "info_player_start")) != NULL) |
|
{ |
|
if (FNullEnt(entity->edict())) |
|
break; |
|
|
|
if (m_zoneCount < MAX_ZONES) |
|
{ |
|
m_zone[ m_zoneCount ].m_center = entity->pev->origin; |
|
m_zone[ m_zoneCount ].m_isLegacy = true; |
|
m_zone[ m_zoneCount ].m_index = m_zoneCount; |
|
m_zone[ m_zoneCount++ ].m_entity = entity; |
|
} |
|
else |
|
CONSOLE_ECHO("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 |
|
{ |
|
zone->m_extent.lo = zone->m_entity->pev->absmin; |
|
zone->m_extent.hi = zone->m_entity->pev->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); |
|
ForAllAreas(collector); |
|
} |
|
} |
|
|
|
bool CCSBotManager::AddBot(const BotProfile *profile, BotProfileTeamType team) |
|
{ |
|
// if (!g_bEnableCSBot) |
|
// return false; |
|
#if 0 |
|
CHalfLifeMultiplay *mp = (CHalfLifeMultiplay *)g_pGameRules; |
|
|
|
int nTeamSlot = UNASSIGNED; |
|
|
|
if (team == BOT_TEAM_ANY) |
|
{ |
|
// if team not specified, check cv_bot_join_team cvar for preference |
|
if (!Q_stricmp(cv_bot_join_team.string, "T")) |
|
nTeamSlot = TERRORIST; |
|
|
|
else if (!Q_stricmp(cv_bot_join_team.string, "CT")) |
|
nTeamSlot = CT; |
|
} |
|
else if (team == BOT_TEAM_CT) |
|
nTeamSlot = CT; |
|
|
|
else if (team == BOT_TEAM_T) |
|
nTeamSlot = TERRORIST; |
|
|
|
if (nTeamSlot == UNASSIGNED) |
|
{ |
|
nTeamSlot = SelectDefaultTeam(); |
|
} |
|
|
|
if (nTeamSlot == UNASSIGNED || mp->TeamFull(nTeamSlot)) |
|
{ |
|
CONSOLE_ECHO("Could not add bot to the game: Team is full\n"); |
|
return false; |
|
} |
|
|
|
if (mp->TeamStacked(nTeamSlot, UNASSIGNED)) |
|
{ |
|
CONSOLE_ECHO("Could not add bot to the game: Team is stacked (to disable this check, set mp_limitteams and mp_autoteambalance to zero and restart the round).\n"); |
|
return false; |
|
} |
|
#endif |
|
CCSBot *pBot = CreateBot<CCSBot>(profile); |
|
if (pBot == NULL) |
|
{ |
|
return false; |
|
} |
|
|
|
|
|
//int nJoinedTeam; |
|
ClientPutInServer(pBot->edict()); |
|
SET_CLIENT_KEY_VALUE(pBot->entindex(), GET_INFO_BUFFER(pBot->edict()), "*bot", "1"); |
|
#if 0 |
|
pBot->m_iMenu = Menu_ChooseTeam; |
|
pBot->m_iJoiningState = PICKINGTEAM; |
|
|
|
if (HandleMenu_ChooseTeam(pBot, nTeamSlot)) |
|
{ |
|
int skin = profile->GetSkin(); |
|
|
|
if (!skin) |
|
skin = 6;// MODEL_GIGN? |
|
|
|
HandleMenu_ChooseAppearance(pBot, skin); |
|
|
|
if (IS_DEDICATED_SERVER()) |
|
{ |
|
UTIL_DPrintf("Added bot %s to server\n", STRING(pBot->pev->netname)); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
SERVER_COMMAND(UTIL_VarArgs("kick \"%s\"\n", STRING(pBot->pev->netname))); |
|
CONSOLE_ECHO("Could not add bot to the game.\n"); |
|
#endif |
|
|
|
return false; |
|
} |
|
|
|
// 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 = 1e9f; |
|
|
|
for (int z = 0; z < m_zoneCount; ++z) |
|
{ |
|
float rangeSq = (m_zone[z].m_center - (*pos)).LengthSquared(); |
|
|
|
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 |
|
{ |
|
const Extent &areaExtent = *area->GetExtent(); |
|
Extent overlap; |
|
overlap.lo.x = Q_max(areaExtent.lo.x, zone->m_extent.lo.x); |
|
overlap.lo.y = Q_max(areaExtent.lo.y, zone->m_extent.lo.y); |
|
overlap.hi.x = Q_min(areaExtent.hi.x, zone->m_extent.hi.x); |
|
overlap.hi.y = Q_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 |
|
{ |
|
// TODO: improvement is needed |
|
if (!zone->m_areaCount) |
|
return NULL; |
|
|
|
return zone->m_area[ RANDOM_LONG(0, zone->m_areaCount - 1) ]; |
|
} |
|
|
|
void CCSBotManager::OnEvent(GameEventType event, CBaseEntity *entity, CBaseEntity *other) |
|
{ |
|
switch (event) |
|
{ |
|
case EVENT_BOMB_PLANTED: |
|
m_isBombPlanted = true; |
|
m_bombPlantTimestamp = gpGlobals->time; |
|
break; |
|
|
|
case EVENT_BOMB_DEFUSING: |
|
m_bombDefuser = (CBasePlayer *)entity; |
|
break; |
|
|
|
case EVENT_BOMB_DEFUSE_ABORTED: |
|
m_bombDefuser = NULL; |
|
break; |
|
|
|
case EVENT_BOMB_DEFUSED: |
|
m_isBombPlanted = false; |
|
m_bombDefuser = NULL; |
|
break; |
|
|
|
case EVENT_TERRORISTS_WIN: |
|
case EVENT_CTS_WIN: |
|
case EVENT_ROUND_DRAW: |
|
m_isRoundOver = true; |
|
break; |
|
|
|
case EVENT_RADIO_ENEMY_SPOTTED: |
|
m_lastSeenEnemyTimestamp = gpGlobals->time; |
|
SetLastSeenEnemyTimestamp(); |
|
break; |
|
|
|
default: |
|
break; |
|
} |
|
|
|
CBotManager::OnEvent(event, entity, other); |
|
} |
|
|
|
// Get the time remaining before the planted bomb explodes |
|
|
|
float CCSBotManager::GetBombTimeLeft() const |
|
{ |
|
return 10;//(g_pGameRules->m_iC4Timer - (gpGlobals->time - m_bombPlantTimestamp)); |
|
} |
|
|
|
void CCSBotManager::SetLooseBomb(CBaseEntity *bomb) |
|
{ |
|
m_looseBomb = bomb; |
|
|
|
if (bomb) |
|
{ |
|
m_looseBombArea = TheNavAreaGrid.GetNearestNavArea(&bomb->pev->origin); |
|
} |
|
else |
|
{ |
|
m_looseBombArea = NULL; |
|
} |
|
} |
|
|
|
// Return true if player is important to scenario (VIP, bomb carrier, etc) |
|
|
|
bool CCSBotManager::IsImportantPlayer(CBasePlayer *player) const |
|
{ |
|
#if 0 |
|
switch (GetScenario()) |
|
{ |
|
case SCENARIO_DEFUSE_BOMB: |
|
{ |
|
if (player->m_iTeam == TERRORIST && player->IsBombGuy()) |
|
return true; |
|
|
|
// TODO: CT's defusing the bomb are important |
|
return false; |
|
} |
|
case SCENARIO_ESCORT_VIP: |
|
{ |
|
if (player->m_iTeam == CT && player->m_bIsVIP) |
|
return true; |
|
|
|
return false; |
|
} |
|
case SCENARIO_RESCUE_HOSTAGES: |
|
{ |
|
// TODO: CT's escorting hostages are important |
|
return false; |
|
} |
|
} |
|
#endif |
|
// 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; |
|
#if 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->m_iTeam == TERRORIST && bot->m_bHasC4) |
|
return 1; |
|
|
|
break; |
|
} |
|
case SCENARIO_ESCORT_VIP: |
|
{ |
|
// the VIP has high priority |
|
if (bot->m_iTeam == CT && bot->m_bIsVIP) |
|
return 1; |
|
|
|
break; |
|
} |
|
case SCENARIO_RESCUE_HOSTAGES: |
|
{ |
|
// CT's rescuing hostages have high priority |
|
if (bot->m_iTeam == CT && bot->GetHostageEscortCount()) |
|
return 1; |
|
|
|
break; |
|
} |
|
} |
|
#endif |
|
// everyone else is ranked by their unique ID (which cannot be zero) |
|
return 1 + bot->GetID(); |
|
} |
|
|
|
// Return the last time the given radio message was sent for given team |
|
// 'teamID' can be CT or TERRORIST |
|
|
|
float CCSBotManager::GetRadioMessageTimestamp(GameEventType event, int teamID) const |
|
{ |
|
// if (event <= EVENT_START_RADIO_1 || event >= EVENT_END_RADIO) |
|
// return 0.0f; |
|
|
|
//int i = (teamID == TERRORIST) ? 0 : 1; |
|
return 0;//m_radioMsgTimestamp[ event - EVENT_START_RADIO_1 ][ i ]; |
|
} |
|
|
|
// Return the interval since the last time this message was sent |
|
|
|
float CCSBotManager::GetRadioMessageInterval(GameEventType event, int teamID) const |
|
{ |
|
// if (event <= EVENT_START_RADIO_1 || event >= EVENT_END_RADIO) |
|
return 99999999.9f; |
|
|
|
// int i = (teamID == TERRORIST) ? 0 : 1; |
|
// return gpGlobals->time - m_radioMsgTimestamp[ event - EVENT_START_RADIO_1 ][ i ]; |
|
} |
|
|
|
// Set the given radio message timestamp. |
|
// 'teamID' can be CT or TERRORIST |
|
|
|
void CCSBotManager::SetRadioMessageTimestamp(GameEventType event, int teamID) |
|
{ |
|
// if (event <= EVENT_START_RADIO_1 || event >= EVENT_END_RADIO) |
|
return; |
|
|
|
// int i = (teamID == TERRORIST) ? 0 : 1; |
|
// m_radioMsgTimestamp[ event - 1 ][ i ] = gpGlobals->time; |
|
} |
|
|
|
// Reset all radio message timestamps |
|
|
|
void CCSBotManager::ResetRadioMessageTimestamps() |
|
{ |
|
#if 0 |
|
for (int t = 0; t < ARRAYSIZE(m_radioMsgTimestamp[0]); ++t) |
|
{ |
|
for (int m = 0; m < ARRAYSIZE(m_radioMsgTimestamp); ++m) |
|
{ |
|
m_radioMsgTimestamp[m][t] = 0.0f; |
|
} |
|
} |
|
#endif |
|
}
|
|
|