Merge pull request #3678 from myk002/myk_sort

[sort] search and sort for squad assignment screen
develop
Myk 2023-08-16 06:26:11 -07:00 committed by GitHub
commit 65b9ee8ded
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 554 additions and 131 deletions

@ -36,8 +36,6 @@ Template for new versions:
## API ## API
## Internals
## Lua ## Lua
## Removed ## Removed
@ -56,10 +54,11 @@ Template for new versions:
## New Tools ## New Tools
## New Features ## New Features
- `caravan`: advanced unit assignment screens for cages, restraints, and pits/ponds - `sort`: search and sort for squad assignment screen
- `zone`: advanced unit assignment screens for cages, restraints, and pits/ponds
## Fixes ## Fixes
- Core: properly reload scripts in mods when a world is unloaded and immediately loaded again - Core: reload scripts in mods when a world is unloaded and immediately loaded again
- Core: fix text getting added to DFHack text entry widgets when Alt- or Ctrl- keys are hit - Core: fix text getting added to DFHack text entry widgets when Alt- or Ctrl- keys are hit
- `orders`: prevent import/export overlay from appearing on the create workorder screen - `orders`: prevent import/export overlay from appearing on the create workorder screen
@ -73,14 +72,12 @@ Template for new versions:
- ``Items::getValue()``: remove ``caravan_buying`` parameter since the identity of the selling party doesn't actually affect the item value - ``Items::getValue()``: remove ``caravan_buying`` parameter since the identity of the selling party doesn't actually affect the item value
- `RemoteFortressReader`: add a ``force_reload`` option to the GetBlockList RPC API to return blocks regardless of whether they have changed since the last request - `RemoteFortressReader`: add a ``force_reload`` option to the GetBlockList RPC API to return blocks regardless of whether they have changed since the last request
## Internals
## Lua ## Lua
- ``new()``: improved error handling so that certain errors that were previously uncatchable (creating objects with members with unknown vtables) are now catchable with ``pcall()`` - ``new()``: improved error handling so that certain errors that were previously uncatchable (creating objects with members with unknown vtables) are now catchable with ``pcall()``
- ``dfhack.items.getValue()``: remove ``caravan_buying`` param as per C++ API change - ``dfhack.items.getValue()``: remove ``caravan_buying`` param as per C++ API change
- ``widgets.BannerPanel``: panel with distinctive border for marking DFHack UI elements on otherwise vanilla screens - ``widgets.BannerPanel``: panel with distinctive border for marking DFHack UI elements on otherwise vanilla screens
- ``widgets.Panel``: new functions to override instead of setting corresponding properties (useful when subclassing instead of just setting attributes): ``onDragBegin``, ``onDragEnd``, ``onResizeBegin``, ``onResizeEnd`` - ``widgets.Panel``: new functions to override instead of setting corresponding properties (useful when subclassing instead of just setting attributes): ``onDragBegin``, ``onDragEnd``, ``onResizeBegin``, ``onResizeEnd``
- ``dfhack.screen.readTile()``: now populates extended tile property fields (like top_of_text) in the returned Pen object - ``dfhack.screen.readTile()``: now populates extended tile property fields (like ``top_of_text``) in the returned ``Pen`` object
## Removed ## Removed

@ -2,59 +2,37 @@ sort
==== ====
.. dfhack-tool:: .. dfhack-tool::
:summary: Sort lists shown in the DF interface. :summary: Search and sort lists shown in the DF interface.
:tags: unavailable fort productivity interface :tags: fort productivity interface
:no-command: :no-command:
.. dfhack-command:: sort-items The ``sort`` tool provides overlays that sorting and searching options for
:summary: Sort the visible item list. lists displayed in the DF interface.
.. dfhack-command:: sort-units Searching and sorting functionality is provided by `overlay` widgets, and widgets for individual lists can be moved via `gui/overlay` or turned on or off via `gui/control-panel`.
:summary: Sort the visible unit list.
Usage Squad assignment overlay
----- ------------------------
:: The squad assignment screen can be sorted by name, by migrant wave, or by various military-related skills.
sort-items <property> [<property> ...] If sorted by "any melee", then the citizen is sorted according to the highest
sort-units <property> [<property> ...] skill they have in axes, short swords, maces, warhammers, spears, or general
fighting.
Both commands sort the visible list using the given sequence of comparisons. If sorted by "any ranged", then the citizen is sorted according to the highest
Each property can be prefixed with a ``<`` or ``>`` character to indicate skill they have in crossbows or general ranged combat.
whether elements that don't have the given property defined go first or last
(respectively) in the sorted list.
Examples If sorted by "leadership", then the citizen is sorted according to the highest
-------- skill they have in leader, teacher, or military tactics.
``sort-items material type quality`` You can search for a dwarf by name by typing in the Search field. You can also
Sort a list of items by material, then by type, then by quality type in the name of any job skill (military-related or not) and dwarves with
``sort-units profession name`` any experience in that skill will be shown. For example, to only see citizens
Sort a list of units by profession, then by name with military tactics skill, type in "tactics".
Properties You can see all the job skill names that you can search for by running::
----------
Items can be sorted by the following properties: :lua @df.job_skill
- ``type`` in `gui/launcher`.
- ``description``
- ``base_quality``
- ``quality``
- ``improvement``
- ``wear``
- ``material``
Units can be sorted by the following properties:
- ``name``
- ``age``
- ``arrival``
- ``noble``
- ``profession``
- ``profession_class``
- ``race``
- ``squad``
- ``squad_position``
- ``happiness``

@ -156,7 +156,7 @@ if(BUILD_SUPPORTED)
dfhack_plugin(seedwatch seedwatch.cpp LINK_LIBRARIES lua) dfhack_plugin(seedwatch seedwatch.cpp LINK_LIBRARIES lua)
dfhack_plugin(showmood showmood.cpp) dfhack_plugin(showmood showmood.cpp)
#dfhack_plugin(siege-engine siege-engine.cpp LINK_LIBRARIES lua) #dfhack_plugin(siege-engine siege-engine.cpp LINK_LIBRARIES lua)
#dfhack_plugin(sort sort.cpp LINK_LIBRARIES lua) dfhack_plugin(sort sort.cpp LINK_LIBRARIES lua)
#dfhack_plugin(steam-engine steam-engine.cpp) #dfhack_plugin(steam-engine steam-engine.cpp)
#add_subdirectory(spectate) #add_subdirectory(spectate)
#dfhack_plugin(stockflow stockflow.cpp LINK_LIBRARIES lua) #dfhack_plugin(stockflow stockflow.cpp LINK_LIBRARIES lua)

@ -1,5 +1,506 @@
local _ENV = mkmodule('plugins.sort') local _ENV = mkmodule('plugins.sort')
local gui = require('gui')
local overlay = require('plugins.overlay')
local utils = require('utils')
local widgets = require('gui.widgets')
local CH_UP = string.char(30)
local CH_DN = string.char(31)
local MELEE_WEAPON_SKILLS = {
df.job_skill.AXE,
df.job_skill.SWORD,
df.job_skill.MACE,
df.job_skill.HAMMER,
df.job_skill.SPEAR,
df.job_skill.MELEE_COMBAT, --Fighter
}
local RANGED_WEAPON_SKILLS = {
df.job_skill.CROSSBOW,
df.job_skill.RANGED_COMBAT,
}
local LEADERSHIP_SKILLS = {
df.job_skill.MILITARY_TACTICS,
df.job_skill.LEADERSHIP,
df.job_skill.TEACHING,
}
local function sort_noop(a, b)
-- this function is used as a marker and never actually gets called
error('sort_noop should not be called')
end
local function sort_by_name_desc(unit_id_1, unit_id_2)
if unit_id_1 == unit_id_2 then return 0 end
local unit1 = df.unit.find(unit_id_1)
local unit2 = df.unit.find(unit_id_2)
if not unit1 then return -1 end
if not unit2 then return 1 end
local name1 = dfhack.TranslateName(dfhack.units.getVisibleName(unit1))
local name2 = dfhack.TranslateName(dfhack.units.getVisibleName(unit2))
return utils.compare_name(name1, name2)
end
local function sort_by_name_asc(unit_id_1, unit_id_2)
if unit_id_1 == unit_id_2 then return 0 end
local unit1 = df.unit.find(unit_id_1)
local unit2 = df.unit.find(unit_id_2)
if not unit1 then return -1 end
if not unit2 then return 1 end
local name1 = dfhack.TranslateName(dfhack.units.getVisibleName(unit1))
local name2 = dfhack.TranslateName(dfhack.units.getVisibleName(unit2))
return utils.compare_name(name2, name1)
end
local function sort_by_migrant_wave_desc(unit_id_1, unit_id_2)
if unit_id_1 == unit_id_2 then return 0 end
local unit1 = df.unit.find(unit_id_1)
local unit2 = df.unit.find(unit_id_2)
if not unit1 then return -1 end
if not unit2 then return 1 end
return utils.compare(unit2.id, unit1.id)
end
local function sort_by_migrant_wave_asc(unit_id_1, unit_id_2)
if unit_id_1 == unit_id_2 then return 0 end
local unit1 = df.unit.find(unit_id_1)
local unit2 = df.unit.find(unit_id_2)
if not unit1 then return -1 end
if not unit2 then return 1 end
return utils.compare(unit1.id, unit2.id)
end
local function get_skill(unit_id, skill, unit)
unit = unit or df.unit.find(unit_id)
return unit and
unit.status.current_soul and
utils.binsearch(unit.status.current_soul.skills, skill, 'id')
end
local function get_max_skill(unit_id, list)
local unit = df.unit.find(unit_id)
if not unit or not unit.status.current_soul then return end
local max
for _,skill in ipairs(list) do
local s = get_skill(unit_id, skill, unit)
if s then
if not max then
max = s
else
if max.rating == s.rating and max.experience < s.experience then
max = s
elseif max.rating < s.rating then
max = s
end
end
end
end
return max
end
local function make_sort_by_skill_list_desc(list)
return function(unit_id_1, unit_id_2)
if unit_id_1 == unit_id_2 then return 0 end
if unit_id_1 == -1 then return -1 end
if unit_id_2 == -1 then return 1 end
local s1 = get_max_skill(unit_id_1, list)
local s2 = get_max_skill(unit_id_2, list)
if s1 == s2 then return sort_by_name_desc(unit_id_1, unit_id_2) end
if not s2 then return -1 end
if not s1 then return 1 end
if s1.rating ~= s2.rating then
return utils.compare(s2.rating, s1.rating)
end
if s1.experience ~= s2.experience then
return utils.compare(s2.experience, s1.experience)
end
return sort_by_name_desc(unit_id_1, unit_id_2)
end
end
local function make_sort_by_skill_list_asc(list)
return function(unit_id_1, unit_id_2)
if unit_id_1 == unit_id_2 then return 0 end
if unit_id_1 == -1 then return -1 end
if unit_id_2 == -1 then return 1 end
local s1 = get_max_skill(unit_id_1, list)
local s2 = get_max_skill(unit_id_2, list)
if s1 == s2 then return sort_by_name_desc(unit_id_1, unit_id_2) end
if not s2 then return 1 end
if not s1 then return -1 end
if s1.rating ~= s2.rating then
return utils.compare(s1.rating, s2.rating)
end
if s1.experience ~= s2.experience then
return utils.compare(s1.experience, s2.experience)
end
return sort_by_name_desc(unit_id_1, unit_id_2)
end
end
local function make_sort_by_skill_desc(sort_skill)
return function(unit_id_1, unit_id_2)
if unit_id_1 == unit_id_2 then return 0 end
if unit_id_1 == -1 then return -1 end
if unit_id_2 == -1 then return 1 end
local s1 = get_skill(unit_id_1, sort_skill)
local s2 = get_skill(unit_id_2, sort_skill)
if s1 == s2 then return sort_by_name_desc(unit_id_1, unit_id_2) end
if not s2 then return -1 end
if not s1 then return 1 end
if s1.rating ~= s2.rating then
return utils.compare(s2.rating, s1.rating)
end
if s1.experience ~= s2.experience then
return utils.compare(s2.experience, s1.experience)
end
return sort_by_name_desc(unit_id_1, unit_id_2)
end
end
local function make_sort_by_skill_asc(sort_skill)
return function(unit_id_1, unit_id_2)
if unit_id_1 == unit_id_2 then return 0 end
if unit_id_1 == -1 then return -1 end
if unit_id_2 == -1 then return 1 end
local s1 = get_skill(unit_id_1, sort_skill)
local s2 = get_skill(unit_id_2, sort_skill)
if s1 == s2 then return sort_by_name_desc(unit_id_1, unit_id_2) end
if not s2 then return 1 end
if not s1 then return -1 end
if s1.rating ~= s2.rating then
return utils.compare(s1.rating, s2.rating)
end
if s1.experience ~= s2.experience then
return utils.compare(s1.experience, s2.experience)
end
return sort_by_name_desc(unit_id_1, unit_id_2)
end
end
local SORT_FNS = {
sort_by_any_melee_desc=make_sort_by_skill_list_desc(MELEE_WEAPON_SKILLS),
sort_by_any_melee_asc=make_sort_by_skill_list_asc(MELEE_WEAPON_SKILLS),
sort_by_any_ranged_desc=make_sort_by_skill_list_desc(RANGED_WEAPON_SKILLS),
sort_by_any_ranged_asc=make_sort_by_skill_list_asc(RANGED_WEAPON_SKILLS),
sort_by_leadership_desc=make_sort_by_skill_list_desc(LEADERSHIP_SKILLS),
sort_by_leadership_asc=make_sort_by_skill_list_asc(LEADERSHIP_SKILLS),
sort_by_axe_desc=make_sort_by_skill_desc(df.job_skill.AXE),
sort_by_axe_asc=make_sort_by_skill_asc(df.job_skill.AXE),
sort_by_sword_desc=make_sort_by_skill_desc(df.job_skill.SWORD),
sort_by_sword_asc=make_sort_by_skill_asc(df.job_skill.SWORD),
sort_by_mace_desc=make_sort_by_skill_desc(df.job_skill.MACE),
sort_by_mace_asc=make_sort_by_skill_asc(df.job_skill.MACE),
sort_by_hammer_desc=make_sort_by_skill_desc(df.job_skill.HAMMER),
sort_by_hammer_asc=make_sort_by_skill_asc(df.job_skill.HAMMER),
sort_by_spear_desc=make_sort_by_skill_desc(df.job_skill.SPEAR),
sort_by_spear_asc=make_sort_by_skill_asc(df.job_skill.SPEAR),
sort_by_crossbow_desc=make_sort_by_skill_desc(df.job_skill.CROSSBOW),
sort_by_crossbow_asc=make_sort_by_skill_asc(df.job_skill.CROSSBOW),
}
-- ----------------------
-- SquadAssignmentOverlay
--
SquadAssignmentOverlay = defclass(SquadAssignmentOverlay, overlay.OverlayWidget)
SquadAssignmentOverlay.ATTRS{
default_pos={x=-33, y=40},
default_enabled=true,
viewscreens='dwarfmode/UnitSelector/SQUAD_FILL_POSITION',
frame={w=63, h=7},
frame_style=gui.FRAME_PANEL,
frame_background=gui.CLEAR_PEN,
}
function SquadAssignmentOverlay:init()
self.dirty = true
self:addviews{
widgets.CycleHotkeyLabel{
view_id='sort',
frame={l=0, t=0, w=29},
label='Sort by:',
key='CUSTOM_SHIFT_S',
options={
{label='any melee skill'..CH_DN, value=SORT_FNS.sort_by_any_melee_desc, pen=COLOR_GREEN},
{label='any melee skill'..CH_UP, value=SORT_FNS.sort_by_any_melee_asc, pen=COLOR_YELLOW},
{label='any ranged skill'..CH_DN, value=SORT_FNS.sort_by_any_ranged_desc, pen=COLOR_GREEN},
{label='any ranged skill'..CH_UP, value=SORT_FNS.sort_by_any_ranged_asc, pen=COLOR_YELLOW},
{label='any leader skill'..CH_DN, value=SORT_FNS.sort_by_leadership_desc, pen=COLOR_GREEN},
{label='any leader skill'..CH_UP, value=SORT_FNS.sort_by_leadership_asc, pen=COLOR_YELLOW},
{label='name'..CH_DN, value=sort_by_name_desc, pen=COLOR_GREEN},
{label='name'..CH_UP, value=sort_by_name_asc, pen=COLOR_YELLOW},
{label='migrant wave'..CH_DN, value=sort_by_migrant_wave_desc, pen=COLOR_GREEN},
{label='migrant wave'..CH_UP, value=sort_by_migrant_wave_asc, pen=COLOR_YELLOW},
{label='axe skill'..CH_DN, value=SORT_FNS.sort_by_axe_desc, pen=COLOR_GREEN},
{label='axe skill'..CH_UP, value=SORT_FNS.sort_by_axe_asc, pen=COLOR_YELLOW},
{label='sword skill'..CH_DN, value=SORT_FNS.sort_by_sword_desc, pen=COLOR_GREEN},
{label='sword skill'..CH_UP, value=SORT_FNS.sort_by_sword_asc, pen=COLOR_YELLOW},
{label='mace skill'..CH_DN, value=SORT_FNS.sort_by_mace_desc, pen=COLOR_GREEN},
{label='mace skill'..CH_UP, value=SORT_FNS.sort_by_mace_asc, pen=COLOR_YELLOW},
{label='hammer skill'..CH_DN, value=SORT_FNS.sort_by_hammer_desc, pen=COLOR_GREEN},
{label='hammer skill'..CH_UP, value=SORT_FNS.sort_by_hammer_asc, pen=COLOR_YELLOW},
{label='spear skill'..CH_DN, value=SORT_FNS.sort_by_spear_desc, pen=COLOR_GREEN},
{label='spear skill'..CH_UP, value=SORT_FNS.sort_by_spear_asc, pen=COLOR_YELLOW},
{label='crossbow skill'..CH_DN, value=SORT_FNS.sort_by_crossbow_desc, pen=COLOR_GREEN},
{label='crossbow skill'..CH_UP, value=SORT_FNS.sort_by_crossbow_asc, pen=COLOR_YELLOW},
},
initial_option=SORT_FNS.sort_by_any_melee_desc,
on_change=self:callback('refresh_list', 'sort'),
},
widgets.EditField{
view_id='search',
frame={l=32, t=0},
label_text='Search: ',
on_char=function(ch) return ch:match('[%l _-]') end,
on_change=function() self:refresh_list() end,
},
widgets.Panel{
frame={t=2, l=0, r=0, b=0},
subviews={
widgets.CycleHotkeyLabel{
view_id='sort_any_melee',
frame={t=0, l=0, w=10},
options={
{label='any melee', value=sort_noop},
{label='any melee'..CH_DN, value=SORT_FNS.sort_by_any_melee_desc, pen=COLOR_GREEN},
{label='any melee'..CH_UP, value=SORT_FNS.sort_by_any_melee_asc, pen=COLOR_YELLOW},
},
initial_option=SORT_FNS.sort_by_any_melee_desc,
option_gap=0,
on_change=self:callback('refresh_list', 'sort_any_melee'),
},
widgets.CycleHotkeyLabel{
view_id='sort_any_ranged',
frame={t=0, l=13, w=11},
options={
{label='any ranged', value=sort_noop},
{label='any ranged'..CH_DN, value=SORT_FNS.sort_by_any_ranged_desc, pen=COLOR_GREEN},
{label='any ranged'..CH_UP, value=SORT_FNS.sort_by_any_ranged_asc, pen=COLOR_YELLOW},
},
option_gap=0,
on_change=self:callback('refresh_list', 'sort_any_ranged'),
},
widgets.CycleHotkeyLabel{
view_id='sort_leadership',
frame={t=0, l=27, w=11},
options={
{label='leadership', value=sort_noop},
{label='leadership'..CH_DN, value=SORT_FNS.sort_by_leadership_desc, pen=COLOR_GREEN},
{label='leadership'..CH_UP, value=SORT_FNS.sort_by_leadership_asc, pen=COLOR_YELLOW},
},
option_gap=0,
on_change=self:callback('refresh_list', 'sort_leadership'),
},
widgets.CycleHotkeyLabel{
view_id='sort_name',
frame={t=0, l=41, w=5},
options={
{label='name', value=sort_noop},
{label='name'..CH_DN, value=sort_by_name_desc, pen=COLOR_GREEN},
{label='name'..CH_UP, value=sort_by_name_asc, pen=COLOR_YELLOW},
},
option_gap=0,
on_change=self:callback('refresh_list', 'sort_name'),
},
widgets.CycleHotkeyLabel{
view_id='sort_migrant_wave',
frame={t=0, l=48, w=13},
options={
{label='migrant wave', value=sort_noop},
{label='migrant wave'..CH_DN, value=sort_by_migrant_wave_desc, pen=COLOR_GREEN},
{label='migrant wave'..CH_UP, value=sort_by_migrant_wave_asc, pen=COLOR_YELLOW},
},
option_gap=0,
on_change=self:callback('refresh_list', 'sort_migrant_wave'),
},
widgets.CycleHotkeyLabel{
view_id='sort_axe',
frame={t=2, l=2, w=4},
options={
{label='axe', value=sort_noop},
{label='axe'..CH_DN, value=SORT_FNS.sort_by_axe_desc, pen=COLOR_GREEN},
{label='axe'..CH_UP, value=SORT_FNS.sort_by_axe_asc, pen=COLOR_YELLOW},
},
option_gap=0,
on_change=self:callback('refresh_list', 'sort_axe'),
},
widgets.CycleHotkeyLabel{
view_id='sort_sword',
frame={t=2, l=9, w=6},
options={
{label='sword', value=sort_noop},
{label='sword'..CH_DN, value=SORT_FNS.sort_by_sword_desc, pen=COLOR_GREEN},
{label='sword'..CH_UP, value=SORT_FNS.sort_by_sword_asc, pen=COLOR_YELLOW},
},
option_gap=0,
on_change=self:callback('refresh_list', 'sort_sword'),
},
widgets.CycleHotkeyLabel{
view_id='sort_mace',
frame={t=2, l=18, w=5},
options={
{label='mace', value=sort_noop},
{label='mace'..CH_DN, value=SORT_FNS.sort_by_mace_desc, pen=COLOR_GREEN},
{label='mace'..CH_UP, value=SORT_FNS.sort_by_mace_asc, pen=COLOR_YELLOW},
},
option_gap=0,
on_change=self:callback('refresh_list', 'sort_mace'),
},
widgets.CycleHotkeyLabel{
view_id='sort_hammer',
frame={t=2, l=25, w=7},
options={
{label='hammer', value=sort_noop},
{label='hammer'..CH_DN, value=SORT_FNS.sort_by_hammer_desc, pen=COLOR_GREEN},
{label='hammer'..CH_UP, value=SORT_FNS.sort_by_hammer_asc, pen=COLOR_YELLOW},
},
option_gap=0,
on_change=self:callback('refresh_list', 'sort_hammer'),
},
widgets.CycleHotkeyLabel{
view_id='sort_spear',
frame={t=2, l=36, w=6},
options={
{label='spear', value=sort_noop},
{label='spear'..CH_DN, value=SORT_FNS.sort_by_spear_desc, pen=COLOR_GREEN},
{label='spear'..CH_UP, value=SORT_FNS.sort_by_spear_asc, pen=COLOR_YELLOW},
},
option_gap=0,
on_change=self:callback('refresh_list', 'sort_spear'),
},
widgets.CycleHotkeyLabel{
view_id='sort_crossbow',
frame={t=2, l=45, w=9},
options={
{label='crossbow', value=sort_noop},
{label='crossbow'..CH_DN, value=SORT_FNS.sort_by_crossbow_desc, pen=COLOR_GREEN},
{label='crossbow'..CH_UP, value=SORT_FNS.sort_by_crossbow_asc, pen=COLOR_YELLOW},
},
option_gap=0,
on_change=self:callback('refresh_list', 'sort_crossbow'),
},
}
},
}
end
local function normalize_search_key(search_key)
local out = ''
for c in dfhack.toSearchNormalized(search_key):gmatch("[%w%s]") do
out = out .. c:lower()
end
return out
end
local function filter_matches(unit_id, search)
if unit_id == -1 then return true end
local unit = df.unit.find(unit_id)
if not unit then return true end
local search_key = dfhack.TranslateName(dfhack.units.getVisibleName(unit))
if unit.status.current_soul then
for _,skill in ipairs(unit.status.current_soul.skills) do
search_key = (search_key or '') .. ' ' .. (df.job_skill[skill.id] or '')
end
end
return normalize_search_key(search_key):find(dfhack.toSearchNormalized(search))
end
local unit_selector = df.global.game.main_interface.unit_selector
-- this function uses the unused itemid and selected vectors to keep state,
-- taking advantage of the fact that they are reset by DF when the list of units changes
local function filter_vector(search, prev_search)
local unid_is_filtered = #unit_selector.selected >= 0 and unit_selector.selected[0] ~= 0
if #search == 0 or #unit_selector.selected == 0 then
if not unid_is_filtered then
-- we haven't modified the unid vector; nothing to do here
return
end
-- restore the unid vector
unit_selector.unid:assign(unit_selector.itemid)
-- clear our "we meddled" flag
unit_selector.selected[0] = 0
return
end
if unid_is_filtered and search == prev_search then
-- prev filter still stands
return
end
if unid_is_filtered then
-- restore the unid vector
unit_selector.unid:assign(unit_selector.itemid)
else
-- save the unid vector and set our meddle flag
unit_selector.itemid:assign(unit_selector.unid)
unit_selector.selected[0] = 1
end
-- do the actual filtering
for idx=#unit_selector.unid-1,0,-1 do
if not filter_matches(unit_selector.unid[idx], search) then
unit_selector.unid:erase(idx)
end
end
end
local SORT_WIDGET_NAMES = {
'sort',
'sort_any_melee',
'sort_any_ranged',
'sort_leadership',
'sort_name',
'sort_migrant_wave',
'sort_axe',
'sort_sword',
'sort_mace',
'sort_hammer',
'sort_spear',
'sort_crossbow',
}
function SquadAssignmentOverlay:refresh_list(sort_widget, sort_fn)
sort_widget = sort_widget or 'sort'
sort_fn = sort_fn or self.subviews.sort:getOptionValue()
if sort_fn == sort_noop then
self.subviews[sort_widget]:cycle()
return
end
for _,widget_name in ipairs(SORT_WIDGET_NAMES) do
self.subviews[widget_name]:setOption(sort_fn)
end
local search = self.subviews.search.text
filter_vector(search, self.prev_search)
self.prev_search = search
utils.sort_vector(unit_selector.unid, nil, sort_fn)
end
function SquadAssignmentOverlay:onInput(keys)
if keys._MOUSE_R_DOWN or
keys._MOUSE_L_DOWN and not self:getMouseFramePos()
then
-- if any click is made outside of our window, we may need to refresh our list
self.dirty = true
end
return SquadAssignmentOverlay.super.onInput(self, keys)
end
function SquadAssignmentOverlay:onRenderFrame(dc, frame_rect)
SquadAssignmentOverlay.super.onRenderFrame(self, dc, frame_rect)
if self.dirty then
self:refresh_list()
self.dirty = false
end
end
OVERLAY_WIDGETS = {
squad_assignment=SquadAssignmentOverlay,
}
--[[
local utils = require('utils') local utils = require('utils')
local units = require('plugins.sort.units') local units = require('plugins.sort.units')
local items = require('plugins.sort.items') local items = require('plugins.sort.items')
@ -51,5 +552,6 @@ function parse_ordering_spec(type,...)
end end
make_sort_order = utils.make_sort_order make_sort_order = utils.make_sort_order
]]
return _ENV return _ENV

@ -1,3 +1,4 @@
#include "df/enabler.h"
#include "df/viewscreen_adopt_regionst.h" #include "df/viewscreen_adopt_regionst.h"
#include "df/viewscreen_choose_game_typest.h" #include "df/viewscreen_choose_game_typest.h"
#include "df/viewscreen_choose_start_sitest.h" #include "df/viewscreen_choose_start_sitest.h"
@ -29,6 +30,7 @@ DFHACK_PLUGIN("overlay");
DFHACK_PLUGIN_IS_ENABLED(is_enabled); DFHACK_PLUGIN_IS_ENABLED(is_enabled);
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(enabler);
namespace DFHack { namespace DFHack {
DBG_DECLARE(overlay, control, DebugCategory::LINFO); DBG_DECLARE(overlay, control, DebugCategory::LINFO);
@ -82,6 +84,8 @@ struct viewscreen_overlay : T {
}); });
if (!input_is_handled) if (!input_is_handled)
INTERPOSE_NEXT(feed)(input); INTERPOSE_NEXT(feed)(input);
else
enabler->last_text_input[0] = '\0';
} }
DEFINE_VMETHOD_INTERPOSE(void, render, ()) { DEFINE_VMETHOD_INTERPOSE(void, render, ()) {
INTERPOSE_NEXT(render)(); INTERPOSE_NEXT(render)();

@ -1,43 +1,13 @@
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h" #include "PluginManager.h"
#include "modules/Gui.h"
#include "modules/Translation.h"
#include "modules/Units.h"
#include "modules/Job.h"
#include "LuaTools.h"
#include "DataDefs.h"
#include "df/plotinfost.h"
#include "df/world.h"
#include "df/viewscreen_joblistst.h"
#include "df/viewscreen_unitlistst.h"
#include "df/viewscreen_layer_militaryst.h"
#include "df/viewscreen_layer_noblelistst.h"
#include "df/viewscreen_layer_overall_healthst.h"
#include "df/viewscreen_layer_assigntradest.h"
#include "df/viewscreen_tradegoodsst.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/viewscreen_petst.h"
#include "df/viewscreen_storesst.h"
#include "df/viewscreen_workshop_profilest.h"
#include "df/layer_object_listst.h"
#include "df/assign_trade_status.h"
#include "MiscUtils.h"
#include <stdlib.h>
using std::vector; using std::vector;
using std::string; using std::string;
using std::endl;
using namespace DFHack; using namespace DFHack;
using namespace df::enums;
DFHACK_PLUGIN("sort"); DFHACK_PLUGIN("sort");
/*
REQUIRE_GLOBAL(plotinfo); REQUIRE_GLOBAL(plotinfo);
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(ui_building_in_assign); REQUIRE_GLOBAL(ui_building_in_assign);
@ -52,16 +22,18 @@ static bool item_list_hotkey(df::viewscreen *top);
static command_result sort_units(color_ostream &out, vector <string> & parameters); static command_result sort_units(color_ostream &out, vector <string> & parameters);
static command_result sort_items(color_ostream &out, vector <string> & parameters); static command_result sort_items(color_ostream &out, vector <string> & parameters);
*/
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands) DFhackCExport command_result plugin_init (color_ostream &out, vector <PluginCommand> &commands)
{ {
commands.push_back(PluginCommand( // commands.push_back(PluginCommand(
"sort-units", "Sort the visible unit list.", sort_units, unit_list_hotkey)); // "sort-units", "Sort the visible unit list.", sort_units, unit_list_hotkey));
commands.push_back(PluginCommand( // commands.push_back(PluginCommand(
"sort-items", "Sort the visible item list.", sort_items, item_list_hotkey)); // "sort-items", "Sort the visible item list.", sort_items, item_list_hotkey));
return CR_OK; return CR_OK;
} }
/*
DFhackCExport command_result plugin_shutdown ( color_ostream &out ) DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{ {
return CR_OK; return CR_OK;
@ -232,10 +204,7 @@ typedef void (*SortHandler)(color_ostream *pout, lua_State *L, int top,
static std::map<std::string, SortHandler> unit_sorters; static std::map<std::string, SortHandler> unit_sorters;
/* // Sort units in the 'u'nit list screen.
* Sort units in the 'u'nit list screen.
*/
DEFINE_SORT_HANDLER(unit_sorters, unitlist, "", units) DEFINE_SORT_HANDLER(unit_sorters, unitlist, "", units)
{ {
PARSE_SPEC("units", parameters); PARSE_SPEC("units", parameters);
@ -250,10 +219,7 @@ DEFINE_SORT_HANDLER(unit_sorters, unitlist, "", units)
} }
} }
/* //Sort units in the 'j'ob list screen.
* Sort units in the 'j'ob list screen.
*/
DEFINE_SORT_HANDLER(unit_sorters, joblist, "", jobs) DEFINE_SORT_HANDLER(unit_sorters, joblist, "", jobs)
{ {
PARSE_SPEC("units", parameters); PARSE_SPEC("units", parameters);
@ -275,10 +241,7 @@ DEFINE_SORT_HANDLER(unit_sorters, joblist, "", jobs)
} }
} }
/* // Sort candidate units in the 'p'osition page of the 'm'ilitary screen.
* Sort candidate units in the 'p'osition page of the 'm'ilitary screen.
*/
DEFINE_SORT_HANDLER(unit_sorters, layer_military, "/Positions/Candidates", military) DEFINE_SORT_HANDLER(unit_sorters, layer_military, "/Positions/Candidates", military)
{ {
auto &candidates = military->positions.candidates; auto &candidates = military->positions.candidates;
@ -293,7 +256,6 @@ DEFINE_SORT_HANDLER(unit_sorters, layer_military, "/Positions/Candidates", milit
} }
} }
DEFINE_SORT_HANDLER(unit_sorters, layer_noblelist, "/Appoint", nobles) DEFINE_SORT_HANDLER(unit_sorters, layer_noblelist, "/Appoint", nobles)
{ {
auto list2 = getLayerList(nobles, 1); auto list2 = getLayerList(nobles, 1);
@ -312,10 +274,7 @@ DEFINE_SORT_HANDLER(unit_sorters, layer_noblelist, "/Appoint", nobles)
} }
} }
/* //Sort animal units in the Animal page of the 'z' status screen.
* Sort animal units in the Animal page of the 'z' status screen.
*/
DEFINE_SORT_HANDLER(unit_sorters, pet, "/List", animals) DEFINE_SORT_HANDLER(unit_sorters, pet, "/List", animals)
{ {
PARSE_SPEC("units", parameters); PARSE_SPEC("units", parameters);
@ -334,10 +293,7 @@ DEFINE_SORT_HANDLER(unit_sorters, pet, "/List", animals)
} }
} }
/* // Sort candidate trainers in the Animal page of the 'z' status screen.
* Sort candidate trainers in the Animal page of the 'z' status screen.
*/
DEFINE_SORT_HANDLER(unit_sorters, pet, "/SelectTrainer", animals) DEFINE_SORT_HANDLER(unit_sorters, pet, "/SelectTrainer", animals)
{ {
sort_null_first(parameters); sort_null_first(parameters);
@ -351,10 +307,7 @@ DEFINE_SORT_HANDLER(unit_sorters, pet, "/SelectTrainer", animals)
} }
} }
/* // Sort units in the Health page of the 'z' status screen.
* Sort units in the Health page of the 'z' status screen.
*/
DEFINE_SORT_HANDLER(unit_sorters, layer_overall_health, "/Units", health) DEFINE_SORT_HANDLER(unit_sorters, layer_overall_health, "/Units", health)
{ {
auto list1 = getLayerList(health, 0); auto list1 = getLayerList(health, 0);
@ -371,10 +324,7 @@ DEFINE_SORT_HANDLER(unit_sorters, layer_overall_health, "/Units", health)
} }
} }
/* // Sort burrow member candidate units in the 'w' sidebar mode.
* Sort burrow member candidate units in the 'w' sidebar mode.
*/
DEFINE_SORT_HANDLER(unit_sorters, dwarfmode, "/Burrows/AddUnits", screen) DEFINE_SORT_HANDLER(unit_sorters, dwarfmode, "/Burrows/AddUnits", screen)
{ {
PARSE_SPEC("units", parameters); PARSE_SPEC("units", parameters);
@ -387,10 +337,7 @@ DEFINE_SORT_HANDLER(unit_sorters, dwarfmode, "/Burrows/AddUnits", screen)
} }
} }
/* // Sort building owner candidate units in the 'q' sidebar mode, or cage assignment.
* Sort building owner candidate units in the 'q' sidebar mode, or cage assignment.
*/
DEFINE_SORT_HANDLER(unit_sorters, dwarfmode, "/QueryBuilding/Some/Assign", screen) DEFINE_SORT_HANDLER(unit_sorters, dwarfmode, "/QueryBuilding/Some/Assign", screen)
{ {
sort_null_first(parameters); sort_null_first(parameters);
@ -410,10 +357,7 @@ DEFINE_SORT_HANDLER(unit_sorters, dwarfmode, "/QueryBuilding/Some/Assign", scree
} }
} }
/* // Sort units in the workshop 'q'uery 'P'rofile modification screen.
* Sort units in the workshop 'q'uery 'P'rofile modification screen.
*/
DEFINE_SORT_HANDLER(unit_sorters, workshop_profile, "/Unit", profile) DEFINE_SORT_HANDLER(unit_sorters, workshop_profile, "/Unit", profile)
{ {
PARSE_SPEC("units", parameters); PARSE_SPEC("units", parameters);
@ -425,10 +369,7 @@ DEFINE_SORT_HANDLER(unit_sorters, workshop_profile, "/Unit", profile)
} }
} }
/* // Sort pen assignment candidate units in 'z'->'N'.
* Sort pen assignment candidate units in 'z'->'N'.
*/
DEFINE_SORT_HANDLER(unit_sorters, dwarfmode, "/ZonesPenInfo/Assign", screen) DEFINE_SORT_HANDLER(unit_sorters, dwarfmode, "/ZonesPenInfo/Assign", screen)
{ {
PARSE_SPEC("units", parameters); PARSE_SPEC("units", parameters);
@ -562,3 +503,4 @@ static command_result sort_items(color_ostream &out, vector <string> &parameters
return CR_OK; return CR_OK;
} }
*/