Some follow-up food/preference findings.
Findings:
Units will get a decent meal thought from meals containing alcohol they prefer, but not a decent drink thought.
Units will seek out available food, no matter where it is on the map (assuming it is pathable).
Units will then seek out preferred food, if it is within a specific range of the first found food item.
- X is East/West, Y is North/South, Z is Up/Down
- This appears to be a range of 230 y and 95 z spaces.
- There doesn't appear to be a max range for x spaces. I had units seeking out preferred food at ~760 tiles.
- If there is no preferred food within the search range, any available food will be eaten.
- Path restriction costs don't appear to impact this seek (d-o-r).
If preferred food is found, a second search for 'more preferred' seems to take place.
- This looks ? x, 1 y, and 0 z spaces for 'more preferred' food stacks
- I saw no evidence that the units will extend the search space after the second preferred food is found.
- The 'most preferred' food appears to be raw ingredients.
- Units don't appear to prefer any mealtype or meal quality over another, as long as it has the preferred ingredient.
-- biscuits == stews == roasts and no quality == masterwork
- There doesn't appear to be any involvement from greed, immoderation, or excitement_seeking traits
Known problems:
Some weirdness with the 'more preferred' search
- There might be code that detects 'same old' food/drink choices and prevents the same choice X times in a row.
- Or there might be a minimum search area. I got to where I could 90% predict food choice depending on where the dwarf was standing.
- Either way, if the raw ingredient was found within ?x,1y,0z of a meal, units went for it ~50% of the time.
Food and preferences were created by script, rather than by the game. I didn't see any difference with a test embark specifically created to test this, but...
Mealmaker doesn't guarantee ingredient order, so specifically named meals were only lightly tested.
Testing:
All testing took place in 44.12 with dfhack 44.12 alpha.
I gave all dwarves a preference for plump helmets.
I created a large number of plump helmets with create-items.
I created a large number of meals containing plump helmets with mealmaker.
I locked two dwarves and two cats(vermin control) in two locked rooms with 3 stacks of food each: non-preference, meal with preference ingredient, raw preference ingredient. Rooms were 3X by 5Y and mirrored each other.
I placed a 1 tile meeting zone on end of the chamber and the food stacks on the other (in a vertical line). Like: Zone>NonPreferred>floor>Meal>Raw
I then used the hunger script on repeat every day to force the dwarf to eat.
I ran this for 56 days, then counted the remaining food.
I ran this trial 10 times.
Trial results pretty consistently looked like:
NonPreference Eaten: 0%
Meals Eaten: 50%
PlumpHelmets Eaten: 50%
Scripts used for testing:
Hunger: Makes a unit hungry (39900) and resets thirsty/sleepy/exhaustion to 0.
-- Make a unit hungry
local utils=require('utils')
validArgs = utils.invert({
'unit'
})
local args = utils.processArgs({...}, validArgs)
local item = dfhack.gui.getSelectedItem(true)
local unit
if args.unit then
unit = df.unit.find(tonumber(args.unit))
elseif df.item_corpsest:is_instance(item) then
unit = df.unit.find(item.unit_id) --hint:df.item_corpsest
else
unit = dfhack.gui.getSelectedUnit()
end
if unit then
unit.counters2.exhaustion = 0
unit.counters2.hunger_timer = 39900
unit.counters2.thirst_timer = 0
unit.counters2.sleepiness_timer = 0
end
Set Preference: Gives everyone a preference for plump helmets
local utils=require('utils')
-- plump helmets: mattype = 419 , matindex = 172
-- goat cheese: mattype = 48 , matindex = 178
-- dwarven ale: mattype 420, matindex = 173
for k,u in ipairs(df.global.world.units.active) do
if ( dfhack.units.isCitizen(u) ) then
utils.insert_or_update(u.status.current_soul.preferences, {
new = true,
type = 2,
item_type = df.item_type.PLANT,
creature_id = df.item_type.PLANT,
color_id = df.item_type.PLANT,
shape_id = df.item_type.PLANT,
plant_id = df.item_type.PLANT,
poetic_form_id = df.item_type.PLANT,
musical_form_id = df.item_type.PLANT,
dance_form_id = df.item_type.PLANT,
item_subtype = -1 ,
mattype = 419,
matindex = 172,
mat_state = 0,
active = true,
prefstring_seed = 1012772714 }, 'prefstring_seed')
end
end
Mealmaker: Makes stacks of meals at the cursor.
-- Mealmaker, make me some meals
-- From milo christiansen: http://www.bay12forums.com/smf/index.php?topic=150520.msg6208772#msg6208772
-- This is intended as a debug script to create stacks of custom meals to test food preferences
local utils=require('utils')
validArgs = utils.invert({
'help',
'verbose',
'mealType',
'stackSize',
'chefSkill',
'foodQuality'
})
local args = utils.processArgs({...}, validArgs)
verbose = false
local stackSize = 10
local mealType = 0
local chefSkill = 0
local foodQuality = 0
local helpme = [===[
mealmaker.lua
=========
This script makes meals for dwarves to eat. All ingredients are currently the same
arguments:
-help
print this help message
-vebose
enable verbose output
-stackSize
size of the resulting meal stack. Defaults to 5
-mealType
type of meal to make: 0(default): easy/biscuits, 1: fine/stew, 2: lavish/roast. Defaults to 0.
-chefSkill
Chef's cooking skill:0-15 Dabbling/Legendary. Defaults to 0
-foodQuality
The quality of the ingredients and the total meal, affects resulting value: 0-5 Normal/Masterwork. Defaults to 0.
]===]
if ( args.verbose ) then
verbose = true
end
if ( args.stackSize ) then
stackSize = tonumber(args.stackSize)
end
if ( args.mealType ) then
mealType = tonumber(args.mealType)
end
if ( args.chefSkill ) then
chefSkill = tonumber(args.chefSkill)
end
if ( args.foodQuality ) then
foodQuality = tonumber(args.foodQuality)
end
-- Handle help requests
if args.help then
print(helpme)
return
end
function mealMaker()
-- Add a new item to the world
local item = df['item_foodst']:new()
-- Cooking skill:0-15 Dabbling/Legendary
local chef_skill = chefSkill
local food_quality = foodQuality
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
-- This is the type of meal: biscuit, fine,roast
item:setSubtype(mealType)
-- Item settings
item.entity = -1
item.recipe_id = -1
item.stack_size = stackSize
item.maker = 107 -- Unit historical id
for i=0,mealType+1,1 do
-- This is all hard set
local foodmat_type = 0
local foodmat_index = 0
local foodmat_itemtype = 0
if(i == 1) then
-- Ingredient material type and index: dwarven ale
-- Dwarven Ale
foodmat_type = 420
foodmat_index = 173
foodmat_itemtype = 68
-- Plump Helmets
foodmat_type = 419
foodmat_index = 172
foodmat_itemtype = 53
else
-- Ingredient material type and index:
-- Giant Skunk Kidney
foodmat_type = 37
foodmat_index = 670
foodmat_itemtype = 47
-- Plump Helmets
foodmat_type = 419
foodmat_index = 172
foodmat_itemtype = 53
end
item.ingredients:insert('#', {
new = true,
-- Seems to always be 0.
anon_1 = 0,
-- The ingredient item type.
item_type = foodmat_itemtype,
-- Seems to always be -1.
unk_4 = -1,
-- Ingredient material type and index: Plump Helmets
mat_type = foodmat_type,
mat_index = foodmat_index,
-- The creator unit ID.
maker = 101,
-- The ingredient quality. 0-6. 5 is masterwork, 6 is artifact. Both would require other entries in other tables?
quality = food_quality,
-- The skill level of the maker.
unk_14 = chef_skill,
-- Seems to always be -1.
unk_18 = -1,
})
end
item.mat_type = 419
item.mat_index = 172
item.skill_used = chef_skill
item.quality = food_quality
item:categorize(true)
item.pos.x = df.global.cursor.x
item.pos.y = df.global.cursor.y
item.pos.z = df.global.window_z
item.flags.on_ground = true
local block = dfhack.maps.getTileBlock(item.pos)
local occupancy = block.occupancy[item.pos.x%16][item.pos.y%16]
block.items:insert('#',item.id)
occupancy.item = true
end
mealMaker()
Lessons?
- Separate prepared meals and raw ingredient piles by at least one Y block.
- Units only get quality modifiers for foods they have a preference for. Meals have a high quality modifier (value), raw ingredients don't. Meals will make your units happier.
- Maybe give your units a preference for a booze type and make your meals from that.
- Maybe give your units a preference for a high value material that isn't edible in the raw form (milk, flour, sugar).
Edits:
- Added max Z testing results