From 38309987c4abb58400b0863bb6c0411a01a6d4ea Mon Sep 17 00:00:00 2001 From: staphen Date: Fri, 11 Oct 2024 13:12:17 -0400 Subject: [PATCH] Lua mod ini file configuration --- Source/lua/lua.cpp | 25 +++++++++++------ Source/lua/lua.hpp | 1 + Source/options.cpp | 46 +++++++++++++++++++++++++++++++ Source/options.h | 24 ++++++++++++++++ Source/utils/ini.cpp | 28 +++++++++++++++---- Source/utils/ini.hpp | 3 ++ assets/lua/devilutionx/events.lua | 6 ++-- 7 files changed, 116 insertions(+), 17 deletions(-) diff --git a/Source/lua/lua.cpp b/Source/lua/lua.cpp index 26ac7859394..7b89d3b3623 100644 --- a/Source/lua/lua.cpp +++ b/Source/lua/lua.cpp @@ -13,6 +13,7 @@ #include "lua/modules/audio.hpp" #include "lua/modules/log.hpp" #include "lua/modules/render.hpp" +#include "options.h" #include "plrmsg.h" #include "utils/console.h" #include "utils/log.hpp" @@ -193,6 +194,20 @@ sol::environment CreateLuaSandbox() return sandbox; } +void LuaReloadActiveMods() +{ + // Loaded without a sandbox. + CurrentLuaState->events = RunScript(/*env=*/std::nullopt, "devilutionx.events", /*optional=*/false); + CurrentLuaState->commonPackages["devilutionx.events"] = CurrentLuaState->events; + + for (std::string_view modname : sgOptions.Mods.GetActiveModList()) { + std::string packageName = StrCat("mods.", modname, ".init"); + RunScript(CreateLuaSandbox(), packageName, /*optional=*/true); + } + + LuaEvent("LoadModsComplete"); +} + void LuaInitialize() { CurrentLuaState.emplace(LuaState { .sol = { sol::c_call } }); @@ -212,9 +227,6 @@ void LuaInitialize() // Registering devilutionx object table SafeCallResult(lua.safe_script(RequireGenSrc), /*optional=*/false); - // Loaded without a sandbox. - CurrentLuaState->events = RunScript(/*env=*/std::nullopt, "devilutionx.events", /*optional=*/false); - CurrentLuaState->commonPackages = lua.create_table_with( #ifdef _DEBUG "devilutionx.dev", LuaDevModule(lua), @@ -224,16 +236,13 @@ void LuaInitialize() "devilutionx.audio", LuaAudioModule(lua), "devilutionx.render", LuaRenderModule(lua), "devilutionx.message", [](std::string_view text) { EventPlrMsg(text, UiFlags::ColorRed); }, - // These packages are loaded without a sandbox: - "devilutionx.events", CurrentLuaState->events, + // This package is loaded without a sandbox: "inspect", RunScript(/*env=*/std::nullopt, "inspect", /*optional=*/false)); // Used by the custom require implementation. lua["setEnvironment"] = [](const sol::environment &env, const sol::function &fn) { sol::set_environment(env, fn); }; - RunScript(CreateLuaSandbox(), "user", /*optional=*/true); - - LuaEvent("GameBoot"); + LuaReloadActiveMods(); } void LuaShutdown() diff --git a/Source/lua/lua.hpp b/Source/lua/lua.hpp index 3efb8fcec2c..afe360f315e 100644 --- a/Source/lua/lua.hpp +++ b/Source/lua/lua.hpp @@ -8,6 +8,7 @@ namespace devilution { void LuaInitialize(); +void LuaReloadActiveMods(); void LuaShutdown(); void LuaEvent(std::string_view name); sol::state &GetLuaState(); diff --git a/Source/options.cpp b/Source/options.cpp index c1a095e07fa..6f8b1c9b212 100644 --- a/Source/options.cpp +++ b/Source/options.cpp @@ -1787,6 +1787,52 @@ bool PadmapperOptions::CanDeferToMovementHandler(const Action &action) const ControllerButton_BUTTON_DPAD_RIGHT); } +ModOptions::ModOptions() + : OptionCategoryBase("Mods", N_("Mods"), N_("Mod Settings")) +{ +} + +std::vector ModOptions::GetActiveModList() +{ + std::vector modList; + for (auto &modEntry : GetModEntries()) { + if (*modEntry.enabled) + modList.emplace_back(modEntry.name); + } + return modList; +} + +std::vector ModOptions::GetModList() +{ + std::vector modList; + for (auto &modEntry : GetModEntries()) { + modList.emplace_back(modEntry.name); + } + return modList; +} + +std::vector ModOptions::GetEntries() +{ + std::vector optionEntries; + for (auto &modEntry : GetModEntries()) { + optionEntries.emplace_back(&modEntry.enabled); + } + return optionEntries; +} + +std::vector &ModOptions::GetModEntries() +{ + if (modEntries) + return *modEntries; + + std::vector modNames = ini->getKeys(name); + std::vector &newModEntries = modEntries.emplace(); + for (auto &modName : modNames) { + newModEntries.emplace_back(modName); + } + return newModEntries; +} + namespace { #ifdef DEVILUTIONX_RESAMPLER_SPEEX constexpr char ResamplerSpeex[] = "Speex"; diff --git a/Source/options.h b/Source/options.h index f365b50ca76..d63ecfecb15 100644 --- a/Source/options.h +++ b/Source/options.h @@ -803,6 +803,28 @@ struct PadmapperOptions : OptionCategoryBase { bool CanDeferToMovementHandler(const Action &action) const; }; +struct ModOptions : OptionCategoryBase { + ModOptions(); + std::vector GetActiveModList(); + std::vector GetModList(); + std::vector GetEntries() override; + +private: + struct ModEntry { + ModEntry(std::string_view name) + : name(name) + , enabled(this->name, OptionEntryFlags::None, this->name.c_str(), "", false) + { + } + + std::string name; + OptionEntryBoolean enabled; + }; + + std::vector &GetModEntries(); + std::optional> modEntries; +}; + struct Options { GameModeOptions GameMode; StartUpOptions StartUp; @@ -817,6 +839,7 @@ struct Options { LanguageOptions Language; KeymapperOptions Keymapper; PadmapperOptions Padmapper; + ModOptions Mods; [[nodiscard]] std::vector GetCategories() { @@ -834,6 +857,7 @@ struct Options { &Chat, &Keymapper, &Padmapper, + &Mods, }; } }; diff --git a/Source/utils/ini.cpp b/Source/utils/ini.cpp index 24933d337ec..562ecd7d16b 100644 --- a/Source/utils/ini.cpp +++ b/Source/utils/ini.cpp @@ -30,6 +30,12 @@ namespace devilution { namespace { +template +bool OrderByValueIndex(const std::pair &a, const std::pair &b) +{ + return a.second.index < b.second.index; +}; + // Returns a pointer to the first non-leading whitespace. // Only ' ' and '\t' are considered whitespace. // Requires: begin <= end. @@ -74,6 +80,22 @@ Ini::Values::Values(const Value &data) { } +std::vector Ini::getKeys(std::string_view section) const +{ + const auto sectionIt = data_.sections.find(section); + if (sectionIt == data_.sections.end()) return {}; + + std::vector> entries(sectionIt->second.entries.begin(), sectionIt->second.entries.end()); + c_sort(entries, OrderByValueIndex); + + std::vector keys; + keys.reserve(entries.size()); + for (const auto &[key, _] : entries) { + keys.push_back(key); + } + return keys; +} + std::span Ini::Values::get() const { if (std::holds_alternative(rep_)) { @@ -367,12 +389,6 @@ void Ini::set(std::string_view section, std::string_view key, float value) namespace { -template -bool OrderByValueIndex(const std::pair &a, const std::pair &b) -{ - return a.second.index < b.second.index; -}; - // Appends a possibly multi-line comment, converting \n to \r\n. void AppendComment(std::string_view comment, std::string &out) { diff --git a/Source/utils/ini.hpp b/Source/utils/ini.hpp index dee4cbb97d3..119453de074 100644 --- a/Source/utils/ini.hpp +++ b/Source/utils/ini.hpp @@ -54,6 +54,9 @@ class Ini { static tl::expected parse(std::string_view buffer); [[nodiscard]] std::string serialize() const; + /** @return all the keys associated with this section in the ini */ + [[nodiscard]] std::vector getKeys(std::string_view section) const; + /** @return all the values associated with this section and key in the ini */ [[nodiscard]] std::span get(std::string_view section, std::string_view key) const; diff --git a/assets/lua/devilutionx/events.lua b/assets/lua/devilutionx/events.lua index fd79130072b..3847171aa7d 100644 --- a/assets/lua/devilutionx/events.lua +++ b/assets/lua/devilutionx/events.lua @@ -40,9 +40,9 @@ local function CreateEvent() end local events = { - ---Called early on game boot. - GameBoot = CreateEvent(), - __doc_GameBoot = "Called early on game boot.", + ---Called after all mods have been loaded. + LoadModsComplete = CreateEvent(), + __doc_LoadModsComplete = "Called after all mods have been loaded.", ---Called every time a new game is started. GameStart = CreateEvent(),