while i was working on my project the idea for this little script came to my mind. i noticed that reagents used in reactions can have many more flags and settings than you can control with raw files. so what this script does is it allows you to use additional tokens in reaction raws, one for each flag that a reagent can have.
for example [MELT_DESIGNATED] and [ALLOW_MELT_DUMP]. or [MILK] and [PROCESSABLE_TO_VIAL]. possibly even [DEEP_MATERIAL], [EXTRACT_BEARING_FISH] or [MURDERED]. there are many others.
i have little to no idea what most of the flags will do, if anything. some sound more useful than others, some might even do something that cant be done right now. what exactly will happen if you use a [MILKABLE][TAMEABLE_VERMIN] reagent in your reaction is for you to find out!
how does it work: you need to run this script before loading a world, that means, at start of game. it will then whenever a world is loaded read all the reaction raws from the save folder. whenever it finds a token that is in the list, it sets the corresponding flag in the reaction struct in game.
i didnt figure out yet how auto-loading script works maybe you can tell me. presumably you have to write the name of the script into your dfhack-init?
disclaimer: i didnt test this beyond making sure it does what it does. i am in no way responsible for any financial, physical, mental and/or spiritual damages that result from the usage of this script. it is also very much an alpha version.
flags that dont have a name but just a number need to be tokenized as [number:number], where the first number stands for number of the bitfield the flag belongs to (0 for the field thats just named "flags"). the second number is the flag obviously. al flags are boolean values.
to write a reaction from memory into a file, use <scriptname>.reactionToRaw(), which will return a table of strings which can then be written into a file in the order provided. takes the reaction struct as its only argument. this will also write non-standard tokens.
-- reads special tokens from reaction raws and patches these reactions in df memory. can also translate reactions from df memory into raw-file text, output as a string array
-- proposed usage is to make a static link to dfhack.script_environment('thisfilename').reactionToRaw
-- in your script where you intend to use that function, if you do.
-- otherwise run the script from dfhack.init
--[[
this is just an overview over actual flag names in the struct
the values will be taken from the second table below this comment section, everything that is a number key in this first table will have special format
everything thats "false", "self" or a string will be same as the name of the field in lower case,
with the only exception being the first three flags which are upper case in the struct.
tokens that are "false" in the REAGENT_FLAGS table are flags that already exist as raw tokens, so they get written into
files, but not read/patched by the script.
REAGENT_FLAGS_INVERSE = {
_toToken = function(tab, idx)
local flag = REAGENT_FLAGS[tab][idx]
return flag and ('['..(flag == 'self' and string.upper(flag) or flag)..']') or ''
end,
flags = {
PRESERVE_REAGENT = 'self',
IN_CONTAINER = 'self',
DOES_NOT_DETERMINE_PRODUCT_AMOUNT = 'self',
['3'] = false,
['4'] = false,
['5'] = false,
['6'] = false,
['7'] = false,
['8'] = false,
['9'] = false,
['10'] = false,
['11'] = false,
['12'] = false,
['13'] = false,
['14'] = false,
['15'] = false,
['16'] = false,
['17'] = false,
['18'] = false,
['19'] = false,
['20'] = false,
['21'] = false,
['22'] = false,
['23'] = false,
['24'] = false,
['25'] = false,
['26'] = false,
['27'] = false,
['28'] = false,
['29'] = false,
['30'] = false,
['31'] = false,
},
flags1 = {
improvable = false, -- maybe the same as 'NOT_IMPROVED' ?
butcherable = false,
millable = false,
allow_buryable = false,
unrotten = 'self',
undisturbed = 'WEB_ONLY',
collected = 'NOT_WEB',
sharpenable = 'NO_EDGE_ALLOWED',
murdered = false,
distillable = false, --? material has drink tag
empty = 'self',
processable = false, --? material has thread tag?
bag = 'self',
cookable = false, -- mat is edible_cooked
extract_bearing_plant = false, -- mat has EXTRACT:
extract_bearing_fish = false, -- "
extract_bearing_vermin = false, -- "
processable_to_vial = false, -- "
processable_to_bag = false, -- "
processable_to_barrel = false, -- "
solid = false, -- not pressed?
tameable_vermin = false, -- ?
nearby = false, -- ?
sand_bearing = false,
glass = 'GLASS_MATERIAL',
milk = false, -- ?
milkable = false, -- ?
finished_goods = false,
ammo = false,
furniture = false,
not_bin = false,
lye_bearing = 'CONTAINS_LYE',
},
flags2 = {
dye = false,
dyeable = false,
dyed = false,
sewn_imageless = false, --?
glass_making = false,
screw = false, -- has press mat?
building_material = 'BUILDMAT',
fire_safe = 'FIRE_BUILD_SAFE',
magma_safe = 'MAGMA_BUILD_SAFE',
deep_material = false,
melt_designated = false,
non_economic = 'WORTHLESS_STONE_ONLY',
allow_melt_dump = false, -- this is interesting
allow_artifact = 'CAN_USE_ARTIFACT',
plant = 'ANY_PLANT_MATERIAL',
silk = 'ANY_SILK_MATERIAL',
leather = 'ANY_LEATHER_MATERIAL',
bone = 'ANY_BONE_MATERIAL',
shell = 'ANY_SHELL_MATERIAL',
totemable = false,
horn = 'ANY_HORN_MATERIAL',
pearl = 'ANY_PEARL_MATERIAL',
plaster_containing = false,
['23'] = false,
soap = 'ANY_SOAP_MATERIAL',
body_part = 'USE_BODY_COMPONENT',
ivory_tooth = 'ANY_TOOTH_MATERIAL',
lye_milk_free = 'NOT_CONTAIN_BARREL_ITEM',
blunt = false,
unengraved = 'NOT_ENGRAVED',
hair_wool = 'ANY_STRAND_TISSUE',
yarn = 'ANY_YARN_MATERIAL',
},
flags3 = {
unimproved = 'NOT_IMPROVED',
any_raw_material = false,
non_absorbent = 'DOES_NOT_ABSORB',
non_pressed = 'NOT_PRESSED',
allow_liquid_powder = false,
any_craft = false,
hard = 'HARD_ITEM_MATERIAL',
food_storage = 'FOOD_STORAGE_CONTAINER',
['8'] = false,
['9'] = false,
['10'] = false,
['11'] = false,
['12'] = false,
['13'] = false,
['14'] = false,
['15'] = false,
['16'] = false,
['17'] = false,
['18'] = false,
['19'] = false,
['20'] = false,
['21'] = false,
['22'] = false,
['23'] = false,
['24'] = false,
['25'] = false,
['26'] = false,
['27'] = false,
['28'] = false,
['29'] = false,
['30'] = false,
['31'] = false,
},
flags4 = {
},
flags5 = {
},
}--]]
REAGENT_FLAGS = {
flags = {
DOES_NOT_DETERMINE_PRODUCT_AMOUNT = false,
PRESERVE_REAGENT = false,
IN_CONTAINER = false, -- this is set by the [CONTAINS:x] token, for the contained reagent
['0:3'] = 3,
['0:4'] = 4,
['0:5'] = 5,
['0:6'] = 6,
['0:7'] = 7,
['0:8'] = 8,
['0:9'] = 9,
['0:10'] = 10,
['0:11'] = 11,
['0:12'] = 12,
['0:13'] = 13,
['0:14'] = 14,
['0:15'] = 15,
['0:16'] = 16,
['0:17'] = 17,
['0:18'] = 18,
['0:19'] = 19,
['0:20'] = 20,
['0:21'] = 21,
['0:22'] = 22,
['0:23'] = 23,
['0:24'] = 24,
['0:25'] = 25,
['0:26'] = 26,
['0:27'] = 27,
['0:28'] = 28,
['0:29'] = 29,
['0:30'] = 30,
['0:31'] = 31,
},
flags1 = {
FURNITURE = 'furniture',
IMPROVABLE = 'improvable', -- not used by custom glazing reactions, uses [NOT_IMPROVED] instead
PROCESSABLE = 'processable',
SAND_BEARING = 'sand_bearing',
NOT_WEB = false,
NOT_BIN = 'not_bin',
EXTRACT_BEARING_FISH = 'extract_bearing_fish',
EXTRACT_BEARING_VERMIN = 'extract_bearing_vermin',
AMMO = 'ammo',
EXTRACT_BEARING_PLANT = 'extract_bearing_plant',
NO_EDGE_ALLOWED = false,
UNROTTEN = false,
NEARBY = 'nearby',
ALLOW_BURYABLE = 'allow_buryable',
FINISHED_GOODS = 'finished_goods',
MILKABLE = 'milkable',
PROCESSABLE_TO_BARREL = 'processable_to_barrel',
BUTCHERABLE = 'butcherable',
TAMEABLE_VERMIN = 'tameable_vermin',
BAG = false,
WEB_ONLY = false,
MURDERED = 'murdered',
PROCESSABLE_TO_VIAL = 'processable_to_vial',
SOLID = 'solid',
MILK = 'milk',
PROCESSABLE_TO_BAG = 'processable_to_bag',
COOKABLE = 'cookable',
DISTILLABLE = 'distillable',
CONTAINS_LYE = false,
MILLABLE = 'millable',
EMPTY = false,
GLASS_MATERIAL = false,
},
flags2 = {
ANY_SHELL_MATERIAL = false,
ANY_YARN_MATERIAL = false,
USE_BODY_COMPONENT = false,
SEWN_IMAGELESS = 'sewn_imageless',
DYE = 'dye',
ANY_PLANT_MATERIAL = false,
ANY_STRAND_TISSUE = false,
DEEP_MATERIAL = 'deep_material',
BLUNT = 'blunt', -- this is a non-sharp rock to be used in knapping. same as [NO_EDGE_ALLOWED]
NOT_ENGRAVED = false,
GLASS_MAKING = 'glass_making',
ANY_SILK_MATERIAL = false,
TOTEMABLE = 'totemable',
NOT_CONTAIN_BARREL_ITEM = false,
MAGMA_BUILD_SAFE = false,
ANY_TOOTH_MATERIAL = false,
CAN_USE_ARTIFACT = false,
PLASTER_CONTAINING = 'plaster_containing',
ANY_PEARL_MATERIAL = false,
DYED = 'dyed',
WORTHLESS_STONE_ONLY = false,
FIRE_BUILD_SAFE = false,
SCREW = 'screw',
DYEABLE = 'dyeable',
ANY_HORN_MATERIAL = false,
ANY_BONE_MATERIAL = false,
ANY_LEATHER_MATERIAL = false,
ALLOW_MELT_DUMP = 'allow_melt_dump',
ANY_SOAP_MATERIAL = false,
BUILDMAT = false,
MELT_DESIGNATED = 'melt_designated',
['2:23'] = 23,
},
flags3 = {
ANY_RAW_MATERIAL = 'any_raw_material',
ANY_CRAFT = 'any_craft',
NOT_PRESSED = false,
FOOD_STORAGE_CONTAINER = false,
ALLOW_LIQUID_POWDER = 'allow_liquid_powder',
NOT_IMPROVED = false,
HARD_ITEM_MATERIAL = false,
DOES_NOT_ABSORB = false,
['3:8'] = 8,
['3:9'] = 9,
['3:10'] = 10,
['3:11'] = 11,
['3:12'] = 12,
['3:13'] = 13,
['3:14'] = 14,
['3:15'] = 15,
['3:16'] = 16,
['3:17'] = 17,
['3:18'] = 18,
['3:19'] = 19,
['3:20'] = 20,
['3:21'] = 21,
['3:22'] = 22,
['3:23'] = 23,
['3:24'] = 24,
['3:25'] = 25,
['3:26'] = 26,
['3:27'] = 27,
['3:28'] = 28,
['3:29'] = 29,
['3:30'] = 30,
['3:31'] = 31,
},
flags4 = {
},
flags5 = {
},
}
----------------------------------------------------------------------------------------------------------------
------------------------------------------ script init
----------------------------------------------------------------------------------------------------------------
REAGENT_FLAGS_INVERSE = {}
do
local uppercaseFlags = {
PRESERVE_REAGENT = true,
IN_CONTAINER = true,
DOES_NOT_DETERMINE_PRODUCT_AMOUNT = true,
}
for fieldname, field in pairs(REAGENT_FLAGS) do
REAGENT_FLAGS_INVERSE[fieldname] = {}
for k, v in pairs(field) do
if v == false then
if uppercaseFlags[k] then
REAGENT_FLAGS_INVERSE[fieldname][k] = k
else
REAGENT_FLAGS_INVERSE[fieldname][string.lower(k)] = k
end
else
REAGENT_FLAGS_INVERSE[fieldname][v] = k
end
end
end
end
----------------------------------------------------------------------------------------------------------------
------------------------------------------ local functions
----------------------------------------------------------------------------------------------------------------
local function flagToToken(tab, idx)
local flag = REAGENT_FLAGS_INVERSE[tab][idx]
return flag and '['..flag..']' or ''
end
local function tokenToFlag(token)
local token = string.sub(token, 2, #token - 1)
for fieldname, field in pairs(REAGENT_FLAGS) do
if field[token] then return field[token], fieldname end
end
end
local function compareFlags(reactions)
for id, data in pairs(reactions) do
local tokenlist = data.tokenlist
local reagent
reactions[id].reagents = {}
local match = false
for _, token in pairs(tokenlist) do
if string.find(token, '%[REAGENT:') then
reagent = string.sub(token, 10, string.find(token, ':', 10) - 1)
reactions[id].reagents[reagent] = {}
elseif string.find(token, '%[PRODUCT') then
reagent = nil
elseif reagent then
local flag, field = tokenToFlag(token)
if flag then
--print("found match: "..flag.." for reagent "..reagent.." in reaction: "..id)
match = true
table.insert(reactions[id].reagents[reagent], {field = field, flag = flag})
end
end
end
if not match then reactions[id] = nil end
end
return reactions
end
local function getReactionDefs(path, rawfiles)
local reactions = {}
for k, _ in pairs(rawfiles) do
local file = io.open(path..k, 'r')
local str = file:read("*all")
local match1, match2 = string.find(str, '%[REACTION:')
--print (match1.." "..match2)
-- str = string.sub(str, match1) -- cull the object token etc
while (match1) do
-- local sub = string.sub(str, 1, match - 1)
str = string.sub(str, match2 + 1)
match1, match2 = string.find(str, '%[REACTION:')
if match1 then
local sub = string.sub(str, 1, match1 - 1)
local code = string.sub(sub, 1, string.find(sub, '%]') - 1)
--print ("\n"..code)
local id
for i, v in pairs(df.global.world.raws.reactions) do
if v.code == code then id = i end
end
if id then
reactions[id] = {tokenlist = {}}
local token1, token2 = string.find(sub, '%[.-%]')
while (token1) do
table.insert(reactions[id].tokenlist, string.sub(sub, token1, token2))
sub = string.sub(sub, token2 + 1)
token1, token2 = string.find(sub, '%[.-%]')
end
else
print("reaction "..code.." is not registered to the world")
end
end
end
file:close()
end
return reactions
end
local function parseRawFolder()
if not dfhack.getSavePath() then
return nil
end
local folder = dfhack.getSavePath()..'/raw/objects/'
local rawList = dfhack.filesystem.listdir(folder)
local reactionRaws = {}
for _, filename in pairs(rawList) do
if string.find(filename, 'reaction') then
reactionRaws[filename] = filename
end
end
return getReactionDefs(folder, reactionRaws)
end
local function printReaction(r)
for k, v in pairs(r) do
print(tostring(k).." - "..tostring(v))
if v and type(v) ~= 'number' and type(v) ~= 'boolean' and type(v) ~= 'string' and type(v) ~= 'function' then
for _k, _v in pairs(v) do
print(" -> "..tostring(_k).." - "..tostring(_v))
if _v and type(_v) ~= 'number' and type(_v) ~= 'boolean' and type(_v) ~= 'string' and type(_v) ~= 'function' then
for __k, __v in pairs(_v) do
print(" + "..tostring(__k).." - "..tostring(__v))
if __v and type(__v) ~= 'number' and type(__v) ~= 'boolean' and type(__v) ~= 'string' and type(__v) ~= 'function' then
for ___k, ___v in pairs(__v) do
print(" - "..tostring(___k).." - "..tostring(___v))
end
end
end
end
end
end
end
end
----------------------------------------------------------------------------------------------------------------
------------------------------------------ external
----------------------------------------------------------------------------------------------------------------
-- turn a reaction struct from df memory into a string representation in the format of a reaction raw file entry
-- returns a sorted table of strings
-- will also write special tokens
function reactionToRaw(reaction)
--local file = fileHandle or getFileHandle()
--if not file then return nil
local stack = {}
stack[1] = '[REACTION:'..reaction.code..']\n'
stack[2] = '[NAME:'..reaction.name..']\n'
-- this could be epic suck because item names need to be translated
for k, v in pairs(reaction.reagents) do
local info = dfhack.matinfo.getToken(v.mat_type, v.mat_index)
local matstr = info and (string.find(info, '%:') and info or info..':NO_SUBTYPE') or 'NONE:NONE'
local itemstr
if v.item_type > -1 then
if v.item_subtype > -1 then
itemstr = df.item_type[v.item_type]..':'..dfhack.items.getSubtypeDef(v.item_type, v.item_subtype).id
else
itemstr = df.item_type[v.item_type]..':NONE'
end
else
itemstr = 'NONE:NONE'
end
local str = '[REAGENT:'..(v.code or k)..':'..v.quantity..':'..itemstr..':'..matstr..']'
if v.reaction_class and v.reaction_class ~= '' then
str = str..'[REACTION_CLASS:'..v.reaction_class..']'
end
-- [HAS_ITEM_REACTION_PRODUCT:x] does not exist in df memory, but it is functionally identical to material reaction product
-- so we just gonna write the later
if v.has_material_reaction_product and v.has_material_reaction_product ~= '' then
str = str..'[HAS_MATERIAL_REACTION_PRODUCT:'..v.has_material_reaction_product..']'
end
if v.min_dimension and v.min_dimension > 0 then
str = str ..'[MIN_DIMENSION:'..v.min_dimension..']'
end
if v.contains then
for _, _v in pairs(v.contains) do
local s = reaction.reagents[_v].code
str = str..'[CONTAINS:'..s..']'
end
end
if v.has_tool_use and v.has_tool_use > -1 then
str = str..'[HAS_TOOL_USE:'..df.tool_uses[v.has_tool_use]..']'
end
if v.metal_ore and v.metal_ore > -1 then
local s = df.global.world.raws.inorganics[v.metal_ore].code
str = str.. '[METAL_ORE:'..s..']'
end
-- translate flags into reaction tokens. this writes unknown flags too (flags dont normally exist as raw tokens)
for fieldname, field in pairs(v) do
if string.find(fieldname, 'flags') and field ~= 0 then
print(fieldname, field)
for flag, isSet in pairs(field) do
if isSet then str = str..flagToToken(fieldname, flag) end
end
end
end
table.insert(stack, str..'\n')
end
for k, v in pairs(reaction.products) do
local str
if v._type == df.reaction_product_itemst then -- normal product
-- need to figure out first if reaction product tags are used
str = '[PRODUCT:'..v.probability..':'..v.count..':'
if v.get_material and v.get_material.reagent_code and v.get_material.reagent_code ~= '' then
if v.flags[6] then -- GET_ITEM_DATA_FROM_REAGENT
-- tags used here are identical with those found in v.material_str:
-- v.get_material.reagent_code := v.material_str[1], v.get_material.product_code := v.material_str[2].
-- v.material_str[0] seems to be always empty
-- GET_ITEM_DATA_FROM_REAGENT equals item_str[0]
local pcode = v.get_material.product_code ~= '' and v.get_material.product_code or 'NONE' -- not sure if can happen
str = str..'GET_ITEM_DATA_FROM_REAGENT:'..v.get_material.reagent_code..':'..pcode..']'
else -- GET_MATERIAL_FROM_REAGENT
local pcode = v.flags.GET_MATERIAL_PRODUCT and v.get_material.product_code or 'NONE'
local itemstr = v.item_type > -1 and df.item_type[v.item_type]..':'..
(v.item_subtype > -1 and dfhack.items.getSubtypeDef(v.item_type, v.item_subtype).id or 'NO_SUBTYPE') or 'NONE:NONE'
str = str..itemstr..':'..'GET_MATERIAL_FROM_REAGENT:'..v.get_material.reagent_code..':'..pcode..']'
end
else -- normal product
local info = dfhack.matinfo.getToken(v.mat_type, v.mat_index)
local matstr = info and (string.find(info, '%:') and info or info..':NO_SUBTYPE') or 'NONE:NONE'
local itemstr = v.item_type > -1 and df.item_type[v.item_type]..':'..
(v.item_subtype > -1 and dfhack.items.getSubtypeDef(v.item_type, v.item_subtype).id or 'NO_SUBTYPE') or 'NONE:NONE'
str = str..itemstr..':'..matstr..']'
end
if v.product_to_container and v.product_to_container ~= '' then
str = str..'[PRODUCT_TO_CONTAINER:'..v.product_to_container..']'
end
if v.product_dimension and v.product_dimension > 0 then
str = str..'[PRODUCT_DIMENSION:'..v.product_dimension..']'
end
--flags
if v.flags.FORCE_EDGE then str = str..'[FORCE_EDGE]' end
if v.flags.PASTE then str = str..'[PRODUCT_PASTE]' end
if v.flags.PRESSED then str = str..'[PRODUCT_PRESSED]' end
if v.flags.CRAFTS then str = str..'[CRAFTS]' end-- this is set when item type CRAFTS is used
-- if v.flags[6] then str = str..'[PRODUCT_FLAG:6]' end -- set by get_item_data_from_reagent
if v.flags[7] then str = str..'[PRODUCT_FLAG:7]' end -- appears to be unused
else -- product is an item improvement (which has different struct)
str = str..'[IMPROVEMENT:'..v.probability..':'..v.target_reagent..':'
local imptype = v.improvement_type
local impstr
-- currently only known improvement types will be written to disc. testing needs to be done what other types would be acceptable
-- to a reaction.
-- unknownImprovementTypes = {ART_IMAGE, ITEMSPECIFIC, THREAD, CLOTH, SEWN_IMAGE, ILLUSTRATION}
local knownImprovementTypes = {['2'] = 'RINGS_HANGING', ['3'] = 'BANDS', ['4'] = 'SPIKES', ['9'] = 'PAGES',}
if imptype == 1 then
impstr = v.flags.GLAZED and 'GLAZED' or 'COVERED'
elseif knownImprovementTypes[imptype] then
impstr = knownImprovementTypes[imptype]
else
impstr = imptype
end
str = str..impstr..':'
if v.get_material and v.get_material.reagent_code and v.get_material.reagent_code ~= '' then
local pcode = v.flags.GET_MATERIAL_PRODUCT and v.get_material.product_code ~= '' or 'NONE'
str = str..'GET_MATERIAL_FROM_REAGENT:'..v.get_material.reagent_code..':'..pcode..']'
else -- not sure if can happen
local info = dfhack.matinfo.getToken(v.mat_type, v.mat_index)
local matstr = info and (string.find(info, '%:') and info or info..':NO_SUBTYPE') or 'NONE:NONE'
end
--flags
for i = 3, 7 do
if v.flags[i] then -- hmmm
str = str..'[IMP_FLAG:'..i..']'
end
end
if v.anon_1 and v.anon_1 ~= '' then
str = str..'[ANON_1:'..v.anon_1..']'
end
end
table.insert(stack, str..'\n')
end
local flags = ''
if reaction.flags.FUEL then flags = flags..'[FUEL]' end
if reaction.flags.AUTOMATIC then flags = flags..'[AUTOMATIC]' end
if reaction.flags.ADVENTURE_MODE_ENABLED then flags = flags..'[ADVENTURE_MODE_ENABLED]' end
for i = 3, 7 do
if flags[i] then
flags = flags..'[REACTION_FLAG:'..i..']'
end
end
if flags ~= '' then
table.insert(stack, flags..'\n')
end
table.insert(stack, '[SKILL:'..df.job_skill[reaction.skill]..']\n')
-- building
table.insert(stack,"\n")
return stack
end
----------------------------------------------------------------------------------------------------------------
--------------------------------------------- world init
----------------------------------------------------------------------------------------------------------------
local function patchReactions()
local reactions = parseRawFolder()
if not reactions then
print("unable to read raw files. world not saved yet?")
return
end
reactions = compareFlags(reactions)
local dfReactions = df.global.world.raws.reactions
print("patching reactions....")
for id, data in pairs(reactions) do
reaction = dfReactions[id]
print("patching reaction: "..reaction.code.." - "..reaction.name)
--print("\n\n-----------------------------------------------\n\n**** reaction "..reaction.code.." before ****")
--printReaction(reaction)
for name, flags in pairs(data.reagents) do
local dfReagent
for _, v in pairs(reaction.reagents) do
if v.code == name then dfReagent = v end
end
if not dfReagent then
print("error: found no matching entry for reagent "..name) -- can happen?
else
for _, v in pairs(flags) do
dfReagent[v.field][v.flag] = true
end
end
end
--print("\n\n**** reaction "..reaction.code.." after ****")
--printReaction(reaction)
end
print("...done. have a nice day! :-)")
end
dfhack.onStateChange.focus = function(state)
if state == 0 then
patchReactions()
end
end