Refactor inventory management to enhance device scanning, sorting, and dispensing functionality
This commit is contained in:
@@ -1,100 +1,294 @@
|
|||||||
-- Inventory Manager: Scan & Group by shared items
|
-- Inventory Manager: Auto-sort barrel, order & dispense via networked dropper
|
||||||
|
-- Main computer (networked). Computer 1 sits next to dropper_0 and runs dropperController.lua.
|
||||||
|
|
||||||
-- Scan all peripherals and categorize them
|
local DROPPER_NAME = "minecraft:dropper_0"
|
||||||
local function getInventoryDevices()
|
local BARREL_NAME = "minecraft:barrel_0"
|
||||||
local devices = {}
|
local MODEM_SIDE = nil -- auto-detected
|
||||||
for _, name in ipairs(peripheral.getNames()) do
|
local CONTROLLER_ID = 1 -- computer ID of the dropper controller
|
||||||
local pType = peripheral.getType(name)
|
local POLL_INTERVAL = 2 -- seconds between barrel checks
|
||||||
if pType == "minecraft:chest" or pType == "minecraft:dropper" then
|
local PROTOCOL = "inventory"
|
||||||
table.insert(devices, {
|
|
||||||
name = name,
|
-------------------------------------------------
|
||||||
type = pType,
|
-- 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
|
||||||
end
|
end
|
||||||
return devices
|
-- 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
|
end
|
||||||
|
|
||||||
-- Scan the contents of a single inventory peripheral
|
-------------------------------------------------
|
||||||
local function scanInventory(deviceName)
|
-- Inventory helpers
|
||||||
local items = {}
|
-------------------------------------------------
|
||||||
local inventory = peripheral.wrap(deviceName)
|
|
||||||
if not inventory then return items end
|
|
||||||
|
|
||||||
local contents = inventory.list()
|
-- Get all chest peripheral names on the network
|
||||||
for slot, item in pairs(contents) do
|
local function getChests()
|
||||||
if items[item.name] then
|
local chests = {}
|
||||||
items[item.name] = items[item.name] + item.count
|
for _, name in ipairs(peripheral.getNames()) do
|
||||||
else
|
if peripheral.getType(name) == "minecraft:chest" then
|
||||||
items[item.name] = item.count
|
table.insert(chests, name)
|
||||||
end
|
end
|
||||||
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
|
return items
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Build a map of item -> list of devices that contain it
|
-------------------------------------------------
|
||||||
local function buildItemMap(devices)
|
-- Main loop
|
||||||
local itemMap = {}
|
-------------------------------------------------
|
||||||
|
|
||||||
for _, device in ipairs(devices) do
|
local function main()
|
||||||
device.items = scanInventory(device.name)
|
print("=================================")
|
||||||
|
print(" Inventory Manager v2")
|
||||||
for itemName, count in pairs(device.items) do
|
print("=================================")
|
||||||
if not itemMap[itemName] then
|
|
||||||
itemMap[itemName] = {}
|
|
||||||
end
|
|
||||||
table.insert(itemMap[itemName], {
|
|
||||||
name = device.name,
|
|
||||||
type = device.type,
|
|
||||||
count = count,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return itemMap
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Display grouped results
|
|
||||||
local function displayGroups(itemMap)
|
|
||||||
print("=== Inventory Groups ===")
|
|
||||||
print("")
|
print("")
|
||||||
|
|
||||||
for itemName, devices in pairs(itemMap) do
|
-- Setup networking
|
||||||
local chests = {}
|
if not setupModem() then
|
||||||
local droppers = {}
|
print("[WARN] No modem found. Dispense signals won't work.")
|
||||||
|
else
|
||||||
|
print("[OK] Modem opened on: " .. tostring(MODEM_SIDE))
|
||||||
|
end
|
||||||
|
|
||||||
for _, device in ipairs(devices) do
|
-- Check dropper
|
||||||
if device.type == "minecraft:chest" then
|
if peripheral.wrap(DROPPER_NAME) then
|
||||||
table.insert(chests, device)
|
print("[OK] Dropper found: " .. DROPPER_NAME)
|
||||||
else
|
else
|
||||||
table.insert(droppers, device)
|
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
|
end
|
||||||
|
)
|
||||||
-- Mark as group when at least one chest AND one dropper share the item
|
|
||||||
local isGroup = #chests > 0 and #droppers > 0
|
|
||||||
local tag = isGroup and "[GROUP]" or "[-----]"
|
|
||||||
|
|
||||||
print(tag .. " " .. itemName)
|
|
||||||
|
|
||||||
for _, device in ipairs(devices) do
|
|
||||||
local label = device.type == "minecraft:chest" and "CHEST " or "DROPPER"
|
|
||||||
print(string.format(" %s %-35s x%d", label, device.name, device.count))
|
|
||||||
end
|
|
||||||
|
|
||||||
print("")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Main
|
main()
|
||||||
local devices = getInventoryDevices()
|
|
||||||
print("Scanning " .. #devices .. " inventories...")
|
|
||||||
print("")
|
|
||||||
|
|
||||||
if #devices == 0 then
|
|
||||||
print("No chests or droppers found on the network.")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local itemMap = buildItemMap(devices)
|
|
||||||
displayGroups(itemMap)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user