From 76bacee2382298c857afc3c3cca093900dceef7f Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sat, 11 Mar 2023 02:07:59 -0800 Subject: [PATCH] dynamically add mod scripts to the script path --- docs/changelog.txt | 1 + library/Core.cpp | 101 +++++++++++++++++++++++++++++++++++------ library/include/Core.h | 3 +- 3 files changed, 91 insertions(+), 14 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index d70b7882c..f968d79ac 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -55,6 +55,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `automelt`: now allows metal chests to be melted (workaround for DF bug 2493 is no longer needed) - `orders`: add minimize button to overlay panel so you can get it out of the way to read long statue descriptions when choosing a subject in the details screen - `enable`: can now interpret aliases defined with the `alias` command +- scripts in installed mods are now automatically added to the DFHack script path. DFHack recognizes two directories in a mod's folder: ``scripts_modinstalled/`` and ``scripts_modactive/``. ``scripts_modinstalled/`` folders will always be added the script path, regardless of whether the mod is active in a world. ``scripts_modactive/`` folders will only be added to the script path when the mod is active in the current loaded world. ## Documentation - ``untested`` tag has been renamed to ``unavailable`` to better reflect the status of the remaining unavaialable tools. all of the simply "untested" tools have now been tested and marked as working. the remaining tools are known to need development work before they are available again. diff --git a/library/Core.cpp b/library/Core.cpp index d55959ceb..1bca0f5ed 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -105,6 +105,7 @@ DBG_DECLARE(core,script,DebugCategory::LINFO); static const std::string CONFIG_PATH = "dfhack-config/"; static const std::string CONFIG_DEFAULTS_PATH = "hack/data/dfhack-config-defaults/"; +static const std::string MOD_PATH = "data/installed_mods/"; class MainThread { public: @@ -440,6 +441,12 @@ bool Core::addScriptPath(std::string path, bool search_before) return true; } +bool Core::setModScriptPaths(const std::vector &mod_script_paths) { + std::lock_guard lock(script_path_mutex); + script_paths[2] = mod_script_paths; + return true; +} + bool Core::removeScriptPath(std::string path) { std::lock_guard lock(script_path_mutex); @@ -464,20 +471,21 @@ void Core::getScriptPaths(std::vector *dest) std::lock_guard lock(script_path_mutex); dest->clear(); std::string df_path = this->p->getPath() + "/"; - for (auto it = script_paths[0].begin(); it != script_paths[0].end(); ++it) - dest->push_back(*it); + for (auto & path : script_paths[0]) + dest->emplace_back(path); dest->push_back(df_path + CONFIG_PATH + "scripts"); if (df::global::world && isWorldLoaded()) { std::string save = World::ReadWorldFolder(); if (save.size()) - dest->push_back(df_path + "/save/" + save + "/scripts"); + dest->emplace_back(df_path + "save/" + save + "/scripts"); } - dest->push_back(df_path + "/hack/scripts"); - for (auto it = script_paths[1].begin(); it != script_paths[1].end(); ++it) - dest->push_back(*it); + dest->emplace_back(df_path + "hack/scripts"); + for (auto & path : script_paths[2]) + dest->emplace_back(path); + for (auto & path : script_paths[1]) + dest->emplace_back(path); } - std::string Core::findScript(std::string name) { std::vector paths; @@ -526,6 +534,61 @@ bool loadScriptPaths(color_ostream &out, bool silent = false) return true; } +bool loadModScriptPaths(color_ostream &out) { + std::map files; + Filesystem::listdir_recursive(MOD_PATH, files, 0); + + DEBUG(script,out).print("found %zd installed mods\n", files.size()); + if (!files.size()) + return true; + + for (auto & entry : files) { + DEBUG(script,out).print(" %s\n", entry.first.c_str()); + } + + std::vector mod_paths; + if (Core::getInstance().isWorldLoaded()) { + DEBUG(script,out).print("active load order:\n"); + for (auto & path : df::global::world->object_loader.object_load_order_src_dir) { + DEBUG(script,out).print(" %s\n", path->c_str()); + if (0 == path->find(MOD_PATH)) + mod_paths.emplace_back(*path); + } + } + + std::vector mod_script_paths; + for (auto pathit = mod_paths.rbegin(); pathit != mod_paths.rend(); ++pathit) { + std::string active_path = *pathit + "scripts_modactive"; + std::string installed_path = *pathit + "scripts_modinstalled"; + DEBUG(script,out).print("checking active path: %s\n", pathit->c_str()); + if (Filesystem::isdir(active_path)) + mod_script_paths.emplace_back(active_path); + if (Filesystem::isdir(installed_path)) + mod_script_paths.emplace_back(installed_path); + std::string slashless = *pathit; + slashless.resize(slashless.size()-1); + if (0 == files.erase(slashless)) + WARN(script,out).print("script path not found: '%s'\n", pathit->c_str()); + } + + for (auto & entry : files) { + if (!entry.second) + continue; + DEBUG(script,out).print("checking inactive path: %s\n", entry.first.c_str()); + std::string installed_path = entry.first + "/scripts_modinstalled"; + if (Filesystem::isdir(installed_path)) + mod_script_paths.emplace_back(installed_path); + } + + DEBUG(script,out).print("final mod script paths:\n"); + for (auto & path : mod_script_paths) + DEBUG(script,out).print(" %s\n", path.c_str()); + + Core::getInstance().setModScriptPaths(mod_script_paths); + + return true; +} + static std::map state_change_event_map; static void sc_event_map_init() { if (!state_change_event_map.size()) @@ -2113,14 +2176,22 @@ void Core::onStateChange(color_ostream &out, state_change_event event) switch (event) { case SC_CORE_INITIALIZED: - { - auto L = Lua::Core::State; - Lua::StackUnwinder top(L); - Lua::CallLuaModuleFunction(con, L, "helpdb", "refresh"); - Lua::CallLuaModuleFunction(con, L, "script-manager", "reload"); - } + { + loadModScriptPaths(out); + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + Lua::CallLuaModuleFunction(con, L, "helpdb", "refresh"); + Lua::CallLuaModuleFunction(con, L, "script-manager", "reload"); break; + } case SC_WORLD_LOADED: + { + loadModScriptPaths(out); + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + Lua::CallLuaModuleFunction(con, L, "script-manager", "reload"); + // fallthrough + } case SC_WORLD_UNLOADED: case SC_MAP_LOADED: case SC_MAP_UNLOADED: @@ -2185,6 +2256,10 @@ void Core::onStateChange(color_ostream &out, state_change_event event) if (event == SC_WORLD_UNLOADED) { Persistence::Internal::clear(); + loadModScriptPaths(out); + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + Lua::CallLuaModuleFunction(con, L, "script-manager", "reload"); } } diff --git a/library/include/Core.h b/library/include/Core.h index 8232708b5..2e022d6ca 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -158,6 +158,7 @@ namespace DFHack bool loadScriptFile(color_ostream &out, std::string fname, bool silent = false); bool addScriptPath(std::string path, bool search_before = false); + bool setModScriptPaths(const std::vector &mod_script_paths); bool removeScriptPath(std::string path); std::string findScript(std::string name); void getScriptPaths(std::vector *dest); @@ -239,7 +240,7 @@ namespace DFHack std::vector> allModules; DFHack::PluginManager * plug_mgr; - std::vector script_paths[2]; + std::vector script_paths[3]; std::mutex script_path_mutex; // hotkey-related stuff