give active mods a chance to reattach their hooks

develop
Myk Taylor 2023-04-07 00:48:04 -07:00
parent 00445767c7
commit 83017e8b8f
No known key found for this signature in database
5 changed files with 38 additions and 16 deletions

@ -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 - `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 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: 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 - `gui/control-panel`: bugfix services are now enabled by default
## Documentation ## Documentation

@ -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) dfhack.onStateChange[GLOBAL_KEY] = function(sc)
if sc == SC_MAP_UNLOADED then if sc == SC_MAP_UNLOADED then
dfhack.run_command('disable', 'example-mod') 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 return
end end

@ -2148,7 +2148,10 @@ void Core::onStateChange(color_ostream &out, state_change_event event)
loadModScriptPaths(out); loadModScriptPaths(out);
auto L = Lua::Core::State; auto L = Lua::Core::State;
Lua::StackUnwinder top(L); 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 // fallthrough
} }
case SC_WORLD_UNLOADED: case SC_WORLD_UNLOADED:

@ -2729,7 +2729,7 @@ static int filesystem_listdir_recursive(lua_State *L)
include_prefix = lua_toboolean(L, 3); include_prefix = lua_toboolean(L, 3);
std::map<std::string, bool> files; std::map<std::string, bool> files;
int err = DFHack::Filesystem::listdir_recursive(dir, files, depth, include_prefix); int err = DFHack::Filesystem::listdir_recursive(dir, files, depth, include_prefix);
if (err != -1) { if (err != 0 && err != -1) {
lua_pushnil(L); lua_pushnil(L);
lua_pushstring(L, strerror(err)); lua_pushstring(L, strerror(err));
lua_pushinteger(L, err); lua_pushinteger(L, err);

@ -6,22 +6,27 @@ local utils = require('utils')
-- enabled API -- enabled API
-- for each script that can be loaded as a module, calls cb(script_name, env) -- 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 for _,script_path in ipairs(dfhack.internal.getScriptPaths()) do
local files = dfhack.filesystem.listdir_recursive( local files = dfhack.filesystem.listdir_recursive(
script_path, nil, false) script_path, nil, false)
if not files then goto skip_path end if not files then goto skip_path end
for _,f in ipairs(files) do for _,f in ipairs(files) do
if not f.isdir and if f.isdir or not f.path:endswith('.lua') or
f.path:endswith('.lua') and f.path:startswith('.git') or
not f.path:startswith('test/') and f.path:startswith('test/') or
not f.path:startswith('internal/') then f.path:startswith('internal/') then
local script_name = f.path:sub(1, #f.path - 4) -- remove '.lua' goto continue
local ok, script_env = pcall(reqscript, script_name)
if ok then
cb(script_name, script_env)
end
end 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 end
::skip_path:: ::skip_path::
end end
@ -42,9 +47,17 @@ local function process_script(env_name, env)
enabled_map[env_name] = fn enabled_map[env_name] = fn
end end
function reload() function reload(refresh_active_mod_scripts)
enabled_map = utils.OrderedTable() 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 end
local function ensure_loaded() local function ensure_loaded()
@ -97,7 +110,7 @@ end
local function add_script_path(mod_script_paths, path) local function add_script_path(mod_script_paths, path)
if dfhack.filesystem.isdir(path) then 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) table.insert(mod_script_paths, path)
end end
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 a world is loaded, process active mods first, and lock to active version
if dfhack.isWorldLoaded() then if dfhack.isWorldLoaded() then
for _,path in ipairs(df.global.world.object_loader.object_load_order_src_dir) do 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 if not path:startswith(INSTALLED_MODS_PATH) then goto continue end
local id = get_mod_id_and_version(path) local id = get_mod_id_and_version(path)
if not id then goto continue end if not id then goto continue end