Merge pull request #283 from diamante0018/new-map-rotation
New map rotation module
This commit is contained in:
commit
a4da5b891e
@ -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());
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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");
|
||||
});
|
||||
|
||||
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");
|
||||
|
||||
|
@ -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();
|
||||
|
199
src/Components/Modules/MapRotation.cpp
Normal file
199
src/Components/Modules/MapRotation.cpp
Normal 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");
|
||||
}
|
||||
}
|
58
src/Components/Modules/MapRotation.hpp
Normal file
58
src/Components/Modules/MapRotation.hpp
Normal 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();
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user