From 170b5443d656a55a849115505502748640b5f9c2 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 25 Apr 2025 18:41:39 -0700 Subject: [PATCH] add more mod utility functions --- docs/changelog.txt | 1 + docs/dev/Lua API.rst | 23 ++++++++++ library/lua/script-manager.lua | 82 ++++++++++++++++++++++++++++------ 3 files changed, 93 insertions(+), 13 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 35018ebce5..545ca09f5a 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -64,6 +64,7 @@ Template for new versions: ## API ## Lua +- ``script-manager``: new ``get_active_mods()`` function for getting information on active mods ## Removed diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index c1c2f78e76..aaf5731b54 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -3783,6 +3783,29 @@ paths will be relative to the top level game directory and will end in a slash Which would open ``dfhack-config/mods/my_awesome_mod/settings.json``. After calling ``getModStatePath``, the returned directory is guaranteed to exist. +* ``get_active_mods()`` + + Returns a list of all active mods in the current world. The list elements are + tables containing the following fields: + + - id: mod id + - name: mod display name + - version: mod display version + - numeric_version: numeric mod version + - path: path to the mod directory + - vanilla: true if this is a vanilla mod + +* ``get_mod_info_metadata(mod_path, tags)`` + + Returns a table with the values of the given tags from the ``info.txt`` file + in the given mod directory. The ``mod_path`` argument must be a path to a mod + directory (retrieved, say, from ``get_active_mods()``). The ``tags`` argument + is a string or a list of strings representing the tags to retrieve. The + function will return a table with the tag names as keys and their values as + values. If a requested tag includes the string ``NUMERIC_``, it will return + the numeric value for that tag (e.g., ``NUMERIC_VERSION`` will return the + numeric version of the mod as a number instead of a string). + utils ===== diff --git a/library/lua/script-manager.lua b/library/lua/script-manager.lua index c2647fbe2d..9a1ecb1df0 100644 --- a/library/lua/script-manager.lua +++ b/library/lua/script-manager.lua @@ -88,25 +88,52 @@ local INSTALLED_MODS_PATH = 'data/installed_mods/' -- 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' +-- returns the values of the given list of tags from the info.txt file in the given mod directory +-- if a requested tag includes the string 'NUMERIC_', it will return the numeric value for that tag +-- (e.g. NUMERIC_VERSION will return the numeric version of the mod as a number instead of a string). +function get_mod_info_metadata(mod_path, tags) + local idfile = mod_path .. '/info.txt' local ok, lines = pcall(io.lines, idfile) if not ok then return end - local id, version + + if type(tags) ~= 'table' then + tags = {tags} + end + + local tag_regexes = {} + for _,tag in ipairs(tags) do + local tag_regex = ('^%%[%s:'):format(tag) + if tag:find('NUMERIC_') then + -- note this doesn't go all the way to the closing brace since some people put + -- non-number characters in here, and DF only reads the leading digits for + -- numeric fields + tag_regex = tag_regex .. '(%d+)' + else + tag_regex = tag_regex .. '([^%]]+)' + end + tag_regexes[tag] = tag_regex + end + + local values = {} for line in lines do - if not id then - _,_,id = line:find('^%[ID:([^%]]+)%]') + local _,_,info_tag = line:find('^%[([^:]+):') + if not info_tag or not tag_regexes[info_tag] then + goto continue 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 leading digits - -- as the numeric version - _,_,version = line:find('^%[NUMERIC_VERSION:(%d+)') + local _,_,value = line:find(tag_regexes[info_tag]) + if value then + values[info_tag] = value end - -- note that we do *not* want to break out of this loop early since - -- lines has to hit EOF to close the file + ::continue:: end - return id, version + + return values +end + +local function get_mod_id_and_version(path) + local values = get_mod_info_metadata(path, {'ID', 'NUMERIC_VERSION'}) + if not values then return end + return values.ID, values.NUMERIC_VERSION end local function add_mod_paths(mod_paths, id, base_path, subdir) @@ -172,6 +199,35 @@ function get_mod_paths(installed_subdir, active_subdir) return mod_paths end +-- returns a list of tables in load order with the following fields: +-- id: mod id +-- name: mod display name +-- version: mod display version +-- numeric_version: numeric mod version +-- path: path to the mod directory +-- vanilla: true if this is a vanilla mod +function get_active_mods() + if not dfhack.isWorldLoaded() then return {} end + + local mods = {} + + local ol = df.global.world.object_loader + + for idx=0,#ol.object_load_order_id-1 do + local path = ol.object_load_order_src_dir[idx].value + table.insert(mods, { + id=ol.object_load_order_id[idx].value, + name=ol.object_load_order_name[idx].value, + version=ol.object_load_order_displayed_version[idx].value, + numeric_version=ol.object_load_order_numeric_version[idx], + path=path, + vanilla=path:startswith('data/vanilla/'), -- windows also uses forward slashes + }) + end + + return mods +end + function get_mod_script_paths() local paths = {} for _,v in ipairs(get_mod_paths('scripts_modinstalled', 'scripts_modactive')) do