@@ -335,24 +335,25 @@ local function save_popup()
335335 end
336336end
337337
338- --- @return string []
339- local function get_active_units_with_missing_nemesis_records ()
340- local namelist = {}
341- for _ , unit in ipairs (df . global . world . units .active ) do
338+ --- @return df.unit.id []
339+ local function _get_active_unit_ids_with_missing_nemesis_records ()
340+ local list = {}
341+ for _ , unit in ipairs (units .active ) do
342342 local ref = dfhack .units .getGeneralRef (unit , df .general_ref_type .IS_NEMESIS )
343343 if ref then
344344 local nrec = ref :getNemesis ()
345345 if nrec == nil then
346- table.insert (namelist , dfhack . units . getReadableName ( unit ) )
346+ table.insert (list , unit . id )
347347 end
348348 end
349349 end
350- return namelist
350+ return list
351351end
352352
353- --- @param vector any[] # a df vector or array, or a Lua list.
353+ --- @generic T
354+ --- @param vector ` T ` [] # a df vector or array, or a Lua list (not tested yet).
354355--- @param field string ? # nil, or the field name to sort on.
355- --- @param comparator fun ( a : any , b : any ): integer | nil
356+ --- @param comparator fun ( a : T , b : T ): integer | nil
356357--- # an optional comparator that returns -1,0,1 per utils.compare_* .
357358--- # nil falls back to utils.compare or utils.compare_field.
358359--- # if a comparator is given, the field parameter is ignored.
@@ -378,17 +379,46 @@ local function verify_vector_is_sorted(vector, field, comparator)
378379 return sorted
379380end
380381
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
382+ local Cache_nemesis_all = defclass (Cache_nemesis_all , nil ) -- singleton, don't need to instantiate.
383+ Cache_nemesis_all .ATTRS {}
384+
385+ function Cache_nemesis_all :invalidate ()
386+ self .ATTRS .cached_on_year = - 1
387+ end
388+
389+ --- @param force boolean ? # true to force updating the cached data
390+ function Cache_nemesis_all :populate (force )
391+ local recheck_after = (self .ATTRS .cached_on_tick or 0 ) + 1200
392+ if force ~= true
393+ and dfhack .world .ReadCurrentYear () == self .ATTRS .cached_on_year
394+ and dfhack .world .ReadCurrentTick () < recheck_after
395+ and self .ATTRS .nemesis_all_length == # df .global .world .nemesis .all
396+ and self .ATTRS .units_active_length == # df .global .world .units .active
397+ then
398+ return
388399 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
400+ self .ATTRS .cached_on_year = dfhack .world .ReadCurrentYear ()
401+ self .ATTRS .cached_on_tick = dfhack .world .ReadCurrentTick ()
402+ self .ATTRS .nemesis_all_length = # df .global .world .nemesis .all
403+ self .ATTRS .units_active_length = # df .global .world .units .active
404+ self .ATTRS .nemesis_all_is_sorted = verify_vector_is_sorted (df .global .world .nemesis .all , ' id' )
405+ self .ATTRS .affected_unit_ids = _get_active_unit_ids_with_missing_nemesis_records ()
406+ end
407+
408+ function Cache_nemesis_all :init ()
409+ self :invalidate ()
410+ end
411+
412+ --- @return boolean
413+ function Cache_nemesis_all :is_sorted ()
414+ self :populate ()
415+ return self .ATTRS .nemesis_all_is_sorted
416+ end
417+
418+ --- @return df.unit[]
419+ function Cache_nemesis_all :get_affected_unit_ids ()
420+ self :populate ()
421+ return self .ATTRS .affected_unit_ids
392422end
393423
394424-- the order of this list controls the order the notifications will appear in the overlay
@@ -398,7 +428,7 @@ NOTIFICATIONS_BY_IDX = {
398428 desc = ' Reports missing nemesis records, indicating savegame corruption.' ,
399429 default = true ,
400430 fn = function ()
401- if not verify_nemesis_all_is_sorted () then
431+ if not Cache_nemesis_all : is_sorted () then
402432 return { {
403433 pen = COLOR_LIGHTRED ,
404434 text = ' nemesis vector not sorted'
@@ -407,12 +437,13 @@ NOTIFICATIONS_BY_IDX = {
407437 local count = df .global .nemesis_next_id - # df .global .world .nemesis .all
408438 if count == 0 then return end
409439 return { {
410- pen = COLOR_LIGHTRED ,
440+ pen = # Cache_nemesis_all :get_affected_unit_ids () > 0
441+ and COLOR_LIGHTRED or COLOR_YELLOW ,
411442 text = (' missing %d nemesis record%s' ):format (count , count == 1 and ' ' or ' s' )
412443 } }
413444 end ,
414445 on_click = function ()
415- if not verify_nemesis_all_is_sorted () then
446+ if not Cache_nemesis_all : is_sorted () then
416447 local message =
417448 ' This save game is corrupt.\n\n The world.nemesis.global vector\n ' ..
418449 ' of this savegame is not sorted.\n\n Some attempts to lookup the\n ' ..
@@ -421,6 +452,7 @@ NOTIFICATIONS_BY_IDX = {
421452 dlg .showMessage (' nemesis vector not sorted' , message , COLOR_RED )
422453 return
423454 end
455+ local list = Cache_nemesis_all :get_affected_unit_ids ()
424456 local message = {
425457 { pen = COLOR_RED , text = ' This save game may be corrupt.' }, NEWLINE ,
426458 NEWLINE ,
@@ -431,23 +463,31 @@ NOTIFICATIONS_BY_IDX = {
431463 { pen = COLOR_WHITE , text = ' crashes during game save and when retiring forts.' }, NEWLINE ,
432464 NEWLINE ,
433465 { pen = COLOR_WHITE , text = ' Units with missing nemesis records will' }, NEWLINE ,
434- { pen = COLOR_RED , text = ' permanently disappear' },
466+ { pen = # list > 0 and COLOR_RED or COLOR_WHITE ,
467+ text = ' permanently disappear' },
435468 { pen = COLOR_WHITE , text = ' if they leave the map or' }, NEWLINE ,
436469 { pen = COLOR_WHITE , text = ' if the fort is retired.' }, NEWLINE ,
437470 NEWLINE ,
438471 }
439- local redtext = get_active_units_with_missing_nemesis_records ()
440- if # redtext > 0 then
472+ if # list > 0 then
441473 table.insert (message , { pen = COLOR_RED ,
442- text = ' These active units are missing their nemesis records:' })
474+ text = ' These units on the map are missing their nemesis records:' })
443475 table.insert (message , NEWLINE )
444- for _ , line in ipairs (redtext ) do
445- table.insert (message , { pen = COLOR_LIGHTRED , text = ' ' .. line })
476+ for _ , unit_id in ipairs (list ) do
477+ local unit = df .unit .find (unit_id )
478+ local text = unit and dfhack .units .getReadableName (unit )
479+ or " missing unit for unit id " .. unit_id
480+ if # text > 55 then text = dfhack .units .getReadableName (unit , true ); end
481+ table.insert (message , { pen = COLOR_LIGHTRED , text = text })
446482 table.insert (message , NEWLINE )
447483 end
484+ else
485+ table.insert (message , { pen = COLOR_YELLOW ,
486+ text = ' No units on the map are missing their nemesis records.' })
487+ table.insert (message , NEWLINE )
448488 end
449- dlg .showMessage ((# redtext > 0 and ' Active units are' or ' This world is' )
450- .. ' missing nemesis records' ,message , COLOR_WHITE )
489+ dlg .showMessage ((# list > 0 and ' Units on the map are' or ' This world is' )
490+ .. ' missing nemesis records' , message , COLOR_WHITE )
451491 end ,
452492 },
453493 {
728768config = get_config ()
729769
730770dfhack .onStateChange [' internal/notify/notifications' ] = function (event )
731- if event == SC_WORLD_LOADED or event == SC_WORLD_UNLOADED then
732- cache_nemesis_all_is_sorted = {}
771+ if event == SC_WORLD_LOADED or event == SC_WORLD_UNLOADED
772+ or event == SC_MAP_LOADED or event == SC_MAP_UNLOADED
773+ then
774+ Cache_nemesis_all :invalidate ()
733775 end
734776end
0 commit comments