diff --git a/data/zonetool/h1_mod_common/fonts/default.otf b/data/zonetool/h1_mod_common/fonts/default.otf index ad4f12ef..6bdf48ec 100644 Binary files a/data/zonetool/h1_mod_common/fonts/default.otf and b/data/zonetool/h1_mod_common/fonts/default.otf differ diff --git a/deps/GSL b/deps/GSL index 6c6111ac..f94c1f6f 160000 --- a/deps/GSL +++ b/deps/GSL @@ -1 +1 @@ -Subproject commit 6c6111acb7b5d687ac006969ac96e5b1f21374cd +Subproject commit f94c1f6f2b5e141d5f6eb3d284cd4a8cf9a81aac diff --git a/deps/curl b/deps/curl index 4ab601d9..fc9f22b4 160000 --- a/deps/curl +++ b/deps/curl @@ -1 +1 @@ -Subproject commit 4ab601d93a07cee665ec2458a51fccd0767c03f1 +Subproject commit fc9f22b46ece93ce60f73fa2e767fef92e48a79f diff --git a/deps/protobuf b/deps/protobuf index 7ce9c415..8d5fdedd 160000 --- a/deps/protobuf +++ b/deps/protobuf @@ -1 +1 @@ -Subproject commit 7ce9c415455c098409222702b3b4572b47232882 +Subproject commit 8d5fdedd42ef361dcfc1531fba4f33470273f375 diff --git a/deps/zlib b/deps/zlib index e5546956..02a6049e 160000 --- a/deps/zlib +++ b/deps/zlib @@ -1 +1 @@ -Subproject commit e554695638228b846d49657f31eeff0ca4680e8a +Subproject commit 02a6049eb3884c430268bb0fe3296d597a03174c diff --git a/src/client/component/command.cpp b/src/client/component/command.cpp index 59a615a3..b3b576c0 100644 --- a/src/client/component/command.cpp +++ b/src/client/component/command.cpp @@ -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); diff --git a/src/client/component/dedicated_info.cpp b/src/client/component/dedicated_info.cpp index 8164345b..d8fbb1d6 100644 --- a/src/client/component/dedicated_info.cpp +++ b/src/client/component/dedicated_info.cpp @@ -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"); diff --git a/src/client/component/discord.cpp b/src/client/component/discord.cpp index 7677662f..4ec5413f 100644 --- a/src/client/component/discord.cpp +++ b/src/client/component/discord.cpp @@ -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()) { diff --git a/src/client/component/map_rotation.cpp b/src/client/component/map_rotation.cpp index 7b80bebf..1a39c0cb 100644 --- a/src/client/component/map_rotation.cpp +++ b/src/client/component/map_rotation.cpp @@ -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 parse_current_map_rotation() - { - const auto rotation = load_current_map_rotation(); - return utils::string::split(rotation, ' '); - } - - void store_new_rotation(const std::vector& 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()); } }; } diff --git a/src/client/component/map_rotation.hpp b/src/client/component/map_rotation.hpp new file mode 100644 index 00000000..ab5d6605 --- /dev/null +++ b/src/client/component/map_rotation.hpp @@ -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; + + 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_entries_; + std::size_t index_; + }; +} diff --git a/src/client/component/party.cpp b/src/client/component/party.cpp index 8c2f1f6e..c320613b 100644 --- a/src/client/component/party.cpp +++ b/src/client/component/party.cpp @@ -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())); diff --git a/src/client/std_include.hpp b/src/client/std_include.hpp index 02d361c2..23801892 100644 --- a/src/client/std_include.hpp +++ b/src/client/std_include.hpp @@ -83,6 +83,7 @@ #include #include #include +#include #include #include