283 lines
9.0 KiB
Lua
283 lines
9.0 KiB
Lua
-- Crafting Turtle Script
|
|
-- Run this on the crafting turtle (turtle with crafting table).
|
|
-- Listens for craft commands from the master via wired modem.
|
|
-- Pulls ingredients from chests, crafts, and pushes results back.
|
|
-- Requires a wired modem attached to the turtle.
|
|
|
|
-------------------------------------------------
|
|
-- Default configuration (overridden by .turtle_config)
|
|
-------------------------------------------------
|
|
|
|
-- Modem channels (must match inventoryManager.lua)
|
|
local CRAFT_CHANNEL = 4203 -- master -> turtle (craft requests)
|
|
local CRAFT_REPLY_CHANNEL = 4204 -- turtle -> master (craft results)
|
|
|
|
-- Crafting grid slots in a turtle's 4x4 inventory
|
|
local CRAFT_SLOTS = {1, 2, 3, 5, 6, 7, 9, 10, 11}
|
|
|
|
-------------------------------------------------
|
|
-- Load config from file if present
|
|
-------------------------------------------------
|
|
|
|
local TURTLE_CONFIG_FILE = ".turtle_config"
|
|
|
|
local function loadConfig()
|
|
if not fs.exists(TURTLE_CONFIG_FILE) then return end
|
|
local f = fs.open(TURTLE_CONFIG_FILE, "r")
|
|
local data = f.readAll()
|
|
f.close()
|
|
local ok, cfg = pcall(textutils.unserialiseJSON, data)
|
|
if not ok or not cfg then
|
|
print("[WARN] Failed to parse " .. TURTLE_CONFIG_FILE)
|
|
return
|
|
end
|
|
if cfg.craftChannel then CRAFT_CHANNEL = cfg.craftChannel end
|
|
if cfg.craftReplyChannel then CRAFT_REPLY_CHANNEL = cfg.craftReplyChannel end
|
|
print("[CONFIG] Loaded from " .. TURTLE_CONFIG_FILE)
|
|
end
|
|
|
|
loadConfig()
|
|
|
|
-------------------------------------------------
|
|
-- Setup
|
|
-------------------------------------------------
|
|
|
|
print("=================================")
|
|
print(" Crafting Turtle (Modem-Based)")
|
|
print("=================================")
|
|
print("")
|
|
|
|
-- Verify this is a crafting turtle
|
|
if not turtle or not turtle.craft then
|
|
print("[ERR] No turtle.craft() available!")
|
|
print(" This must be a Crafting Turtle.")
|
|
return
|
|
end
|
|
|
|
-- Find wired modem and get our network name
|
|
local modem = nil
|
|
local modemSide = nil
|
|
local selfName = nil
|
|
|
|
for _, side in ipairs({"top", "bottom", "left", "right", "front", "back"}) do
|
|
if peripheral.getType(side) == "modem" then
|
|
local m = peripheral.wrap(side)
|
|
if m.getNameLocal then
|
|
local name = m.getNameLocal()
|
|
if name then
|
|
modem = m
|
|
modemSide = side
|
|
selfName = name
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if not modem or not selfName then
|
|
print("[ERR] No wired modem found!")
|
|
print(" Attach a wired modem to the turtle")
|
|
print(" and connect it to the network.")
|
|
return
|
|
end
|
|
|
|
print("[OK] Modem: " .. modemSide)
|
|
print("[OK] Network name: " .. selfName)
|
|
|
|
-- Open channel for receiving craft commands
|
|
modem.open(CRAFT_CHANNEL)
|
|
print("[OK] Listening on channel " .. CRAFT_CHANNEL)
|
|
|
|
-- Wrap our own inventory peripheral for pullItems/pushItems
|
|
local selfInv = peripheral.wrap(selfName)
|
|
if not selfInv then
|
|
print("[ERR] Cannot wrap own peripheral: " .. selfName)
|
|
print(" Make sure the wired modem is connected.")
|
|
return
|
|
end
|
|
|
|
print("[OK] Self-inventory peripheral ready")
|
|
print("")
|
|
print("Waiting for craft commands from master...")
|
|
print("")
|
|
|
|
-------------------------------------------------
|
|
-- Helpers
|
|
-------------------------------------------------
|
|
|
|
local function clearInventory(chests)
|
|
-- Push all items from all 16 turtle slots back to chests
|
|
local cleared = 0
|
|
for slot = 1, 16 do
|
|
if turtle.getItemCount(slot) > 0 then
|
|
for _, chestName in ipairs(chests) do
|
|
local ok, n = pcall(selfInv.pushItems, chestName, slot)
|
|
if ok and n and n > 0 then
|
|
cleared = cleared + n
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return cleared
|
|
end
|
|
|
|
local function describeGrid()
|
|
local parts = {}
|
|
for _, slot in ipairs(CRAFT_SLOTS) do
|
|
local detail = turtle.getItemDetail(slot)
|
|
if detail then
|
|
table.insert(parts, string.format(" slot %d: %s x%d", slot, detail.name, detail.count))
|
|
end
|
|
end
|
|
return table.concat(parts, "\n")
|
|
end
|
|
|
|
-------------------------------------------------
|
|
-- Craft command handler
|
|
-------------------------------------------------
|
|
|
|
local function handleCraftCommand(message)
|
|
-- message format from master:
|
|
-- {
|
|
-- type = "craft_request",
|
|
-- recipeIdx = <number>,
|
|
-- output = <string>, -- expected output item name
|
|
-- slots = { [turtleSlot] = { chestName=..., chestSlot=..., itemName=..., count=... }, ... },
|
|
-- returnChests = { "chest_0", "chest_1", ... },
|
|
-- }
|
|
|
|
local slots = message.slots
|
|
local returnChests = message.returnChests
|
|
local output = message.output or "unknown"
|
|
|
|
if not slots or not returnChests then
|
|
print("[CRAFT] Invalid command: missing slots or returnChests")
|
|
return { type = "craft_result", success = false, error = "Invalid command" }
|
|
end
|
|
|
|
print(string.format("[CRAFT] Received craft request: %s", output))
|
|
|
|
-- 1. Clear turtle inventory (safety — should be empty)
|
|
clearInventory(returnChests)
|
|
|
|
-- 2. Pull ingredients from chests into crafting grid slots
|
|
local placedItems = {} -- turtleSlot -> itemName
|
|
local allPlaced = true
|
|
|
|
for turtleSlotStr, info in pairs(slots) do
|
|
local turtleSlot = tonumber(turtleSlotStr)
|
|
local chestName = info.chestName
|
|
local chestSlot = info.chestSlot
|
|
local itemName = info.itemName
|
|
local count = info.count or 1
|
|
|
|
print(string.format("[CRAFT] Pulling %s from %s slot %d -> turtle slot %d",
|
|
itemName, chestName, chestSlot, turtleSlot))
|
|
|
|
local ok, n = pcall(selfInv.pullItems, chestName, chestSlot, count, turtleSlot)
|
|
if ok and n and n > 0 then
|
|
placedItems[turtleSlot] = itemName
|
|
print(string.format("[CRAFT] Placed %s x%d in slot %d", itemName, n, turtleSlot))
|
|
else
|
|
if not ok then
|
|
print(string.format("[CRAFT] pullItems error: %s", tostring(n)))
|
|
else
|
|
print(string.format("[CRAFT] pullItems returned %s (expected %d)", tostring(n), count))
|
|
end
|
|
allPlaced = false
|
|
break
|
|
end
|
|
end
|
|
|
|
if not allPlaced then
|
|
-- Abort: push everything back
|
|
print("[CRAFT] Failed to place all ingredients, aborting")
|
|
local returnedItems = {}
|
|
for slot, itemName in pairs(placedItems) do
|
|
returnedItems[tostring(slot)] = itemName
|
|
end
|
|
clearInventory(returnChests)
|
|
return {
|
|
type = "craft_result",
|
|
success = false,
|
|
error = "Failed to pull ingredients",
|
|
returnedItems = returnedItems,
|
|
}
|
|
end
|
|
|
|
-- 3. Craft
|
|
print("[CRAFT] Grid contents:")
|
|
print(describeGrid())
|
|
print("[CRAFT] Attempting craft...")
|
|
|
|
local craftOk, craftErr = turtle.craft()
|
|
|
|
if not craftOk then
|
|
print("[CRAFT] Failed: " .. tostring(craftErr))
|
|
-- Collect what's still in the grid for the master to track
|
|
local returnedItems = {}
|
|
for slot, itemName in pairs(placedItems) do
|
|
returnedItems[tostring(slot)] = itemName
|
|
end
|
|
-- Push ingredients back
|
|
clearInventory(returnChests)
|
|
return {
|
|
type = "craft_result",
|
|
success = false,
|
|
error = "Craft failed: " .. tostring(craftErr),
|
|
returnedItems = returnedItems,
|
|
}
|
|
end
|
|
|
|
-- 4. Collect results info
|
|
local results = {}
|
|
local totalOutput = 0
|
|
for slot = 1, 16 do
|
|
local detail = turtle.getItemDetail(slot)
|
|
if detail then
|
|
table.insert(results, { name = detail.name, count = detail.count, slot = slot })
|
|
totalOutput = totalOutput + detail.count
|
|
end
|
|
end
|
|
|
|
print("[CRAFT] Success! Output:")
|
|
for _, r in ipairs(results) do
|
|
print(string.format(" %s x%d", r.name, r.count))
|
|
end
|
|
|
|
-- 5. Push results back to chests
|
|
local pushed = clearInventory(returnChests)
|
|
print(string.format("[CRAFT] Pushed %d items back to storage", pushed))
|
|
|
|
return {
|
|
type = "craft_result",
|
|
success = true,
|
|
output = output,
|
|
totalOutput = totalOutput,
|
|
results = results,
|
|
}
|
|
end
|
|
|
|
-------------------------------------------------
|
|
-- Main loop: listen for modem commands
|
|
-------------------------------------------------
|
|
|
|
while true do
|
|
local event, side, channel, replyChannel, message, distance = os.pullEvent("modem_message")
|
|
|
|
if channel == CRAFT_CHANNEL and type(message) == "table" then
|
|
if message.type == "craft_request" then
|
|
local result = handleCraftCommand(message)
|
|
-- Send result back to master
|
|
modem.transmit(CRAFT_REPLY_CHANNEL, CRAFT_CHANNEL, result)
|
|
elseif message.type == "ping" then
|
|
-- Health check from master
|
|
modem.transmit(CRAFT_REPLY_CHANNEL, CRAFT_CHANNEL, {
|
|
type = "pong",
|
|
name = selfName,
|
|
})
|
|
end
|
|
end
|
|
end
|