diff --git a/include/engine/eiface.h b/include/engine/eiface.h index e159998..403fee8 100644 --- a/include/engine/eiface.h +++ b/include/engine/eiface.h @@ -33,11 +33,7 @@ struct cvar_t { char *name; -#if defined (YAPB_INCLUDED) - char *strval; // (dz): changed since genclass.h library #define -#else char *string; -#endif int flags; float value; cvar_t *next; @@ -76,12 +72,10 @@ typedef enum print_chat, } PRINT_TYPE; -#if defined (YAPB_INCLUDED) typedef enum { print_withtag = print_console | 0x3ff, } PRINT_TYPE_EX; // (dz): added for bots needs -#endif // For integrity checking of content on clients typedef enum diff --git a/project/yapb.rc b/project/yapb.rc index e22a44d..1b0ea2a 100644 --- a/project/yapb.rc +++ b/project/yapb.rc @@ -26,7 +26,7 @@ #include <../include/resource.h> // generated by update tool -- do not edit -- -#define PRODUCT_BUILD_TOOL 3853 +#define PRODUCT_BUILD_TOOL 3885 VS_VERSION_INFO VERSIONINFO FILEVERSION PRODUCT_VERSION_DWORD, PRODUCT_BUILD_TOOL @@ -45,7 +45,6 @@ FILETYPE 0x2 VALUE "LegalCopyright", PRODUCT_COPYRIGHT "\0" VALUE "LegalTrademarks", PRODUCT_LEGAL "\0" VALUE "ProductName", PRODUCT_NAME "\0" - VALUE "SpecialBuild", PRODUCT_OPT_TYPE "\0" VALUE "InternalName", PRODUCT_INTERNAL_NAME "\0" } } diff --git a/project/yapb.vcxproj b/project/yapb.vcxproj index 17b1187..0057f6a 100644 --- a/project/yapb.vcxproj +++ b/project/yapb.vcxproj @@ -35,7 +35,7 @@ - + @@ -60,7 +60,7 @@ DynamicLibrary - Intel C++ Compiler XE 14.0 + v120_xp false diff --git a/project/yapb.vcxproj.filters b/project/yapb.vcxproj.filters index a220a26..6c66ac5 100644 --- a/project/yapb.vcxproj.filters +++ b/project/yapb.vcxproj.filters @@ -68,12 +68,6 @@ - - source - - - source - source @@ -98,6 +92,12 @@ source + + source + + + source + diff --git a/source/manager.cpp b/source/manager.cpp new file mode 100644 index 0000000..0bdc632 --- /dev/null +++ b/source/manager.cpp @@ -0,0 +1,1377 @@ +// +// Yet Another POD-Bot, based on PODBot by Markus Klinge ("CountFloyd"). +// Copyright (c) YaPB Development Team. +// +// This software is licensed under the BSD-style license. +// Additional exceptions apply. For full license details, see LICENSE.txt or visit: +// http://yapb.jeefo.net/license +// + +#include + +ConVar yb_autovacate ("yb_autovacate", "-1"); + +ConVar yb_quota ("yb_quota", "0"); +ConVar yb_quota_match ("yb_quota_match", "0"); +ConVar yb_quota_match_max ("yb_quota_match_max", "0"); +ConVar yb_join_after_player ("yb_join_after_player", "0"); + +ConVar yb_join_team ("yb_join_team", "any"); +ConVar yb_name_prefix ("yb_name_prefix", ""); + +ConVar yb_minskill ("yb_minskill", "60"); +ConVar yb_maxskill ("yb_maxskill", "100"); + +ConVar yb_skill_tags ("yb_skill_tags", "0"); +ConVar yb_latency_display ("yb_latency_display", "2"); + +BotManager::BotManager (void) +{ + // this is a bot manager class constructor + + m_lastWinner = -1; + + m_economicsGood[TEAM_TF] = true; + m_economicsGood[TEAM_CF] = true; + + memset (m_bots, 0, sizeof (m_bots)); + + if (g_pGlobals != NULL) + InitQuota (); +} + +BotManager::~BotManager (void) +{ + // this is a bot manager class destructor, do not use GetMaxClients () here !! + + Free (); +} + +void BotManager::CallGameEntity (entvars_t *vars) +{ + // this function calls gamedll player() function, in case to create player entity in game + + if (g_isMetamod) + { + CALL_GAME_ENTITY (PLID, "player", vars); + return; + } + + static EntityPtr_t playerFunction = NULL; + + if (playerFunction == NULL) + playerFunction = (EntityPtr_t) g_gameLib->GetFunctionAddr ("player"); + + if (playerFunction != NULL) + (*playerFunction) (vars); +} + +int BotManager::CreateBot (String name, int skill, int personality, int team, int member) +{ + // this function completely prepares bot entity (edict) for creation, creates team, skill, sets name etc, and + // then sends result to bot constructor + + + edict_t *bot = NULL; + char outputName[33]; + + if (g_numWaypoints < 1) // don't allow creating bots with no waypoints loaded + { + CenterPrint ("Map is not waypointed. Cannot create bot"); + return 0; + } + else if (g_waypointsChanged) // don't allow creating bots with changed waypoints (distance tables are messed up) + { + CenterPrint ("Waypoints have been changed. Load waypoints again..."); + return 0; + } + + if (skill < 0 || skill > 100) + skill = g_randGen.Long (yb_minskill.GetInt (), yb_maxskill.GetInt ()); + + if (skill > 100 || skill < 0) + skill = g_randGen.Long (0, 100); + + if (personality < 0 || personality > 2) + { + if (g_randGen.Long (0, 100) < 50) + personality = PERSONALITY_NORMAL; + else + { + if (g_randGen.Long (0, 100) > 50) + personality = PERSONALITY_RUSHER; + else + personality = PERSONALITY_CAREFUL; + } + } + + // setup name + if (name.IsEmpty ()) + { + if (!g_botNames.IsEmpty ()) + { + bool nameFound = false; + + for (int i = 0; i < g_botNames.GetSize (); i++) + { + if (nameFound) + break; + + BotName *name = &g_botNames.GetRandomElement (); + + if (name == NULL || (name != NULL && name->used)) + continue; + + name->used = nameFound = true; + strcpy (outputName, name->name); + } + } + else + sprintf (outputName, "bot%i", g_randGen.Long (0, 100)); // just pick ugly random name + } + else + strncpy (outputName, name, 21); + + if (!IsNullString (yb_name_prefix.GetString ()) || yb_skill_tags.GetBool ()) + { + char prefixedName[33]; // temp buffer for storing modified name + + if (!IsNullString (yb_name_prefix.GetString ())) + sprintf (prefixedName, "%s %s", yb_name_prefix.GetString (), outputName); + + else if (yb_skill_tags.GetBool ()) + sprintf (prefixedName, "%s (%d)", outputName, skill); + + else if (!IsNullString (yb_name_prefix.GetString ()) && yb_skill_tags.GetBool ()) + sprintf (prefixedName, "%s %s (%d)", yb_name_prefix.GetString (), outputName, skill); + + // buffer has been modified, copy to real name + if (!IsNullString (prefixedName)) + strcpy (outputName, prefixedName); + } + + if (FNullEnt ((bot = (*g_engfuncs.pfnCreateFakeClient) (outputName)))) + { + CenterPrint ("Maximum players reached (%d/%d). Unable to create Bot.", GetMaxClients (), GetMaxClients ()); + return 2; + } + + int index = ENTINDEX (bot) - 1; + + InternalAssert (index >= 0 && index <= 32); // check index + InternalAssert (m_bots[index] == NULL); // check bot slot + + m_bots[index] = new Bot (bot, skill, personality, team, member); + + if (m_bots == NULL) + TerminateOnMalloc (); + + ServerPrint ("Connecting Bot... (Skill %d)", skill); + + return 1; +} + +int BotManager::GetIndex (edict_t *ent) +{ + // this function returns index of bot (using own bot array) + if (FNullEnt (ent)) + return -1; + + int index = ENTINDEX (ent) - 1; + + if (index < 0 || index >= 32) + return -1; + + if (m_bots[index] != NULL) + return index; + + return -1; // if no edict, return -1; +} + +Bot *BotManager::GetBot (int index) +{ + // this function finds a bot specified by index, and then returns pointer to it (using own bot array) + + if (index < 0 || index >= 32) + return NULL; + + if (m_bots[index] != NULL) + return m_bots[index]; + + return NULL; // no bot +} + +Bot *BotManager::GetBot (edict_t *ent) +{ + // same as above, but using bot entity + + return GetBot (GetIndex (ent)); +} + +Bot *BotManager::FindOneValidAliveBot (void) +{ + // this function finds one bot, alive bot :) + + Array foundBots; + + for (int i = 0; i < GetMaxClients (); i++) + { + if (m_bots[i] != NULL && IsAlive (m_bots[i]->GetEntity ())) + foundBots.Push (i); + } + + if (!foundBots.IsEmpty ()) + return m_bots[foundBots.GetRandomElement ()]; + + return NULL; +} + +void BotManager::Think (void) +{ + // this function calls think () function for all available at call moment bots, and + // try to catch internal error if such shit occurs + + for (int i = 0; i < GetMaxClients (); i++) + { + if (m_bots[i] != NULL) + { + // use these try-catch blocks to prevent server crashes when error occurs +#if defined (NDEBUG) && !defined (PLATFORM_LINUX) && !defined (PLATFORM_OSX) + try + { + m_bots[i]->Think (); + } + catch (...) + { + // error occurred. kick off all bots and then print a warning message + RemoveAll (); + + ServerPrintNoTag ("**** INTERNAL BOT ERROR! PLEASE SHUTDOWN AND RESTART YOUR SERVER! ****"); + } +#else + m_bots[i]->Think (); + +#endif + + } + } +} + +void BotManager::AddBot (const String &name, int skill, int personality, int team, int member) +{ + // this function putting bot creation process to queue to prevent engine crashes + + CreateQueue bot; + + // fill the holder + bot.name = name; + bot.skill = skill; + bot.personality = personality; + bot.team = team; + bot.member = member; + + // put to queue + m_creationTab.Push (bot); + + // keep quota number up to date + if (GetBotsNum () + 1 > yb_quota.GetInt ()) + yb_quota.SetInt (GetBotsNum () + 1); +} + +void BotManager::AddBot (String name, String skill, String personality, String team, String member) +{ + // this function is same as the function above, but accept as parameters string instead of integers + + CreateQueue bot; + const String &any = "*"; + + bot.name = (name.IsEmpty () || (name == any)) ? String ("\0") : name; + bot.skill = (skill.IsEmpty () || (skill == any)) ? -1 : skill.ToInt (); + bot.team = (team.IsEmpty () || (team == any)) ? -1 : team.ToInt (); + bot.member = (member.IsEmpty () || (member == any)) ? -1 : member.ToInt (); + bot.personality = (personality.IsEmpty () || (personality == any)) ? -1 : personality.ToInt (); + + m_creationTab.Push (bot); + + // keep quota number up to date + if (GetBotsNum () + 1 > yb_quota.GetInt ()) + yb_quota.SetInt (GetBotsNum () + 1); +} + +void BotManager::CheckAutoVacate (edict_t *ent) +{ + // this function sets timer to kick one bot off. + + if (yb_autovacate.GetBool ()) + RemoveRandom (); +} + +void BotManager::MaintainBotQuota (void) +{ + // this function keeps number of bots up to date, and don't allow to maintain bot creation + // while creation process in process. + + if (yb_join_after_player.GetInt () > 0 && GetHumansJoinedTeam () == 0) + { + RemoveAll (false); + return; + } + + if (!m_creationTab.IsEmpty () && m_maintainTime < GetWorldTime ()) + { + CreateQueue last = m_creationTab.Pop (); + int resultOfCall = CreateBot (last.name, last.skill, last.personality, last.team, last.member); + + // check the result of creation + if (resultOfCall == 0) + { + m_creationTab.RemoveAll (); // something worng with waypoints, reset tab of creation + yb_quota.SetInt (0); // reset quota + } + else if (resultOfCall == 2) + { + m_creationTab.RemoveAll (); // maximum players reached, so set quota to maximum players + yb_quota.SetInt (GetBotsNum ()); + } + m_maintainTime = GetWorldTime () + 0.15f; + } + + // now keep bot number up to date + if (m_maintainTime < GetWorldTime ()) + { + int botNumber = GetBotsNum (); + int humanNumber = GetHumansNum (); + + if (botNumber > yb_quota.GetInt ()) + RemoveRandom (); + + if (yb_quota_match.GetInt () > 0) + { + int num = yb_quota_match.GetInt () * humanNumber; + + if (num >= GetMaxClients () - humanNumber) + num = GetMaxClients () - humanNumber; + + if (yb_quota_match_max.GetInt () > 0 && num > yb_quota_match_max.GetInt ()) + num = yb_quota_match_max.GetInt (); + + yb_quota.SetInt (num); + yb_autovacate.SetInt (0); + } + + if (yb_autovacate.GetBool ()) + { + if (botNumber < yb_quota.GetInt () && botNumber < GetMaxClients () - 1) + AddRandom (); + + if (humanNumber >= GetMaxClients ()) + RemoveRandom (); + } + else + { + if (botNumber < yb_quota.GetInt () && botNumber < GetMaxClients ()) + AddRandom (); + } + + int botQuota = yb_autovacate.GetBool () ? (GetMaxClients () - 1 - (humanNumber + 1)) : GetMaxClients (); + + // check valid range of quota + if (yb_quota.GetInt () > botQuota) + yb_quota.SetInt (botQuota); + + else if (yb_quota.GetInt () < 0) + yb_quota.SetInt (0); + + m_maintainTime = GetWorldTime () + 0.15f; + } +} + +void BotManager::InitQuota (void) +{ + m_maintainTime = GetWorldTime () + 1.5f; + m_creationTab.RemoveAll (); +} + +void BotManager::FillServer (int selection, int personality, int skill, int numToAdd) +{ + // this function fill server with bots, with specified team & personality + + if (GetBotsNum () >= GetMaxClients () - GetHumansNum ()) + return; + + if (selection == 1 || selection == 2) + { + CVAR_SET_STRING ("mp_limitteams", "0"); + CVAR_SET_STRING ("mp_autoteambalance", "0"); + } + else + selection = 5; + + char teamDesc[6][12] = + { + "", + {"Terrorists"}, + {"CTs"}, + "", + "", + {"Random"}, + }; + + int toAdd = numToAdd == -1 ? GetMaxClients () - (GetHumansNum () + GetBotsNum ()) : numToAdd; + + for (int i = 0; i <= toAdd; i++) + { + // since we got constant skill from menu (since creation process call automatic), we need to manually + // randomize skill here, on given skill there. + int randomizedSkill = 0; + + if (skill >= 0 && skill <= 20) + randomizedSkill = g_randGen.Long (0, 20); + else if (skill > 20 && skill <= 40) + randomizedSkill = g_randGen.Long (20, 40); + else if (skill > 40 && skill <= 60) + randomizedSkill = g_randGen.Long (40, 60); + else if (skill > 60 && skill <= 80) + randomizedSkill = g_randGen.Long (60, 80); + else if (skill > 80 && skill <= 99) + randomizedSkill = g_randGen.Long (80, 99); + else if (skill == 100) + randomizedSkill = skill; + + AddBot ("", randomizedSkill, personality, selection, -1); + } + + yb_quota.SetInt (toAdd); + yb_quota_match.SetInt (0); + + CenterPrint ("Fill Server with %s bots...", &teamDesc[selection][0]); +} + +void BotManager::RemoveAll (bool zeroQuota) +{ + // this function drops all bot clients from server (this function removes only yapb's)`q + + if (zeroQuota) + CenterPrint ("Bots are removed from server."); + + for (int i = 0; i < GetMaxClients (); i++) + { + if (m_bots[i] != NULL) // is this slot used? + m_bots[i]->Kick (); + } + m_creationTab.RemoveAll (); + + // reset cvars + if (zeroQuota) + { + yb_quota.SetInt (0); + yb_autovacate.SetInt (0); + } +} + +void BotManager::RemoveFromTeam (Team team, bool removeAll) +{ + // this function remove random bot from specified team (if removeAll value = 1 then removes all players from team) + + for (int i = 0; i < GetMaxClients (); i++) + { + if (m_bots[i] != NULL && team == GetTeam (m_bots[i]->GetEntity ())) + { + m_bots[i]->Kick (); + + if (!removeAll) + break; + } + } +} + +void BotManager::RemoveMenu (edict_t *ent, int selection) +{ + // this function displays remove bot menu to specified entity (this function show's only yapb's). + + + if (selection > 4 || selection < 1) + return; + + static char tempBuffer[1024]; + char buffer[1024]; + + memset (tempBuffer, 0, sizeof (tempBuffer)); + memset (buffer, 0, sizeof (buffer)); + + int validSlots = (selection == 4) ? (1 << 9) : ((1 << 8) | (1 << 9)); + + for (int i = ((selection - 1) * 8); i < selection * 8; i++) + { + if ((m_bots[i] != NULL) && !FNullEnt (m_bots[i]->GetEntity ())) + { + validSlots |= 1 << (i - ((selection - 1) * 8)); + sprintf (buffer, "%s %1.1d. %s%s\n", buffer, i - ((selection - 1) * 8) + 1, STRING (m_bots[i]->pev->netname), GetTeam (m_bots[i]->GetEntity ()) == TEAM_CF ? " \\y(CT)\\w" : " \\r(T)\\w"); + } + else + sprintf (buffer, "%s\\d %1.1d. Not a YaPB\\w\n", buffer, i - ((selection - 1) * 8) + 1); + } + + sprintf (tempBuffer, "\\yYaPB Remove Menu (%d/4):\\w\n\n%s\n%s 0. Back", selection, buffer, (selection == 4) ? "" : " 9. More...\n"); + + switch (selection) + { + case 1: + g_menus[14].validSlots = validSlots & static_cast (-1); + g_menus[14].menuText = tempBuffer; + + DisplayMenuToClient (ent, &g_menus[14]); + break; + + case 2: + g_menus[15].validSlots = validSlots & static_cast (-1); + g_menus[15].menuText = tempBuffer; + + DisplayMenuToClient (ent, &g_menus[15]); + break; + + case 3: + g_menus[16].validSlots = validSlots & static_cast (-1); + g_menus[16].menuText = tempBuffer; + + DisplayMenuToClient (ent, &g_menus[16]); + break; + + case 4: + g_menus[17].validSlots = validSlots & static_cast (-1); + g_menus[17].menuText = tempBuffer; + + DisplayMenuToClient (ent, &g_menus[17]); + break; + } +} + +void BotManager::KillAll (int team) +{ + // this function kills all bots on server (only this dll controlled bots) + + for (int i = 0; i < GetMaxClients (); i++) + { + if (m_bots[i] != NULL) + { + if (team != -1 && team != GetTeam (m_bots[i]->GetEntity ())) + continue; + + m_bots[i]->Kill (); + } + } + CenterPrint ("All Bots died !"); +} + +void BotManager::RemoveRandom (void) +{ + // this function removes random bot from server (only yapb's) + + for (int i = 0; i < GetMaxClients (); i++) + { + if (m_bots[i] != NULL) // is this slot used? + { + m_bots[i]->Kick (); + break; + } + } +} + +void BotManager::SetWeaponMode (int selection) +{ + // this function sets bots weapon mode + + int tabMapStandart[7][NUM_WEAPONS] = + { + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, // Knife only + {-1,-1,-1, 2, 2, 0, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, // Pistols only + {-1,-1,-1,-1,-1,-1,-1, 2, 2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, // Shotgun only + {-1,-1,-1,-1,-1,-1,-1,-1,-1, 2, 1, 2, 0, 2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 2,-1}, // Machine Guns only + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 0, 0, 1, 0, 1, 1,-1,-1,-1,-1,-1,-1}, // Rifles only + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 2, 2, 0, 1,-1,-1}, // Snipers only + {-1,-1,-1, 2, 2, 0, 1, 2, 2, 2, 1, 2, 0, 2, 0, 0, 1, 0, 1, 1, 2, 2, 0, 1, 2, 1} // Standard + }; + + int tabMapAS[7][NUM_WEAPONS] = + { + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, // Knife only + {-1,-1,-1, 2, 2, 0, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, // Pistols only + {-1,-1,-1,-1,-1,-1,-1, 1, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, // Shotgun only + {-1,-1,-1,-1,-1,-1,-1,-1,-1, 1, 1, 1, 0, 2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 1,-1}, // Machine Guns only + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 0,-1, 1, 0, 1, 1,-1,-1,-1,-1,-1,-1}, // Rifles only + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 0, 0,-1, 1,-1,-1}, // Snipers only + {-1,-1,-1, 2, 2, 0, 1, 1, 1, 1, 1, 1, 0, 2, 0,-1, 1, 0, 1, 1, 0, 0,-1, 1, 1, 1} // Standard + }; + + char modeName[7][12] = + { + {"Knife"}, + {"Pistol"}, + {"Shotgun"}, + {"Machine Gun"}, + {"Rifle"}, + {"Sniper"}, + {"Standard"} + }; + selection--; + + for (int i = 0; i < NUM_WEAPONS; i++) + { + g_weaponSelect[i].teamStandard = tabMapStandart[selection][i]; + g_weaponSelect[i].teamAS = tabMapAS[selection][i]; + } + + if (selection == 0) + yb_jasonmode.SetInt (1); + else + yb_jasonmode.SetInt (0); + + CenterPrint ("%s weapon mode selected", &modeName[selection][0]); +} + +void BotManager::ListBots (void) +{ + // this function list's bots currently playing on the server + + ServerPrintNoTag ("%-3.5s %-9.13s %-17.18s %-3.4s %-3.4s %-3.4s", "index", "name", "personality", "team", "skill", "frags"); + + for (int i = 0; i < GetMaxClients (); i++) + { + edict_t *player = INDEXENT (i); + + // is this player slot valid + if (IsValidBot (player)) + { + Bot *bot = GetBot (player); + + if (bot != NULL) + ServerPrintNoTag ("[%-3.1d] %-9.13s %-17.18s %-3.4s %-3.1d %-3.1d", i, STRING (player->v.netname), bot->m_personality == PERSONALITY_RUSHER ? "rusher" : bot->m_personality == PERSONALITY_NORMAL ? "normal" : "careful", GetTeam (player) != 0 ? "CT" : "T", bot->m_skill, static_cast (player->v.frags)); + } + } +} + +int BotManager::GetBotsNum (void) +{ + // this function returns number of yapb's playing on the server + + int count = 0; + + for (int i = 0; i < GetMaxClients (); i++) + { + if (m_bots[i] != NULL) + count++; + } + return count; +} + +Bot *BotManager::GetHighestFragsBot (int team) +{ + Bot *highFragBot = NULL; + + int bestIndex = 0; + float bestScore = -1; + + // search bots in this team + for (int i = 0; i < GetMaxClients (); i++) + { + highFragBot = g_botManager->GetBot (i); + + if (highFragBot != NULL && IsAlive (highFragBot->GetEntity ()) && GetTeam (highFragBot->GetEntity ()) == team) + { + if (highFragBot->pev->frags > bestScore) + { + bestIndex = i; + bestScore = highFragBot->pev->frags; + } + } + } + return GetBot (bestIndex); +} + +void BotManager::CheckTeamEconomics (int team) +{ + // this function decides is players on specified team is able to buy primary weapons by calculating players + // that have not enough money to buy primary (with economics), and if this result higher 80%, player is can't + // buy primary weapons. + + extern ConVar yb_economics_rounds; + + if (!yb_economics_rounds.GetBool ()) + { + m_economicsGood[team] = true; + return; // don't check economics while economics disable + } + + int numPoorPlayers = 0; + int numTeamPlayers = 0; + + // start calculating + for (int i = 0; i < GetMaxClients (); i++) + { + if (m_bots[i] != NULL && GetTeam (m_bots[i]->GetEntity ()) == team) + { + if (m_bots[i]->m_moneyAmount <= g_botBuyEconomyTable[0]) + numPoorPlayers++; + + numTeamPlayers++; // update count of team + } + } + m_economicsGood[team] = true; + + if (numTeamPlayers <= 1) + return; + + // if 80 percent of team have no enough money to purchase primary weapon + if ((numTeamPlayers * 80) / 100 <= numPoorPlayers) + m_economicsGood[team] = false; + + // winner must buy something! + if (m_lastWinner == team) + m_economicsGood[team] = true; +} + +void BotManager::Free (void) +{ + // this function free all bots slots (used on server shutdown) + + for (int i = 0; i < 32; i++) + { + if (m_bots[i] != NULL) + Free (i); + } +} + +void BotManager::Free (int index) +{ + // this function frees one bot selected by index (used on bot disconnect) + + m_bots[index]->SwitchChatterIcon (false); + m_bots[index]->ReleaseUsedName (); + + delete m_bots[index]; + m_bots[index] = NULL; +} + +Bot::Bot (edict_t *bot, int skill, int personality, int team, int member) +{ + // this function does core operation of creating bot, it's called by CreateBot (), + // when bot setup completed, (this is a bot class constructor) + + char rejectReason[128]; + int clientIndex = ENTINDEX (bot); + + memset (this, 0, sizeof (Bot)); + + pev = VARS (bot); + + if (bot->pvPrivateData != NULL) + FREE_PRIVATE (bot); + + bot->pvPrivateData = NULL; + bot->v.frags = 0; + + // create the player entity by calling MOD's player function + BotManager::CallGameEntity (&bot->v); + + // set all info buffer keys for this bot + char *buffer = GET_INFOKEYBUFFER (bot); + + SET_CLIENT_KEYVALUE (clientIndex, buffer, "model", ""); + SET_CLIENT_KEYVALUE (clientIndex, buffer, "rate", "3500.000000"); + SET_CLIENT_KEYVALUE (clientIndex, buffer, "cl_updaterate", "20"); + SET_CLIENT_KEYVALUE (clientIndex, buffer, "cl_lw", "0"); + SET_CLIENT_KEYVALUE (clientIndex, buffer, "cl_lc", "0"); + SET_CLIENT_KEYVALUE (clientIndex, buffer, "tracker", "0"); + SET_CLIENT_KEYVALUE (clientIndex, buffer, "cl_dlmax", "128"); + SET_CLIENT_KEYVALUE (clientIndex, buffer, "friends", "0"); + SET_CLIENT_KEYVALUE (clientIndex, buffer, "dm", "0"); + SET_CLIENT_KEYVALUE (clientIndex, buffer, "_ah", "0"); + SET_CLIENT_KEYVALUE (clientIndex, buffer, "_vgui_menus", "0"); + + if (g_gameVersion != CSV_OLD && yb_latency_display.GetInt () == 1) + SET_CLIENT_KEYVALUE (clientIndex, buffer, "*bot", "1"); + + rejectReason[0] = 0; // reset the reject reason template string + MDLL_ClientConnect (bot, "BOT", FormatBuffer ("127.0.0.%d", ENTINDEX (bot) + 100), rejectReason); + + if (!IsNullString (rejectReason)) + { + AddLogEntry (true, LL_WARNING, "Server refused '%s' connection (%s)", STRING (bot->v.netname), rejectReason); + ServerCommand ("kick \"%s\"", STRING (bot->v.netname)); // kick the bot player if the server refused it + + bot->v.flags |= FL_KILLME; + } + + MDLL_ClientPutInServer (bot); + bot->v.flags |= FL_FAKECLIENT; // set this player as fakeclient + + // initialize all the variables for this bot... + m_notStarted = true; // hasn't joined game yet + + m_startAction = GSM_IDLE; + m_moneyAmount = 0; + + m_logotypeIndex = g_randGen.Long (0, 5); + m_msecVal = static_cast (g_pGlobals->frametime * 1000.0); + + // assign how talkative this bot will be + m_sayTextBuffer.chatDelay = g_randGen.Float (3.8, 10.0); + m_sayTextBuffer.chatProbability = g_randGen.Long (1, 100); + + m_notKilled = false; + m_skill = skill; + m_weaponBurstMode = BM_OFF; + + m_lastThinkTime = GetWorldTime () - 0.1f; + m_frameInterval = GetWorldTime (); + + bot->v.idealpitch = bot->v.v_angle.x; + bot->v.ideal_yaw = bot->v.v_angle.y; + + bot->v.yaw_speed = g_randGen.Float (g_skillTab[m_skill / 20].minTurnSpeed, g_skillTab[m_skill / 20].maxTurnSpeed); + bot->v.pitch_speed = g_randGen.Float (g_skillTab[m_skill / 20].minTurnSpeed, g_skillTab[m_skill / 20].maxTurnSpeed); + + switch (personality) + { + case 1: + m_personality = PERSONALITY_RUSHER; + m_baseAgressionLevel = g_randGen.Float (0.7, 1.0); + m_baseFearLevel = g_randGen.Float (0.0, 0.4); + break; + + case 2: + m_personality = PERSONALITY_CAREFUL; + m_baseAgressionLevel = g_randGen.Float (0.2, 0.5); + m_baseFearLevel = g_randGen.Float (0.7, 1.0); + break; + + default: + m_personality = PERSONALITY_NORMAL; + m_baseAgressionLevel = g_randGen.Float (0.4, 0.7); + m_baseFearLevel = g_randGen.Float (0.4, 0.7); + break; + } + + memset (&m_ammoInClip, 0, sizeof (m_ammoInClip)); + memset (&m_ammo, 0, sizeof (m_ammo)); + + m_currentWeapon = 0; // current weapon is not assigned at start + m_voicePitch = g_randGen.Long (166, 250) / 2; // assign voice pitch + + // copy them over to the temp level variables + m_agressionLevel = m_baseAgressionLevel; + m_fearLevel = m_baseFearLevel; + m_nextEmotionUpdate = GetWorldTime () + 0.5; + + // just to be sure + m_actMessageIndex = 0; + m_pushMessageIndex = 0; + + // assign team and class + m_wantedTeam = team; + m_wantedClass = member; + + NewRound (); +} + +void Bot::ReleaseUsedName (void) +{ + IterateArray (g_botNames, j) + { + BotName &name = g_botNames[j]; + + if (strcmp (name.name, STRING (pev->netname)) == 0) + { + name.used = false; + break; + } + } +} + +Bot::~Bot (void) +{ + // this is bot destructor + + DeleteSearchNodes (); + ResetTasks (); +} + +int BotManager::GetHumansNum (void) +{ + // this function returns number of humans playing on the server + + int count = 0; + + for (int i = 0; i < GetMaxClients (); i++) + { + Client *cl = &g_clients[i]; + + if ((cl->flags & CF_USED) && m_bots[i] == NULL && !(cl->ent->v.flags & FL_FAKECLIENT)) + count++; + } + return count; +} + +int BotManager::GetHumansAliveNum (void) +{ + // this function returns number of humans playing on the server + + int count = 0; + + for (int i = 0; i < GetMaxClients (); i++) + { + Client *cl = &g_clients[i]; + + if ((cl->flags & (CF_USED | CF_ALIVE)) && m_bots[i] == NULL && !(cl->ent->v.flags & FL_FAKECLIENT)) + count++; + } + return count; +} + +int BotManager::GetHumansJoinedTeam (void) +{ + // this function returns number of humans playing on the server + + int count = 0; + + for (int i = 0; i < GetMaxClients (); i++) + { + Client *cl = &g_clients[i]; + + if ((cl->flags & (CF_USED | CF_ALIVE)) && m_bots[i] == NULL && cl->team != TEAM_SPEC && !(cl->ent->v.flags & FL_FAKECLIENT) && cl->ent->v.movetype != MOVETYPE_FLY) + count++; + } + return count; +} + +void Bot::NewRound (void) +{ + // this function initializes a bot after creation & at the start of each round + + g_canSayBombPlanted = true; + int i = 0; + + + // delete all allocated path nodes + DeleteSearchNodes (); + + m_waypointOrigin = nullvec; + m_destOrigin = nullvec; + m_currentWaypointIndex = -1; + m_currentPath = NULL; + m_currentTravelFlags = 0; + m_desiredVelocity = nullvec; + m_prevGoalIndex = -1; + m_chosenGoalIndex = -1; + m_loosedBombWptIndex = -1; + + m_moveToC4 = false; + m_duckDefuse = false; + m_duckDefuseCheckTime = 0.0; + + m_prevWptIndex[0] = -1; + m_prevWptIndex[1] = -1; + m_prevWptIndex[2] = -1; + m_prevWptIndex[3] = -1; + m_prevWptIndex[4] = -1; + + m_navTimeset = GetWorldTime (); + m_team = GetTeam (GetEntity ()); + + switch (m_personality) + { + case PERSONALITY_NORMAL: + m_pathType = g_randGen.Long (0, 100) > 50 ? 1 : 2; + break; + + case PERSONALITY_RUSHER: + m_pathType = 0; + break; + + case PERSONALITY_CAREFUL: + m_pathType = 2; + break; + } + + // clear all states & tasks + m_states = 0; + ResetTasks (); + + m_isVIP = false; + m_isLeader = false; + m_hasProgressBar = false; + m_canChooseAimDirection = true; + + m_timeTeamOrder = 0.0; + m_askCheckTime = 0.0; + m_minSpeed = 260.0; + m_prevSpeed = 0.0; + m_prevOrigin = Vector (9999.0, 9999.0, 9999.0); + m_prevTime = GetWorldTime (); + m_blindRecognizeTime = GetWorldTime (); + + m_viewDistance = 4096.0; + m_maxViewDistance = 4096.0; + + m_liftEntity = NULL; + m_pickupItem = NULL; + m_itemIgnore = NULL; + m_itemCheckTime = 0.0; + + m_breakableEntity = NULL; + m_breakable = nullvec; + m_timeDoorOpen = 0.0; + + ResetCollideState (); + ResetDoubleJumpState (); + + m_enemy = NULL; + m_lastVictim = NULL; + m_lastEnemy = NULL; + m_lastEnemyOrigin = nullvec; + m_trackingEdict = NULL; + m_timeNextTracking = 0.0; + + m_buttonPushTime = 0.0; + m_enemyUpdateTime = 0.0; + m_seeEnemyTime = 0.0; + m_shootAtDeadTime = 0.0; + m_oldCombatDesire = 0.0; + m_liftUsageTime = 0.0; + + m_avoidGrenade = NULL; + m_needAvoidGrenade = 0; + + m_lastDamageType = -1; + m_voteKickIndex = 0; + m_lastVoteKick = 0; + m_voteMap = 0; + m_doorOpenAttempt = 0; + m_aimFlags = 0; + m_liftState = 0; + m_burstShotsFired = 0; + + m_position = nullvec; + m_liftTravelPos = nullvec; + + m_idealReactionTime = g_skillTab[m_skill / 20].minSurpriseTime; + m_actualReactionTime = g_skillTab[m_skill / 20].minSurpriseTime; + + m_targetEntity = NULL; + m_tasks = NULL; + m_followWaitTime = 0.0; + + for (i = 0; i < MAX_HOSTAGES; i++) + m_hostages[i] = NULL; + + for (i = 0; i < Chatter_Total; i++) + m_voiceTimers[i] = -1.0; + + m_isReloading = false; + m_reloadState = RELOAD_NONE; + + m_reloadCheckTime = 0.0; + m_shootTime = GetWorldTime (); + m_playerTargetTime = GetWorldTime (); + m_firePause = 0.0; + m_timeLastFired = 0.0; + + m_grenadeCheckTime = 0.0; + m_isUsingGrenade = false; + + m_skillOffset = static_cast ((100 - m_skill) / 100.0); + m_blindButton = 0; + m_blindTime = 0.0; + m_jumpTime = 0.0; + m_jumpFinished = false; + m_isStuck = false; + + m_sayTextBuffer.timeNextChat = GetWorldTime (); + m_sayTextBuffer.entityIndex = -1; + m_sayTextBuffer.sayText[0] = 0x0; + + m_buyState = 0; + + if (!m_notKilled) // if bot died, clear all weapon stuff and force buying again + { + memset (&m_ammoInClip, 0, sizeof (m_ammoInClip)); + memset (&m_ammo, 0, sizeof (m_ammo)); + + m_currentWeapon = 0; + } + + m_knifeAttackTime = GetWorldTime () + g_randGen.Float (1.3, 2.6); + m_nextBuyTime = GetWorldTime () + g_randGen.Float (0.6, 1.2); + + m_buyPending = false; + m_inBombZone = false; + + m_shieldCheckTime = 0.0; + m_zoomCheckTime = 0.0; + m_strafeSetTime = 0.0; + m_combatStrafeDir = 0; + m_fightStyle = 0; + m_lastFightStyleCheck = 0.0; + + m_checkWeaponSwitch = true; + m_checkKnifeSwitch = true; + m_buyingFinished = false; + + m_radioEntity = NULL; + m_radioOrder = 0; + m_defendedBomb = false; + + m_timeLogoSpray = GetWorldTime () + g_randGen.Float (0.5, 2.0); + m_spawnTime = GetWorldTime (); + m_lastChatTime = GetWorldTime (); + pev->v_angle.y = pev->ideal_yaw; + + m_timeCamping = 0; + m_campDirection = 0; + m_nextCampDirTime = 0; + m_campButtons = 0; + + m_soundUpdateTime = 0.0; + m_heardSoundTime = GetWorldTime (); + + // clear its message queue + for (i = 0; i < 32; i++) + m_messageQueue[i] = GSM_IDLE; + + m_actMessageIndex = 0; + m_pushMessageIndex = 0; + + // and put buying into its message queue + PushMessageQueue (GSM_BUY_STUFF); + StartTask (TASK_NORMAL, TASKPRI_NORMAL, -1, 0.0, true); + + if (g_randGen.Long (0, 100) < 50) + ChatterMessage (Chatter_NewRound); +} + +void Bot::Kill (void) +{ + // this function kills a bot (not just using ClientKill, but like the CSBot does) + // base code courtesy of Lazy (from bots-united forums!) + + edict_t *hurtEntity = (*g_engfuncs.pfnCreateNamedEntity) (MAKE_STRING ("trigger_hurt")); + + if (FNullEnt (hurtEntity)) + return; + + hurtEntity->v.classname = MAKE_STRING (g_weaponDefs[m_currentWeapon].className); + hurtEntity->v.dmg_inflictor = GetEntity (); + hurtEntity->v.dmg = 9999.0; + hurtEntity->v.dmg_take = 1.0; + hurtEntity->v.dmgtime = 2.0; + hurtEntity->v.effects |= EF_NODRAW; + + (*g_engfuncs.pfnSetOrigin) (hurtEntity, Vector (-4000, -4000, -4000)); + + KeyValueData kv; + kv.szClassName = const_cast (g_weaponDefs[m_currentWeapon].className); + kv.szKeyName = "damagetype"; + kv.szValue = FormatBuffer ("%d", (1 << 4)); + kv.fHandled = FALSE; + + MDLL_KeyValue (hurtEntity, &kv); + + MDLL_Spawn (hurtEntity); + MDLL_Touch (hurtEntity, GetEntity ()); + + (*g_engfuncs.pfnRemoveEntity) (hurtEntity); +} + +void Bot::Kick (void) +{ + // this function kick off one bot from the server. + + ServerCommand ("kick #%d", GETPLAYERUSERID (GetEntity ())); + CenterPrint ("Bot '%s' kicked", STRING (pev->netname)); + + // balances quota + if (g_botManager->GetBotsNum () - 1 < yb_quota.GetInt ()) + yb_quota.SetInt (g_botManager->GetBotsNum () - 1); +} + +void Bot::StartGame (void) +{ + // this function handles the selection of teams & class + + // handle counter-strike stuff here... + if (m_startAction == GSM_TEAM_SELECT) + { + m_startAction = GSM_IDLE; // switch back to idle + + if (yb_join_team.GetString ()[0] == 'C' || yb_join_team.GetString ()[0] == 'c') + m_wantedTeam = 2; + else if (yb_join_team.GetString ()[0] == 'T' || yb_join_team.GetString ()[0] == 't') + m_wantedTeam = 1; + + if (m_wantedTeam != 1 && m_wantedTeam != 2) + m_wantedTeam = 5; + + // select the team the bot wishes to join... + FakeClientCommand (GetEntity (), "menuselect %d", m_wantedTeam); + } + else if (m_startAction == GSM_CLASS_SELECT) + { + m_startAction = GSM_IDLE; // switch back to idle + + if (g_gameVersion == CSV_CZERO) // czero has spetsnaz and militia skins + { + if (m_wantedClass < 1 || m_wantedClass > 5) + m_wantedClass = g_randGen.Long (1, 5); // use random if invalid + } + else + { + if (m_wantedClass < 1 || m_wantedClass > 4) + m_wantedClass = g_randGen.Long (1, 4); // use random if invalid + } + + // select the class the bot wishes to use... + FakeClientCommand (GetEntity (), "menuselect %d", m_wantedClass); + + // bot has now joined the game (doesn't need to be started) + m_notStarted = false; + + // check for greeting other players, since we connected + if (g_randGen.Long (0, 100) < 20) + ChatMessage (CHAT_WELCOME); + } +} + +void BotManager::CalculatePingOffsets (void) +{ + if (yb_latency_display.GetInt () != 2) + return; + + int averagePing = 0; + int numHumans = 0; + + for (int i = 0; i < GetMaxClients (); i++) + { + edict_t *ent = INDEXENT (i + 1); + + if (!IsValidPlayer (ent)) + continue; + + numHumans++; + + int ping, loss; + PLAYER_CNX_STATS (ent, &ping, &loss); + + if (ping < 0 || ping > 100) + ping = g_randGen.Long (3, 15); + + averagePing += ping; + } + + if (numHumans > 0) + averagePing /= numHumans; + else + averagePing = g_randGen.Long (30, 40); + + for (int i = 0; i < GetMaxClients (); i++) + { + Bot *bot = GetBot (i); + + if (bot == NULL) + continue; + + int botPing = g_randGen.Long (averagePing - averagePing * 0.2f, averagePing + averagePing * 0.2f) + g_randGen.Long (bot->m_skill / 100 * 0.5, bot->m_skill / 100); + + if (botPing <= 5) + botPing = g_randGen.Long (10, 23); + else if (botPing > 100) + botPing = g_randGen.Long (30, 40); + + for (int j = 0; j < 2; j++) + { + for (bot->m_pingOffset[j] = 0; bot->m_pingOffset[j] < 4; bot->m_pingOffset[j]++) + { + if ((botPing - bot->m_pingOffset[j]) % 4 == 0) + { + bot->m_ping[j] = (botPing - bot->m_pingOffset[j]) / 4; + break; + } + } + } + bot->m_ping[2] = botPing; + } +} + +void BotManager::SendPingDataOffsets (edict_t *to) +{ + if (yb_latency_display.GetInt () != 2) + return; + + if (!IsValidPlayer (to) || IsValidBot (to)) + return; + + if (!((to->v.button & IN_SCORE) || (to->v.oldbuttons & IN_SCORE))) + return; + + static int sending; + sending = 0; + + // missing from sdk + static const int SVC_PINGS = 17; + + for (int i = 0; i < GetMaxClients (); i++) + { + Bot *bot = GetBot (i); + + if (bot == NULL) + continue; + + switch (sending) + { + case 0: + { + // start a new message + MESSAGE_BEGIN (MSG_ONE_UNRELIABLE, SVC_PINGS, NULL, to); + WRITE_BYTE ((m_bots[i]->m_pingOffset[sending] * 64) + (1 + 2 * i)); + WRITE_SHORT (m_bots[i]->m_ping[sending]); + + sending++; + } + case 1: + { + // append additional data + WRITE_BYTE ((m_bots[i]->m_pingOffset[sending] * 128) + (2 + 4 * i)); + WRITE_SHORT (m_bots[i]->m_ping[sending]); + + sending++; + } + case 2: + { + // append additional data and end message + WRITE_BYTE (4 + 8 * i); + WRITE_SHORT (m_bots[i]->m_ping[sending]); + WRITE_BYTE (0); + MESSAGE_END (); + + sending = 0; + } + } + } + + // end message if not yet sent + if (sending) + { + WRITE_BYTE (0); + MESSAGE_END (); + } +} + +void BotManager::SendDeathMsgFix (void) +{ + if (yb_latency_display.GetInt () == 2 && m_deathMsgSent) + { + m_deathMsgSent = false; + + for (int i = 0; i < GetMaxClients (); i++) + SendPingDataOffsets (g_clients[i].ent); + } +} \ No newline at end of file