random map rotation + cleanup

This commit is contained in:
m 2023-01-01 10:30:33 -06:00
parent f3f1428946
commit 504889fe15
3 changed files with 215 additions and 74 deletions

View File

@ -3,6 +3,7 @@
#include "command.hpp" #include "command.hpp"
#include "console.hpp" #include "console.hpp"
#include "map_rotation.hpp"
#include "scheduler.hpp" #include "scheduler.hpp"
#include "game/game.hpp" #include "game/game.hpp"
@ -15,20 +16,23 @@ namespace map_rotation
{ {
namespace namespace
{ {
DWORD previous_priority{}; rotation_data dedicated_rotation;
void set_dvar(const std::string& dvar, const std::string& value) const game::dvar_t* sv_map_rotation;
{ const game::dvar_t* sv_map_rotation_current;
command::execute(utils::string::va("%s \"%s\"", dvar.data(), value.data()), true); const game::dvar_t* sv_random_map_rotation;
}
void set_gametype(const std::string& gametype) void set_gametype(const std::string& gametype)
{ {
set_dvar("g_gametype", gametype); assert(!gametype.empty());
game::Dvar_SetFromStringByNameFromSource("g_gametype", gametype.data(), game::DVAR_SOURCE_INTERNAL);
} }
void launch_map(const std::string& mapname) void launch_map(const std::string& mapname)
{ {
assert(!mapname.empty());
command::execute(utils::string::va("map %s", mapname.data()), false); command::execute(utils::string::va("map %s", mapname.data()), false);
} }
@ -46,53 +50,103 @@ namespace map_rotation
} }
} }
std::string load_current_map_rotation() void apply_rotation(rotation_data& rotation)
{ {
auto* rotation = game::Dvar_FindVar("sv_mapRotationCurrent"); assert(!rotation.empty());
if (!strlen(rotation->current.string))
std::size_t i = 0;
while (i < rotation.get_entries_size())
{ {
rotation = game::Dvar_FindVar("sv_mapRotation"); const auto& entry = rotation.get_next_entry();
set_dvar("sv_mapRotationCurrent", rotation->current.string); if (entry.first == "map"s)
}
return rotation->current.string;
}
std::vector<std::string> parse_current_map_rotation()
{
const auto rotation = load_current_map_rotation();
return utils::string::split(rotation, ' ');
}
void store_new_rotation(const std::vector<std::string>& elements, const size_t index)
{
std::string value{};
for (auto i = index; i < elements.size(); ++i)
{
if (i != index)
{ {
value.push_back(' '); console::info("Loading new map: '%s'\n", entry.second.data());
if (!game::SV_MapExists(entry.second.data()))
{
console::info("map_rotation: '%s' map doesn't exist!\n", entry.second.data());
launch_default_map();
return;
}
launch_map(entry.second);
break;
} }
value.append(elements[i]); if (entry.first == "gametype"s)
} {
console::info("Applying new gametype: '%s'\n", entry.second.data());
set_gametype(entry.second);
}
set_dvar("sv_mapRotationCurrent", value); ++i;
}
} }
void change_process_priority() void load_rotation(const std::string& data)
{ {
auto* const dvar = game::Dvar_FindVar("sv_autoPriority"); static auto loaded = false;
if (dvar && dvar->current.enabled) if (loaded)
{ {
scheduler::on_game_initialized([] return;
{ }
SetPriorityClass(GetCurrentProcess(), previous_priority);
}, scheduler::pipeline::main, 1s);
previous_priority = GetPriorityClass(GetCurrentProcess()); loaded = true;
SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS); try
{
dedicated_rotation.parse(data);
}
catch (const std::exception& ex)
{
console::error("%s: sv_map_rotation contains invalid data!\n", ex.what());
}
console::debug("dedicated_rotation size after parsing is '%llu'", dedicated_rotation.get_entries_size());
}
void load_map_rotation()
{
const std::string map_rotation = sv_map_rotation->current.string;
if (!map_rotation.empty())
{
console::debug("sv_map_rotation is not empty. Parsing...\n");
load_rotation(map_rotation);
}
}
void apply_map_rotation_current(const std::string& data)
{
assert(!data.empty());
rotation_data rotation_current;
try
{
rotation_current.parse(data);
}
catch (const std::exception& ex)
{
console::error("%s: sv_map_rotation_current contains invalid data!\n", ex.what());
}
game::Dvar_SetFromStringByNameFromSource("sv_map_rotation_current", "", game::DVAR_SOURCE_INTERNAL);
if (rotation_current.empty())
{
console::warn("sv_map_rotation_current is empty or contains invalid data\n");
launch_default_map();
return;
}
apply_rotation(rotation_current);
}
void randomize_map_rotation()
{
if (sv_random_map_rotation->current.enabled)
{
console::info("Randomizing map rotation\n");
dedicated_rotation.randomize();
} }
} }
@ -104,37 +158,28 @@ namespace map_rotation
return; return;
} }
const auto rotation = parse_current_map_rotation(); console::info("Rotating map...\n");
for (size_t i = 0; !rotation.empty() && i < (rotation.size() - 1); i += 2) // This takes priority because of backwards compatibility
const std::string map_rotation_current = sv_map_rotation_current->current.string;
if (!map_rotation_current.empty())
{ {
const auto& key = rotation[i]; console::debug("Applying sv_map_rotation_current\n");
const auto& value = rotation[i + 1]; apply_map_rotation_current(map_rotation_current);
return;
if (key == "gametype")
{
set_gametype(value);
}
else if (key == "map")
{
store_new_rotation(rotation, i + 2);
change_process_priority();
if (!game::SV_MapExists(value.data()))
{
console::info("map_rotation: '%s' map doesn't exist!\n", value.data());
launch_default_map();
return;
}
launch_map(value);
return;
}
else
{
console::info("Invalid map rotation key: %s\n", key.data());
}
} }
launch_default_map(); load_map_rotation();
if (dedicated_rotation.empty())
{
console::warn("sv_map_rotation is empty or contains invalid data. Restarting map\n");
launch_default_map();
return;
}
randomize_map_rotation();
apply_rotation(dedicated_rotation);
} }
void trigger_map_rotation() void trigger_map_rotation()
@ -152,6 +197,68 @@ namespace map_rotation
} }
} }
rotation_data::rotation_data()
: index_(0)
{
}
void rotation_data::randomize()
{
std::random_device rd;
std::mt19937 gen(rd());
std::ranges::shuffle(this->rotation_entries_, gen);
}
void rotation_data::add_entry(const std::string& key, const std::string& value)
{
this->rotation_entries_.emplace_back(std::make_pair(key, value));
}
bool rotation_data::contains(const std::string& key, const std::string& value) const
{
return std::ranges::any_of(this->rotation_entries_, [&](const auto& entry)
{
return entry.first == key && entry.second == value;
});
}
bool rotation_data::empty() const noexcept
{
return this->rotation_entries_.empty();
}
std::size_t rotation_data::get_entries_size() const noexcept
{
return this->rotation_entries_.size();
}
rotation_data::rotation_entry& rotation_data::get_next_entry()
{
const auto index = this->index_;
++this->index_ %= this->rotation_entries_.size();
return this->rotation_entries_.at(index);
}
void rotation_data::parse(const std::string& data)
{
const auto tokens = utils::string::split(data, ' ');
for (std::size_t i = 0; !tokens.empty() && i < (tokens.size() - 1); i += 2)
{
const auto& key = tokens[i];
const auto& value = tokens[i + 1];
if (key == "map"s || key == "gametype"s)
{
this->add_entry(key, value);
}
else
{
throw parse_rotation_error();
}
}
}
class component final : public component_interface class component final : public component_interface
{ {
public: public:
@ -164,17 +271,16 @@ namespace map_rotation
scheduler::once([] scheduler::once([]
{ {
dvars::register_string("sv_mapRotation", "", game::DVAR_FLAG_NONE, ""); sv_map_rotation = dvars::register_string("sv_mapRotation", "", game::DVAR_FLAG_NONE, "");
dvars::register_string("sv_mapRotationCurrent", "", game::DVAR_FLAG_NONE, ""); sv_map_rotation_current = dvars::register_string("sv_mapRotationCurrent", "", game::DVAR_FLAG_NONE, "");
dvars::register_string("sv_autoPriority", "", game::DVAR_FLAG_NONE, "Lowers the process priority during map changes to not cause lags on other servers.");
}, scheduler::pipeline::main); }, scheduler::pipeline::main);
sv_random_map_rotation = dvars::register_bool("sv_randomMapRotation", false, game::DVAR_FLAG_NONE, "Randomize map rotation");
command::add("map_rotate", &perform_map_rotation); command::add("map_rotate", &perform_map_rotation);
// Hook GScr_ExitLevel // Hook GScr_ExitLevel
utils::hook::jump(0xE2670_b, &trigger_map_rotation, true); // not sure if working utils::hook::jump(0xE2670_b, &trigger_map_rotation, true); // not sure if working
previous_priority = GetPriorityClass(GetCurrentProcess());
} }
}; };
} }

View File

@ -0,0 +1,34 @@
#pragma once
namespace map_rotation
{
struct parse_rotation_error : public std::exception
{
const char* what() const noexcept override { return "Rotation parse error"; }
};
class rotation_data
{
public:
using rotation_entry = std::pair<std::string, std::string>;
rotation_data();
void randomize();
// In case a new way to enrich the map rotation is added (other than sv_mapRotation)
// this method should be called to add a new entry (gamemode/map & value)
void add_entry(const std::string& key, const std::string& value);
[[nodiscard]] bool contains(const std::string& key, const std::string& value) const;
[[nodiscard]] bool empty() const noexcept;
[[nodiscard]] std::size_t get_entries_size() const noexcept;
[[nodiscard]] rotation_entry& get_next_entry();
void parse(const std::string& data);
private:
std::vector<rotation_entry> rotation_entries_;
std::size_t index_;
};
}

View File

@ -83,6 +83,7 @@
#include <optional> #include <optional>
#include <unordered_set> #include <unordered_set>
#include <variant> #include <variant>
#include <random>
#include <gsl/gsl> #include <gsl/gsl>
#include <udis86.h> #include <udis86.h>