autocraft: add WIP automatic crafting CSM
Right now it can traverse the craft tree and construct a crafting queue.master
parent
8e6bb7a2aa
commit
4db64944c9
|
@ -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
|
|
@ -0,0 +1,3 @@
|
|||
name = autocraft
|
||||
author = emilia
|
||||
description = Automatic crafting. Supports crafting blocks (Mineclone) and custom crafting trees.
|
|
@ -35,3 +35,4 @@ load_mod_adupe = false
|
|||
load_mod_speedlimit = true
|
||||
load_mod_slowscaffold = true
|
||||
load_mod_frenemies = true
|
||||
load_mod_autocraft = true
|
||||
|
|
Loading…
Reference in New Issue