33local dlg = require (' gui.dialogs' )
44local gui = require (' gui' )
55local json = require (' json' )
6+ local utils = require (' utils' )
67local list_agreements = reqscript (' list-agreements' )
78local repeat_util = require (' repeat-util' )
89local stuck_squad = reqscript (' fix/stuck-squad' )
@@ -334,7 +335,8 @@ local function save_popup()
334335 end
335336end
336337
337- local function get_units_with_missing_nemesis_records ()
338+ --- @return string[]
339+ local function get_active_units_with_missing_nemesis_records ()
338340 local namelist = {}
339341 for _ , unit in ipairs (df .global .world .units .active ) do
340342 local ref = dfhack .units .getGeneralRef (unit , df .general_ref_type .IS_NEMESIS )
@@ -348,21 +350,77 @@ local function get_units_with_missing_nemesis_records()
348350 return namelist
349351end
350352
353+ --- @param vector any[] # a df vector or array, or a Lua list.
354+ --- @param field string ? # nil, or the field name to sort on.
355+ --- @param comparator fun ( a : any , b : any ): integer | nil
356+ --- # an optional comparator that returns -1,0,1 per utils.compare_* .
357+ --- # nil falls back to utils.compare or utils.compare_field.
358+ --- # if a comparator is given, the field parameter is ignored.
359+ --- @return boolean
360+ local function verify_vector_is_sorted (vector , field , comparator )
361+ assert (type (vector ) == ' table' or utils .is_container (vector ))
362+ assert (type (field ) == ' string' or field == nil )
363+ assert (type (comparator ) == ' function' or comparator == nil )
364+ comparator = comparator or utils .compare_field (field )
365+ local lo , hi
366+ if type (vector ) == ' table' then
367+ lo , hi = 1 , # vector
368+ else
369+ lo , hi = 0 , # vector - 1
370+ end
371+ local sorted = true
372+ for i = lo , hi - 1 do
373+ if comparator (vector [i ], vector [i + 1 ]) ~= - 1 then
374+ sorted = false
375+ break
376+ end
377+ end
378+ return sorted
379+ end
380+
381+ local cache_nemesis_all_is_sorted = {}
382+ --- only verifies if the vector length has changed.
383+ --- @return boolean
384+ local function verify_nemesis_all_is_sorted ()
385+ local vector = df .global .world .nemesis .all
386+ if # vector == cache_nemesis_all_is_sorted .length then
387+ return cache_nemesis_all_is_sorted .sorted
388+ end
389+ cache_nemesis_all_is_sorted .length = # vector
390+ cache_nemesis_all_is_sorted .sorted = verify_vector_is_sorted (vector , ' id' )
391+ return cache_nemesis_all_is_sorted .sorted
392+ end
393+
351394-- the order of this list controls the order the notifications will appear in the overlay
352395NOTIFICATIONS_BY_IDX = {
353396 {
354397 name = ' missing_nemesis' ,
355398 desc = ' Reports missing nemesis records, indicating savegame corruption.' ,
356399 default = true ,
357400 fn = function ()
401+ if not verify_nemesis_all_is_sorted () then
402+ return { {
403+ pen = COLOR_LIGHTRED ,
404+ text = ' nemesis vector not sorted'
405+ } }
406+ end
358407 local count = df .global .nemesis_next_id - # df .global .world .nemesis .all
359408 if count == 0 then return end
360409 return { {
361410 pen = COLOR_LIGHTRED ,
362- text = (' missing %d nemesis record%s' ):format (count , count == 1 and ' ' or ' s' ),
411+ text = (' missing %d nemesis record%s' ):format (count , count == 1 and ' ' or ' s' )
363412 } }
364413 end ,
365414 on_click = function ()
415+ if not verify_nemesis_all_is_sorted () then
416+ local message =
417+ ' This save game is corrupt.\n\n The world.nemesis.global vector\n ' ..
418+ ' of this savegame is not sorted.\n\n Some attempts to lookup the\n ' ..
419+ ' nemesis record for a unit or\n historical figure will fail.\n\n ' ..
420+ ' This should be reported to\n Bay 12 Games as a bug.\n '
421+ dlg .showMessage (' nemesis vector not sorted' , message , COLOR_RED )
422+ return
423+ end
366424 local message = {
367425 { pen = COLOR_RED , text = ' This save game may be corrupt.' }, NEWLINE ,
368426 NEWLINE ,
@@ -378,7 +436,7 @@ NOTIFICATIONS_BY_IDX = {
378436 { pen = COLOR_WHITE , text = ' if the fort is retired.' }, NEWLINE ,
379437 NEWLINE ,
380438 }
381- local redtext = get_units_with_missing_nemesis_records ()
439+ local redtext = get_active_units_with_missing_nemesis_records ()
382440 if # redtext > 0 then
383441 table.insert (message , { pen = COLOR_RED ,
384442 text = ' These active units are missing their nemesis records:' })
@@ -668,3 +726,9 @@ local function get_config()
668726end
669727
670728config = get_config ()
729+
730+ dfhack .onStateChange [' internal/notify/notifications' ] = function (event )
731+ if event == SC_WORLD_LOADED or event == SC_WORLD_UNLOADED then
732+ cache_nemesis_all_is_sorted = {}
733+ end
734+ end
0 commit comments