Enhance crafting functionality: implement modem communication for turtle crafting requests and responses
This commit is contained in:
@@ -1593,9 +1593,12 @@ local function getMissingIngredients(recipe)
|
||||
return missing
|
||||
end
|
||||
|
||||
--- Execute a craft via the networked turtle
|
||||
-- Uses chest-side pullItems/pushItems to move items to/from the turtle,
|
||||
-- since remote turtles may not expose the inventory API (list/pushItems).
|
||||
--- Execute a craft via the networked turtle.
|
||||
-- Sends a craft_request message via modem with ingredient locations.
|
||||
-- The turtle pulls items herself, crafts, pushes results back, and replies.
|
||||
-- Requires the turtle to have a wired modem and run the updated craftingTurtle.lua.
|
||||
local CRAFT_TIMEOUT = 15 -- seconds to wait for turtle reply
|
||||
|
||||
local function craftItem(recipeIdx)
|
||||
local recipe = CRAFTABLE[recipeIdx]
|
||||
if not recipe then
|
||||
@@ -1606,6 +1609,10 @@ local function craftItem(recipeIdx)
|
||||
print("[CRAFT] No turtle detected on network")
|
||||
return false, "No turtle"
|
||||
end
|
||||
if not networkModem then
|
||||
print("[CRAFT] No modem available for craft commands")
|
||||
return false, "No modem"
|
||||
end
|
||||
|
||||
-- Verify the turtle is still on the network
|
||||
if not peripheral.isPresent(craftTurtleName) then
|
||||
@@ -1621,124 +1628,85 @@ local function craftItem(recipeIdx)
|
||||
|
||||
local chests = getChests()
|
||||
|
||||
-- Helper: pull all 16 turtle slots into chests (chest-side)
|
||||
local function clearTurtle(knownItems)
|
||||
for slot = 1, 16 do
|
||||
for _, ch in ipairs(chests) do
|
||||
local chest = peripheral.wrap(ch)
|
||||
if chest then
|
||||
local n = chest.pullItems(craftTurtleName, slot)
|
||||
if n and n > 0 then
|
||||
if knownItems and knownItems[slot] then
|
||||
adjustCache(knownItems[slot], ch, n)
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Build the slot map: for each grid position that needs an ingredient,
|
||||
-- find the exact chest and slot where the item is located.
|
||||
local slotMap = {} -- turtleSlot -> { chestName, chestSlot, itemName, count }
|
||||
local reservedSlots = {} -- "chestName:slot" -> true (prevent double-booking)
|
||||
|
||||
-- 1. Clear turtle inventory (safety – turtle should be empty)
|
||||
clearTurtle(nil)
|
||||
|
||||
-- 2. Push ingredients into correct grid slots, track what we placed
|
||||
local placedItems = {} -- turtleSlot -> itemName
|
||||
for gridPos = 1, 9 do
|
||||
local itemName = recipe.grid[gridPos]
|
||||
if itemName then
|
||||
local turtleSlot = GRID_TO_SLOT[gridPos]
|
||||
local placed = false
|
||||
local found = 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
|
||||
print(string.format("[CRAFT] Pushing %s from %s slot %d -> turtle slot %d", itemName, source.chest, slot, turtleSlot))
|
||||
local ok, n = pcall(chest.pushItems, craftTurtleName, slot, 1, turtleSlot)
|
||||
if ok and n and n > 0 then
|
||||
adjustCache(itemName, source.chest, -n)
|
||||
placedItems[turtleSlot] = itemName
|
||||
placed = true
|
||||
print(string.format("[CRAFT] Placed %s in turtle slot %d", itemName, turtleSlot))
|
||||
break
|
||||
elseif not ok then
|
||||
print(string.format("[CRAFT] pushItems error: %s", tostring(n)))
|
||||
else
|
||||
print(string.format("[CRAFT] pushItems returned %s", tostring(n)))
|
||||
end
|
||||
local key = source.chest .. ":" .. slot
|
||||
if slotItem.name == itemName and not reservedSlots[key] then
|
||||
slotMap[tostring(turtleSlot)] = {
|
||||
chestName = source.chest,
|
||||
chestSlot = slot,
|
||||
itemName = itemName,
|
||||
count = 1,
|
||||
}
|
||||
reservedSlots[key] = true
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if placed then break end
|
||||
if found then break end
|
||||
end
|
||||
else
|
||||
print(string.format("[CRAFT] Item %s not in catalogue!", itemName))
|
||||
end
|
||||
if not placed then
|
||||
-- Cleanup: pull placed items back using tracked names
|
||||
print("[CRAFT] Missing ingredient, aborting")
|
||||
clearTurtle(placedItems)
|
||||
|
||||
if not found then
|
||||
print(string.format("[CRAFT] Cannot find %s in storage, aborting", itemName))
|
||||
activity.crafting = false
|
||||
needsRedraw = true
|
||||
smelterNeedsRedraw = true
|
||||
return false, "Missing ingredient"
|
||||
return false, "Missing ingredient: " .. itemName
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 3. Wait for turtle to auto-craft
|
||||
-- The turtle polls its crafting slots and crafts automatically.
|
||||
-- Give it time: SETTLE_DELAY (1s) + craft time + margin.
|
||||
print(string.format("[CRAFT] Ingredients placed for %s, waiting for turtle...", recipe.output))
|
||||
sleep(3)
|
||||
-- Send craft request to turtle via modem
|
||||
local craftMessage = {
|
||||
type = "craft_request",
|
||||
recipeIdx = recipeIdx,
|
||||
output = recipe.output,
|
||||
slots = slotMap,
|
||||
returnChests = chests,
|
||||
}
|
||||
|
||||
-- 4. Pull all items from turtle back to chests
|
||||
-- Strategy: first check crafting grid slots. If they're empty,
|
||||
-- the turtle consumed the ingredients = craft succeeded.
|
||||
-- Then pull everything remaining (output items or leftover ingredients).
|
||||
print(string.format("[CRAFT] Sending craft request to turtle on channel %d", CRAFT_CHANNEL))
|
||||
networkModem.transmit(CRAFT_CHANNEL, CRAFT_REPLY_CHANNEL, craftMessage)
|
||||
|
||||
local ingredientsRemain = false
|
||||
|
||||
-- Check crafting grid slots first
|
||||
for gridPos = 1, 9 do
|
||||
if placedItems[GRID_TO_SLOT[gridPos]] then
|
||||
for _, ch in ipairs(chests) do
|
||||
local chest = peripheral.wrap(ch)
|
||||
if chest then
|
||||
local n = chest.pullItems(craftTurtleName, GRID_TO_SLOT[gridPos])
|
||||
if n and n > 0 then
|
||||
-- Ingredient still there = craft failed for this slot
|
||||
ingredientsRemain = true
|
||||
local itemName = placedItems[GRID_TO_SLOT[gridPos]]
|
||||
adjustCache(itemName, ch, n)
|
||||
print(string.format("[CRAFT] Ingredient returned: %s x%d from slot %d", itemName, n, GRID_TO_SLOT[gridPos]))
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Adjust cache: mark ingredients as "in transit" (removed from chests)
|
||||
for _, info in pairs(slotMap) do
|
||||
adjustCache(info.itemName, info.chestName, -info.count)
|
||||
end
|
||||
|
||||
local success = not ingredientsRemain
|
||||
local pulledOutput = 0
|
||||
-- Wait for reply from turtle with timeout
|
||||
print(string.format("[CRAFT] Waiting for turtle reply (timeout: %ds)...", CRAFT_TIMEOUT))
|
||||
local deadline = os.clock() + CRAFT_TIMEOUT
|
||||
local result = nil
|
||||
|
||||
-- Pull everything remaining (output on success, or stray items)
|
||||
for slot = 1, 16 do
|
||||
for _, ch in ipairs(chests) do
|
||||
local chest = peripheral.wrap(ch)
|
||||
if chest then
|
||||
local n = chest.pullItems(craftTurtleName, slot)
|
||||
if n and n > 0 then
|
||||
-- On success these are crafted output; on failure, leftovers
|
||||
local itemName = success and recipe.output or (placedItems[slot] or recipe.output)
|
||||
adjustCache(itemName, ch, n)
|
||||
if success then pulledOutput = pulledOutput + n end
|
||||
print(string.format("[CRAFT] Pulled %s x%d from slot %d -> %s", itemName, n, slot, ch))
|
||||
break
|
||||
end
|
||||
while os.clock() < deadline do
|
||||
local timerId = os.startTimer(math.max(0.1, deadline - os.clock()))
|
||||
local event, p1, p2, p3, p4 = os.pullEvent()
|
||||
|
||||
if event == "modem_message" then
|
||||
local channel = p2
|
||||
local message = p4
|
||||
if channel == CRAFT_REPLY_CHANNEL and type(message) == "table" and message.type == "craft_result" then
|
||||
result = message
|
||||
break
|
||||
end
|
||||
elseif event == "timer" and p1 == timerId then
|
||||
-- Timeout tick, loop will check deadline
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1746,13 +1714,68 @@ local function craftItem(recipeIdx)
|
||||
needsRedraw = true
|
||||
smelterNeedsRedraw = true
|
||||
|
||||
if success then
|
||||
if not result then
|
||||
-- Timeout — turtle didn't respond. Items may be stuck in turtle.
|
||||
-- We've already adjusted the cache as if ingredients were consumed.
|
||||
-- A manual scan will reconcile later.
|
||||
print("[CRAFT] TIMEOUT: No reply from turtle within " .. CRAFT_TIMEOUT .. "s")
|
||||
print("[CRAFT] Items may be stuck in turtle. Run a manual scan to reconcile.")
|
||||
return false, "Turtle timeout"
|
||||
end
|
||||
|
||||
if result.success then
|
||||
-- Craft succeeded. The turtle already pushed results back to chests.
|
||||
-- We need to credit the output items to the cache.
|
||||
-- The ingredients were already debited above.
|
||||
-- The turtle reports what it pushed; we credit the output item.
|
||||
local totalOutput = result.totalOutput or recipe.count
|
||||
-- Credit output across the chests (we don't know exactly which chest
|
||||
-- the turtle pushed to, so trigger a targeted rescan)
|
||||
-- For now, credit to the first available chest as an approximation.
|
||||
-- The next periodic scan will reconcile.
|
||||
if result.results then
|
||||
for _, r in ipairs(result.results) do
|
||||
-- Find which chest likely received it
|
||||
for _, ch in ipairs(chests) do
|
||||
local chest = peripheral.wrap(ch)
|
||||
if chest then
|
||||
for slot, slotItem in pairs(chest.list()) do
|
||||
if slotItem.name == r.name then
|
||||
-- Found the output in a chest, credit it
|
||||
adjustCache(r.name, ch, r.count)
|
||||
goto credited
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- If we can't find it, still credit to first chest for cache consistency
|
||||
if #chests > 0 then
|
||||
adjustCache(r.name, chests[1], r.count)
|
||||
end
|
||||
::credited::
|
||||
end
|
||||
else
|
||||
-- Fallback: credit the expected output
|
||||
if #chests > 0 then
|
||||
adjustCache(recipe.output, chests[1], totalOutput)
|
||||
end
|
||||
end
|
||||
|
||||
local short = recipe.output:gsub("^minecraft:", ""):gsub("_", " ")
|
||||
print(string.format("[CRAFT] OK: %s x%d", short, pulledOutput))
|
||||
print(string.format("[CRAFT] OK: %s x%d", short, totalOutput))
|
||||
return true
|
||||
else
|
||||
print("[CRAFT] Failed: ingredients were not consumed by turtle")
|
||||
return false, "Craft failed"
|
||||
-- Craft failed. The turtle returned ingredients to chests.
|
||||
-- We already debited the cache; now credit them back.
|
||||
-- The returned items are back in the chests, so re-credit them.
|
||||
for turtleSlotStr, info in pairs(slotMap) do
|
||||
-- Re-credit the ingredient (it was returned)
|
||||
adjustCache(info.itemName, info.chestName, info.count)
|
||||
end
|
||||
|
||||
local errMsg = result.error or "Craft failed"
|
||||
print("[CRAFT] Failed: " .. errMsg)
|
||||
return false, errMsg
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user