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 function getInventoryDevices()
|
||||
local devices = {}
|
||||
for _, name in ipairs(peripheral.getNames()) do
|
||||
local pType = peripheral.getType(name)
|
||||
if pType == "minecraft:chest" or pType == "minecraft:dropper" then
|
||||
table.insert(devices, {
|
||||
name = name,
|
||||
type = pType,
|
||||
})
|
||||
local DROPPER_NAME = "minecraft:dropper_0"
|
||||
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
|
||||
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
|
||||
|
||||
-- Scan the contents of a single inventory peripheral
|
||||
local function scanInventory(deviceName)
|
||||
local items = {}
|
||||
local inventory = peripheral.wrap(deviceName)
|
||||
if not inventory then return items end
|
||||
-------------------------------------------------
|
||||
-- Inventory helpers
|
||||
-------------------------------------------------
|
||||
|
||||
local contents = inventory.list()
|
||||
for slot, item in pairs(contents) do
|
||||
if items[item.name] then
|
||||
items[item.name] = items[item.name] + item.count
|
||||
else
|
||||
items[item.name] = item.count
|
||||
-- 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
|
||||
|
||||
-- Build a map of item -> list of devices that contain it
|
||||
local function buildItemMap(devices)
|
||||
local itemMap = {}
|
||||
-------------------------------------------------
|
||||
-- Main loop
|
||||
-------------------------------------------------
|
||||
|
||||
for _, device in ipairs(devices) do
|
||||
device.items = scanInventory(device.name)
|
||||
|
||||
for itemName, count in pairs(device.items) do
|
||||
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 ===")
|
||||
local function main()
|
||||
print("=================================")
|
||||
print(" Inventory Manager v2")
|
||||
print("=================================")
|
||||
print("")
|
||||
|
||||
for itemName, devices in pairs(itemMap) do
|
||||
local chests = {}
|
||||
local droppers = {}
|
||||
-- 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
|
||||
|
||||
for _, device in ipairs(devices) do
|
||||
if device.type == "minecraft:chest" then
|
||||
table.insert(chests, device)
|
||||
else
|
||||
table.insert(droppers, device)
|
||||
-- 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
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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)
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user