mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-30 15:43:19 -04:00
Mods can be disabled by config
We now register a CVar for mod enablement for each mod. Also made LoadedMod stored in unique_ptr because we keep pointers to them already, and I'm entirely uncomfortable with keeping pointers to the vector directly.
This commit is contained in:
@@ -3,8 +3,10 @@
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <ranges>
|
||||
|
||||
#include "dusk/mod_api.h"
|
||||
#include "dusk/config_var.hpp"
|
||||
|
||||
namespace dusk::modding {
|
||||
class ModBundle;
|
||||
@@ -85,6 +87,8 @@ struct LoadedMod {
|
||||
std::string mod_path;
|
||||
std::string dir;
|
||||
|
||||
std::unique_ptr<ConfigVar<bool>> cvarIsEnabled;
|
||||
|
||||
bool active = false;
|
||||
bool load_failed = false;
|
||||
|
||||
@@ -106,10 +110,16 @@ public:
|
||||
void tick();
|
||||
void shutdown();
|
||||
|
||||
const std::vector<LoadedMod>& mods() const { return m_mods; }
|
||||
[[nodiscard]] auto mods() const {
|
||||
return m_mods | std::views::transform([](const auto& m) -> LoadedMod& { return *m; });
|
||||
}
|
||||
|
||||
[[nodiscard]] auto active_mods() const {
|
||||
return mods() | std::views::filter([](const auto& m) { return m.active; });
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<LoadedMod> m_mods;
|
||||
std::vector<std::unique_ptr<LoadedMod>> m_mods;
|
||||
std::filesystem::path m_modsDir;
|
||||
bool m_initialized = false;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "aurora/dvd.h"
|
||||
#include "dusk/config.hpp"
|
||||
#include "dusk/io.hpp"
|
||||
#include "miniz.h"
|
||||
#include "native_module.hpp"
|
||||
@@ -32,6 +33,10 @@ static constexpr std::string_view k_archSuffix = ""sv;
|
||||
|
||||
static dusk::ModLoader g_modLoader;
|
||||
|
||||
// We cannot delete config vars registered by mods until the game shuts down fully.
|
||||
// Therefore, orphan them during shutdown.
|
||||
static std::vector<std::unique_ptr<dusk::ConfigVarBase>> OrphanedConfigVars;
|
||||
|
||||
namespace dusk {
|
||||
|
||||
ModLoader& ModLoader::instance() {
|
||||
@@ -134,10 +139,11 @@ static ModMetadata loadMetadata(const std::filesystem::path& modPath, ModBundle&
|
||||
};
|
||||
}
|
||||
|
||||
static bool checkDuplicateMod(const ModMetadata& metadata, const std::vector<LoadedMod>& mods) {
|
||||
return std::ranges::any_of(mods, [&](const LoadedMod& mod) {
|
||||
return mod.metadata.id == metadata.id;
|
||||
});
|
||||
template <std::ranges::input_range TIter>
|
||||
bool checkDuplicateMod(
|
||||
const ModMetadata& metadata, TIter mods) {
|
||||
return std::ranges::any_of(mods,
|
||||
[&](const LoadedMod& mod) { return mod.metadata.id == metadata.id; });
|
||||
}
|
||||
|
||||
void ModLoader::tryLoadNativeMod(LoadedMod& mod) {
|
||||
@@ -219,6 +225,29 @@ void ModLoader::tryLoadNativeMod(LoadedMod& mod) {
|
||||
mod.native_status = NativeModStatus::Loaded;
|
||||
}
|
||||
|
||||
static std::string escapeModIdForConfig(std::string_view const id) {
|
||||
std::string buf;
|
||||
|
||||
// Simple escaping. All characters in mod IDs literal, except for '.' and '_'.
|
||||
// '.' -> '_', '_' -> '__'
|
||||
for (char const chr : id) {
|
||||
if (chr == '.') {
|
||||
buf.push_back('_');
|
||||
} else if (chr == '_') {
|
||||
buf.push_back('_');
|
||||
buf.push_back('_');
|
||||
} else {
|
||||
buf.push_back(chr);
|
||||
}
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static std::string modEnabledCVarName(std::string_view const id) {
|
||||
return fmt::format("mod.{}.enabled", escapeModIdForConfig(id));
|
||||
}
|
||||
|
||||
void ModLoader::tryLoadDusk(const std::filesystem::path& modPath, bool fromDir) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -241,7 +270,7 @@ void ModLoader::tryLoadDusk(const std::filesystem::path& modPath, bool fromDir)
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkDuplicateMod(metadata, m_mods)) {
|
||||
if (checkDuplicateMod(metadata, mods())) {
|
||||
Log.error(
|
||||
"mod with id '{}' already exists, not loading {}",
|
||||
metadata.id,
|
||||
@@ -249,11 +278,14 @@ void ModLoader::tryLoadDusk(const std::filesystem::path& modPath, bool fromDir)
|
||||
return;
|
||||
}
|
||||
|
||||
LoadedMod mod;
|
||||
const auto& inserted = m_mods.emplace_back(std::make_unique<LoadedMod>());
|
||||
|
||||
auto& mod = *inserted;
|
||||
mod.active = true;
|
||||
mod.mod_path = io::fs_path_to_string(fs::absolute(modPath));
|
||||
mod.metadata = std::move(metadata);
|
||||
mod.bundle = std::move(bundle);
|
||||
mod.cvarIsEnabled = std::make_unique<ConfigVar<bool>>(modEnabledCVarName(mod.metadata.id), true);
|
||||
|
||||
if (mod.metadata.hasCode) {
|
||||
mod.native_status = NativeModStatus::Unknown;
|
||||
@@ -267,14 +299,13 @@ void ModLoader::tryLoadDusk(const std::filesystem::path& modPath, bool fromDir)
|
||||
}
|
||||
}
|
||||
|
||||
const auto& inserted = m_mods.emplace_back(std::move(mod));
|
||||
|
||||
Log.info(
|
||||
"found '{}' ('{}') v{} by {} ({})",
|
||||
inserted.metadata.name,
|
||||
inserted.metadata.id,
|
||||
inserted.metadata.version,
|
||||
inserted.metadata.author,
|
||||
mod.metadata.name,
|
||||
mod.metadata.id,
|
||||
mod.metadata.version,
|
||||
mod.metadata.author,
|
||||
io::fs_path_to_string(modPath.filename()));
|
||||
}
|
||||
|
||||
@@ -317,14 +348,23 @@ void ModLoader::init() {
|
||||
|
||||
|
||||
Log.info("initializing {} mod(s)...", m_mods.size());
|
||||
for (auto& mod : m_mods) {
|
||||
if (mod.native && mod.active) {
|
||||
for (auto& mod : mods()) {
|
||||
Register(*mod.cvarIsEnabled);
|
||||
|
||||
if (!mod.cvarIsEnabled->getValue()) {
|
||||
Log.info("Mod '{}' is disabled by config", mod.metadata.id);
|
||||
mod.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& mod : active_mods()) {
|
||||
if (mod.native) {
|
||||
buildAPI(mod);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& mod : m_mods) {
|
||||
if (!mod.native || !mod.active) {
|
||||
for (auto& mod : active_mods()) {
|
||||
if (!mod.native) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -350,22 +390,20 @@ void ModLoader::init() {
|
||||
|
||||
initOverlayFiles();
|
||||
|
||||
auto active =
|
||||
std::count_if(m_mods.begin(), m_mods.end(), [](const LoadedMod& m) { return m.active; });
|
||||
auto active = std::ranges::count_if(mods(), [](const LoadedMod& m) { return m.active; });
|
||||
Log.info("{}/{} mod(s) active", active, m_mods.size());
|
||||
}
|
||||
|
||||
void ModLoader::tick() {
|
||||
for (auto& mod : m_mods) {
|
||||
if (!mod.active || !mod.native) {
|
||||
for (auto& mod : active_mods()) {
|
||||
if (!mod.native) {
|
||||
continue;
|
||||
}
|
||||
ModGuard guard(&mod);
|
||||
try {
|
||||
mod.native->fn_tick(&mod.native->api);
|
||||
} catch (const std::exception& e) {
|
||||
Log.error(
|
||||
"exception in {}.mod_tick(): {} — disabling", mod.metadata.id, e.what());
|
||||
Log.error("exception in {}.mod_tick(): {} — disabling", mod.metadata.id, e.what());
|
||||
mod.active = false;
|
||||
} catch (...) {
|
||||
Log.error("unknown exception in {}.mod_tick() — disabling", mod.metadata.id);
|
||||
@@ -375,7 +413,7 @@ void ModLoader::tick() {
|
||||
}
|
||||
|
||||
void ModLoader::shutdown() {
|
||||
for (auto& mod : m_mods) {
|
||||
for (auto& mod : mods()) {
|
||||
hookClearMod(&mod);
|
||||
if (mod.native && mod.native->fn_cleanup) {
|
||||
ModGuard guard(&mod);
|
||||
@@ -384,7 +422,10 @@ void ModLoader::shutdown() {
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
OrphanedConfigVars.emplace_back(std::move(mod.cvarIsEnabled));
|
||||
}
|
||||
|
||||
m_mods.clear();
|
||||
g_services.clear();
|
||||
Log.info("all mods unloaded");
|
||||
|
||||
@@ -114,10 +114,8 @@ void ModLoader::initOverlayFiles() {
|
||||
|
||||
std::vector<AuroraOverlayFile> files;
|
||||
|
||||
for (auto& mod : m_mods) {
|
||||
if (mod.active) {
|
||||
findOverlayFiles(files, mod);
|
||||
}
|
||||
for (auto& mod : active_mods()) {
|
||||
findOverlayFiles(files, mod);
|
||||
}
|
||||
|
||||
Log.debug("Found {} overlay files.", files.size());
|
||||
|
||||
Reference in New Issue
Block a user