This requires DFHack!
Making prepared meals from a reaction has been a long requested feature, and I have finally figured out a way to do it.
The following DFHack script, when used with proper reactions, will allow you to make any prepared meal items you want.
First a few notes:
- AFAIK if you provide an invalid "LEVEL" for a food item it will not be possible to make it with the hardcoded cooking reaction, but it still works with this script.
- You will need to register your reactions in the script, see the comments at the end of the script for details.
- This script is not a command! You can call it like a command, but you should only do so once when the world is loaded.
- If you want to see this in action download Rubble and look at the "User/DFHack/Cook Steak" addon.
- I identified two previously unknown offsets in the ingredient type, each is commented in the script.
local eventful = require "plugins.eventful"
-- This Lua hook will make a prepared meal using the items with reagent IDs "item_1", "item_2",
-- "item_3", and "item_4" as the first, second, third, and fourth meal ingredients.
--
-- Ingredient reagents (the ones with the special IDs) will always have the full stack used up.
--
-- The number of ingredients does NOT have to match the food item level!
--
-- The meal type is determined by the the reaction name. The reaction must be named "<Reaction ID>|<Food ID>",
-- for example "COOK_MEAL|ITEM_FOOD_ROAST"
--
-- Here are some example reactions.
--
-- # Make a roast from any meat (but you only need two ingredients!)
-- [REACTION:COOK_MEAL|ITEM_FOOD_ROAST]
-- [NAME:cook a roast]
-- [BUILDING:KITCHEN:NONE]
-- [REAGENT:item_1:1:MEAT:NONE:NONE:NONE]
-- [REAGENT:item_2:1:MEAT:NONE:NONE:NONE]
-- [PRODUCT:0:0:ROCK:NONE:INORGANIC:NONE]
-- [SKILL:COOK]
--
-- # You need a ITEM_FOOD_PIE item defined and flour materials must have the FLOUR_MAT
-- # reaction class for this reaction.
-- [REACTION:COOK_MEAL|ITEM_FOOD_PIE]
-- [NAME:cook a fruit pie (flour and fruit)]
-- [BUILDING:KITCHEN:NONE]
-- [REAGENT:item_1:1:POWDER_MISC:NONE:NONE:NONE]
-- [REACTION_CLASS:FLOUR_MAT]
-- [REAGENT:bag:1:NONE:NONE:NONE:NONE]
-- [CONTAINS:item_1]
-- [PRESERVE_REAGENT]
-- [DOES_NOT_DETERMINE_PRODUCT_AMOUNT]
-- [REAGENT:item_2:1:PLANT_GROWTH:NONE:NONE:NONE]
-- [PRODUCT:0:0:ROCK:NONE:INORGANIC:NONE]
-- [SKILL:COOK]
--
function cookMeal(reaction, reaction_product, unit, in_items, in_reag, out_items, call_native)
call_native.value = false
local ingredients = {}
local amount = {}
-- We do a bit of trickery here to make the reaction always use the whole stack.
for x = 0, #in_items - 1, 1 do
if in_reag[x].code == "item_1" then
ingredients[1] = in_items[x]
amount[1] = in_items[x].stack_size
in_items[x].stack_size = in_reag[x].quantity
elseif in_reag[x].code == "item_2" then
ingredients[2] = in_items[x]
amount[2] = in_items[x].stack_size
in_items[x].stack_size = in_reag[x].quantity
elseif in_reag[x].code == "item_3" then
ingredients[3] = in_items[x]
amount[3] = in_items[x].stack_size
in_items[x].stack_size = in_reag[x].quantity
elseif in_reag[x].code == "item_4" then
ingredients[4] = in_items[x]
amount[4] = in_items[x].stack_size
in_items[x].stack_size = in_reag[x].quantity
end
end
local ok = true
if ingredients[1] == nil then
ok = false
end
if ingredients[2] ~= nil then
if not ok then
error "Ingredient missing from sequence: 1"
end
else
ok = false
end
if ingredients[3] ~= nil then
if not ok then
error "Ingredient missing from sequence: 2"
end
else
ok = false
end
if ingredients[4] ~= nil then
if not ok then
error "Ingredient missing from sequence: 3"
end
else
ok = false
end
local mealtype = dfhack.items.findSubtype("FOOD:"..string.match(reaction.code, "[a-zA-Z_]+|([a-zA-Z_]+)"))
if mealtype == -1 then
error "Invalid FOOD type."
end
local item = df['item_foodst']:new()
item.id = df.global.item_next_id
df.global.world.items.all:insert('#',item)
df.global.item_next_id = df.global.item_next_id + 1
item:setSubtype(mealtype)
-- These always seem to be -1.
item.entity = -1
item.recipe_id = -1
local stacksize = 0
for i, ingredient in ipairs(ingredients) do
local imat = dfhack.matinfo.decode(ingredient)
stacksize = stacksize + amount[i]
item.ingredients:insert('#', {
new = true,
-- Seems to always be 0.
anon_1 = 0,
-- The ingredient item type.
item_type = ingredient:getType(),
-- Seems to always be -1.
unk_4 = -1,
-- Ingredient material type and index.
mat_type = imat.type,
mat_index = imat.index,
-- The creator unit ID.
maker = unit.id,
-- The ingredient quality.
unk_10 = 0,
-- The skill level of the maker.
unk_14 = 0,
-- Seems to always be -1.
unk_18 = -1,
})
end
item.stack_size = stacksize
-- This is something of a hack to assign ingredient quality,
-- we just calculate item quality once for each ingredient, then
-- set ingredient quality directly from item quality.
for _, ingredient in ipairs(item.ingredients) do
item:assignQuality(unit, df.job_skill["COOK"])
ingredient.unk_10 = item.quality
end
-- Set item quality for one last time.
item:assignQuality(unit, df.job_skill["COOK"])
item:setMakerRace(df.global.ui.race_id)
item:setMaker(unit.id)
item:categorize(true)
out_items:insert('#', item)
end
-- You will need to register your reactions here, just like this example.
eventful.registerReaction("COOK_MEAL|ITEM_FOOD_STEAK", cookMeal)
Here is the example reaction and food item:
[REACTION:COOK_MEAL|ITEM_FOOD_STEAK]
[NAME:cook a steak]
[BUILDING:KITCHEN:NONE]
[REAGENT:item_1:1:MEAT:NONE:NONE:NONE]
[PRODUCT:0:0:ROCK:NONE:INORGANIC:NONE]
[SKILL:COOK]
[ITEM_FOOD:ITEM_FOOD_STEAK]
[NAME:steak]
[LEVEL:1]