2023-10-08 14:44:09 -06:00
|
|
|
local _ENV = mkmodule('plugins.sort.info')
|
2023-10-07 13:11:24 -06:00
|
|
|
|
2023-10-09 03:37:32 -06:00
|
|
|
local gui = require('gui')
|
2023-11-05 04:09:30 -07:00
|
|
|
local overlay = require('plugins.overlay')
|
2023-10-10 04:57:03 -06:00
|
|
|
local sortoverlay = require('plugins.sort.sortoverlay')
|
2023-10-07 13:11:24 -06:00
|
|
|
local widgets = require('gui.widgets')
|
2023-10-07 19:40:47 -06:00
|
|
|
local utils = require('utils')
|
2023-10-07 13:11:24 -06:00
|
|
|
|
2023-10-08 12:30:57 -06:00
|
|
|
local info = df.global.game.main_interface.info
|
2023-10-13 15:33:22 -06:00
|
|
|
local administrators = info.administrators
|
2023-10-08 12:30:57 -06:00
|
|
|
local creatures = info.creatures
|
2023-10-08 16:49:11 -06:00
|
|
|
local justice = info.justice
|
2023-10-09 02:10:32 -06:00
|
|
|
local objects = info.artifacts
|
2023-10-09 02:44:21 -06:00
|
|
|
local tasks = info.jobs
|
2023-10-09 02:10:32 -06:00
|
|
|
local work_details = info.labor.work_details
|
2023-10-07 13:11:24 -06:00
|
|
|
|
2023-10-09 02:44:21 -06:00
|
|
|
-- these sort functions attempt to match the vanilla info panel sort behavior, which
|
2023-10-08 12:30:57 -06:00
|
|
|
-- is not quite the same as the rest of DFHack. For example, in other DFHack sorts,
|
2023-10-07 19:40:47 -06:00
|
|
|
-- 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
|
|
|
|
|
2023-10-10 04:57:03 -06:00
|
|
|
local function get_cri_unit_search_key(cri_unit)
|
|
|
|
return ('%s %s'):format(
|
2023-10-26 20:24:01 -06:00
|
|
|
cri_unit.un and sortoverlay.get_unit_search_key(cri_unit.un) or '',
|
2023-10-10 04:57:03 -06:00
|
|
|
cri_unit.job_sort_name)
|
|
|
|
end
|
|
|
|
|
2023-11-20 18:32:50 -07:00
|
|
|
local function get_task_search_key(cri_unit)
|
|
|
|
local result = {get_cri_unit_search_key(cri_unit)}
|
|
|
|
|
|
|
|
if cri_unit.jb then
|
|
|
|
local bld = dfhack.job.getHolder(cri_unit.jb)
|
|
|
|
if bld then
|
|
|
|
table.insert(result, bld.name)
|
|
|
|
local btype = bld:getType()
|
|
|
|
if btype == df.building_type.Workshop then
|
|
|
|
table.insert(result, df.workshop_type.attrs[bld.type].name or '')
|
|
|
|
table.insert(result, df.workshop_type[bld.type])
|
|
|
|
elseif btype == df.building_type.Furnace then
|
|
|
|
table.insert(result, df.furnace_type[bld.type])
|
|
|
|
elseif btype == df.building_type.Construction then
|
|
|
|
table.insert(result, df.construction_type[bld.type])
|
|
|
|
elseif btype == df.building_type.Trap then
|
|
|
|
table.insert(result, df.trap_type[bld.trap_type])
|
|
|
|
elseif btype == df.building_type.SiegeEngine then
|
|
|
|
table.insert(result, df.siegeengine_type[bld.type])
|
|
|
|
else
|
|
|
|
table.insert(result, df.building_type.attrs[btype].name)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return table.concat(result, ' ')
|
|
|
|
end
|
|
|
|
|
2023-10-10 04:57:03 -06:00
|
|
|
local function get_race_name(raw_id)
|
|
|
|
local raw = df.creature_raw.find(raw_id)
|
|
|
|
if not raw then return end
|
|
|
|
return raw.name[1]
|
2023-10-07 19:40:47 -06:00
|
|
|
end
|
|
|
|
|
2023-10-10 04:57:03 -06:00
|
|
|
-- get name in both dwarvish and English
|
|
|
|
local function get_artifact_search_key(artifact)
|
|
|
|
return ('%s %s'):format(dfhack.TranslateName(artifact.name), dfhack.TranslateName(artifact.name, true))
|
2023-10-07 19:40:47 -06:00
|
|
|
end
|
|
|
|
|
2023-10-10 04:57:03 -06:00
|
|
|
local function work_details_search(vec, data, text, incremental)
|
2023-10-09 02:10:32 -06:00
|
|
|
if work_details.selected_work_detail_index ~= data.selected then
|
|
|
|
data.saved_original = nil
|
|
|
|
data.selected = work_details.selected_work_detail_index
|
|
|
|
end
|
2023-10-10 04:57:03 -06:00
|
|
|
sortoverlay.single_vector_search(
|
2023-10-26 20:24:01 -06:00
|
|
|
{get_search_key_fn=sortoverlay.get_unit_search_key},
|
2023-10-10 04:57:03 -06:00
|
|
|
vec, data, text, incremental)
|
2023-10-09 02:44:21 -06:00
|
|
|
end
|
|
|
|
|
2023-11-20 13:53:58 -07:00
|
|
|
local function free_allocated_data(data)
|
|
|
|
if data.saved_visible and data.saved_original and #data.saved_visible ~= #data.saved_original then
|
|
|
|
for _,elem in ipairs(data.saved_original) do
|
|
|
|
if not utils.linear_index(data.saved_visible, elem) then
|
|
|
|
elem:delete()
|
|
|
|
end
|
2023-10-10 17:41:02 -06:00
|
|
|
end
|
|
|
|
end
|
2023-11-20 13:53:58 -07:00
|
|
|
data.saved_original, data.saved_visible = nil, nil
|
2023-10-10 17:41:02 -06:00
|
|
|
end
|
|
|
|
|
2023-10-13 15:33:22 -06:00
|
|
|
local function serialize_skills(unit)
|
|
|
|
if not unit or not unit.status or not unit.status.current_soul then
|
|
|
|
return ''
|
|
|
|
end
|
|
|
|
local skills = {}
|
|
|
|
for _, skill in ipairs(unit.status.current_soul.skills) do
|
|
|
|
if skill.rating > 0 then -- ignore dabbling
|
|
|
|
table.insert(skills, df.job_skill[skill.id])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return table.concat(skills, ' ')
|
|
|
|
end
|
|
|
|
|
|
|
|
local function get_candidate_search_key(cand)
|
|
|
|
if not cand.un then return end
|
|
|
|
return ('%s %s'):format(
|
2023-10-26 20:24:01 -06:00
|
|
|
sortoverlay.get_unit_search_key(cand.un),
|
2023-10-13 15:33:22 -06:00
|
|
|
serialize_skills(cand.un))
|
|
|
|
end
|
|
|
|
|
2023-10-07 13:11:24 -06:00
|
|
|
-- ----------------------
|
|
|
|
-- InfoOverlay
|
|
|
|
--
|
|
|
|
|
2023-10-10 04:57:03 -06:00
|
|
|
InfoOverlay = defclass(InfoOverlay, sortoverlay.SortOverlay)
|
2023-10-07 13:11:24 -06:00
|
|
|
InfoOverlay.ATTRS{
|
2023-10-08 12:30:57 -06:00
|
|
|
default_pos={x=64, y=8},
|
2023-10-09 02:44:21 -06:00
|
|
|
viewscreens='dwarfmode/Info',
|
2023-11-05 01:26:14 -07:00
|
|
|
frame={w=40, h=6},
|
2023-10-07 13:11:24 -06:00
|
|
|
}
|
|
|
|
|
2023-11-06 09:52:48 -07:00
|
|
|
function get_squad_options()
|
2023-11-05 01:26:14 -07:00
|
|
|
local options = {{label='Any', value='all', pen=COLOR_GREEN}}
|
|
|
|
local fort = df.historical_entity.find(df.global.plotinfo.group_id)
|
|
|
|
if not fort then return options end
|
|
|
|
for _, squad_id in ipairs(fort.squads) do
|
|
|
|
table.insert(options, {
|
|
|
|
label=dfhack.military.getSquadName(squad_id),
|
|
|
|
value=squad_id,
|
|
|
|
pen=COLOR_YELLOW,
|
|
|
|
})
|
|
|
|
end
|
|
|
|
return options
|
|
|
|
end
|
|
|
|
|
2023-11-06 09:52:48 -07:00
|
|
|
function get_burrow_options()
|
2023-11-05 04:20:36 -07:00
|
|
|
local options = {
|
|
|
|
{label='Any', value='all', pen=COLOR_GREEN},
|
|
|
|
{label='Unburrowed', value='none', pen=COLOR_LIGHTRED},
|
|
|
|
}
|
2023-11-05 01:26:14 -07:00
|
|
|
for _, burrow in ipairs(df.global.plotinfo.burrows.list) do
|
|
|
|
table.insert(options, {
|
|
|
|
label=#burrow.name > 0 and burrow.name or ('Burrow %d'):format(burrow.id + 1),
|
|
|
|
value=burrow.id,
|
|
|
|
pen=COLOR_YELLOW,
|
|
|
|
})
|
|
|
|
end
|
|
|
|
return options
|
|
|
|
end
|
|
|
|
|
2023-11-06 09:52:48 -07:00
|
|
|
function matches_squad_burrow_filters(unit, subset, target_squad_id, target_burrow_id)
|
|
|
|
if subset == 'all' then
|
|
|
|
return true
|
|
|
|
elseif subset == 'civilian' then
|
|
|
|
return unit.military.squad_id == -1
|
|
|
|
elseif subset == 'military' then
|
|
|
|
local squad_id = unit.military.squad_id
|
|
|
|
if squad_id == -1 then return false end
|
|
|
|
if target_squad_id == 'all' then return true end
|
|
|
|
return target_squad_id == squad_id
|
|
|
|
elseif subset == 'burrow' then
|
|
|
|
if target_burrow_id == 'all' then return #unit.burrows + #unit.inactive_burrows > 0 end
|
|
|
|
if target_burrow_id == 'none' then return #unit.burrows + #unit.inactive_burrows == 0 end
|
|
|
|
return utils.binsearch(unit.burrows, target_burrow_id) or
|
|
|
|
utils.binsearch(unit.inactive_burrows, target_burrow_id)
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2023-10-07 13:11:24 -06:00
|
|
|
function InfoOverlay:init()
|
|
|
|
self:addviews{
|
|
|
|
widgets.BannerPanel{
|
|
|
|
view_id='panel',
|
|
|
|
frame={l=0, t=0, r=0, h=1},
|
2023-10-10 04:57:03 -06:00
|
|
|
visible=self:callback('get_key'),
|
2023-10-07 13:11:24 -06:00
|
|
|
subviews={
|
|
|
|
widgets.EditField{
|
|
|
|
view_id='search',
|
|
|
|
frame={l=1, t=0, r=1},
|
|
|
|
label_text="Search: ",
|
|
|
|
key='CUSTOM_ALT_S',
|
2023-10-10 04:57:03 -06:00
|
|
|
on_change=function(text) self:do_search(text) end,
|
2023-10-07 13:11:24 -06:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2023-11-04 19:59:20 -06:00
|
|
|
widgets.BannerPanel{
|
2023-11-05 01:26:14 -07:00
|
|
|
view_id='subset_panel',
|
2023-11-04 19:59:20 -06:00
|
|
|
frame={l=0, t=1, r=0, h=1},
|
|
|
|
visible=function() return self:get_key() == 'PET_WA' end,
|
|
|
|
subviews={
|
|
|
|
widgets.CycleHotkeyLabel{
|
|
|
|
view_id='subset',
|
2023-11-06 09:52:48 -07:00
|
|
|
frame={l=1, t=0, r=1},
|
2023-11-04 19:59:20 -06:00
|
|
|
key='CUSTOM_SHIFT_F',
|
|
|
|
label='Show:',
|
|
|
|
options={
|
|
|
|
{label='All', value='all', pen=COLOR_GREEN},
|
|
|
|
{label='Military', value='military', pen=COLOR_YELLOW},
|
|
|
|
{label='Civilians', value='civilian', pen=COLOR_CYAN},
|
2023-11-05 01:26:14 -07:00
|
|
|
{label='Burrowed', value='burrow', pen=COLOR_MAGENTA},
|
|
|
|
},
|
|
|
|
on_change=function(value)
|
|
|
|
local squad = self.subviews.squad
|
|
|
|
local burrow = self.subviews.burrow
|
|
|
|
squad.visible = false
|
|
|
|
burrow.visible = false
|
|
|
|
if value == 'military' then
|
|
|
|
squad.options = get_squad_options()
|
|
|
|
squad:setOption('all')
|
|
|
|
squad.visible = true
|
|
|
|
elseif value == 'burrow' then
|
|
|
|
burrow.options = get_burrow_options()
|
|
|
|
burrow:setOption('all')
|
|
|
|
burrow.visible = true
|
|
|
|
end
|
|
|
|
self:do_search(self.subviews.search.text, true)
|
|
|
|
end,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
widgets.BannerPanel{
|
|
|
|
view_id='subfilter_panel',
|
|
|
|
frame={l=0, t=2, r=0, h=1},
|
|
|
|
visible=function()
|
|
|
|
local subset = self.subviews.subset:getOptionValue()
|
|
|
|
return self:get_key() == 'PET_WA' and (subset == 'military' or subset == 'burrow')
|
|
|
|
end,
|
|
|
|
subviews={
|
|
|
|
widgets.CycleHotkeyLabel{
|
|
|
|
view_id='squad',
|
2023-11-06 09:52:48 -07:00
|
|
|
frame={l=1, t=0, r=1},
|
2023-11-05 01:26:14 -07:00
|
|
|
key='CUSTOM_SHIFT_S',
|
|
|
|
label='Squad:',
|
|
|
|
options={
|
|
|
|
{label='Any', value='all', pen=COLOR_GREEN},
|
2023-11-04 19:59:20 -06:00
|
|
|
},
|
2023-11-05 01:26:14 -07:00
|
|
|
visible=false,
|
|
|
|
on_change=function() self:do_search(self.subviews.search.text, true) end,
|
|
|
|
},
|
|
|
|
widgets.CycleHotkeyLabel{
|
|
|
|
view_id='burrow',
|
2023-11-06 09:52:48 -07:00
|
|
|
frame={l=1, t=0, r=1},
|
2023-11-05 01:26:14 -07:00
|
|
|
key='CUSTOM_SHIFT_B',
|
|
|
|
label='Burrow:',
|
|
|
|
options={
|
|
|
|
{label='Any', value='all', pen=COLOR_GREEN},
|
|
|
|
},
|
|
|
|
visible=false,
|
2023-11-04 19:59:20 -06:00
|
|
|
on_change=function() self:do_search(self.subviews.search.text, true) end,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2023-10-07 13:11:24 -06:00
|
|
|
}
|
|
|
|
|
2023-10-10 04:57:03 -06:00
|
|
|
local CRI_UNIT_VECS = {
|
|
|
|
CITIZEN=creatures.cri_unit.CITIZEN,
|
|
|
|
PET=creatures.cri_unit.PET,
|
|
|
|
OTHER=creatures.cri_unit.OTHER,
|
|
|
|
DECEASED=creatures.cri_unit.DECEASED,
|
|
|
|
}
|
|
|
|
for key,vec in pairs(CRI_UNIT_VECS) do
|
|
|
|
self:register_handler(key, vec,
|
|
|
|
curry(sortoverlay.single_vector_search,
|
|
|
|
{
|
|
|
|
get_search_key_fn=get_cri_unit_search_key,
|
|
|
|
get_sort_fn=get_sort
|
|
|
|
}),
|
2023-11-20 13:53:58 -07:00
|
|
|
free_allocated_data)
|
2023-10-10 04:57:03 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
self:register_handler('JOBS', tasks.cri_job,
|
2023-11-20 18:32:50 -07:00
|
|
|
curry(sortoverlay.single_vector_search, {get_search_key_fn=get_task_search_key}),
|
2023-11-20 13:53:58 -07:00
|
|
|
free_allocated_data)
|
2023-10-10 04:57:03 -06:00
|
|
|
self:register_handler('PET_OT', creatures.atk_index,
|
|
|
|
curry(sortoverlay.single_vector_search, {get_search_key_fn=get_race_name}))
|
|
|
|
self:register_handler('PET_AT', creatures.trainer,
|
2023-11-04 19:59:20 -06:00
|
|
|
curry(sortoverlay.single_vector_search, {get_search_key_fn=sortoverlay.get_unit_search_key}))
|
|
|
|
self:register_handler('PET_WA', creatures.work_animal_recipient,
|
|
|
|
curry(sortoverlay.single_vector_search, {
|
|
|
|
get_search_key_fn=sortoverlay.get_unit_search_key,
|
|
|
|
matches_filters_fn=self:callback('matches_filters'),
|
|
|
|
}))
|
2023-10-10 04:57:03 -06:00
|
|
|
self:register_handler('WORK_DETAILS', work_details.assignable_unit, work_details_search)
|
|
|
|
|
|
|
|
for idx,name in ipairs(df.artifacts_mode_type) do
|
|
|
|
if idx < 0 then goto continue end
|
|
|
|
self:register_handler(name, objects.list[idx],
|
|
|
|
curry(sortoverlay.single_vector_search, {get_search_key_fn=get_artifact_search_key}))
|
|
|
|
::continue::
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-11-06 18:58:09 -07:00
|
|
|
function InfoOverlay:reset()
|
|
|
|
InfoOverlay.super.reset(self)
|
|
|
|
self.subviews.subset:setOption('all')
|
|
|
|
end
|
|
|
|
|
2023-10-10 04:57:03 -06:00
|
|
|
function InfoOverlay:get_key()
|
|
|
|
if info.current_mode == df.info_interface_mode_type.CREATURES then
|
|
|
|
if creatures.current_mode == df.unit_list_mode_type.PET then
|
|
|
|
if creatures.showing_overall_training then
|
2023-11-20 13:53:58 -07:00
|
|
|
return 'PET_OT', 'cre'
|
2023-10-10 04:57:03 -06:00
|
|
|
elseif creatures.adding_trainer then
|
2023-11-20 13:53:58 -07:00
|
|
|
return 'PET_AT', 'cre'
|
2023-11-04 19:59:20 -06:00
|
|
|
elseif creatures.assign_work_animal then
|
2023-11-20 13:53:58 -07:00
|
|
|
return 'PET_WA', 'cre'
|
2023-10-10 04:57:03 -06:00
|
|
|
end
|
|
|
|
end
|
2023-11-20 13:53:58 -07:00
|
|
|
return df.unit_list_mode_type[creatures.current_mode], 'cre'
|
2023-10-10 04:57:03 -06:00
|
|
|
elseif info.current_mode == df.info_interface_mode_type.JOBS then
|
|
|
|
return 'JOBS'
|
|
|
|
elseif info.current_mode == df.info_interface_mode_type.ARTIFACTS then
|
|
|
|
return df.artifacts_mode_type[objects.mode]
|
|
|
|
elseif info.current_mode == df.info_interface_mode_type.LABOR then
|
|
|
|
if info.labor.mode == df.labor_mode_type.WORK_DETAILS then
|
|
|
|
return 'WORK_DETAILS'
|
|
|
|
end
|
|
|
|
end
|
2023-10-09 03:32:26 -06:00
|
|
|
end
|
|
|
|
|
2023-11-10 14:56:14 -07:00
|
|
|
function resize_overlay(self)
|
2023-10-07 13:11:24 -06:00
|
|
|
local sw = dfhack.screen.getWindowSize()
|
|
|
|
local overlay_width = math.min(40, sw-(self.frame_rect.x1 + 30))
|
|
|
|
if overlay_width ~= self.frame.w then
|
|
|
|
self.frame.w = overlay_width
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-10-08 16:49:11 -06:00
|
|
|
local function is_tabs_in_two_rows()
|
|
|
|
return dfhack.screen.readTile(64, 6, false).ch == 0
|
|
|
|
end
|
|
|
|
|
2023-11-10 14:56:14 -07:00
|
|
|
function get_panel_offsets()
|
2023-10-08 16:49:11 -06:00
|
|
|
local tabs_in_two_rows = is_tabs_in_two_rows()
|
2023-10-09 02:10:32 -06:00
|
|
|
local shift_right = info.current_mode == df.info_interface_mode_type.ARTIFACTS or
|
|
|
|
info.current_mode == df.info_interface_mode_type.LABOR
|
|
|
|
local l_offset = (not tabs_in_two_rows and shift_right) and 4 or 0
|
2023-10-08 12:30:57 -06:00
|
|
|
local t_offset = 1
|
|
|
|
if tabs_in_two_rows then
|
2023-10-09 02:10:32 -06:00
|
|
|
t_offset = shift_right and 0 or 3
|
2023-10-08 12:30:57 -06:00
|
|
|
end
|
2023-11-10 14:56:14 -07:00
|
|
|
if info.current_mode == df.info_interface_mode_type.JOBS or
|
|
|
|
info.current_mode == df.info_interface_mode_type.BUILDINGS then
|
2023-10-09 02:44:21 -06:00
|
|
|
t_offset = t_offset - 1
|
|
|
|
end
|
2023-10-08 12:30:57 -06:00
|
|
|
return l_offset, t_offset
|
|
|
|
end
|
|
|
|
|
2023-10-07 13:11:24 -06:00
|
|
|
function InfoOverlay:updateFrames()
|
|
|
|
local ret = resize_overlay(self)
|
2023-10-08 12:30:57 -06:00
|
|
|
local l, t = get_panel_offsets()
|
|
|
|
local frame = self.subviews.panel.frame
|
2023-10-13 15:33:22 -06:00
|
|
|
if frame.l == l and frame.t == t then return ret end
|
2023-10-08 12:30:57 -06:00
|
|
|
frame.l, frame.t = l, t
|
2023-11-05 01:26:14 -07:00
|
|
|
local frame2 = self.subviews.subset_panel.frame
|
2023-11-04 19:59:20 -06:00
|
|
|
frame2.l, frame2.t = l, t + 1
|
2023-11-05 01:26:14 -07:00
|
|
|
local frame3 = self.subviews.subfilter_panel.frame
|
|
|
|
frame3.l, frame3.t = l, t + 2
|
2023-10-07 13:11:24 -06:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2023-11-27 11:09:23 -07:00
|
|
|
function InfoOverlay:do_refresh()
|
|
|
|
self.refresh_search = nil
|
|
|
|
if self:get_key() == 'JOBS' then
|
|
|
|
local data = self.state.JOBS
|
|
|
|
-- if any jobs have been canceled, fix up our data vectors
|
|
|
|
if data and data.saved_visible and data.saved_original then
|
|
|
|
local to_remove = {}
|
|
|
|
for _,elem in ipairs(data.saved_visible) do
|
|
|
|
if not utils.linear_index(tasks.cri_job, elem) then
|
|
|
|
table.insert(to_remove, elem)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
for _,elem in ipairs(to_remove) do
|
|
|
|
table.remove(data.saved_visible, utils.linear_index(data.saved_visible, elem))
|
|
|
|
data.saved_visible_size = data.saved_visible_size - 1
|
|
|
|
table.remove(data.saved_original, utils.linear_index(data.saved_original, elem))
|
|
|
|
data.saved_original_size = data.saved_original_size - 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:do_search(self.subviews.search.text, true)
|
|
|
|
end
|
|
|
|
|
2023-10-07 13:11:24 -06:00
|
|
|
function InfoOverlay:onRenderBody(dc)
|
2023-11-27 11:09:23 -07:00
|
|
|
if self.refresh_search then
|
|
|
|
self:do_refresh()
|
|
|
|
end
|
2023-10-10 04:57:03 -06:00
|
|
|
InfoOverlay.super.onRenderBody(self, dc)
|
2023-10-07 13:11:24 -06:00
|
|
|
if self:updateFrames() then
|
|
|
|
self:updateLayout()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-10-07 19:40:47 -06:00
|
|
|
function InfoOverlay:onInput(keys)
|
2023-11-27 11:09:23 -07:00
|
|
|
if self.refresh_search then
|
|
|
|
self:do_refresh()
|
|
|
|
end
|
|
|
|
if keys._MOUSE_L then
|
|
|
|
local key = self:get_key()
|
|
|
|
if key == 'WORK_DETAILS' or key == 'JOBS' then
|
|
|
|
self.refresh_search = true
|
|
|
|
end
|
2023-10-07 19:40:47 -06:00
|
|
|
end
|
2023-10-10 04:57:03 -06:00
|
|
|
return InfoOverlay.super.onInput(self, keys)
|
2023-10-07 19:40:47 -06:00
|
|
|
end
|
|
|
|
|
2023-11-04 19:59:20 -06:00
|
|
|
function InfoOverlay:matches_filters(unit)
|
2023-11-06 09:52:48 -07:00
|
|
|
return matches_squad_burrow_filters(unit, self.subviews.subset:getOptionValue(),
|
|
|
|
self.subviews.squad:getOptionValue(), self.subviews.burrow:getOptionValue())
|
2023-11-04 19:59:20 -06:00
|
|
|
end
|
|
|
|
|
2023-10-13 15:33:22 -06:00
|
|
|
-- ----------------------
|
|
|
|
-- CandidatesOverlay
|
|
|
|
--
|
|
|
|
|
|
|
|
CandidatesOverlay = defclass(CandidatesOverlay, sortoverlay.SortOverlay)
|
|
|
|
CandidatesOverlay.ATTRS{
|
|
|
|
default_pos={x=54, y=8},
|
|
|
|
viewscreens='dwarfmode/Info/ADMINISTRATORS/Candidates',
|
|
|
|
frame={w=27, h=3},
|
|
|
|
}
|
|
|
|
|
|
|
|
function CandidatesOverlay:init()
|
|
|
|
self:addviews{
|
|
|
|
widgets.BannerPanel{
|
|
|
|
view_id='panel',
|
|
|
|
frame={l=0, t=0, r=0, h=1},
|
|
|
|
subviews={
|
|
|
|
widgets.EditField{
|
|
|
|
view_id='search',
|
|
|
|
frame={l=1, t=0, r=1},
|
|
|
|
label_text="Search: ",
|
|
|
|
key='CUSTOM_ALT_S',
|
|
|
|
on_change=function(text) self:do_search(text) end,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
self:register_handler('CANDIDATE', administrators.candidate,
|
|
|
|
curry(sortoverlay.single_vector_search, {get_search_key_fn=get_candidate_search_key}),
|
2023-11-20 13:53:58 -07:00
|
|
|
free_allocated_data)
|
2023-10-13 15:33:22 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
function CandidatesOverlay:get_key()
|
|
|
|
if administrators.choosing_candidate then
|
|
|
|
return 'CANDIDATE'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function CandidatesOverlay:updateFrames()
|
|
|
|
local t = is_tabs_in_two_rows() and 2 or 0
|
|
|
|
local frame = self.subviews.panel.frame
|
|
|
|
if frame.t == t then return end
|
|
|
|
frame.t = t
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function CandidatesOverlay:onRenderBody(dc)
|
|
|
|
CandidatesOverlay.super.onRenderBody(self, dc)
|
|
|
|
if self:updateFrames() then
|
|
|
|
self:updateLayout()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-11-05 04:09:30 -07:00
|
|
|
-- ----------------------
|
|
|
|
-- WorkAnimalOverlay
|
|
|
|
--
|
|
|
|
|
|
|
|
WorkAnimalOverlay = defclass(WorkAnimalOverlay, overlay.OverlayWidget)
|
|
|
|
WorkAnimalOverlay.ATTRS{
|
|
|
|
default_pos={x=-33, y=12},
|
|
|
|
viewscreens='dwarfmode/Info/CREATURES/AssignWorkAnimal',
|
|
|
|
default_enabled=true,
|
|
|
|
frame={w=29, h=1},
|
|
|
|
}
|
|
|
|
|
|
|
|
function WorkAnimalOverlay:init()
|
|
|
|
self:addviews{
|
|
|
|
widgets.Label{
|
|
|
|
view_id='annotations',
|
|
|
|
frame={t=0, l=0},
|
|
|
|
text='',
|
|
|
|
}
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
local function get_work_animal_counts()
|
|
|
|
local counts = {}
|
|
|
|
for _,unit in ipairs(df.global.world.units.active) do
|
|
|
|
if not dfhack.units.isOwnCiv(unit) or
|
|
|
|
(not dfhack.units.isWar(unit) and not dfhack.units.isHunter(unit))
|
|
|
|
then
|
|
|
|
goto continue
|
|
|
|
end
|
|
|
|
local owner_id = unit.relationship_ids.Pet
|
|
|
|
if owner_id == -1 then goto continue end
|
|
|
|
counts[owner_id] = (counts[owner_id] or 0) + 1
|
|
|
|
::continue::
|
|
|
|
end
|
|
|
|
return counts
|
|
|
|
end
|
|
|
|
|
2023-11-24 18:22:29 -07:00
|
|
|
function WorkAnimalOverlay:preUpdateLayout(parent_rect)
|
2023-11-05 04:09:30 -07:00
|
|
|
local _, t = get_panel_offsets()
|
2023-11-24 18:22:29 -07:00
|
|
|
local list_height = parent_rect.height - (17 + t)
|
|
|
|
self.frame.h = list_height + t
|
|
|
|
self.subviews.annotations.frame.t = t
|
|
|
|
end
|
|
|
|
|
|
|
|
function WorkAnimalOverlay:onRenderFrame(dc, rect)
|
|
|
|
local t = self.subviews.annotations.frame.t
|
|
|
|
local num_elems = (self.frame.h - t) // 3
|
2023-11-05 04:09:30 -07:00
|
|
|
local max_elem = math.min(#creatures.work_animal_recipient-1,
|
|
|
|
creatures.scroll_position_work_animal+num_elems-1)
|
|
|
|
|
|
|
|
local annotations = {}
|
|
|
|
local counts = get_work_animal_counts()
|
|
|
|
for idx=creatures.scroll_position_work_animal,max_elem do
|
|
|
|
table.insert(annotations, NEWLINE)
|
|
|
|
table.insert(annotations, NEWLINE)
|
|
|
|
local animal_count = counts[creatures.work_animal_recipient[idx].id]
|
|
|
|
if animal_count and animal_count > 0 then
|
|
|
|
table.insert(annotations, {text='[', pen=COLOR_RED})
|
|
|
|
table.insert(annotations, ('Assigned work animals: %d'):format(animal_count))
|
|
|
|
table.insert(annotations, {text=']', pen=COLOR_RED})
|
|
|
|
end
|
|
|
|
table.insert(annotations, NEWLINE)
|
|
|
|
end
|
|
|
|
self.subviews.annotations:setText(annotations)
|
2023-11-24 18:22:29 -07:00
|
|
|
self.subviews.annotations:updateLayout()
|
2023-11-05 04:09:30 -07:00
|
|
|
|
|
|
|
WorkAnimalOverlay.super.onRenderFrame(self, dc, rect)
|
|
|
|
end
|
|
|
|
|
2023-10-08 16:49:11 -06:00
|
|
|
-- ----------------------
|
|
|
|
-- InterrogationOverlay
|
|
|
|
--
|
|
|
|
|
2023-10-10 04:57:03 -06:00
|
|
|
InterrogationOverlay = defclass(InterrogationOverlay, sortoverlay.SortOverlay)
|
2023-10-08 16:49:11 -06:00
|
|
|
InterrogationOverlay.ATTRS{
|
|
|
|
default_pos={x=47, y=10},
|
2023-10-08 17:28:02 -06:00
|
|
|
viewscreens='dwarfmode/Info/JUSTICE',
|
2023-10-08 16:49:11 -06:00
|
|
|
frame={w=27, h=9},
|
|
|
|
}
|
|
|
|
|
|
|
|
function InterrogationOverlay:init()
|
|
|
|
self:addviews{
|
|
|
|
widgets.Panel{
|
|
|
|
view_id='panel',
|
|
|
|
frame={l=0, t=4, h=5, r=0},
|
|
|
|
frame_background=gui.CLEAR_PEN,
|
|
|
|
frame_style=gui.FRAME_MEDIUM,
|
2023-10-10 04:57:03 -06:00
|
|
|
visible=self:callback('get_key'),
|
2023-10-08 16:49:11 -06:00
|
|
|
subviews={
|
|
|
|
widgets.EditField{
|
|
|
|
view_id='search',
|
|
|
|
frame={l=0, t=0, r=0},
|
|
|
|
label_text="Search: ",
|
|
|
|
key='CUSTOM_ALT_S',
|
2023-10-10 04:57:03 -06:00
|
|
|
on_change=function(text) self:do_search(text) end,
|
2023-10-09 02:10:32 -06:00
|
|
|
},
|
|
|
|
widgets.ToggleHotkeyLabel{
|
|
|
|
view_id='include_interviewed',
|
|
|
|
frame={l=0, t=1, w=23},
|
|
|
|
key='CUSTOM_SHIFT_I',
|
|
|
|
label='Interviewed:',
|
|
|
|
options={
|
|
|
|
{label='Include', value=true, pen=COLOR_GREEN},
|
|
|
|
{label='Exclude', value=false, pen=COLOR_RED},
|
|
|
|
},
|
|
|
|
visible=function() return justice.interrogating end,
|
2023-10-10 04:57:03 -06:00
|
|
|
on_change=function() self:do_search(self.subviews.search.text, true) end,
|
2023-10-08 16:49:11 -06:00
|
|
|
},
|
|
|
|
widgets.CycleHotkeyLabel{
|
|
|
|
view_id='subset',
|
2023-10-09 02:10:32 -06:00
|
|
|
frame={l=0, t=2, w=28},
|
2023-10-08 16:49:11 -06:00
|
|
|
key='CUSTOM_SHIFT_F',
|
|
|
|
label='Show:',
|
|
|
|
options={
|
|
|
|
{label='All', value='all', pen=COLOR_GREEN},
|
2023-10-09 02:10:32 -06:00
|
|
|
{label='Risky visitors', value='risky', pen=COLOR_RED},
|
2023-10-08 16:49:11 -06:00
|
|
|
{label='Other visitors', value='visitors', pen=COLOR_LIGHTRED},
|
|
|
|
{label='Residents', value='residents', pen=COLOR_YELLOW},
|
|
|
|
{label='Citizens', value='citizens', pen=COLOR_CYAN},
|
2023-10-09 02:10:32 -06:00
|
|
|
{label='Animals', value='animals', pen=COLOR_BLUE},
|
|
|
|
{label='Deceased or missing', value='deceased', pen=COLOR_MAGENTA},
|
|
|
|
{label='Others', value='others', pen=COLOR_GRAY},
|
2023-10-08 16:49:11 -06:00
|
|
|
},
|
2023-10-10 04:57:03 -06:00
|
|
|
on_change=function() self:do_search(self.subviews.search.text, true) end,
|
2023-10-08 16:49:11 -06:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2023-10-10 04:57:03 -06:00
|
|
|
|
|
|
|
self:register_handler('INTERROGATING', justice.interrogation_list,
|
|
|
|
curry(sortoverlay.flags_vector_search,
|
|
|
|
{
|
2023-10-26 20:24:01 -06:00
|
|
|
get_search_key_fn=sortoverlay.get_unit_search_key,
|
2023-10-10 04:57:03 -06:00
|
|
|
get_elem_id_fn=function(unit) return unit.id end,
|
|
|
|
matches_filters_fn=self:callback('matches_filters'),
|
|
|
|
},
|
|
|
|
justice.interrogation_list_flag))
|
|
|
|
self:register_handler('CONVICTING', justice.conviction_list,
|
|
|
|
curry(sortoverlay.single_vector_search,
|
|
|
|
{
|
2023-10-26 20:24:01 -06:00
|
|
|
get_search_key_fn=sortoverlay.get_unit_search_key,
|
2023-10-10 04:57:03 -06:00
|
|
|
matches_filters_fn=self:callback('matches_filters'),
|
|
|
|
}))
|
|
|
|
end
|
|
|
|
|
|
|
|
function InterrogationOverlay:reset()
|
|
|
|
InterrogationOverlay.super.reset(self)
|
|
|
|
self.subviews.include_interviewed:setOption(true, false)
|
|
|
|
self.subviews.subset:setOption('all')
|
|
|
|
end
|
|
|
|
|
|
|
|
function InterrogationOverlay:get_key()
|
|
|
|
if justice.interrogating then
|
|
|
|
return 'INTERROGATING'
|
|
|
|
elseif justice.convicting then
|
|
|
|
return 'CONVICTING'
|
|
|
|
end
|
2023-10-08 16:49:11 -06:00
|
|
|
end
|
|
|
|
|
2023-10-09 03:25:45 -06:00
|
|
|
local RISKY_PROFESSIONS = utils.invert{
|
|
|
|
df.profession.THIEF,
|
|
|
|
df.profession.MASTER_THIEF,
|
|
|
|
df.profession.CRIMINAL,
|
|
|
|
}
|
|
|
|
|
2023-10-09 02:10:32 -06:00
|
|
|
local function is_risky(unit)
|
2023-10-09 03:25:45 -06:00
|
|
|
if RISKY_PROFESSIONS[unit.profession] or RISKY_PROFESSIONS[unit.profession2] then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
if dfhack.units.getReadableName(unit):endswith('necromancer') then return true end
|
|
|
|
return not dfhack.units.isAlive(unit) -- detect intelligent undead
|
2023-10-09 02:10:32 -06:00
|
|
|
end
|
|
|
|
|
2023-10-10 04:57:03 -06:00
|
|
|
function InterrogationOverlay:matches_filters(unit, flag)
|
2023-10-09 02:10:32 -06:00
|
|
|
if justice.interrogating then
|
|
|
|
local include_interviewed = self.subviews.include_interviewed:getOptionValue()
|
2023-10-10 04:57:03 -06:00
|
|
|
if not include_interviewed and flag == 2 then return false end
|
2023-10-09 02:10:32 -06:00
|
|
|
end
|
|
|
|
local subset = self.subviews.subset:getOptionValue()
|
|
|
|
if subset == 'all' then
|
|
|
|
return true
|
|
|
|
elseif dfhack.units.isDead(unit) or not dfhack.units.isActive(unit) then
|
|
|
|
return subset == 'deceased'
|
2023-10-10 04:57:03 -06:00
|
|
|
elseif dfhack.units.isInvader(unit) or dfhack.units.isOpposedToLife(unit)
|
|
|
|
or unit.flags2.visitor_uninvited or unit.flags4.agitated_wilderness_creature
|
|
|
|
then
|
|
|
|
return subset == 'others'
|
2023-10-09 02:10:32 -06:00
|
|
|
elseif dfhack.units.isVisiting(unit) then
|
|
|
|
local risky = is_risky(unit)
|
|
|
|
return (subset == 'risky' and risky) or (subset == 'visitors' and not risky)
|
|
|
|
elseif dfhack.units.isAnimal(unit) then
|
|
|
|
return subset == 'animals'
|
|
|
|
elseif dfhack.units.isCitizen(unit) then
|
|
|
|
return subset == 'citizens'
|
2023-10-09 03:25:45 -06:00
|
|
|
elseif unit.flags2.roaming_wilderness_population_source then
|
|
|
|
return subset == 'others'
|
2023-10-09 02:10:32 -06:00
|
|
|
end
|
2023-10-09 03:25:45 -06:00
|
|
|
return subset == 'residents'
|
2023-10-09 02:10:32 -06:00
|
|
|
end
|
|
|
|
|
2023-10-08 16:49:11 -06:00
|
|
|
function InterrogationOverlay:render(dc)
|
|
|
|
local sw = dfhack.screen.getWindowSize()
|
|
|
|
local info_panel_border = 31 -- from edges of panel to screen edges
|
|
|
|
local info_panel_width = sw - info_panel_border
|
|
|
|
local info_panel_center = info_panel_width // 2
|
|
|
|
local panel_x_offset = (info_panel_center + 5) - self.frame_rect.x1
|
|
|
|
local frame_w = math.min(panel_x_offset + 37, info_panel_width - 56)
|
|
|
|
local panel_l = panel_x_offset
|
|
|
|
local panel_t = is_tabs_in_two_rows() and 4 or 0
|
|
|
|
|
|
|
|
if self.frame.w ~= frame_w or
|
|
|
|
self.subviews.panel.frame.l ~= panel_l or
|
|
|
|
self.subviews.panel.frame.t ~= panel_t
|
|
|
|
then
|
|
|
|
self.frame.w = frame_w
|
|
|
|
self.subviews.panel.frame.l = panel_l
|
|
|
|
self.subviews.panel.frame.t = panel_t
|
|
|
|
self:updateLayout()
|
|
|
|
end
|
|
|
|
|
|
|
|
InterrogationOverlay.super.render(self, dc)
|
|
|
|
end
|
|
|
|
|
2023-10-07 13:11:24 -06:00
|
|
|
return _ENV
|