feat: implement unified recipe database with learning support
This commit is contained in:
248
lib/recipeBook.lua
Normal file
248
lib/recipeBook.lua
Normal file
@@ -0,0 +1,248 @@
|
||||
-- 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
|
||||
Reference in New Issue
Block a user