-- 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 ------------------------------------------------- -- Monitor Dashboard ------------------------------------------------- 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 -- 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 -- Draw the full dashboard local function drawDashboard() if not mon then return end local w, h = mon.getSize() -- Clear 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: --") 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) -- ===== 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 = 5 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) end ------------------------------------------------- -- 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 = {} -- itemName -> list of {chest, total} 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 ------------------------------------------------- -- 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()