From 997f9ee36c5123cae053600ca9ac7f1aca60a565 Mon Sep 17 00:00:00 2001 From: Alistair-Afton Date: Wed, 16 Sep 2026 03:31:31 +0200 Subject: [PATCH] item: items in unwalkable buildings are reachable from adjacent tiles The reachable/unreachable filters only checked the item's own tile, so loose contents of impassable buildings (like bolts loaded in a bolt thrower) were always reported unreachable even though citizens can retrieve them by standing next to the building. When the item tile is not citizen-walkable, now also check whether the item is in or on a building with a citizen-reachable tile in or next to its footprint. Items installed as building components (use_mode PERM, e.g. mechanisms, well buckets, built furniture) are still unreachable, as are items in wall constructions. Fixes DFHack/dfhack#5771 --- changelog.txt | 1 + item.lua | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/changelog.txt b/changelog.txt index b492b6d2ec..d208639fb8 100644 --- a/changelog.txt +++ b/changelog.txt @@ -34,6 +34,7 @@ Template for new versions: - `bodyswap`: fix "invalid argument count" when the target unit has no nemesis record - `fix/loyaltycascade`: guard against citizens that are not historical figures and emit a warning. - `gui/siegemanager`: fix nil index if there are no siege engines on the map +- `item`: the ``reachable``/``unreachable`` filters no longer count installed building parts, and now treat loose contents of unwalkable buildings (like bolts loaded in a bolt thrower) as reachable when a citizen can stand next to the building ## Misc Improvements - `caravan`: the ``Bring goods to depot``, ``Trade``, and ``Assign items for display`` overlays now allow searching for items with non-ASCII characters in their description diff --git a/item.lua b/item.lua index 7077984410..edd2a74105 100644 --- a/item.lua +++ b/item.lua @@ -21,12 +21,38 @@ end --- @return boolean function fastReachable(item,wgroups) local x, y, z = dfhack.items.getPosition(item) - if x then -- item has a valid position - local igroup = dfhack.maps.getWalkableGroup(xyz2pos(x, y, z)) - return not not wgroups[igroup] - else + if not x then + return false -- item has no valid position (e.g., inside inventories) + end + local igroup = dfhack.maps.getWalkableGroup(xyz2pos(x, y, z)) + if wgroups[igroup] then + return true + end + -- items on unwalkable building tiles can still be retrieved by standing + -- next to the building, unless they are installed parts (use_mode PERM) + local bld = dfhack.items.getHolderBuilding(item) or + dfhack.buildings.findAtTile(xyz2pos(x, y, z)) + if not bld or bld:getType() == df.building_type.Construction then return false end + if df.building_actual:is_instance(bld) then + for _, ci in ipairs(bld.contained_items) do + if ci.item == item then + if ci.use_mode == df.building_item_role_type.PERM then + return false + end + break + end + end + end + for bx = bld.x1 - 1, bld.x2 + 1 do + for by = bld.y1 - 1, bld.y2 + 1 do + if wgroups[dfhack.maps.getWalkableGroup(xyz2pos(bx, by, bld.z))] then + return true + end + end + end + return false end --- @return table