-- Inventory Manager: Auto-sort barrel & order items to dropper -- 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 ------------------------------------------------- -- Get all chest peripheral names on the network 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 -- Scan a single inventory: returns { [itemName] = { total=N, slots={ [slot]={name,count} } } } 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 -- Build a full catalogue: itemName -> { {chest=name, total=N}, ... } 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 Dashboard ------------------------------------------------- local mon = nil local buf = nil -- offscreen buffer to prevent flickering local function setupMonitor() mon = peripheral.wrap(MONITOR_SIDE) if not mon then return false end mon.setTextScale(0.5) mon.clear() return true end -- Drawing target (buf or mon, set in drawDashboard) local draw = nil -- Helpers (draw to buffer) local function monWrite(x, y, text, fg, bg) draw.setCursorPos(x, y) if fg then draw.setTextColor(fg) end if bg then draw.setBackgroundColor(bg) end draw.write(text) end local function monFill(y, color) local w, _ = draw.getSize() draw.setCursorPos(1, y) draw.setBackgroundColor(color) draw.write(string.rep(" ", w)) end local function monCenter(y, text, fg, bg) local w, _ = draw.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) draw.setCursorPos(x, y) draw.setBackgroundColor(barColor) draw.write(string.rep(" ", filled)) draw.setBackgroundColor(bgColor) draw.write(string.rep(" ", width - filled)) end -- Draw the full dashboard (double-buffered) local function drawDashboard() if not mon then return end local w, h = mon.getSize() -- Create offscreen buffer buf = window.create(mon, 1, 1, w, h, false) draw = buf -- Clear buffer draw.setBackgroundColor(colors.black) draw.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: --") local statusLine = table.concat(statusParts, " | ") monWrite(2, 2, statusLine, colors.white, colors.gray) -- Dropper/Barrel status indicators local indicatorX = w - 10 if indicatorX > #statusLine + 3 then monWrite(indicatorX, 2, dropperOk and " \7 " or " x ", dropperOk and colors.lime or colors.red, colors.gray) monWrite(indicatorX + 4, 2, barrelOk and " \7 " or " x ", barrelOk and colors.lime or colors.red, colors.gray) end -- ===== Divider ===== monFill(3, colors.lightBlue) monCenter(3, string.rep("\140", math.min(w - 4, 60)), colors.cyan, colors.lightBlue) -- ===== Storage capacity bar ===== -- Count total slots and used slots across all chests 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 local capY = 4 monFill(capY, colors.black) local capLabel = string.format(" Storage: %d/%d slots (%d free)", usedSlots, totalSlots, freeSlots) monWrite(2, capY, capLabel, colors.lightGray, colors.black) -- Draw capacity bar 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, capY, barWidth, usedRatio, barColor, colors.gray) -- Show percentage on the bar local pctStr = string.format(" %d%% ", math.floor(usedRatio * 100)) local pctX = barStart + math.floor(barWidth / 2) - math.floor(#pctStr / 2) monWrite(pctX, capY, pctStr, colors.white, barColor) end -- ===== Item catalogue ===== local catalogue = buildCatalogue() -- Collect and sort items 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, sources = #sources }) end table.sort(itemList, function(a, b) return a.total > b.total end) -- Header row local row = 6 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) row = row + 1 -- Find max for bar scaling 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 -- Item rows local maxRows = h - row - 3 -- leave space for footer for i, item in ipairs(itemList) do if i > maxRows then break end local y = row local short = item.name:gsub("^minecraft:", ""):gsub("_", " ") -- Capitalize first letter short = short:sub(1,1):upper() .. short:sub(2) -- Truncate if too long local maxNameLen = w - 30 if #short > maxNameLen then short = short:sub(1, maxNameLen - 2) .. ".." end -- Alternating row background local rowBg = (i % 2 == 0) and colors.gray or colors.black monFill(y, rowBg) -- Index number monWrite(2, y, string.format("%2d", i), colors.lightBlue, rowBg) -- Item name monWrite(5, y, short, colors.white, rowBg) -- Quantity local qtyStr = tostring(item.total) monWrite(w - 22, y, qtyStr, 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) row = row + 1 end -- ===== Footer ===== local footerY = h - 1 monFill(footerY, colors.gray) monWrite(2, footerY, string.format(" Total: %d items across %d types ", grandTotal, #itemList), colors.white, colors.gray) -- Timestamp 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, " \4 Auto-Sort Active \4 ", colors.lightBlue, colors.blue) -- Flush buffer to monitor in one go (no flicker) buf.setVisible(true) buf.setVisible(false) end ------------------------------------------------- -- Barrel auto-sort ------------------------------------------------- -- Move all items from the barrel into matching chests (or first chest with space) 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 -- Try chests that already hold this item first 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 some remain, push to any chest with space 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 ------------------------------------------------- -- Order items: move from chest(s) to dropper -- Computer 1 will auto-detect and dispense local function orderItem(itemName, amount) local catalogue = buildCatalogue() if not catalogue[itemName] then print("[ERR] Item not found: " .. itemName) return false end local dropper = peripheral.wrap(DROPPER_NAME) if not dropper then print("[ERR] Dropper not found: " .. DROPPER_NAME) return false end local remaining = amount for _, entry in ipairs(catalogue[itemName]) do local chest = peripheral.wrap(entry.chest) if chest then -- Find slots in this chest with the target item 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 -> dropper", itemName, moved, entry.chest)) end if remaining <= 0 then break end end end end if remaining <= 0 then break end end if remaining > 0 then print(string.format("[WARN] Only moved %d/%d of %s", amount - remaining, amount, itemName)) end print("[OK] Items in dropper. Computer 1 will dispense.") return true end ------------------------------------------------- -- Display ------------------------------------------------- local function showCatalogue() local catalogue = buildCatalogue() print("") print("=== Item Catalogue ===") print("") local index = 1 local items = {} for itemName, sources in pairs(catalogue) do local total = 0 for _, s in ipairs(sources) do total = total + s.total end -- Strip "minecraft:" prefix for cleaner display local short = itemName:gsub("^minecraft:", "") print(string.format(" %2d. %-30s x%d", index, short, total)) items[index] = itemName index = index + 1 end print("") return items end ------------------------------------------------- -- Main loop ------------------------------------------------- local function main() print("=================================") print(" Inventory Manager v2") print("=================================") print("") -- Check dropper if peripheral.wrap(DROPPER_NAME) then print("[OK] Dropper found: " .. DROPPER_NAME) else print("[WARN] Dropper not found: " .. DROPPER_NAME) end -- Check barrel if peripheral.wrap(BARREL_NAME) then print("[OK] Barrel found: " .. BARREL_NAME) else print("[WARN] Barrel not found: " .. BARREL_NAME) end -- Setup monitor if setupMonitor() then print("[OK] Monitor found on: " .. MONITOR_SIDE) else print("[WARN] No monitor on " .. MONITOR_SIDE .. ". Dashboard disabled.") end print("") -- Run barrel watcher, dashboard, and command prompt in parallel parallel.waitForAny( -- Task 1: Watch barrel for new items function() while true do sortBarrel() sleep(POLL_INTERVAL) end end, -- Task 2: Dashboard refresh function() while true do pcall(drawDashboard) sleep(DASH_REFRESH) end end, -- Task 3: Interactive command prompt function() while true do local items = showCatalogue() print("Commands:") print(" order [amount] - Dispense an item") print(" scan - Refresh catalogue") print(" quit - Exit") print("") write("> ") local input = read() local parts = {} for word in input:gmatch("%S+") do table.insert(parts, word) end local cmd = parts[1] if cmd == "order" or cmd == "o" then local idx = tonumber(parts[2]) local amt = tonumber(parts[3]) or 1 if idx and items[idx] then orderItem(items[idx], amt) else print("[ERR] Invalid item number.") end elseif cmd == "scan" or cmd == "s" then print("Rescanning...") elseif cmd == "quit" or cmd == "q" then print("Shutting down.") return else print("[ERR] Unknown command: " .. tostring(cmd)) end print("") end end ) end main()