Skip to content

Commit 2131f55

Browse files
authored
Merge branch 'DFHack:master' into pedestal
2 parents 514da4a + 30993ee commit 2131f55

File tree

3 files changed

+188
-53
lines changed

3 files changed

+188
-53
lines changed

changelog.txt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,34 @@ Template for new versions:
3636

3737
## Removed
3838

39+
# 52.02-r2
40+
41+
## New Tools
42+
43+
## New Features
44+
- `gui/mod-manager`: now supports arena mode
45+
46+
## Fixes
47+
- `gui/mod-manager`: gracefully handle vanilla mods with different versions from the user's preset
48+
- `gui/mod-manager`: hide other versions of loaded mods and unhides them when unloaded
49+
50+
## Misc Improvements
51+
52+
## Removed
53+
54+
# 52.02-r1
55+
56+
## New Tools
57+
58+
## New Features
59+
60+
## Fixes
61+
- ``embark-anyone``: validate viewscreen before using, avoids a crash
62+
63+
## Misc Improvements
64+
65+
## Removed
66+
3967
# 52.01-r1
4068

4169
## New Tools

embark-anyone.lua

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
11
local dialogs = require('gui.dialogs')
22
local utils = require('utils')
33

4-
function addCivToEmbarkList(info)
5-
local viewscreen = dfhack.gui.getDFViewscreen(true)
4+
function embarkAnyone()
65

7-
viewscreen.start_civ:insert ('#', info.civ)
8-
viewscreen.start_civ_nem_num:insert ('#', info.nemeses)
9-
viewscreen.start_civ_entpop_num:insert ('#', info.pops)
10-
viewscreen.start_civ_site_num:insert ('#', info.sites)
11-
end
6+
function addCivToEmbarkList(info)
7+
local viewscreen = dfhack.gui.getDFViewscreen(true)
8+
9+
viewscreen.start_civ:insert ('#', info.civ)
10+
viewscreen.start_civ_nem_num:insert ('#', info.nemeses)
11+
viewscreen.start_civ_entpop_num:insert ('#', info.pops)
12+
viewscreen.start_civ_site_num:insert ('#', info.sites)
13+
end
1214

13-
function embarkAnyone()
1415
local viewscreen = dfhack.gui.getDFViewscreen(true)
16+
if viewscreen._type ~= df.viewscreen_choose_start_sitest then
17+
qerror("This script can only be used on the embark screen!")
18+
end
19+
1520
local choices, existing_civs = {}, {}
1621

1722
for _,existing_civ in ipairs(viewscreen.start_civ) do
1823
existing_civs[existing_civ.id] = true
1924
end
2025

21-
if viewscreen._type ~= df.viewscreen_choose_start_sitest then
22-
qerror("This script can only be used on the embark screen!")
23-
end
24-
2526
for i, civ in ipairs (df.global.world.entities.all) do
2627
-- Test if entity is a civ
2728
if civ.type ~= df.historical_entity_type.Civilization then goto continue end

gui/mod-manager.lua

Lines changed: 147 additions & 41 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

58-
local function move_mod_entry(viewscreen, to, from, mod_id, mod_version)
75+
---@return boolean # true if the mod entry was copied over; false if the mod or mod version was not found.
76+
---@return string|nil # loaded version - DISPLAYED_VERSION from the mod's info.txt
77+
local function copy_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,19 +106,49 @@ 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
84-
v:erase(mod_index)
85-
end
86-
87-
return true
109+
return true, loaded_version
88110
end
89111

112+
---@return boolean # true if the mod entry was copied over; false if the mod or mod version was not found.
113+
---@return string|nil # loaded version - DISPLAYED_VERSION from the mod's info.txt
90114
local function enable_mod(viewscreen, mod_id, mod_version)
91-
return move_mod_entry(viewscreen, "object_load_order", "available", mod_id, mod_version)
115+
return copy_mod_entry(viewscreen, "object_load_order", "base_available", mod_id, mod_version)
116+
end
117+
118+
---@return boolean # true if the mod entry was copied over; false if the mod or mod version was not found.
119+
---@return string|nil # loaded version - DISPLAYED_VERSION from the mod's info.txt
120+
local function make_available_mod(viewscreen, mod_id, mod_version)
121+
return copy_mod_entry(viewscreen, "available", "base_available", mod_id, mod_version)
122+
end
123+
124+
local function clear_mods(viewscreen)
125+
local active_modlist = get_modlist_fields('object_load_order', viewscreen)
126+
local avail_modlist = get_modlist_fields('available', viewscreen)
127+
for _, modlist in ipairs({active_modlist, avail_modlist}) do
128+
for _, v in pairs(modlist) do
129+
for i = #v - 1, 0, -1 do
130+
v:erase(i)
131+
end
132+
end
133+
end
92134
end
93135

94-
local function disable_mod(viewscreen, mod_id, mod_version)
95-
return move_mod_entry(viewscreen, "available", "object_load_order", mod_id, mod_version)
136+
local function set_available_mods(viewscreen, loaded)
137+
local base_avail = get_modlist_fields('base_available', viewscreen)
138+
local unused = {}
139+
for i, id in ipairs(base_avail.id) do
140+
if not loaded[id.value] then
141+
local version = base_avail.numeric_version[i]
142+
table.insert(unused, { id= id.value, version= version })
143+
end
144+
end
145+
146+
for _, v in ipairs(unused) do
147+
local success, _ = make_available_mod(viewscreen, v.id, v.version)
148+
if not success then
149+
dfhack.printerr('failed to show '..v.id..' in available list')
150+
end
151+
end
96152
end
97153

98154
local function get_active_modlist(viewscreen)
@@ -105,19 +161,28 @@ local function get_active_modlist(viewscreen)
105161
return t
106162
end
107163

164+
--- @return string[]
165+
--- @return { id: string, new: string }[]
108166
local function swap_modlist(viewscreen, modlist)
109-
local current = get_active_modlist(viewscreen)
110-
for _, v in ipairs(current) do
111-
disable_mod(viewscreen, v.id, v.version)
112-
end
167+
clear_mods(viewscreen)
113168

114169
local failures = {}
170+
local changed = {}
171+
local loaded = {}
115172
for _, v in ipairs(modlist) do
116-
if not enable_mod(viewscreen, v.id, v.version) then
173+
local success, version = enable_mod(viewscreen, v.id, v.version)
174+
if not success then
117175
table.insert(failures, v.id)
176+
else
177+
if version then
178+
table.insert(changed, { id= v.id, new= version })
179+
end
180+
loaded[v.id] = true
118181
end
119182
end
120-
return failures
183+
184+
set_available_mods(viewscreen, loaded)
185+
return failures, changed
121186
end
122187

123188
--------------------
@@ -137,7 +202,7 @@ ModmanageMenu.ATTRS {
137202
}
138203

139204
local function save_new_preset(preset_name)
140-
local viewscreen = get_newregion_viewscreen()
205+
local viewscreen = get_any_moddable_viewscreen()
141206
local modlist = get_active_modlist(viewscreen)
142207
table.insert(presets_file.data, { name = preset_name, modlist = modlist })
143208
presets_file:write()
@@ -157,27 +222,17 @@ local function overwrite_preset(idx)
157222
return
158223
end
159224

160-
local viewscreen = get_newregion_viewscreen()
225+
local viewscreen = get_any_moddable_viewscreen()
161226
local modlist = get_active_modlist(viewscreen)
162227
presets_file.data[idx].modlist = modlist
163228
presets_file:write()
164229
end
165230

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

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 = {}
234+
if failed then
177235
if unset_default_on_failure then
178-
presets_file.data[idx].default = false
179-
presets_file:write()
180-
181236
table.insert(text, {
182237
text='Failed to load some mods from your default preset.',
183238
pen=COLOR_LIGHTRED,
@@ -193,19 +248,70 @@ local function load_preset(idx, unset_default_on_failure)
193248
pen=COLOR_LIGHTRED,
194249
})
195250
end
251+
end
252+
253+
if failed and changed then
196254
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)
255+
end
256+
257+
if changed then
258+
table.insert(text, {
259+
text='Some vanilla mods have been updated.',
260+
pen=COLOR_LIGHTRED,
261+
})
262+
end
263+
table.insert(text, NEWLINE)
264+
table.insert(text, 'Please re-create your preset with mods you currently have installed.')
265+
table.insert(text, NEWLINE)
266+
table.insert(text, NEWLINE)
267+
end
268+
269+
local function load_preset(idx, unset_default_on_failure)
270+
if idx > #presets_file.data then
271+
return
272+
end
273+
274+
local viewscreen = get_any_moddable_viewscreen()
275+
local modlist = presets_file.data[idx].modlist
276+
local failures, changes = swap_modlist(viewscreen, modlist)
277+
local text = {}
278+
279+
local failed = #failures > 0
280+
local changed = #changes > 0
281+
282+
prepare_warning(text, failed, changed)
283+
if failed and unset_default_on_failure then
284+
presets_file.data[idx].default = false
285+
presets_file:write()
286+
end
287+
288+
if failed then
200289
table.insert(text, 'Here are the mods that failed to load:')
201290
table.insert(text, NEWLINE)
202291
table.insert(text, NEWLINE)
203292
for _, v in ipairs(failures) do
204293
table.insert(text, ('- %s'):format(v))
205294
table.insert(text, NEWLINE)
206295
end
296+
end
297+
298+
if failed and changed then
299+
table.insert(text, NEWLINE) -- just to separate the sections
300+
end
301+
302+
if changed then
303+
table.insert(text, 'Here are the vanilla mods that have been updated:')
304+
table.insert(text, NEWLINE)
305+
table.insert(text, NEWLINE)
306+
for _, v in ipairs(changes) do
307+
table.insert(text, ('- %s to %s'):format(v.id, v.new))
308+
table.insert(text, NEWLINE)
309+
end
310+
end
311+
312+
if failed or changed then
207313
dialogs.showMessage("Warning", text)
208-
end
314+
end
209315
end
210316

211317
local function find_preset_by_name(name)
@@ -573,7 +679,7 @@ ModmanageOverlay.ATTRS {
573679
desc = "Adds a link to the mod selection screen for accessing the mod manager.",
574680
default_pos = { x=5, y=-6 },
575681
version = 2,
576-
viewscreens = { "new_region/Mods" },
682+
viewscreens = { "new_region/Mods", "new_arena/Mods" },
577683
default_enabled=true,
578684
}
579685

@@ -636,7 +742,7 @@ notification_timer_fn()
636742
local default_applied = false
637743
dfhack.onStateChange[GLOBAL_KEY] = function(sc)
638744
if sc == SC_VIEWSCREEN_CHANGED then
639-
local vs = get_newregion_viewscreen()
745+
local vs = get_any_moddable_viewscreen()
640746
if vs and not default_applied then
641747
default_applied = true
642748
for i, v in ipairs(presets_file.data) do

0 commit comments

Comments
 (0)