summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS1
-rw-r--r--README.rst40
-rw-r--r--dfhack.init-example3
-rw-r--r--library/lua/utils.lua21
-rw-r--r--library/modules/Gui.cpp31
-rw-r--r--scripts/gui/choose-weapons.lua154
6 files changed, 237 insertions, 13 deletions
diff --git a/NEWS b/NEWS
index 20c51f41..fe7b5b66 100644
--- a/NEWS
+++ b/NEWS
@@ -56,6 +56,7 @@ DFHack v0.34.11-r2 (UNRELEASED)
- gui/rename: renaming stockpiles, workshops and units via an in-game dialog.
- gui/power-meter: front-end for the Power Meter plugin.
- gui/siege-engine: front-end for the Siege Engine plugin.
+ - gui/choose-weapons: auto-choose matching weapons in the military equip screen.
Autolabor plugin:
- can set nonidle hauler percentage.
- broker excluded from all labors when needed at depot.
diff --git a/README.rst b/README.rst
index a36cba60..7ca37503 100644
--- a/README.rst
+++ b/README.rst
@@ -1800,19 +1800,19 @@ Pressing ESC normally returns to the unit screen, but Shift-ESC would exit
directly to the main dwarf mode screen.
-Liquids
-=======
+gui/liquids
+===========
-Implemented by the gui/liquids script. To use, bind to a key and activate in the 'k' mode.
+To use, bind to a key and activate in the 'k' mode.
While active, use the suggested keys to switch the usual liquids parameters, and Enter
to select the target area and apply changes.
-Mechanisms
-==========
+gui/mechanisms
+==============
-Implemented by the gui/mechanims script. To use, bind to a key and activate in the 'q' mode.
+To use, bind to a key and activate in the 'q' mode.
Lists mechanisms connected to the building, and their links. Navigating the list centers
the view on the relevant linked buildings.
@@ -1822,10 +1822,10 @@ focus on the current one. Shift-Enter has an effect equivalent to pressing Enter
re-entering the mechanisms ui.
-Rename
-======
+gui/rename
+==========
-Backed by the rename plugin, the gui/rename script allows entering the desired name
+Backed by the rename plugin, this script allows entering the desired name
via a simple dialog in the game ui.
* ``gui/rename [building]`` in 'q' mode changes the name of a building.
@@ -1840,16 +1840,30 @@ via a simple dialog in the game ui.
The ``building`` or ``unit`` options are automatically assumed when in relevant ui state.
-Room List
-=========
+gui/room-list
+=============
-Implemented by the gui/room-list script. To use, bind to a key and activate in the 'q' mode,
-either immediately or after opening the assign owner page.
+To use, bind to a key and activate in the 'q' mode, either immediately or after opening
+the assign owner page.
The script lists other rooms owned by the same owner, or by the unit selected in the assign
list, and allows unassigning them.
+gui/choose-weapons
+==================
+
+Bind to a key, and activate in the Equip->View/Customize page of the military screen.
+
+Depending on the cursor location, it rewrites all 'individual choice weapon' entries
+in the selected squad or position to use a specific weapon type matching the assigned
+unit's top skill. If the cursor is in the rightmost list over a weapon entry, it rewrites
+only that entry, and does it even if it is not 'individual choice'.
+
+Rationale: individual choice seems to be unreliable when there is a weapon shortage,
+and may lead to inappropriate weapons being selected.
+
+
=============
Behavior Mods
=============
diff --git a/dfhack.init-example b/dfhack.init-example
index 2e656a60..8505f5ac 100644
--- a/dfhack.init-example
+++ b/dfhack.init-example
@@ -69,6 +69,9 @@ keybinding add Ctrl-Shift-M@dwarfmode/Build/Position/Trap gui/power-meter
# siege engine control
keybinding add Alt-A@dwarfmode/QueryBuilding/Some/SiegeEngine gui/siege-engine
+# military weapon auto-select
+keybinding add Ctrl-W@layer_military/Equip/Customize/View gui/choose-weapons
+
############################
# UI and game logic tweaks #
############################
diff --git a/library/lua/utils.lua b/library/lua/utils.lua
index 9fa473ed..b46363cd 100644
--- a/library/lua/utils.lua
+++ b/library/lua/utils.lua
@@ -361,6 +361,27 @@ function insert_or_update(vector,item,field,cmp)
return added,cur,pos
end
+-- Binary search and erase
+function erase_sorted_key(vector,key,field,cmp)
+ local cur,found,pos = binsearch(vector,key,field,cmp)
+ if found then
+ if df.isvalid(vector) then
+ vector:erase(pos)
+ else
+ table.remove(vector, pos)
+ end
+ end
+ return found,cur,pos
+end
+
+function erase_sorted(vector,item,field,cmp)
+ local key = item
+ if field and item then
+ key = item[field]
+ end
+ return erase_sorted_key(vector,key,field,cmp)
+end
+
-- Calls a method with a string temporary
function call_with_string(obj,methodname,...)
return dfhack.with_temp_object(
diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp
index 21156ac0..2afb3620 100644
--- a/library/modules/Gui.cpp
+++ b/library/modules/Gui.cpp
@@ -347,6 +347,37 @@ DEFINE_GET_FOCUS_STRING_HANDLER(layer_military)
break;
}
+ case df::viewscreen_layer_militaryst::Equip:
+ {
+ focus += "/" + enum_item_key(screen->equip.mode);
+
+ switch (screen->equip.mode)
+ {
+ case df::viewscreen_layer_militaryst::T_equip::Customize:
+ {
+ if (screen->equip.edit_mode < 0)
+ focus += "/View";
+ else
+ focus += "/" + enum_item_key(screen->equip.edit_mode);
+ break;
+ }
+ case df::viewscreen_layer_militaryst::T_equip::Uniform:
+ break;
+ case df::viewscreen_layer_militaryst::T_equip::Priority:
+ {
+ if (screen->equip.prio_in_move >= 0)
+ focus += "/Move";
+ else
+ focus += "/View";
+ break;
+ }
+ }
+
+ static const char *lists[] = { "/Squads", "/Positions", "/Choices" };
+ focus += lists[cur_list];
+ break;
+ }
+
default:
break;
}
diff --git a/scripts/gui/choose-weapons.lua b/scripts/gui/choose-weapons.lua
new file mode 100644
index 00000000..85ad62b6
--- /dev/null
+++ b/scripts/gui/choose-weapons.lua
@@ -0,0 +1,154 @@
+-- Rewrite individual choice weapons into specific types.
+
+local utils = require 'utils'
+local dlg = require 'gui.dialogs'
+
+local defs = df.global.world.raws.itemdefs
+local entity = df.global.ui.main.fortress_entity
+local tasks = df.global.ui.tasks
+local equipment = df.global.ui.equipment
+
+function find_best_weapon(unit,mode)
+ local best = nil
+ local skill = nil
+ local skill_level = nil
+ local count = 0
+ local function try(id,iskill)
+ local slevel = dfhack.units.getNominalSkill(unit,iskill)
+ -- Choose most skill
+ if (skill ~= nil and slevel > skill_level)
+ or (skill == nil and slevel > 0) then
+ best,skill,skill_level,count = id,iskill,slevel,0
+ end
+ -- Then most produced within same skill
+ if skill == iskill then
+ local cnt = tasks.created_weapons[id]
+ if cnt > count then
+ best,count = id,cnt
+ end
+ end
+ end
+ for _,id in ipairs(entity.resources.weapon_type) do
+ local def = defs.weapons[id]
+ if def.skill_ranged >= 0 then
+ if mode == nil or mode == 'ranged' then
+ try(id, def.skill_ranged)
+ end
+ else
+ if mode == nil or mode == 'melee' then
+ try(id, def.skill_melee)
+ end
+ end
+ end
+ return best
+end
+
+function unassign_wrong_items(unit,position,spec,subtype)
+ for i=#spec.assigned-1,0,-1 do
+ local id = spec.assigned[i]
+ local item = df.item.find(id)
+
+ if item.subtype.subtype ~= subtype then
+ spec.assigned:erase(i)
+
+ -- TODO: somewhat unexplored area; maybe missing some steps
+ utils.erase_sorted(position.assigned_items,id)
+ if utils.erase_sorted(equipment.items_assigned.WEAPON,item,'id') then
+ utils.insert_sorted(equipment.items_unassigned.WEAPON,item,'id')
+ end
+ equipment.update.weapon = true
+ unit.military.pickup_flags.update = true
+ end
+ end
+end
+
+local count = 0
+
+function adjust_uniform_spec(unit,position,spec,force)
+ if not unit then return end
+ local best
+ if spec.indiv_choice.melee then
+ best = find_best_weapon(unit, 'melee')
+ elseif spec.indiv_choice.ranged then
+ best = find_best_weapon(unit, 'ranged')
+ elseif spec.indiv_choice.any or force then
+ best = find_best_weapon(unit, nil)
+ end
+ if best then
+ count = count + 1
+ spec.item_filter.item_subtype = best
+ spec.indiv_choice.any = false
+ spec.indiv_choice.melee = false
+ spec.indiv_choice.ranged = false
+ unassign_wrong_items(unit, position, spec, best)
+ end
+end
+
+function adjust_position(unit,position,force)
+ if not unit then
+ local fig = df.historical_figure.find(position.occupant)
+ if not fig then return end
+ unit = df.unit.find(fig.unit_id)
+ end
+
+ for _,v in ipairs(position.uniform.weapon) do
+ adjust_uniform_spec(unit, position, v, force)
+ end
+end
+
+function adjust_squad(squad, force)
+ for _,pos in ipairs(squad.positions) do
+ adjust_position(nil, pos, force)
+ end
+end
+
+local args = {...}
+local vs = dfhack.gui.getCurViewscreen()
+local vstype = df.viewscreen_layer_militaryst
+if not vstype:is_instance(vs) then
+ qerror('Call this from the military screen')
+end
+
+if vs.page == vstype.T_page.Equip
+and vs.equip.mode == vstype.T_equip.T_mode.Customize then
+ local slist = vs.layer_objects[0]
+ local squad = vs.equip.squads[slist:getListCursor()]
+
+ if slist.active then
+ print('Adjusting squad.')
+ adjust_squad(squad)
+ else
+ local plist = vs.layer_objects[1]
+ local pidx = plist:getListCursor()
+ local pos = squad.positions[pidx]
+ local unit = vs.equip.units[pidx]
+
+ if plist.active then
+ print('Adjusting position.')
+ adjust_position(unit, pos)
+ elseif unit and vs.equip.edit_mode < 0 then
+ local wlist = vs.layer_objects[2]
+ local idx = wlist:getListCursor()
+ local cat = vs.equip.assigned.category[idx]
+
+ if wlist.active and cat == df.uniform_category.weapon then
+ print('Adjusting spec.')
+ adjust_uniform_spec(unit, pos, vs.equip.assigned.spec[idx], true)
+ end
+ end
+ end
+else
+ qerror('Call this from the Equip page of military screen')
+end
+
+if count > 1 then
+ dlg.showMessage(
+ 'Choose Weapons',
+ 'Updated '..count..' uniform entries.', COLOR_GREEN
+ )
+elseif count == 0 then
+ dlg.showMessage(
+ 'Choose Weapons',
+ 'Did not find any entries to update.', COLOR_YELLOW
+ )
+end