|
|
|
@ -2,9 +2,168 @@ local _ENV = mkmodule('plugins.sort.creatures')
|
|
|
|
|
|
|
|
|
|
local overlay = require('plugins.overlay')
|
|
|
|
|
local widgets = require('gui.widgets')
|
|
|
|
|
local utils = require('utils')
|
|
|
|
|
|
|
|
|
|
local creatures = df.global.game.main_interface.info.creatures
|
|
|
|
|
|
|
|
|
|
-- these sort functions attempt to match the vanilla sort behavior, which is not
|
|
|
|
|
-- quite the same as the rest of DFHack. For example, in other DFHack sorts,
|
|
|
|
|
-- we'd always sort by name descending as a secondary sort. To match vanilla sorting,
|
|
|
|
|
-- if the primary sort is ascending, the secondary name sort will also be ascending.
|
|
|
|
|
--
|
|
|
|
|
-- also note that vanilla sorts are not stable, so there might still be some jitter
|
|
|
|
|
-- if the player clicks one of the vanilla sort widgets after searching
|
|
|
|
|
local function sort_by_name_desc(a, b)
|
|
|
|
|
return a.sort_name < b.sort_name
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function sort_by_name_asc(a, b)
|
|
|
|
|
return a.sort_name > b.sort_name
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function sort_by_prof_desc(a, b)
|
|
|
|
|
if a.profession_list_order1 == b.profession_list_order1 then
|
|
|
|
|
return sort_by_name_desc(a, b)
|
|
|
|
|
end
|
|
|
|
|
return a.profession_list_order1 < b.profession_list_order1
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function sort_by_prof_asc(a, b)
|
|
|
|
|
if a.profession_list_order1 == b.profession_list_order1 then
|
|
|
|
|
return sort_by_name_asc(a, b)
|
|
|
|
|
end
|
|
|
|
|
return a.profession_list_order1 > b.profession_list_order1
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function sort_by_job_name_desc(a, b)
|
|
|
|
|
if a.job_sort_name == b.job_sort_name then
|
|
|
|
|
return sort_by_name_desc(a, b)
|
|
|
|
|
end
|
|
|
|
|
return a.job_sort_name < b.job_sort_name
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function sort_by_job_name_asc(a, b)
|
|
|
|
|
if a.job_sort_name == b.job_sort_name then
|
|
|
|
|
-- use descending tertiary sort for visual stability
|
|
|
|
|
return sort_by_name_desc(a, b)
|
|
|
|
|
end
|
|
|
|
|
return a.job_sort_name > b.job_sort_name
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function sort_by_job_desc(a, b)
|
|
|
|
|
if not not a.jb == not not b.jb then
|
|
|
|
|
return sort_by_job_name_desc(a, b)
|
|
|
|
|
end
|
|
|
|
|
return not not a.jb
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function sort_by_job_asc(a, b)
|
|
|
|
|
if not not a.jb == not not b.jb then
|
|
|
|
|
return sort_by_job_name_asc(a, b)
|
|
|
|
|
end
|
|
|
|
|
return not not b.jb
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function sort_by_stress_desc(a, b)
|
|
|
|
|
if a.stress == b.stress then
|
|
|
|
|
return sort_by_name_desc(a, b)
|
|
|
|
|
end
|
|
|
|
|
return a.stress > b.stress
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function sort_by_stress_asc(a, b)
|
|
|
|
|
if a.stress == b.stress then
|
|
|
|
|
return sort_by_name_asc(a, b)
|
|
|
|
|
end
|
|
|
|
|
return a.stress < b.stress
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function get_sort()
|
|
|
|
|
if creatures.sorting_cit_job then
|
|
|
|
|
return creatures.sorting_cit_job_is_ascending and sort_by_job_asc or sort_by_job_desc
|
|
|
|
|
elseif creatures.sorting_cit_stress then
|
|
|
|
|
return creatures.sorting_cit_stress_is_ascending and sort_by_stress_asc or sort_by_stress_desc
|
|
|
|
|
elseif creatures.sorting_cit_nameprof_doing_prof then
|
|
|
|
|
return creatures.sorting_cit_nameprof_is_ascending and sort_by_prof_asc or sort_by_prof_desc
|
|
|
|
|
else
|
|
|
|
|
return creatures.sorting_cit_nameprof_is_ascending and sort_by_name_asc or sort_by_name_desc
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function copy_to_lua_table(vec)
|
|
|
|
|
local tab = {}
|
|
|
|
|
for k,v in ipairs(vec) do
|
|
|
|
|
tab[k+1] = v
|
|
|
|
|
end
|
|
|
|
|
return tab
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function general_search(vec, get_search_key_fn, get_sort_fn, data, filter, incremental)
|
|
|
|
|
if not data.saved_original then
|
|
|
|
|
data.saved_original = copy_to_lua_table(vec)
|
|
|
|
|
elseif not incremental then
|
|
|
|
|
vec:assign(data.saved_original)
|
|
|
|
|
end
|
|
|
|
|
if filter ~= '' then
|
|
|
|
|
local search_tokens = filter:split()
|
|
|
|
|
for idx = #vec-1,0,-1 do
|
|
|
|
|
local search_key = get_search_key_fn(vec[idx])
|
|
|
|
|
if search_key and not utils.search_text(search_key, search_tokens) then
|
|
|
|
|
vec:erase(idx)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
data.saved_visible = copy_to_lua_table(vec)
|
|
|
|
|
if get_sort_fn then
|
|
|
|
|
table.sort(data.saved_visible, get_sort_fn())
|
|
|
|
|
vec:assign(data.saved_visible)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- add dynamically allocated elements that were not visible at the time of
|
|
|
|
|
-- closure back to the vector so they can be cleaned up when it is next initialized
|
|
|
|
|
local function cri_unitst_cleanup(vec, data)
|
|
|
|
|
if not data.saved_visible or not data.saved_original then return end
|
|
|
|
|
for _,elem in ipairs(data.saved_original) do
|
|
|
|
|
if not utils.linear_index(data.saved_visible, elem) then
|
|
|
|
|
vec:insert('#', elem)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function make_cri_unitst_handlers(vec)
|
|
|
|
|
return {
|
|
|
|
|
search_fn=curry(general_search, vec,
|
|
|
|
|
function(elem) return elem.sort_name end,
|
|
|
|
|
get_sort),
|
|
|
|
|
cleanup_fn=curry(cri_unitst_cleanup, vec),
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function overall_training_search(data, filter, incremental)
|
|
|
|
|
general_search(creatures.atk_index, function(elem)
|
|
|
|
|
local raw = df.creature_raw.find(elem)
|
|
|
|
|
if not raw then return '' end
|
|
|
|
|
return raw.name[1]
|
|
|
|
|
end, nil, data, filter, incremental)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function assign_trainer_search(data, filter, incremental)
|
|
|
|
|
general_search(creatures.trainer, function(elem)
|
|
|
|
|
if not elem then return nil end
|
|
|
|
|
return ('%s %s'):format(dfhack.TranslateName(elem.name), dfhack.units.getProfessionName(elem))
|
|
|
|
|
end, nil, data, filter, incremental)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local HANDLERS = {
|
|
|
|
|
CITIZEN=make_cri_unitst_handlers(creatures.cri_unit.CITIZEN),
|
|
|
|
|
PET=make_cri_unitst_handlers(creatures.cri_unit.PET),
|
|
|
|
|
OTHER=make_cri_unitst_handlers(creatures.cri_unit.OTHER),
|
|
|
|
|
DECEASED=make_cri_unitst_handlers(creatures.cri_unit.DECEASED),
|
|
|
|
|
PET_OT={search_fn=overall_training_search},
|
|
|
|
|
PET_AT={search_fn=assign_trainer_search},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
-- ----------------------
|
|
|
|
|
-- InfoOverlay
|
|
|
|
|
--
|
|
|
|
@ -46,10 +205,18 @@ function InfoOverlay:init()
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function InfoOverlay:overlay_onupdate(scr)
|
|
|
|
|
if next(self.state) and not dfhack.gui.matchFocusString('dwarfmode/Info', scr) then
|
|
|
|
|
-- TODO: add dynamically allocated elements that were not visible at the time of
|
|
|
|
|
-- closure back to the list so they can be properly disposed of
|
|
|
|
|
local function cleanup(state)
|
|
|
|
|
for k,v in pairs(state) do
|
|
|
|
|
local cleanup_fn = safe_index(HANDLERS, k, 'cleanup_fn')
|
|
|
|
|
if cleanup_fn then cleanup_fn(v) end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function InfoOverlay:overlay_onupdate()
|
|
|
|
|
if next(self.state) and
|
|
|
|
|
not dfhack.gui.matchFocusString('dwarfmode/Info', dfhack.gui.getDFViewscreen(true))
|
|
|
|
|
then
|
|
|
|
|
cleanup(self.state)
|
|
|
|
|
self.state = {}
|
|
|
|
|
self.subviews.search:setText('')
|
|
|
|
|
self.subviews.search:setFocus(false)
|
|
|
|
@ -121,19 +288,19 @@ function InfoOverlay:text_input(text)
|
|
|
|
|
-- check_context.
|
|
|
|
|
local key = get_key()
|
|
|
|
|
local prev_text = ensure_key(self.state, key).prev_text
|
|
|
|
|
if text == prev_text then return end
|
|
|
|
|
if prev_text and text:startswith(prev_text) then
|
|
|
|
|
-- TODO: search
|
|
|
|
|
print('performing incremental search; text:', text, 'key:', key)
|
|
|
|
|
else
|
|
|
|
|
-- TODO: save list if not already saved
|
|
|
|
|
-- TODO: else restore list from saved list
|
|
|
|
|
-- TODO: if text ~= '' then search
|
|
|
|
|
-- TODO: sort according to vanilla sort widget state
|
|
|
|
|
print('performing full search; text:', text, 'key:', key)
|
|
|
|
|
end
|
|
|
|
|
-- TODO: save visible list
|
|
|
|
|
-- some screens reset their contents between context switches; regardless
|
|
|
|
|
-- a switch back to the context should results in an incremental search
|
|
|
|
|
local incremental = prev_text and text:startswith(prev_text)
|
|
|
|
|
HANDLERS[key].search_fn(self.state[key], text, incremental)
|
|
|
|
|
self.state[key].prev_text = text
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function InfoOverlay:onInput(keys)
|
|
|
|
|
if keys._MOUSE_R and self.subviews.search.focus then
|
|
|
|
|
self.subviews.search:setFocus(false)
|
|
|
|
|
return true
|
|
|
|
|
end
|
|
|
|
|
return InfoOverlay.super.onInput(self, keys)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
return _ENV
|
|
|
|
|