Skip to content

Commit ddae413

Browse files
Add a window showing the active mods in a world
1 parent 1f7663b commit ddae413

File tree

1 file changed

+117
-0
lines changed

1 file changed

+117
-0
lines changed

gui/mod-manager.lua

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ local utils = require('utils')
1010

1111
local presets_file = json.open("dfhack-config/mod-manager.json")
1212
local GLOBAL_KEY = 'mod-manager'
13+
local INSTALLED_MODS_PATH = 'data/installed_mods/'
1314

1415
-- get_newregion_viewscreen and get_modlist_fields are declared as global functions
1516
-- so external tools can call them to get the DF mod list
@@ -401,6 +402,120 @@ function ModmanageScreen:init()
401402
}
402403
end
403404

405+
ModlistMenu = defclass(ModlistMenu, widgets.Window)
406+
ModlistMenu.ATTRS {
407+
view_id = "modlist_menu",
408+
frame_title = "Active Modlist",
409+
frame_style = gui.WINDOW_FRAME,
410+
411+
resize_min = { w = 30, h = 15 },
412+
frame = { w = 40, t = 10, b = 15 },
413+
414+
resizable = true,
415+
autoarrange_subviews=false,
416+
}
417+
418+
local function get_mod_id_and_version(path)
419+
local idfile = path .. '/info.txt'
420+
local ok, lines = pcall(io.lines, idfile)
421+
if not ok then return end
422+
local id, version
423+
for line in lines do
424+
if not id then
425+
_,_,id = line:find('^%[ID:([^%]]+)%]')
426+
end
427+
if not version then
428+
-- note this doesn't include the closing brace since some people put
429+
-- non-number characters in here, and DF only reads the leading digits
430+
-- as the numeric version
431+
_,_,version = line:find('^%[NUMERIC_VERSION:(%d+)')
432+
end
433+
-- note that we do *not* want to break out of this loop early since
434+
-- lines has to hit EOF to close the file
435+
end
436+
return id, version
437+
end
438+
439+
local function add_mod_paths(mod_paths, id, base_path, subdir)
440+
local sep = base_path:endswith('/') and '' or '/'
441+
local path = ('%s%s%s'):format(base_path, sep, subdir)
442+
if dfhack.filesystem.isdir(path) then
443+
table.insert(mod_paths, {id=id, path=path})
444+
end
445+
end
446+
447+
local function getWorldModlist()
448+
-- ordered map of mod id -> {handled=bool, versions=map of version -> path}
449+
local mods = utils.OrderedTable()
450+
local mod_paths = {}
451+
452+
-- if a world is loaded, process active mods first, and lock to active version
453+
if dfhack.isWorldLoaded() then
454+
for _,path in ipairs(df.global.world.object_loader.object_load_order_src_dir) do
455+
path = tostring(path.value)
456+
-- skip vanilla "mods"
457+
if not path:startswith(INSTALLED_MODS_PATH) then goto continue end
458+
local id = get_mod_id_and_version(path)
459+
if not id then goto continue end
460+
mods[id] = {handled=true}
461+
add_mod_paths(mod_paths, id, path, '.')
462+
::continue::
463+
end
464+
local modlist = {}
465+
for _,mod in ipairs(mod_paths) do
466+
table.insert(modlist,mod.id)
467+
end
468+
return modlist
469+
end
470+
qerror('No world is loaded')
471+
end
472+
473+
function ModlistMenu:init()
474+
local modlist = widgets.List{
475+
view_id='modlist',
476+
frame = {t=3},
477+
choices = getWorldModlist()
478+
}
479+
self:addviews{
480+
widgets.Label{
481+
frame = { l=0, t=0 },
482+
text = {'Active mods:'},
483+
},
484+
widgets.HotkeyLabel{
485+
view_id='copy',
486+
frame={t=1, r=1},
487+
label='Copy modlist to clipboard',
488+
text_pen=COLOR_YELLOW,
489+
auto_width=true,
490+
on_activate=function()
491+
local mods = ''
492+
for _,mod in ipairs(getWorldModlist()) do
493+
mods = (mods == '' and mod) or (mods .. ', ' .. mod)
494+
end
495+
dfhack.internal.setClipboardTextCp437(mods)
496+
end,
497+
enabled=function() return #modlist:getChoices() > 0 end,
498+
},
499+
modlist
500+
}
501+
end
502+
503+
504+
ModlistScreen = defclass(ModlistScreen, gui.ZScreen)
505+
ModlistScreen.ATTRS {
506+
focus_path = "modlist",
507+
}
508+
509+
function ModlistScreen:init()
510+
self:addviews{
511+
ModlistMenu{}
512+
}
513+
end
514+
515+
function ModlistScreen:onDismiss()
516+
view = nil
517+
end
518+
404519
ModmanageOverlay = defclass(ModmanageOverlay, overlay.OverlayWidget)
405520
ModmanageOverlay.ATTRS {
406521
frame = { w=16, h=3 },
@@ -496,3 +611,5 @@ end
496611

497612
-- TODO: when invoked as a command, should show information on which mods are loaded
498613
-- and give the player the option to export the list (or at least copy it to the clipboard)
614+
615+
view = view and view:raise() or ModlistScreen{}:show()

0 commit comments

Comments
 (0)