diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 3debbcd3..3eea8d8c 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -1,5 +1,8 @@ #include +#include + #include "Bots.hpp" +#include "ServerList.hpp" #include "GSC/Script.hpp" @@ -11,7 +14,7 @@ namespace Components { std::vector Bots::BotNames; - Dvar::Var Bots::SVRandomBotNames; + Dvar::Var Bots::SVClanName; struct BotMovementInfo { @@ -49,72 +52,112 @@ namespace Components { "activate", Game::CMD_BUTTON_ACTIVATE }, }; + void Bots::RandomizeBotNames() + { + std::random_device rd; + std::mt19937 gen(rd()); + std::ranges::shuffle(BotNames, gen); + } + + void Bots::UpdateBotNames() + { + const auto masterPort = (*Game::com_masterPort)->current.integer; + const auto* masterServerName = (*Game::com_masterServerName)->current.string; + + Game::netadr_t master; + if (ServerList::GetMasterServer(masterServerName, masterPort, master)) + { + Logger::Print("Getting bots...\n"); + Network::Send(master, "getbots"); + } + } + + void Bots::LoadBotNames() + { + FileSystem::File bots("bots.txt"); + + if (!bots.exists()) + { + return; + } + + auto data = Utils::String::Split(bots.getBuffer(), '\n'); + + auto i = 0; + for (auto& entry : data) + { + if (i >= 18) + { + // Only parse 18 names from the file + break; + } + + // Take into account for CR line endings + Utils::String::Replace(entry, "\r", ""); + // Remove whitespace + Utils::String::Trim(entry); + + if (entry.empty()) + { + continue; + } + + std::string clanAbbrev; + + // Check if there is a clan tag + if (const auto pos = entry.find(','); pos != std::string::npos) + { + // Only start copying over from non-null characters (otherwise it can be "<=") + if ((pos + 1) < entry.size()) + { + clanAbbrev = entry.substr(pos + 1); + } + + entry = entry.substr(0, pos); + } + + BotNames.emplace_back(std::make_pair(entry, clanAbbrev)); + ++i; + } + + if (i) + { + RandomizeBotNames(); + } + } + int Bots::BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port) { static size_t botId = 0; // Loop over the BotNames vector static bool loadedNames = false; // Load file only once - const char* botName; - const char* clanName; + std::string botName; + std::string clanName; - if (BotNames.empty() && !loadedNames) + if (!loadedNames) { - FileSystem::File bots("bots.txt"); loadedNames = true; - - if (bots.exists()) - { - auto data = Utils::String::Split(bots.getBuffer(), '\n'); - - if (SVRandomBotNames.get()) - { - std::random_device rd; - std::mt19937 gen(rd()); - std::ranges::shuffle(data, gen); - } - - for (auto& entry : data) - { - // Take into account for CR line endings - Utils::String::Replace(entry, "\r", ""); - // Remove whitespace - Utils::String::Trim(entry); - - if (!entry.empty()) - { - std::string clanAbbrev; - - // Check if there is a clan tag - if (const auto pos = entry.find(','); pos != std::string::npos) - { - // Only start copying over from non-null characters (otherwise it can be "<=") - if ((pos + 1) < entry.size()) - { - clanAbbrev = entry.substr(pos + 1); - } - - entry = entry.substr(0, pos); - } - - BotNames.emplace_back(std::make_pair(entry, clanAbbrev)); - } - } - } + LoadBotNames(); } if (!BotNames.empty()) { botId %= BotNames.size(); const auto index = botId++; - botName = BotNames[index].first.data(); - clanName = BotNames[index].second.data(); + botName = BotNames[index].first; + clanName = BotNames[index].second; } else { - botName = Utils::String::VA("bot%d", ++botId); - clanName = "BOT"; + botName = std::format("bot{}", ++botId); + clanName = "BOT"s; } - return _snprintf_s(buffer, 0x400, _TRUNCATE, connectString, num, botName, clanName, protocol, checksum, statVer, statStuff, port); + if (const auto svClanName = SVClanName.get(); !svClanName.empty()) + { + clanName = svClanName; + } + + return _snprintf_s(buffer, 0x400, _TRUNCATE, connectString, num, botName.data(), clanName.data(), protocol, checksum, statVer, statStuff, port); } void Bots::Spawn(unsigned int count) @@ -352,7 +395,30 @@ namespace Components Utils::Hook(0x441B80, G_SelectWeaponIndex_Hk, HOOK_JUMP).install()->quick(); - SVRandomBotNames = Dvar::Register("sv_randomBotNames", false, Game::DVAR_NONE, "Randomize the bots' names"); + Events::OnDvarInit([] + { + SVClanName = Dvar::Register("sv_clanName", "", Game::DVAR_NONE, "The clan name for test clients"); + }); + + Scheduler::OnGameInitialized(UpdateBotNames, Scheduler::Pipeline::MAIN); + + Network::OnClientPacket("getbotsResponse", [](const Network::Address& address, const std::string& data) + { + const auto masterPort = (*Game::com_masterPort)->current.integer; + const auto* masterServerName = (*Game::com_masterServerName)->current.string; + + Network::Address master(Utils::String::VA("%s:%u", masterServerName, masterPort)); + if (master == address) + { + auto botNames = Utils::String::Split(data, '\n'); + for (const auto& entry : botNames) + { + BotNames.emplace_back(std::make_pair(entry, "BOT")); + } + + RandomizeBotNames(); + } + }); // Reset BotMovementInfo.active when client is dropped Events::OnClientDisconnect([](const int clientNum) diff --git a/src/Components/Modules/Bots.hpp b/src/Components/Modules/Bots.hpp index f47f7a9a..efa4d3a7 100644 --- a/src/Components/Modules/Bots.hpp +++ b/src/Components/Modules/Bots.hpp @@ -11,8 +11,11 @@ namespace Components using botData = std::pair< std::string, std::string>; static std::vector BotNames; - static Dvar::Var SVRandomBotNames; + static Dvar::Var SVClanName; + static void RandomizeBotNames(); + static void UpdateBotNames(); + static void LoadBotNames(); static int BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port); static void Spawn(unsigned int count); diff --git a/src/Components/Modules/Dedicated.cpp b/src/Components/Modules/Dedicated.cpp index 3fac583c..51291f79 100644 --- a/src/Components/Modules/Dedicated.cpp +++ b/src/Components/Modules/Dedicated.cpp @@ -165,8 +165,8 @@ namespace Components return; } - auto masterPort = Dvar::Var("masterPort").get(); - const auto* masterServerName = Dvar::Var("masterServerName").get(); + const auto masterPort = (*Game::com_masterPort)->current.integer; + const auto* masterServerName = (*Game::com_masterServerName)->current.string; Network::Address master(Utils::String::VA("%s:%u", masterServerName, masterPort)); diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index 8005f87b..4f81fa43 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -299,8 +299,8 @@ namespace Components } else if (IsOnlineList()) { - const auto masterPort = Dvar::Var("masterPort").get(); - const auto masterServerName = Dvar::Var("masterServerName").get(); + const auto masterPort = (*Game::com_masterPort)->current.integer; + const auto* masterServerName = (*Game::com_masterServerName)->current.string; // Check if our dvars can properly convert to a address Game::netadr_t masterServerAddr; diff --git a/src/Game/Dvars.cpp b/src/Game/Dvars.cpp index f3caaa44..827ad8d6 100644 --- a/src/Game/Dvars.cpp +++ b/src/Game/Dvars.cpp @@ -37,6 +37,8 @@ namespace Game const dvar_t** com_timescale = reinterpret_cast(0x1AD7920); const dvar_t** com_maxFrameTime = reinterpret_cast(0x1AD78F4); const dvar_t** com_sv_running = reinterpret_cast(0x1AD7934); + const dvar_t** com_masterServerName = reinterpret_cast(0x1AD8F48); + const dvar_t** com_masterPort = reinterpret_cast(0x1AD8F30); const dvar_t** dev_timescale = reinterpret_cast(0x1AD8F20); diff --git a/src/Game/Dvars.hpp b/src/Game/Dvars.hpp index a88f236d..95e78d08 100644 --- a/src/Game/Dvars.hpp +++ b/src/Game/Dvars.hpp @@ -89,6 +89,8 @@ namespace Game extern const dvar_t** com_timescale; extern const dvar_t** com_maxFrameTime; extern const dvar_t** com_sv_running; + extern const dvar_t** com_masterServerName; + extern const dvar_t** com_masterPort; extern const dvar_t** dev_timescale; diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 01d90307..9876c3fc 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -272,7 +272,7 @@ namespace Game typedef bool(*NET_CompareAdr_t)(netadr_t a, netadr_t b); extern NET_CompareAdr_t NET_CompareAdr; - typedef void(*NET_DeferPacketToClient_t)(netadr_t *, msg_t *); + typedef void(*NET_DeferPacketToClient_t)(netadr_t*, msg_t*); extern NET_DeferPacketToClient_t NET_DeferPacketToClient; typedef const char* (*NET_ErrorString_t)(); @@ -284,19 +284,19 @@ namespace Game typedef bool(*NET_IsLocalAddress_t)(netadr_t adr); extern NET_IsLocalAddress_t NET_IsLocalAddress; - typedef int(*NET_StringToAdr_t)(const char *s, netadr_t *a); + typedef int(*NET_StringToAdr_t)(const char* s, netadr_t* a); extern NET_StringToAdr_t NET_StringToAdr; - typedef void(*NET_OutOfBandPrint_t)(netsrc_t sock, netadr_t adr, const char *data); + typedef void(*NET_OutOfBandPrint_t)(netsrc_t sock, netadr_t adr, const char* data); extern NET_OutOfBandPrint_t NET_OutOfBandPrint; - typedef void(*NET_OutOfBandData_t)(netsrc_t sock, netadr_t adr, const char *format, int len); + typedef void(*NET_OutOfBandData_t)(netsrc_t sock, netadr_t adr, const char* format, int len); extern NET_OutOfBandData_t NET_OutOfBandData; typedef int(*NET_OutOfBandVoiceData_t)(netsrc_t sock, netadr_t adr, unsigned char* format, int len, bool voiceData); extern NET_OutOfBandVoiceData_t NET_OutOfBandVoiceData; - typedef void(*Live_MPAcceptInvite_t)(_XSESSION_INFO *hostInfo, const int controllerIndex, bool fromGameInvite); + typedef void(*Live_MPAcceptInvite_t)(_XSESSION_INFO *hostInfo, int controllerIndex, bool fromGameInvite); extern Live_MPAcceptInvite_t Live_MPAcceptInvite; typedef int(*Live_GetMapIndex_t)(const char* mapname);