From b67012622dee62f8a88c4926c3d621ae9ee4dac0 Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Fri, 28 Jan 2022 00:27:53 +0100 Subject: [PATCH] Add mod loading menu --- src/client/component/command.cpp | 57 +++++++++ src/client/game/game.cpp | 2 + src/client/game/game.hpp | 3 +- src/client/game/scripting/lua/engine.cpp | 20 +++- src/client/game/ui_scripting/lua/context.cpp | 58 +++++---- src/client/game/ui_scripting/lua/context.hpp | 8 +- src/client/game/ui_scripting/lua/engine.cpp | 26 +++- src/client/resource.hpp | 1 + src/client/resource.rc | 1 + src/client/resources/mods_menu.lua | 118 +++++++++++++++++++ 10 files changed, 261 insertions(+), 33 deletions(-) create mode 100644 src/client/resources/mods_menu.lua diff --git a/src/client/component/command.cpp b/src/client/component/command.cpp index 760a3b0b..a765be2a 100644 --- a/src/client/component/command.cpp +++ b/src/client/component/command.cpp @@ -15,6 +15,7 @@ #include #include #include +#include namespace command { @@ -447,6 +448,62 @@ namespace command } }, scheduler::pipeline::server); }); + + add("loadmod", [](const params& params) + { + if (params.size() < 2) + { + game_console::print(game_console::con_type_info, "Usage: loadmod mods/"); + return; + } + + if (::game::SV_Loaded()) + { + game_console::print(game_console::con_type_error, "Cannot load mod while in-game!\n"); + game::CG_GameMessage(0, "^1Cannot unload mod while in-game!"); + return; + } + + const auto path = params.get(1); + game_console::print(game_console::con_type_info, "Loading mod %s\n", path); + + if (!utils::io::directory_exists(path)) + { + game_console::print(game_console::con_type_error, "Mod %s not found!\n", path); + return; + } + + game::mod_folder = path; + + ::scheduler::once([]() + { + command::execute("lui_restart", true); + }, ::scheduler::pipeline::renderer); + }); + + add("unloadmod", [](const params& params) + { + if (game::mod_folder.empty()) + { + game_console::print(game_console::con_type_info, "No mod loaded\n"); + return; + } + + if (::game::SV_Loaded()) + { + game_console::print(game_console::con_type_error, "Cannot unload mod while in-game!\n"); + game::CG_GameMessage(0, "^1Cannot unload mod while in-game!"); + return; + } + + game_console::print(game_console::con_type_info, "Unloading mod %s\n", game::mod_folder.data()); + game::mod_folder.clear(); + + ::scheduler::once([]() + { + command::execute("lui_restart", true); + }, ::scheduler::pipeline::renderer); + }); } }; } diff --git a/src/client/game/game.cpp b/src/client/game/game.cpp index fd4ae831..28261c72 100644 --- a/src/client/game/game.cpp +++ b/src/client/game/game.cpp @@ -11,6 +11,8 @@ namespace game base_address = uint64_t(module); } + std::string mod_folder{}; + namespace environment { launcher::mode mode = launcher::mode::none; diff --git a/src/client/game/game.hpp b/src/client/game/game.hpp index 6085c887..601179ad 100644 --- a/src/client/game/game.hpp +++ b/src/client/game/game.hpp @@ -6,9 +6,10 @@ namespace game { extern uint64_t base_address; - void load_base_address(); + extern std::string mod_folder; + namespace environment { launcher::mode get_mode(); diff --git a/src/client/game/scripting/lua/engine.cpp b/src/client/game/scripting/lua/engine.cpp index 821d4ec4..58008313 100644 --- a/src/client/game/scripting/lua/engine.cpp +++ b/src/client/game/scripting/lua/engine.cpp @@ -6,6 +6,7 @@ #include "../execution.hpp" #include +#include namespace scripting::lua::engine { @@ -17,12 +18,13 @@ namespace scripting::lua::engine return scripts; } - void load_scripts() + void load_generic_script() { get_scripts().push_back(std::make_unique()); + } - const auto script_dir = "scripts/"s; - + void load_scripts(const std::string& script_dir) + { if (!utils::io::directory_exists(script_dir)) { return; @@ -44,7 +46,17 @@ namespace scripting::lua::engine { clear_custom_fields(); get_scripts().clear(); - load_scripts(); + + load_generic_script(); + + load_scripts("scripts/"); + load_scripts("h2-mod/scripts/"); + load_scripts("data/scripts/"); + + if (!game::mod_folder.empty()) + { + load_scripts(utils::string::va("%s/scripts/", game::mod_folder.data())); + } } void stop() diff --git a/src/client/game/ui_scripting/lua/context.cpp b/src/client/game/ui_scripting/lua/context.cpp index 59ed9a9c..d9f5b298 100644 --- a/src/client/game/ui_scripting/lua/context.cpp +++ b/src/client/game/ui_scripting/lua/context.cpp @@ -1087,6 +1087,11 @@ namespace ui_scripting::lua return std::string(buffer); }; + game_type["getloadedmod"] = [](const game&) + { + return ::game::mod_folder; + }; + struct player { }; @@ -1242,9 +1247,8 @@ namespace ui_scripting::lua } } - context::context(std::string folder) - : folder_(std::move(folder)) - , scheduler_(state_) + context::context(std::string data, script_type type) + : scheduler_(state_) , event_handler_(state_) { @@ -1256,23 +1260,6 @@ namespace ui_scripting::lua sol::lib::math, sol::lib::table); - this->state_["include"] = [this](const std::string& file) - { - this->load_script(file); - }; - - sol::function old_require = this->state_["require"]; - auto base_path = utils::string::replace(this->folder_, "/", ".") + "."; - this->state_["require"] = [base_path, old_require](const std::string& path) - { - return old_require(base_path + path); - }; - - this->state_["scriptdir"] = [this]() - { - return this->folder_; - }; - setup_io(this->state_); setup_vector_type(this->state_); setup_element_type(this->state_, this->event_handler_, this->scheduler_); @@ -1280,8 +1267,35 @@ namespace ui_scripting::lua setup_game_type(this->state_, this->event_handler_, this->scheduler_); setup_lui_types(this->state_, this->event_handler_, this->scheduler_); - printf("Loading ui script '%s'\n", this->folder_.data()); - this->load_script("__init__"); + if (type == script_type::file) + { + this->folder_ = data; + + this->state_["include"] = [this](const std::string& file) + { + this->load_script(file); + }; + + sol::function old_require = this->state_["require"]; + auto base_path = utils::string::replace(this->folder_, "/", ".") + "."; + this->state_["require"] = [base_path, old_require](const std::string& path) + { + return old_require(base_path + path); + }; + + this->state_["scriptdir"] = [this]() + { + return this->folder_; + }; + + printf("Loading ui script '%s'\n", this->folder_.data()); + this->load_script("__init__"); + } + + if (type == script_type::code) + { + handle_error(this->state_.safe_script(data, &sol::script_pass_on_error)); + } } context::~context() diff --git a/src/client/game/ui_scripting/lua/context.hpp b/src/client/game/ui_scripting/lua/context.hpp index 70e2022d..78e6c57b 100644 --- a/src/client/game/ui_scripting/lua/context.hpp +++ b/src/client/game/ui_scripting/lua/context.hpp @@ -15,6 +15,12 @@ namespace ui_scripting::lua { + enum script_type + { + file, + code + }; + extern std::unordered_map menus; extern std::vector elements; extern element ui_element; @@ -23,7 +29,7 @@ namespace ui_scripting::lua class context { public: - context(std::string folder); + context(std::string data, script_type); ~context(); context(context&&) noexcept = delete; diff --git a/src/client/game/ui_scripting/lua/engine.cpp b/src/client/game/ui_scripting/lua/engine.cpp index da4b2107..000e96e1 100644 --- a/src/client/game/ui_scripting/lua/engine.cpp +++ b/src/client/game/ui_scripting/lua/engine.cpp @@ -7,11 +7,14 @@ #include #include +#include namespace ui_scripting::lua::engine { namespace { + const auto mods_menu_script = utils::nt::load_resource(LUI_MODS_MENU); + float screen_max[2]; void check_resize() @@ -302,10 +305,8 @@ namespace ui_scripting::lua::engine return scripts; } - void load_scripts() + void load_scripts(const std::string& script_dir) { - const auto script_dir = "ui_scripts/"s; - if (!utils::io::directory_exists(script_dir)) { return; @@ -317,11 +318,16 @@ namespace ui_scripting::lua::engine { if (std::filesystem::is_directory(script) && utils::io::file_exists(script + "/__init__.lua")) { - get_scripts().push_back(std::make_unique(script)); + get_scripts().push_back(std::make_unique(script, script_type::file)); } } } + void load_code(const std::string& code) + { + get_scripts().push_back(std::make_unique(code, script_type::code)); + } + void render_menus() { check_resize(); @@ -406,7 +412,17 @@ namespace ui_scripting::lua::engine close_all_menus(); get_scripts().clear(); clear_menus(); - load_scripts(); + + load_code(mods_menu_script); + + load_scripts("ui_scripts/"); + load_scripts("h2-mod/ui_scripts/"); + load_scripts("data/ui_scripts/"); + + if (!game::mod_folder.empty()) + { + load_scripts(utils::string::va("%s/ui_scripts/", game::mod_folder.data())); + } } void stop() diff --git a/src/client/resource.hpp b/src/client/resource.hpp index 50ce9bb4..e9af0a1b 100644 --- a/src/client/resource.hpp +++ b/src/client/resource.hpp @@ -22,3 +22,4 @@ #define ICON_IMAGE 313 #define LUA_ANIMATION_SCRIPT 314 +#define LUI_MODS_MENU 315 diff --git a/src/client/resource.rc b/src/client/resource.rc index ce4e7c05..f87531a4 100644 --- a/src/client/resource.rc +++ b/src/client/resource.rc @@ -98,6 +98,7 @@ ID_ICON ICON "resources/icon.ico" MENU_MAIN RCDATA "resources/main.html" LUA_ANIMATION_SCRIPT RCDATA "resources/animation.lua" +LUI_MODS_MENU RCDATA "resources/mods_menu.lua" #ifdef _DEBUG TLS_DLL RCDATA "../../build/bin/x64/Debug/tlsdll.dll" diff --git a/src/client/resources/mods_menu.lua b/src/client/resources/mods_menu.lua new file mode 100644 index 00000000..f47f410c --- /dev/null +++ b/src/client/resources/mods_menu.lua @@ -0,0 +1,118 @@ +function createdivider(menu, text) + local element = LUI.UIElement.new( { + leftAnchor = true, + rightAnchor = true, + left = 0, + right = 0, + topAnchor = true, + bottomAnchor = false, + top = 0, + bottom = 33.33 + }) + + element.scrollingToNext = true + element:addElement(LUI.MenuBuilder.BuildRegisteredType("h1_option_menu_titlebar", { + title_bar_text = Engine.ToUpperCase(Engine.Localize(text)) + })) + + menu.list:addElement(element) +end + +local maincampaign = LUI.MenuBuilder.m_types_build["main_campaign"] +LUI.MenuBuilder.m_types_build["main_campaign"] = function(a1, a2) + local menu = maincampaign(a1, a2) + local buttonlist = menu:getChildById("main_campaign_list") + + local button = menu:AddButton("$_MODS", function() + LUI.FlowManager.RequestAddMenu(nil, "mods_menu") + end, nil, true, nil, { + desc_text = "$_Open mods menu" + }) + + buttonlist:removeElement(button) + buttonlist:insertElement(button, 6) + button.id = "mods_menu-button" + + local hintbox = menu.optionTextInfo + local firstbutton = buttonlist:getFirstChild() + hintbox:dispatchEventToRoot({ + name = "set_button_info_text", + text = firstbutton.properties.desc_text, + immediate = true + }) + + menu:CreateBottomDivider() + menu:AddBottomDividerToList(buttonlist:getLastChild()) + menu:removeElement(menu.optionTextInfo) + + LUI.Options.InitScrollingList(menu.list, nil) + menu:CreateBottomDivider() + menu.optionTextInfo = LUI.Options.AddOptionTextInfo(menu) + + return menu +end + +function modsmenu(a1) + local menu = LUI.MenuTemplate.new(a1, { + menu_title = "$_MODS", + exclusiveController = 0, + menu_width = 400, + menu_top_indent = LUI.MenuTemplate.spMenuOffset, + showTopRightSmallBar = true + }) + + local modfolder = game:getloadedmod() + if (modfolder ~= "") then + createdivider(menu, "$_Loaded mod: " .. modfolder) + + menu:AddButton("$_UNLOAD", function() + game:executecommand("unloadmod") + end, nil, true, nil, { + desc_text = "Unload the currently loaded mod" + }) + end + + createdivider(menu, "$_Available mods") + + local mods = io.listfiles("mods/") + for i = 1, #mods do + local desc = "Load " .. mods[i] + local infofile = mods[i] .. "/mod.txt" + local exists = io.fileexists(infofile) + + if (exists) then + desc = io.readfile(infofile) + end + + if (mods[i] ~= modfolder) then + menu:AddButton("$_" .. mods[i], function() + game:executecommand("loadmod " .. mods[i]) + end, nil, true, nil, { + desc_text = desc + }) + end + end + + menu:AddBackButton(function(a1) + Engine.PlaySound(CoD.SFX.MenuBack) + LUI.FlowManager.RequestLeaveMenu(a1) + end) + + LUI.Options.InitScrollingList(menu.list, nil) + menu:CreateBottomDivider() + menu.optionTextInfo = LUI.Options.AddOptionTextInfo(menu) + + return menu +end + +LUI.MenuBuilder.m_types_build["mods_menu"] = modsmenu + +local localize = Engine.Localize +Engine.Localize = function(...) + local args = {...} + if (args[1]:sub(1, 2) == "$_") then + return args[1]:sub(3, -1) + end + + return localize(table.unpack(args)) +end \ No newline at end of file