Skip to content

Commit ce8efb4

Browse files
authored
Merge pull request #1374 from chdoc/autocheese
new tool: autocheese
2 parents 1a58e45 + e07775a commit ce8efb4

File tree

4 files changed

+230
-0
lines changed

4 files changed

+230
-0
lines changed

autocheese.lua

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
--@module = true
2+
3+
local ic = reqscript('idle-crafting')
4+
5+
---make cheese using a specific barrel and workshop
6+
---@param barrel df.item
7+
---@param workshop df.building_workshopst
8+
---@return df.job
9+
function makeCheese(barrel, workshop)
10+
---@type df.job
11+
local job = ic.make_job()
12+
job.job_type = df.job_type.MakeCheese
13+
14+
local jitem = df.job_item:new()
15+
jitem.quantity = 0
16+
jitem.vector_id = df.job_item_vector_id.ANY_COOKABLE
17+
jitem.flags1.unrotten = true
18+
jitem.flags1.milk = true
19+
job.job_items.elements:insert('#', jitem)
20+
21+
if not dfhack.job.attachJobItem(job, barrel, df.job_item_ref.T_role.Reagent, 0, -1) then
22+
dfhack.error('could not attach item')
23+
end
24+
25+
ic.assignToWorkshop(job, workshop)
26+
return job
27+
end
28+
29+
30+
31+
---unit is ready to take jobs
32+
---@param unit df.unit
33+
---@return boolean
34+
function unitIsAvailable(unit)
35+
if unit.job.current_job then
36+
return false
37+
elseif #unit.individual_drills > 0 then
38+
return false
39+
elseif unit.flags1.caged or unit.flags1.chained then
40+
return false
41+
elseif unit.military.squad_id ~= -1 then
42+
local squad = df.squad.find(unit.military.squad_id)
43+
-- this lookup should never fail
44+
---@diagnostic disable-next-line: need-check-nil
45+
return #squad.orders == 0 and squad.activity == -1
46+
end
47+
return true
48+
end
49+
50+
---check if unit can perform labor at workshop
51+
---@param unit df.unit
52+
---@param unit_labor df.unit_labor
53+
---@param workshop df.building
54+
---@return boolean
55+
function availableLaborer(unit, unit_labor, workshop)
56+
return unit.status.labors[unit_labor]
57+
and unitIsAvailable(unit)
58+
and ic.canAccessWorkshop(unit, workshop)
59+
end
60+
61+
---find unit with a particular labor enabled
62+
---@param unit_labor df.unit_labor
63+
---@param job_skill df.job_skill
64+
---@param workshop df.building
65+
---@return df.unit|nil
66+
---@return integer|nil
67+
function findAvailableLaborer(unit_labor, job_skill, workshop)
68+
local max_unit = nil
69+
local max_skill = -1
70+
for _, unit in ipairs(dfhack.units.getCitizens(true, false)) do
71+
if
72+
availableLaborer(unit, unit_labor, workshop)
73+
then
74+
local unit_skill = dfhack.units.getNominalSkill(unit, job_skill, true)
75+
if unit_skill > max_skill then
76+
max_unit = unit
77+
max_skill = unit_skill
78+
end
79+
end
80+
end
81+
return max_unit, max_skill
82+
end
83+
84+
local function findMilkBarrel(min_liquids)
85+
for _, container in ipairs(df.global.world.items.other.FOOD_STORAGE) do
86+
if
87+
not (container.flags.in_job or container.flags.forbid) and
88+
container.flags.container and #container.general_refs >= min_liquids
89+
then
90+
local content_reference = dfhack.items.getGeneralRef(container, df.general_ref_type.CONTAINS_ITEM)
91+
local contained_item = df.item.find(content_reference and content_reference.item_id or -1)
92+
if contained_item then
93+
local mat_info = dfhack.matinfo.decode(contained_item)
94+
if mat_info:matches { milk = true } then
95+
return container
96+
end
97+
end
98+
end
99+
end
100+
end
101+
102+
---find a workshop to which the barrel can be brought
103+
---if the workshop has a master, only return workshop and master if the master is available
104+
---@param pos df.coord
105+
---@return df.building_workshopst?
106+
---@return df.unit?
107+
function findWorkshop(pos)
108+
for _,workshop in ipairs(df.global.world.buildings.other.WORKSHOP_FARMER) do
109+
if
110+
dfhack.maps.canWalkBetween(pos, xyz2pos(workshop.centerx, workshop.centery, workshop.z)) and
111+
not workshop.profile.blocked_labors[df.unit_labor.MAKE_CHEESE] and
112+
#workshop.jobs == 0
113+
then
114+
if #workshop.profile.permitted_workers == 0 then
115+
-- immediately return workshop without master
116+
return workshop, nil
117+
else
118+
unit = df.unit.find(workshop.profile.permitted_workers[0])
119+
if
120+
unit and availableLaborer(unit, df.unit_labor.MAKE_CHEESE, workshop)
121+
then
122+
-- return workshop and master, if master is available
123+
return workshop, unit
124+
else
125+
print("autocheese: Skipping farmer's workshop with unavailable master")
126+
end
127+
end
128+
end
129+
end
130+
end
131+
132+
if dfhack_flags.module then
133+
return
134+
end
135+
136+
-- actual script action
137+
138+
local argparse = require('argparse')
139+
140+
local min_number = 50
141+
142+
local _ = argparse.processArgsGetopt({...},
143+
{
144+
{ 'm', 'min-milk', hasArg = true,
145+
handler = function(min)
146+
min_number = argparse.nonnegativeInt(min, 'min-milk')
147+
end }
148+
})
149+
150+
151+
local reagent = findMilkBarrel(min_number)
152+
153+
if not reagent then
154+
-- print('autocheese: no sufficiently full barrel found')
155+
return
156+
end
157+
158+
local workshop, worker = findWorkshop(xyz2pos(dfhack.items.getPosition(reagent)))
159+
160+
if not workshop then
161+
print("autocheese: no Farmer's Workshop available")
162+
return
163+
end
164+
165+
-- try to find laborer for workshop without master
166+
if not worker then
167+
worker, _ = findAvailableLaborer(df.unit_labor.MAKE_CHEESE, df.job_skill.CHEESEMAKING, workshop)
168+
end
169+
170+
if not worker then
171+
print('autocheese: no cheesemaker available')
172+
return
173+
end
174+
local job = makeCheese(reagent, workshop)
175+
176+
print(('autocheese: dispatching cheesemaking job for %s (%d milk) to %s'):format(
177+
dfhack.df2console(dfhack.items.getReadableDescription(reagent)),
178+
#reagent.general_refs,
179+
dfhack.df2console(dfhack.units.getReadableName(worker))
180+
))
181+
182+
183+
-- assign a worker and send it to fetch the barrel
184+
dfhack.job.addWorker(job, worker)
185+
dfhack.units.setPathGoal(worker, reagent.pos, df.unit_path_goal.GrabJobResources)
186+
job.items[0].flags.is_fetching = true
187+
job.flags.fetching = true

changelog.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ Template for new versions:
2828

2929
## New Tools
3030

31+
- `autocheese`: automatically make cheese using barrels that have accumulated sufficient milk
32+
3133
## New Features
3234

3335
## Fixes

docs/autocheese.rst

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
autocheese
2+
==========
3+
4+
.. dfhack-tool::
5+
:summary: Schedule cheese making jobs based on milk reserves.
6+
:tags: fort auto
7+
8+
Cheese making is difficult to automate using work orders. A single job
9+
can consume anything from a bucket with a single unit of milk to a barrel
10+
with 100 units of milk. This makes it hard to predict how much cheese will
11+
actually be produced by an automated order.
12+
13+
The script will scan your fort for barrels with a certain minimum amount of milk
14+
(default: 50), create a cheese making job specifically for that barrel, and
15+
assign this job to one of your idle dwarves (giving preference to skilled cheese
16+
makers).
17+
18+
When enabled using `gui/control-panel`, the script will run automatically, with
19+
default options, twice a month.
20+
21+
Usage
22+
-----
23+
24+
::
25+
26+
autocheese [<options>]
27+
28+
Examples
29+
--------
30+
31+
``autocheese -m 100``
32+
Only create a job if there is a barrel that is filled to the maximum.
33+
34+
Options
35+
-------
36+
37+
``-m``, ``--min-milk``
38+
Set the minimum number of milk items in a barrel for the barrel to be
39+
considered for cheese making.

internal/control-panel/registry.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ COMMANDS_BY_IDX = {
1818
{command='autobutcher target 10 10 14 2 BIRD_PEAFOWL_BLUE', group='automation', mode='run',
1919
desc='Enable if you usually want to raise peafowl.'},
2020
{command='autochop', group='automation', mode='enable'},
21+
{command='autocheese', group='automation', mode='repeat',
22+
params={'--time', '14', '--timeUnits', 'days', '--command', '[', 'autocheese', ']'}},
2123
{command='autoclothing', group='automation', mode='enable'},
2224
{command='autofarm', group='automation', mode='enable'},
2325
{command='autofarm threshold 150 grass_tail_pig', group='automation', mode='run',

0 commit comments

Comments
 (0)