initial commit
This commit is contained in:
commit
628381ef61
9
.luarc.json
Normal file
9
.luarc.json
Normal 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
20
LICENSE
Normal 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.
|
374
main.lua
Normal file
374
main.lua
Normal 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
97
term-stack.lua
Normal 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
61
ui.lua
Normal 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
|
Loading…
Reference in New Issue
Block a user