@ -2,23 +2,31 @@ local _ENV = mkmodule('script-manager')
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
@ -39,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 ( )
@ -57,4 +73,107 @@ function list()
end
end
---------------------
-- mod script paths
-- this perhaps could/should be queried from the Steam API
-- are there any installation configurations where this will be wrong, though?
local WORKSHOP_MODS_PATH = ' ../../workshop/content/975370/ '
local MODS_PATH = ' mods/ '
local INSTALLED_MODS_PATH = ' data/installed_mods/ '
-- last instance of the same version of the same mod wins, so read them in this
-- order (in increasing order of liklihood that players may have made custom
-- changes to the files)
local MOD_PATH_ROOTS = { WORKSHOP_MODS_PATH , MODS_PATH , INSTALLED_MODS_PATH }
local function get_mod_id_and_version ( path )
local idfile = path .. ' /info.txt '
local ok , lines = pcall ( io.lines , idfile )
if not ok then return end
local id , version
for line in lines do
if not id then
_ , _ , id = line : find ( ' ^%[ID:([^%]]+)%] ' )
end
if not version then
-- note this doesn't include the closing brace since some people put
-- non-number characters in here, and DF only reads the digits as the
-- numeric version
_ , _ , version = line : find ( ' ^%[NUMERIC_VERSION:(%d+) ' )
end
-- note that we do *not* want to break out of this loop early since
-- lines has to hit EOF to close the file
end
return id , version
end
local function add_script_path ( mod_script_paths , path )
if dfhack.filesystem . isdir ( path ) then
print ( ' indexing mod scripts: ' .. path )
table.insert ( mod_script_paths , path )
end
end
local function add_script_paths ( mod_script_paths , base_path , include_modactive )
if not base_path : endswith ( ' / ' ) then
base_path = base_path .. ' / '
end
if include_modactive then
add_script_path ( mod_script_paths , base_path .. ' scripts_modactive ' )
end
add_script_path ( mod_script_paths , base_path .. ' scripts_modinstalled ' )
end
function get_mod_script_paths ( )
-- ordered map of mod id -> {handled=bool, versions=map of version -> path}
local mods = utils.OrderedTable ( )
local 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.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
mods [ id ] = { handled = true }
add_script_paths ( mod_script_paths , path , true )
:: continue ::
end
end
-- assemble version -> path maps for all (non-handled) mod source dirs
for _ , mod_path_root in ipairs ( MOD_PATH_ROOTS ) do
local files = dfhack.filesystem . listdir_recursive ( mod_path_root , 0 )
if not files then goto skip_path_root end
for _ , f in ipairs ( files ) do
if not f.isdir then goto continue end
local id , version = get_mod_id_and_version ( f.path )
if not id or not version then goto continue end
local mod = ensure_key ( mods , id )
if mod.handled then goto continue end
ensure_key ( mod , ' versions ' ) [ version ] = f.path
:: continue ::
end
:: skip_path_root ::
end
-- add script paths from most recent version of all not-yet-handled mods
for _ , v in pairs ( mods ) do
if v.handled then goto continue end
local max_version , path
for version , mod_path in pairs ( v.versions ) do
if not max_version or max_version < version then
path = mod_path
max_version = version
end
end
add_script_paths ( mod_script_paths , path )
:: continue ::
end
return mod_script_paths
end
return _ENV