Files
Inventory-Manager-CC/inventoryManager.lua

512 lines
16 KiB
Lua

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