--
http://www.bay12forums.com/smf/index.php?topic=172489.0 Food Preferences 6mod
--
http://www.bay12forums.com/smf/index.php?topic=172335.0-- Bumblebee honey is impossible to obtain in vanilla, and thus does bumblebee mead remain
-- out of reach. Feather trees are bugged so you can't get any eggs, and thus not get at
-- the egg white or yolk (despite the trade depot being contaminated by feather tree egg
-- yolk after the elves have visited).
--
LOG_PREF=true --change to false to disable logging
local cits_ltrs,cit_prefs_total,tmp_str=0,0,""
function get_master_race() --master race and master civ
local master_race,master_civ,entity_k=nil,nil,nil
if df.global.gamemode==0 then
if #df.global.world.world_data.active_site>0 then
if #df.global.world.world_data.active_site[0].entity_links>0 then
master_civ=df.global.world.world_data.active_site[0].entity_links[0].entity_id
-- master_race=df.global.world.entities.all[master_civ].race --OK for 0.44 worlds, but may fail for older ones
for k,v in ipairs(df.global.world.entities.all) do --workaround for older worlds
if v.id == master_civ then master_race=v.race entity_k=k end
end
end
else
for k,v in ipairs(df.global.world.raws.creatures.all) do
if string.find(v.creature_id,"^DWARF$") then master_race=k end
end
end
end
return master_race,master_civ,entity_k
end
local _,master_civ,civ_k=get_master_race()
function get_citizen_status(unit) --whether the person is a citizen (1), long-term resident (2), visitor (3) (only alive) or else (nil); 1+2 = Citizen tab
if not unit.flags1.inactive then
if dfhack.units.isCitizen(unit) then return 1
elseif not unit.flags1.marauder and unit.flags2.visitor or unit.flags1.diplomat then return 3
elseif unit.civ_id==master_civ then
if not unit.flags1.marauder and not unit.flags1.tame and not unit.flags2.visitor and not unit.flags1.merchant and not unit.flags1.forest and not unit.flags1.diplomat then return 2
end
else return nil
end
else return nil
end
end
function get_date (year,ticks)
local f_Year,f_Month,f_Day,f_tmp=nil,0,0,0
if not year then return nil end
if year and not ticks then
return year,0,0
end
ticks=tonumber(ticks)
if ticks < 0 then ticks = 0 end
if ticks > 403200 then ticks = 403200 end
f_Year=tonumber(year)
f_Month=math.floor(ticks/33600)+1
f_tmp=(ticks/33600)-(math.floor(ticks/33600))
f_Day=math.floor(f_tmp*28)+1
return f_Year,f_Month,f_Day
end
if df.global.world.world_data then
if #df.global.world.world_data.active_site>0 then
my_fortA=dfhack.TranslateName(df.global.world.world_data.active_site[0].name,true)
my_fortB=dfhack.TranslateName(df.global.world.world_data.active_site[0].name,false)
else
my_fortA=df.game_mode[df.global.gamemode] my_fortB=df.game_type[df.global.gametype]
end
my_filename=my_fortA.."_food_preferences_"..df.global.cur_year.."_"..df.global.cur_year_tick..".txt"
my_file = assert(io.open(my_filename, "w"))
if my_file then print(string.format("Logging to %s",my_filename))
my_file:write(string.format("Food preferences of %s/%s on %s-%02d-%02d:",my_fortA,my_fortB,get_date(df.global.cur_year,df.global.cur_year_tick)))
end
end
--program proper--
local bumblebee = -1
local feather_tree = -1
for i, creature in ipairs (df.global.world.raws.creatures.all) do
if creature.creature_id == "BUMBLEBEE" then
bumblebee = i
break
end
end
for i, plant in ipairs (df.global.world.raws.plants.all) do
if plant.id == "FEATHER" then
feather_tree = i
break
end
end
local min_butcher_size = -- Data taken from
http://www.bay12forums.com/smf/index.php?topic=78108.0 {["MUSCLE"] = 1900, -- Meat in the reference
["FAT"] = 1900, -- Haven't seen any preferences for it
["TALLOW"] = 1900, -- i.e. rendered fat. Haven't seen any preferences for it, though
["SKIN"] = 1900, -- Inedible, but here for completeness
["BONE"] = 2000, -- Inedible
["INTESTINES"] = 5000,
["BRAIN"] = 10000,
["LUNG"] = 10000,
["LIVER"] = 10000,
["STOMACH"] = 10000, -- Called tripe as food
["GUT"] = 10000, -- ###Not in reference. Guessing it's the same most others.
["GIZZARD"] = 10000, -- Not in reference. Wiki claims size similar to brain and liver. Wiki DF2014:Prepared_organs does not list Gut or Gizzard.
["NERVE"] = 15000, -- ###Name not checked. Inedible anyway
["HOOF"] = 15000, -- Inedible
["HEART"] = 20000,
["PANCREAS"] = 20000, -- Called sweetbread as food
["SPLEEN"] = 20000,
["KIDNEY"] = 20000,
["HORN"] = 20000, -- Inedible
["CARTILAGE"] = 200000, -- Inedible
["HAIR"] = 200000, -- Inedible
["EYE"] = 500000,
["TOOTH"] = 25000000, -- Inedible
["CHEESE"] = 0} -- Not a butcher product, and thus no size relation, but exists as food preferences, so here for reference.
local available_plant = {}
local available_creature = {}
-----------------------------
function add (list, item)
for i, element in ipairs (list) do
if element [1] == item then
list
[2] = list [2] + 1
return
end
end
table.insert (list, {item, 1})
end
-----------------------------
function sort (list)
local temp
for i = 1, #list - 1 do
for k = i + 1, #list do
if list [k] [1] < list [1] then
temp = list
list = list [k]
list [k] = temp
end
end
end
end
-----------------------------
function preference_string (preference)
local material = dfhack.matinfo.decode (preference.mattype, preference.matindex)
if preference.matindex ~= -1 and
(material.mode == "plant" or
material.mode == "creature") then
if preference.item_type == df.item_type.DRINK or
preference.item_type == df.item_type.LIQUID_MISC then -- The state in the preferences seems locked to Solid
local unavailable = ""
if material.mode == "plant" then
if not available_plant [material.plant.index] then
unavailable = "UNAVAILABLE "
end
return unavailable .. material.material.state_name.Liquid .. " [" .. material.plant.name .. "]"
else
local impossible = ""
if preference.matindex == bumblebee then
impossible = "IMPOSSIBLE "
end
if not available_creature [material.index] then
unavailable = "UNAVAILABLE "
end
return unavailable .. impossible .. material.material.state_name.Liquid .. " [" .. material.creature.name
end
else
local butcherable = ""
local impossible = ""
local too_small = ""
local unavailable = ""
if material.mode == "creature" then
for i, str in ipairs (material.creature.raws) do
if str - == "[NOT_BUTCHERABLE]" then
butcherable = "NOT BUTCHERABLE "
break
end
end
if not available_creature [material.index] then
unavailable = "UNAVAILABLE "
end
if min_butcher_size [material.material.id] then
if min_butcher_size [material.material.id] > material.creature.adultsize then
too_small = "TOO SMALL "
end
else
tmp_str="preference_string found unexpected animal food preference (continuing): "
dfhack.printerr (tmp_str .. material.material.id) if LOG_PREF and my_file then my_file:write("\n"..tmp_str) end
end
else
if not available_plant [material.plant.index] then
unavailable = "UNAVAILABLE "
end
if material.matindex == feather_tree then
impossible = "IMPOSSIBLE "
end
end
if material.material.prefix == "" then
if material.mode == "plant" then
return unavailable .. impossible .. material.material.state_name.Solid .. " [" .. material.plant.name .. "]"
else
return unavailable .. butcherable .. too_small .. material.material.state_name.Solid .. " [" .. material.creature.name
end
else
if material.mode == "plant" then
return unavailable .. impossible .. material.material.prefix .. " " .. material.material.state_name.Solid .. " [" .. material.plant.name .. "]"
else
return unavailable .. butcherable .. too_small .. material.material.prefix .. " " .. material.material.state_name.Solid .. " [" .. material.creature.name
end
end
end
else
local unavailable = ""
if not available_creature [preference.mattype] then
unavailable = "UNAVAILABLE "
end
return unavailable .. df.global.world.raws.creatures.all [preference.mattype].name
end
end
-----------------------------
function food_needs ()
local preferences = {}
local unit_preferences = {}
local drink_demands = {}
local food_demands = {}
local unsatisfied_residents = 0
local food_unsatisified_residents = 0
for i, plant in ipairs (df.global.world.raws.plants.all) do
available_plant = false
end
for i, creature in ipairs (df.global.world.raws.creatures.all) do
available_creature = creature.flags.CASTE_COMMON_DOMESTIC or -- Some of them don't exist in the wild
creature.flags.BIOME_RIVER_TEMPERATE_FRESHWATER or -- Don't know how to check availability
creature.flags.BIOME_RIVER_TEMPERATE_BRACKISHWATER or --
creature.flags.BIOME_RIVER_TEMPERATE_SALTWATER or --
creature.flags.BIOME_RIVER_TROPICAL_FRESHWATER or --
creature.flags.BIOME_RIVER_TROPICAL_BRACKISHWATER or --
creature.flags.BIOME_RIVER_TROPICAL_SALTWATER --
end
for i, region in ipairs (df.global.world.world_data.regions) do
for k, pop in ipairs (region.population) do
if pop.type == df.world_population_type.Animal or
pop.type == df.world_population_type.Vermin or
pop.type == df.world_population_type.VerminInnumerable or
pop.type == df.world_population_type.ColonyInsect then
available_creature [pop.race] = true
elseif pop.type == df.world_population_type.Tree or
pop.type == df.world_population_type.Grass or
pop.type == df.world_population_type.Bush then
available_plant [pop.plant] = true
end
end
end
for i, region in ipairs (df.global.world.world_data.underground_regions) do
for k, pop in ipairs (region.feature_init.feature.population) do
if pop.type == df.world_population_type.Animal or
pop.type == df.world_population_type.Vermin or
pop.type == df.world_population_type.VerminInnumerable or
pop.type == df.world_population_type.ColonyInsect then
available_creature [pop.race] = true
elseif pop.type == df.world_population_type.Tree or
pop.type == df.world_population_type.Grass or
pop.type == df.world_population_type.Bush then
available_plant [pop.plant] = true
end
end
end
for i, unit in ipairs (df.global.world.units.all) do
--if (dfhack.units.isCitizen (unit) or
-- unit.flags2.resident) and
-- dfhack.units.isAlive (unit) and
-- unit.status.current_soul then
local citizen=get_citizen_status(unit)
local cit=false
if citizen==1 or citizen==2 then cit=true cits_ltrs=cits_ltrs+1 end
if cit and
unit.status.current_soul then
for k, preference in ipairs (unit.status.current_soul.preferences) do
if preference.active then
if preference.type == df.unit_preference.T_type.LikeFood then
table.insert (preferences, preference)
cit_prefs_total=cit_prefs_total+1
if not unit_preferences [unit.id] then
unit_preferences [unit.id] = {}
end
table.insert (unit_preferences [unit.id], preference)
end
end
end
end
end
for i, unit_preference in pairs (unit_preferences) do
local drink_satisfied = false
local food_satisfied
for k, preference in ipairs (unit_preference) do
local material = dfhack.matinfo.decode (preference.mattype, preference.matindex)
-- See if there's a plant "booze" being produced that satisfies this unit's needs
-- through booze cooking.
--
if preference.matindex ~= -1 and
material.mode == "plant" and
(preference.item_type == df.item_type.DRINK or
preference.item_type == df.item_type.LIQUID_MISC) then
if material.plant.flags.TREE then
for l, zone in ipairs (df.global.world.buildings.other.ACTIVITY_ZONE) do
if zone.name:len() >= material.plant.id:len() and
string.sub (zone.name:upper (), 1, material.plant.id:len()) == material.plant.id then
if drink_demands [material.plant.index] then
drink_demands [material.plant.index] = drink_demands [material.plant.index] + 1
else
drink_demands [material.plant.index] = 1
end
drink_satisfied = true
break
end
end
else
for l, plot in ipairs (df.global.world.buildings.other.FARM_PLOT) do
if plot.name == material.plant.id then -- Farm plot set up
for m, seed in ipairs (df.global.world.items.other.SEEDS) do
if seed.mat_index == material.plant.index then
if drink_demands [material.plant.index] then
drink_demands [material.plant.index] = drink_demands [material.plant.index] + 1
else
drink_demands [material.plant.index] = 1
end
drink_satisfied = true
break
end
end
if drink_satisfied then
break
end
end
end
end
end
if drink_satisfied then
break
end
end
food_satisfied = drink_satisfied -- Through booze cooking
if not food_satisfied then
-- Look for farm plots that produce edible stuff, at least
--
for k, preference in ipairs (unit_preference) do
local material = dfhack.matinfo.decode (preference.mattype, preference.matindex)
if preference.matindex ~= -1 and
material.mode == "plant" and
preference.item_type ~= df.item_type.DRINK and -- Don't really need this check, as it will fail on
preference.item_type ~= df.item_type.LIQUID_MISC then -- these cases anyway.
if material.plant.flags.TREE then
for l, zone in ipairs (df.global.world.buildings.other.ACTIVITY_ZONE) do
if zone.name:len() >= material.plant.id:len() and
string.sub (zone.name:upper (), 1, material.plant.id:len()) == material.plant.id then
if food_demands [material.plant.index] then
food_demands [material.plant.index] = food_demands [material.plant.index] + 1
else
food_demands [material.plant.index] = 1
end
food_satisfied = true
break
end
end
else
for l, plot in ipairs (df.global.world.buildings.other.FARM_PLOT) do
if plot.name == material.plant.id then -- Farm plot set up
for m, seed in ipairs (df.global.world.items.other.SEEDS) do
if seed.mat_index == material.plant.index then
if food_demands [material.plant.index] then
food_demands [material.plant.index] = food_demands [material.plant.index] + 1
else
food_demands [material.plant.index] = 1
end
food_satisfied = true
break
end
end
if food_satisfied then
break
end
end
end
end
end
if food_satisfied then
break
end
end
end
if not drink_satisfied then
unsatisfied_residents = unsatisfied_residents + 1
end
if not food_satisfied then
food_unsatisified_residents = food_unsatisified_residents + 1
end
if not food_satisfied then
tmp_str="Failed to find satisfying 'farmed' food or drink for " .. dfhack.TranslateName (df.unit.find (i).name, true)
print(tmp_str) if LOG_PREF and my_file then my_file:write("\n"..tmp_str) end
for k, preference in ipairs (unit_preference) do
tmp_str=" " .. preference_string (preference)
print(tmp_str) if LOG_PREF and my_file then my_file:write("\n"..tmp_str) end
end
elseif not drink_satisfied then
tmp_str="Failed to find satisfying 'farmed' drink for " .. dfhack.TranslateName (df.unit.find (i).name, true)
print(tmp_str) if LOG_PREF and my_file then my_file:write("\n"..tmp_str) end
for k, preference in ipairs (unit_preference) do
local material = dfhack.matinfo.decode (preference.mattype, preference.matindex)
if preference.matindex ~= -1 and
material.mode == "plant" and
(preference.item_type == df.item_type.DRINK or
preference.item_type == df.item_type.LIQUID_MISC) then
tmp_str=" " .. preference_string (preference)
print(tmp_str) if LOG_PREF and my_file then my_file:write("\n"..tmp_str) end
end
end
end
end
tmp_str=string.format("Number of drink unsatisfied residents: %s, food unsatisfied residents: %s",unsatisfied_residents,food_unsatisified_residents)
print(tmp_str) if LOG_PREF and my_file then my_file:write("\n"..tmp_str) end
local count = 0
tmp_str="Drink demand satisfying crops:"
print(tmp_str) if LOG_PREF and my_file then my_file:write("\n"..tmp_str) end
for i, drink in pairs (drink_demands) do
count = count + 1
tmp_str=string.format("%s %s",df.global.world.raws.plants.all .name, drink)
print(tmp_str) if LOG_PREF and my_file then my_file:write("\n"..tmp_str) end
end
tmp_str=tostring(count)
print(tmp_str) if LOG_PREF and my_file then my_file:write("\n"..tmp_str) end
count = 0
tmp_str="Food demand satisfying crops:"
print(tmp_str) if LOG_PREF and my_file then my_file:write("\n"..tmp_str) end
for i, food in pairs (food_demands) do
count = count + 1
tmp_str=string.format("%s %s",df.global.world.raws.plants.all .name, food)
print(tmp_str) if LOG_PREF and my_file then my_file:write("\n"..tmp_str) end
end
tmp_str=tostring(count)
print(tmp_str) if LOG_PREF and my_file then my_file:write("\n"..tmp_str) end
local drink_list = {}
local food_list = {}
for i, preference in ipairs (preferences) do
local material = dfhack.matinfo.decode (preference.mattype, preference.matindex)
if preference.matindex ~= -1 and
(material.mode == "plant" or
material.mode == "creature") then
if preference.item_type == df.item_type.DRINK or
preference.item_type == df.item_type.LIQUID_MISC then -- The state in the preferences seems locked to Solid
add (drink_list, preference_string (preference))
else
add (food_list, preference_string (preference))
end
else
add (food_list, preference_string (preference))
end
end
sort (drink_list)
sort (food_list)
dfhack.color (COLOR_YELLOW)
tmp_str="Drink preferences found:"
print(tmp_str) if LOG_PREF and my_file then my_file:write("\n"..tmp_str) end
dfhack.color (COLOR_RESET)
for i, drink in ipairs (drink_list) do
tmp_str=string.format("%s %s",drink [1], drink [2])
print(tmp_str) if LOG_PREF and my_file then my_file:write("\n"..tmp_str) end
end
print() if LOG_PREF and my_file then my_file:write("\n") end
dfhack.color (COLOR_YELLOW)
tmp_str="Food preferences found:"
print(tmp_str) if LOG_PREF and my_file then my_file:write("\n"..tmp_str) end
dfhack.color (COLOR_RESET)
for i, food in ipairs (food_list) do
tmp_str=string.format("%s %s",food [1], food [2])
print(tmp_str) if LOG_PREF and my_file then my_file:write("\n"..tmp_str) end
end
end
food_needs ()
tmp_str=string.format("\nCitizens and long-term-residents: %s, total preferences=%s, AVG=%.4f",cits_ltrs,cit_prefs_total,cit_prefs_total/cits_ltrs)
print(tmp_str) if LOG_PREF and my_file then my_file:write("\n"..tmp_str) end
if my_file then my_file:close() end