1
0
cc-storage-terminal/main.lua
2022-08-28 14:49:24 +00:00

375 lines
11 KiB
Lua

local ui = require("ui")
local ensure_width = require("cc.strings").ensure_width
local term_stack = require("term-stack")
local LEFT_KEY = {keys.left , {"ctrl", keys.h}}
local DOWN_KEY = {keys.down , {"ctrl", keys.j}}
local UP_KEY = {keys.up , {"ctrl", keys.k}}
local RIGHT_KEY = {keys.right, {"ctrl", keys.l}}
local REQUEST_KEY = {"ctrl", keys.space}
local HELP_KEY = keys.f1
local STATISTICS_KEY = keys.f2
local REFRESH_KEY = keys.f5
local dbg_file = fs.combine(shell.dir(), "logs.txt")
if dbg_file then
fs.delete(dbg_file)
end
local function dbg(...)
if dbg_file then
local out = io.open(dbg_file, "a")
if not out then return end
for i = 1, select("#", ...) do
local value = select(i, ...)
if type(value) == "table" then
out:write(textutils.serialise(value))
else
out:write(tostring(value))
out:write(" ")
end
end
out:write("\n")
out:close()
else
local pretty = require("cc.pretty")
for i = 1, select("#", ...) do
local value = select(i, ...)
pretty.pretty_print(value)
end
end
end
local function is_inventory(peripheral_name)
local types = {peripheral.getType(peripheral_name)}
for _, t in ipairs(types) do
if t == "inventory" then
return true
end
end
return false
end
local function remove_value(array, value)
for i, v in ipairs(array) do
if v == value then
table.remove(array, i)
return
end
end
end
local function has_in_array(value, array)
for _, v in ipairs(array) do
if v == value then
return true
end
end
end
local function list_inventories()
local inventories = {}
for _, name in ipairs(peripheral.getNames()) do
if is_inventory(name) and not has_in_array(name, { "top", "left", "right", "back", "bottom", "front" }) then
table.insert(inventories, name)
end
end
return inventories
end
local function list_items(inventories)
local items = {}
for _, name in ipairs(inventories) do
for slot, item in pairs(peripheral.call(name, "list")) do
items[item.name] = items[item.name] or {}
table.insert(items[item.name], {
count = item.count,
slot = slot,
peripheral = name
})
end
end
for name, item_collection in pairs(items) do
local item = item_collection[1]
local detail = peripheral.call(item.peripheral, "getItemDetail", item.slot)
items[name].displayName = detail.displayName
items[name].stack_size = detail.maxCount
item_collection.count = 0
for _, slot in ipairs(item_collection) do
item_collection.count = item_collection.count + slot.count
end
end
return items
end
local function get_item_counts(items)
local item_counts = {}
for name, item_collection in pairs(items) do
item_counts[name] = item_collection.count
end
return item_counts
end
local function vline(x, y, height)
for i=1, height do
term.setCursorPos(x, y+i-1)
term.write("|")
end
end
local function clamp(x, min, max)
return math.min(math.max(x, min), max)
end
local function rect(x, y, w, h)
return { x = x, y = y, w = w, h = h or w }
end
local function vsplit(area, x)
local left_area = rect(area.x, area.y, x - area.x, area.h)
local right_area = rect(area.x + left_area.w+1, area.y, area.w - left_area.w-1, area.h)
return left_area, right_area
end
local main_view = { event = {} }
function main_view:list_shown_names(all_items, name_filter, max_names)
name_filter = name_filter:lower()
local names = {}
for name, count in pairs(all_items) do
local displayName = self.items[name].displayName:lower()
if displayName:find(name_filter) and count > 0 then
table.insert(names, name)
end
end
table.sort(names, function(a, b) return all_items[a] > all_items[b] end)
if #names > max_names then
local names_subset = {}
for i=1, max_names do
table.insert(names_subset, names[i])
end
names = names_subset
end
return names
end
function main_view:display_item_list(x, y, w, all_items, shown_names, highlight_row)
term_stack.push_cursor(x, y)
local max_count = 0
for _, name in pairs(shown_names) do
local count = all_items[name]
max_count = math.max(max_count, count)
end
local max_count_width = math.floor(math.log(max_count, 10) + 1)
for row, name in pairs(shown_names) do
local count = all_items[name]
local item_info = self.items[name]
local count_width = math.floor(math.log(count, 10) + 1)
if row == highlight_row then
term.setTextColor(colors.yellow)
else
term.setTextColor(colors.white)
end
term.setCursorPos((max_count_width+1 - count_width), row)
term.write(count)
term.setCursorPos(max_count_width+2, row)
term.write(ensure_width(item_info.displayName, w-max_count_width-1))
end
term_stack.pop_cursor()
end
function main_view:move_items(item_name, amount, destination)
local item_collection = self.items[item_name]
local moved_amount = 0
while moved_amount < amount and item_collection.count > 0 do
local slot_details = item_collection[#item_collection]
local needed_amount = amount - moved_amount
local transferred_amount = peripheral.call(slot_details.peripheral, "pushItems", destination, slot_details.slot, needed_amount)
moved_amount = moved_amount + transferred_amount
slot_details.count = slot_details.count - transferred_amount
item_collection.count = item_collection.count - transferred_amount
if slot_details.count == 0 then
table.remove(item_collection)
end
end
return moved_amount
end
function main_view:refresh_shown_names(store, area)
store.shown_names = main_view:list_shown_names(store.all_items, store.search_bar or "", area.h-1)
store.selected_row = clamp(store.selected_row or 1, 1, #store.shown_names)
end
function main_view:display_items_with_search(store, area, active)
store.search_bar = store.search_bar or ""
store.selected_row = store.selected_row or 0
store.all_items = store.all_items or {}
store.shown_names = store.shown_names or main_view:list_shown_names(store.all_items, store.search_bar, area.h-1)
term_stack.push_cursor(area.x, area.y)
local new_search_bar = ui:textbox(area.w, active, "Search", store.search_bar)
if new_search_bar ~= store.search_bar then
store.search_bar = new_search_bar
self:refresh_shown_names(store, area)
end
if active then
if ui.event[1] == "key" then
local key = ui.event[2]
if key == keys.down or (main_view.ctrl_down and key == keys.j) then
store.selected_row = math.min(store.selected_row + 1, #store.shown_names)
elseif key == keys.up or (main_view.ctrl_down and key == keys.k) then
store.selected_row = math.max(store.selected_row - 1, 1)
end
end
end
term.setBackgroundColor(colors.black)
main_view:display_item_list(1, 2, area.w, store.all_items, store.shown_names, store.selected_row)
term_stack.pop_cursor()
end
local function main()
main_view.result_inventory = "minecraft:barrel_4"
local inventories = list_inventories()
remove_value(inventories, main_view.result_inventory)
main_view.items = list_items(inventories)
local update_interval = 0.05
local main_area = rect(1, 1, term.getSize())
local split_x = math.floor(main_area.w*0.5)
local left_area, right_area = vsplit(main_area, split_x)
local is_left_active = true
local left_store = {
all_items = get_item_counts(main_view.items)
}
local right_store = {
all_items = {}
}
local update_timer = os.startTimer(update_interval)
while true do
---@diagnostic disable-next-line: missing-parameter
ui.event = {os.pullEvent()}
local has_selected_items = false
for _, count in pairs(right_store.all_items) do
if count > 0 then
has_selected_items = true
break
end
end
if not has_selected_items and not is_left_active then
is_left_active = true
end
local event = ui.event[1]
if event == "timer" then
local timer = ui.event[2]
if timer == update_timer then
update_timer = os.startTimer(update_interval)
end
elseif event == "key" then
local key = ui.event[2]
if key == keys.rightCtrl or key == keys.leftCtrl then
main_view.ctrl_down = true
elseif key == keys.tab then
is_left_active = not is_left_active
elseif key == keys.f5 then
main_view.items = list_items(inventories)
left_store.all_items = get_item_counts(main_view.items)
right_store.all_items = {}
main_view:refresh_shown_names(left_store, left_area)
main_view:refresh_shown_names(right_store, right_area)
update_timer = os.startTimer(update_interval)
end
if main_view.ctrl_down then
if key == keys.space then
if has_selected_items then
for name, count in pairs(right_store.all_items) do
local transferred_count = main_view:move_items(name, count, main_view.result_inventory)
left_store.all_items[name] = left_store.all_items[name] - transferred_count
end
right_store.all_items = {}
main_view:refresh_shown_names(left_store, left_area)
main_view:refresh_shown_names(right_store, right_area)
else
local target_item = left_store.shown_names[left_store.selected_row]
if target_item then
local item_collection = main_view.items[target_item]
local transferred_count = main_view:move_items(target_item, item_collection.stack_size, main_view.result_inventory)
left_store.all_items[target_item] = left_store.all_items[target_item] - transferred_count
main_view:refresh_shown_names(left_store, left_area)
end
end
end
if is_left_active then
if key == keys.l then
local target_item = left_store.shown_names[left_store.selected_row]
if target_item then
local stack_size = main_view.items[target_item].stack_size
local transferred_count = math.min(left_store.all_items[target_item], stack_size)
left_store.all_items[target_item] = left_store.all_items[target_item] - transferred_count
right_store.all_items[target_item] = (right_store.all_items[target_item] or 0) + transferred_count
main_view:refresh_shown_names(left_store, left_area)
main_view:refresh_shown_names(right_store, right_area)
end
end
else
if key == keys.h then
local target_item = right_store.shown_names[right_store.selected_row]
if target_item then
local stack_size = main_view.items[target_item].stack_size
local transferred_count = math.min(right_store.all_items[target_item], stack_size)
left_store.all_items[target_item] = left_store.all_items[target_item] + transferred_count
right_store.all_items[target_item] = right_store.all_items[target_item] - transferred_count
main_view:refresh_shown_names(left_store, left_area)
main_view:refresh_shown_names(right_store, right_area)
end
end
end
end
elseif event == "key_up" then
local key = ui.event[2]
if key == keys.rightCtrl or key == keys.leftCtrl then
main_view.ctrl_down = false
end
end
term.setBackgroundColor(colors.black)
term.clear()
if has_selected_items then
term.setTextColor(colors.lightGray)
vline(split_x, main_area.y, main_area.h)
main_view:display_items_with_search(left_store, left_area, is_left_active)
main_view:display_items_with_search(right_store, right_area, not is_left_active)
else
main_view:display_items_with_search(left_store, main_area, is_left_active)
end
end
end
main(...)