Files
Inventory-Manager-CC/lib/recipeBook.lua

249 lines
7.7 KiB
Lua

-- lib/recipeBook.lua — Unified recipe database with learning support
-- Loads built-in recipes from data files + user-learned recipes from disk.
-- Compatible with Prominence II Hasturian Era modpack (or any modded setup).
--
-- Usage:
-- local recipeBook = dofile("lib/recipeBook.lua")
-- recipeBook.init(".recipes.db")
-- recipeBook.loadLegacyCrafting(dofile("data/craftable.lua"))
-- recipeBook.loadLegacySmelting(dofile("data/smeltable.lua"))
-- recipeBook.learnCraftingRecipe("mod:item", 4, { ... })
-- recipeBook.flush()
local recipeBook = {}
local recipes = {
crafting = {}, -- keyed by output item name
smelting = {}, -- keyed by input item name
}
local RECIPE_FILE = nil
local dirty = false
-------------------------------------------------
-- Initialization and persistence
-------------------------------------------------
function recipeBook.init(recipeFile)
RECIPE_FILE = recipeFile
recipeBook.loadUserRecipes()
end
--- Load legacy crafting recipes from data/craftable.lua format.
-- Does not overwrite recipes already loaded (user-learned take priority).
function recipeBook.loadLegacyCrafting(craftableArray)
for _, recipe in ipairs(craftableArray) do
if not recipes.crafting[recipe.output] then
recipes.crafting[recipe.output] = {
output = recipe.output,
count = recipe.count,
grid = recipe.grid,
source = "builtin",
}
end
end
end
--- Load legacy smelting recipes from data/smeltable.lua format.
-- Does not overwrite recipes already loaded (user-learned take priority).
function recipeBook.loadLegacySmelting(smeltableTable)
for input, recipe in pairs(smeltableTable) do
if not recipes.smelting[input] then
recipes.smelting[input] = {
input = input,
result = recipe.result,
furnaces = recipe.furnaces,
source = "builtin",
}
end
end
end
--- Load user-learned recipes from disk.
function recipeBook.loadUserRecipes()
if not RECIPE_FILE or not fs.exists(RECIPE_FILE) then return end
pcall(function()
local f = fs.open(RECIPE_FILE, "r")
local raw = f.readAll()
f.close()
local data = textutils.unserialise(raw)
if type(data) == "table" then
for output, recipe in pairs(data.crafting or {}) do
recipe.source = "learned"
recipe.output = output
recipes.crafting[output] = recipe
end
for input, recipe in pairs(data.smelting or {}) do
recipe.source = "learned"
recipe.input = input
recipes.smelting[input] = recipe
end
end
end)
end
--- Save user-learned recipes to disk.
function recipeBook.flush()
if not dirty or not RECIPE_FILE then return end
pcall(function()
local uc, us = {}, {}
for output, r in pairs(recipes.crafting) do
if r.source == "learned" then
uc[output] = { output = r.output, count = r.count, grid = r.grid }
end
end
for input, r in pairs(recipes.smelting) do
if r.source == "learned" then
us[input] = { result = r.result, furnaces = r.furnaces }
end
end
local f = fs.open(RECIPE_FILE, "w")
f.write(textutils.serialise({ crafting = uc, smelting = us }))
f.close()
end)
dirty = false
end
-------------------------------------------------
-- Learning / forgetting recipes
-------------------------------------------------
--- Learn a new crafting recipe (or overwrite an existing one).
-- @param output string — output item name (e.g. "minecraft:stick")
-- @param count number — items produced per craft
-- @param grid table — 9-entry grid array
function recipeBook.learnCraftingRecipe(output, count, grid)
recipes.crafting[output] = {
output = output,
count = count,
grid = grid,
source = "learned",
}
dirty = true
end
--- Learn a new smelting recipe.
-- @param input string — input item name
-- @param result string — output item name
-- @param furnaces table — array of furnace type strings (default: {"minecraft:furnace"})
function recipeBook.learnSmeltingRecipe(input, result, furnaces)
recipes.smelting[input] = {
input = input,
result = result,
furnaces = furnaces or { "minecraft:furnace" },
source = "learned",
}
dirty = true
end
--- Forget a learned crafting recipe (built-in recipes cannot be forgotten).
-- @return true if removed, false if recipe was built-in or not found
function recipeBook.forgetCraftingRecipe(output)
if recipes.crafting[output] and recipes.crafting[output].source == "learned" then
recipes.crafting[output] = nil
dirty = true
return true
end
return false
end
--- Forget a learned smelting recipe.
function recipeBook.forgetSmeltingRecipe(input)
if recipes.smelting[input] and recipes.smelting[input].source == "learned" then
recipes.smelting[input] = nil
dirty = true
return true
end
return false
end
-------------------------------------------------
-- Lookups
-------------------------------------------------
--- Get a crafting recipe by output item name.
function recipeBook.getCraftingRecipe(output)
return recipes.crafting[output]
end
--- Get a smelting recipe by input item name.
function recipeBook.getSmeltingRecipe(input)
return recipes.smelting[input]
end
--- Find any recipe (crafting or smelting) that produces a given item.
-- @return recipe, recipeType ("crafting" or "smelting") or nil
function recipeBook.findRecipeFor(itemName)
if recipes.crafting[itemName] then
return recipes.crafting[itemName], "crafting"
end
for input, recipe in pairs(recipes.smelting) do
if recipe.result == itemName then
return recipe, "smelting"
end
end
return nil
end
-------------------------------------------------
-- Backward-compatible accessors
-------------------------------------------------
--- Get all crafting recipes as an indexed array (compat with cfg.CRAFTABLE).
-- Sorted by output name.
function recipeBook.getCraftingList()
local list = {}
for _, recipe in pairs(recipes.crafting) do
table.insert(list, recipe)
end
table.sort(list, function(a, b) return a.output < b.output end)
return list
end
--- Get all smelting recipes as a keyed table (compat with cfg.SMELTABLE).
function recipeBook.getSmeltingTable()
local result = {}
for input, recipe in pairs(recipes.smelting) do
result[input] = recipe
end
return result
end
-------------------------------------------------
-- Utilities
-------------------------------------------------
--- Get summed ingredients for a crafting recipe.
-- @return table { [itemName] = count }
function recipeBook.getIngredients(recipe)
local ingredients = {}
if recipe and recipe.grid then
for _, item in ipairs(recipe.grid) do
if item then
ingredients[item] = (ingredients[item] or 0) + 1
end
end
end
return ingredients
end
--- Quick craftability check.
function recipeBook.isCraftable(itemName)
return recipes.crafting[itemName] ~= nil
end
--- Quick smeltability check.
function recipeBook.isSmeltable(itemName)
return recipes.smelting[itemName] ~= nil
end
--- Count total recipes.
-- @return craftCount, smeltCount
function recipeBook.count()
local cc, sc = 0, 0
for _ in pairs(recipes.crafting) do cc = cc + 1 end
for _ in pairs(recipes.smelting) do sc = sc + 1 end
return cc, sc
end
return recipeBook