Merge pull request #283 from diamante0018/new-map-rotation

New map rotation module
This commit is contained in:
Edo 2022-06-12 19:40:17 +02:00 committed by GitHub
commit a4da5b891e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 261 additions and 153 deletions

View File

@ -108,6 +108,7 @@ namespace Components
Loader::Register(new Branding());
Loader::Register(new RawMouse());
Loader::Register(new Bullet());
Loader::Register(new MapRotation());
Loader::Register(new Ceg());
Loader::Register(new UserInfo());

View File

@ -139,5 +139,6 @@ namespace Components
#include "Modules/Branding.hpp"
#include "Modules/RawMouse.hpp"
#include "Modules/Bullet.hpp"
#include "Modules/MapRotation.hpp"
#include "Modules/Ceg.hpp"
#include "Modules/UserInfo.hpp"

View File

@ -4,9 +4,7 @@ namespace Components
{
SteamID Dedicated::PlayerGuids[18][2];
Dvar::Var Dedicated::SVRandomMapRotation;
Dvar::Var Dedicated::SVLanOnly;
Dvar::Var Dedicated::SVDontRotate;
Dvar::Var Dedicated::COMLogFilter;
bool Dedicated::IsEnabled()
@ -124,141 +122,6 @@ namespace Components
Game::Com_Error(code, message);
}
void Dedicated::RandomizeMapRotation()
{
auto rotation = Dvar::Var("sv_mapRotation").get<std::string>();
const auto tokens = Utils::String::Split(rotation, ' ');
std::vector<std::pair<std::string, std::string>> mapRotationPair;
for (std::size_t i = 0; i < (tokens.size() - 1); i += 2)
{
if (i + 1 >= tokens.size()) break;
const auto& key = tokens[i];
const auto& value = tokens[i + 1];
mapRotationPair.emplace_back(std::make_pair(key, value));
}
const auto seed = Utils::Cryptography::Rand::GenerateInt();
std::shuffle(mapRotationPair.begin(), mapRotationPair.end(), std::default_random_engine(seed));
// Rebuild map rotation using the randomized key/values
rotation.clear();
for (std::size_t j = 0; j < mapRotationPair.size(); j++)
{
const auto& pair = mapRotationPair[j];
rotation.append(pair.first);
rotation.append(" ");
rotation.append(pair.second);
if (j != mapRotationPair.size() - 1)
rotation.append(" "); // No space on last element
}
Dvar::Var("sv_mapRotationCurrent").set(rotation);
}
void Dedicated::ApplyMapRotation()
{
auto rotation = Dvar::Var("sv_mapRotationCurrent").get<std::string>();
const auto tokens = Utils::String::Split(rotation, ' ');
for (std::size_t i = 0; i < (tokens.size() - 1); i += 2)
{
if (i + 1 >= tokens.size())
{
Dvar::Var("sv_mapRotationCurrent").set("");
Command::Execute("map_rotate", true);
return;
}
const auto& key = tokens[i];
const auto& value = tokens[i + 1];
if (key == "map")
{
// Rebuild map rotation string
rotation.clear();
for (std::size_t j = (i + 2); j < tokens.size(); ++j)
{
if (j != (i + 2)) rotation += " ";
rotation += tokens[j];
}
Dvar::Var("sv_mapRotationCurrent").set(rotation);
Logger::Print(Game::conChannel_t::CON_CHANNEL_SERVER,"Loading new map: %s\n", value.data());
Command::Execute(Utils::String::VA("map %s", value.data()), true);
break;
}
if (key == "gametype")
{
Logger::Print(Game::conChannel_t::CON_CHANNEL_SERVER, "Applying new gametype: %s\n", value.data());
Dvar::Var("g_gametype").set(value);
}
else
{
Logger::Print(Game::conChannel_t::CON_CHANNEL_SERVER, "Unsupported maprotation key '%s'!\n", key.data());
}
}
}
void Dedicated::MapRotate()
{
if (!Dedicated::IsEnabled() && Dedicated::SVDontRotate.get<bool>())
{
Dedicated::SVDontRotate.set(false);
return;
}
if (Dvar::Var("party_enable").get<bool>() && Dvar::Var("party_host").get<bool>())
{
Logger::Print(Game::conChannel_t::CON_CHANNEL_SERVER, "Not performing map rotation as we are hosting a party!\n");
return;
}
Logger::Print(Game::conChannel_t::CON_CHANNEL_SERVER, "Rotating map...\n");
const auto mapRotation = Dvar::Var("sv_mapRotation").get<std::string>();
// if nothing, just restart
if (mapRotation.empty())
{
Logger::Print(Game::conChannel_t::CON_CHANNEL_SERVER, "No rotation defined, restarting map.\n");
if (!Dvar::Var("sv_cheats").get<bool>())
{
Command::Execute(Utils::String::VA("map %s", Dvar::Var("mapname").get<const char*>()), true);
}
else
{
Command::Execute(Utils::String::VA("devmap %s", Dvar::Var("mapname").get<const char*>()), true);
}
return;
}
// First, check if the string contains nothing
if (Dvar::Var("sv_mapRotationCurrent").get<std::string>().empty())
{
Logger::Print(Game::conChannel_t::CON_CHANNEL_SERVER, "Current map rotation has finished, reloading...\n");
if (Dedicated::SVRandomMapRotation.get<bool>())
{
Logger::Print(Game::conChannel_t::CON_CHANNEL_SERVER, "Randomizing map rotation\n");
Dedicated::RandomizeMapRotation();
}
else
{
Dvar::Var("sv_mapRotationCurrent").set(mapRotation);
}
}
Dedicated::ApplyMapRotation();
}
void Dedicated::Heartbeat()
{
// Do not send a heartbeat if sv_lanOnly is set to true
@ -296,15 +159,8 @@ namespace Components
Dedicated::Dedicated()
{
// Map rotation
Utils::Hook::Set(0x4152E8, Dedicated::MapRotate);
Dvar::OnInit([]
{
Dedicated::SVDontRotate = Dvar::Register<bool>("sv_dontRotate", false,
Game::dvar_flag::DVAR_NONE, "");
Dedicated::COMLogFilter = Dvar::Register<bool>("com_logFilter", true,
Game::dvar_flag::DVAR_LATCH, "Removes ~95% of unneeded lines from the log");
});
Dedicated::COMLogFilter = Dvar::Register<bool>("com_logFilter", true,
Game::dvar_flag::DVAR_LATCH, "Removes ~95% of unneeded lines from the log");
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled())
{
@ -414,7 +270,6 @@ namespace Components
Dvar::OnInit([]()
{
Dedicated::SVRandomMapRotation = Dvar::Register<bool>("sv_randomMapRotation", false, Game::dvar_flag::DVAR_ARCHIVE, "Randomize map rotation when true");
Dvar::Register<const char*>("sv_sayName", "^7Console", Game::dvar_flag::DVAR_NONE, "The name to pose as for 'say' commands");
Dvar::Register<const char*>("sv_motd", "", Game::dvar_flag::DVAR_NONE, "A custom message of the day for servers");

View File

@ -9,7 +9,6 @@ namespace Components
static SteamID PlayerGuids[18][2];
static Dvar::Var SVLanOnly;
static Dvar::Var SVDontRotate;
static Dvar::Var COMLogFilter;
static bool IsEnabled();
@ -17,11 +16,6 @@ namespace Components
static void Heartbeat();
private:
static Dvar::Var SVRandomMapRotation;
static void RandomizeMapRotation();
static void ApplyMapRotation();
static void MapRotate();
static void InitDedicatedServer();
static void PostInitialization();

View File

@ -0,0 +1,199 @@
#include <STDInclude.hpp>
namespace Components
{
Dvar::Var MapRotation::SVRandomMapRotation;
Dvar::Var MapRotation::SVDontRotate;
Game::dvar_t** MapRotation::SVMapRotation = reinterpret_cast<Game::dvar_t**>(0x62C7C44);
Game::dvar_t** MapRotation::SVMapname = reinterpret_cast<Game::dvar_t**>(0x2098DDC);
MapRotation::RotationData MapRotation::DedicatedRotation;
MapRotation::RotationData::RotationData()
:index_(0)
{
}
void MapRotation::RotationData::randomize()
{
// Code from https://en.cppreference.com/w/cpp/algorithm/random_shuffle
std::random_device rd;
std::mt19937 gen(rd());
std::ranges::shuffle(this->rotationEntries_, gen);
}
void MapRotation::RotationData::addEntry(const std::string& key, const std::string& value)
{
this->rotationEntries_.emplace_back(std::make_pair(key, value));
}
std::size_t MapRotation::RotationData::getEntriesSize() const
{
return this->rotationEntries_.size();
}
MapRotation::RotationData::rotationEntry& MapRotation::RotationData::getNextEntry()
{
const auto index = this->index_;
++this->index_ %= this->rotationEntries_.size(); // Point index_ to the next entry
return this->rotationEntries_.at(index);
}
void MapRotation::RotationData::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" || key == "gametype")
{
this->addEntry(key, value);
}
else
{
throw ParseRotationError();
}
}
}
void MapRotation::LoadRotation(const std::string& data)
{
static auto loaded = false;
if (loaded)
{
// Load the rotation once
return;
}
try
{
DedicatedRotation.parse(data);
}
catch (const std::exception& ex)
{
Logger::Print(Game::CON_CHANNEL_SERVER, "%s: sv_mapRotation contains invalid data!\n", ex.what());
}
Logger::Print(Game::CON_CHANNEL_SERVER, "DedicatedRotation size after parsing is '%u'\n", DedicatedRotation.getEntriesSize());
// Shuffles values
if (SVRandomMapRotation.get<bool>())
{
Logger::Print(Game::CON_CHANNEL_SERVER, "Randomizing the map rotation\n");
DedicatedRotation.randomize();
}
loaded = true;
}
bool MapRotation::ShouldRotate()
{
if (!Dedicated::IsEnabled() && SVDontRotate.get<bool>())
{
Logger::Print(Game::CON_CHANNEL_SERVER, "Not performing map rotation as sv_dontRotate is true\n");
SVDontRotate.set(false);
return false;
}
if (Dvar::Var("party_enable").get<bool>() && Dvar::Var("party_host").get<bool>())
{
Logger::Print(Game::CON_CHANNEL_SERVER, "Not performing map rotation as we are hosting a party!\n");
return false;
}
return true;
}
void MapRotation::RestartCurrentMap()
{
std::string svMapname = (*SVMapname)->current.string;
if (svMapname.empty())
{
Logger::Print(Game::CON_CHANNEL_SERVER, "mapname dvar is empty! Defaulting to mp_afghan\n");
svMapname = "mp_afghan";
}
if (!Dvar::Var("sv_cheats").get<bool>())
{
Command::Execute(Utils::String::VA("map %s", svMapname.data()), true);
}
else
{
Command::Execute(Utils::String::VA("devmap %s", svMapname.data()), true);
}
}
void MapRotation::ApplyMapRotation()
{
// Continue to apply gamemode until a map is found
auto foundMap = false;
std::size_t i = 0;
while (!foundMap && i < DedicatedRotation.getEntriesSize())
{
const auto& entry = DedicatedRotation.getNextEntry();
if (entry.first == "map")
{
Logger::Print("Loading new map: '%s'\n", entry.second.data());
Command::Execute(Utils::String::VA("map %s", entry.second.data()), true);
// Map was found so we exit the loop
foundMap = true;
}
else if (entry.first == "gamemode")
{
Logger::Print("Applying new gametype: '%s'\n", entry.second.data());
Dvar::Var("g_gametype").set(entry.second);
}
++i;
}
}
void MapRotation::SV_MapRotate_f()
{
if (!ShouldRotate())
{
return;
}
Logger::Print(Game::CON_CHANNEL_SERVER, "Rotating map...\n");
const std::string mapRotation = (*SVMapRotation)->current.string;
if (mapRotation.empty())
{
Logger::Print(Game::CON_CHANNEL_SERVER, "No rotation defined (sv_mapRotation is empty), restarting map.\n");
RestartCurrentMap();
return;
}
LoadRotation(mapRotation);
if (DedicatedRotation.getEntriesSize() == 0)
{
Logger::Print(Game::CON_CHANNEL_SERVER, "sv_mapRotation is empty or contains invalid data, restarting map.\n");
RestartCurrentMap();
return;
}
ApplyMapRotation();
}
MapRotation::MapRotation()
{
Utils::Hook::Set<void(*)()>(0x4152E8, SV_MapRotate_f);
SVRandomMapRotation = Dvar::Register<bool>("sv_randomMapRotation", false,
Game::dvar_flag::DVAR_ARCHIVE, "Randomize map rotation when true");
SVDontRotate = Dvar::Register<bool>("sv_dontRotate", false,
Game::dvar_flag::DVAR_NONE, "Do not perform map rotation");
}
}

View File

@ -0,0 +1,58 @@
#pragma once
namespace Components
{
class MapRotation : public Component
{
public:
MapRotation();
private:
struct ParseRotationError : public std::exception
{
const char* what() const noexcept override { return "Parse Rotation Error"; }
};
class RotationData
{
public:
using rotationEntry = std::pair<std::string, std::string>;
RotationData();
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 addEntry(const std::string& key, const std::string& value);
[[nodiscard]] std::size_t getEntriesSize() const;
rotationEntry& getNextEntry();
void parse(const std::string& data);
private:
std::vector<rotationEntry> rotationEntries_;
std::size_t index_;
};
// Rotation Dvars
static Dvar::Var SVRandomMapRotation;
static Dvar::Var SVDontRotate;
// Game Dvars
static Game::dvar_t** SVMapRotation;
static Game::dvar_t** SVMapname;
// Holds the parsed data from sv_mapRotation
static RotationData DedicatedRotation;
static void LoadRotation(const std::string& data);
static bool ShouldRotate();
static void RestartCurrentMap();
static void ApplyMapRotation();
static void SV_MapRotate_f();
};
}