295 lines
9.3 KiB
Lua
295 lines
9.3 KiB
Lua
-- Inventory Manager: Auto-sort barrel, order & dispense via networked dropper
|
|
-- Main computer (networked). Computer 1 sits next to dropper_0 and runs dropperController.lua.
|
|
|
|
local DROPPER_NAME = "minecraft:dropper_9"
|
|
local BARREL_NAME = "minecraft:barrel_0"
|
|
local MODEM_SIDE = nil -- auto-detected
|
|
local CONTROLLER_ID = 1 -- computer ID of the dropper controller
|
|
local POLL_INTERVAL = 2 -- seconds between barrel checks
|
|
local PROTOCOL = "inventory"
|
|
|
|
-------------------------------------------------
|
|
-- Networking
|
|
-------------------------------------------------
|
|
|
|
-- Find and open the modem
|
|
local function setupModem()
|
|
for _, side in ipairs({"top","bottom","left","right","front","back"}) do
|
|
if peripheral.getType(side) == "modem" then
|
|
rednet.open(side)
|
|
MODEM_SIDE = side
|
|
return true
|
|
end
|
|
end
|
|
-- Also check for wired modems wrapped as peripherals
|
|
for _, name in ipairs(peripheral.getNames()) do
|
|
if peripheral.getType(name) == "modem" then
|
|
rednet.open(name)
|
|
MODEM_SIDE = name
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
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 & dispense
|
|
-------------------------------------------------
|
|
|
|
-- Send redstone trigger to computer 1 via rednet
|
|
local function triggerDropper()
|
|
rednet.send(CONTROLLER_ID, "dispense", PROTOCOL)
|
|
print("[NET] Sent dispense signal to computer " .. CONTROLLER_ID)
|
|
end
|
|
|
|
-- Order items: move from chest(s) to dropper, then trigger 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
|
|
|
|
-- Trigger computer 1 to fire the dropper
|
|
triggerDropper()
|
|
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("")
|
|
|
|
-- Setup networking
|
|
if not setupModem() then
|
|
print("[WARN] No modem found. Dispense signals won't work.")
|
|
else
|
|
print("[OK] Modem opened on: " .. tostring(MODEM_SIDE))
|
|
end
|
|
|
|
-- 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
|
|
|
|
print("")
|
|
|
|
-- Run barrel watcher 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: 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()
|