Files
Inventory-Manager-CC/inventoryManager.lua

443 lines
14 KiB
Lua

-- 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 <number> [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()