Implement adding workflow constraints from gui/workflow.

develop
Alexander Gavrilov 2012-10-24 21:49:30 +04:00
parent 616c57257d
commit 59ec9b304e
5 changed files with 384 additions and 26 deletions

@ -283,6 +283,16 @@ function clone_with_default(obj,default,force)
return rv
end
function parse_bitfield_int(value, type_ref)
local res = {}
for i,v in ipairs(type_ref) do
if bit32.extract(value, i) ~= 0 then
res[v] = true
end
end
return res
end
-- Sort a vector or lua table
function sort_vector(vector,field,cmp)
local fcmp = compare_field(field,cmp)
@ -304,16 +314,26 @@ end
-- Linear search
function linear_index(vector,obj)
function linear_index(vector,key,field)
local min,max
if df.isvalid(vector) then
min,max = 0,#vector-1
else
min,max = 1,#vector
end
if field then
for i=min,max do
local obj = vector[i]
if obj[field] == key then
return i, obj
end
end
else
for i=min,max do
if vector[i] == obj then
return i
local obj = vector[i]
if obj == key then
return i, obj
end
end
end
return nil

@ -312,7 +312,7 @@ std::string MaterialInfo::getToken()
else if (index == 1)
return "COAL:CHARCOAL";
}
return material->id;
return material->id + ":NONE";
case Inorganic:
return "INORGANIC:" + inorganic->id;
case Creature:

@ -1,5 +1,7 @@
local _ENV = mkmodule('plugins.workflow')
local utils = require 'utils'
--[[
Native functions:
@ -8,7 +10,266 @@ local _ENV = mkmodule('plugins.workflow')
* setEnabled(enable)
* listConstraints([job]) -> {...}
* setConstraint(token, by_count, goal[, gap]) -> {...}
* deleteConstraint(token) -> true/false
--]]
local reaction_id_cache = nil
if dfhack.is_core_context then
dfhack.onStateChange[_ENV] = function(code)
if code == SC_MAP_LOADED then
reaction_id_cache = nil
end
end
end
local function get_reaction(name)
if not reaction_id_cache then
reaction_id_cache = {}
for i,v in ipairs(df.global.world.raws.reactions) do
reaction_id_cache[v.code] = i
end
end
local id = reaction_id_cache[name] or -1
return id, df.reaction.find(id)
end
local job_outputs = {}
function job_outputs.CustomReaction(callback, job)
local rid, r = get_reaction(job.reaction_name)
for i,prod in ipairs(r.products) do
if df.reaction_product_itemst:is_instance(prod) then
local mat_type, mat_index = prod.mat_type, prod.mat_index
local mat_mask
local get_mat_prod = prod.flags.GET_MATERIAL_PRODUCT
if get_mat_prod or prod.flags.GET_MATERIAL_SAME then
local reagent_code = prod.get_material.reagent_code
local reagent_idx, src = utils.linear_index(r.reagents, reagent_code, 'code')
if not reagent_idx then goto continue end
local item_idx, jitem = utils.linear_index(job.job_items, reagent_idx, 'reagent_index')
if jitem then
mat_type, mat_index = jitem.mat_type, jitem.mat_index
else
if not df.reaction_reagent_itemst:is_instance(src) then goto continue end
mat_type, mat_index = src.mat_type, src.mat_index
end
if get_mat_prod then
local mat = dfhack.matinfo.decode(mat_type, mat_index)
mat_type, mat_index = -1, -1
if mat then
local p_code = prod.get_material.product_code
local rp = mat.material.reaction_product
local idx = utils.linear_index(rp.id, p_code)
if not idx then
goto continue
end
mat_type, mat_index = rp.material.mat_type[idx], rp.material.mat_index[idx]
else
if code == "SOAP_MAT" then
mat_mask = { soap = true }
end
end
end
end
callback{
is_craft = prod.flags.CRAFTS,
item_type = prod.item_type, item_subtype = prod.item_subtype,
mat_type = mat_type, mat_index = mat_index, mat_mask = mat_mask
}
end
::continue::
end
end
local function guess_job_material(job)
if job.job_type == df.job_type.PrepareMeal then
return -1, -1, nil
end
local mat_type, mat_index = job.mat_type, job.mat_index
local mask_whole = job.material_category.whole
local mat_mask
local jmat = df.job_type.attrs[job.job_type].material
if jmat then
mat_type, mat_index = df.builtin_mats[jmat] or -1, -1
if mat_type < 0 and df.job_material_category[jmat] then
mat_mask = { [jmat] = true }
end
end
if not mat_mask and mask_whole ~= 0 then
mat_mask = utils.parse_bitfield_int(mask_whole, df.job_material_category)
if mat_mask.wood2 then
mat_mask.wood = true
mat_mask.wood2 = nil
end
end
if mat_type < 0 and #job.job_items > 0 then
local item0 = job.job_items[0]
if #job.job_items == 1 or item0.item_type == df.item_type.PLANT then
mat_type, mat_index = item0.mat_type, item0.mat_index
if item0.item_type == df.item_type.WOOD then
mat_mask = mat_mask or {}
mat_mask.wood = true
end
end
end
return mat_type, mat_index, mat_mask
end
function default_output(callback, job, mat_type, mat_index, mat_mask)
local itype = df.job_type.attrs[job.job_type].item
if itype >= 0 then
local subtype = nil
if df.item_type.attrs[itype].is_rawable then
subtype = job.item_subtype
end
callback{
item_type = itype, item_subtype = subtype,
mat_type = mat_type, mat_index = mat_index, mat_mask = mat_mask
}
end
end
function job_outputs.SmeltOre(callback, job)
local mat = dfhack.matinfo.decode(job.job_items[0])
if mat and mat.inorganic then
for i,v in ipairs(mat.inorganic.metal_ore.mat_index) do
callback{ item_type = df.item_type.BAR, mat_type = 0, mat_index = v }
end
else
callback{ item_type = df.item_type.BAR, mat_type = 0 }
end
end
function job_outputs.ExtractMetalStrands(callback, job)
local mat = dfhack.matinfo.decode(job.job_items[0])
if mat and mat.inorganic then
for i,v in ipairs(mat.inorganic.thread_metal.mat_index) do
callback{ item_type = df.item_type.THREAD, mat_type = 0, mat_index = v }
end
else
callback{ item_type = df.item_type.THREAD, mat_type = 0 }
end
end
function job_outputs.PrepareMeal(callback, job)
if job.mat_type ~= -1 then
for i,v in ipairs(df.global.world.raws.itemdefs.food) do
if v.level == job.mat_type then
callback{ item_type = df.item_type.FOOD, item_subtype = i }
end
end
else
callback{ item_type = df.item_type.FOOD }
end
end
function job_outputs.MakeCrafts(callback, job)
local mat_type, mat_index, mat_mask = guess_job_material(job)
callback{ is_craft = true, mat_type = mat_type, mat_index = mat_index, mat_mask = mat_mask }
end
local plant_products = {
BrewDrink = 'DRINK',
MillPlants = 'MILL',
ProcessPlants = 'THREAD',
ProcessPlantsBag = 'LEAVES',
ProcessPlantsBarrel = 'EXTRACT_BARREL',
ProcessPlantsVial = 'EXTRACT_VIAL',
ExtractFromPlants = 'EXTRACT_STILL_VIAL',
}
for job,flag in pairs(plant_products) do
local ttag = 'type_'..string.lower(flag)
local itag = 'idx_'..string.lower(flag)
job_outputs[job] = function(callback, job)
local mat_type, mat_index = -1, -1
local mat = dfhack.matinfo.decode(job.job_items[0])
if mat and mat.plant and mat.plant.flags[flag] then
mat_type = mat.plant.material_defs[ttag]
mat_index = mat.plant.material_defs[itag]
end
default_output(callback, job, mat_type, mat_index)
end
end
local function enum_job_outputs(callback, job)
local handler = job_outputs[df.job_type[job.job_type]]
if handler then
handler(callback, job)
else
default_output(callback, job, guess_job_material(job))
end
end
function listJobOutputs(job)
local res = {}
enum_job_outputs(curry(table.insert, res), job)
return res
end
function constraintToToken(cspec)
local token
if cspec.is_craft then
token = 'CRAFTS'
else
token = df.item_type[cspec.item_type] or error('invalid item type: '..cspec.item_type)
if cspec.item_subtype and cspec.item_subtype >= 0 then
local def = dfhack.items.getSubtypeDef(cspec.item_type, cspec.item_subtype)
if def then
token = token..':'..def.id
else
error('invalid subtype '..cspec.item_subtype..' of '..token)
end
end
end
local mask_part
if cspec.mat_mask then
local lst = {}
for n,v in pairs(cspec.mat_mask) do
if v then table.insert(lst,n) end
end
mask_part = table.concat(lst, ',')
end
local mat_part
if cspec.mat_type and cspec.mat_type >= 0 then
local mat = dfhack.matinfo.decode(cspec.mat_type, cspec.mat_index or -1)
if mat then
mat_part = mat:getToken()
else
error('invalid material: '..cspec.mat_type..':'..(cspec.mat_index or -1))
end
end
local qpart
if cspec.quality and cspec.quality > 0 then
qpart = df.item_quality[cspec.quality] or error('invalid quality: '..cspec.quality)
end
if mask_part or mat_part or qpart then
token = token .. '/' .. (mask_part or '')
if mat_part or qpart then
token = token .. '/' .. (mat_part or '')
if qpart then
token = token .. '/' .. (qpart or '')
end
end
end
return token
end
return _ENV

@ -323,6 +323,7 @@ public:
void init(const std::string &str)
{
config.val() = str;
config.ival(0) = 10;
config.ival(2) = 0;
}
@ -1481,7 +1482,7 @@ static int setConstraint(lua_State *L)
{
auto token = luaL_checkstring(L, 1);
bool by_count = lua_toboolean(L, 2);
int count = luaL_checkint(L, 3);
int count = luaL_optint(L, 3, -1);
int gap = luaL_optint(L, 4, -1);
color_ostream &out = *Lua::GetOutput(L);
@ -1491,7 +1492,9 @@ static int setConstraint(lua_State *L)
luaL_error(L, "invalid constraint: %s", token);
icv->setGoalByCount(by_count);
if (!lua_isnil(L, 3))
icv->setGoalCount(count);
if (!lua_isnil(L, 4))
icv->setGoalGap(gap);
process_constraints(out);

@ -125,10 +125,13 @@ end
function describe_item_type(iobj)
local itemline = 'any item'
if iobj.item_type >= 0 then
if iobj.is_craft then
itemline = 'any craft'
elseif iobj.item_type >= 0 then
itemline = df.item_type.attrs[iobj.item_type].caption or iobj.item_type
local def = dfhack.items.getSubtypeDef(iobj.item_type, iobj.item_subtype)
local count = dfhack.items.getSubtypeCount(iobj.item_type, iobj.item_subtype)
local subtype = iobj.item_subtype or -1
local def = dfhack.items.getSubtypeDef(iobj.item_type, subtype)
local count = dfhack.items.getSubtypeCount(iobj.item_type, subtype)
if def then
itemline = def.name
elseif count >= 0 then
@ -139,14 +142,14 @@ function describe_item_type(iobj)
end
function is_caste_mat(iobj)
return dfhack.items.isCasteMaterial(iobj.item_type)
return dfhack.items.isCasteMaterial(iobj.item_type or -1)
end
function describe_material(iobj)
local matline = 'any material'
if is_caste_mat(iobj) then
matline = 'material not applicable'
elseif iobj.mat_type >= 0 then
matline = 'no material'
elseif (iobj.mat_type or -1) >= 0 then
local info = dfhack.matinfo.decode(iobj.mat_type, iobj.mat_index)
if info then
matline = info:toString()
@ -157,13 +160,17 @@ function describe_material(iobj)
return matline
end
function list_flags(list, bitfield)
function list_flags(bitfield)
local list = {}
if bitfield then
for name,val in pairs(bitfield) do
if val then
table.insert(list, name)
end
end
end
return list
end
function JobConstraints:initListChoices(clist)
clist = clist or workflow.listConstraints(self.job)
@ -186,19 +193,13 @@ function JobConstraints:initListChoices(clist)
elseif cons.request == 'suspend' then
order_pen = COLOR_RED
end
local itemstr
if cons.is_craft then
itemstr = 'any craft'
else
itemstr = describe_item_type(cons)
end
local itemstr = describe_item_type(cons)
if cons.min_quality > 0 then
itemstr = itemstr .. ' ('..df.item_quality[cons.min_quality]..')'
end
local matstr = describe_material(cons)
local matflagstr = ''
local matflags = {}
list_flags(matflags, cons.mat_mask)
local matflags = list_flags(cons.mat_mask)
if #matflags > 0 then
matflags[1] = 'any '..matflags[1]
if matstr == 'any material' then
@ -284,7 +285,80 @@ function JobConstraints:onIncRange(field, delta)
self:saveConstraint(cons)
end
function make_constraint_variants(outputs)
local variants = {}
local known = {}
local function register(cons)
cons.token = workflow.constraintToToken(cons)
if not known[cons.token] then
known[cons.token] = true
table.insert(variants, cons)
end
end
local generic = {}
local anymat = {}
for i,cons in ipairs(outputs) do
local mask = cons.mat_mask
if (cons.mat_type or -1) >= 0 then
cons.mat_mask = nil
end
register(cons)
if mask then
table.insert(generic, {
item_type = cons.item_type,
item_subtype = cons.item_subtype,
is_craft = cons.is_craft,
mat_mask = mask
})
end
table.insert(anymat, {
item_type = cons.item_type,
item_subtype = cons.item_subtype,
is_craft = cons.is_craft
})
end
for i,cons in ipairs(generic) do register(cons) end
for i,cons in ipairs(anymat) do register(cons) end
return variants
end
function JobConstraints:onNewConstraint()
local outputs = workflow.listJobOutputs(self.job)
if #outputs == 0 then
dlg.showMessage('Unsupported', 'Workflow cannot guess the outputs of this job.', COLOR_LIGHTRED)
return
end
local variants = make_constraint_variants(outputs)
local choices = {}
for i,cons in ipairs(variants) do
local itemstr = describe_item_type(cons)
local matstr = describe_material(cons)
local matflags = list_flags(cons.mat_mask or {})
if #matflags > 0 then
local fstr = table.concat(matflags, '/')
if matstr == 'any material' then
matstr = 'any '..fstr
else
matstr = 'any '..fstr..' '..matstr
end
end
table.insert(choices, { text = itemstr..' of '..matstr, obj = cons })
end
dlg.showListPrompt(
'Job Outputs',
'Select one of the job outputs:',
COLOR_WHITE,
choices,
function(idx,item)
self:saveConstraint(item.obj)
end
)
end
function JobConstraints:onDeleteConstraint()