From 83017e8b8f7fffb935fd72bee5e356f972c1e6dd Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 7 Apr 2023 00:48:04 -0700 Subject: [PATCH] give active mods a chance to reattach their hooks --- docs/changelog.txt | 1 + docs/guides/modding-guide.rst | 5 +++++ library/Core.cpp | 5 ++++- library/LuaApi.cpp | 2 +- library/lua/script-manager.lua | 41 ++++++++++++++++++++++------------ 5 files changed, 38 insertions(+), 16 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 2ae96aa86..a0919c602 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -46,6 +46,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `buildingplan`: items in the item selection dialog should now use the same item quality symbols as the base game - Mods: scripts in mods that are only in the steam workshop directory are now accessible. this means that a script-only mod that you never mark as "active" when generating a world will still receive automatic updates and be usable from in-game - Mods: scripts from only the most recent version of an installed mod are added to the script path +- Mods: give active mods a chance to reattach their load hooks when a world is reloaded - `gui/control-panel`: bugfix services are now enabled by default ## Documentation diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 0c9513fef..38117503c 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -461,6 +461,11 @@ Ok, you're all set up! Now, let's take a look at an example dfhack.onStateChange[GLOBAL_KEY] = function(sc) if sc == SC_MAP_UNLOADED then dfhack.run_command('disable', 'example-mod') + + -- ensure our mod doesn't try to enable itself when a different + -- world is loaded where we are *not* active + dfhack.onStateChange[GLOBAL_KEY] = nil + return end diff --git a/library/Core.cpp b/library/Core.cpp index d13c82fa5..97c69946e 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -2148,7 +2148,10 @@ void Core::onStateChange(color_ostream &out, state_change_event event) loadModScriptPaths(out); auto L = Lua::Core::State; Lua::StackUnwinder top(L); - Lua::CallLuaModuleFunction(con, L, "script-manager", "reload"); + Lua::CallLuaModuleFunction(con, L, "script-manager", "reload", 1, 0, + [](lua_State* L) { + Lua::Push(L, true); + }); // fallthrough } case SC_WORLD_UNLOADED: diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index d30dc7f77..08903c7bd 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2729,7 +2729,7 @@ static int filesystem_listdir_recursive(lua_State *L) include_prefix = lua_toboolean(L, 3); std::map files; int err = DFHack::Filesystem::listdir_recursive(dir, files, depth, include_prefix); - if (err != -1) { + if (err != 0 && err != -1) { lua_pushnil(L); lua_pushstring(L, strerror(err)); lua_pushinteger(L, err); diff --git a/library/lua/script-manager.lua b/library/lua/script-manager.lua index 1a7161a10..450012357 100644 --- a/library/lua/script-manager.lua +++ b/library/lua/script-manager.lua @@ -6,22 +6,27 @@ local utils = require('utils') -- enabled API -- for each script that can be loaded as a module, calls cb(script_name, env) -function foreach_module_script(cb) +function foreach_module_script(cb, preprocess_script_file_fn) for _,script_path in ipairs(dfhack.internal.getScriptPaths()) do local files = dfhack.filesystem.listdir_recursive( script_path, nil, false) if not files then goto skip_path end for _,f in ipairs(files) do - if not f.isdir and - f.path:endswith('.lua') and - not f.path:startswith('test/') and - not f.path:startswith('internal/') then - local script_name = f.path:sub(1, #f.path - 4) -- remove '.lua' - local ok, script_env = pcall(reqscript, script_name) - if ok then - cb(script_name, script_env) - end + if f.isdir or not f.path:endswith('.lua') or + f.path:startswith('.git') or + f.path:startswith('test/') or + f.path:startswith('internal/') then + goto continue end + if preprocess_script_file_fn then + preprocess_script_file_fn(script_path, f.path) + end + local script_name = f.path:sub(1, #f.path - 4) -- remove '.lua' + local ok, script_env = pcall(reqscript, script_name) + if ok then + cb(script_name, script_env) + end + ::continue:: end ::skip_path:: end @@ -42,9 +47,17 @@ local function process_script(env_name, env) enabled_map[env_name] = fn end -function reload() +function reload(refresh_active_mod_scripts) enabled_map = utils.OrderedTable() - foreach_module_script(process_script) + local force_refresh_fn = refresh_active_mod_scripts and function(script_path, script_name) + if script_path:find('scripts_modactive') then + internal_script = dfhack.internal.scripts[script_path..'/'..script_name] + if internal_script then + internal_script.env = nil + end + end + end or nil + foreach_module_script(process_script, force_refresh_fn) end local function ensure_loaded() @@ -97,7 +110,7 @@ end local function add_script_path(mod_script_paths, path) if dfhack.filesystem.isdir(path) then - print('indexing scripts from mod script path: ' .. path) + print('indexing mod scripts: ' .. path) table.insert(mod_script_paths, path) end end @@ -120,7 +133,7 @@ function get_mod_script_paths() -- if a world is loaded, process active mods first, and lock to active version if dfhack.isWorldLoaded() then for _,path in ipairs(df.global.world.object_loader.object_load_order_src_dir) do - path = tostring(path) + path = tostring(path.value) if not path:startswith(INSTALLED_MODS_PATH) then goto continue end local id = get_mod_id_and_version(path) if not id then goto continue end