diff --git a/inventoryClient.lua b/inventoryClient.lua index 751a694..8624eb9 100644 --- a/inventoryClient.lua +++ b/inventoryClient.lua @@ -38,12 +38,15 @@ local activity = { smelting = false, defragging = false, composting = false, + crafting = false, } local activeAlerts = {} local smeltingPaused = false local disabledRecipes = {} local SMELTABLE = {} -- populated from master broadcast +local CRAFTABLE = {} -- populated from master broadcast +local craftTurtleOk = false local connected = false -- true once first state received ------------------------------------------------- @@ -175,6 +178,67 @@ local function hitTest(x, y) return nil, nil end +------------------------------------------------- +-- Crafting helpers (display-only, no peripheral calls) +------------------------------------------------- + +local function getRecipeIngredients(recipe) + local ingredients = {} + for _, item in ipairs(recipe.grid) do + if item then + ingredients[item] = (ingredients[item] or 0) + 1 + end + end + return ingredients +end + +local function canCraftRecipe(recipe) + local ingredients = getRecipeIngredients(recipe) + local itemTotals = {} + for _, item in ipairs(cache.itemList) do + itemTotals[item.name] = item.total + end + for itemName, needed in pairs(ingredients) do + if (itemTotals[itemName] or 0) < needed then return false end + end + return true +end + +local function maxCraftBatches(recipe) + local ingredients = getRecipeIngredients(recipe) + local itemTotals = {} + for _, item in ipairs(cache.itemList) do + itemTotals[item.name] = item.total + end + local minBatches = math.huge + for itemName, needed in pairs(ingredients) do + local batches = math.floor((itemTotals[itemName] or 0) / needed) + if batches < minBatches then minBatches = batches end + end + if minBatches == math.huge then return 0 end + return minBatches +end + +local function getMissingIngredients(recipe) + local ingredients = getRecipeIngredients(recipe) + local itemTotals = {} + for _, item in ipairs(cache.itemList) do + itemTotals[item.name] = item.total + end + local missing = {} + for itemName, needed in pairs(ingredients) do + local have = itemTotals[itemName] or 0 + if have < needed then + table.insert(missing, { name = itemName, have = have, need = needed }) + end + end + return missing +end + +------------------------------------------------- +-- Touch zone helpers +------------------------------------------------- + local function addSmelterZone(x1, y1, x2, y2, action, data) table.insert(smelterPendingZones, { x1 = x1, y1 = y1, x2 = x2, y2 = y2, @@ -599,14 +663,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 ===== @@ -705,8 +777,8 @@ local function drawSmelterDashboard() row = row + 1 end - else - -- ===== Recipe Manager View ===== + elseif smelterView == "smelt" then + -- ===== Smelt Recipe Manager View ===== -- Build item totals lookup from itemList local itemTotals = {} for _, item in ipairs(cache.itemList) do @@ -800,6 +872,163 @@ local function drawSmelterDashboard() row = row + 1 end + while row <= h - 2 do + monFill(row, colors.black) + row = row + 1 + end + + elseif smelterView == "craft" then + -- ===== Available Crafting Recipes ===== + + -- Turtle status on tab row + local tLabel = craftTurtleOk and " Turtle OK " or " No Turtle " + local tBg = craftTurtleOk and colors.lime or colors.red + local tFg = craftTurtleOk and colors.black or colors.white + monWrite(w - #tLabel, 4, tLabel, tFg, tBg) + + 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 + + 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) + + if craftTurtleOk 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 ===== + + 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) + 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 + + 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) + + 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 @@ -822,14 +1051,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) draw.setVisible(true) @@ -966,6 +1208,15 @@ local function handleSmelterTouch(x, y) smelterPage = smelterPage + 1 end smelterNeedsRedraw = true + + elseif action == "craft" then + sendToMaster({ type = "craft", recipeIdx = data }) + local recipe = CRAFTABLE[data] + if recipe then + local short = recipe.output:gsub("^minecraft:", ""):gsub("_", " ") + print("[CRAFT-UI] Craft request sent: " .. short) + end + smelterNeedsRedraw = true end end @@ -1051,6 +1302,12 @@ local function main() if message.smeltable then SMELTABLE = message.smeltable end + if message.craftable then + CRAFTABLE = message.craftable + end + if message.craftTurtleOk ~= nil then + craftTurtleOk = message.craftTurtleOk + end if not connected then connected = true @@ -1071,6 +1328,18 @@ local function main() else print("[WARN] " .. statusMessage) end + + elseif channel == CLIENT_CHANNEL and type(message) == "table" and message.type == "craft_result" then + -- Craft result from master + statusMessage = message.message or "" + statusColor = message.success and colors.lime or colors.red + statusTimer = 5 + smelterNeedsRedraw = true + if message.success then + print("[OK] " .. statusMessage) + else + print("[WARN] " .. statusMessage) + end end end end,