Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added autowheelbarrow script for stockpile management #1328

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions autowheelbarrow.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
local repeatutil = require("repeat-util")
local GLOBAL_KEY = "autowheelbarrow"
enabled = enabled or false

-- Main function for managing wheelbarrows
local function unassign_wheelbarrows()
local wheelbarrow_count = 0
for _, item in ipairs(df.global.world.items.other.TOOL) do
if df.item_toolst:is_instance(item) then
local tool_def = dfhack.items.getSubtypeDef(item:getType(), item:getSubtype())
if tool_def and tool_def.id == "ITEM_TOOL_WHEELBARROW" then
wheelbarrow_count = wheelbarrow_count + 1 -- Count each wheelbarrow
if item.flags.in_job then
-- Skip if in use
elseif item.stockpile then
item.stockpile.id = -1 -- Unassign wheelbarrow from stockpile
end
end
end
end
return wheelbarrow_count -- Return the total count of wheelbarrows processed
end

local function set_wheelbarrows_for_all_stockpiles()
for _, building in ipairs(df.global.world.buildings.all) do
if building:getType() == df.building_type.Stockpile then
local stockpile_settings = building.settings
local skip_wheelbarrows = false

-- Check food settings to skip wheelbarrows
if stockpile_settings and stockpile_settings.food then
local food_settings = stockpile_settings.food
skip_wheelbarrows = #food_settings.meat > 0 or #food_settings.fish > 0 or #food_settings.unprepared_fish > 0 or
#food_settings.egg > 0 or #food_settings.plants > 0 or #food_settings.drink_plant > 0 or
#food_settings.drink_animal > 0 or #food_settings.cheese_animal > 0 or #food_settings.cheese_plant > 0 or
#food_settings.seeds > 0 or #food_settings.leaves > 0 or #food_settings.powder_plant > 0 or
#food_settings.powder_creature > 0 or #food_settings.glob > 0 or #food_settings.glob_paste > 0 or
#food_settings.glob_pressed > 0 or #food_settings.liquid_plant > 0 or #food_settings.liquid_animal > 0 or
#food_settings.liquid_misc > 0
--dfhack.println("Skipping stockpile due to food items" .. tostring(building))
Comment on lines +31 to +40
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea why stockpiles containing food should not have wheelbarrows. After all, it is quite possible to have a stockpile that has both food and boulders enabled.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand what you're saying, but I don't like it. The main purpose of creating this program was to maintain high efficiency in stockpiles containing wheelbarrows and, of course, have wheelbarrows move freely between stockpiles. Having food and stones together sounds insane to me, and this program will instead be a detriment to users, as those stockpiles will not be set to 1 automatically if stone is found or to x (wheelbarrows in the fort) per the program's functionality. Additionally, it will be a problem if 30 wheelbarrows inhabit a food and stone stockpile, as that is a possibility if I remove the conditions, which will cascade into a problem with food. I'm sorry for being stubborn; I think these conditions are essential. If this program is scrapped or repurposed, that's fine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you missed the main point of my remark. I wasn't suggesting that this script should fully account for mixed food and stone stockpiles - I agree that those aren't very useful.

My point was that the program logic could be drastically simplified using a positive check for item categories that require wheelbarrows (i.e. boulders). I don't know which other item categories consistently reach the 75Γ threshold for being carried using a wheelbarrow. Platinum and gold bars do, as do filled minecarts.

Another possibility might be to never check the item flags of the stockpile, and instead only modify the number of requested wheelbarrows for stockpiles that request at least one wheelbarrow. By default, that applies only to stone stockpiles, but the user could decide to assign in wheelbarrows to other stockpiles as well.


end

-- Check all the flags and skip if any of them are true (for individual stockpiles)
if stockpile_settings and stockpile_settings.flags then
local flags = stockpile_settings.flags
if flags.animals == true or flags.food == true or flags.furniture == true or
flags.corpses == true or flags.refuse == true or flags.ammo == true or flags.coins == true or
flags.bars_blocks == true or flags.gems == true or flags.finished_goods == true or
flags.leather == true or flags.cloth == true or flags.wood == true or
flags.weapons == true or flags.armor == true or flags.sheet == true or
flags[17] == true or flags[18] == true or flags[19] == true or flags[20] == true or
flags[21] == true or flags[22] == true or flags[23] == true or flags[24] == true or
flags[25] == true or flags[26] == true or flags[27] == true or flags[28] == true or
flags[29] == true or flags[30] == true or flags[31] == true then
skip_wheelbarrows = true
--dfhack.println("individual stockpiles Skipping stockpile due to one or more flags being true at " .. tostring(building))
end
end

-- Check if all flags are set to false (for the none stockpile)
if stockpile_settings and stockpile_settings.flags then
local flags = stockpile_settings.flags
if flags.animals == false and flags.food == false and flags.furniture == false and
flags.corpses == false and flags.refuse == false and flags.stone == false and flags.ammo == false and
flags.coins == false and flags.bars_blocks == false and flags.gems == false and flags.finished_goods == false and
flags.leather == false and flags.cloth == false and flags.wood == false and flags.weapons == false and
flags.armor == false and flags.sheet == false and
flags[17] == false and flags[18] == false and flags[19] == false and flags[20] == false and
flags[21] == false and flags[22] == false and flags[23] == false and flags[24] == false and
flags[25] == false and flags[26] == false and flags[27] == false and flags[28] == false and
flags[29] == false and flags[30] == false and flags[31] == false then
skip_wheelbarrows = true
--dfhack.println("Skipping NONE stockpile" .. tostring(building))
end
end
Comment on lines +61 to +76
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking unnamed flags likely does not make sense at all. If anything, the code should probably check whether the stockpile has any goods enabled that make use of wheelbarrows, instead of checking whether it has (also) some goods enabled that do not make use of them.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You got me there. idk what they are for, but they exist so I added them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they only "exist" in the sense that the flag word has 32 bits. they don't have a (known) use and so predicating any behavior on their value will result in unpredictable behavior because their value is unpredictable


if skip_wheelbarrows then
building.max_wheelbarrows = 0
else
local count = unassign_wheelbarrows()
building.max_wheelbarrows = count
Comment on lines +81 to +82
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't calling unassign_wheelbarrows inside the loop mean that only the last valid stockpile will ever get wheelbarrows assigned to it?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes you're right I should unassign all wheelbarrows before the loop.

--dfhack.println("Set wheelbarrow limit for stockpile at " .. tostring(building) .. " to " .. count)
end
end
end
end

local function auto_wheelbarrows()
for _, unit in ipairs(df.global.world.units.active) do
if dfhack.units.isCitizen(unit) and unit.job.current_job and unit.job.current_job.job_type == 38 then
unassign_wheelbarrows()
set_wheelbarrows_for_all_stockpiles()
return
end
end
end

-- Event loop function to call periodically
local function event_loop()
if enabled then
dfhack.println("Running autowheelbarrow.lua ")
auto_wheelbarrows()
-- Check again in 7 days
repeatutil.scheduleUnlessAlreadyScheduled(GLOBAL_KEY, 7, "days", event_loop)
end
end
Comment on lines +89 to +107
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the rationale for your timing. It looks to me as if you check every 7 days whether there is, at that very moment, a unit that has the StoreItemInStockpile job, and only call auto_wheelbarrows if that is the case.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason for the timing of seven days is children decide to pick up a wheelbarrow and it can take them quite a bit of time to haul the wheelbarrow meaning if the child is hauling for a while this will cause the program to run again and unassign all of the wheelbarrows in the fort resulting in a loop causing less work to be done.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 7 days may be fine, even though it may be preferable to make this configurable. My question was more: why should the reassignment only occur if there is an active StoreItemInStockpile job.


-- Manage on state change
dfhack.onStateChange[GLOBAL_KEY] = function(sc)
if sc == SC_MAP_LOADED and df.global.gamemode == df.game_mode.DWARF then
event_loop() -- Start the event loop when the game map is loaded
elseif sc == SC_MAP_UNLOADED then
repeatutil.cancel(GLOBAL_KEY) -- Stop the event loop when the map is unloaded
end
end

-- Enable the script and start the event loop
enabled = true
event_loop()
Comment on lines +109 to +120
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have a look at how the enable API is supposed to be used:
https://docs.dfhack.org/en/stable/docs/dev/Lua%20API.html#script-enable-api

8 changes: 8 additions & 0 deletions docs/autowheelbarrow.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
autowheelbarrow
===============

.. dfhack-tool::
:summary: Automatically manage wheelbarrows in stockpiles based on conditions.
:tags: fort auto stockpiles

This script automates the assignment of wheelbarrows to stockpiles based on specific criteria, such as the type of items stored in the stockpile. Stockpiles with food, armor, or all flags set to ``false`` will not have wheelbarrows assigned.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That documentation should clearly identify the problem the script is trying to solve. In particular, it should answer the following questions:

  • What is the deficiency of the automatic wheelbarrow assignment of DF that the script is addressing?
  • How is the script overcoming the aforementioned deficiency?