From e4615deeb75c6f3aa7842adc9cc0a4e16dd9b789 Mon Sep 17 00:00:00 2001 From: SilasD Date: Sun, 14 Sep 2025 12:29:37 -0700 Subject: [PATCH 1/7] uniform-unstick Add quivers, backpacks, and flasks/waterskins https://discord.com/channels/793331351645323264/807444467140788254/1394988398187647007 Discord user Dogma reported: Hello there! I would like to provide feedback on the "conflicting gear" option in DFHack. It worked for normal military gear, such as mail shirts and gauntlets, where dwarves chose pieces belonging to other soldiers. Sadly, the same happened with waterskins, quivers, and backpacks, and the problem remains, resulting in dwarves not equipping them at all. Is there a way to add these items to the problem-fixing function? Dogma did not open an issue on GitHub. Experimentation showed that the report is correct. --- uniform-unstick.lua | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/uniform-unstick.lua b/uniform-unstick.lua index bd8b550ba..474aa54ee 100644 --- a/uniform-unstick.lua +++ b/uniform-unstick.lua @@ -119,6 +119,12 @@ local function remove_item_from_position(squad_position, item_id) end end end + for _, special_case in ipairs({"quiver", "backpack", "flask"}) do + if squad_position.equipment[special_case] == item_id then + squad_position.equipment[special_case] = -1 + return + end + end end -- Will figure out which items need to be moved to the floor, returns an item_id:item map @@ -164,7 +170,8 @@ local function process(unit, args) -- Include weapons so we can check we have them later if inv_item.mode == df.inv_item_role_type.Worn or inv_item.mode == df.inv_item_role_type.Weapon or - inv_item.mode == df.inv_item_role_type.Strapped + inv_item.mode == df.inv_item_role_type.Strapped or + inv_item.mode == df.inv_item_role_type.Flask then worn_items[item.id] = item worn_parts[item.id] = inv_item.body_part_id @@ -181,6 +188,12 @@ local function process(unit, args) end end end + for _, special_case in ipairs({"quiver", "backpack", "flask"}) do + local assigned_item_id = squad_position.equipment[special_case] + if assigned_item_id ~= -1 then + uniform_assigned_items[assigned_item_id] = df.item.find(assigned_item_id) + end + end -- Figure out which assigned items are currently not being worn -- and if some other unit is carrying the item, unassign it from this unit's uniform @@ -215,13 +228,7 @@ local function process(unit, args) -- Make the equipment.assigned_items list consistent with what is present in equipment.uniform for i=#(squad_position.equipment.assigned_items)-1,0,-1 do local assigned_item_id = squad_position.equipment.assigned_items[i] - -- Quiver, backpack, and flask are assigned in their own locations rather than in equipment.uniform, and thus need their own checks - -- If more separately-assigned items are added in the future, this handling will need to be updated accordingly - if uniform_assigned_items[assigned_item_id] == nil and - assigned_item_id ~= squad_position.equipment.quiver and - assigned_item_id ~= squad_position.equipment.backpack and - assigned_item_id ~= squad_position.equipment.flask - then + if uniform_assigned_items[assigned_item_id] == nil then local item = df.item.find(assigned_item_id) if item ~= nil then print(unit_name .. " has an improperly assigned item, " .. item_description(item) .. "; removing it") From 7a64eb068294eb8283a8aecc77b5f70f29b1c086 Mon Sep 17 00:00:00 2001 From: SilasD Date: Wed, 17 Sep 2025 10:12:11 -0700 Subject: [PATCH 2/7] uniform-unstick Only clothing can block, so only drop clothing This change minimizes the number of inventory items which need to be dropped in order to pick up and wear uniform items. Per the Wiki and experimentation, only item types which can have the `LAYER_SIZE` token can block uniform items. Only the five clothing types can have the `LAYER_SIZE` token. Quivers and backpacks can be blocked, but cannot block. Weapons, shields, and flasks cannot block or be blocked. And most importantly, jewelry items cannot not block uniform items (experimentally verified) so do not need to be dropped. --- uniform-unstick.lua | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/uniform-unstick.lua b/uniform-unstick.lua index 474aa54ee..567ca4e42 100644 --- a/uniform-unstick.lua +++ b/uniform-unstick.lua @@ -247,8 +247,10 @@ local function process(unit, args) local covered = {} -- map of body part id to true/nil if not args.multi then for item_id, item in pairs(present_ids) do - -- weapons and shields don't "cover" the bodypart they're assigned to. (Needed to figure out if we're missing gloves.) - if item._type ~= df.item_weaponst and item._type ~= df.item_shieldst then + -- only the five clothing types can block armor for the bodypart they're worn on. + if utils.linear_index({ df.item_helmst, df.item_armorst, df.item_glovesst, + df.item_pantsst, df.item_shoesst }, item._type) + then covered[worn_parts[item_id]] = true end end @@ -264,9 +266,13 @@ local function process(unit, args) end end - -- Drop everything (except uniform pieces) from body parts which should be covered but aren't + -- Drop clothing (except uniform pieces) from body parts which should be covered but aren't for worn_item_id, item in pairs(worn_items) do - if uniform_assigned_items[worn_item_id] == nil then -- don't drop uniform pieces (including shields, weapons for hands) + if uniform_assigned_items[worn_item_id] == nil -- don't drop uniform pieces + -- only the five clothing types can block armor for the bodypart they're worn on. + and utils.linear_index({ df.item_helmst, df.item_armorst, df.item_glovesst, + df.item_pantsst, df.item_shoesst }, item._type) + then if uncovered[worn_parts[worn_item_id]] then print(unit_name .. " potentially has " .. item_description(item) .. " blocking a missing uniform item.") printed = true From d44c5d10ac0d29cadb59c5035f29d543753c8e18 Mon Sep 17 00:00:00 2001 From: SilasD Date: Wed, 17 Sep 2025 11:05:58 -0700 Subject: [PATCH 3/7] uniform-unstick Removing from uniform: remove from .assigned_items When removing an item from a uniform, the item id should also be removed from the `uniform.assigned_items` vector. This avoids a redundant report of "an improperly assigned item". --- uniform-unstick.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/uniform-unstick.lua b/uniform-unstick.lua index 567ca4e42..179f13eec 100644 --- a/uniform-unstick.lua +++ b/uniform-unstick.lua @@ -109,6 +109,7 @@ end -- @param squad_position df.squad_position -- @param item_id number local function remove_item_from_position(squad_position, item_id) + utils.erase_sorted(squad_position.equipment.assigned_items, item_id) for _, uniform_slot_specs in ipairs(squad_position.equipment.uniform) do for _, uniform_spec in ipairs(uniform_slot_specs) do for idx, assigned_item_id in ipairs(uniform_spec.assigned) do From 85300044a494c9f8eb9145c2871a2566a062712b Mon Sep 17 00:00:00 2001 From: SilasD Date: Wed, 17 Sep 2025 11:48:20 -0700 Subject: [PATCH 4/7] Refactor bodyparts_that_can_wear() to remove duplicated/tweaked code. Verified to give identical output. --- uniform-unstick.lua | 42 ++++++++++++------------------------------ 1 file changed, 12 insertions(+), 30 deletions(-) diff --git a/uniform-unstick.lua b/uniform-unstick.lua index 179f13eec..27ca45949 100644 --- a/uniform-unstick.lua +++ b/uniform-unstick.lua @@ -62,38 +62,20 @@ local function bodyparts_that_can_wear(unit, item) local bodyparts = {} local unitparts = dfhack.units.getCasteRaw(unit).body_info.body_parts - if item._type == df.item_helmst then - for index, part in ipairs(unitparts) do - if part.flags.HEAD then - table.insert(bodyparts, index) - end - end - elseif item._type == df.item_armorst then - for index, part in ipairs(unitparts) do - if part.flags.UPPERBODY then - table.insert(bodyparts, index) - end - end - elseif item._type == df.item_glovesst then - for index, part in ipairs(unitparts) do - if part.flags.GRASP then - table.insert(bodyparts, index) - end - end - elseif item._type == df.item_pantsst then - for index, part in ipairs(unitparts) do - if part.flags.LOWERBODY then - table.insert(bodyparts, index) - end - end - elseif item._type == df.item_shoesst then - for index, part in ipairs(unitparts) do - if part.flags.STANCE then - table.insert(bodyparts, index) + for bodypart_flag, item_type in pairs({ + HEAD = df.item_helmst, + UPPERBODY = df.item_armorst, + GRASP = df.item_glovesst, + LOWERBODY = df.item_pantsst, + STANCE = df.item_shoesst, + }) do + if item._type == item_type then + for index, part in ipairs(unitparts) do + if part.flags[bodypart_flag] then + table.insert(bodyparts, index) + end end end - else - -- print("Ignoring item type for "..item_description(item) ) end return bodyparts From 4b1a41a44a74a7fe7f4cf9f6cacb818254ea80c2 Mon Sep 17 00:00:00 2001 From: SilasD Date: Thu, 18 Sep 2025 15:16:34 -0700 Subject: [PATCH 5/7] uniform-unstick Only drop items which are held by a unit In certain cases involving the --free command-line parameter, items are moved to the ground even if they are already on the ground, in a container, or in a building. An item only needs to be moved to the ground if it is held by a unit which does not have it assigned to that units uniform after the --free processing completes. --- uniform-unstick.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/uniform-unstick.lua b/uniform-unstick.lua index 27ca45949..ab0724156 100644 --- a/uniform-unstick.lua +++ b/uniform-unstick.lua @@ -281,8 +281,12 @@ local function do_drop(item_list) for _, item in pairs(item_list) do local pos = get_visible_item_pos(item) + + -- only drop if the item is on the map and is being held by a unit. if not pos then dfhack.printerr("Could not find drop location for " .. item_description(item)) + elseif dfhack.items.getHolderUnit(item) == nil then + -- dfhack.printerr("Not in inventory: " .. item_description(item)) else if dfhack.items.moveToGround(item, pos) then print("Dropped " .. item_description(item)) From f805705a348c7495faed2300d0e89838a0459353 Mon Sep 17 00:00:00 2001 From: SilasD Date: Fri, 19 Sep 2025 16:21:26 -0700 Subject: [PATCH 6/7] update changelog.txt --- changelog.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/changelog.txt b/changelog.txt index 5ecf8024e..52453fd5f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -31,6 +31,10 @@ Template for new versions: ## New Features ## Fixes +- `uniform-unstick`: added quivers, backpacks, and flasks/waterskins to uniform analysis +- `uniform-unstick`: the ``--drop`` option now only evaluates clothing as possible items to drop +- `uniform-unstick`: the ``--free`` option no longer redundantly reports an improperly assigned item when that item is removed from a uniform +- `uniform-unstick`: the ``--drop`` and ``--free`` options now only drop items which are actually in a unit's inventory ## Misc Improvements From 1989990e0615c7193148a0c192cbca7ddf5b0a4b Mon Sep 17 00:00:00 2001 From: SilasD Date: Sat, 20 Sep 2025 10:07:24 -0700 Subject: [PATCH 7/7] uniform-unstick print separator line in correct place When the --all and --drop command-line parameters are used together, each unit's report should be separated by a blank line. Due to the print-code refactor in #1490, this separator line was moved between each unit's basic report and the unit's list of dropped items. --- changelog.txt | 1 + uniform-unstick.lua | 15 ++++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/changelog.txt b/changelog.txt index 52453fd5f..4ef10f691 100644 --- a/changelog.txt +++ b/changelog.txt @@ -35,6 +35,7 @@ Template for new versions: - `uniform-unstick`: the ``--drop`` option now only evaluates clothing as possible items to drop - `uniform-unstick`: the ``--free`` option no longer redundantly reports an improperly assigned item when that item is removed from a uniform - `uniform-unstick`: the ``--drop`` and ``--free`` options now only drop items which are actually in a unit's inventory +- `uniform-unstick`: the ``--all`` and ``--drop`` options, when used together, now print the separator line between each unit's report in the proper place ## Misc Improvements diff --git a/uniform-unstick.lua b/uniform-unstick.lua index ab0724156..5be03a4cc 100644 --- a/uniform-unstick.lua +++ b/uniform-unstick.lua @@ -111,6 +111,7 @@ local function remove_item_from_position(squad_position, item_id) end -- Will figure out which items need to be moved to the floor, returns an item_id:item map +-- and a flag that indicates whether a separator line needs to be printed local function process(unit, args) local silent = args.all -- Don't print details if we're iterating through all dwarves local unit_name = dfhack.df2console(dfhack.units.getReadableName(unit)) @@ -266,15 +267,10 @@ local function process(unit, args) end end - -- add a spacing line if there was any output - if printed then - print() - end - - return to_drop + return to_drop, printed end -local function do_drop(item_list) +local function do_drop(item_list, printed) if not item_list then return end @@ -295,6 +291,11 @@ local function do_drop(item_list) end end end + + -- add a spacing line if there was any output + if printed then + print() + end end local function main(args)