Skip to content

Commit 14ad7b9

Browse files
committed
internal/notify/notifications add test for unsorted world.nemesis.all
1 parent 021a9cf commit 14ad7b9

1 file changed

Lines changed: 67 additions & 3 deletions

File tree

internal/notify/notifications.lua

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
local dlg = require('gui.dialogs')
44
local gui = require('gui')
55
local json = require('json')
6+
local utils = require('utils')
67
local list_agreements = reqscript('list-agreements')
78
local repeat_util = require('repeat-util')
89
local stuck_squad = reqscript('fix/stuck-squad')
@@ -334,7 +335,8 @@ local function save_popup()
334335
end
335336
end
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
349351
end
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
352395
NOTIFICATIONS_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\nThe world.nemesis.global vector\n' ..
418+
'of this savegame is not sorted.\n\nSome attempts to lookup the\n' ..
419+
'nemesis record for a unit or\nhistorical figure will fail.\n\n' ..
420+
'This should be reported to\nBay 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()
668726
end
669727

670728
config = 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

Comments
 (0)