diff --git a/changelog.txt b/changelog.txt index ff710b92a..26badf3a6 100644 --- a/changelog.txt +++ b/changelog.txt @@ -29,6 +29,7 @@ Template for new versions: ## New Tools ## New Features +- `gui/mod-manager`: when run in a loaded world, shows a list of active mods -- click to export the list to the clipboard for easy sharing or posting ## Fixes - `starvingdead`: properly restore to correct enabled state when loading a new game that is different from the first game loaded in this session diff --git a/docs/gui/mod-manager.rst b/docs/gui/mod-manager.rst index 8972fece7..56b1538f2 100644 --- a/docs/gui/mod-manager.rst +++ b/docs/gui/mod-manager.rst @@ -2,11 +2,11 @@ gui/mod-manager =============== .. dfhack-tool:: - :summary: Save and restore lists of active mods. + :summary: Manange your active mods. :tags: dfhack interface -Adds an optional overlay to the mod list screen that allows you to save and -load mod list presets, as well as set a default mod list preset for new worlds. +When run with a world loaded, shows a list of active mods. You can copy the +list to the system clipboard for easy sharing or posting. Usage ----- @@ -14,3 +14,21 @@ Usage :: gui/mod-manager + +Overlay +------- + +This tool also provides two overlays that are managed by the `overlay` +framework. + +gui/mod-manager.button +~~~~~~~~~~~~~~~~~~~~~~ + +Adds a widget to the mod list screen that allows you to save and load mod list +presets. You can also set a default mod list preset for new worlds so you don't +have to manualy re-select the same mods every time you generate a world. + +gui/mod-manager.notification +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Displays a message when a mod preset has been auto-applied. diff --git a/gui/mod-manager.lua b/gui/mod-manager.lua index 51df00d67..ee3a48976 100644 --- a/gui/mod-manager.lua +++ b/gui/mod-manager.lua @@ -1,12 +1,13 @@ --- Save and restore lists of active mods. +-- Show, save, and restore lists of active mods. --@ module = true -local overlay = require('plugins.overlay') -local gui = require('gui') -local widgets = require('gui.widgets') local dialogs = require('gui.dialogs') +local gui = require('gui') local json = require('json') +local overlay = require('plugins.overlay') +local scriptmanager = require('script-manager') local utils = require('utils') +local widgets = require('gui.widgets') local presets_file = json.open("dfhack-config/mod-manager.json") local GLOBAL_KEY = 'mod-manager' @@ -119,6 +120,9 @@ local function swap_modlist(viewscreen, modlist) return failures end +-------------------- +-- ModmanageMenu + ModmanageMenu = defclass(ModmanageMenu, widgets.Window) ModmanageMenu.ATTRS { view_id = "modman_menu", @@ -389,6 +393,9 @@ function ModmanageMenu:init() } end +-------------------- +-- ModmanageScreen + ModmanageScreen = defclass(ModmanageScreen, gui.ZScreen) ModmanageScreen.ATTRS { focus_path = "mod-manager", @@ -401,6 +408,149 @@ function ModmanageScreen:init() } end +-------------------- +-- ModlistWindow + +ModlistWindow = defclass(ModlistWindow, widgets.Window) +ModlistWindow.ATTRS{ + frame_title="Active Mods", + frame={w=55, h=20}, + resizable=true, +} + +local function get_num_vanilla_mods() + local count = 0 + for _,mod in ipairs(scriptmanager.get_active_mods()) do + if mod.vanilla then + count = count + 1 + end + end + return count +end + +local function get_num_non_vanilla_mods() + local count = 0 + for _,mod in ipairs(scriptmanager.get_active_mods()) do + if not mod.vanilla then + count = count + 1 + end + end + return count +end + +function ModlistWindow:init() + self:addviews{ + widgets.CycleHotkeyLabel{ + view_id='vanilla', + frame={l=0, t=0, w=24}, + key='CUSTOM_V', + label='Vanilla mods:', + options={ + {label='Include', value=true, pen=COLOR_LIGHTBLUE}, + {label='Exclude', value=false, pen=COLOR_LIGHTRED}, + }, + initial_option=false, + on_change=function() self:refresh_list() end, + }, + widgets.HotkeyLabel{ + frame={t=0, r=0}, + label='Copy list to clipboard', + text_pen=COLOR_YELLOW, + auto_width=true, + on_activate=function() + local text = {} + for _,choice in ipairs(self.subviews.list:getChoices()) do + table.insert(text, choice.export_text) + end + dfhack.internal.setClipboardTextCp437Multiline(table.concat(text, NEWLINE)) + end, + enabled=function() return #self.subviews.list:getChoices() > 0 end, + }, + widgets.Divider{ + frame={t=2, h=1}, + frame_style=gui.FRAME_THIN, + frame_style_l=false, + frame_style_r=false, + }, + widgets.Label{ + frame={l=0, t=3}, + text={ + 'Load', + NEWLINE, + 'order', + }, + }, + widgets.Label{ + frame={l=7, t=4}, + text='Mod', + }, + widgets.List{ + view_id='list', + frame={t=6, b=2}, + }, + widgets.Label{ + frame={l=0, b=0}, + text={ + {text=('%d'):format(get_num_vanilla_mods()), pen=COLOR_LIGHTBLUE}, + ' vanilla mods', + {text=function() return self.subviews.vanilla:getOptionValue() and '' or ' (hidden)' end}, + ', ', + {text=('%d'):format(get_num_non_vanilla_mods()), pen=COLOR_BROWN}, + ' non-vanilla mods', + }, + }, + } + + self:refresh_list() +end + +function ModlistWindow:refresh_list() + local include_vanilla = self.subviews.vanilla:getOptionValue() + + local choices = {} + for idx,mod in ipairs(scriptmanager.get_active_mods()) do + if not include_vanilla and mod.vanilla then goto continue end + local steam_id = scriptmanager.get_mod_info_metadata(mod.path, 'STEAM_FILE_ID').STEAM_FILE_ID + local url = steam_id and (': https://steamcommunity.com/sharedfiles/filedetails/?id=%s'):format(steam_id) or '' + table.insert(choices, { + text={ + {text=idx, width=2, rjustify=true}, + ') ', + {text=mod.name, gap=3}, + ' (', + {text=mod.version, pen=COLOR_LIGHTGREEN}, + ')', + }, + data=mod, + export_text=('- %s (%s)%s'):format(mod.name, mod.version, url), + }) + ::continue:: + end + + self.subviews.list:setChoices(choices) +end + +-------------------- +-- ModlistScreen + +ModlistScreen = defclass(ModlistScreen, gui.ZScreen) +ModlistScreen.ATTRS{ + focus_path="mod-manager", +} + +function ModlistScreen:init() + self:addviews{ + ModlistWindow{} + } +end + +function ModlistScreen:onDismiss() + view = nil +end + +-------------------- +-- Overlays + ModmanageOverlay = defclass(ModmanageOverlay, overlay.OverlayWidget) ModmanageOverlay.ATTRS { frame = { w=16, h=3 }, @@ -494,5 +644,8 @@ if dfhack_flags.module then return end --- TODO: when invoked as a command, should show information on which mods are loaded --- and give the player the option to export the list (or at least copy it to the clipboard) +if not dfhack.isWorldLoaded() then + qerror("Please load a game before using the mod manager to see active mods.") +end + +view = view and view:raise() or ModlistScreen{}:show()