Merge branch 'develop' into demonware
This commit is contained in:
commit
58d8b085ac
Binary file not shown.
2
deps/GSL
vendored
2
deps/GSL
vendored
@ -1 +1 @@
|
||||
Subproject commit 6c6111acb7b5d687ac006969ac96e5b1f21374cd
|
||||
Subproject commit f94c1f6f2b5e141d5f6eb3d284cd4a8cf9a81aac
|
2
deps/curl
vendored
2
deps/curl
vendored
@ -1 +1 @@
|
||||
Subproject commit 4ab601d93a07cee665ec2458a51fccd0767c03f1
|
||||
Subproject commit fc9f22b46ece93ce60f73fa2e767fef92e48a79f
|
2
deps/protobuf
vendored
2
deps/protobuf
vendored
@ -1 +1 @@
|
||||
Subproject commit 7ce9c415455c098409222702b3b4572b47232882
|
||||
Subproject commit 8d5fdedd42ef361dcfc1531fba4f33470273f375
|
2
deps/zlib
vendored
2
deps/zlib
vendored
@ -1 +1 @@
|
||||
Subproject commit e554695638228b846d49657f31eeff0ca4680e8a
|
||||
Subproject commit 02a6049eb3884c430268bb0fe3296d597a03174c
|
@ -669,7 +669,6 @@ namespace command
|
||||
|
||||
const auto name = params.get(1);
|
||||
const auto dvar = game::Dvar_FindVar(name);
|
||||
|
||||
if (dvar == nullptr)
|
||||
{
|
||||
console::info("%s doesn't exist\n", name);
|
||||
|
@ -21,7 +21,7 @@ namespace dedicated_info
|
||||
|
||||
scheduler::loop([]
|
||||
{
|
||||
auto* sv_running = game::Dvar_FindVar("sv_running");
|
||||
const auto sv_running = game::Dvar_FindVar("sv_running");
|
||||
if (!sv_running || !sv_running->current.enabled || (*game::mp::svs_clients) == nullptr)
|
||||
{
|
||||
SetConsoleTitle("H1-Mod Dedicated Server");
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "console.hpp"
|
||||
#include "command.hpp"
|
||||
#include "discord.hpp"
|
||||
#include "fastfiles.hpp"
|
||||
#include "materials.hpp"
|
||||
#include "network.hpp"
|
||||
#include "party.hpp"
|
||||
@ -37,21 +38,21 @@ namespace discord
|
||||
discord_presence.details = SELECT_VALUE("Singleplayer", "Multiplayer");
|
||||
discord_presence.state = "Main Menu";
|
||||
|
||||
const auto in_firing_range = game::Dvar_FindVar("virtualLobbyInFiringRange");
|
||||
if (in_firing_range && in_firing_range->current.enabled == 1)
|
||||
{
|
||||
discord_presence.state = "Firing Range";
|
||||
}
|
||||
|
||||
discord_presence.partySize = 0;
|
||||
discord_presence.partyMax = 0;
|
||||
discord_presence.startTimestamp = 0;
|
||||
discord_presence.largeImageKey = SELECT_VALUE("menu_singleplayer", "menu_multiplayer");
|
||||
|
||||
// set to blank when in lobby
|
||||
|
||||
discord_presence.matchSecret = "";
|
||||
discord_presence.joinSecret = "";
|
||||
discord_presence.partyId = "";
|
||||
|
||||
const auto in_firing_range = game::Dvar_FindVar("virtualLobbyInFiringRange");
|
||||
if (in_firing_range && in_firing_range->current.enabled == 1)
|
||||
{
|
||||
discord_presence.state = "Firing Range";
|
||||
discord_presence.largeImageKey = "mp_vlobby_room";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -121,6 +122,11 @@ namespace discord
|
||||
discord_presence.partyMax = max_clients;
|
||||
discord_presence.state = clean_hostname;
|
||||
discord_presence.largeImageKey = map;
|
||||
|
||||
if (!fastfiles::is_stock_map(map))
|
||||
{
|
||||
discord_presence.largeImageKey = "menu_multiplayer";
|
||||
}
|
||||
}
|
||||
else if (game::environment::is_sp())
|
||||
{
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include "command.hpp"
|
||||
#include "console.hpp"
|
||||
#include "map_rotation.hpp"
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
@ -15,20 +16,23 @@ namespace map_rotation
|
||||
{
|
||||
namespace
|
||||
{
|
||||
DWORD previous_priority{};
|
||||
rotation_data dedicated_rotation;
|
||||
|
||||
void set_dvar(const std::string& dvar, const std::string& value)
|
||||
{
|
||||
command::execute(utils::string::va("%s \"%s\"", dvar.data(), value.data()), true);
|
||||
}
|
||||
const game::dvar_t* sv_map_rotation;
|
||||
const game::dvar_t* sv_map_rotation_current;
|
||||
const game::dvar_t* sv_random_map_rotation;
|
||||
|
||||
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)
|
||||
{
|
||||
assert(!mapname.empty());
|
||||
|
||||
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");
|
||||
if (!strlen(rotation->current.string))
|
||||
assert(!rotation.empty());
|
||||
|
||||
std::size_t i = 0;
|
||||
while (i < rotation.get_entries_size())
|
||||
{
|
||||
rotation = game::Dvar_FindVar("sv_mapRotation");
|
||||
set_dvar("sv_mapRotationCurrent", rotation->current.string);
|
||||
}
|
||||
|
||||
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)
|
||||
const auto& entry = rotation.get_next_entry();
|
||||
if (entry.first == "map"s)
|
||||
{
|
||||
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");
|
||||
if (dvar && dvar->current.enabled)
|
||||
static auto loaded = false;
|
||||
if (loaded)
|
||||
{
|
||||
scheduler::on_game_initialized([]
|
||||
{
|
||||
SetPriorityClass(GetCurrentProcess(), previous_priority);
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
return;
|
||||
}
|
||||
|
||||
previous_priority = GetPriorityClass(GetCurrentProcess());
|
||||
SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS);
|
||||
loaded = true;
|
||||
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;
|
||||
}
|
||||
|
||||
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];
|
||||
const auto& value = rotation[i + 1];
|
||||
|
||||
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());
|
||||
}
|
||||
console::debug("Applying sv_map_rotation_current\n");
|
||||
apply_map_rotation_current(map_rotation_current);
|
||||
return;
|
||||
}
|
||||
|
||||
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()
|
||||
@ -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
|
||||
{
|
||||
public:
|
||||
@ -164,17 +271,16 @@ namespace map_rotation
|
||||
|
||||
scheduler::once([]
|
||||
{
|
||||
dvars::register_string("sv_mapRotation", "", game::DVAR_FLAG_NONE, "");
|
||||
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.");
|
||||
sv_map_rotation = dvars::register_string("sv_mapRotation", "", game::DVAR_FLAG_NONE, "");
|
||||
sv_map_rotation_current = dvars::register_string("sv_mapRotationCurrent", "", game::DVAR_FLAG_NONE, "");
|
||||
}, 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);
|
||||
|
||||
// Hook GScr_ExitLevel
|
||||
utils::hook::jump(0xE2670_b, &trigger_map_rotation, true); // not sure if working
|
||||
|
||||
previous_priority = GetPriorityClass(GetCurrentProcess());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
34
src/client/component/map_rotation.hpp
Normal file
34
src/client/component/map_rotation.hpp
Normal 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_;
|
||||
};
|
||||
}
|
@ -11,6 +11,7 @@
|
||||
#include "fastfiles.hpp"
|
||||
#include "mods.hpp"
|
||||
|
||||
#include "game/dvars.hpp"
|
||||
#include "game/game.hpp"
|
||||
#include "game/ui_scripting/execution.hpp"
|
||||
|
||||
@ -57,6 +58,8 @@ namespace party
|
||||
utils::info_string info_string{};
|
||||
} saved_info_response;
|
||||
|
||||
const game::dvar_t* sv_say_name = nullptr;
|
||||
|
||||
void perform_game_initialization()
|
||||
{
|
||||
command::execute("onlinegame 1", true);
|
||||
@ -908,8 +911,7 @@ namespace party
|
||||
|
||||
scheduler::once([]()
|
||||
{
|
||||
const auto hash = game::generateHashValue("sv_sayName");
|
||||
game::Dvar_RegisterString(hash, "sv_sayName", "console", game::DvarFlags::DVAR_FLAG_NONE);
|
||||
sv_say_name = dvars::register_string("sv_sayName", "console", game::DvarFlags::DVAR_FLAG_NONE, "");
|
||||
}, scheduler::pipeline::main);
|
||||
|
||||
command::add("tell", [](const command::params& params)
|
||||
@ -921,7 +923,7 @@ namespace party
|
||||
|
||||
const auto client_num = atoi(params.get(1));
|
||||
const auto message = params.join(2);
|
||||
const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string;
|
||||
const auto* const name = sv_say_name->current.string;
|
||||
|
||||
game::SV_GameSendServerCommand(client_num, game::SV_CMD_CAN_IGNORE,
|
||||
utils::string::va("%c \"%s: %s\"", 84, name, message.data()));
|
||||
@ -951,7 +953,7 @@ namespace party
|
||||
}
|
||||
|
||||
const auto message = params.join(1);
|
||||
const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string;
|
||||
const auto* const name = sv_say_name->current.string;
|
||||
|
||||
game::SV_GameSendServerCommand(
|
||||
-1, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s: %s\"", 84, name, message.data()));
|
||||
|
@ -83,6 +83,7 @@
|
||||
#include <optional>
|
||||
#include <unordered_set>
|
||||
#include <variant>
|
||||
#include <random>
|
||||
|
||||
#include <gsl/gsl>
|
||||
#include <udis86.h>
|
||||
|
Loading…
Reference in New Issue
Block a user