Dumfing

Substitae2 - Minecraft storage system

Jul 3rd, 2021 (edited)
145
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 12.66 KB | None | 0 0
  1. local component = require("component")
  2. local serialization = require("serialization")
  3. local io = require("io")
  4. local filesystem = require("filesystem")
  5. local thread = require("thread")
  6. local computer = require("computer")
  7. local sides = require("sides")
  8.  
  9. local RETURN_CHEST = "minecraft:trapped_chest"
  10. function find_transposers()
  11.   return component.list("transposer")
  12. end
  13.  
  14. function get_item_table(db, item_handle)
  15.     -- load a table representing the locations of this item in the storage system
  16.     if db["item_files"][item_handle] == nil then
  17.       db["item_files"][item_handle] = __get_file_handle(db, item_handle)
  18.     end
  19.     local file_path = db["item_files"][item_handle]
  20.     if filesystem.exists(file_path) then
  21.         local read_file = io.open(file_path, "r")
  22.         table_out = serialization.unserialize(read_file:read("*a"))
  23.         read_file:close()
  24.         return table_out
  25.     end
  26.     return {locations={}}
  27. end
  28.  
  29. function __get_file_handle(db, item_handle)
  30.   if not filesystem.exists("/home/db_items") then
  31.     filesystem.makeDirectory("/home/db_items")
  32.   end
  33.  
  34.   local encoded_name = item_handle:gsub("\"", "x_01")
  35.   local encoded_name = encoded_name:gsub("?", "x_02")
  36.   local encoded_name = encoded_name:gsub(":", "x_03")
  37.  
  38.   return filesystem.concat("/home", "db_items", encoded_name..".idb")
  39. end
  40.  
  41. function write_item_table(db, item_table, item_handle)
  42.     -- write a item storage table to its representative file
  43.     -- TODO
  44.     if db["item_files"][item_handle] == nil then
  45.       db["item_files"][item_handle] = __get_file_handle(db, item_handle)
  46.     end
  47.     local file_path = db["item_files"][item_handle]
  48.     local out_file = io.open(file_path, "w")
  49.     out_file:write(serialization.serialize(item_table, 1e309))
  50.     out_file:flush()
  51.     out_file:close()
  52. end
  53.  
  54. function item_present(db, item_handle)
  55.   local file_path = __get_file_handle(db, item_handle)
  56.   if filesystem.exists(file_path) then
  57.     local item_table = get_item_table(db, item_handle)
  58.     local transposer_entries = 0
  59.     for address, info in pairs(item_table["locations"]) do
  60.       transposer_entries = transposer_entries + 1
  61.     end
  62.     if transposer_entries == 0 then
  63.       return false
  64.     end
  65.     -- assumes I'll remove an entry from the table once the value it represents is no longer referenced
  66.     return true
  67.   else
  68.     return false
  69.   end
  70. end
  71.  
  72. function load_database()
  73.   if filesystem.exists("/home/items.db") then
  74.     local read_file = io.open("/home/items.db", "r")
  75.     db = serialization.unserialize(read_file:read("*a"))
  76.     read_file:close()
  77.     print("deserialize",db)
  78.   else
  79.     db = {item_files={}, transposer_configurations={}, empty_slots = 0, empty_slots_final = 0}
  80.     print("No cached Database found.")
  81.     print("Performing first time read...")
  82.     read_items(db, find_transposers())
  83.     save_database(db)
  84.     print("blank", db)
  85.   end
  86.   print("return", db)
  87.   return db
  88. end
  89.  
  90. function save_database(db)
  91.   local out_file = io.open("/home/items.db", "w")
  92.   out_file:write(serialization.serialize(db, 1e309))
  93.   out_file:flush()
  94.   out_file:close()
  95. end  
  96.  
  97. function read_transposer_configurations(db, transposer_addresses)
  98.   db["transposer_configurations"] = {}
  99.   -- Check every transposer given
  100.   for transposer_address in transposer_addresses do
  101.     local transposer = component.proxy(transposer_address)
  102.     for side=0,5 do
  103.       -- a usable inventory is present if the size is not null
  104.       local inventory_present = transposer.getInventorySize(side) ~= nil
  105.  
  106.       if inventory_present then
  107.         local inventory_name = transposer.getInventoryName(side)
  108.         if inventory_name == RETURN_CHEST then
  109.           db["transposer_configurations"][transposer_address] = {return_side=side}
  110.           break -- remove if we want to support multiple return chests
  111.         end
  112.       end
  113.     end
  114.   end
  115. end
  116.  
  117. function get_item_amount(db, file_handle)
  118.   local item_table = get_item_table(db, file_handle)
  119.   local count = 0
  120.   for transposer, sides in pairs(item_table["locations"]) do
  121.     for side, slots in pairs(sides) do
  122.       for slot, slot_amount in pairs(slots) do
  123.         count  = count + slot_amount
  124.       end
  125.     end
  126.   end
  127.   return count
  128. end
  129.  
  130. function read_items(db, transposers)
  131.   db["empty_slots"] = 0
  132.  
  133.   read_transposer_configurations(db, transposers)
  134.  
  135.   for transposer_address in transposers do
  136.  
  137.     local transposer = component.proxy(transposer_address)
  138.  
  139.     local transposer_configuration = db["transposer_configurations"][transposer_address]
  140.     for side=0,5 do
  141.       local size = transposer.getInventorySize(side)
  142.       if size ~= nil then
  143.         local inventory_name = transposer.getInventoryName(side)
  144.         if side ~= transposer_configuration["return_side"] then
  145.           for slot_index=1,size do          
  146.             local stack = transposer.getStackInSlot(side, slot_index)
  147.             if stack ~= nil then
  148.               local stack_item = string.lower(stack.label)
  149.               local item_table = get_item_table(db, stack_item)
  150.  
  151.               if item_table["locations"][transposer_address] == nil then
  152.                 item_table["locations"][transposer_address] = {}
  153.               end
  154.  
  155.               if item_table["locations"][transposer_address][tostring(side)] == nil then
  156.                 item_table["locations"][transposer_address][tostring(side)] = {}
  157.               end
  158.  
  159.               if item_table["locations"][transposer_address][tostring(side)][tostring(slot_index)] == nil then
  160.                 item_table["locations"][transposer_address][tostring(side)][tostring(slot_index)] = 0
  161.               end
  162.               item_table["locations"][transposer_address][tostring(side)][tostring(slot_index)] = stack.size
  163.               write_item_table(db, item_table, stack_item)
  164.               os.sleep(0)
  165.             else
  166.               db["empty_slots"] = db["empty_slots"] + 1
  167.             end
  168.           end
  169.         end
  170.       end
  171.     end
  172.   end
  173.   db["empty_slots_final"] = db["empty_slots"]
  174. end
  175.  
  176. --[[
  177. -- Unused but present in code?
  178. function dfs_entry(table_in)
  179.   for k, v in pairs(table_in) do
  180.     print(k, v)
  181.     if type(v) == "table" then
  182.       dfs_entry(v)
  183.     end
  184.   end
  185. end
  186. ]]
  187.  
  188. function constantly_update_db_in_background(db, transposers)
  189.   while true do
  190.     read_items(db, transposers)
  191.     save_database(db)
  192.     check_memory_load(true)
  193.     computer.beep(1000, 0.1)
  194.   end
  195. end
  196.  
  197. function output_item(db, transposer_address, side, slot, amount)
  198.   local transposer_interface = component.proxy(transposer_address)
  199.   local return_side = db["transposer_configurations"][transposer_address]["return_side"]
  200.   local transferred_item = string.lower(transposer_interface.getStackInSlot(side, slot)["label"])
  201.   print("Transferring " .. amount .. " " .. transferred_item .. " from " .. string.sub(transposer_address, 0, 8) .. " " .. side .. " " .. slot)
  202.   while amount > 0 do
  203.     for insert_slot=1,transposer_interface.getInventorySize(return_side) do
  204.       local in_slot = transposer_interface.getSlotStackSize(return_side, insert_slot)
  205.       local slot_capacity  transposer_interface.getSlotMaxStackSize(return_side, insert_slot)
  206.       if slot_capacity == nil then
  207.         slot_capacity = 64
  208.       end
  209.       local item_in_slot = transposer_interface.getStackInSlot(return_side, insert_slot)
  210.  
  211.       if in_slot < slot_capacity and (item_in_slot == nil or string.lower(item_in_slot["label"]) == transferred_item) then
  212.         local transfer_amount = math.min(slot_capacity - in_slot, amount)
  213.         transposer_interface.transferItem(side, return_side, transfer_amount, slot, insert_slot)
  214.         amount = amount - transfer_amount
  215.         break
  216.       end
  217.     end
  218.   end
  219.   transposer_interface.transferItem(side, return_side, amount, slot, 1)  
  220. end
  221.  
  222. function string_distance(string_in, search_term)
  223.   local search_result = string.find(string_in, search_term, nil, true)
  224.   return search_result    
  225. end
  226.  
  227. function compare(q1, q2)
  228.   return q1[1] < q2[1]
  229. end
  230.  
  231. function process_query(db, query)
  232.   -- if there isn't a direct match, find the closest match to it
  233.   if db["item_files"][query] == nil then
  234.     local results = {} -- array style table where a value is (distance from query, string)
  235.     local result_count = 0
  236.     for k, v in pairs(db["item_files"]) do
  237.       local distance = string_distance(k, string.lower(query))
  238.       if distance ~= nil then
  239.         results[result_count+1] = {distance, k}
  240.         result_count = result_count + 1
  241.       end
  242.     end
  243.    
  244.     -- sort the results table (array), comparing elements with compare
  245.     table.sort(results, compare)
  246.  
  247.     local printed_results = 0
  248.     local print_limit = 5
  249.     print("Could not find",query)
  250.     if result_count == 0 then
  251.       return
  252.     end
  253.     print("did you mean...")
  254.     while true do
  255.       while printed_results < print_limit and results[printed_results+1] ~= nil do
  256.         print(printed_results+1,"/",#results, results[printed_results+1][2])
  257.         printed_results = printed_results + 1
  258.       end
  259.       print("Select result or show more (y/#/n)?")
  260.       local u_in = string.lower(io.read())
  261.  
  262.       local u_in_num = tonumber(u_in)
  263.  
  264.       if u_in == "y" then
  265.         print_limit = print_limit + 5
  266.       elseif u_in_num ~= nil and 1 <= u_in_num and u_in_num <= printed_results then
  267.         return results[u_in_num][2]
  268.       else
  269.         break
  270.       end
  271.     end
  272.   else
  273.     return query    
  274.   end
  275. end
  276.  
  277. function fulfill_query(db, query)
  278.   local item_table = get_item_table(db, query)
  279.   local item_count = get_item_amount(db, query)
  280.   print("The storage contains",item_count, query)
  281.   print("How many would you like?")
  282.   local u_in = -1
  283.   while u_in<0 or u_in > item_count do
  284.     local input = io.read()
  285.     if input ~= nil then
  286.       u_in = tonumber(input)
  287.     end
  288.   end
  289.  
  290.   local requested_amount = math.floor(u_in)
  291.   local total_moved = 0
  292.  
  293.   for transposer_address, sides in pairs(item_table["locations"]) do
  294.     for side, slots in pairs(sides) do
  295.       for slot, amount in pairs(slots) do
  296.         if requested_amount == 0 then
  297.           write_item_table(db, item_table, query)
  298.           return total_moved
  299.         end
  300.         -- if I want to deal with suddenly nil entries it would be here
  301.         -- check if inventory at slot is the item we want
  302.         -- if not, set table entry to nil and continue looping
  303.         local moved = math.min(requested_amount, amount)
  304.          -- print("moved "..moved.." "..query.." from "..side.." "..slot.." of "..string.sub(transposer_address, 0, 8))
  305.         output_item(db, transposer_address, tonumber(side), tonumber(slot), moved)
  306.         item_table["locations"][transposer_address][tostring(side)][tostring(slot)] = item_table["locations"][transposer_address][tostring(side)][tostring(slot)] - moved
  307.         if item_table["locations"][transposer_address][tostring(side)][tostring(slot)] == 0 then
  308.           item_table["locations"][transposer_address][tostring(side)][tostring(slot)] = nil
  309.         end
  310.         requested_amount = requested_amount - moved
  311.         total_moved = total_moved + moved
  312.       end
  313.     end    
  314.   end
  315.   write_item_table(db, item_table, query)
  316.   return total_moved
  317. end
  318.  
  319. function check_memory_load(warn)
  320.   local usage_percentage = 1 - (computer.freeMemory() / computer.totalMemory())
  321.  
  322.   if usage_percentage > 0.9 and warn then
  323.     print("HIGH MEMORY USAGE", computer.freeMemory()/1024, "kB remaining")
  324.   end
  325.   return usage_percentage
  326. end
  327.  
  328. local db = load_database()
  329. local transposers = find_transposers()
  330. print("Creating DB process")
  331. local db_update_thread = thread.create(constantly_update_db_in_background, db, transposers)
  332. print("Process created")
  333.  
  334. print("DB middleware status:", db_update_thread:status())
  335.  
  336. while true do
  337.   print("Query database:")
  338.   local query = io.read()
  339.   if query == "UPDATE DATABASE" then
  340.     read_items(db, transposers)
  341.     save_database(db)
  342.   elseif query == "EXIT" then
  343.     save_database(db)
  344.     os.exit()
  345.   elseif query == "CHECK MIDDLEWARE" then
  346.     print("DB middleware status: " .. db_update_thread:status())
  347.   elseif query == "CHECK FREE" then
  348.     print("DB free slots: ".. db["empty_slots_final"])
  349.   elseif query == "CHECK MEMORY" then
  350.     print("DB free memory:",computer.freeMemory()/1024, "kB remaining")
  351.   else
  352.     local search_term = process_query(db, query)
  353.     if search_term ~= nil then
  354.       local moved = fulfill_query(db, search_term)
  355.       print("Transferred ".. moved .. " of " .. search_term)
  356.       local item_table = get_item_table(db, search_term)
  357.       if get_item_amount(db, search_term) == 0 then
  358.         filesystem.remove(__get_file_handle(db, search_term))
  359.         db["item_files"][search_term] = nil
  360.       else
  361.         write_item_table(db, item_table, search_term)
  362.       end
  363.       save_database(db)
  364.     end
  365.   end
  366. end
Add Comment
Please, Sign In to add comment