@@ -12,10 +12,27 @@ local widgets = require('gui.widgets')
1212local presets_file = json .open (" dfhack-config/mod-manager.json" )
1313local 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
2037end
2138
@@ -55,21 +72,30 @@ function get_modlist_fields(kind, viewscreen)
5572 end
5673end
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
88110end
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
90114local 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
92134end
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
96152end
97153
98154local function get_active_modlist (viewscreen )
@@ -105,19 +161,28 @@ local function get_active_modlist(viewscreen)
105161 return t
106162end
107163
164+ --- @return string[]
165+ --- @return { id : string , new : string } []
108166local 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
121186end
122187
123188---- ----------------
@@ -137,7 +202,7 @@ ModmanageMenu.ATTRS {
137202}
138203
139204local 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 ()
164229end
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
209315end
210316
211317local 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()
636742local default_applied = false
637743dfhack .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