-- Inventory Manager: Touch UI on monitor -- Main computer (networked). Computer 1 sits next to dropper_0 and auto-dispenses. local DROPPER_NAME = "minecraft:dropper_9" local BARREL_NAME = "minecraft:barrel_0" local POLL_INTERVAL = 2 -- seconds between barrel checks local MONITOR_SIDE = "left" local DASH_REFRESH = 3 -- seconds between dashboard refreshes ------------------------------------------------- -- Inventory helpers ------------------------------------------------- local function getChests() local chests = {} for _, name in ipairs(peripheral.getNames()) do if peripheral.getType(name) == "minecraft:chest" then table.insert(chests, name) end end return chests end local function scanInventory(deviceName) local inv = peripheral.wrap(deviceName) if not inv then return {} end local result = {} for slot, item in pairs(inv.list()) do if not result[item.name] then result[item.name] = { total = 0, slots = {} } end result[item.name].total = result[item.name].total + item.count result[item.name].slots[slot] = { name = item.name, count = item.count } end return result end local function buildCatalogue() local catalogue = {} for _, chest in ipairs(getChests()) do local contents = scanInventory(chest) for itemName, info in pairs(contents) do if not catalogue[itemName] then catalogue[itemName] = {} end table.insert(catalogue[itemName], { chest = chest, total = info.total }) end end return catalogue end ------------------------------------------------- -- Monitor setup ------------------------------------------------- local mon = nil local function setupMonitor() mon = peripheral.wrap(MONITOR_SIDE) if not mon then return false end mon.setTextScale(0.5) mon.clear() return true end ------------------------------------------------- -- UI State ------------------------------------------------- local selectedAmount = 1 local amountOptions = {1, 4, 8, 16, 32, 64} local statusMessage = "" local statusColor = colors.white local statusTimer = 0 -- Touch zones: list of {x1, y1, x2, y2, action, data} local touchZones = {} local function addZone(x1, y1, x2, y2, action, data) table.insert(touchZones, { x1 = x1, y1 = y1, x2 = x2, y2 = y2, action = action, data = data }) end local function hitTest(x, y) for _, zone in ipairs(touchZones) do if x >= zone.x1 and x <= zone.x2 and y >= zone.y1 and y <= zone.y2 then return zone.action, zone.data end end return nil, nil end ------------------------------------------------- -- Drawing helpers ------------------------------------------------- local function monWrite(x, y, text, fg, bg) mon.setCursorPos(x, y) if fg then mon.setTextColor(fg) end if bg then mon.setBackgroundColor(bg) end mon.write(text) end local function monFill(y, color) local w, _ = mon.getSize() mon.setCursorPos(1, y) mon.setBackgroundColor(color) mon.write(string.rep(" ", w)) end local function monCenter(y, text, fg, bg) local w, _ = mon.getSize() local x = math.floor((w - #text) / 2) + 1 monWrite(x, y, text, fg, bg) end local function monBar(x, y, width, ratio, barColor, bgColor) local filled = math.floor(ratio * width) mon.setCursorPos(x, y) mon.setBackgroundColor(barColor) mon.write(string.rep(" ", filled)) mon.setBackgroundColor(bgColor) mon.write(string.rep(" ", width - filled)) end local function drawButton(x, y, text, fg, bg, padLeft, padRight) padLeft = padLeft or 1 padRight = padRight or 1 local full = string.rep(" ", padLeft) .. text .. string.rep(" ", padRight) monWrite(x, y, full, fg, bg) return x, y, x + #full - 1, y end ------------------------------------------------- -- Dashboard ------------------------------------------------- -- Cached item list for touch mapping local cachedItems = {} local function drawDashboard() if not mon then return end local w, h = mon.getSize() touchZones = {} mon.setBackgroundColor(colors.black) mon.clear() -- ===== Title bar ===== monFill(1, colors.blue) monCenter(1, " ** INVENTORY MANAGER ** ", colors.white, colors.blue) -- ===== Status bar ===== monFill(2, colors.gray) local chests = getChests() local dropperOk = peripheral.wrap(DROPPER_NAME) ~= nil local barrelOk = peripheral.wrap(BARREL_NAME) ~= nil local statusParts = {} table.insert(statusParts, string.format(" Chests: %d", #chests)) table.insert(statusParts, dropperOk and "Dropper: OK" or "Dropper: --") table.insert(statusParts, barrelOk and "Barrel: OK" or "Barrel: --") monWrite(2, 2, table.concat(statusParts, " | "), colors.white, colors.gray) -- ===== Divider ===== monFill(3, colors.lightBlue) monCenter(3, string.rep("-", math.min(w - 4, 60)), colors.cyan, colors.lightBlue) -- ===== Storage capacity ===== local totalSlots = 0 local usedSlots = 0 for _, chestName in ipairs(chests) do local inv = peripheral.wrap(chestName) if inv then totalSlots = totalSlots + inv.size() for _ in pairs(inv.list()) do usedSlots = usedSlots + 1 end end end local freeSlots = totalSlots - usedSlots local usedRatio = totalSlots > 0 and (usedSlots / totalSlots) or 0 monFill(4, colors.black) local capLabel = string.format(" Storage: %d/%d slots (%d free)", usedSlots, totalSlots, freeSlots) monWrite(2, 4, capLabel, colors.lightGray, colors.black) local barStart = #capLabel + 4 local barWidth = w - barStart - 2 if barWidth > 4 then local barColor = colors.lime if usedRatio > 0.9 then barColor = colors.red elseif usedRatio > 0.7 then barColor = colors.orange elseif usedRatio > 0.5 then barColor = colors.yellow end monBar(barStart, 4, barWidth, usedRatio, barColor, colors.gray) local pctStr = string.format(" %d%% ", math.floor(usedRatio * 100)) local pctX = barStart + math.floor(barWidth / 2) - math.floor(#pctStr / 2) monWrite(pctX, 4, pctStr, colors.white, barColor) end -- ===== Amount selector (row 5) ===== monFill(5, colors.black) monWrite(2, 5, "Qty:", colors.lightGray, colors.black) local btnX = 7 for _, amt in ipairs(amountOptions) do local label = tostring(amt) local bg = (amt == selectedAmount) and colors.cyan or colors.gray local fg = (amt == selectedAmount) and colors.white or colors.lightGray local x1, y1, x2, y2 = drawButton(btnX, 5, label, fg, bg) addZone(x1, y1, x2, y2, "amount", amt) btnX = x2 + 2 end -- Scan button on the right local scanLabel = " Refresh " local scanX = w - #scanLabel - 1 local sx1, sy1, sx2, sy2 = drawButton(scanX, 5, "Refresh", colors.white, colors.green, 1, 1) addZone(sx1, sy1, sx2, sy2, "scan", nil) -- ===== Column headers (row 6) ===== local row = 7 monFill(row, colors.gray) monWrite(2, row, "#", colors.lightGray, colors.gray) monWrite(5, row, "Item", colors.lightGray, colors.gray) monWrite(w - 22, row, "Qty", colors.lightGray, colors.gray) monWrite(w - 14, row, "Stock", colors.lightGray, colors.gray) monWrite(w - 1, row, ">", colors.lightGray, colors.gray) row = row + 1 -- ===== Item catalogue ===== local catalogue = buildCatalogue() local itemList = {} local grandTotal = 0 for itemName, sources in pairs(catalogue) do local total = 0 for _, s in ipairs(sources) do total = total + s.total end grandTotal = grandTotal + total table.insert(itemList, { name = itemName, total = total }) end table.sort(itemList, function(a, b) return a.total > b.total end) cachedItems = itemList local maxCount = 0 for _, item in ipairs(itemList) do if item.total > maxCount then maxCount = item.total end end if maxCount == 0 then maxCount = 1 end local maxRows = h - row - 3 for i, item in ipairs(itemList) do if i > maxRows then break end local y = row local short = item.name:gsub("^minecraft:", ""):gsub("_", " ") short = short:sub(1,1):upper() .. short:sub(2) local maxNameLen = w - 30 if #short > maxNameLen then short = short:sub(1, maxNameLen - 2) .. ".." end local rowBg = (i % 2 == 0) and colors.gray or colors.black monFill(y, rowBg) -- Index monWrite(2, y, string.format("%2d", i), colors.lightBlue, rowBg) -- Name monWrite(5, y, short, colors.white, rowBg) -- Quantity monWrite(w - 22, y, tostring(item.total), colors.yellow, rowBg) -- Stock bar local ratio = item.total / maxCount local barColor = colors.lime if ratio < 0.25 then barColor = colors.red elseif ratio < 0.5 then barColor = colors.orange end monBar(w - 14, y, 12, ratio, barColor, rowBg == colors.gray and colors.lightGray or colors.gray) -- Order button monWrite(w - 1, y, ">", colors.orange, rowBg) -- Entire row is a touch zone addZone(1, y, w, y, "order", i) row = row + 1 end -- ===== Status message (above footer) ===== local msgY = h - 2 monFill(msgY, colors.black) if statusTimer > 0 and #statusMessage > 0 then monCenter(msgY, statusMessage, statusColor, colors.black) end -- ===== Footer ===== local footerY = h - 1 monFill(footerY, colors.gray) monWrite(2, footerY, string.format(" Total: %d items | %d types ", grandTotal, #itemList), colors.white, colors.gray) local timeStr = textutils.formatTime(os.time(), true) monWrite(w - #timeStr - 1, footerY, timeStr, colors.lightGray, colors.gray) -- Bottom accent monFill(h, colors.blue) monCenter(h, " Tap item to order ", colors.lightBlue, colors.blue) end ------------------------------------------------- -- Barrel auto-sort ------------------------------------------------- local function sortBarrel() local barrel = peripheral.wrap(BARREL_NAME) if not barrel then return end local contents = barrel.list() if not contents or not next(contents) then return end local catalogue = buildCatalogue() local chests = getChests() for slot, item in pairs(contents) do local moved = 0 if catalogue[item.name] then for _, entry in ipairs(catalogue[item.name]) do local n = barrel.pushItems(entry.chest, slot) if n and n > 0 then moved = moved + n print(string.format("[SORT] %s x%d -> %s", item.name, n, entry.chest)) end if moved >= item.count then break end end end if moved < item.count then for _, chest in ipairs(chests) do local n = barrel.pushItems(chest, slot) if n and n > 0 then moved = moved + n print(string.format("[SORT] %s x%d -> %s", item.name, n, chest)) end if moved >= item.count then break end end end if moved < item.count then print(string.format("[WARN] Could not sort %d remaining %s", item.count - moved, item.name)) end end end ------------------------------------------------- -- Order ------------------------------------------------- local function orderItem(itemName, amount) local catalogue = buildCatalogue() if not catalogue[itemName] then statusMessage = "Not found: " .. itemName:gsub("^minecraft:", "") statusColor = colors.red statusTimer = 3 print("[ERR] Item not found: " .. itemName) return false end local dropper = peripheral.wrap(DROPPER_NAME) if not dropper then statusMessage = "Dropper offline!" statusColor = colors.red statusTimer = 3 print("[ERR] Dropper not found") return false end local remaining = amount for _, entry in ipairs(catalogue[itemName]) do local chest = peripheral.wrap(entry.chest) if chest then for slot, slotItem in pairs(chest.list()) do if slotItem.name == itemName then local toMove = math.min(remaining, slotItem.count) local moved = chest.pushItems(DROPPER_NAME, slot, toMove) if moved and moved > 0 then remaining = remaining - moved print(string.format("[ORDER] %s x%d from %s", itemName, moved, entry.chest)) end if remaining <= 0 then break end end end end if remaining <= 0 then break end end local sent = amount - remaining local short = itemName:gsub("^minecraft:", ""):gsub("_", " ") if sent > 0 then statusMessage = string.format("Ordered %s x%d", short, sent) statusColor = colors.lime print(string.format("[OK] Ordered %s x%d", short, sent)) else statusMessage = "Could not order " .. short statusColor = colors.red end statusTimer = 3 return sent > 0 end ------------------------------------------------- -- Touch handler ------------------------------------------------- local function handleTouch(x, y) local action, data = hitTest(x, y) if not action then return end if action == "amount" then selectedAmount = data print("[UI] Amount set to " .. data) elseif action == "order" then local idx = data if cachedItems[idx] then local item = cachedItems[idx] orderItem(item.name, selectedAmount) end elseif action == "scan" then statusMessage = "Refreshing..." statusColor = colors.cyan statusTimer = 2 print("[UI] Manual refresh") end -- Redraw immediately after touch pcall(drawDashboard) end ------------------------------------------------- -- Main ------------------------------------------------- local function main() print("=================================") print(" Inventory Manager v2 (Touch)") print("=================================") print("") if peripheral.wrap(DROPPER_NAME) then print("[OK] Dropper: " .. DROPPER_NAME) else print("[WARN] Dropper not found: " .. DROPPER_NAME) end if peripheral.wrap(BARREL_NAME) then print("[OK] Barrel: " .. BARREL_NAME) else print("[WARN] Barrel not found: " .. BARREL_NAME) end if setupMonitor() then print("[OK] Monitor: " .. MONITOR_SIDE) else print("[WARN] No monitor on " .. MONITOR_SIDE) end print("") print("Console shows log output. Use the monitor to order items.") print("") parallel.waitForAny( -- Task 1: Barrel auto-sort function() while true do pcall(sortBarrel) sleep(POLL_INTERVAL) end end, -- Task 2: Dashboard refresh + status timer function() while true do pcall(drawDashboard) if statusTimer > 0 then statusTimer = statusTimer - DASH_REFRESH end sleep(DASH_REFRESH) end end, -- Task 3: Touch event listener function() while true do local event, side, x, y = os.pullEvent("monitor_touch") print(string.format("[TOUCH] x=%d y=%d", x, y)) handleTouch(x, y) end end ) end main()