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 return rv
end 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 -- Sort a vector or lua table
function sort_vector(vector,field,cmp) function sort_vector(vector,field,cmp)
local fcmp = compare_field(field,cmp) local fcmp = compare_field(field,cmp)
@ -304,16 +314,26 @@ end
-- Linear search -- Linear search
function linear_index(vector,obj) function linear_index(vector,key,field)
local min,max local min,max
if df.isvalid(vector) then if df.isvalid(vector) then
min,max = 0,#vector-1 min,max = 0,#vector-1
else else
min,max = 1,#vector min,max = 1,#vector
end 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 for i=min,max do
if vector[i] == obj then local obj = vector[i]
return i if obj == key then
return i, obj
end
end end
end end
return nil return nil

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

@ -1,5 +1,7 @@
local _ENV = mkmodule('plugins.workflow') local _ENV = mkmodule('plugins.workflow')
local utils = require 'utils'
--[[ --[[
Native functions: Native functions:
@ -8,7 +10,266 @@ local _ENV = mkmodule('plugins.workflow')
* setEnabled(enable) * setEnabled(enable)
* listConstraints([job]) -> {...} * listConstraints([job]) -> {...}
* setConstraint(token, by_count, goal[, gap]) -> {...} * 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 return _ENV

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

@ -125,10 +125,13 @@ end
function describe_item_type(iobj) function describe_item_type(iobj)
local itemline = 'any item' 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 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 subtype = iobj.item_subtype or -1
local count = dfhack.items.getSubtypeCount(iobj.item_type, iobj.item_subtype) local def = dfhack.items.getSubtypeDef(iobj.item_type, subtype)
local count = dfhack.items.getSubtypeCount(iobj.item_type, subtype)
if def then if def then
itemline = def.name itemline = def.name
elseif count >= 0 then elseif count >= 0 then
@ -139,14 +142,14 @@ function describe_item_type(iobj)
end end
function is_caste_mat(iobj) function is_caste_mat(iobj)
return dfhack.items.isCasteMaterial(iobj.item_type) return dfhack.items.isCasteMaterial(iobj.item_type or -1)
end end
function describe_material(iobj) function describe_material(iobj)
local matline = 'any material' local matline = 'any material'
if is_caste_mat(iobj) then if is_caste_mat(iobj) then
matline = 'material not applicable' matline = 'no material'
elseif iobj.mat_type >= 0 then elseif (iobj.mat_type or -1) >= 0 then
local info = dfhack.matinfo.decode(iobj.mat_type, iobj.mat_index) local info = dfhack.matinfo.decode(iobj.mat_type, iobj.mat_index)
if info then if info then
matline = info:toString() matline = info:toString()
@ -157,12 +160,16 @@ function describe_material(iobj)
return matline return matline
end end
function list_flags(list, bitfield) function list_flags(bitfield)
local list = {}
if bitfield then
for name,val in pairs(bitfield) do for name,val in pairs(bitfield) do
if val then if val then
table.insert(list, name) table.insert(list, name)
end end
end end
end
return list
end end
function JobConstraints:initListChoices(clist) function JobConstraints:initListChoices(clist)
@ -186,19 +193,13 @@ function JobConstraints:initListChoices(clist)
elseif cons.request == 'suspend' then elseif cons.request == 'suspend' then
order_pen = COLOR_RED order_pen = COLOR_RED
end end
local itemstr local itemstr = describe_item_type(cons)
if cons.is_craft then
itemstr = 'any craft'
else
itemstr = describe_item_type(cons)
end
if cons.min_quality > 0 then if cons.min_quality > 0 then
itemstr = itemstr .. ' ('..df.item_quality[cons.min_quality]..')' itemstr = itemstr .. ' ('..df.item_quality[cons.min_quality]..')'
end end
local matstr = describe_material(cons) local matstr = describe_material(cons)
local matflagstr = '' local matflagstr = ''
local matflags = {} local matflags = list_flags(cons.mat_mask)
list_flags(matflags, cons.mat_mask)
if #matflags > 0 then if #matflags > 0 then
matflags[1] = 'any '..matflags[1] matflags[1] = 'any '..matflags[1]
if matstr == 'any material' then if matstr == 'any material' then
@ -284,7 +285,80 @@ function JobConstraints:onIncRange(field, delta)
self:saveConstraint(cons) self:saveConstraint(cons)
end 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() 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 end
function JobConstraints:onDeleteConstraint() function JobConstraints:onDeleteConstraint()