wyvern/backend-chests.lua

226 lines
8.9 KiB
Lua
Raw Permalink Normal View History

2018-07-26 09:48:51 +00:00
-- Chest backend
-- Currently just the one for Dragon. Will not actually work yet.
2018-07-26 14:56:42 +00:00
local w = require "lib"
local d = require "luadash"
local fuzzy_match = require "fuzzy"
2018-07-26 09:28:37 +00:00
2018-07-26 14:56:42 +00:00
local conf = w.load_config({
"buffer_internal",
"buffer_external"
}, {
2018-07-29 09:36:43 +00:00
modem_internal = nil
2018-07-26 14:56:42 +00:00
})
local BUFFER_OUT_SLOT = 1
local BUFFER_IN_SLOT = 2
2018-07-26 09:28:37 +00:00
-- Find all chests or shulker boxes
2018-07-26 21:08:27 +00:00
local inventories = d.map_with_key(w.find_peripherals(function(type, name, wrapped)
2018-07-26 14:56:42 +00:00
return string.find(name, "chest") or string.find(name, "shulker")
end, conf.modem_internal), function(_, p) return p.name, p.wrapped end)
2018-07-26 09:28:37 +00:00
2018-08-15 21:21:43 +00:00
local info_cache = {}
2018-07-26 14:56:42 +00:00
2018-07-26 09:28:37 +00:00
-- Gets the display name of the given item (in the given chest peripheral & slot)
-- If its name is not cached, cache it.
-- If it is, just return the cached name
2018-07-26 14:56:42 +00:00
local function cache(item, chest, slot)
2018-07-26 15:32:48 +00:00
local idx = w.get_internal_identifier(item)
2018-07-26 09:28:37 +00:00
2018-08-15 21:22:24 +00:00
if info_cache[idx] then
2018-08-15 21:21:43 +00:00
return info_cache[idx]
2018-07-26 09:28:37 +00:00
else
2018-08-25 21:14:09 +00:00
local info = chest.getItemMeta(slot)
sleep() -- probably will slow things, but important to reduce peripheral calls a second
2018-08-15 21:21:43 +00:00
info_cache[idx] = { display_name = info.displayName, max_count = info.maxCount }
return info_cache[idx]
2018-07-26 09:28:37 +00:00
end
end
local index = {}
2018-07-26 14:56:42 +00:00
-- Update the index for the given peripheral
local function update_index_for(name)
2018-07-26 09:28:37 +00:00
local inv = inventories[name]
local data = inv.list()
for slot, item in pairs(data) do
2018-08-13 07:33:32 +00:00
data[slot] = w.to_wyvern_item(data[slot])
2018-08-15 21:25:59 +00:00
w.join(data[slot], cache(data[slot], inv, slot))
2018-07-26 09:28:37 +00:00
end
index[name] = data
2018-08-15 20:05:17 +00:00
2018-07-27 10:58:54 +00:00
print("Indexed " .. name .. ".")
2018-07-26 09:28:37 +00:00
end
2018-07-26 14:56:42 +00:00
-- Reindex all connected inventories
local function update_index()
2018-07-27 10:51:28 +00:00
print "Full indexing started."
2018-07-26 09:28:37 +00:00
for n in pairs(inventories) do
update_index_for(n)
2018-07-26 09:28:37 +00:00
sleep()
end
2018-07-27 10:51:28 +00:00
print "Full indexing complete."
2018-07-26 09:28:37 +00:00
end
2018-07-26 14:56:42 +00:00
-- Finds all items matching a certain predicate.
-- Returns a table of tables of { name, slot, item }
local function find(predicate)
local ret = {}
for inventory, items in pairs(index) do
2018-07-26 09:28:37 +00:00
for slot, item in pairs(items) do
2018-07-26 14:56:42 +00:00
local ok, extra = predicate(item) -- allow predicates to return some extra data which will come out in resulting results
if ok then
table.insert(ret, { location = { inventory = inventory, slot = slot }, item = item, extra = extra })
2018-07-26 09:28:37 +00:00
end
end
end
2018-07-26 14:56:42 +00:00
return ret
2018-07-26 09:28:37 +00:00
end
2018-07-26 14:56:42 +00:00
-- Finds space in the chest system. Returns the name of an inventory which has space.
2018-08-16 07:32:36 +00:00
local function find_space(item)
2018-08-15 21:21:43 +00:00
local locations = {}
2018-08-16 07:32:36 +00:00
local quantity = item.count
2018-07-26 09:28:37 +00:00
for name, items in pairs(index) do
2018-08-15 21:21:43 +00:00
for i = 1, inventories[name].size() do
local item_in_slot = index[name][i]
2018-08-15 21:27:55 +00:00
if not item_in_slot or (item_in_slot.ID == item.ID and item_in_slot.meta == item.meta and item_in_slot.NBT_hash == item.NBT_hash) then -- slot is free or contains same type of item
2018-08-15 21:21:43 +00:00
local available_space = item.max_count
if item_in_slot then available_space = available_space - item_in_slot.count end
2018-08-16 07:32:36 +00:00
if available_space > 0 then
quantity = quantity - available_space
table.insert(locations, { inventory = name, slot = i })
end
2018-08-15 21:21:43 +00:00
if quantity <= 0 then return locations end
end
2018-07-26 09:28:37 +00:00
end
end
2018-08-15 21:21:43 +00:00
error(w.errors.make(w.errors.NOSPACE))
2018-07-26 09:28:37 +00:00
end
2018-08-13 07:28:44 +00:00
local function find_by_ID_meta_NBT(ID, meta, NBT_hash)
2018-07-26 09:28:37 +00:00
return find(function(item)
return
2018-08-15 20:05:17 +00:00
(not meta or item.meta == meta) and -- if metadata provided, ensure match
(not ID or item.ID == ID) and -- if internal name provided, ensure match
(not NBT_hash or item.NBT_hash == NBT_hash) -- if NBT hash provided, ensure match
2018-07-26 09:28:37 +00:00
end)
end
2018-08-15 12:17:53 +00:00
local function search(query, exact)
2018-07-26 14:56:42 +00:00
local results = find(function(item)
2018-08-15 12:17:53 +00:00
if exact then
2018-08-15 15:22:13 +00:00
return string.lower(query) == string.lower(item.display_name), 0
2018-08-15 12:17:53 +00:00
else
local match, best_start = fuzzy_match(item.display_name, query)
if best_start ~= nil and match > 0 then return true, match end
end
2018-07-26 14:56:42 +00:00
end)
2018-07-27 08:57:14 +00:00
return d.sort_by(results, function(x) return x.extra end) -- sort returned results by closeness to query
2018-07-26 09:28:37 +00:00
end
2018-07-27 11:41:07 +00:00
-- Retrives items from a location in storage and puts them in the buffer
2018-07-26 15:15:48 +00:00
local function fetch_by_location(loc, limit)
2018-07-26 14:56:42 +00:00
local peripheral_name, slot, limit = loc.inventory, loc.slot, limit or 64
return peripheral.call(conf.buffer_internal, "pullItems", peripheral_name, slot, limit, BUFFER_OUT_SLOT)
end
2018-07-26 09:28:37 +00:00
2018-07-27 11:41:07 +00:00
-- Clears out the buffer into storage.
2018-07-27 10:44:24 +00:00
local function clear_buffer()
for i = 1, peripheral.call(conf.buffer_internal, "size") do
local space_location = find_space()
if not space_location then error("Storage capacity reached. Please add more chests or shulker boxes.") end
2018-07-27 13:03:24 +00:00
peripheral.call(conf.buffer_internal, "pushItems", space_location, i)
2018-07-27 10:44:24 +00:00
os.queueEvent("reindex", space_location)
sleep()
end
end
2018-08-16 07:36:01 +00:00
local function update_slot(inv, slot, by, stack)
if index[inv][slot] then
index[inv][slot].count = index[inv][slot].count - by
if index[inv][slot].count == 0 then index[inv][slot] = nil
elseif index[inv][slot].count < 0 then os.queueEvent "reindex" error "Index inconsistency error." end
else
index[inv][slot] = stack
end
2018-08-15 21:21:43 +00:00
end
2018-07-26 14:56:42 +00:00
local function server(command)
if command.type == "buffers" then -- Sends the external address of the buffer
return conf.buffer_external
elseif command.type == "reindex" then
os.queueEvent "reindex"
2018-07-26 14:56:42 +00:00
elseif command.type == "extract" then
2018-08-13 07:28:44 +00:00
local result = find_by_ID_meta_NBT(command.ID, command.meta, command.NBT_hash)
2018-08-15 20:05:17 +00:00
local stacks = {}
2018-08-15 20:05:17 +00:00
-- Check if we have an item, and its stack is big enough; otherwise, send back an error.
local quantity_to_fetch_remaining, items_moved_from_storage = command.quantity or 0, 0
repeat
local stack_to_pull = table.remove(result, 1)
2018-08-15 20:05:17 +00:00
2018-08-15 06:22:58 +00:00
if not stack_to_pull then
error(w.errors.make(w.errors.NOITEMS, { type = w.get_internal_identifier(command), quantity = quantity_to_fetch_remaining }))
end
2018-08-15 20:05:17 +00:00
table.insert(stacks, stack_to_pull)
items_moved_from_storage = items_moved_from_storage + fetch_by_location(stack_to_pull.location, command.quantity)
2018-08-15 20:06:34 +00:00
2018-08-15 21:21:43 +00:00
-- update index
2018-08-15 20:06:34 +00:00
local location = stack_to_pull.location
2018-08-16 07:36:01 +00:00
update_slot(location.inventory, location.slot, items_moved_from_storage, stack_to_pull)
until items_moved_from_storage >= quantity_to_fetch_remaining
2018-08-15 20:05:17 +00:00
if command.destination_inventory then
2018-07-27 10:44:24 +00:00
-- push items to destination
items_moved_to_destination = peripheral.call(conf.buffer_external, "pushItems", command.destination_inventory, BUFFER_OUT_SLOT, command.quantity, command.destination_slot)
2018-08-15 20:05:17 +00:00
2018-07-27 10:44:24 +00:00
-- If destination didn't accept all items, clear out the buffer.
if items_moved_to_destination < items_moved_from_storage then
clear_buffer()
end
end
2018-08-15 20:05:17 +00:00
return { moved = items_moved_to_destination or items_moved_from_storage, stacks = stacks_moved }
2018-07-27 10:44:24 +00:00
elseif command.type == "insert" then
if command.from_inventory and command.from_slot then
2018-07-27 11:41:07 +00:00
peripheral.call(conf.buffer_external, "pullItems", command.from_inventory, command.from_slot, command.quantity, BUFFER_IN_SLOT) -- pull from from_inventory to buffer
end
2018-08-15 21:21:43 +00:00
2018-08-16 07:39:40 +00:00
local raw_item = peripheral.call(conf.buffer_internal, "getItemMeta", BUFFER_IN_SLOT)
2018-08-16 07:47:13 +00:00
if not raw_item then return end
2018-08-16 07:39:40 +00:00
local item = w.to_wyvern_item(raw_item)
2018-08-16 07:49:19 +00:00
if not item then return end
2018-08-15 21:21:43 +00:00
2018-08-15 21:29:37 +00:00
w.join(item, cache(item, peripheral.wrap(conf.buffer_internal), BUFFER_IN_SLOT))
2018-08-16 07:32:36 +00:00
local space_locations = find_space(item) -- command contains item-related stuff
2018-08-15 21:21:43 +00:00
local moved = 0
for _, loc in pairs(space_locations) do
2018-08-16 07:06:01 +00:00
local moved_now = peripheral.call(conf.buffer_internal, "pushItems", loc.inventory, BUFFER_IN_SLOT, 64, loc.slot) -- push from buffer to free space
2018-08-16 07:36:01 +00:00
update_slot(loc.inventory, loc.slot, -moved_now, item)
2018-08-16 07:06:01 +00:00
moved = moved + moved_now
2018-08-15 21:21:43 +00:00
end
2018-08-15 20:05:17 +00:00
2018-07-27 10:44:24 +00:00
return { moved = moved }
2018-07-27 11:50:45 +00:00
elseif command.type == "search" then
2018-08-15 12:17:53 +00:00
return w.collate_stacks(d.map(search(command.query, command.exact), function(x) return x.item end))
2018-07-27 15:18:33 +00:00
elseif command.type == "list" then
return index
2018-07-26 09:28:37 +00:00
end
end
local function indexer_thread()
while true do
2018-07-27 10:44:24 +00:00
local _, inventory = os.pullEvent "reindex"
if inventory then update_index_for(inventory) else update_index() end
end
end
2018-07-26 20:59:42 +00:00
w.init()
2018-07-27 10:44:24 +00:00
parallel.waitForAll(function() os.queueEvent("reindex") w.serve(server, "storage") end, indexer_thread)