249 lines
7.7 KiB
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
|