From 62cde96724622a08f9dd6d4bf83f1763a02fd9df Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 29 Sep 2012 17:21:49 +0400 Subject: [PATCH] Add a script to reclassify 'individual choice' weapons based on unit skill. --- NEWS | 1 + README.rst | 40 ++++++--- dfhack.init-example | 3 + library/lua/utils.lua | 21 +++++ library/modules/Gui.cpp | 31 +++++++ scripts/gui/choose-weapons.lua | 154 +++++++++++++++++++++++++++++++++ 6 files changed, 237 insertions(+), 13 deletions(-) create mode 100644 scripts/gui/choose-weapons.lua diff --git a/NEWS b/NEWS index 20c51f415..fe7b5b667 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 a36cba607..7ca375039 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 2e656a609..8505f5acc 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 9fa473ed8..b46363cd7 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 21156ac0a..2afb36205 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 000000000..85ad62b6e --- /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