Compare commits

...

3 Commits

2 changed files with 290 additions and 13 deletions

45
craftingTurtle.lua Normal file
View File

@@ -0,0 +1,45 @@
-- Crafting Turtle Script
-- Run this on the crafting turtle (turtle with crafting table).
-- Listens for craft commands from the master computer via wired modem.
local CRAFT_CHANNEL = 4203
local CRAFT_REPLY_CHANNEL = 4204
-- Find modem (wired or wireless)
local modem = peripheral.find("modem")
if not modem then
print("[ERR] No modem found! Attach a wired modem.")
return
end
modem.open(CRAFT_CHANNEL)
print("=================================")
print(" Crafting Turtle (Listener)")
print("=================================")
print("")
print("Listening on channel " .. CRAFT_CHANNEL)
print("Ready for craft commands from master.")
print("")
while true do
local event, side, channel, replyChannel, message = os.pullEvent("modem_message")
if channel == CRAFT_CHANNEL and type(message) == "table" and message.type == "craft" then
local count = message.count or 1
print(string.format("[CRAFT] Received craft request (count=%d)", count))
local ok, err = turtle.craft(count)
if ok then
print("[CRAFT] Success!")
else
print("[CRAFT] Failed: " .. tostring(err))
end
modem.transmit(replyChannel, CRAFT_CHANNEL, {
type = "craft_result",
success = ok,
error = not ok and tostring(err) or nil,
})
end
end

View File

@@ -1635,14 +1635,22 @@ local function drawSmelterDashboard()
-- ===== Tab row =====
monFill(4, colors.black)
local tabStatusBg = smelterView == "status" and colors.purple or colors.gray
local tabRecipesBg = smelterView == "recipes" and colors.purple or colors.gray
local tabStatusBg = smelterView == "status" and colors.purple or colors.gray
local tabSmeltBg = smelterView == "smelt" and colors.purple or colors.gray
local tabCraftBg = smelterView == "craft" and colors.purple or colors.gray
local tabMissingBg = smelterView == "missing" and colors.purple or colors.gray
local bx1, by1, bx2, by2
bx1, by1, bx2, by2 = drawButton(2, 4, "Status", colors.white, tabStatusBg)
addSmelterZone(bx1, by1, bx2, by2, "tab", "status")
bx1, by1, bx2, by2 = drawButton(bx2 + 2, 4, "Recipes", colors.white, tabRecipesBg)
addSmelterZone(bx1, by1, bx2, by2, "tab", "recipes")
bx1, by1, bx2, by2 = drawButton(bx2 + 2, 4, "Smelt", colors.white, tabSmeltBg)
addSmelterZone(bx1, by1, bx2, by2, "tab", "smelt")
bx1, by1, bx2, by2 = drawButton(bx2 + 2, 4, "Craft", colors.white, tabCraftBg)
addSmelterZone(bx1, by1, bx2, by2, "tab", "craft")
bx1, by1, bx2, by2 = drawButton(bx2 + 2, 4, "Missing", colors.white, tabMissingBg)
addSmelterZone(bx1, by1, bx2, by2, "tab", "missing")
if smelterView == "status" then
-- ===== Furnace Status View =====
@@ -1750,8 +1758,8 @@ local function drawSmelterDashboard()
row = row + 1
end
else
-- ===== Recipe Manager View =====
elseif smelterView == "smelt" then
-- ===== Smelt Recipe Manager View =====
-- Build sorted recipe list
local recipeList = {}
@@ -1861,6 +1869,171 @@ local function drawSmelterDashboard()
monFill(row, colors.black)
row = row + 1
end
elseif smelterView == "craft" then
-- ===== Available Crafting Recipes =====
-- Turtle status on tab row
local turtleOk = craftTurtleName and peripheral.wrap(craftTurtleName) ~= nil
local tLabel = turtleOk and " Turtle OK " or " No Turtle "
local tBg = turtleOk and colors.lime or colors.red
local tFg = turtleOk and colors.black or colors.white
monWrite(w - #tLabel, 4, tLabel, tFg, tBg)
-- Build list of craftable recipes
local availList = {}
for idx, recipe in ipairs(CRAFTABLE) do
if canCraftRecipe(recipe) then
local short = recipe.output:gsub("^minecraft:", ""):gsub("_", " ")
short = short:sub(1,1):upper() .. short:sub(2)
local batches = maxCraftBatches(recipe)
table.insert(availList, {
idx = idx,
short = short,
count = recipe.count,
batches = batches,
})
end
end
-- Column headers
monFill(5, colors.gray)
local makeCol = w - 6
monWrite(2, 5, "#", colors.lightGray, colors.gray)
monWrite(4, 5, "Output", colors.lightGray, colors.gray)
monWrite(math.floor(w * 0.45), 5, "Yield", colors.lightGray, colors.gray)
monWrite(math.floor(w * 0.60), 5, "Can Make", colors.lightGray, colors.gray)
monWrite(makeCol, 5, "Go", colors.lightGray, colors.gray)
local maxRows = h - 8
if maxRows < 1 then maxRows = 1 end
smelterTotalPages = math.max(1, math.ceil(#availList / maxRows))
if smelterPage > smelterTotalPages then smelterPage = smelterTotalPages end
if smelterPage < 1 then smelterPage = 1 end
local startIdx = (smelterPage - 1) * maxRows + 1
local endIdx = math.min(startIdx + maxRows - 1, #availList)
local row = 6
if #availList == 0 then
monFill(7, colors.black)
monCenter(7, "No recipes available to craft", colors.gray, colors.black)
row = 8
else
for i = startIdx, endIdx do
local r = availList[i]
local y = row
local rowBg = ((i - startIdx) % 2 == 0) and colors.black or colors.gray
monFill(y, rowBg)
monWrite(2, y, string.format("%2d", i), colors.lightBlue, rowBg)
local maxNameLen = math.floor(w * 0.40)
local nameDisplay = r.short
if #nameDisplay > maxNameLen then
nameDisplay = nameDisplay:sub(1, maxNameLen - 2) .. ".."
end
monWrite(4, y, nameDisplay, colors.white, rowBg)
monWrite(math.floor(w * 0.45), y, "x" .. r.count, colors.yellow, rowBg)
monWrite(math.floor(w * 0.60), y,
string.format("x%d", r.batches), colors.lime, rowBg)
-- MAKE button
if turtleOk then
monWrite(makeCol, y, " MAKE ", colors.white, colors.green)
addSmelterZone(makeCol, y, makeCol + 5, y, "craft", r.idx)
else
monWrite(makeCol, y, " ---- ", colors.gray, colors.black)
end
row = row + 1
end
end
while row <= h - 2 do
monFill(row, colors.black)
row = row + 1
end
elseif smelterView == "missing" then
-- ===== Unavailable Crafting Recipes =====
-- Build list of recipes that CANNOT be crafted
local missList = {}
for idx, recipe in ipairs(CRAFTABLE) do
if not canCraftRecipe(recipe) then
local short = recipe.output:gsub("^minecraft:", ""):gsub("_", " ")
short = short:sub(1,1):upper() .. short:sub(2)
local missing = getMissingIngredients(recipe)
-- Build summary string
local parts = {}
for _, m in ipairs(missing) do
local mShort = m.name:gsub("^minecraft:", ""):gsub("_", " ")
table.insert(parts, string.format("%s %d/%d", mShort, m.have, m.need))
end
table.insert(missList, {
idx = idx,
short = short,
count = recipe.count,
summary = table.concat(parts, ", "),
})
end
end
-- Column headers
monFill(5, colors.gray)
monWrite(2, 5, "#", colors.lightGray, colors.gray)
monWrite(4, 5, "Output", colors.lightGray, colors.gray)
monWrite(math.floor(w * 0.35), 5, "Missing (have/need)", colors.lightGray, colors.gray)
local maxRows = h - 8
if maxRows < 1 then maxRows = 1 end
smelterTotalPages = math.max(1, math.ceil(#missList / maxRows))
if smelterPage > smelterTotalPages then smelterPage = smelterTotalPages end
if smelterPage < 1 then smelterPage = 1 end
local startIdx = (smelterPage - 1) * maxRows + 1
local endIdx = math.min(startIdx + maxRows - 1, #missList)
local row = 6
if #missList == 0 then
monFill(7, colors.black)
monCenter(7, "All recipes can be crafted!", colors.lime, colors.black)
row = 8
else
for i = startIdx, endIdx do
local r = missList[i]
local y = row
local rowBg = ((i - startIdx) % 2 == 0) and colors.black or colors.gray
monFill(y, rowBg)
monWrite(2, y, string.format("%2d", i), colors.lightBlue, rowBg)
local nameCol = math.floor(w * 0.35) - 5
local nameDisplay = r.short .. " x" .. r.count
if #nameDisplay > nameCol then
nameDisplay = nameDisplay:sub(1, nameCol - 2) .. ".."
end
monWrite(4, y, nameDisplay, colors.white, rowBg)
-- Missing items summary
local missCol = math.floor(w * 0.35)
local missW = w - missCol - 1
local summaryDisplay = r.summary
if #summaryDisplay > missW then
summaryDisplay = summaryDisplay:sub(1, missW - 2) .. ".."
end
monWrite(missCol, y, summaryDisplay, colors.red, rowBg)
row = row + 1
end
end
while row <= h - 2 do
monFill(row, colors.black)
row = row + 1
end
end
-- ===== Pagination (h - 1) =====
@@ -1879,14 +2052,27 @@ local function drawSmelterDashboard()
-- ===== Bottom accent =====
monFill(h, colors.purple)
local enabledCount = 0
local totalRecipes = 0
for _ in pairs(SMELTABLE) do totalRecipes = totalRecipes + 1 end
for inputName in pairs(SMELTABLE) do
if not disabledRecipes[inputName] then enabledCount = enabledCount + 1 end
local bottomMsg = ""
if smelterView == "status" or smelterView == "smelt" then
local enabledCount = 0
local totalRecipes = 0
for _ in pairs(SMELTABLE) do totalRecipes = totalRecipes + 1 end
for inputName in pairs(SMELTABLE) do
if not disabledRecipes[inputName] then enabledCount = enabledCount + 1 end
end
bottomMsg = string.format(" Smelt: %d/%d enabled ", enabledCount, totalRecipes)
if activity.smelting then bottomMsg = " SMELTING... " end
elseif smelterView == "craft" then
bottomMsg = " Tap MAKE to craft "
if activity.crafting then bottomMsg = " CRAFTING... " end
elseif smelterView == "missing" then
local totalC = #CRAFTABLE
local availC = 0
for _, r in ipairs(CRAFTABLE) do
if canCraftRecipe(r) then availC = availC + 1 end
end
bottomMsg = string.format(" Available: %d/%d recipes ", availC, totalC)
end
local bottomMsg = string.format(" Recipes: %d/%d enabled ", enabledCount, totalRecipes)
if activity.smelting then bottomMsg = " SMELTING... " end
monCenter(h, bottomMsg, colors.pink, colors.purple)
-- Flush to monitor
@@ -2563,6 +2749,25 @@ local function handleSmelterTouch(x, y)
smelterPage = smelterPage + 1
end
smelterNeedsRedraw = true
elseif action == "craft" then
local recipeIdx = data
local recipe = CRAFTABLE[recipeIdx]
if recipe then
local short = recipe.output:gsub("^minecraft:", ""):gsub("_", " ")
print(string.format("[CRAFT-UI] Craft request: %s (#%d)", short, recipeIdx))
local ok, err = craftItem(recipeIdx)
if ok then
statusMessage = "Crafted " .. short .. " x" .. recipe.count
statusColor = colors.lime
else
statusMessage = "Craft failed: " .. (err or "unknown")
statusColor = colors.red
end
statusTimer = 5
needsRedraw = true
smelterNeedsRedraw = true
end
end
end
@@ -2592,6 +2797,8 @@ local function broadcastState()
smeltingPaused = smeltingPaused,
disabledRecipes = disabledRecipes,
smeltable = SMELTABLE,
craftable = CRAFTABLE,
craftTurtleOk = craftTurtleName and peripheral.wrap(craftTurtleName) ~= nil,
}
networkModem.transmit(BROADCAST_CHANNEL, ORDER_CHANNEL, state)
end
@@ -2636,6 +2843,7 @@ local function main()
networkModem = peripheral.wrap(name)
networkModemName = name
networkModem.open(ORDER_CHANNEL)
networkModem.open(CRAFT_REPLY_CHANNEL)
break
end
end
@@ -2645,6 +2853,19 @@ local function main()
print("[WARN] No modem found for client sync")
end
-- Detect crafting turtle on network
for _, name in ipairs(peripheral.getNames()) do
if name:match("^turtle_") then
craftTurtleName = name
break
end
end
if craftTurtleName then
print("[OK] Crafting turtle: " .. craftTurtleName)
else
print("[WARN] No crafting turtle found")
end
-- Load recipe toggles from disk
loadDisabledRecipes()
if smeltingPaused then
@@ -2946,6 +3167,17 @@ local function main()
saveDisabledRecipes()
smelterNeedsRedraw = true
pcall(broadcastState)
elseif message.type == "craft" and message.recipeIdx then
print(string.format("[NET] Craft request: recipe #%d", message.recipeIdx))
local ok, err = craftItem(message.recipeIdx)
networkModem.transmit(replyChannel, ORDER_CHANNEL, {
type = "craft_result",
success = ok,
error = err,
})
smelterNeedsRedraw = true
needsRedraw = true
pcall(broadcastState)
end
end
end