2022-02-27 23:54:27 -05:00
|
|
|
#include <std_include.hpp>
|
|
|
|
#include "loader/component_loader.hpp"
|
|
|
|
|
|
|
|
#include "command.hpp"
|
2022-07-05 23:18:12 -04:00
|
|
|
#include "console.hpp"
|
2022-02-27 23:54:27 -05:00
|
|
|
#include "scheduler.hpp"
|
|
|
|
#include "network.hpp"
|
|
|
|
#include "party.hpp"
|
2022-07-05 23:18:12 -04:00
|
|
|
#include "scripting.hpp"
|
2022-02-27 23:54:27 -05:00
|
|
|
|
|
|
|
#include "game/game.hpp"
|
2022-03-04 14:46:09 -05:00
|
|
|
#include "game/scripting/execution.hpp"
|
2022-02-27 23:54:27 -05:00
|
|
|
|
|
|
|
#include <utils/hook.hpp>
|
|
|
|
#include <utils/string.hpp>
|
|
|
|
#include <utils/cryptography.hpp>
|
2022-07-05 23:18:12 -04:00
|
|
|
#include <utils/io.hpp>
|
2022-02-27 23:54:27 -05:00
|
|
|
|
|
|
|
namespace bots
|
|
|
|
{
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
bool can_add()
|
|
|
|
{
|
2022-03-13 18:12:34 -04:00
|
|
|
return party::get_client_count() < *game::mp::svs_numclients
|
|
|
|
&& game::SV_Loaded() && !game::VirtualLobby_Loaded();
|
2022-02-27 23:54:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void bot_team_join(const int entity_num)
|
|
|
|
{
|
2022-03-04 14:46:09 -05:00
|
|
|
const game::scr_entref_t entref{static_cast<uint16_t>(entity_num), 0};
|
|
|
|
scheduler::once([entref]()
|
2022-02-27 23:54:27 -05:00
|
|
|
{
|
2022-03-04 14:46:09 -05:00
|
|
|
scripting::notify(entref, "luinotifyserver", {"team_select", 2});
|
|
|
|
scheduler::once([entref]()
|
2022-02-27 23:54:27 -05:00
|
|
|
{
|
2022-03-04 14:46:09 -05:00
|
|
|
auto* _class = utils::string::va("class%d", utils::cryptography::random::get_integer() % 5);
|
|
|
|
scripting::notify(entref, "luinotifyserver", {"class_select", _class});
|
|
|
|
}, scheduler::pipeline::server, 2s);
|
|
|
|
}, scheduler::pipeline::server, 2s);
|
2022-02-27 23:54:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void spawn_bot(const int entity_num)
|
|
|
|
{
|
|
|
|
game::SV_SpawnTestClient(&game::mp::g_entities[entity_num]);
|
|
|
|
if (game::Com_GetCurrentCoDPlayMode() == game::CODPLAYMODE_CORE)
|
|
|
|
{
|
|
|
|
bot_team_join(entity_num);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void add_bot()
|
|
|
|
{
|
|
|
|
if (!can_add())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-07-05 23:18:12 -04:00
|
|
|
const auto* const bot_name = game::SV_BotGetRandomName();
|
|
|
|
const auto* bot_ent = game::SV_AddBot(bot_name);
|
2022-03-13 18:12:34 -04:00
|
|
|
|
2022-02-27 23:54:27 -05:00
|
|
|
if (bot_ent)
|
|
|
|
{
|
2022-07-04 23:51:57 -04:00
|
|
|
spawn_bot(bot_ent->s.number);
|
2022-02-27 23:54:27 -05:00
|
|
|
}
|
2022-03-13 19:00:47 -04:00
|
|
|
else
|
|
|
|
{
|
|
|
|
scheduler::once([]()
|
|
|
|
{
|
|
|
|
add_bot();
|
|
|
|
}, scheduler::pipeline::server, 100ms);
|
|
|
|
}
|
2022-02-27 23:54:27 -05:00
|
|
|
}
|
2022-07-05 23:18:12 -04:00
|
|
|
|
|
|
|
utils::hook::detour get_bot_name_hook;
|
|
|
|
std::vector<std::string> bot_names{};
|
|
|
|
|
|
|
|
void load_bot_data()
|
|
|
|
{
|
2022-07-06 11:35:38 -04:00
|
|
|
static const char* bots_txt = "h1-mod/bots.txt";
|
2022-07-05 23:18:12 -04:00
|
|
|
|
|
|
|
std::string bots_content;
|
2022-07-06 11:35:38 -04:00
|
|
|
if (!utils::io::read_file(bots_txt, &bots_content))
|
2022-07-05 23:18:12 -04:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-07-06 11:35:38 -04:00
|
|
|
auto names = utils::string::split(bots_content, '\n');
|
|
|
|
for (auto& name : names)
|
2022-07-05 23:18:12 -04:00
|
|
|
{
|
2022-07-06 11:35:38 -04:00
|
|
|
name = utils::string::replace(name, "\r", "");
|
|
|
|
if (!name.empty())
|
|
|
|
{
|
|
|
|
bot_names.emplace_back(name);
|
|
|
|
}
|
2022-07-05 23:18:12 -04:00
|
|
|
}
|
|
|
|
}
|
2022-07-07 17:07:16 -04:00
|
|
|
|
|
|
|
size_t bot_id = 0;
|
2022-07-05 23:18:12 -04:00
|
|
|
|
|
|
|
const char* get_random_bot_name()
|
|
|
|
{
|
|
|
|
if (bot_names.empty())
|
|
|
|
{
|
|
|
|
load_bot_data();
|
|
|
|
}
|
|
|
|
|
|
|
|
// only use bot names once, no dupes in names
|
|
|
|
if (!bot_names.empty() && bot_id < bot_names.size())
|
|
|
|
{
|
|
|
|
bot_id %= bot_names.size();
|
|
|
|
const auto& entry = bot_names.at(bot_id++);
|
|
|
|
return utils::string::va("%.*s", static_cast<int>(entry.size()), entry.data());
|
|
|
|
}
|
|
|
|
|
|
|
|
return get_bot_name_hook.invoke<const char*>();
|
|
|
|
}
|
2022-02-27 23:54:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
class component final : public component_interface
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
void post_unpack() override
|
|
|
|
{
|
|
|
|
if (game::environment::is_sp())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-07-05 23:18:12 -04:00
|
|
|
get_bot_name_hook.create(game::SV_BotGetRandomName, get_random_bot_name);
|
|
|
|
|
2022-02-27 23:54:27 -05:00
|
|
|
command::add("spawnBot", [](const command::params& params)
|
|
|
|
{
|
2022-03-13 18:12:34 -04:00
|
|
|
if (!can_add())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2022-02-27 23:54:27 -05:00
|
|
|
|
|
|
|
auto num_bots = 1;
|
|
|
|
if (params.size() == 2)
|
|
|
|
{
|
|
|
|
num_bots = atoi(params.get(1));
|
|
|
|
}
|
|
|
|
|
2022-05-21 06:26:30 -04:00
|
|
|
num_bots = std::min(num_bots, *game::mp::svs_numclients);
|
2022-03-05 20:37:08 -05:00
|
|
|
|
|
|
|
for (auto i = 0; i < num_bots; i++)
|
2022-02-27 23:54:27 -05:00
|
|
|
{
|
|
|
|
scheduler::once(add_bot, scheduler::pipeline::server, 100ms * i);
|
|
|
|
}
|
|
|
|
});
|
2022-07-05 23:18:12 -04:00
|
|
|
|
|
|
|
// Clear bot names and reset ID on game shutdown to allow new names to be added without restarting
|
2022-09-11 13:14:42 -04:00
|
|
|
scripting::on_shutdown([](int)
|
2022-07-05 23:18:12 -04:00
|
|
|
{
|
|
|
|
bot_names.clear();
|
|
|
|
bot_id = 0;
|
|
|
|
});
|
2022-02-27 23:54:27 -05:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-05-19 12:42:09 -04:00
|
|
|
REGISTER_COMPONENT(bots::component)
|