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