1
0

initial commit

This commit is contained in:
Rokas Puzonas 2022-08-28 14:49:08 +00:00
commit 628381ef61
6 changed files with 562 additions and 0 deletions

9
.luarc.json Normal file
View File

@ -0,0 +1,9 @@
{
"$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json",
"Lua.diagnostics.disable": [
"undefined-field"
],
"Lua.workspace.library": {
"../cc-lsp-config": true
}
}

20
LICENSE Normal file
View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright © 2022 Rokas Puzonas
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the “Software”), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

1
README.md Normal file
View File

@ -0,0 +1 @@
# Storage system

374
main.lua Normal file
View File

@ -0,0 +1,374 @@
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(...)

97
term-stack.lua Normal file
View File

@ -0,0 +1,97 @@
local expectlib = require("cc.expect")
local expect, field = expectlib.expect, expectlib.field
local term_stack = {}
local cursor_stack = { }
local setCursorPos = term.setCursorPos
local getCursorPos = term.getCursorPos
local cursor_ox = 0
local cursor_oy = 0
function term_stack.push_cursor(x, y)
expect(1, x, "number", "nil")
expect(2, y, "number", "nil")
local dx = ((x or 1) - 1)
local dy = ((y or 1) - 1)
table.insert(cursor_stack, { dx, dy })
cursor_ox = cursor_ox + dx
cursor_oy = cursor_oy + dy
setCursorPos(cursor_ox+1, cursor_oy+1)
end
function term_stack.pop_cursor()
local frame = table.remove(cursor_stack)
cursor_ox = cursor_ox - frame[1]
cursor_oy = cursor_oy - frame[2]
setCursorPos(cursor_ox+1, cursor_oy+1)
end
function term.setCursorPos(x, y)
expect(1, x, "number")
expect(2, y, "number")
setCursorPos(x + cursor_ox, y + cursor_oy)
end
function term.getCursorPos()
local x, y = getCursorPos()
return x - cursor_ox, y - cursor_oy
end
local size_stack = {}
local getSize = term.getSize
local current_w, current_h = getSize()
function term_stack.push_size(w, h)
expect(1, w, "number")
expect(2, h, "number")
table.insert(size_stack, { current_w, current_h })
current_w = w
current_h = h
return w, h
end
function term_stack.pop_size()
local frame = table.remove(cursor_stack)
current_w = frame[1]
current_h = frame[2]
end
function term.getSize()
return current_w, current_h
end
function term_stack.push_rect(x, y, w, h)
if type(x) == "table" then
local rect = x
field(rect, "x", "number")
field(rect, "y", "number")
field(rect, "w", "number")
field(rect, "h", "number")
term_stack.push_cursor(rect.x, rect.y)
term_stack.push_size(rect.w, rect.h)
return rect.w, rect.h
else
expect(1, x, "number")
expect(2, y, "number")
expect(3, w, "number")
expect(4, h, "number")
term_stack.push_cursor(x, y)
term_stack.push_size(w, h)
return w, h
end
end
function term_stack.pop_rect()
term_stack.pop_cursor()
term_stack.pop_size()
end
return term_stack

61
ui.lua Normal file
View File

@ -0,0 +1,61 @@
local ui = {}
function ui:button(x, y, text)
local active = false
local hot = false
if self.event[1] == "mouse_up" then
local mx = self.event[3]
local my = self.event[4]
if mx >= x and x < mx + #text and my == y then
active = true
end
elseif self.event[1] == "mouse_click" then
local mx = self.event[3]
local my = self.event[4]
if mx >= x and x < mx + #text and my == y then
hot = true
end
end
if hot then
term.setBackgroundColor(colors.lightGray)
else
term.setBackgroundColor(colors.gray)
end
term.write(text)
return active
end
function ui:textbox(width, active, placeholder, text)
if active then
if self.event[1] == "char" and #text < width-1 then
text = text .. self.event[2]
elseif self.event[1] == "key" and self.event[2] == keys.backspace then
text = text:sub(1, -2)
end
end
local x, y = term.getCursorPos()
paintutils.drawLine(x, y, x+width-1, y, colors.gray)
term.setCursorPos(x, y)
term.setTextColor(colors.white)
if active then
term.write(text)
if os.clock() % 1 < 0.5 then
term.write("|")
end
else
if text == "" then
term.setTextColor(colors.lightGray)
term.write(placeholder)
else
term.write(text)
end
end
return text
end
return ui