Add crafting functionality for networked turtle with recipe management
This commit is contained in:
@@ -20,6 +20,10 @@ local BROADCAST_CHANNEL = 4200
|
||||
local ORDER_CHANNEL = 4201
|
||||
local BROADCAST_INTERVAL = 1 -- seconds between state broadcasts
|
||||
|
||||
-- Crafting turtle
|
||||
local CRAFT_CHANNEL = 4203
|
||||
local CRAFT_REPLY_CHANNEL = 4204
|
||||
|
||||
-------------------------------------------------
|
||||
-- Furnace types to manage
|
||||
-------------------------------------------------
|
||||
@@ -339,6 +343,229 @@ local COMPOST_RESERVE = 16
|
||||
local COMPOST_DROPPER = "minecraft:dropper_10"
|
||||
local COMPOST_HOPPER = "minecraft:hopper_0"
|
||||
|
||||
-------------------------------------------------
|
||||
-- Crafting recipes (for networked crafting turtle)
|
||||
-- grid: 9 entries mapping to turtle slots 1-3, 5-7, 9-11
|
||||
-------------------------------------------------
|
||||
|
||||
local GRID_TO_SLOT = {1, 2, 3, 5, 6, 7, 9, 10, 11}
|
||||
|
||||
local CRAFTABLE = {
|
||||
-- Basic materials
|
||||
{
|
||||
output = "minecraft:oak_planks",
|
||||
count = 4,
|
||||
grid = {
|
||||
"minecraft:oak_log", nil, nil,
|
||||
nil, nil, nil,
|
||||
nil, nil, nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
output = "minecraft:spruce_planks",
|
||||
count = 4,
|
||||
grid = {
|
||||
"minecraft:spruce_log", nil, nil,
|
||||
nil, nil, nil,
|
||||
nil, nil, nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
output = "minecraft:birch_planks",
|
||||
count = 4,
|
||||
grid = {
|
||||
"minecraft:birch_log", nil, nil,
|
||||
nil, nil, nil,
|
||||
nil, nil, nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
output = "minecraft:stick",
|
||||
count = 4,
|
||||
grid = {
|
||||
"minecraft:oak_planks", nil, nil,
|
||||
"minecraft:oak_planks", nil, nil,
|
||||
nil, nil, nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
output = "minecraft:oak_slab",
|
||||
count = 6,
|
||||
grid = {
|
||||
"minecraft:oak_planks", "minecraft:oak_planks", "minecraft:oak_planks",
|
||||
nil, nil, nil,
|
||||
nil, nil, nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
output = "minecraft:torch",
|
||||
count = 4,
|
||||
grid = {
|
||||
"minecraft:coal", nil, nil,
|
||||
"minecraft:stick", nil, nil,
|
||||
nil, nil, nil,
|
||||
},
|
||||
},
|
||||
-- Crafting & storage
|
||||
{
|
||||
output = "minecraft:crafting_table",
|
||||
count = 1,
|
||||
grid = {
|
||||
"minecraft:oak_planks", "minecraft:oak_planks", nil,
|
||||
"minecraft:oak_planks", "minecraft:oak_planks", nil,
|
||||
nil, nil, nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
output = "minecraft:chest",
|
||||
count = 1,
|
||||
grid = {
|
||||
"minecraft:oak_planks", "minecraft:oak_planks", "minecraft:oak_planks",
|
||||
"minecraft:oak_planks", nil, "minecraft:oak_planks",
|
||||
"minecraft:oak_planks", "minecraft:oak_planks", "minecraft:oak_planks",
|
||||
},
|
||||
},
|
||||
{
|
||||
output = "minecraft:barrel",
|
||||
count = 1,
|
||||
grid = {
|
||||
"minecraft:oak_planks", "minecraft:oak_slab", "minecraft:oak_planks",
|
||||
"minecraft:oak_planks", nil, "minecraft:oak_planks",
|
||||
"minecraft:oak_planks", "minecraft:oak_slab", "minecraft:oak_planks",
|
||||
},
|
||||
},
|
||||
{
|
||||
output = "minecraft:hopper",
|
||||
count = 1,
|
||||
grid = {
|
||||
"minecraft:iron_ingot", nil, "minecraft:iron_ingot",
|
||||
"minecraft:iron_ingot", "minecraft:chest", "minecraft:iron_ingot",
|
||||
nil, "minecraft:iron_ingot", nil,
|
||||
},
|
||||
},
|
||||
-- Building
|
||||
{
|
||||
output = "minecraft:furnace",
|
||||
count = 1,
|
||||
grid = {
|
||||
"minecraft:cobblestone", "minecraft:cobblestone", "minecraft:cobblestone",
|
||||
"minecraft:cobblestone", nil, "minecraft:cobblestone",
|
||||
"minecraft:cobblestone", "minecraft:cobblestone", "minecraft:cobblestone",
|
||||
},
|
||||
},
|
||||
{
|
||||
output = "minecraft:ladder",
|
||||
count = 3,
|
||||
grid = {
|
||||
"minecraft:stick", nil, "minecraft:stick",
|
||||
"minecraft:stick", "minecraft:stick", "minecraft:stick",
|
||||
"minecraft:stick", nil, "minecraft:stick",
|
||||
},
|
||||
},
|
||||
{
|
||||
output = "minecraft:glass_pane",
|
||||
count = 16,
|
||||
grid = {
|
||||
"minecraft:glass", "minecraft:glass", "minecraft:glass",
|
||||
"minecraft:glass", "minecraft:glass", "minecraft:glass",
|
||||
nil, nil, nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
output = "minecraft:iron_bars",
|
||||
count = 16,
|
||||
grid = {
|
||||
"minecraft:iron_ingot", "minecraft:iron_ingot", "minecraft:iron_ingot",
|
||||
"minecraft:iron_ingot", "minecraft:iron_ingot", "minecraft:iron_ingot",
|
||||
nil, nil, nil,
|
||||
},
|
||||
},
|
||||
-- Tools & combat
|
||||
{
|
||||
output = "minecraft:bucket",
|
||||
count = 1,
|
||||
grid = {
|
||||
"minecraft:iron_ingot", nil, "minecraft:iron_ingot",
|
||||
nil, "minecraft:iron_ingot", nil,
|
||||
nil, nil, nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
output = "minecraft:arrow",
|
||||
count = 4,
|
||||
grid = {
|
||||
"minecraft:flint", nil, nil,
|
||||
"minecraft:stick", nil, nil,
|
||||
"minecraft:feather", nil, nil,
|
||||
},
|
||||
},
|
||||
-- Redstone
|
||||
{
|
||||
output = "minecraft:piston",
|
||||
count = 1,
|
||||
grid = {
|
||||
"minecraft:oak_planks", "minecraft:oak_planks", "minecraft:oak_planks",
|
||||
"minecraft:cobblestone", "minecraft:iron_ingot", "minecraft:cobblestone",
|
||||
"minecraft:cobblestone", "minecraft:redstone", "minecraft:cobblestone",
|
||||
},
|
||||
},
|
||||
{
|
||||
output = "minecraft:rail",
|
||||
count = 16,
|
||||
grid = {
|
||||
"minecraft:iron_ingot", nil, "minecraft:iron_ingot",
|
||||
"minecraft:iron_ingot", "minecraft:stick", "minecraft:iron_ingot",
|
||||
"minecraft:iron_ingot", nil, "minecraft:iron_ingot",
|
||||
},
|
||||
},
|
||||
{
|
||||
output = "minecraft:powered_rail",
|
||||
count = 6,
|
||||
grid = {
|
||||
"minecraft:gold_ingot", nil, "minecraft:gold_ingot",
|
||||
"minecraft:gold_ingot", "minecraft:stick", "minecraft:gold_ingot",
|
||||
"minecraft:gold_ingot", "minecraft:redstone", "minecraft:gold_ingot",
|
||||
},
|
||||
},
|
||||
-- Food & misc
|
||||
{
|
||||
output = "minecraft:bread",
|
||||
count = 1,
|
||||
grid = {
|
||||
"minecraft:wheat", "minecraft:wheat", "minecraft:wheat",
|
||||
nil, nil, nil,
|
||||
nil, nil, nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
output = "minecraft:paper",
|
||||
count = 3,
|
||||
grid = {
|
||||
"minecraft:sugar_cane", "minecraft:sugar_cane", "minecraft:sugar_cane",
|
||||
nil, nil, nil,
|
||||
nil, nil, nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
output = "minecraft:compass",
|
||||
count = 1,
|
||||
grid = {
|
||||
nil, "minecraft:iron_ingot", nil,
|
||||
"minecraft:iron_ingot", "minecraft:redstone", "minecraft:iron_ingot",
|
||||
nil, "minecraft:iron_ingot", nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
output = "minecraft:clock",
|
||||
count = 1,
|
||||
grid = {
|
||||
nil, "minecraft:gold_ingot", nil,
|
||||
"minecraft:gold_ingot", "minecraft:redstone", "minecraft:gold_ingot",
|
||||
nil, "minecraft:gold_ingot", nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
-------------------------------------------------
|
||||
-- Low-stock alerts
|
||||
-- When a tracked item drops below 'min', an alert
|
||||
@@ -394,6 +621,7 @@ local activity = {
|
||||
smelting = false, -- auto-smelt in progress
|
||||
defragging = false, -- defrag in progress
|
||||
composting = false, -- auto-compost in progress
|
||||
crafting = false, -- crafting in progress
|
||||
}
|
||||
|
||||
-------------------------------------------------
|
||||
@@ -658,6 +886,7 @@ local smelterMon = nil
|
||||
local smelterMonName = nil
|
||||
local networkModem = nil
|
||||
local networkModemName = nil
|
||||
local craftTurtleName = nil
|
||||
|
||||
local function setupMonitor()
|
||||
mon = peripheral.wrap(MONITOR_SIDE)
|
||||
@@ -1167,6 +1396,203 @@ local function drawDashboard()
|
||||
touchZones = pendingZones
|
||||
end
|
||||
|
||||
-------------------------------------------------
|
||||
-- Crafting helpers
|
||||
-------------------------------------------------
|
||||
|
||||
--- Get ingredient counts from a recipe's grid
|
||||
local function getRecipeIngredients(recipe)
|
||||
local ingredients = {}
|
||||
for _, item in ipairs(recipe.grid) do
|
||||
if item then
|
||||
ingredients[item] = (ingredients[item] or 0) + 1
|
||||
end
|
||||
end
|
||||
return ingredients
|
||||
end
|
||||
|
||||
--- Check if a recipe can be crafted with current stock
|
||||
local function canCraftRecipe(recipe)
|
||||
local ingredients = getRecipeIngredients(recipe)
|
||||
for itemName, needed in pairs(ingredients) do
|
||||
local have = 0
|
||||
if cache.catalogue[itemName] then
|
||||
for _, src in ipairs(cache.catalogue[itemName]) do
|
||||
have = have + src.total
|
||||
end
|
||||
end
|
||||
if have < needed then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- How many times a recipe can be crafted
|
||||
local function maxCraftBatches(recipe)
|
||||
local ingredients = getRecipeIngredients(recipe)
|
||||
local minBatches = math.huge
|
||||
for itemName, needed in pairs(ingredients) do
|
||||
local have = 0
|
||||
if cache.catalogue[itemName] then
|
||||
for _, src in ipairs(cache.catalogue[itemName]) do
|
||||
have = have + src.total
|
||||
end
|
||||
end
|
||||
local batches = math.floor(have / needed)
|
||||
if batches < minBatches then minBatches = batches end
|
||||
end
|
||||
if minBatches == math.huge then return 0 end
|
||||
return minBatches
|
||||
end
|
||||
|
||||
--- Get list of missing ingredients for a recipe
|
||||
local function getMissingIngredients(recipe)
|
||||
local ingredients = getRecipeIngredients(recipe)
|
||||
local missing = {}
|
||||
for itemName, needed in pairs(ingredients) do
|
||||
local have = 0
|
||||
if cache.catalogue[itemName] then
|
||||
for _, src in ipairs(cache.catalogue[itemName]) do
|
||||
have = have + src.total
|
||||
end
|
||||
end
|
||||
if have < needed then
|
||||
table.insert(missing, {
|
||||
name = itemName,
|
||||
have = have,
|
||||
need = needed,
|
||||
})
|
||||
end
|
||||
end
|
||||
return missing
|
||||
end
|
||||
|
||||
--- Execute a craft via the networked turtle
|
||||
local function craftItem(recipeIdx)
|
||||
local recipe = CRAFTABLE[recipeIdx]
|
||||
if not recipe then return false, "Invalid recipe" end
|
||||
if not craftTurtleName then return false, "No turtle" end
|
||||
if not networkModem then return false, "No modem" end
|
||||
|
||||
local turtleInv = peripheral.wrap(craftTurtleName)
|
||||
if not turtleInv then return false, "Turtle offline" end
|
||||
|
||||
activity.crafting = true
|
||||
needsRedraw = true
|
||||
smelterNeedsRedraw = true
|
||||
|
||||
local chests = getChests()
|
||||
|
||||
-- 1. Clear turtle inventory (pull everything out)
|
||||
local tc = turtleInv.list()
|
||||
if tc then
|
||||
for slot, item in pairs(tc) do
|
||||
for _, ch in ipairs(chests) do
|
||||
local n = turtleInv.pushItems(ch, slot)
|
||||
if n and n > 0 then
|
||||
adjustCache(item.name, ch, n)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 2. Push ingredients into correct grid slots
|
||||
for gridPos = 1, 9 do
|
||||
local itemName = recipe.grid[gridPos]
|
||||
if itemName then
|
||||
local turtleSlot = GRID_TO_SLOT[gridPos]
|
||||
local placed = false
|
||||
if cache.catalogue[itemName] then
|
||||
for _, source in ipairs(cache.catalogue[itemName]) do
|
||||
local chest = peripheral.wrap(source.chest)
|
||||
if chest then
|
||||
for slot, slotItem in pairs(chest.list()) do
|
||||
if slotItem.name == itemName then
|
||||
local n = chest.pushItems(craftTurtleName, slot, 1, turtleSlot)
|
||||
if n and n > 0 then
|
||||
adjustCache(itemName, source.chest, -n)
|
||||
placed = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if placed then break end
|
||||
end
|
||||
end
|
||||
if not placed then
|
||||
-- Cleanup: pull items back from turtle
|
||||
print("[CRAFT] Missing ingredient, aborting")
|
||||
local cleanup = turtleInv.list()
|
||||
if cleanup then
|
||||
for s, it in pairs(cleanup) do
|
||||
for _, ch in ipairs(chests) do
|
||||
local n = turtleInv.pushItems(ch, s)
|
||||
if n and n > 0 then
|
||||
adjustCache(it.name, ch, n)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
activity.crafting = false
|
||||
needsRedraw = true
|
||||
smelterNeedsRedraw = true
|
||||
return false, "Missing ingredient"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 3. Signal turtle to craft
|
||||
networkModem.transmit(CRAFT_CHANNEL, CRAFT_REPLY_CHANNEL, { type = "craft", count = 1 })
|
||||
print(string.format("[CRAFT] Sent craft request: %s", recipe.output))
|
||||
|
||||
-- 4. Wait for reply with timeout
|
||||
local timer = os.startTimer(5)
|
||||
local success = false
|
||||
local errMsg = "Turtle timeout"
|
||||
while true do
|
||||
local event = {os.pullEvent()}
|
||||
if event[1] == "modem_message" and event[3] == CRAFT_REPLY_CHANNEL
|
||||
and type(event[5]) == "table" and event[5].type == "craft_result" then
|
||||
os.cancelTimer(timer)
|
||||
success = event[5].success
|
||||
errMsg = event[5].error
|
||||
break
|
||||
elseif event[1] == "timer" and event[2] == timer then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- 5. Pull all items from turtle back to chests
|
||||
tc = turtleInv.list()
|
||||
if tc then
|
||||
for s, it in pairs(tc) do
|
||||
for _, ch in ipairs(chests) do
|
||||
local n = turtleInv.pushItems(ch, s)
|
||||
if n and n > 0 then
|
||||
adjustCache(it.name, ch, n)
|
||||
print(string.format("[CRAFT] Result %s x%d -> %s", it.name, n, ch))
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
activity.crafting = false
|
||||
needsRedraw = true
|
||||
smelterNeedsRedraw = true
|
||||
|
||||
if success then
|
||||
local short = recipe.output:gsub("^minecraft:", ""):gsub("_", " ")
|
||||
print(string.format("[CRAFT] OK: %s x%d", short, recipe.count))
|
||||
return true
|
||||
else
|
||||
print(string.format("[CRAFT] Failed: %s", errMsg or "unknown"))
|
||||
return false, errMsg or "Craft failed"
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------
|
||||
-- Smelter Dashboard
|
||||
-------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user