Last active
February 5, 2021 04:17
-
-
Save CoderPuppy/0a984e9276c415b08315275356b997d2 to your computer and use it in GitHub Desktop.
ComputerCraft Inventory System
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local I = dofile 'cc/mining/inventory.lua' | |
xpcall(function() | |
dofile 'cc/mining/inventory-ui.lua' (I) | |
end, function(err) | |
if err == 'Terminated' then return end | |
sleep(3) | |
term.setTextColor(colors.red) | |
term.setBackgroundColor(colors.black) | |
term.clear() | |
term.setCursorPos(1, 1) | |
print(err) | |
local i = 3 | |
while true do | |
local _, msg = pcall(error, '@', i) | |
if msg == '@' then | |
break | |
end | |
if i > 10 then break end | |
print(i, msg) | |
i = i + 1 | |
end | |
end) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
-- this is a stupid approach | |
local function split_pat(pat) | |
local initial = nil | |
or pat:match '^%b[][?*+-]?' | |
or pat:match '^%%.[?*+-]?' | |
or pat:match '^[^[%%][?*+-]?' | |
return initial, pat:sub(#initial + 1) | |
end | |
local function fuzzy_match(pat, str) | |
local rest = pat | |
local pat_pos = 1 | |
while #rest > 0 do | |
local initial | |
initial, rest = split_pat(rest) | |
local last = initial:sub(#initial) | |
if initial ~= '%*' and last == '*' then | |
initial = initial:sub(1, #initial - 1) .. '+' | |
elseif initial ~= '%?' and last == '?' then | |
initial = initial:sub(1, #initial - 1) | |
end | |
for str_pos, m in str:gmatch('()(' .. initial .. ')') do | |
local subpat = '^' .. initial | |
local rest_run = rest | |
local longest = m | |
while true do | |
local part | |
part, rest_run = split_pat(rest_run) | |
local try_pat = subpat .. part | |
local m = str:match(try_pat, str_pos) | |
if m then | |
longest = m | |
subpat = try_pat | |
else | |
break | |
end | |
end | |
end | |
pat_pos = pat_pos + #initial | |
end | |
end | |
--]] | |
--[[ | |
local function fuzzy_match(pat, str) | |
local tbl = {} | |
for pat_i = 1, #pat do | |
local pat_c = pat:sub(pat_i, pat_i) | |
for str_i = 1, #str do | |
local str_c = str:sub(str_i, str_i) | |
if str_c == pat_c then | |
else | |
end | |
end | |
end | |
end | |
--]] | |
return function(I) | |
local width, height = term.getSize() | |
local function format_count(n) | |
local suffix = '' | |
local decimal = false | |
if n > 1000 then | |
n = n / 1000 | |
suffix = 'k' | |
decimal = n < 10000 | |
end | |
return string.format(decimal and '%.1f%s' or '%d%s', n, suffix) | |
end | |
local keys_down = {} | |
local list_model | |
local function make_list_model() | |
local model = { | |
search = ''; | |
search_i = 1; | |
list = {}; | |
list_i = 1; | |
selected_i = 1; | |
-- required fields: | |
-- iter | |
-- item_str = function(item) return 'display string' end; | |
-- item_searchable = function(item) return 'string to search' end; | |
-- order = function(a, b) return a < b end; | |
} | |
return model | |
end | |
local function list_model_draw_search() | |
term.setCursorPos(1, 1) | |
term.blit( | |
list_model.search:sub(list_model.search_i) .. string.rep(' ', width - #list_model.search - list_model.search_i + 1), | |
string.rep('f', width), | |
string.rep('1', width) | |
) | |
term.setCursorPos(#list_model.search - list_model.search_i + 2, 1) | |
end | |
local function list_model_draw_item(i) | |
if i < list_model.list_i or i > list_model.list_i + height - 2 then | |
return | |
end | |
term.setCursorPos(1, i - list_model.list_i + 2) | |
local item = list_model.list[i] | |
local selected = i == list_model.selected_i | |
if item then | |
term.blit( | |
list_model.item_str(item), | |
string.rep(selected and '0' or '8', width), | |
string.rep(selected and '8' or '7', width) | |
) | |
else | |
term.blit( | |
string.rep(' ', width), | |
string.rep('c', width), | |
string.rep('f', width) | |
) | |
end | |
end | |
local function list_model_fix_scroll(skip_draw) | |
local old_list_i = list_model.list_i | |
if list_model.list_i < 1 then | |
list_model.list_i = 1 | |
elseif list_model.list_i ~= 1 and list_model.list_i > #list_model.list - height + 2 then | |
list_model.list_i = #list_model.list - height + 2 | |
end | |
if list_model.selected_i < 1 then | |
list_model.selected_i = 1 | |
elseif list_model.selected_i > #list_model.list then | |
list_model.selected_i = #list_model.list | |
end | |
if list_model.selected_i < list_model.list_i then | |
list_model.list_i = list_model.selected_i | |
elseif list_model.selected_i > list_model.list_i + height - 2 then | |
list_model.list_i = list_model.selected_i - height + 2 | |
else | |
return | |
end | |
if not skip_draw then | |
term.scroll(list_model.list_i - old_list_i) | |
if list_model.list_i < old_list_i then | |
for i = list_model.list_i, math.min(list_model.list_i + height - 2, old_list_i) do | |
list_model_draw_item(i) | |
end | |
elseif list_model.list_i > old_list_i then | |
for i = math.max(list_model.list_i - height + 2, old_list_i), list_model.list_i + height - 2 do | |
list_model_draw_item(i) | |
end | |
end | |
list_model_draw_search() | |
end | |
return list_model.list_i ~= old_list_i | |
end | |
local function list_model_update_list() | |
local prev_selected = list_model.list[list_model.selected_i] | |
local new_select_i | |
local new_list = { n = 0; } | |
local ignore_case = not list_model.search:match '[A-Z]' | |
for item in list_model.iter() do | |
local name = list_model.item_searchable(item) | |
-- item_type.example.displayName | |
if ignore_case then | |
name = string.lower(name) | |
end | |
local ok, match = pcall(string.match, name, list_model.search) | |
if not ok then return end | |
if match then | |
new_list.n = new_list.n + 1 | |
new_list[new_list.n] = item | |
if item == selected then | |
new_select_i = new_list.n | |
end | |
end | |
end | |
table.sort(new_list, list_model.order) | |
list_model.list = new_list | |
list_model.selected_i = new_select_i or 1 | |
list_model_fix_scroll(true) | |
for i = list_model.list_i, list_model.list_i + height - 2 do | |
list_model_draw_item(i) | |
end | |
end | |
local function list_model_full_update() | |
list_model_update_list() | |
list_model_draw_search() | |
term.setCursorBlink(true) | |
end | |
function handle_list_model(evt) | |
if not list_model then return end | |
if evt[1] == 'key' then | |
if evt[2] == keys.backspace then | |
if keys_down.ctrl then | |
if keys_down.shift then | |
list_model.search = '' | |
else | |
list_model.search = list_model.search:gsub('[^%s]+[%s]*$', '') | |
end | |
else | |
list_model.search = list_model.search:sub(1, #list_model.search - 1) | |
end | |
list_model_full_update() | |
elseif evt[2] == keys.down then | |
list_model.selected_i = list_model.selected_i + 1 | |
if not list_model_fix_scroll() then | |
list_model_draw_item(list_model.selected_i) | |
end | |
list_model_draw_item(list_model.selected_i - 1) | |
elseif evt[2] == keys.up then | |
list_model.selected_i = list_model.selected_i - 1 | |
if not list_model_fix_scroll() then | |
list_model_draw_item(list_model.selected_i) | |
end | |
list_model_draw_item(list_model.selected_i + 1) | |
end | |
elseif evt[1] == 'char' then | |
if not keys_down.ctrl and not keys_down.alt then | |
list_model.search = list_model.search .. evt[2] | |
list_model_full_update() | |
end | |
end | |
end | |
local inv_list_model; do | |
local function custom_next(s, prev_item) | |
local key, item = next(s, prev_item and prev_item.key) | |
-- if item and item.number <= 0 then | |
-- return custom_next(s, item) | |
-- else | |
return item | |
-- end | |
end | |
inv_list_model = make_list_model() | |
function inv_list_model.iter() | |
return custom_next, I.state.item_types | |
end | |
function inv_list_model.item_str(item) | |
local name = item.example.displayName | |
local count = format_count(item.number) | |
return name .. string.rep(' ', width - #name - #count) .. count | |
end | |
function inv_list_model.item_searchable(item) | |
return item.example.displayName | |
end | |
function inv_list_model.order(a, b) | |
-- return a.example.displayName < b.example.displayName | |
return a.number > b.number | |
end | |
end | |
local ext_list_model, ext_handle; do | |
ext_list_model = make_list_model() | |
local function custom_next(s, prev_item) | |
local slot, item = next(s, prev_item and prev_item.slot) | |
if item then | |
item.slot = slot | |
item.item_type = I.identify(item, function() | |
return ext_handle.getItemDetail(slot) | |
end) | |
end | |
return item | |
end | |
function ext_list_model.iter() | |
return custom_next, ext_handle.list() | |
end | |
function ext_list_model.item_str(item) | |
local name = item.item_type.example.displayName | |
local count = tostring(item.count) | |
return name .. string.rep(' ', width - #name - #count) .. count | |
end | |
function ext_list_model.item_searchable(item) | |
return item.item_type.example.displayName | |
end; | |
function ext_list_model.order(a, b) | |
return a.slot < b.slot | |
end | |
end | |
if I.initialize() > 16 then | |
I.save() | |
end | |
ext_handle = peripheral.wrap'minecraft:chest_0' | |
-- TODO: this is a bit messy | |
ext_handle.num_slots = ext_handle.size() | |
-- list_model = ext_list_model | |
list_model = inv_list_model | |
list_model_full_update() | |
while true do | |
local evt = table.pack(os.pullEvent()) | |
if evt[1] == 'key' then | |
keys_down[evt[2]] = true | |
elseif evt[1] == 'key_up' then | |
keys_down[evt[2]] = nil | |
end | |
keys_down.ctrl = keys_down[keys.leftCtrl ] or keys_down[keys.rightCtrl ] | |
keys_down.shift = keys_down[keys.leftShift] or keys_down[keys.rightShift] | |
keys_down.alt = keys_down[keys.leftAlt ] or keys_down[keys.rightAlt ] | |
keys_down.mods = (keys_down.ctrl and 'c' or '') .. (keys_down.alt and 'a' or '') .. (keys_down.shift and 's' or '') | |
if evt[1] == 'key' and evt[2] == keys.s and keys_down.mods == 'c' then | |
I.save() | |
elseif evt[1] == 'key' and evt[2] == keys.enter and list_model and not keys_down.shift then | |
if list_model == ext_list_model then | |
local item = list_model.list[list_model.selected_i] | |
if not item then | |
-- skip | |
elseif keys_down.alt then | |
-- TODO | |
else | |
-- this will get refreshed anyways by list_model_full_update | |
-- and it causes problem with inventory's logging | |
-- because it's recursive | |
item.item_type = nil | |
local n = I.insert(ext_handle, item.slot, keys_down.ctrl and item.count or 1, item) | |
item.count = item.count - n | |
if item.count <= 0 then | |
table.remove(list_model.list, list_model.selected_i) | |
list_model_fix_scroll() | |
for i = list_model.selected_i, list_model.list_i + height - 2 do | |
list_model_draw_item(i) | |
end | |
else | |
list_model_draw_item(list_model.selected_i) | |
end | |
end | |
elseif list_model == inv_list_model then | |
local item_type = list_model.list[list_model.selected_i] | |
if not item_type then | |
-- skip | |
elseif keys_down.alt then | |
-- TODO | |
else | |
-- TODO: maybe this shouldn't reference ext_handle | |
assert(ext_handle) | |
local list = ext_handle.list() | |
local remaining = keys_down.ctrl and item_type.example.maxCount or 1 | |
for slot = 1, ext_handle.num_slots do | |
local stack = list[slot] | |
if not stack or (stack.name == item_type.name and stack.nbt == item_type.nbt) then | |
local n = I.extract(item_type, ext_handle, slot, remaining) | |
remaining = remaining - n | |
if remaining <= 0 then | |
break | |
end | |
end | |
end | |
list_model_draw_item(list_model.selected_i) | |
end | |
end | |
elseif evt[1] == 'key' and evt[2] == keys.tab and keys_down.mods == '' and list_model then | |
if list_model == inv_list_model then | |
list_model = ext_list_model | |
elseif list_model == ext_list_model then | |
list_model = inv_list_model | |
end | |
list_model_full_update() | |
end | |
handle_list_model(evt) | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if false then | |
local Tchest = T { | |
name = T.string; | |
num_slots = T.int; | |
empties = T.map(T.int, T.lit(true)); | |
item_types = T.map(Titem_type, T.lit(true)); | |
} | |
local Titem_type = T { | |
key = T.string; | |
name = T.string; | |
nbt = T.union(T.string, T.null); | |
example = T.any; | |
number = T.int; | |
-- TODO: maybe figure out the max stack size | |
-- max_stack_size = T.int; | |
chests = T.map(Tchest, T.map(T.int, T.int)); | |
partials = T.map(Tchest, T.int); | |
} | |
local Tsave = T { | |
id = T.int; | |
item_types = T.map(T.string, T { | |
name = T.string; | |
nbt = T.union(T.string, T.null); | |
example = T.any; | |
number = T.int; | |
chests = T.map(T.string, T.map(T.int, T.int)); | |
partials = T.map(T.string, T.int); | |
}); | |
chests = T.map(T.string, T { | |
num_slots = T.int; | |
empties = T.map(T.int, T.lit(true)); | |
item_types = T.map(T.string, T.lit(true)); | |
}); | |
chest_room = T.map(T.string, T.lit(true)); | |
} | |
end | |
local log; do | |
local f = fs.open('log', 'a') | |
function log(msg) | |
f.write(os.date'%F %T\t' .. msg .. '\n') | |
f.flush() | |
end | |
log 'start' | |
end | |
local save_id | |
local item_types | |
local chests | |
local chest_room | |
local transaction_log | |
-- the transaction log records virtual updates which are not saved to disk otherwise | |
-- TODO: in flight records physical updates which are in progress | |
local function item_type_key(name, nbt) | |
return string.format('%q%q', name, nbt) | |
end | |
local function serialize(v) | |
local t = type(v) | |
if t == 'string' then | |
return string.format('%q', v) | |
elseif t == 'number' or t == 'boolean' then | |
return tostring(t) | |
elseif t == 'table' then | |
local s = '{' | |
for k, v in pairs(v) do | |
s = string.format('%s[%s]=%s;', s, serialize(k), serialize(v)) | |
end | |
return s .. '}' | |
else | |
error(string.format('unhandled type: %q', t)) | |
end | |
end | |
-- TODO: one function which writes to the transaction log and performs the transaction | |
local function track_add_item_type(itk, detail) | |
-- TODO: there's no good reason for itk to be passed in | |
-- just because it was already computed in `insert` | |
-- where this was extracted from | |
local item_type = { | |
key = itk; | |
name = detail.name; | |
nbt = detail.nbt; | |
example = detail; | |
number = 0; | |
chests = {}; | |
partials = {}; | |
} | |
item_types[itk] = item_type | |
return item_type | |
end | |
local function track_insert(chest, slot, item_type, num, full) | |
item_type.partials[chest] = not full and slot or nil | |
if chest.empties[slot] then | |
chest.empties[slot] = nil | |
if not next(chest.empties) then | |
chest_room[chest] = nil | |
end | |
end | |
chest.item_types[item_type] = true | |
local chest_stacks = item_type.chests[chest] | |
if not chest_stacks then | |
chest_stacks = {} | |
item_type.chests[chest] = chest_stacks | |
end | |
chest_stacks[slot] = (chest_stacks[slot] or 0) + num | |
item_type.number = item_type.number + num | |
end | |
local function track_extract(chest, slot, item_type, num) | |
local chest_slots = item_type.chests[chest] | |
local slot_num = chest_slots[slot] | |
chest_slots[slot] = slot_num - num | |
item_type.number = item_type.number - num | |
if num >= slot_num then | |
assert(num == slot_num) | |
assert(item_type.partials[chest] == slot) | |
item_type.partials[chest] = nil | |
chest_slots[slot] = nil | |
if not next(chest_slots) then | |
item_type.chests[chest] = nil | |
chest.item_types[item_type] = nil | |
if not next(item_type.chests) then | |
-- TODO: destroy the item type? | |
end | |
end | |
chest.empties[slot] = true | |
chest_room[chest] = true | |
else | |
item_type.partials[chest] = slot | |
end | |
end | |
local function initialize() | |
if transaction_log then | |
transaction_log.close() | |
end | |
-- TODO: filesystem layout | |
if fs.exists('inv-save-new') then | |
assert(not fs.isDir('inv-save-new')) | |
fs.delete('inv-save') | |
fs.move('inv-save-new', 'inv-save') | |
end | |
local h = fs.open('inv-save', 'r') | |
if h then | |
local save = textutils.unserialize(h.readAll()) | |
h.close() | |
save_id = save.id | |
chests = {} | |
for name, save_chest in pairs(save.chests) do | |
local chest = { | |
name = name; | |
num_slots = save_chest.num_slots; | |
empties = save_chest.empties; | |
item_types = {}; | |
} | |
chests[name] = chest | |
end | |
item_types = {} | |
for key, save_item_type in pairs(save.item_types) do | |
local item_type = { | |
key = key; | |
name = save_item_type.name; | |
nbt = save_item_type.nbt; | |
example = save_item_type.example; | |
number = save_item_type.number; | |
chests = {}; | |
partials = {}; | |
} | |
for chest_name, slots in pairs(save_item_type.chests) do | |
item_type.chests[chests[chest_name]] = slots | |
end | |
for chest_name, slot in pairs(save_item_type.partials) do | |
item_type.partials[chests[chest_name]] = slot | |
end | |
item_types[item_type.key] = item_type | |
end | |
for name, save_chest in pairs(save.chests) do | |
local chest = chests[name] | |
for itk in pairs(save_chest.item_types) do | |
chest.item_types[item_types[itk]] = true | |
end | |
end | |
chest_room = {} | |
for name in pairs(save.chest_room) do | |
chest_room[chests[name]] = true | |
end | |
else | |
save_id = -1 | |
item_types = {} | |
chests = {} | |
chest_room = {} | |
end | |
local n = 0 | |
-- TODO: filesystem layout | |
local h = fs.open('inv-transaction-log', 'r') | |
if h then | |
assert(tostring(save_id) == h.readLine(), 'TODO') | |
while true do | |
local line = h.readLine(true) | |
if not line then break end | |
local entry = textutils.unserialize(line) | |
log('replay: ' .. textutils.serialize(entry)) | |
if entry.type == 'add_chest' then | |
-- TODO: this is quite limited | |
-- see `add_chest` for more about this | |
local chest = { | |
name = entry.name; | |
num_slots = entry.num_slots; | |
empties = {}; | |
item_types = {}; | |
} | |
chests[chest.name] = chest | |
for i = 1, chest.num_slots do | |
chest.empties[i] = true | |
end | |
chest_room[chest] = true | |
elseif entry.type == 'add_item_type' then | |
track_add_item_type(entry.item_type_key, entry.detail) | |
elseif entry.type == 'insert' then | |
track_insert( | |
chests[entry.chest_name], entry.slot, | |
item_types[entry.item_type_key], | |
entry.num, entry.full | |
) | |
elseif entry.type == 'extract' then | |
track_extract( | |
chests[entry.chest_name], entry.slot, | |
item_types[entry.item_type_key], | |
entry.num | |
) | |
else | |
error(string.format('unhandled transaction type: %q', entry.type)) | |
end | |
n = n + 1 | |
end | |
h.close() | |
-- TODO: filesystem layout | |
transaction_log = fs.open('inv-transaction-log', 'a') | |
else | |
-- TODO: filesystem layout | |
transaction_log = fs.open('inv-transaction-log', 'w') | |
transaction_log.write(string.format('%d\n', save_id)) | |
transaction_log.flush() | |
end | |
-- TODO: in flight | |
return n | |
end | |
local function save() | |
transaction_log.close() | |
local save = { | |
id = save_id + 1; | |
item_types = {}; | |
chests = {}; | |
chest_room = {}; | |
} | |
for key, item_type in pairs(item_types) do | |
local save_item_type = { | |
name = item_type.name; | |
nbt = item_type.nbt; | |
example = item_type.example; | |
number = item_type.number; | |
chests = {}; | |
partials = {}; | |
} | |
for chest, slots in pairs(item_type.chests) do | |
save_item_type.chests[chest.name] = slots | |
end | |
for chest, slot in pairs(item_type.partials) do | |
save_item_type.partials[chest.name] = slot | |
end | |
save.item_types[key] = save_item_type | |
end | |
for name, chest in pairs(chests) do | |
local save_chest = { | |
num_slots = chest.num_slots; | |
empties = chest.empties; | |
item_types = {}; | |
} | |
for item_type in pairs(chest.item_types) do | |
save_chest.item_types[item_type.key] = true | |
end | |
save.chests[chest.name] = save_chest | |
end | |
for chest in pairs(chest_room) do | |
save.chest_room[chest.name] = true | |
end | |
-- TODO: filesystem layout | |
local h = fs.open('inv-save-new', 'w') | |
h.write(textutils.serialize(save)) | |
h.close() | |
fs.delete('inv-save') | |
fs.move('inv-save-new', 'inv-save') | |
save_id = save.id | |
-- TODO: filesystem layout | |
transaction_log = fs.open('inv-transaction-log', 'w') | |
transaction_log.write(string.format('%d\n', save_id)) | |
transaction_log.flush() | |
end | |
local function reset() | |
save_id = nil | |
item_types = nil | |
chests = nil | |
chest_room = nil | |
if transaction_log then | |
transaction_log.close() | |
transaction_log = nil | |
end | |
-- TODO: filesystem layout | |
fs.delete('inv-save') | |
fs.delete('inv-save-new') | |
fs.delete('inv-transaction-log') | |
end | |
local function close() | |
transaction_log.close() | |
transaction_log = nil | |
save_id = nil | |
item_types = nil | |
chests = nil | |
chest_room = nil | |
end | |
local function identify(detail, add) | |
local itk = item_type_key(detail.name, detail.nbt) | |
local item_type = item_types[itk] | |
if not item_type and add then | |
if not detail.displayName then | |
detail = add() | |
end | |
-- create the item type if none exists | |
item_type = track_add_item_type(itk, detail) | |
transaction_log.write(string.format( | |
'{' | |
.. ' type = "add_item_type";' | |
.. ' item_type_key = %q;' | |
.. ' detail = %s;' | |
.. '}\n', | |
itk, serialize(detail) | |
)) | |
transaction_log.flush() | |
end | |
return item_type | |
end | |
local function add_chest(name) | |
local inv = peripheral.wrap(name) | |
local chest = { | |
name = name; | |
num_slots = inv.size(); | |
empties = {}; | |
item_types = {}; | |
} | |
chests[chest.name] = chest | |
local contents = inv.list() | |
local has_room = false | |
for i = 1, chest.num_slots do | |
if contents[i] then | |
error 'TODO' | |
-- it can't just index it, because there are rules (specifically about only one partial per item type and chest) | |
-- maybe just return nil, saying we can't add this chest | |
-- or it could move items around to maintain the rules | |
-- remember to change the transaction log stuff if implementing this | |
else | |
has_room = true | |
chest.empties[i] = true | |
end | |
end | |
if has_room then | |
chest_room[chest] = true | |
end | |
transaction_log.write(string.format('{ type = "add_chest"; name = %q; num_slots = %d; }\n', name, chest.num_slots)) | |
transaction_log.flush() | |
return chest | |
end | |
local function insert(inv, slot, amt, detail) | |
local detail = detail or inv.getItemDetail(slot) | |
log('insert: ' .. textutils.serialize(detail)) | |
-- find or create a corresponding item type | |
local item_type = identify(detail, function() | |
return inv.getItemDetail(slot) | |
end) | |
-- move the entire stack | |
local remaining = amt or detail.count | |
while remaining > 0 do | |
-- find a place to put it | |
local dst_chest, dst_slot | |
-- first try a slot with some of this item type (but not full) | |
dst_chest, dst_slot = next(item_type.partials) | |
if not dst_chest then | |
-- otherwise find an empty slot | |
for chest in pairs(chest_room) do | |
dst_slot = next(chest.empties) | |
if dst_slot then | |
dst_chest = chest | |
break | |
end | |
end | |
assert(dst_chest, 'TODO: no room in system') | |
end | |
-- TODO: in flight | |
local n = inv.pushItems(dst_chest.name, slot, remaining, dst_slot) | |
track_insert(dst_chest, dst_slot, item_type, n, n < remaining) | |
transaction_log.write(string.format( | |
'{' | |
.. ' type = "insert";' | |
.. ' chest_name = %q;' | |
.. ' slot = %d;' | |
.. ' item_type_key = %q;' | |
.. ' num = %d;' | |
.. ' full = %s;' | |
.. '}\n', | |
dst_chest.name, dst_slot, item_type.key, n, n < remaining | |
)) | |
transaction_log.flush() | |
remaining = remaining - n | |
end | |
return amt or detail.count, item_type | |
end | |
local function extract(item_type, inv, dst_slot, amt) | |
local transferred = 0 | |
local remaining = amt or item_type.number | |
local function extract_part(chest, slot) | |
local chest_slots = item_type.chests[chest] | |
local slot_num = chest_slots[slot] | |
local pull = slot_num < remaining and slot_num or remaining | |
-- TODO: in flight | |
local n = inv.pullItems(chest.name, slot, pull, dst_slot) | |
remaining = remaining - n | |
transferred = transferred + n | |
track_extract(chest, slot, item_type, n) | |
transaction_log.write(string.format( | |
'{' | |
.. ' type = "extract";' | |
.. ' chest_name = %q;' | |
.. ' slot = %d;' | |
.. ' item_type_key = %q;' | |
.. ' num = %d;' | |
.. '}\n', | |
chest.name, slot, item_type.key, n | |
)) | |
transaction_log.flush() | |
if n < pull then | |
-- no more room in the destination slot | |
-- this is a slightly hacky way to break out | |
remaining = 0 | |
end | |
end | |
if remaining > 0 then | |
for chest, slot in pairs(item_type.partials) do | |
extract_part(chest, slot) | |
if remaining <= 0 then | |
assert(remaining == 0) | |
break | |
end | |
end | |
end | |
if remaining > 0 then | |
for chest, slots in pairs(item_type.chests) do | |
for slot, number in pairs(slots) do | |
extract_part(chest, slot) | |
if remaining <= 0 then | |
assert(remaining == 0) | |
break | |
end | |
end | |
if remaining <= 0 then | |
assert(remaining == 0) | |
break | |
end | |
end | |
end | |
return transferred | |
end | |
return { | |
log = log; | |
state = setmetatable({}, { | |
__index = function(self, key) | |
if key == 'save_id' then | |
return save_id | |
elseif key == 'item_types' then | |
return item_types | |
elseif key == 'chests' then | |
return chests | |
elseif key == 'chest_room' then | |
return chest_room | |
else | |
return nil | |
end | |
end; | |
__newindex = function(self, key, val) | |
error('bad') | |
end; | |
}); | |
item_type_key = item_type_key; | |
identify = identify; | |
initialize = initialize; | |
save = save; | |
reset = reset; | |
close = close; | |
track_insert = track_insert; | |
track_extract = track_extract; | |
add_chest = add_chest; | |
insert = insert; | |
extract = extract; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment