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 "
2018-08-14 20:16:27 +00:00
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:35:10 +00:00
} , {
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 " )
2018-07-29 09:35:10 +00:00
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
2018-07-27 09:42:43 +00:00
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
2018-07-27 08:47:57 +00:00
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
2018-08-15 06:20:48 +00:00
local stacks = { }
2018-08-15 20:05:17 +00:00
2018-08-15 06:20:48 +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
2018-08-15 06:20:48 +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 )
2018-08-15 06:20:48 +00:00
until items_moved_from_storage >= quantity_to_fetch_remaining
2018-08-15 20:05:17 +00:00
2018-07-27 09:39:32 +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
2018-08-15 06:20:48 +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
2018-07-27 09:39:32 +00:00
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
2018-07-27 08:47:57 +00:00
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
2018-07-27 08:47:57 +00:00
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 )