1
0

Compare commits

...

10 Commits

5 changed files with 676 additions and 221 deletions

View File

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

View File

@ -1,5 +1,5 @@
The MIT License (MIT)
Copyright © 2022 Rokas Puzonas
Copyright © 2023 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

35
dbg.lua Normal file
View File

@ -0,0 +1,35 @@
local dbg_file
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("\t")
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
return setmetatable({}, { __call = function(_, file)
if dbg_file then return end
if file then
dbg_file = fs.combine(shell.dir(), file)
if dbg_file then
fs.delete(dbg_file)
end
end
end})

View File

@ -1,31 +1,9 @@
local ui = require("ui")
local term_stack = require("term-stack")
local dbg_file = fs.combine(shell.dir(), "logs.txt")
if dbg_file then
fs.delete(dbg_file)
end
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
do
local success, dbg_module = pcall(require, "dbg")
if success then
dbg_module("logs.txt")
end
end
@ -60,8 +38,14 @@ end
local function main()
local main_view = require("views.main")
local inventories = list_inventories()
main_view:prepare(inventories, "minecraft:barrel_4")
local save_file = "/storage-config.txt"
if fs.exists(save_file) then
main_view:load_from_file(save_file)
else
local inventories = list_inventories()
main_view:prepare(inventories, {}, "minecraft:barrel_4")
end
main_view.save_file = save_file
local update_interval = 0.1
local update_timer = os.startTimer(update_interval)
@ -81,7 +65,7 @@ local function main()
end
end
main_view:draw()
main_view:run()
end
end

View File

@ -3,6 +3,20 @@ local ensure_width = require("cc.strings").ensure_width
local ui = require("ui")
local main_view = {}
local nbt_sensitive_names = {
"minecraft:potion", "minecraft:enchanted_book"
}
local display_name_transformers = {
["minecraft:enchanted_book"] = function(detail)
local enchantments = {}
for _, enchant in ipairs(detail.enchantments) do
table.insert(enchantments, enchant.displayName)
end
return table.concat(enchantments, ", ") .. " Book"
end
}
local function clamp(x, min, max)
return math.min(math.max(x, min), max)
end
@ -11,12 +25,55 @@ local function rect(x, y, w, h)
return { x = x, y = y, w = w, h = h or w }
end
local function list_items(inventories)
local items = {}
local function copy_table(t)
local t_copy = {}
for k, v in pairs(t) do
t_copy[k] = v
end
return t_copy
end
local function is_integer(num)
return num % 1 == 0
end
local function does_array_contain(array, value)
for _, v in ipairs(array) do
if v == value then
return true
end
end
return false
end
local function add_item_detail(item_details, name, detail)
if item_details[name] then return end
local display_name
if not display_name_transformers[detail.name] then
display_name = detail.displayName
else
display_name = display_name_transformers[detail.name](detail)
end
item_details[name] = {
display_name = display_name,
stack_size = detail.maxCount
}
end
local function list_items(inventories, item_details)
local item_registry = {}
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], {
local item_name = item.name
if does_array_contain(nbt_sensitive_names, item_name) then
local detail = peripheral.call(name, "getItemDetail", slot)
item_name = item_name .. "#" .. item.nbt
add_item_detail(item_details, item_name, detail)
end
item_registry[item_name] = item_registry[item_name] or {}
table.insert(item_registry[item_name], {
count = item.count,
slot = slot,
peripheral = name
@ -24,19 +81,24 @@ local function list_items(inventories)
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
for _, items in pairs(item_registry) do
items.count = 0
for _, slot in ipairs(items) do
items.count = items.count + slot.count
end
end
return items
return item_registry
end
local function populate_item_details(item_details, item_registry)
for name, items in pairs(item_registry) do
local item = items[1]
if item and not item_details[name] then
local detail = peripheral.call(item.peripheral, "getItemDetail", item.slot)
add_item_detail(item_details, name, detail)
end
end
end
local function remove_value(array, value)
@ -61,6 +123,13 @@ local function vline(x, y, height)
end
end
local function hline(x, y, length)
for i=1, length do
term.setCursorPos(x+i-1, y)
term.write("-")
end
end
local function get_item_counts(items)
local item_counts = {}
for name, item_collection in pairs(items) do
@ -69,95 +138,402 @@ local function get_item_counts(items)
return item_counts
end
function main_view:prepare(inventories, result_inventory)
self.event = {}
local function is_bundle_name(name)
return name:find("#") == 1
end
self.is_left_active = true
local function do_bundles_contain_cycle(bundles)
local visited = {}
local stack = {}
local function is_cyclic_util(bundle_name)
if not visited[bundle_name] then
visited[bundle_name] = true
stack[bundle_name] = true
for dep_name in pairs(bundles[bundle_name]) do
if is_bundle_name(dep_name) then
dep_name = dep_name:sub(2)
if not visited[dep_name] and is_cyclic_util(dep_name) then
return true
elseif stack[dep_name] then
return true
end
end
end
end
stack[bundle_name] = false
return false
end
for bundle_name in pairs(bundles) do
if not visited[bundle_name] and is_cyclic_util(bundle_name) then
return true
end
end
return false
end
-- TODO: improve this function
local function resolve_bundle_items(bundles, bundle_name, seen_bundles)
seen_bundles = seen_bundles or {}
if seen_bundles[bundle_name] then return nil, "Circular dependency" end
local bundle = bundles[bundle_name]
if not bundle then return {} end
local items = {}
for dep_name, dep_count in pairs(bundle) do
if is_bundle_name(dep_name) then
seen_bundles[bundle_name] = true
local dep_items, err = resolve_bundle_items(bundles, dep_name:sub(2), seen_bundles)
if not dep_items then return nil, err end
for item_name, item_count in pairs(dep_items) do
items[item_name] = (items[item_name] or 0) + item_count * dep_count
end
seen_bundles[bundle_name] = nil
else
items[dep_name] = (items[dep_name] or 0) + dep_count
end
end
return items
end
-- TODO: improve this function
local function populate_bundle_details(bundle_details, bundles)
for bundle_name in pairs(bundles) do
local hash_name = "#"..bundle_name
if not bundle_details[hash_name] then
bundle_details[hash_name] = resolve_bundle_items(bundles, bundle_name)
end
end
end
local function derive_available_bundles(available_items, bundle_details)
local available_bundles = {}
for bundle_name, items in pairs(bundle_details) do
local bundle_count = math.huge
for item_name, item_count in pairs(items) do
local available_count = available_items[item_name] or 0
bundle_count = math.min(bundle_count, math.max(0, available_count / item_count))
end
if bundle_count ~= math.huge and bundle_count > 0 then
available_bundles[bundle_name] = bundle_count
end
end
return available_bundles
end
local function sort_names_by_counts(names, items, bundles)
table.sort(names, function(a, b)
local count_a = items[a] or bundles[a]
local count_b = items[b] or bundles[b]
if count_a == count_b then
return a:gsub("^#", "") < b:gsub("^#", "")
else
return count_a > count_b
end
end)
end
function main_view:prepare(inventories, bundles, result_inventory)
self.event = {}
self.main_area = rect(1, 1, term.getSize())
self.split_x = math.floor(self.main_area.w*0.5)
self.left_area, self.right_area = vsplit(self.main_area, self.split_x)
self.is_left_active = true
self.bundle_name = ""
self.result_inventory = result_inventory
self.inventories = inventories
remove_value(self.inventories, result_inventory)
self.items = list_items(self.inventories)
local all_item_counts = get_item_counts(self.items)
self.left_store = {
all_items = all_item_counts,
filtered_names = self:list_filtered_names(all_item_counts, ""),
search_bar = "",
selected_idx = 1,
scroll = 0
}
self.right_store = {
all_items = {},
filtered_names = {},
search_bar = "",
selected_idx = 1,
scroll = 0
}
self.item_details = {}
self.item_registry = list_items(self.inventories, self.item_details)
populate_item_details(self.item_details, self.item_registry)
self.bundles = bundles or {}
self.bundle_details = {}
populate_bundle_details(self.bundle_details, self.bundles)
do
local available_items = get_item_counts(self.item_registry)
local available_bundles = derive_available_bundles(available_items, self.bundle_details)
local filtered_names = {}
do
for name, _ in pairs(available_items) do
table.insert(filtered_names, name)
end
for name, _ in pairs(available_bundles) do
table.insert(filtered_names, name)
end
sort_names_by_counts(filtered_names, available_items, available_bundles)
end
self.left_store = {
items = available_items,
bundles = available_bundles,
filtered_names = filtered_names,
selected_idx = 1,
search_bar = "",
scroll = 0
}
self.right_store = {
total_items = {},
items = {},
bundles = {},
filtered_names = {},
selected_idx = 1,
search_bar = "",
scroll = 0
}
end
end
function main_view:list_filtered_names(all_items, name_filter)
function main_view:save_to_file(filename)
local f, err = io.open(filename, "w")
if not f then return nil, err end
f:write(textutils.serialise{
inventories = self.inventories,
bundles = self.bundles,
result_inventory = self.result_inventory
})
f:close()
end
function main_view:load_from_file(filename)
local f, err = io.open(filename, "r")
if not f then return nil, err end
local data = textutils.unserialise(f:read("a"))
main_view:prepare(data.inventories, data.bundles, data.result_inventory)
f:close()
end
function main_view:list_filtered_names(items, bundles, name_filter)
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
for name, count in pairs(items) do
local display_name = self.item_details[name].display_name:lower()
if display_name: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)
for name, count in pairs(bundles) do
if name:find(name_filter) and count > 0 then
table.insert(names, name)
end
end
sort_names_by_counts(names, items, bundles)
return names
end
function main_view:display_item_list(store, area, active)
function main_view:refresh_filtered_names(store, area)
store.filtered_names = main_view:list_filtered_names(store.items, store.bundles, store.search_bar or "")
self:set_selected_option(store, area, store.selected_idx)
end
function main_view:set_selected_option(store, area, idx)
local filtered_count = #store.filtered_names
store.selected_idx = clamp(idx, 1, filtered_count)
local item_list_height = area.h-1
if store.scroll+item_list_height > filtered_count and filtered_count > item_list_height then
store.scroll = filtered_count-item_list_height
end
local margin = 2
if store.selected_idx > store.scroll+item_list_height-margin then
local max_scroll = math.max(filtered_count - item_list_height, 0)
store.scroll = math.min(store.selected_idx-item_list_height+margin, max_scroll)
elseif store.selected_idx <= store.scroll+margin then
store.scroll = math.max(store.selected_idx-1-margin, 0)
end
end
function main_view:select_option(store, area, item_name)
for i, name in ipairs(store.filtered_names) do
if name == item_name then
self:set_selected_option(store, area, i)
return
end
end
end
function main_view:process_movement_keys(store, area)
if self.down_pressed then
self:set_selected_option(store, area, store.selected_idx + 1)
elseif self.up_pressed then
self:set_selected_option(store, area, store.selected_idx - 1)
end
local lstore = self.left_store
local rstore = self.right_store
local left_selected = lstore.filtered_names[lstore.selected_idx]
local right_selected = rstore.filtered_names[rstore.selected_idx]
local selected_option = self.is_left_active and left_selected or right_selected
if selected_option and (self.right_pressed or self.left_pressed) then
if is_bundle_name(selected_option) then
local bundle_name = selected_option
local bundle = self.bundle_details[bundle_name]
if self.right_pressed and lstore.bundles[bundle_name] > 0 then
local transferred = math.min(lstore.bundles[bundle_name], 1)
for item, count in pairs(bundle) do
lstore.items[item] = lstore.items[item] - math.floor(count * transferred)
end
rstore.bundles[bundle_name] = (rstore.bundles[bundle_name] or 0) + transferred
elseif self.left_pressed and rstore.bundles[bundle_name] > 0 then
local transferred
if is_integer(rstore.bundles[bundle_name]) then
transferred = math.min(rstore.bundles[bundle_name], 1)
else
transferred = rstore.bundles[bundle_name] % 1
end
for item, count in pairs(bundle) do
lstore.items[item] = lstore.items[item] + math.floor(count * transferred)
end
rstore.bundles[bundle_name] = rstore.bundles[bundle_name] - transferred
end
else
local item = selected_option
local stack_size = self.item_details[item].stack_size
local src_items, dest_items
if self.right_pressed then
src_items, dest_items = lstore.items, rstore.items
elseif self.left_pressed then
src_items, dest_items = rstore.items, lstore.items
end
local transferred = math.min(src_items[item] or 0, stack_size)
src_items[item] = (src_items[item] or 0) - transferred
dest_items[item] = (dest_items[item] or 0) + transferred
end
lstore.bundles = derive_available_bundles(lstore.items, self.bundle_details)
self:refresh_filtered_names(lstore, self.left_area)
self:refresh_filtered_names(rstore, self.right_area)
self:select_option(store, area, selected_option)
end
end
function main_view:display_list(store, area, selected_idx)
term_stack.push_cursor(area.x, area.y)
local filtered_names = store.filtered_names
local shown_count = #filtered_names
local filtered_count = #store.filtered_names
local max_count = 0
for i=1, math.min(area.h, shown_count) do
local name = filtered_names[i + store.scroll]
local count = store.all_items[name]
max_count = math.max(max_count, count)
local function get_number_width(num)
return math.floor(math.log(num, 10) + 1)
end
local max_count_width = math.floor(math.log(max_count, 10) + 1)
for row=1, math.min(area.h, shown_count) do
local i = row + store.scroll
local name = filtered_names[i]
local count = store.all_items[name]
local item_info = self.items[name]
local count_width = math.floor(math.log(count, 10) + 1)
if i == store.selected_idx and active then
term.setTextColor(colors.yellow)
else
term.setTextColor(colors.white)
local count_collumn_width
do -- Figure out how wide does the count column need to be
local max_count = 0
for row=1, math.min(area.h, filtered_count) do
local name = store.filtered_names[row + store.scroll]
if name then
if is_bundle_name(name) then
max_count = math.max(max_count, store.bundles[name])
else
max_count = math.max(max_count, store.items[name])
end
end
end
count_collumn_width = get_number_width(max_count)
if not is_integer(max_count) then
count_collumn_width = count_collumn_width + 1
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, area.w-max_count_width-1))
end
if shown_count > area.h then
local scroll_height = math.max(1, area.h/shown_count*area.h)
local y = 1+store.scroll/shown_count*area.h
paintutils.drawLine(area.w, y, area.w, y+scroll_height-1, colors.lightGray)
do -- Display the options
for row=1, math.min(area.h, filtered_count) do
local i = row + store.scroll
local name = store.filtered_names[i]
if name then
local count, display_name
if is_bundle_name(name) then
count, display_name = store.bundles[name], name
else
count, display_name = store.items[name], self.item_details[name].display_name
end
local count_width = math.floor(math.log(count, 10) + 1)
if i == selected_idx then
term.setTextColor(colors.yellow)
else
term.setTextColor(colors.white)
end
if is_integer(count) then
term.setCursorPos((count_collumn_width+1 - count_width), row)
term.write(count)
else
term.setCursorPos((count_collumn_width+1 - count_width - 1), row)
if count >= 1 then
term.write(math.floor(count))
end
term.write("*")
end
term.setCursorPos(count_collumn_width+2, row)
term.write(ensure_width(display_name, area.w-count_collumn_width-1))
end
end
end
do -- Display the scrollbar on the right
if filtered_count > area.h then
local total_height = math.max(filtered_count)
local scroll_height = math.max(1, area.h/total_height*area.h)
local y = 1+store.scroll/filtered_count*area.h
paintutils.drawLine(area.w, y, area.w, y+scroll_height-1, colors.lightGray)
end
end
term_stack.pop_cursor()
end
function main_view:display_list_with_search(store, area, active)
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_filtered_names(store, area)
end
if active then
self:process_movement_keys(store, area)
end
term.setBackgroundColor(colors.black)
main_view:display_list(store, rect(1, 2, area.w, area.h-1), active and store.selected_idx)
term_stack.pop_cursor()
end
function main_view:move_item(item_name, amount, destination)
local item_collection = self.items[item_name]
local item_collection = self.item_registry[item_name]
local moved_amount = 0
while moved_amount < amount and item_collection.count > 0 do
@ -165,6 +541,8 @@ function main_view:move_item(item_name, amount, destination)
local needed_amount = amount - moved_amount
local transferred_amount = peripheral.call(slot_details.peripheral, "pushItems", destination, slot_details.slot, needed_amount)
if transferred_amount == 0 then break end
moved_amount = moved_amount + transferred_amount
slot_details.count = slot_details.count - transferred_amount
item_collection.count = item_collection.count - transferred_amount
@ -176,78 +554,150 @@ function main_view:move_item(item_name, amount, destination)
return moved_amount
end
function main_view:update_selected_item(store, area, idx)
local filtered_count = #store.filtered_names
store.selected_idx = clamp(idx, 1, filtered_count)
local margin = 2
local item_list_height = area.h-1
if store.selected_idx > store.scroll+item_list_height-margin then
store.scroll = math.min(store.selected_idx-item_list_height+margin, filtered_count-item_list_height)
elseif store.selected_idx <= store.scroll+margin then
store.scroll = math.max(store.selected_idx-1-margin, 0)
end
end
function main_view:select_item(store, area, item_name)
for i, name in ipairs(store.filtered_names) do
if name == item_name then
self:update_selected_item(store, area, i)
return
end
end
end
function main_view:refresh_filtered_names(store, area)
store.filtered_names = main_view:list_filtered_names(store.all_items, store.search_bar or "")
self:update_selected_item(store, area, store.selected_idx)
end
function main_view:display_items_with_search(store, area, active)
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_filtered_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_idx = math.min(store.selected_idx + 1, #store.filtered_names)
elseif key == keys.up or (main_view.ctrl_down and key == keys.k) then
store.selected_idx = math.max(store.selected_idx - 1, 1)
end
local margin = 2
local item_list_height = area.h-1
if store.selected_idx > store.scroll+item_list_height-margin then
store.scroll = math.min(store.selected_idx-item_list_height+margin, #store.filtered_names - item_list_height)
elseif store.selected_idx <= store.scroll+margin then
store.scroll = math.max(store.selected_idx-1-margin, 0)
end
end
end
term.setBackgroundColor(colors.black)
main_view:display_item_list(store, rect(1, 2, area.w, area.h-1), active)
term_stack.pop_cursor()
end
function main_view:has_selected_items()
for _, count in pairs(main_view.right_store.all_items) do
if count > 0 then
return true
end
for _, count in pairs(main_view.right_store.items) do
if count > 0 then return true end
end
for _, count in pairs(main_view.right_store.bundles) do
if count > 0 then return true end
end
return false
end
function main_view:draw()
function main_view:refresh_items()
self.item_registry = list_items(self.inventories, self.item_details)
populate_item_details(self.item_details, self.item_registry)
self.right_store.items = {}
self.right_store.bundles = {}
self.left_store.items = get_item_counts(self.item_registry)
self.left_store.bundles = derive_available_bundles(self.left_store.items, self.bundle_details)
self:refresh_filtered_names(self.left_store, self.left_area)
self:refresh_filtered_names(self.right_store, self.right_area)
end
function main_view:deposit_items()
for slot, item in pairs(peripheral.call(self.result_inventory, "list")) do
local total_transferred = 0
for _, inventory in ipairs(self.inventories) do
local transferred = peripheral.call(self.result_inventory, "pushItems", inventory, slot)
total_transferred = total_transferred + transferred
if total_transferred == item.count then break end
end
end
self:refresh_items()
end
function main_view:save_bundle(name, items, bundles)
local bundle = {}
for item, count in pairs(items) do
if count > 0 then
bundle[item] = count
end
end
for bundle_name, count in pairs(bundles) do
if count > 0 then
bundle[bundle_name] = count
end
end
self.bundles[name] = bundle
if do_bundles_contain_cycle(self.bundles) then
self.bundles[name] = nil
return false
end
populate_bundle_details(self.bundle_details, self.bundles)
self.left_store.bundles = derive_available_bundles(self.left_store.items, self.bundle_details)
self:refresh_filtered_names(self.left_store, self.left_area)
if self.save_file then
self:save_to_file(self.save_file)
end
return true
end
function main_view:display_bundle_popup(area)
term_stack.push_cursor(area.x, area.y)
-- Draw ascii box around textbox
term.setBackgroundColor(colors.black)
term.setTextColor(colors.lightGray)
term.write("+")
hline(2, 1, area.w-2)
term.write("+")
term.setCursorPos(1, 2)
term.write("|")
term.setCursorPos(area.w, 2)
term.write("|")
term.setCursorPos(1, 3)
term.write("+")
hline(2, 3, area.w-2)
term.write("+")
term_stack.push_cursor(2, 2)
self.bundle_name = ui:textbox(area.w-2, true, "Name", self.bundle_name)
term_stack.pop_cursor()
term_stack.pop_cursor()
if self.tab_pressed then
self.bundle_popup = false
elseif self.submit_pressed and #self.bundle_name > 0 then
self.bundle_popup = false
local success = self:save_bundle(self.bundle_name, self.right_store.items, self.right_store.bundles)
if success then
self.bundle_name = ""
end
end
end
function main_view:request_items()
local lstore = self.left_store
local rstore = self.right_store
if self:has_selected_items() then
for name, count in pairs(rstore.items) do
local transferred = self:move_item(name, count, self.result_inventory)
lstore.items[name] = lstore.items[name] + (count - transferred)
end
for bundle_name, bundle_count in pairs(rstore.bundles) do
bundle_count = math.max(bundle_count, 1)
for item, count in pairs(self.bundle_details[bundle_name]) do
local amount = math.floor(count*bundle_count)
local transferred = self:move_item(item, amount, self.result_inventory)
lstore.items[item] = lstore.items[item] + (amount - transferred)
end
end
rstore.items = {}
rstore.bundles = {}
lstore.bundles = derive_available_bundles(lstore.items, self.bundle_details)
self:refresh_filtered_names(lstore, self.left_area)
self:refresh_filtered_names(rstore, self.right_area)
else
local selected_option = lstore.filtered_names[lstore.selected_idx]
if is_bundle_name(selected_option) then
local bundle_name = selected_option
local bundle_count = math.min(lstore.bundles[bundle_name], 1)
for item, count in pairs(self.bundle_details[bundle_name]) do
local transferred = self:move_item(item, count, self.result_inventory)
lstore.items[item] = lstore.items[item] - math.floor(transferred * bundle_count)
end
else
local item = selected_option
local stack_size = self.item_details[item].stack_size
local transferred_count = self:move_item(item, stack_size, self.result_inventory)
lstore.items[item] = lstore.items[item] - transferred_count
end
lstore.bundles = derive_available_bundles(lstore.items, self.bundle_details)
self:refresh_filtered_names(lstore, self.left_area)
self:select_option(lstore, self.left_area, selected_option)
end
end
function main_view:run()
term.setBackgroundColor(colors.black)
term.clear()
@ -256,11 +706,28 @@ function main_view:draw()
term.setTextColor(colors.lightGray)
vline(self.split_x, self.main_area.y, self.main_area.h)
main_view:display_items_with_search(self.left_store, self.left_area, self.is_left_active)
main_view:display_items_with_search(self.right_store, self.right_area, not self.is_left_active)
main_view:display_list_with_search(self.left_store, self.left_area, self.is_left_active and not self.bundle_popup)
main_view:display_list_with_search(self.right_store, self.right_area, not self.is_left_active and not self.bundle_popup)
else
main_view:display_items_with_search(self.left_store, self.main_area, true)
main_view:display_list_with_search(self.left_store, self.main_area, not self.bundle_popup)
end
if self.bundle_popup then
local w = self.main_area.w - 4
local h = 3
local area = rect(
1+math.floor((self.main_area.w-w)/2),
1+math.floor((self.main_area.h-h)/2),
w, h
)
self:display_bundle_popup(area)
end
self.up_pressed = false
self.down_pressed = false
self.left_pressed = false
self.right_pressed = false
self.tab_pressed = false
end
function main_view:on_event(event, ...)
@ -277,68 +744,35 @@ function main_view:on_event(event, ...)
end
function main_view:on_key(key)
local left_selected = self.left_store.filtered_names[self.left_store.selected_idx]
local right_selected = self.right_store.filtered_names[self.right_store.selected_idx]
self.up_pressed = key == keys.up or (self.ctrl_down and key == keys.k)
self.down_pressed = key == keys.down or (self.ctrl_down and key == keys.j)
self.left_pressed = key == keys.left or (self.ctrl_down and key == keys.h)
self.right_pressed = key == keys.right or (self.ctrl_down and key == keys.l)
self.submit_pressed = key == keys.enter or (self.ctrl_down and key == keys.space)
self.tab_pressed = key == keys.tab
if key == keys.rightCtrl or key == keys.leftCtrl then
self.ctrl_down = true
elseif key == keys.rightAlt or key == keys.leftAlt then
self.alt_down = true
elseif key == keys.tab then
self.is_left_active = not self.is_left_active
elseif key == keys.f5 then
self.items = list_items(self.inventories)
self.left_store.all_items = get_item_counts(self.items)
self.right_store.all_items = {}
self:refresh_filtered_names(self.left_store, self.left_area)
self:refresh_filtered_names(self.right_store, self.right_area)
end
if self.ctrl_down then
local has_selected_items = self:has_selected_items()
if key == keys.space then
if has_selected_items then
for name, count in pairs(self.right_store.all_items) do
local transferred = self:move_item(name, count, self.result_inventory)
self.left_store.all_items[name] = self.left_store.all_items[name] - transferred
end
self.right_store.all_items = {}
self:refresh_filtered_names(self.left_store, self.left_area)
self:refresh_filtered_names(self.right_store, self.right_area)
else
if left_selected then
local item = left_selected
local stack_size = self.items[item].stack_size
local transferred_count = self:move_item(item, stack_size, self.result_inventory)
self.left_store.all_items[item] = self.left_store.all_items[item] - transferred_count
self:refresh_filtered_names(self.left_store, self.left_area)
self:select_item(self.left_store, self.left_area, item)
end
end
end
if not self.bundle_popup then
if self.ctrl_down and key == keys.n and self:has_selected_items() then
self.bundle_popup = true
elseif key == keys.tab then
self.is_left_active = not self.is_left_active
elseif key == keys.f5 then
self:refresh_items()
elseif self.submit_pressed then
self:request_items()
elseif self.ctrl_down and key == keys.d then
self:deposit_items()
if self.is_left_active then
if key == keys.l and left_selected then
local item = left_selected
local stack_size = self.items[item].stack_size
local transferred = math.min(self.left_store.all_items[item], stack_size)
self.left_store.all_items[item] = self.left_store.all_items[item] - transferred
self.right_store.all_items[item] = (self.right_store.all_items[item] or 0) + transferred
self:refresh_filtered_names(self.left_store, self.left_area)
self:refresh_filtered_names(self.right_store, self.right_area)
self:select_item(self.left_store, self.left_area, item)
end
else
if key == keys.h and right_selected then
local item = right_selected
local stack_size = self.items[item].stack_size
local transferred = math.min(self.right_store.all_items[item], stack_size)
self.left_store.all_items[item] = self.left_store.all_items[item] + transferred
self.right_store.all_items[item] = self.right_store.all_items[item] - transferred
self:refresh_filtered_names(self.left_store, self.left_area)
self:refresh_filtered_names(self.right_store, self.right_area)
self:select_item(self.right_store, self.right_area, item)
end
end
end
end