Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite recipe code and add group support #338

Merged
merged 8 commits into from
Jan 14, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .luacheckrc
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ read_globals = {
"drawers", "mg",
"craftguide", "i3", "mtt",
"vizlib", "mcl_sounds", "mcl_vars",
"mcl_worlds", "exchangeclone",
"mcl_worlds", "mcl_craftguide",

-- Only used in technic/machines/MV/lighting.lua (disabled)
"isprotect", "homedecor_expect_infinite_stacks",
Expand Down
24 changes: 5 additions & 19 deletions technic/machines/register/grindings.lua
Original file line number Diff line number Diff line change
Expand Up @@ -45,29 +45,15 @@ local function register_tree_grinding(name, tree, wood, extract, grinding_color)
end
end

local rubber_tree_planks = moretrees and "moretrees:rubber_tree_planks"
local rubber_planks = moretrees and "moretrees:rubber_tree_planks"
local default_extract = dye and "dye:brown 2"

-- https://en.wikipedia.org/wiki/Catechu ancient brown dye from the wood of acacia trees
local acacia_extract = dye and "dye:brown 8"

-- technic recipes don't support groups yet :/
--register_tree_grinding("Common Tree", "group:tree", "group:wood", default_extract)

-- Specific recipes for acacia and rubber trees
register_tree_grinding("Acacia", "default:acacia_tree", "default:acacia_wood", acacia_extract)
register_tree_grinding("Common Tree", "default:tree", "default:wood", default_extract)
register_tree_grinding("Common Tree", "default:aspen_tree", "default:aspen_wood", default_extract)
register_tree_grinding("Common Tree", "default:jungletree", "default:junglewood", default_extract)
register_tree_grinding("Common Tree", "default:pine_tree", "default:pine_wood", default_extract)
register_tree_grinding("Rubber Tree", "moretrees:rubber_tree_trunk", rubber_tree_planks, "technic:raw_latex")
register_tree_grinding("Rubber Tree", "moretrees:rubber_tree_trunk", rubber_planks, "technic:raw_latex 2")
register_tree_grinding("Rubber Tree", "moretrees:rubber_tree_trunk_empty", nil, "technic:raw_latex")

if moretrees then
local trees = {
"beech", "apple_tree", "oak", "sequoia", "birch", "palm",
"date_palm", "spruce", "cedar", "poplar", "willow", "fir"
}
for _,tree in pairs(trees) do
register_tree_grinding("Common Tree", "moretrees:"..tree.."_trunk", "moretrees:"..tree.."_planks", default_extract)
end
end
-- Group recipe for all other trees
register_tree_grinding("Common Tree", "group:tree", "group:wood", default_extract)
289 changes: 197 additions & 92 deletions technic/machines/register/recipes.lua
Original file line number Diff line number Diff line change
@@ -1,158 +1,263 @@

local have_ui = minetest.get_modpath("unified_inventory")
local have_cg = minetest.get_modpath("craftguide")
local have_mcl_cg = minetest.get_modpath("mcl_craftguide")
local have_i3 = minetest.get_modpath("i3")

technic.recipes = { cooking = { input_size = 1, output_size = 1 } }
technic.recipes = {
cooking = {input_size = 1, output_size = 1, recipes = {}},
}

function technic.register_recipe_type(typename, origdata)
local data = table.copy(origdata)
local temp_recipes = {} -- Used to store recipes before caching
local recipe_cache = {} -- Cache used by technic.get_recipe

function technic.register_recipe_type(method, data)
data = table.copy(data)
data.input_size = data.input_size or 1
data.output_size = data.output_size or 1
if have_ui and unified_inventory.register_craft_type then
unified_inventory.register_craft_type(typename, {
data.recipes = {}
if have_ui then
unified_inventory.register_craft_type(method, {
description = data.description,
icon = data.icon,
width = data.input_size,
height = 1,
})
end
if have_cg and craftguide.register_craft_type then
craftguide.register_craft_type(typename, {
if have_cg then
craftguide.register_craft_type(method, {
description = data.description,
icon = data.icon,
})
end
if have_mcl_cg then
mcl_craftguide.register_craft_type(method, {
description = data.description,
icon = data.icon,
})
end
if have_i3 then
i3.register_craft_type(typename, {
i3.register_craft_type(method, {
description = data.description,
icon = data.icon,
})
end
data.recipes = {}
technic.recipes[typename] = data
technic.recipes[method] = data
end

local function get_recipe_index(items)
if not items or type(items) ~= "table" then return false end
local l = {}
for i, stack in ipairs(items) do
l[i] = ItemStack(stack):get_name()
function technic.register_recipe(method, data)
data.time = data.time or 1
data.method = method
if type(data.input) == "string" then
data.input = {data.input}
end
if type(data.output) == "string" then
data.output = {data.output}
end
table.sort(l)
return table.concat(l, "/")
table.insert(temp_recipes, data)
end

local function register_recipe(typename, data)
-- Handle aliases
for i, stack in ipairs(data.input) do
data.input[i] = ItemStack(stack):to_string()
end
if type(data.output) == "table" then
for i, v in ipairs(data.output) do
data.output[i] = ItemStack(data.output[i]):to_string()
end
else
data.output = ItemStack(data.output):to_string()
local function get_recipe_key(method, items)
local t = {}
for i, stack in ipairs(items) do
t[i] = ItemStack(stack):get_name()
end
table.sort(t)
return method.."/"..table.concat(t, "/")
end

local recipe = {time = data.time, input = {}, output = data.output}
local index = get_recipe_index(data.input)
if not index then
minetest.log("warning", "[Technic] ignored registration of garbage recipe!")
function technic.get_recipe(method, items)
local key = get_recipe_key(method, items)
local recipe = recipe_cache[key]
if not recipe then
return
end
for _, stack in ipairs(data.input) do
recipe.input[ItemStack(stack):get_name()] = ItemStack(stack):get_count()
local new_input = {}
for i, stack in ipairs(items) do
local amount = recipe.input[stack:get_name()]
if stack:get_count() < amount then
return
else
new_input[i] = ItemStack(stack)
new_input[i]:take_item(amount)
end
end
return {
time = recipe.time,
new_input = new_input,
output = recipe.output
}
end

technic.recipes[typename].recipes[index] = recipe
if data.hidden then return end

local outputs = type(data.output) == "table" and data.output or {data.output}
for _,output in ipairs(outputs) do
local function add_to_craftguides(recipe)
for _, output in ipairs(recipe.output) do
if have_ui then
unified_inventory.register_craft({
type = typename,
type = recipe.method,
output = output,
items = data.input,
items = table.copy(recipe.input),
width = 0,
})
end
if have_cg and craftguide.register_craft then
craftguide.register_craft({
type = typename,
type = recipe.method,
result = output,
items = {table.concat(data.input, ", ")},
items = {table.concat(recipe.input, ", ")},
})
end
if have_mcl_cg then
mcl_craftguide.register_craft({
type = recipe.method,
output = output,
items = table.copy(recipe.input),
width = 0,
})
end
if have_i3 then
i3.register_craft({
type = typename,
type = recipe.method,
result = output,
items = {table.concat(data.input, ", ")},
items = {table.concat(recipe.input, ", ")},
})
end
end
end

-- Checks for "zzzz_exchangeclone_crafthook" mod so that it won't crash with older versions of ExchangeClone
-- which don't have it.
local has_exchangeclone = minetest.get_modpath("zzzz_exchangeclone_init")
local function get_items_in_group(group)
local items = {}
local groups = group:split(",")
for name, def in pairs(minetest.registered_items) do
local match = true
for _,g in pairs(groups) do
if not def.groups[g] then
match = false
break
end
end
if match then
items[#items+1] = name
end
end
return items
end

function technic.register_recipe(typename, data)
if has_exchangeclone then
exchangeclone.register_technic_recipe(typename, data)
local function get_recipe_variants(items, index)
index = index or 1
if not items[index] then
return
end
local list = {}
local variants = get_recipe_variants(items, index + 1)
if variants then
for _,a in pairs(items[index]) do
for _,b in pairs(variants) do
list[#list+1] = a..","..b
end
end
else
for _,a in pairs(items[index]) do
list[#list+1] = a
end
end
if index == 1 then
for i, str in pairs(list) do
list[i] = str:split(",")
end
end
minetest.after(0.01, register_recipe, typename, data) -- Handle aliases
return list
end

function technic.get_recipe(typename, items)
if typename == "cooking" then -- Already built into Minetest, so use that
local result, new_input = minetest.get_craft_result({
method = "cooking",
width = 1,
items = items,
})
-- Compatibility layer
if not result or result.time == 0 then
return
-- Workaround for recipes with replacements
elseif not new_input.items[1]:is_empty() and new_input.items[1]:get_name() ~= items[1]:get_name() then
items[1]:take_item(1)
return {
time = result.time,
new_input = {items[1]},
output = {new_input.items[1], result.item}
}
local function cache_recipe(data)
-- Create the basic recipe
local recipe = {time = data.time, input = {}, output = {}}
for _, item in ipairs(data.input) do
if item:match("^group:") then
local split = item:split(" ")
recipe.input[split[1]] = tonumber(split[2]) or 1
else
return {
time = result.time,
new_input = new_input.items,
output = result.item
local stack = ItemStack(item)
recipe.input[stack:get_name()] = stack:get_count()
end
end
for i, item in ipairs(data.output) do
recipe.output[i] = ItemStack(item):to_string()
end
if data.method ~= "cooking" then
table.insert(technic.recipes[data.method].recipes, recipe)
end
-- Find all unique variants of the recipe and cache them
-- If there are no group items, there will only be one
local all_items, item_counts = {}, {}
local has_group_item = false
for item, count in pairs(recipe.input) do
local group = item:match("^group:(.+)$")
if group then
table.insert(all_items, get_items_in_group(group))
has_group_item = true
else
table.insert(all_items, {ItemStack(item):get_name()})
end
table.insert(item_counts, count)
end
if not has_group_item then
local key = get_recipe_key(data.method, data.input)
recipe_cache[key] = table.copy(recipe)
return
end
for _,items in pairs(get_recipe_variants(all_items)) do
local key = get_recipe_key(data.method, items)
-- Non-group recipes take priority over group recipes
if not has_group_item or not recipe_cache[key] then
local input = {}
for i, item in ipairs(items) do
input[item] = item_counts[i]
end
recipe_cache[key] = {
time = data.time,
input = input,
output = table.copy(recipe.output),
}
end
end
local index = get_recipe_index(items)
if not index then return end
end

local recipe = technic.recipes[typename].recipes[index]
if recipe then
local new_input = {}
for i, stack in ipairs(items) do
if stack:get_count() < recipe.input[stack:get_name()] then
return
else
new_input[i] = ItemStack(stack)
new_input[i]:take_item(recipe.input[stack:get_name()])
local function cache_all_recipes()
-- Cache built in cooking recipes
for item in pairs(minetest.registered_items) do
local recipes = minetest.get_all_craft_recipes(item)
for _,recipe in ipairs(recipes or {}) do
if recipe.method == "cooking" then
local result, new_input = minetest.get_craft_result(recipe)
if result and result.time > 0 then
local data = {
method = "cooking",
time = result.time,
input = recipe.items,
output = {result.item:to_string()},
}
local replacement = new_input.items[1]
if not replacement:is_empty() then
data.output[2] = replacement:to_string()
end
cache_recipe(data)
end
end
end
return {
time = recipe.time,
new_input = new_input,
output = recipe.output
}
else
return
end
-- Cache custom recipes
for _, data in pairs(temp_recipes) do
if not data.hidden then
add_to_craftguides(data)
end
cache_recipe(data)
end
temp_recipes = nil
end

-- Slightly hacky way to be the first function called
table.insert(minetest.registered_on_mods_loaded, 1, cache_all_recipes)
minetest.callback_origins[cache_all_recipes] = {
mod = "technic",
name = "register_on_mods_loaded",
}
Loading
Loading