From a280a1429e29272e5cae9b95c01e1621a1904dfc Mon Sep 17 00:00:00 2001 From: OgelGames Date: Sun, 14 Jan 2024 15:03:32 +1100 Subject: [PATCH] Rewrite recipe code and add group support (#338) --- .luacheckrc | 2 +- technic/machines/register/grindings.lua | 24 +- technic/machines/register/recipes.lua | 280 ++++++++++++++++-------- technic/materials.lua | 6 - technic/mod.conf | 2 +- 5 files changed, 190 insertions(+), 124 deletions(-) diff --git a/.luacheckrc b/.luacheckrc index 148443af..49fd267d 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -30,7 +30,7 @@ read_globals = { "craftguide", "i3", "mtt", "vizlib", "mcl_sounds", "mcl_vars", "mcl_worlds", "mcl_buckets", "mcl_formspec", - "mcl_craftguide","exchangeclone", + "mcl_craftguide", -- Only used in technic/machines/MV/lighting.lua (disabled) "isprotect", "homedecor_expect_infinite_stacks", diff --git a/technic/machines/register/grindings.lua b/technic/machines/register/grindings.lua index 18c62131..4c622947 100644 --- a/technic/machines/register/grindings.lua +++ b/technic/machines/register/grindings.lua @@ -46,29 +46,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", mat.acacia_tree, mat.acacia_wood, acacia_extract) -register_tree_grinding("Common Tree", mat.tree, mat.wood, default_extract) -register_tree_grinding("Common Tree", mat.aspen_tree, mat.aspen_wood, default_extract) -register_tree_grinding("Common Tree", mat.jungletree, mat.junglewood, default_extract) -register_tree_grinding("Common Tree", mat.pine_tree, mat.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) diff --git a/technic/machines/register/recipes.lua b/technic/machines/register/recipes.lua index 50ab1163..45b88c70 100644 --- a/technic/machines/register/recipes.lua +++ b/technic/machines/register/recipes.lua @@ -4,174 +4,260 @@ 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(typename, { + 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 = typename, + type = recipe.method, output = output, - items = data.input, + 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 - if have_mcl_cg then - register_recipe(typename, data) + 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 - minetest.after(0.01, register_recipe, typename, data) -- Handle aliases + 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 + 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 + 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 - return { - time = result.time, - new_input = new_input.items, - output = result.item + 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", +} diff --git a/technic/materials.lua b/technic/materials.lua index d6008a87..bd381055 100644 --- a/technic/materials.lua +++ b/technic/materials.lua @@ -60,12 +60,6 @@ technic.materials = { wood = has_mcl and "mcl_core:wood" or "default:wood", acacia_tree = has_mcl and "mcl_core:acaciatree" or "default:acacia_tree", acacia_wood = has_mcl and "mcl_core:acaciawood" or "default:acacia_wood", - aspen_tree = has_mcl and "mcl_core:birchtree" or "default:aspen_tree", - aspen_wood = has_mcl and "mcl_core:birchwood" or "default:aspen_wood", - jungletree = has_mcl and "mcl_core:jungletree" or "default:jungletree", - junglewood = has_mcl and "mcl_core:junglewood" or "default:junglewood", - pine_tree = has_mcl and "mcl_core:sprucetree" or "default:pine_tree", - pine_wood = has_mcl and "mcl_core:sprucewood" or "default:pine_wood", water_source = has_mcl and "mcl_core:water_source" or "default:water_source", water_flowing = has_mcl and "mcl_core:water_flowing" or "default:water_flowing", river_water_source = has_mcl and "mclx_core:river_water_source" or "default:river_water_source", diff --git a/technic/mod.conf b/technic/mod.conf index 9759e34a..6e8b33d3 100644 --- a/technic/mod.conf +++ b/technic/mod.conf @@ -1,3 +1,3 @@ name = technic depends = pipeworks, technic_worldgen, basic_materials -optional_depends = mcl_core, mcl_sounds, default, bucket, mesecons, mesecons_mvps, digilines, digiline_remote, unified_inventory, dye, craftguide, i3, mtt, vizlib, moreores, mcl_buckets, mcl_explosions, mcl_craftguide, zzzz_exchangeclone_init +optional_depends = mcl_core, mcl_sounds, default, bucket, mesecons, mesecons_mvps, digilines, digiline_remote, unified_inventory, dye, craftguide, i3, mtt, vizlib, moreores, mcl_buckets, mcl_explosions, mcl_craftguide