Compare commits
3 Commits
cd3e4e1fd9
...
10ec47611b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10ec47611b | ||
|
|
718cce0c7b | ||
|
|
ceb8ed8819 |
45
craftingTurtle.lua
Normal file
45
craftingTurtle.lua
Normal 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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user