Add mod loading menu

This commit is contained in:
Federico Cecchetto 2022-01-28 00:27:53 +01:00
parent f8e689397b
commit b67012622d
10 changed files with 261 additions and 33 deletions

View File

@ -15,6 +15,7 @@
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/string.hpp> #include <utils/string.hpp>
#include <utils/memory.hpp> #include <utils/memory.hpp>
#include <utils/io.hpp>
namespace command namespace command
{ {
@ -447,6 +448,62 @@ namespace command
} }
}, scheduler::pipeline::server); }, scheduler::pipeline::server);
}); });
add("loadmod", [](const params& params)
{
if (params.size() < 2)
{
game_console::print(game_console::con_type_info, "Usage: loadmod mods/<modname>");
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);
});
} }
}; };
} }

View File

@ -11,6 +11,8 @@ namespace game
base_address = uint64_t(module); base_address = uint64_t(module);
} }
std::string mod_folder{};
namespace environment namespace environment
{ {
launcher::mode mode = launcher::mode::none; launcher::mode mode = launcher::mode::none;

View File

@ -6,9 +6,10 @@
namespace game namespace game
{ {
extern uint64_t base_address; extern uint64_t base_address;
void load_base_address(); void load_base_address();
extern std::string mod_folder;
namespace environment namespace environment
{ {
launcher::mode get_mode(); launcher::mode get_mode();

View File

@ -6,6 +6,7 @@
#include "../execution.hpp" #include "../execution.hpp"
#include <utils/io.hpp> #include <utils/io.hpp>
#include <utils/string.hpp>
namespace scripting::lua::engine namespace scripting::lua::engine
{ {
@ -17,12 +18,13 @@ namespace scripting::lua::engine
return scripts; return scripts;
} }
void load_scripts() void load_generic_script()
{ {
get_scripts().push_back(std::make_unique<context>()); get_scripts().push_back(std::make_unique<context>());
}
const auto script_dir = "scripts/"s; void load_scripts(const std::string& script_dir)
{
if (!utils::io::directory_exists(script_dir)) if (!utils::io::directory_exists(script_dir))
{ {
return; return;
@ -44,7 +46,17 @@ namespace scripting::lua::engine
{ {
clear_custom_fields(); clear_custom_fields();
get_scripts().clear(); 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() void stop()

View File

@ -1087,6 +1087,11 @@ namespace ui_scripting::lua
return std::string(buffer); return std::string(buffer);
}; };
game_type["getloadedmod"] = [](const game&)
{
return ::game::mod_folder;
};
struct player struct player
{ {
}; };
@ -1242,9 +1247,8 @@ namespace ui_scripting::lua
} }
} }
context::context(std::string folder) context::context(std::string data, script_type type)
: folder_(std::move(folder)) : scheduler_(state_)
, scheduler_(state_)
, event_handler_(state_) , event_handler_(state_)
{ {
@ -1256,23 +1260,6 @@ namespace ui_scripting::lua
sol::lib::math, sol::lib::math,
sol::lib::table); 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_io(this->state_);
setup_vector_type(this->state_); setup_vector_type(this->state_);
setup_element_type(this->state_, this->event_handler_, this->scheduler_); 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_game_type(this->state_, this->event_handler_, this->scheduler_);
setup_lui_types(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()); if (type == script_type::file)
this->load_script("__init__"); {
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() context::~context()

View File

@ -15,6 +15,12 @@
namespace ui_scripting::lua namespace ui_scripting::lua
{ {
enum script_type
{
file,
code
};
extern std::unordered_map<std::string, menu> menus; extern std::unordered_map<std::string, menu> menus;
extern std::vector<element*> elements; extern std::vector<element*> elements;
extern element ui_element; extern element ui_element;
@ -23,7 +29,7 @@ namespace ui_scripting::lua
class context class context
{ {
public: public:
context(std::string folder); context(std::string data, script_type);
~context(); ~context();
context(context&&) noexcept = delete; context(context&&) noexcept = delete;

View File

@ -7,11 +7,14 @@
#include <utils/io.hpp> #include <utils/io.hpp>
#include <utils/string.hpp> #include <utils/string.hpp>
#include <utils/nt.hpp>
namespace ui_scripting::lua::engine namespace ui_scripting::lua::engine
{ {
namespace namespace
{ {
const auto mods_menu_script = utils::nt::load_resource(LUI_MODS_MENU);
float screen_max[2]; float screen_max[2];
void check_resize() void check_resize()
@ -302,10 +305,8 @@ namespace ui_scripting::lua::engine
return scripts; 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)) if (!utils::io::directory_exists(script_dir))
{ {
return; return;
@ -317,11 +318,16 @@ namespace ui_scripting::lua::engine
{ {
if (std::filesystem::is_directory(script) && utils::io::file_exists(script + "/__init__.lua")) if (std::filesystem::is_directory(script) && utils::io::file_exists(script + "/__init__.lua"))
{ {
get_scripts().push_back(std::make_unique<context>(script)); get_scripts().push_back(std::make_unique<context>(script, script_type::file));
} }
} }
} }
void load_code(const std::string& code)
{
get_scripts().push_back(std::make_unique<context>(code, script_type::code));
}
void render_menus() void render_menus()
{ {
check_resize(); check_resize();
@ -406,7 +412,17 @@ namespace ui_scripting::lua::engine
close_all_menus(); close_all_menus();
get_scripts().clear(); get_scripts().clear();
clear_menus(); 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() void stop()

View File

@ -22,3 +22,4 @@
#define ICON_IMAGE 313 #define ICON_IMAGE 313
#define LUA_ANIMATION_SCRIPT 314 #define LUA_ANIMATION_SCRIPT 314
#define LUI_MODS_MENU 315

View File

@ -98,6 +98,7 @@ ID_ICON ICON "resources/icon.ico"
MENU_MAIN RCDATA "resources/main.html" MENU_MAIN RCDATA "resources/main.html"
LUA_ANIMATION_SCRIPT RCDATA "resources/animation.lua" LUA_ANIMATION_SCRIPT RCDATA "resources/animation.lua"
LUI_MODS_MENU RCDATA "resources/mods_menu.lua"
#ifdef _DEBUG #ifdef _DEBUG
TLS_DLL RCDATA "../../build/bin/x64/Debug/tlsdll.dll" TLS_DLL RCDATA "../../build/bin/x64/Debug/tlsdll.dll"

View File

@ -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