#include #include "loader/component_loader.hpp" #include "game/game.hpp" #include "command.hpp" #include "console.hpp" #include "filesystem.hpp" #include "network.hpp" #include "party.hpp" #include "scheduler.hpp" #include "server_list.hpp" #include #include namespace bots { namespace { constexpr std::size_t MAX_NAME_LENGTH = 16; bool can_add() { return party::get_client_count() < game::mp::svs->clientCount; } void bot_team_join(const int entity_num) { // schedule the select team call scheduler::once([entity_num]() { game::SV_ExecuteClientCommand(&game::mp::svs->clients[entity_num], utils::string::va("lui 68 2 %i", *game::mp::sv_serverId_value), false); // scheduler the select class call scheduler::once([entity_num]() { game::SV_ExecuteClientCommand(&game::mp::svs->clients[entity_num], utils::string::va("lui 5 %i %i", (rand() % 5) + 10, *game::mp::sv_serverId_value), false); }, scheduler::pipeline::server, 1s); }, scheduler::pipeline::server, 1s); } void bot_team(const int entity_num) { if (game::SV_BotIsBot(game::mp::g_entities[entity_num].s.clientNum)) { if (game::mp::g_entities[entity_num].client->sess.cs.team == game::mp::team_t::TEAM_SPECTATOR) { bot_team_join(entity_num); } scheduler::once([entity_num]() { bot_team(entity_num); }, scheduler::pipeline::server, 3s); } } void spawn_bot(const int entity_num) { scheduler::once([entity_num]() { game::SV_SpawnTestClient(&game::mp::g_entities[entity_num]); bot_team(entity_num); }, scheduler::pipeline::server, 1s); } void add_bot() { if (!can_add()) { return; } const auto* bot_name = game::SV_BotGetRandomName(); const auto* bot_ent = game::SV_AddBot(bot_name, 26, 62, 0); if (bot_ent) { spawn_bot(bot_ent->s.number); } } utils::hook::detour get_bot_name_hook; volatile bool bot_names_received = false; std::vector bot_names; bool should_use_remote_bot_names() { #ifdef ALLOW_CUSTOM_BOT_NAMES return !filesystem::exists("bots.txt"); #else return true; #endif } void parse_bot_names_from_file() { std::string data; filesystem::read_file("bots.txt", &data); if (data.empty()) { return; } auto name_list = utils::string::split(data, '\n'); for (auto& entry : name_list) { // Take into account CR line endings entry = utils::string::replace(entry, "\r", ""); if (entry.empty()) { continue; } entry = entry.substr(0, MAX_NAME_LENGTH - 1); bot_names.emplace_back(entry); } } const char* get_random_bot_name() { if (!bot_names_received && bot_names.empty()) { // last attempt to use custom names if they can be found parse_bot_names_from_file(); } if (bot_names.empty()) { return get_bot_name_hook.invoke(); } const auto index = std::rand() % bot_names.size(); const auto& name = bot_names.at(index); return utils::string::va("%.*s", static_cast(name.size()), name.data()); } void update_bot_names() { bot_names_received = false; game::netadr_t master{}; if (server_list::get_master_server(master)) { console::info("Getting bots...\n"); network::send(master, "getbots"); } } } class component final : public component_interface { public: void post_unpack() override { if (game::environment::is_sp()) { return; } get_bot_name_hook.create(game::SV_BotGetRandomName, get_random_bot_name); command::add("spawnBot", [](const command::params& params) { if (!game::SV_Loaded()) return; std::size_t num_bots = 1; if (params.size() == 2) { num_bots = std::strtoul(params.get(1), nullptr, 10); } num_bots = std::min(num_bots, static_cast(game::mp::svs->clientCount)); console::info("Spawning %zu %s\n", num_bots, (num_bots == 1 ? "bot" : "bots")); for (std::size_t i = 0; i < num_bots; ++i) { scheduler::once(add_bot, scheduler::pipeline::server, 100ms * i); } }); if (should_use_remote_bot_names()) { scheduler::on_game_initialized([]() { update_bot_names(); scheduler::loop(update_bot_names, scheduler::main, 1h); }, scheduler::main); } else { parse_bot_names_from_file(); } network::on("getbotsResponse", [](const game::netadr_t& target, const std::string& data) { game::netadr_t master{}; if (server_list::get_master_server(master) && !bot_names_received && target == master) { bot_names = utils::string::split(data, '\n'); console::info("Got %zu names from the master server\n", bot_names.size()); bot_names_received = true; } }); } }; } REGISTER_COMPONENT(bots::component)