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
|
return missing
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Execute a craft via the networked turtle
|
--- Execute a craft via the networked turtle.
|
||||||
-- Uses chest-side pullItems/pushItems to move items to/from the turtle,
|
-- Sends a craft_request message via modem with ingredient locations.
|
||||||
-- since remote turtles may not expose the inventory API (list/pushItems).
|
-- 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 function craftItem(recipeIdx)
|
||||||
local recipe = CRAFTABLE[recipeIdx]
|
local recipe = CRAFTABLE[recipeIdx]
|
||||||
if not recipe then
|
if not recipe then
|
||||||
@@ -1606,6 +1609,10 @@ local function craftItem(recipeIdx)
|
|||||||
print("[CRAFT] No turtle detected on network")
|
print("[CRAFT] No turtle detected on network")
|
||||||
return false, "No turtle"
|
return false, "No turtle"
|
||||||
end
|
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
|
-- Verify the turtle is still on the network
|
||||||
if not peripheral.isPresent(craftTurtleName) then
|
if not peripheral.isPresent(craftTurtleName) then
|
||||||
@@ -1621,124 +1628,85 @@ local function craftItem(recipeIdx)
|
|||||||
|
|
||||||
local chests = getChests()
|
local chests = getChests()
|
||||||
|
|
||||||
-- Helper: pull all 16 turtle slots into chests (chest-side)
|
-- Build the slot map: for each grid position that needs an ingredient,
|
||||||
local function clearTurtle(knownItems)
|
-- find the exact chest and slot where the item is located.
|
||||||
for slot = 1, 16 do
|
local slotMap = {} -- turtleSlot -> { chestName, chestSlot, itemName, count }
|
||||||
for _, ch in ipairs(chests) do
|
local reservedSlots = {} -- "chestName:slot" -> true (prevent double-booking)
|
||||||
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
|
|
||||||
|
|
||||||
-- 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
|
for gridPos = 1, 9 do
|
||||||
local itemName = recipe.grid[gridPos]
|
local itemName = recipe.grid[gridPos]
|
||||||
if itemName then
|
if itemName then
|
||||||
local turtleSlot = GRID_TO_SLOT[gridPos]
|
local turtleSlot = GRID_TO_SLOT[gridPos]
|
||||||
local placed = false
|
local found = false
|
||||||
|
|
||||||
if cache.catalogue[itemName] then
|
if cache.catalogue[itemName] then
|
||||||
for _, source in ipairs(cache.catalogue[itemName]) do
|
for _, source in ipairs(cache.catalogue[itemName]) do
|
||||||
local chest = peripheral.wrap(source.chest)
|
local chest = peripheral.wrap(source.chest)
|
||||||
if chest then
|
if chest then
|
||||||
for slot, slotItem in pairs(chest.list()) do
|
for slot, slotItem in pairs(chest.list()) do
|
||||||
if slotItem.name == itemName then
|
local key = source.chest .. ":" .. slot
|
||||||
print(string.format("[CRAFT] Pushing %s from %s slot %d -> turtle slot %d", itemName, source.chest, slot, turtleSlot))
|
if slotItem.name == itemName and not reservedSlots[key] then
|
||||||
local ok, n = pcall(chest.pushItems, craftTurtleName, slot, 1, turtleSlot)
|
slotMap[tostring(turtleSlot)] = {
|
||||||
if ok and n and n > 0 then
|
chestName = source.chest,
|
||||||
adjustCache(itemName, source.chest, -n)
|
chestSlot = slot,
|
||||||
placedItems[turtleSlot] = itemName
|
itemName = itemName,
|
||||||
placed = true
|
count = 1,
|
||||||
print(string.format("[CRAFT] Placed %s in turtle slot %d", itemName, turtleSlot))
|
}
|
||||||
break
|
reservedSlots[key] = true
|
||||||
elseif not ok then
|
found = true
|
||||||
print(string.format("[CRAFT] pushItems error: %s", tostring(n)))
|
break
|
||||||
else
|
|
||||||
print(string.format("[CRAFT] pushItems returned %s", tostring(n)))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if placed then break end
|
if found then break end
|
||||||
end
|
end
|
||||||
else
|
|
||||||
print(string.format("[CRAFT] Item %s not in catalogue!", itemName))
|
|
||||||
end
|
end
|
||||||
if not placed then
|
|
||||||
-- Cleanup: pull placed items back using tracked names
|
if not found then
|
||||||
print("[CRAFT] Missing ingredient, aborting")
|
print(string.format("[CRAFT] Cannot find %s in storage, aborting", itemName))
|
||||||
clearTurtle(placedItems)
|
|
||||||
activity.crafting = false
|
activity.crafting = false
|
||||||
needsRedraw = true
|
needsRedraw = true
|
||||||
smelterNeedsRedraw = true
|
smelterNeedsRedraw = true
|
||||||
return false, "Missing ingredient"
|
return false, "Missing ingredient: " .. itemName
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 3. Wait for turtle to auto-craft
|
-- Send craft request to turtle via modem
|
||||||
-- The turtle polls its crafting slots and crafts automatically.
|
local craftMessage = {
|
||||||
-- Give it time: SETTLE_DELAY (1s) + craft time + margin.
|
type = "craft_request",
|
||||||
print(string.format("[CRAFT] Ingredients placed for %s, waiting for turtle...", recipe.output))
|
recipeIdx = recipeIdx,
|
||||||
sleep(3)
|
output = recipe.output,
|
||||||
|
slots = slotMap,
|
||||||
|
returnChests = chests,
|
||||||
|
}
|
||||||
|
|
||||||
-- 4. Pull all items from turtle back to chests
|
print(string.format("[CRAFT] Sending craft request to turtle on channel %d", CRAFT_CHANNEL))
|
||||||
-- Strategy: first check crafting grid slots. If they're empty,
|
networkModem.transmit(CRAFT_CHANNEL, CRAFT_REPLY_CHANNEL, craftMessage)
|
||||||
-- the turtle consumed the ingredients = craft succeeded.
|
|
||||||
-- Then pull everything remaining (output items or leftover ingredients).
|
|
||||||
|
|
||||||
local ingredientsRemain = false
|
-- Adjust cache: mark ingredients as "in transit" (removed from chests)
|
||||||
|
for _, info in pairs(slotMap) do
|
||||||
-- Check crafting grid slots first
|
adjustCache(info.itemName, info.chestName, -info.count)
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local success = not ingredientsRemain
|
-- Wait for reply from turtle with timeout
|
||||||
local pulledOutput = 0
|
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)
|
while os.clock() < deadline do
|
||||||
for slot = 1, 16 do
|
local timerId = os.startTimer(math.max(0.1, deadline - os.clock()))
|
||||||
for _, ch in ipairs(chests) do
|
local event, p1, p2, p3, p4 = os.pullEvent()
|
||||||
local chest = peripheral.wrap(ch)
|
|
||||||
if chest then
|
if event == "modem_message" then
|
||||||
local n = chest.pullItems(craftTurtleName, slot)
|
local channel = p2
|
||||||
if n and n > 0 then
|
local message = p4
|
||||||
-- On success these are crafted output; on failure, leftovers
|
if channel == CRAFT_REPLY_CHANNEL and type(message) == "table" and message.type == "craft_result" then
|
||||||
local itemName = success and recipe.output or (placedItems[slot] or recipe.output)
|
result = message
|
||||||
adjustCache(itemName, ch, n)
|
break
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
elseif event == "timer" and p1 == timerId then
|
||||||
|
-- Timeout tick, loop will check deadline
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1746,13 +1714,68 @@ local function craftItem(recipeIdx)
|
|||||||
needsRedraw = true
|
needsRedraw = true
|
||||||
smelterNeedsRedraw = 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("_", " ")
|
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
|
return true
|
||||||
else
|
else
|
||||||
print("[CRAFT] Failed: ingredients were not consumed by turtle")
|
-- Craft failed. The turtle returned ingredients to chests.
|
||||||
return false, "Craft failed"
|
-- 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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user