Skip to content

Commit ca3ab76

Browse files
authored
Merge pull request #1481 from ong-yinggao98/modmanager-vanilla
`gui/mod-manager` improvements
2 parents 166b237 + 63a64fd commit ca3ab76

File tree

2 files changed

+109
-30
lines changed

2 files changed

+109
-30
lines changed

changelog.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ Template for new versions:
6060
- `gui/gm-unit`: remove reference to ``think_counter``, removed in v51.12
6161
- `gui/journal`: fix typo which caused the table of contents to always be regenerated even when not needed
6262
- `gui/mod-manager`: gracefully handle mods with missing or broken ``info.txt`` files
63+
- `gui/mod-manager`: gracefully handle vanilla mods with different versions from the user's preset
64+
- `gui/mod-manager`: now supports arena mode
6365
- `uniform-unstick`: resolve overlap with new buttons in 51.13
6466

6567
## Misc Improvements

gui/mod-manager.lua

Lines changed: 107 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,27 @@ local widgets = require('gui.widgets')
1212
local presets_file = json.open("dfhack-config/mod-manager.json")
1313
local GLOBAL_KEY = 'mod-manager'
1414

15-
-- get_newregion_viewscreen and get_modlist_fields are declared as global functions
16-
-- so external tools can call them to get the DF mod list
17-
function get_newregion_viewscreen()
15+
local function vanilla(dir)
16+
return dir:startswith('data/vanilla')
17+
end
18+
19+
-- get_moddable_viewscreen(), get_any_moddable_viewscreen() and get_modlist_fields are declared
20+
-- as global functions so external tools can call them to get the DF mod list
21+
function get_moddable_viewscreen(type)
22+
local vs = nil
23+
if type == 'region' then
24+
vs = dfhack.gui.getViewscreenByType(df.viewscreen_new_regionst, 0)
25+
elseif type == 'arena' then
26+
vs = dfhack.gui.getViewscreenByType(df.viewscreen_new_arenast, 0)
27+
end
28+
return vs
29+
end
30+
31+
function get_any_moddable_viewscreen()
1832
local vs = dfhack.gui.getViewscreenByType(df.viewscreen_new_regionst, 0)
33+
if not vs then
34+
vs = dfhack.gui.getViewscreenByType(df.viewscreen_new_arenast, 0)
35+
end
1936
return vs
2037
end
2138

@@ -55,21 +72,30 @@ function get_modlist_fields(kind, viewscreen)
5572
end
5673
end
5774

75+
---@return boolean # true if the mod entry was moved; false if the mod or mod version was not found.
76+
---@return string|nil # loaded version - DISPLAYED_VERSION from the mod's info.txt
5877
local function move_mod_entry(viewscreen, to, from, mod_id, mod_version)
5978
local to_fields = get_modlist_fields(to, viewscreen)
6079
local from_fields = get_modlist_fields(from, viewscreen)
6180

6281
local mod_index = nil
82+
local loaded_version = nil
6383
for i, v in ipairs(from_fields.id) do
6484
local version = from_fields.numeric_version[i]
65-
if v.value == mod_id and version == mod_version then
85+
local src_dir = from_fields.src_dir[i]
86+
local displayed_version = from_fields.displayed_version[i].value
87+
-- assumes that vanilla mods will not have multiple possible indices.
88+
if v.value == mod_id and (vanilla(src_dir) or version == mod_version) then
89+
if version ~= mod_version then
90+
loaded_version = displayed_version
91+
end
6692
mod_index = i
6793
break
6894
end
6995
end
7096

7197
if mod_index == nil then
72-
return false
98+
return false, nil
7399
end
74100

75101
for k, v in pairs(to_fields) do
@@ -80,17 +106,21 @@ local function move_mod_entry(viewscreen, to, from, mod_id, mod_version)
80106
end
81107
end
82108

83-
for k, v in pairs(from_fields) do
109+
for _, v in pairs(from_fields) do
84110
v:erase(mod_index)
85111
end
86112

87-
return true
113+
return true, loaded_version
88114
end
89115

116+
---@return boolean # true if the mod entry was moved; false if the mod or mod version was not found.
117+
---@return string|nil # loaded version - DISPLAYED_VERSION from the mod's info.txt
90118
local function enable_mod(viewscreen, mod_id, mod_version)
91119
return move_mod_entry(viewscreen, "object_load_order", "available", mod_id, mod_version)
92120
end
93121

122+
---@return boolean # true if the mod entry was moved; false if the mod or mod version was not found.
123+
---@return string|nil # loaded version - DISPLAYED_VERSION from the mod's info.txt
94124
local function disable_mod(viewscreen, mod_id, mod_version)
95125
return move_mod_entry(viewscreen, "available", "object_load_order", mod_id, mod_version)
96126
end
@@ -105,19 +135,25 @@ local function get_active_modlist(viewscreen)
105135
return t
106136
end
107137

138+
--- @return string[]
139+
--- @return { id: string, new: string }[]
108140
local function swap_modlist(viewscreen, modlist)
109141
local current = get_active_modlist(viewscreen)
110142
for _, v in ipairs(current) do
111143
disable_mod(viewscreen, v.id, v.version)
112144
end
113145

114146
local failures = {}
147+
local changed = {}
115148
for _, v in ipairs(modlist) do
116-
if not enable_mod(viewscreen, v.id, v.version) then
149+
local success, version = enable_mod(viewscreen, v.id, v.version)
150+
if not success then
117151
table.insert(failures, v.id)
152+
elseif version then
153+
table.insert(changed, { id= v.id, new= version })
118154
end
119155
end
120-
return failures
156+
return failures, changed
121157
end
122158

123159
--------------------
@@ -137,7 +173,7 @@ ModmanageMenu.ATTRS {
137173
}
138174

139175
local function save_new_preset(preset_name)
140-
local viewscreen = get_newregion_viewscreen()
176+
local viewscreen = get_any_moddable_viewscreen()
141177
local modlist = get_active_modlist(viewscreen)
142178
table.insert(presets_file.data, { name = preset_name, modlist = modlist })
143179
presets_file:write()
@@ -157,27 +193,17 @@ local function overwrite_preset(idx)
157193
return
158194
end
159195

160-
local viewscreen = get_newregion_viewscreen()
196+
local viewscreen = get_any_moddable_viewscreen()
161197
local modlist = get_active_modlist(viewscreen)
162198
presets_file.data[idx].modlist = modlist
163199
presets_file:write()
164200
end
165201

166-
local function load_preset(idx, unset_default_on_failure)
167-
if idx > #presets_file.data then
168-
return
169-
end
202+
local function prepare_warning(text, failed, changed, unset_default_on_failure)
203+
if not failed and not changed then return end
170204

171-
local viewscreen = get_newregion_viewscreen()
172-
local modlist = presets_file.data[idx].modlist
173-
local failures = swap_modlist(viewscreen, modlist)
174-
175-
if #failures > 0 then
176-
local text = {}
205+
if failed then
177206
if unset_default_on_failure then
178-
presets_file.data[idx].default = false
179-
presets_file:write()
180-
181207
table.insert(text, {
182208
text='Failed to load some mods from your default preset.',
183209
pen=COLOR_LIGHTRED,
@@ -193,19 +219,70 @@ local function load_preset(idx, unset_default_on_failure)
193219
pen=COLOR_LIGHTRED,
194220
})
195221
end
222+
end
223+
224+
if failed and changed then
196225
table.insert(text, NEWLINE)
197-
table.insert(text, NEWLINE)
198-
table.insert(text, 'Please re-create your preset with mods you currently have installed.')
199-
table.insert(text, NEWLINE)
226+
end
227+
228+
if changed then
229+
table.insert(text, {
230+
text='Some vanilla mods have been updated.',
231+
pen=COLOR_LIGHTRED,
232+
})
233+
end
234+
table.insert(text, NEWLINE)
235+
table.insert(text, 'Please re-create your preset with mods you currently have installed.')
236+
table.insert(text, NEWLINE)
237+
table.insert(text, NEWLINE)
238+
end
239+
240+
local function load_preset(idx, unset_default_on_failure)
241+
if idx > #presets_file.data then
242+
return
243+
end
244+
245+
local viewscreen = get_any_moddable_viewscreen()
246+
local modlist = presets_file.data[idx].modlist
247+
local failures, changes = swap_modlist(viewscreen, modlist)
248+
local text = {}
249+
250+
local failed = #failures > 0
251+
local changed = #changes > 0
252+
253+
prepare_warning(text, failed, changed)
254+
if failed and unset_default_on_failure then
255+
presets_file.data[idx].default = false
256+
presets_file:write()
257+
end
258+
259+
if failed then
200260
table.insert(text, 'Here are the mods that failed to load:')
201261
table.insert(text, NEWLINE)
202262
table.insert(text, NEWLINE)
203263
for _, v in ipairs(failures) do
204264
table.insert(text, ('- %s'):format(v))
205265
table.insert(text, NEWLINE)
206266
end
267+
end
268+
269+
if failed and changed then
270+
table.insert(text, NEWLINE) -- just to separate the sections
271+
end
272+
273+
if changed then
274+
table.insert(text, 'Here are the vanilla mods that have been updated:')
275+
table.insert(text, NEWLINE)
276+
table.insert(text, NEWLINE)
277+
for _, v in ipairs(changes) do
278+
table.insert(text, ('- %s to %s'):format(v.id, v.new))
279+
table.insert(text, NEWLINE)
280+
end
281+
end
282+
283+
if failed or changed then
207284
dialogs.showMessage("Warning", text)
208-
end
285+
end
209286
end
210287

211288
local function find_preset_by_name(name)
@@ -573,7 +650,7 @@ ModmanageOverlay.ATTRS {
573650
desc = "Adds a link to the mod selection screen for accessing the mod manager.",
574651
default_pos = { x=5, y=-6 },
575652
version = 2,
576-
viewscreens = { "new_region/Mods" },
653+
viewscreens = { "new_region/Mods", "new_arena/Mods" },
577654
default_enabled=true,
578655
}
579656

@@ -636,7 +713,7 @@ notification_timer_fn()
636713
local default_applied = false
637714
dfhack.onStateChange[GLOBAL_KEY] = function(sc)
638715
if sc == SC_VIEWSCREEN_CHANGED then
639-
local vs = get_newregion_viewscreen()
716+
local vs = get_any_moddable_viewscreen()
640717
if vs and not default_applied then
641718
default_applied = true
642719
for i, v in ipairs(presets_file.data) do

0 commit comments

Comments
 (0)