autocraft: add WIP automatic crafting CSM

Right now it can traverse the craft tree and construct a crafting queue.
master
cron 2020-11-18 22:29:57 +00:00
parent 8e6bb7a2aa
commit 4db64944c9
3 changed files with 268 additions and 0 deletions

View File

@ -0,0 +1,264 @@
-- TODO
--[[
free space could be a concern
traverse_recurse should replace groups with the concrete item
maybe recursions yield an item name?
that way has_sub could indicate the item which would be yielded in traverse_recurse and replaced in the recipe before being queued
autocraft.recipes should be loaded and custom per modset
it might choke on empty items in the recipe
this needs to handle groups a bit better, i dont think it will mix woods/etc
--]]
autocraft = {}
autocraft.recipes = {
["mcl_core:crafting_table"] = {
recipes = {
{
recipe = {
{"group:planks", "group:planks"},
{"group:planks", "group:planks"}}
}
}
},
["mcl_core:wood"] = {
groups = {"planks"},
recipes = {
{
count = 4,
recipe = {
"mcl_core:tree"
},
shapeless = true -- redundant, can detect from the lack of subtables
}
}
}
}
autocraft.groups = {}
-- extract groups from autocraft.recipes
local function group_arrange()
for k, v in pairs(autocraft.recipes) do
if v.groups then
for gi, gv in ipairs(v.groups) do
if not autocraft.groups[gv] then
autocraft.groups[gv] = {}
end
table.insert(autocraft.groups[gv], k)
end
end
end
end
group_arrange()
local function startswith(str, start)
return string.sub(str, 1, #start) == start
end
local function combine(t1, t2)
local t1l = #t1
local o = {}
for i, v in ipairs(t1) do
o[i] = v
end
for i, v in ipairs(t2) do
o[t1l + i] = v
end
return o
end
local function parse_group(str)
if startswith(str, "group:") then
return str:match("group:(.+)")
end
end
-- get recipes for an item/group
local function get_recipes(str)
local group = parse_group(str)
if group then
local o = {}
if autocraft.groups[group] then
for i, v in ipairs(autocraft.groups[group]) do
o = combine(o, get_recipes(v))
end
return o
end
else
local idef = autocraft.recipes[str]
if idef then
local o = idef.recipes
for i, v in ipairs(o) do
v.name = str
end
return o
end
end
return {}
end
-- count up all the items in the player's inventory
-- output:
-- {
-- item = n,
-- item = n
-- }
local function count_inv()
local o = {}
local lpim = minetest.get_inventory("current_player").main
for i, v in ipairs(lpim) do
if not v:is_empty() then
o[v:get_name()] = (o[v:get_name()] or 0) + v:get_count()
end
end
return o
end
-- effectively turn a recipe shapeless
local function flatten_recipe(recipe)
if type(recipe[1]) == "table" then
local o = {}
for i, v in ipairs(recipe) do
o = combine(o, v)
end
return o
else
return recipe
end
end
-- count the requirements for a recipe, uses count_inv format
local function count_recipe(recipe)
local o = {}
for i, v in ipairs(flatten_recipe(recipe)) do
local item = ItemStack(v)
o[item:get_name()] = (o[item:get_name()] or 0) + item:get_count()
end
return o
end
-- get all item strings for an item or group string
local function get_items_of(str)
local group = parse_group(str)
if group then
return autocraft.groups[group]
else
return {str}
end
end
-- check if the recipe can be crafted with current resources
local function can_craft(resources, recipe)
for k, count in pairs(count_recipe(recipe)) do
for i, vv in ipairs(get_items_of(k)) do
local item = ItemStack(vv)
if (resources[item:get_name()] or 0) >= count then
break
end
end
end
return true
end
-- traverse all items in a recipe
local function recurse_recipe(resources, queue, recipe)
for i, v in ipairs(flatten_recipe(recipe)) do
if not autocraft.traverse_recurse(resources, queue, ItemStack(v)) then
return false
end
end
return true
end
-- checks if the item/group is in the resource list and subtracts it
-- is the base case for traverse_recurse
local function has_sub(resources, item)
for i, v in ipairs(get_items_of(item:get_name())) do
if resources[v] and resources[v] >= item:get_count() then
resources[v] = resources[v] - item:get_count()
return true
end
end
return false
end
-- enqueues a recipe for an item and its needed sub items
function autocraft.traverse_recurse(resources, queue, item)
if type(item) == "string" then
item = ItemStack(item)
end
-- base case, uncraftibles/already in inventory
if has_sub(resources, item) then
return true
else
for i, v in ipairs(get_recipes(item:get_name())) do
if can_craft(resources, v.recipe) then
if recurse_recipe(resources, queue, v.recipe) then
table.insert(queue, v.recipe)
local tgt = item:get_count()
local result = v.count or 1
local delta = tgt - result
resources[v.name] = (resources[v.name] or 0) + result
if delta > 0 then
item:set_count(delta)
traverse_recurse(resources, queue, item)
end
return true
end
end
end
end
-- not enough resources
return false
end
-- create a crafting queue for an item
-- return queue if enough resources, nil if not
function autocraft.traverse(item)
local queue = {}
local resources = count_inv()
if autocraft.traverse_recurse(resources, queue, item) then
return queue
end
end
-- craft a traversed craft tree
local function queuecraft(tree)
end
-- make a queue and craft it
function autocraft.craft(item)
end

View File

@ -0,0 +1,3 @@
name = autocraft
author = emilia
description = Automatic crafting. Supports crafting blocks (Mineclone) and custom crafting trees.

View File

@ -35,3 +35,4 @@ load_mod_adupe = false
load_mod_speedlimit = true
load_mod_slowscaffold = true
load_mod_frenemies = true
load_mod_autocraft = true