Skip to content

Commit

Permalink
Merge pull request #1374 from chdoc/autocheese
Browse files Browse the repository at this point in the history
new tool: autocheese
  • Loading branch information
myk002 authored Jan 25, 2025
2 parents 1a58e45 + e07775a commit ce8efb4
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 0 deletions.
187 changes: 187 additions & 0 deletions autocheese.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
--@module = true

local ic = reqscript('idle-crafting')

---make cheese using a specific barrel and workshop
---@param barrel df.item
---@param workshop df.building_workshopst
---@return df.job
function makeCheese(barrel, workshop)
---@type df.job
local job = ic.make_job()
job.job_type = df.job_type.MakeCheese

local jitem = df.job_item:new()
jitem.quantity = 0
jitem.vector_id = df.job_item_vector_id.ANY_COOKABLE
jitem.flags1.unrotten = true
jitem.flags1.milk = true
job.job_items.elements:insert('#', jitem)

if not dfhack.job.attachJobItem(job, barrel, df.job_item_ref.T_role.Reagent, 0, -1) then
dfhack.error('could not attach item')
end

ic.assignToWorkshop(job, workshop)
return job
end



---unit is ready to take jobs
---@param unit df.unit
---@return boolean
function unitIsAvailable(unit)
if unit.job.current_job then
return false
elseif #unit.individual_drills > 0 then
return false
elseif unit.flags1.caged or unit.flags1.chained then
return false
elseif unit.military.squad_id ~= -1 then
local squad = df.squad.find(unit.military.squad_id)
-- this lookup should never fail
---@diagnostic disable-next-line: need-check-nil
return #squad.orders == 0 and squad.activity == -1
end
return true
end

---check if unit can perform labor at workshop
---@param unit df.unit
---@param unit_labor df.unit_labor
---@param workshop df.building
---@return boolean
function availableLaborer(unit, unit_labor, workshop)
return unit.status.labors[unit_labor]
and unitIsAvailable(unit)
and ic.canAccessWorkshop(unit, workshop)
end

---find unit with a particular labor enabled
---@param unit_labor df.unit_labor
---@param job_skill df.job_skill
---@param workshop df.building
---@return df.unit|nil
---@return integer|nil
function findAvailableLaborer(unit_labor, job_skill, workshop)
local max_unit = nil
local max_skill = -1
for _, unit in ipairs(dfhack.units.getCitizens(true, false)) do
if
availableLaborer(unit, unit_labor, workshop)
then
local unit_skill = dfhack.units.getNominalSkill(unit, job_skill, true)
if unit_skill > max_skill then
max_unit = unit
max_skill = unit_skill
end
end
end
return max_unit, max_skill
end

local function findMilkBarrel(min_liquids)
for _, container in ipairs(df.global.world.items.other.FOOD_STORAGE) do
if
not (container.flags.in_job or container.flags.forbid) and
container.flags.container and #container.general_refs >= min_liquids
then
local content_reference = dfhack.items.getGeneralRef(container, df.general_ref_type.CONTAINS_ITEM)
local contained_item = df.item.find(content_reference and content_reference.item_id or -1)
if contained_item then
local mat_info = dfhack.matinfo.decode(contained_item)
if mat_info:matches { milk = true } then
return container
end
end
end
end
end

---find a workshop to which the barrel can be brought
---if the workshop has a master, only return workshop and master if the master is available
---@param pos df.coord
---@return df.building_workshopst?
---@return df.unit?
function findWorkshop(pos)
for _,workshop in ipairs(df.global.world.buildings.other.WORKSHOP_FARMER) do
if
dfhack.maps.canWalkBetween(pos, xyz2pos(workshop.centerx, workshop.centery, workshop.z)) and
not workshop.profile.blocked_labors[df.unit_labor.MAKE_CHEESE] and
#workshop.jobs == 0
then
if #workshop.profile.permitted_workers == 0 then
-- immediately return workshop without master
return workshop, nil
else
unit = df.unit.find(workshop.profile.permitted_workers[0])
if
unit and availableLaborer(unit, df.unit_labor.MAKE_CHEESE, workshop)
then
-- return workshop and master, if master is available
return workshop, unit
else
print("autocheese: Skipping farmer's workshop with unavailable master")
end
end
end
end
end

if dfhack_flags.module then
return
end

-- actual script action

local argparse = require('argparse')

local min_number = 50

local _ = argparse.processArgsGetopt({...},
{
{ 'm', 'min-milk', hasArg = true,
handler = function(min)
min_number = argparse.nonnegativeInt(min, 'min-milk')
end }
})


local reagent = findMilkBarrel(min_number)

if not reagent then
-- print('autocheese: no sufficiently full barrel found')
return
end

local workshop, worker = findWorkshop(xyz2pos(dfhack.items.getPosition(reagent)))

if not workshop then
print("autocheese: no Farmer's Workshop available")
return
end

-- try to find laborer for workshop without master
if not worker then
worker, _ = findAvailableLaborer(df.unit_labor.MAKE_CHEESE, df.job_skill.CHEESEMAKING, workshop)
end

if not worker then
print('autocheese: no cheesemaker available')
return
end
local job = makeCheese(reagent, workshop)

print(('autocheese: dispatching cheesemaking job for %s (%d milk) to %s'):format(
dfhack.df2console(dfhack.items.getReadableDescription(reagent)),
#reagent.general_refs,
dfhack.df2console(dfhack.units.getReadableName(worker))
))


-- assign a worker and send it to fetch the barrel
dfhack.job.addWorker(job, worker)
dfhack.units.setPathGoal(worker, reagent.pos, df.unit_path_goal.GrabJobResources)
job.items[0].flags.is_fetching = true
job.flags.fetching = true
2 changes: 2 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ Template for new versions:

## New Tools

- `autocheese`: automatically make cheese using barrels that have accumulated sufficient milk

## New Features

## Fixes
Expand Down
39 changes: 39 additions & 0 deletions docs/autocheese.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
autocheese
==========

.. dfhack-tool::
:summary: Schedule cheese making jobs based on milk reserves.
:tags: fort auto

Cheese making is difficult to automate using work orders. A single job
can consume anything from a bucket with a single unit of milk to a barrel
with 100 units of milk. This makes it hard to predict how much cheese will
actually be produced by an automated order.

The script will scan your fort for barrels with a certain minimum amount of milk
(default: 50), create a cheese making job specifically for that barrel, and
assign this job to one of your idle dwarves (giving preference to skilled cheese
makers).

When enabled using `gui/control-panel`, the script will run automatically, with
default options, twice a month.

Usage
-----

::

autocheese [<options>]

Examples
--------

``autocheese -m 100``
Only create a job if there is a barrel that is filled to the maximum.

Options
-------

``-m``, ``--min-milk``
Set the minimum number of milk items in a barrel for the barrel to be
considered for cheese making.
2 changes: 2 additions & 0 deletions internal/control-panel/registry.lua
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ COMMANDS_BY_IDX = {
{command='autobutcher target 10 10 14 2 BIRD_PEAFOWL_BLUE', group='automation', mode='run',
desc='Enable if you usually want to raise peafowl.'},
{command='autochop', group='automation', mode='enable'},
{command='autocheese', group='automation', mode='repeat',
params={'--time', '14', '--timeUnits', 'days', '--command', '[', 'autocheese', ']'}},
{command='autoclothing', group='automation', mode='enable'},
{command='autofarm', group='automation', mode='enable'},
{command='autofarm threshold 150 grass_tail_pig', group='automation', mode='run',
Expand Down

0 comments on commit ce8efb4

Please sign in to comment.