Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lua mod INI file configuration #7615

Merged
merged 1 commit into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions Source/lua/lua.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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<decltype(&LuaPanic), &LuaPanic> } });
Expand All @@ -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),
Expand All @@ -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()
Expand Down
1 change: 1 addition & 0 deletions Source/lua/lua.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace devilution {

void LuaInitialize();
void LuaReloadActiveMods();
void LuaShutdown();
void LuaEvent(std::string_view name);
sol::state &GetLuaState();
Expand Down
46 changes: 46 additions & 0 deletions Source/options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string_view> ModOptions::GetActiveModList()
{
std::vector<std::string_view> modList;
for (auto &modEntry : GetModEntries()) {
if (*modEntry.enabled)
modList.emplace_back(modEntry.name);
}
return modList;
}

std::vector<std::string_view> ModOptions::GetModList()
{
std::vector<std::string_view> modList;
for (auto &modEntry : GetModEntries()) {
modList.emplace_back(modEntry.name);
}
return modList;
}

std::vector<OptionEntryBase *> ModOptions::GetEntries()
{
std::vector<OptionEntryBase *> optionEntries;
for (auto &modEntry : GetModEntries()) {
optionEntries.emplace_back(&modEntry.enabled);
}
return optionEntries;
}

std::vector<ModOptions::ModEntry> &ModOptions::GetModEntries()
{
if (modEntries)
return *modEntries;

std::vector<std::string> modNames = ini->getKeys(name);
std::vector<ModOptions::ModEntry> &newModEntries = modEntries.emplace();
for (auto &modName : modNames) {
newModEntries.emplace_back(modName);
}
return newModEntries;
}

namespace {
#ifdef DEVILUTIONX_RESAMPLER_SPEEX
constexpr char ResamplerSpeex[] = "Speex";
Expand Down
24 changes: 24 additions & 0 deletions Source/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,28 @@ struct PadmapperOptions : OptionCategoryBase {
bool CanDeferToMovementHandler(const Action &action) const;
};

struct ModOptions : OptionCategoryBase {
ModOptions();
std::vector<std::string_view> GetActiveModList();
std::vector<std::string_view> GetModList();
std::vector<OptionEntryBase *> 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<ModEntry> &GetModEntries();
std::optional<std::vector<ModEntry>> modEntries;
};

struct Options {
GameModeOptions GameMode;
StartUpOptions StartUp;
Expand All @@ -817,6 +839,7 @@ struct Options {
LanguageOptions Language;
KeymapperOptions Keymapper;
PadmapperOptions Padmapper;
ModOptions Mods;

[[nodiscard]] std::vector<OptionCategoryBase *> GetCategories()
{
Expand All @@ -834,6 +857,7 @@ struct Options {
&Chat,
&Keymapper,
&Padmapper,
&Mods,
};
}
};
Expand Down
28 changes: 22 additions & 6 deletions Source/utils/ini.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ namespace devilution {

namespace {

template <typename T>
bool OrderByValueIndex(const std::pair<std::string, T> &a, const std::pair<std::string, T> &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.
Expand Down Expand Up @@ -74,6 +80,22 @@ Ini::Values::Values(const Value &data)
{
}

std::vector<std::string> Ini::getKeys(std::string_view section) const
{
const auto sectionIt = data_.sections.find(section);
if (sectionIt == data_.sections.end()) return {};

std::vector<std::pair<std::string, ValuesData>> entries(sectionIt->second.entries.begin(), sectionIt->second.entries.end());
c_sort(entries, OrderByValueIndex<ValuesData>);

std::vector<std::string> keys;
StephenCWills marked this conversation as resolved.
Show resolved Hide resolved
keys.reserve(entries.size());
for (const auto &[key, _] : entries) {
keys.push_back(key);
}
return keys;
}

std::span<const Ini::Value> Ini::Values::get() const
{
if (std::holds_alternative<Ini::Value>(rep_)) {
Expand Down Expand Up @@ -367,12 +389,6 @@ void Ini::set(std::string_view section, std::string_view key, float value)

namespace {

template <typename T>
bool OrderByValueIndex(const std::pair<std::string, T> &a, const std::pair<std::string, T> &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)
{
Expand Down
3 changes: 3 additions & 0 deletions Source/utils/ini.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ class Ini {
static tl::expected<Ini, std::string> parse(std::string_view buffer);
[[nodiscard]] std::string serialize() const;

/** @return all the keys associated with this section in the ini */
[[nodiscard]] std::vector<std::string> getKeys(std::string_view section) const;

/** @return all the values associated with this section and key in the ini */
[[nodiscard]] std::span<const Value> get(std::string_view section, std::string_view key) const;

Expand Down
6 changes: 3 additions & 3 deletions assets/lua/devilutionx/events.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
Loading