dfhack icon indicating copy to clipboard operation
dfhack copied to clipboard

DFhack assign function on Pen/Pastures not working

Open turniprodeo opened this issue 4 months ago • 3 comments

Image

This button and hotkey to assign animals to a pen or pasture does not activate/work.

turniprodeo avatar Sep 10 '25 02:09 turniprodeo

relevant log file extract

...teamapps\common\Dwarf Fortress\hack\lua\plugins\zone.lua:992: attempt to index a nil value (local 'civzone')
stack traceback:
	...teamapps\common\Dwarf Fortress\hack\lua\plugins\zone.lua:992: in field 'get_status'
	...teamapps\common\Dwarf Fortress\hack\lua\plugins\zone.lua:642: in method 'cache_choices'
	...teamapps\common\Dwarf Fortress\hack\lua\plugins\zone.lua:709: in method 'get_choices'
	...teamapps\common\Dwarf Fortress\hack\lua\plugins\zone.lua:456: in local 'fun'
	...Steam\steamapps\common\Dwarf Fortress\hack\lua\class.lua:124: in upvalue 'invoke_after_rec'
	...Steam\steamapps\common\Dwarf Fortress\hack\lua\class.lua:153: in global 'AssignAnimal'
	...teamapps\common\Dwarf Fortress\hack\lua\plugins\zone.lua:889: in local 'fun'
	...Steam\steamapps\common\Dwarf Fortress\hack\lua\class.lua:124: in upvalue 'invoke_after_rec'
	...Steam\steamapps\common\Dwarf Fortress\hack\lua\class.lua:153: in global 'AssignAnimalScreen'
	...teamapps\common\Dwarf Fortress\hack\lua\plugins\zone.lua:1030: in upvalue 'show_pasture_pond_screen'
	...teamapps\common\Dwarf Fortress\hack\lua\plugins\zone.lua:1049: in field 'on_activate'
	...rf Fortress\hack\lua\gui\widgets\labels\hotkey_label.lua:65: in function 'gui.widgets.labels.hotkey_label.onInput'
	...)\Steam\steamapps\common\Dwarf Fortress\hack\lua\gui.lua:869: in function <...)\Steam\steamapps\common\Dwarf Fortress\hack\lua\gui.lua:856>
	(...tail calls...)
	...Dwarf Fortress\hack\lua\gui\widgets\containers\panel.lua:306: in function 'gui.widgets.containers.panel.onInput'
	...)\Steam\steamapps\common\Dwarf Fortress\hack\lua\gui.lua:869: in function <...)\Steam\steamapps\common\Dwarf Fortress\hack\lua\gui.lua:856>
	(...tail calls...)
	...Dwarf Fortress\hack\lua\gui\widgets\containers\panel.lua:306: in function 'gui.widgets.containers.panel.onInput'
	(...tail calls...)
	...mapps\common\Dwarf Fortress\hack\lua\plugins\overlay.lua:435: in upvalue 'detect_frame_change'
	...mapps\common\Dwarf Fortress\hack\lua\plugins\overlay.lua:524: in upvalue '_feed_viewscreen_widgets'
	...mapps\common\Dwarf Fortress\hack\lua\plugins\overlay.lua:534: in function 'plugins.overlay.feed_viewscreen_widgets'

ab9rf avatar Sep 10 '25 04:09 ab9rf

The animal has a link to a civzone building that doesn't exist, or to a building that is not a civzone.

I was able to reproduce this by setting a pastured unit's general_ref_building_civzone_assignedst.building_id to 999999, a nonexistent building. I was also able to reproduce it by setting the same field to 2, a workshop. Maybe this needs:

(change to line 992, loads and runs without errors but has minimal testing) (edit: found a second instance of the same bug, loads and runs without errors but has minimal testing)

diff --git a/plugins/lua/zone.lua b/plugins/lua/zone.lua
index 92940ef48..2923f8e33 100644
--- a/plugins/lua/zone.lua
+++ b/plugins/lua/zone.lua
@@ -989,7 +989,9 @@ local function get_zone_status(unit_or_vermin, bld_assignments)
             return PASTURE_STATUS.ASSIGNED_HERE.value
         else
             local civzone = df.building.find(assigned_zone_ref.building_id)
-            if civzone.type == df.civzone_type.Pen then
+            if not civzone or not df.building_civzonest:is_instance(civzone) then
+                return PASTURE_STATUS.NONE.value
+            elseif civzone.type == df.civzone_type.Pen then
                 return PASTURE_STATUS.PASTURED.value
             elseif civzone.type == df.civzone_type.Pond then
                 return PASTURE_STATUS.PITTED.value
@@ -1150,7 +1152,9 @@ local function get_cage_status(unit_or_vermin, bld_assignments)
     local assigned_zone_ref = get_general_ref(unit_or_vermin, df.general_ref_type.BUILDING_CIVZONE_ASSIGNED)
     if assigned_zone_ref then
         local civzone = df.building.find(assigned_zone_ref.building_id)
-        if civzone.type == df.civzone_type.Pen then
+        if not civzone or not df.building_civzonest:is_instance(civzone) then
+            return CAGE_STATUS.NONE.value
+        elseif civzone.type == df.civzone_type.Pen then
             return CAGE_STATUS.PASTURED.value
         elseif civzone.type == df.civzone_type.Pond then
             return CAGE_STATUS.PITTED.value

I am torn on whether to treat this case as PASTURED, ROAMING, or NONE. NONE is an unknown or script-error case, and cannot be displayed with the sliders. This makes the animal essentially disappear from the overlay, even if (see below) it is also assigned to a valid pasture.

In the game itself:

  • if a pasture has a link to the animal, it roams free for a while, then a PenLargeAnimal job is created and a worker takes the job, the animal is led to the pasture, but the invalid link from animal to invalid/nonexistent pasture is not cleaned up. This sequence then repeats, presumably indefinitely.
  • If no pasture has a link to the animal, it roams free. Again, the invalid link from animal to invalid/nonexistent pasture is not cleaned up.
  • In either case, the invalid link is not cleaned up even if the animal is subsequently assigned to a valid pasture through the normal DF interface. Instead, the animal gets a second link to the valid pasture.
  • This situation was not cleared up by a save/load.

I did not test pits, cages or chains.

Do you want me to make a PR?

SilasD avatar Sep 10 '25 18:09 SilasD

Use the NONE case for now. This is probably a bug in DF. If we can figure out a way to safely clean up after it, we should do so, but I don't want to delay a usability fix while we try to come up with a mitigation for a DF bug.

ab9rf avatar Sep 11 '25 00:09 ab9rf