Enhance crafting functionality: implement modem communication for turtle crafting requests and responses

This commit is contained in:
MayaTheShy
2026-03-22 01:28:50 -04:00
parent cc6b1e999a
commit 98f8157bea

View File

@@ -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