435 lines
15 KiB
Lua
435 lines
15 KiB
Lua
|
|
local function draw_bar(y, c1, c2, p, fmt, ...)
|
|
local str = string.format(fmt, ...)
|
|
local tw = term.getSize()
|
|
local w1 = math.ceil(p * tw)
|
|
local w2 = tw - w1
|
|
|
|
local old_bg = term.getBackgroundColor()
|
|
term.setCursorPos(1, y)
|
|
term.setBackgroundColor(c1)
|
|
term.write(str:sub(1, w1))
|
|
local rem = w1 - #str
|
|
if rem > 0 then
|
|
term.write(string.rep(" ", rem))
|
|
end
|
|
|
|
term.setBackgroundColor(c2)
|
|
term.write(str:sub(w1 + 1, w1 + w2))
|
|
|
|
rem = math.min(tw - #str, w2)
|
|
if rem > 0 then
|
|
term.write(string.rep(" ", rem))
|
|
end
|
|
|
|
term.setBackgroundColor(old_bg)
|
|
end
|
|
|
|
function table.deepcopy(obj)
|
|
if type(obj) ~= 'table' then return obj end
|
|
local res = {}
|
|
for k, v in pairs(obj) do res[table.deepcopy(k)] = table.deepcopy(v) end
|
|
return res
|
|
end
|
|
|
|
function spairs(t, order)
|
|
-- collect the tbl_keys
|
|
local tbl_keys = {}
|
|
for k in pairs(t) do tbl_keys[#tbl_keys+1] = k end
|
|
|
|
-- if order function given, sort by it by passing the table and tbl_keys a, b,
|
|
-- otherwise just sort the tbl_keys
|
|
if order then
|
|
table.sort(tbl_keys, function(a,b) return order(t, a, b) end)
|
|
else
|
|
table.sort(tbl_keys)
|
|
end
|
|
|
|
-- return the iterator function
|
|
local i = 0
|
|
return function()
|
|
i = i + 1
|
|
if tbl_keys[i] then
|
|
return tbl_keys[i], t[tbl_keys[i]]
|
|
end
|
|
end
|
|
end
|
|
|
|
function string.split(inputstr, sep)
|
|
sep = sep or "%s"
|
|
local t = {}
|
|
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
|
|
table.insert(t, str)
|
|
end
|
|
return table.unpack(t)
|
|
end
|
|
|
|
function map(tbl, fn)
|
|
local out = {}
|
|
for k, v in pairs(tbl) do
|
|
out[k] = fn(k, v, tbl)
|
|
end
|
|
return out
|
|
end
|
|
|
|
function filter(tbl, fil)
|
|
local out = {}
|
|
for k, v in pairs(tbl) do
|
|
if fil(k, v, tbl) then
|
|
out[k] = v
|
|
end
|
|
end
|
|
return out
|
|
end
|
|
|
|
function pop(tbl)
|
|
local out = { k = nil, v = nil }
|
|
for k, v in pairs(tbl) do
|
|
out.k = k
|
|
out.v = v
|
|
table.remove(tbl, k)
|
|
break
|
|
end
|
|
return out.k, out.v
|
|
end
|
|
|
|
function groupby(tbl, fn)
|
|
local out = {}
|
|
for k, v in pairs(tbl) do
|
|
local group = fn(k, v, tbl)
|
|
out[group] = out[group] or {}
|
|
table.insert(out[group], { k = k, v = v })
|
|
end
|
|
return out
|
|
end
|
|
|
|
function reduce(tbl, operator, initial)
|
|
local accumulator = initial
|
|
for k, v in pairs(tbl) do
|
|
accumulator = operator(k, v, accumulator, tbl)
|
|
end
|
|
return accumulator
|
|
end
|
|
|
|
function pipe(input)
|
|
local function chain(fn)
|
|
return function(self, ...)
|
|
fn(self, ...)
|
|
return self
|
|
end
|
|
end
|
|
return {
|
|
_value = table.deepcopy(input),
|
|
groupby = chain(function(self, fn) self._value = groupby(self._value, fn) end),
|
|
filter = chain(function(self, fn) self._value = filter(self._value, fn) end),
|
|
map = chain(function(self, fn) self._value = map(self._value, fn) end),
|
|
reduce = chain(function(self, operator, initial) self._value = reduce(self._value, operator, initial) end),
|
|
sort = chain(function(self, key)
|
|
local out = {}
|
|
for k, v in spairs(self._value, key) do
|
|
table.insert(out, { k = k, v = v })
|
|
end
|
|
self._value = out
|
|
end),
|
|
get = function(self) return self._value end
|
|
}
|
|
end
|
|
|
|
local keyboard = {
|
|
"qwertyuiop",
|
|
"asdfghjkl\x1b",
|
|
"zxcvbnm \xd7",
|
|
}
|
|
|
|
local config = { stock = {}, trash = {}, take = {}, storage = {} }
|
|
local item_state = { stock = {}, storage = {}, junk = {} }
|
|
local storage_sizes = { stock = {}, storage = {}, junk = {} }
|
|
local sort_mode, sort_inverse = "count", false
|
|
local search_open, search_query = false, ""
|
|
|
|
local mon = peripheral.find("monitor")
|
|
mon.setTextScale(0.5)
|
|
mon.clear()
|
|
mon.setPaletteColor(colors.blue, 0x131326) -- odd lines
|
|
mon.setPaletteColor(colors.purple, 0x261326) -- even lines
|
|
mon.setPaletteColor(colors.magenta, 0xaa55ff) -- search query
|
|
mon.setPaletteColor(colors.cyan, 0x55aaff) -- keyboard
|
|
mon.setPaletteColor(colors.brown, 0x262626) -- keyboard bg
|
|
|
|
parallel.waitForAll(function()
|
|
while true do
|
|
local fp = assert(io.open("/restock.json", "r"))
|
|
config = textutils.unserializeJSON(fp:read("a"))
|
|
fp:close()
|
|
os.sleep(1)
|
|
end
|
|
end, function()
|
|
while true do
|
|
local new_state = {}
|
|
local new_sizes = {}
|
|
map(config.storage, function(i, inv)
|
|
new_sizes[inv] = peripheral.call(inv, "size")
|
|
for slot, item in pairs(peripheral.call(inv, "list")) do
|
|
new_state[string.format("%s#%d", inv, slot)] = item
|
|
end
|
|
end)
|
|
item_state.storage = new_state
|
|
storage_sizes.storage = new_sizes
|
|
os.sleep(0)
|
|
end
|
|
end, function()
|
|
while true do
|
|
local new_state = {}
|
|
local new_sizes = {}
|
|
map(
|
|
groupby(config.stock, function(location)
|
|
local storage = string.split(location, "#")
|
|
return storage
|
|
end
|
|
), function(inv)
|
|
new_sizes[inv] = peripheral.call(inv, "size")
|
|
for slot, item in pairs(peripheral.call(inv, "list")) do
|
|
new_state[string.format("%s#%d", inv, slot)] = item
|
|
end
|
|
end)
|
|
item_state.stock = new_state
|
|
storage_sizes.stock = new_sizes
|
|
os.sleep(0)
|
|
end
|
|
end, function() -- IMPORT
|
|
while true do
|
|
map(
|
|
groupby(config.stock, function(location) return ({ string.split(location, "#") })[1] end),
|
|
function(stock_inv)
|
|
local size = storage_sizes.stock[stock_inv] or 0
|
|
for i = 1, size do
|
|
local slot = string.format("%s#%d", stock_inv, i)
|
|
local slot_stocked, slot_expected = item_state.stock[slot], config.stock[slot]
|
|
if (slot_stocked and slot_expected and slot_stocked.name ~= slot_expected.name) or (slot_stocked ~= nil and slot_expected == nil) then
|
|
for _, output in ipairs(config.storage) do
|
|
if peripheral.call(stock_inv, "pushItems", output, i) ~= 0 then
|
|
print("MOVE", slot, slot_stocked.name)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
os.sleep(0)
|
|
end
|
|
end, function() -- STOCK
|
|
while true do
|
|
local new_junk = {}
|
|
local item_locations = groupby(item_state.storage, function(location, item)
|
|
new_junk[item.name] = (new_junk[item.name] or 0) + item.count
|
|
return item.name
|
|
end)
|
|
|
|
local tw, th = term.getSize()
|
|
map(config.stock, function(destination, item)
|
|
local dst_container, dst_slot = string.split(destination, "#")
|
|
new_junk[item.name] = nil
|
|
if item_locations[item.name] == nil then return end
|
|
local dst_item = item_state.stock[destination]
|
|
local dst_count = dst_item and dst_item.count or 0
|
|
if dst_count >= item.count then return end
|
|
local missing = item.count - dst_count
|
|
|
|
local _, take_from = pop(filter(item_locations[item.name], function(_, src_item)
|
|
local src_container, src_slot = string.split(src_item.k, "#")
|
|
return src_container ~= dst_container
|
|
end))
|
|
|
|
if not take_from then return end
|
|
local src_container, src_slot = string.split(take_from.k, "#")
|
|
local t = os.clock()
|
|
local out = peripheral.call(dst_container, "pullItems", src_container, tonumber(src_slot), missing, tonumber(dst_slot))
|
|
print(string.format("PUT %s*%d/%d to %s", item.name, out, missing, destination))
|
|
-- print(string.format("%s -> %s (%s*%d) took %.2f", take_from.k, destination, item.name, item.count, os.clock() - t))
|
|
end)
|
|
item_state.junk = new_junk
|
|
os.sleep(0)
|
|
end
|
|
end, function() -- JUNK
|
|
while true do
|
|
local y = 2
|
|
for name, count in pairs(item_state.junk) do
|
|
map(filter(item_state.storage, function(location, item)
|
|
return item.name == name
|
|
end), function(location, item)
|
|
local junk_container, junk_slot = string.split(location, "#")
|
|
for _, junk_output in ipairs(config.junk) do
|
|
if peripheral.call(junk_container, "pushItems", junk_output, tonumber(junk_slot)) ~= 0 then
|
|
print("JUNK OUT", item.name, "->", junk_container)
|
|
break
|
|
end
|
|
end
|
|
end)
|
|
y = y + 1
|
|
end
|
|
os.sleep(0)
|
|
end
|
|
end, function() -- USER INPUT
|
|
while true do
|
|
local ev = { os.pullEvent() }
|
|
if ev[1] == "char" and search_open then
|
|
search_query = search_query .. ev[2]
|
|
mon.setBackgroundColor(colors.gray)
|
|
mon.setTextColor(colors.magenta)
|
|
mon.setCursorPos(tw - 14, th - 20)
|
|
mon.write("> " .. search_query)
|
|
for i, line in ipairs(keyboard) do
|
|
mon.setCursorPos(tw - 14, th - 20 + i)
|
|
mon.setBackgroundColor(colors.brown)
|
|
mon.setTextColor(colors.cyan)
|
|
mon.write(line)
|
|
end
|
|
elseif ev[1] == "key" then
|
|
if ev[2] == keys.enter then
|
|
search_open = not search_open
|
|
elseif ev[2] == keys.backspace and search_open then
|
|
search_query = search_query:sub(1, #search_query - 1)
|
|
mon.setBackgroundColor(colors.gray)
|
|
mon.setTextColor(colors.magenta)
|
|
mon.setCursorPos(tw - 14, th - 20)
|
|
mon.write("> " .. search_query)
|
|
for i, line in ipairs(keyboard) do
|
|
mon.setCursorPos(tw - 14, th - 20 + i)
|
|
mon.setBackgroundColor(colors.brown)
|
|
mon.setTextColor(colors.cyan)
|
|
mon.write(line)
|
|
end
|
|
end
|
|
elseif ev[1] == "monitor_touch" then
|
|
local tw, th = mon.getSize()
|
|
local _, _, x, y = table.unpack(ev)
|
|
if y == 3 or y == th then
|
|
local new_mode
|
|
if x >= 1 and x <= 8 then new_mode = "count"
|
|
elseif x >= 10 and x <= (tw - 1) then new_mode = "name"
|
|
elseif x == tw then
|
|
search_open = not search_open
|
|
end
|
|
|
|
if new_mode ~= nil and new_mode == sort_mode then
|
|
sort_inverse = not sort_inverse
|
|
elseif new_mode ~= nil then
|
|
sort_mode = new_mode
|
|
sort_inverse = false
|
|
end
|
|
elseif search_open and x >= (tw - 14) and x < (tw - 4) and y > (th - 20) and y <= (th - 17) then
|
|
mon.setBackgroundColor(colors.gray)
|
|
mon.setTextColor(colors.magenta)
|
|
mon.setCursorPos(tw - 14, th - 20)
|
|
mon.write("> " .. search_query)
|
|
for i, line in ipairs(keyboard) do
|
|
mon.setCursorPos(tw - 14, th - 20 + i)
|
|
mon.setBackgroundColor(colors.brown)
|
|
mon.setTextColor(colors.cyan)
|
|
mon.write(line)
|
|
end
|
|
|
|
local char = keyboard[20 - th + y]:sub(15 - tw + x, 15 - tw + x)
|
|
if char >= "a" and char <= "z" then
|
|
os.queueEvent("key", keys[char], false)
|
|
os.queueEvent("char", char)
|
|
os.queueEvent("key_up", keys[char])
|
|
else
|
|
-- 27 backspace (ev=259)
|
|
-- 215 enter (ev=257)
|
|
local code = nil
|
|
|
|
if char == "\x1b" then code = keys.backspace
|
|
elseif char == "\xd7" then code = keys.enter
|
|
end
|
|
|
|
if code then
|
|
os.queueEvent("key", code, false)
|
|
os.queueEvent("key_up", code)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end, function() -- STATUS
|
|
|
|
while true do
|
|
local back_term = term.redirect(mon)
|
|
local tw, th = term.getSize()
|
|
term.setBackgroundColor(colors.black)
|
|
term.setTextColor(colors.white)
|
|
term.clear()
|
|
|
|
local usage = pipe(config.storage)
|
|
:map(function(_, storage)
|
|
local used, total = 0, storage_sizes.storage[storage] or 0
|
|
for slot = 1, total do
|
|
if item_state.storage[string.format("%s#%d", storage, slot)] then
|
|
used = used + 1
|
|
end
|
|
end
|
|
return { used = used, total = total }
|
|
end)
|
|
:reduce(function(_, info, acc)
|
|
return { used = info.used + acc.used, total = info.total + acc.total }
|
|
end, { used = 0, total = 0 }):get()
|
|
|
|
draw_bar(1, colors.lightGray, colors.gray, usage.used / usage.total, "Storage utilization: %7.3f%%", 100 * usage.used / usage.total)
|
|
|
|
term.setCursorPos(1, 3)
|
|
term.setBackgroundColor(colors.gray)
|
|
term.clearLine()
|
|
local infoline = string.format("Count %s Name %s",
|
|
(sort_mode == "count" and (sort_inverse and "\x1e" or "\x1f") or " "),
|
|
(sort_mode == "name" and (sort_inverse and "\x1e" or "\x1f") or " "))
|
|
local fg = sort_mode == "count" and "55555551f88888881" or "88888881f55555551"
|
|
term.blit(infoline, fg, "77777777777777777")
|
|
term.setCursorPos(tw, 3)
|
|
term.write("\x0c")
|
|
term.setCursorPos(1, th)
|
|
term.clearLine()
|
|
term.blit(infoline, fg, "77777777777777777")
|
|
term.setCursorPos(tw, th)
|
|
term.write("\x0c")
|
|
|
|
|
|
pipe(item_state.storage)
|
|
:groupby(function(slot, item) return item.name end)
|
|
:filter(function(name, slots)
|
|
if not search_open then return true end
|
|
return string.match(name, search_query)
|
|
end)
|
|
:map(function(name, slots) return reduce(slots, function(_, slot, acc) return slot.v.count + acc end, 0) end)
|
|
:sort(function(t, a, b)
|
|
local swap = false
|
|
if sort_mode == "count" then swap = t[a] > t[b] end
|
|
if sort_mode == "name" then swap = a > b end
|
|
return swap ~= sort_inverse
|
|
end)
|
|
:map(function(i, kv)
|
|
if i >= (th - 4) then return end
|
|
term.setCursorPos(1, 3 + i)
|
|
term.setBackgroundColor((i % 2) == 0 and colors.blue or colors.purple)
|
|
term.clearLine()
|
|
term.write(string.format("%8d %s", kv.v, kv.k))
|
|
end)
|
|
|
|
if search_open then
|
|
mon.setBackgroundColor(colors.gray)
|
|
mon.setTextColor(colors.magenta)
|
|
term.setCursorPos(tw - 14, th - 20)
|
|
term.write("> " .. search_query)
|
|
for i, line in ipairs(keyboard) do
|
|
term.setCursorPos(tw - 14, th - 20 + i)
|
|
term.setBackgroundColor(colors.brown)
|
|
term.setTextColor(colors.cyan)
|
|
term.write(line)
|
|
end
|
|
end
|
|
|
|
term.redirect(back_term)
|
|
os.sleep(1)
|
|
end
|
|
end)
|