Checkpoint.

This commit is contained in:
Mike Stump 2023-12-30 15:52:43 -08:00
parent f5aee436d0
commit 9235b7bad1
18 changed files with 3829 additions and 736 deletions

@ -116,7 +116,7 @@ function microexpansion.register_node(itemstring, def)
end end
end end
-- get a node, if nessecary load it -- get a node, if necessary load it
function microexpansion.get_node(pos) function microexpansion.get_node(pos)
local node = minetest.get_node_or_nil(pos) local node = minetest.get_node_or_nil(pos)
if node then return node end if node then return node end

@ -1,4 +1,4 @@
name = microexpansion name = microexpansion
description = A storage managing solution to get an overview over all your items. description = A storage managing solution to get an overview over all your items.
depends = default depends = default
optional_depends = pipeworks optional_depends = pipeworks, drawers, technic_plus

@ -0,0 +1,477 @@
local me = microexpansion
local pipeworks_craft_time = 1
function me.autocraft_next_start(net)
if net.pending then
-- start subsequent autocrafting jobs sequentially.
-- We really only need zero/not zero for build to queue actions or not
return net.pending.time[net.pending.max_index]
end
return 0
end
function me.start_crafting(pos, step_time)
local meta = minetest.get_meta(pos)
local timer = minetest.get_node_timer(pos)
timer:set(step_time, 0)
end
local function round(v)
return math.floor(v + 0.5)
end
-- technic_plus doesn't export machine speed. We
-- use this to know exactly how long a machine will take to process
-- anything, after that time, we know it is done and we can grab the
-- outputs, no polling. We do this for efficiency.
me.speed = {
["default:furnace"] = 1,
}
-- Use to wire in how long a machine takes to process something.
function me.set_speed(name, speed)
me.speed[name] = speed
end
-- Sometimes the poor autocrafter doesn't have infinite input and output room
-- for a craft, break large ones up to fit.
-- Also machine inputs/outputs don't fit.
me.maximums = {
}
-- Allow a maximum craft to be defined to avoid overrunning machine
-- inputs and outputs and the autocrafter inputs and output..
function me.register_max(name, count)
me.maximums[name] = count
end
local function build(net, cpos, inv, name, count, stack, sink, time)
-- The autocrafters nor the machines can take really large amounts
-- of things, help them out.
local max = me.maximums[name]
if not max then
-- If no explicit max, assume this is a pipeworks autocrafter and
-- it only has 12 outputs.
max = stack:get_stack_max()*12
end
if max and count > max then
local next_time = time
local built = true
while count > 1 and built do
local substack = ItemStack(stack)
max = math.min(max, count)
substack:set_count(max)
local step_time
built, step_time = build(net, cpos, inv, name, max, substack, sink, next_time)
if not built then
-- we are done, can't craft, so stop
else
next_time = math.max(next_time, next_time + step_time)
end
count = count - max
end
local step_time = next_time - time
return built, step_time
end
me.log("BUILD: count is "..count.." and stack size is "..stack:get_count(), "error")
local dat = {}
local second_output = nil
if net.process and net.process[name] then
dat.apos, dat.ipos = next(net.process[name])
dat.rinv = minetest.get_meta(dat.apos):get_inventory()
me.log("INT: looking up output "..name, "error")
local inputs = me.find_by_output(name)
local machine_name = minetest.get_node(dat.apos).name
local typename = me.block_to_typename_map[machine_name]
dat.recip = me.get_recipe(typename, inputs)
me.log("MACHINE: "..machine_name.." typename is "..typename.." and time is "..tostring(dat.recip.time), "error")
dat.recip.input = inputs
-- freezer can produce two outputs, we only care about the first.
if dat.recip.output[1] then
second_output = dat.recip.output[2]
dat.recip.output = dat.recip.output[1]
end
dat.ostack = ItemStack(dat.recip.output)
-- me.log("SEE: "..machine_name.." "..minetest.serialize(technic.recipes))
local speed = me.speed[machine_name]
local craft_count = dat.ostack:get_count()
local total = math.ceil(count/craft_count)
-- crafting 4 carbon plates misses taking 1 carbin plate on output, make this bigger
-- we'll try 1 for now, figure out right formula. 1 looks perfect
dat.recip.time = round((total+1)*dat.recip.time/speed)
if second_output then
second_output = ItemStack(second_output)
second_output:set_count(second_output:get_count()*total)
end
me.log("MACHINE: "..machine_name.." is speed "..speed.." and final time is "..dat.recip.time, "error")
elseif net.autocrafters[name] then
-- fill all recipe slots, wait, grab all output slots
-- "src" "dst" "recipe" "output"
dat.apos, dat.ipos = next(net.autocrafters[name])
-- TODO: If we set up pipeworks ac, then we remove interface for it and craft
-- it goes to ac, and dies here. Flush net.autocrafters for all the
-- attached inventories during interface removal.
dat.rinv = minetest.get_meta(dat.apos):get_inventory()
if dat.rinv == nil then
me.log("no inventory", "error")
return
end
dat.ostack = dat.rinv:get_stack("output", 1)
if dat.ostack:get_name() ~= name then
-- invalidate it
net.autocrafters[name][dat.apos] = nil
-- me.log("invalidating autocrafter", "error")
return
end
else
me.log("can't craft a "..name, "error")
return
end
dat.isink = function(sstack)
me.log("TIMER: prep inputs, moving "..sstack:get_count().." "..sstack:get_name(), "error")
return dat.rinv:add_item("src", sstack)
end
local craft_count = dat.ostack:get_count()
-- These will be returned to the me system
local extra = ItemStack(name)
local total = math.ceil(count/craft_count)
extra:set_count(total*craft_count - count)
me.log("AC: count "..count.." craft_count "..craft_count.." extra "..extra:get_count(), "error");
-- we craft a minimum of count, to the multiple of the crafting count
count = total
me.log("AC: numcount "..count, "error");
local consume = {}
if net.process and net.process[name] then
for i = 1, #dat.recip.input do
local inp = dat.recip.input[i]
me.log("MID: consuming "..inp:get_name().." count: "..count.." inp:getcount: "..inp:get_count(), "error")
consume[inp:get_name()] = (consume[inp:get_name()] or 0) + count*inp:get_count()
end
else
for i = 1, 9 do
local inp = dat.rinv:get_stack("recipe", i)
if inp and not inp:is_empty() then
consume[inp:get_name()] = (consume[inp:get_name()] or 0) + count*inp:get_count()
end
end
end
local replace = true
local next_time = time
me.log("PREP: pre count is "..count, "error")
-- prepwork
me.log("PREP: count is "..count, "error")
local prepworkbits = {}
local previous_ac_size = inv:get_size("ac")
me.log("PREP: ac size at top is "..previous_ac_size, "error")
for name, count in pairs(consume) do
local istack = ItemStack(name)
if count >= math.pow(2,16) then
replace = false
break
end
-- Don't consume the last item by autocrafting
istack:set_count(count+1)
local hasit = inv:contains_item("main", istack)
istack:set_count(count)
me.log("ac checking "..name, "error")
if hasit then
me.log("ac grabbing "..name, "error")
local grabbed = me.remove_item(net, inv, "main", istack)
if grabbed then
me.log("ac grabbed "..name, "error")
local slot = inv:get_size("ac")+1
inv:set_size("ac", slot)
inv:set_stack("ac", slot, grabbed)
-- and later we do this:
prepworkbits[function()
me.log("PREP: about to move "..name, "error")
local stack = inv:get_stack("ac", slot)
me.log("PREP: before move actual content of slot "..slot.." is "..stack:get_count().." "..stack:get_name(), "error")
local leftovers = dat.rinv:add_item("src", stack)
me.log("PREP: post move into real inventory "..leftovers:get_count().." leftovers", "error")
me.log("PREP: did move "..name, "error")
inv:set_stack("ac", slot, leftovers)
end] = name
-- and then something moves the size of ac back to before we started
end
else
-- Try and autocraft it
me.log("AC: recursive crafting "..count.." "..istack:get_count(), "error")
local built, step_time = build(net, cpos, inv, name, count, istack, dat.isink, time)
if built then
hasit = true
next_time = math.max(next_time, time + step_time)
else
me.log("can't craft "..istack:get_count().." "..istack:get_name(), "error")
end
end
replace = replace and hasit
end
local prepwork = function ()
-- Do all the little bits of prepwork
for func, name in pairs(prepworkbits) do
me.log("PREPing: before "..name, "error")
func()
me.log("PREPing: done "..name, "error")
end
end
-- end of prepwork
if not replace then
-- If we don't have everything, and can't craft it, we're stuck,
-- do as much as we can, then nothing else
me.log("missing items", "error")
-- Existing items are already loaded.
return
end
local main_action_time = count * pipeworks_craft_time + 1
if net.process and net.process[name] then
main_action_time = dat.recip.time + 1
end
local main_action = function()
me.log("ACTION: prep for "..stack:get_name(), "error")
prepwork()
-- and once we are done with all the postponed work, we can reduce "ac"
-- lifetimes are more complex than you can imagine.
-- We use a simple rule. When all done, there is nothing left. At that point,
-- we can put any leftovers back into the main inventory.
-- Even this might be too soon, if we have multiple independent crafts going, we
-- need the last one.
if previous_ac_size == 0 then
for i = 1,inv:get_size("ac") do
local stack = inv:get_stack("ac", i)
if stack:get_count() ~= 0 then
me.log("AC: putting "..stack:get_count().." "..stack:get_name().." back into main inventory", "error")
local leftovers = me.insert_item(stack, net, inv, "main")
if leftovers:get_count() > 0 then
-- drop on floor, todo: play sound
minetest.add_item(cpos, leftovers)
end
end
end
me.log("PREP: ac size is now down to "..previous_ac_size, "error")
inv:set_size("ac", previous_ac_size)
end
me.log("ACTION: main for "..stack:get_name(), "error")
local rmeta = minetest.get_meta(dat.apos)
-- Let's start up the crafter since we loaded it up to run
if (net.process and net.process[name]) or rmeta:get_int("enabled") == 1 then
local timer = minetest.get_node_timer(dat.apos)
if not timer:is_started() then
me.log("TIMER: starting ac now for "..stack:get_name(), "error")
timer:start(pipeworks_craft_time)
end
me.log("TIMER: registering timer for "..stack:get_name(), "error")
local action_time_step = count * pipeworks_craft_time + 1
if net.process and net.process[name] then
action_time_step = dat.recip.time + 1
end
local action = function(net)
me.log("ACTION: post craft for "..stack:get_name(), "error")
me.log("TIMER: moving "..stack:get_count().." "..stack:get_name(), "error")
-- deal with output and replacements
local dst_stack = dat.rinv:remove_item("dst", stack)
if dst_stack:get_count() ~= stack:get_count() then
me.log("wow, missing items that should have been crafted "..stack:get_name(), "error")
-- me.log("saw "..dst_stack:get_count().." items instead of "..stack:get_count().." items", "error")
end
if not dst_stack:is_empty() then
me.log("TIMER: inserting "..dst_stack:get_count().." "..dst_stack:get_name(), "error")
local leftovers = sink(dst_stack)
if leftovers and not leftovers:is_empty() then
me.log("autocrafter overflow, backpressuring", "error")
-- If any don't fit, back pressure on the crafter, we don't
-- mean to do this, and want to chunk the crafting items smaller
dat.rinv:add_item("dst", leftovers)
end
end
if not extra:is_empty() then
dst_stack = dat.rinv:remove_item("dst", extra)
if dst_stack:get_count() ~= extra:get_count() then
me.log("wow, missing items that should have been crafted "..stack:get_name(), "error")
me.log("saw "..dst_stack:get_count().." items instead of "..extra:get_count().." items", "error")
end
if not dst_stack:is_empty() then
local leftovers = me.insert_item(dst_stack, net, inv, "main")
net:set_storage_space(true)
if leftovers and not leftovers:is_empty() then
me.log("autocrafter overflow, backpressuring", "error")
-- If any don't fit, back pressure on the crafter, we don't
-- mean to do this, and want to chunk the crafting items smaller
dat.rinv:add_item("dst", leftovers)
end
end
end
if second_output then
local second = dat.rinv:remove_item("dst", second_output)
if second and not second:is_empty() then
local leftovers = sink(second)
if leftovers and not leftovers:is_empty() then
me.log("autocrafter overflow, backpressuring", "error")
-- If any don't fit, back pressure on the crafter, we don't
-- mean to do this, and want to chunk the crafting items smaller
dat.rinv:add_item("dst", leftovers)
end
end
end
me.log("ACTION: done post craft for "..stack:get_name(), "error")
end
local net,cpos = me.get_connected_network(dat.ipos)
me.later(net, cpos, action, next_time + action_time_step)
end
me.log("ACTION: main done for "..stack:get_name(), "error")
end
local net,cpos = me.get_connected_network(dat.ipos)
-- queue main action for later
me.log("LATER: main action for "..stack:get_name().." in "..next_time.." seconds", "error")
me.later(net, cpos, main_action, next_time)
-- The step time is the prep time and the main_action_time
local step_time = next_time - time + main_action_time
return true, step_time
end
-- time is absolute, starting from 0 from the front of a craft or
-- non-zero if a previous craft was running.
function me.later(net, cpos, action, time)
if not net.pending then
net.pending = {}
net.pending.time = {}
end
local i = (net.pending.max_index or 0) + 1
net.pending.max_index = i
net.pending[i] = action
net.pending.time[i] = time
if not net.pending.index then
net.pending.index = 1
end
if i == 1 then
me.log("TIMER: starting timer to fire at "..time.." seconds", "error")
me.start_crafting(cpos, time+0.1)
else
-- me.log("TIMER: did not start timer for later, index "..i.." at time "..time, "error")
-- bubble sort the entry back to the right spot
while i > 1 do
-- me.log("TIME ds: "..i.." "..net.pending.time[i].." "..net.pending.time[i-1], "error")
if net.pending.time[i] < net.pending.time[i-1] then
-- if out of order, swap. This works as previously the list was sorted
local t = net.pending.time[i-1]
net.pending.time[i-1] = net.pending.time[i]
net.pending.time[i] = t
t = net.pending[i-1]
net.pending[i-1] = net.pending[i]
net.pending[i] = t
if i == 2 then
me.start_crafting(cpos, net.pending.time[1]+0.1)
end
else
break
end
i = i - 1
end
end
end
function me.autocraft(autocrafterCache, cpos, net, linv, inv, count)
local ostack = linv:get_stack("output", 1)
local name = ostack:get_name()
me.log("crafting "..name.." "..tostring(count), "error")
local stack = ItemStack(name)
local craft_count = ostack:get_count()
me.log("auto: craft_count "..craft_count.." count "..count, "error")
-- we craft a minimum of count, to the multiple of the crafting count
count = math.ceil(count/craft_count)
me.log("auto: count is now "..count, "error")
stack:set_count(count*craft_count)
me.log("auto: stack size is now "..stack:get_count(), "error")
me.log("auto: and build count is "..(count*craft_count), "error")
-- me.log("autocrafters: "..minetest.serialize(net.autocrafters), "error")
if not net.process then
-- rewalk the interfaces on the network to rebuild the machines.
net:reload_network()
end
if net.autocrafters[name] or net.process[name] then
me.log("using pipeworks autocrafter", "error")
local sink = function(stack)
local leftovers = me.insert_item(stack, net, inv, "main")
net:set_storage_space(true)
return leftovers
end
local start_time = me.autocraft_next_start(net)
local built, step_time = build(net, cpos, inv, name, count*craft_count, stack, sink, start_time)
if built then
me.log("crafting "..stack:get_count().." "..stack:get_name().." in "..step_time.." seconds", "error")
else
me.log("can't craft "..stack:get_count().." "..stack:get_name(), "error")
end
return
end
me.log("using microexpansion autocrafter", "error")
local consume = {}
for i = 1, 9 do
local inp = linv:get_stack("recipe", i)
if inp and inp:get_name() ~= "" then
consume[inp:get_name()] = (consume[inp:get_name()] or 0) + count*inp:get_count()
end
end
local replace = true
for name, count in pairs(consume) do
local stack = ItemStack(name)
if count >= math.pow(2,16) then
replace = false
break
end
-- Don't consume the last item by autocrafting
stack:set_count(count+1)
replace = replace and inv:contains_item("main", stack)
end
if replace then
for name, count in pairs(consume) do
local stack = ItemStack(name)
stack:set_count(count)
me.log("REMOVE: "..count.." "..stack:get_name(), "error")
if not inv:contains_item("main", stack) then
fixme1()
end
local ret = me.remove_item(net, inv, "main", stack)
if ret:get_count() ~= stack:get_count() then
me.log("AUTO: found "..(ret:get_count()).." "..(stack:get_name()).." but wanted "..stack:get_count(), "error")
-- fixme2()
end
end
local leftovers = me.insert_item(stack, net, inv, "main")
if leftovers:get_count() > 0 then
-- Ick, no room, just drop on the floor. Maybe player inventory?
minetest.add_item(cpos, leftovers)
end
net:set_storage_space(true)
-- deal with replacements
local hash = minetest.hash_node_position(cpos)
local craft = autocrafterCache[hash] or me.get_craft(cpos, linv, hash)
for i = 1, 9 do
if (craft.decremented_input.items[i]:get_count() ~= linv:get_stack("recipe", i):get_count()
or craft.decremented_input.items[i]:get_name() ~= linv:get_stack("recipe", i):get_name())
and not craft.decremented_input.items[i]:is_empty() then
local leftovers = me.insert_item(craft.decremented_input.items[i], net, inv, "main")
net:set_storage_space(true)
if leftovers:get_count() > 0 then
-- Ick, no room, just drop on the floor. Maybe player inventory?
minetest.add_item(cpos, leftovers)
end
end
if replace then
linv:set_stack("output", 1, craft.output.item)
else
linv:set_list("output", {})
end
end
end
end

@ -45,12 +45,12 @@ me.register_node("ctrl", {
groups = { cracky = 1, me_connect = 1, }, groups = { cracky = 1, me_connect = 1, },
connect_sides = "nobottom", connect_sides = "nobottom",
me_update = function(pos,_,ev) me_update = function(pos,_,ev)
local cnet = me.get_network(pos) local net = me.get_network(pos)
if cnet == nil then if net == nil then
microexpansion.log("no network for ctrl at pos "..minetest.pos_to_string(pos),"error") me.log("no network for ctrl at pos "..minetest.pos_to_string(pos),"error")
return return
end end
cnet:update() net:update()
end, end,
on_construct = function(pos) on_construct = function(pos)
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
@ -86,6 +86,38 @@ me.register_node("ctrl", {
machine = { machine = {
type = "controller", type = "controller",
}, },
on_timer = function(pos, elapsed)
::top::
me.log("TIMER: starting service", "error")
local net = me.get_network(pos)
if not net then return false end
if not net.pending then return false end
local i = net.pending.index
local action = net.pending[i]
-- me.log("TIMER: doing service", "error")
if not action then
net.pending = nil
return false
end
local prev_time = net.pending.time[i]
action(net)
net.pending[i] = nil
net.pending.time[i] = nil
net.pending.index = i + 1
if net.pending[i+1] then
local next_action_time = net.pending.time[i+1]
local step_time = next_action_time - prev_time
me.log("TIMER: starting next timer for "..step_time.." seconds", "error")
if step_time == 0 then
goto top
end
me.start_crafting(pos, step_time)
else
-- me.log("TIMER: ending service", "error")
net.pending = nil
end
return false
end,
}) })
-- [register node] Cable -- [register node] Cable

@ -1,7 +1,7 @@
local me = microexpansion local me = microexpansion
me.networks = {} me.networks = {}
local networks = me.networks local networks = me.networks
local path = microexpansion.get_module_path("network") local path = me.get_module_path("network")
--deprecated: use ItemStack(x) instead --deprecated: use ItemStack(x) instead
--[[ --[[
@ -30,36 +30,335 @@ local function split_stack_values(stack)
end end
--]] --]]
function me.insert_item(stack, inv, listname) local annotate_large_stack = function(stack, count)
if me.settings.huge_stacks == false then local description = minetest.registered_items[stack:get_name()]
return inv:add_item(listname, stack) if description then
end -- steel is an alias and won't be found in here, skip it
local to_insert = type(stack) == "userdata" and stack or ItemStack(stack) description = description.description
local found = false --stack:set_count(1)
for i = 0, inv:get_size(listname) do --This screw up everything, autocrafting, item removal and more
local inside = inv:get_stack(listname, i) --stack:get_meta():set_string("description", description.." "..count)
if inside:get_name() == to_insert:get_name() and inside:get_wear() == to_insert:get_wear() then stack:get_meta():set_string("description", "")
if inside:get_meta():equals(to_insert:get_meta()) then
local total_count = inside:get_count() + to_insert:get_count()
-- bigger item count is not possible, we only have unsigned 16 bit
if total_count <= math.pow(2,16) then
if not inside:set_count(total_count) then
microexpansion.log("adding items to stack in microexpansion network failed","error")
print("stack is now " .. inside:to_string())
end
inv:set_stack(listname, i, inside)
found = true
break;
end
end
end
end
if not found then
return inv:add_item(listname, stack)
end end
end end
function me.insert_item(stack, net, inv, listname, bias)
if stack == nil then
foobar()
return ItemStack(), 0
end
stack = type(stack) == "userdata" and stack or ItemStack(stack)
if stack:get_name() == "" then
-- foobar() -- TODO: can trip, ignore for now
return ItemStack(), 0
end
local slot
assert(net, not stack:is_empty())
local found = false
if not net.byname then
net.byname = {}
end
if not net.byname[listname] then
net.byname[listname] = {}
end
if not net.byname[listname][stack:get_name()] then
net.byname[listname][stack:get_name()] = {}
end
--if not net.byname[listname][stack:get_name()][stack:get_wear()] then
-- net.byname[listname][stack:get_name()][stack:get_wear()] = {}
--end
local meta = stack:get_meta()
-- slot = net.byname[listname][stack:get_name()][stack:get_wear()][meta]
-- assert(net, minetest.serialize(meta))
::was_empty::
slot = net.byname[listname][stack:get_name()][stack:get_wear()]
-- me.log("checking "..listname.." slot "..tostring(slot).." has "..stack:get_name().." in it", "error")
if not slot then
local ret = inv:add_item(listname, stack) -- TODO: fix to use set_stack, careful capacity
slot = inv:get_size(listname)
-- me.log(listname.." slot "..tostring(slot).." should now have "..stack:get_name().." in it", "error")
-- me.log(listname.." slot "..tostring(slot).." has "..inv:get_stack(listname, slot):get_name().." in it", "error")
if inv:get_stack(listname, slot):get_name() ~= stack:get_name() then
return ret, 0
--net:sync_main(inv)
-- TODO: infinite loop on full?
--goto was_empty
end
-- net.byname[listname][stack:get_name()][stack:get_wear()][meta] = slot
net.byname[listname][stack:get_name()][stack:get_wear()] = slot
-- me.log("byname is "..minetest.serialize(net.byname), "error")
if bias then
if not net.bias then
net.bias = {}
end
if not net.bias[listname] then
net.bias[listname] = {}
end
if not net.bias[listname][stack:get_name()] then
net.bias[listname][stack:get_name()] = 0
end
net.bias[listname][stack:get_name()] = net.bias[listname][stack:get_name()] + bias
me.log("LARGE: init insert_item bias "..bias.." for "..stack:get_name(), "error")
local mstack = inv:get_stack(listname, slot)
annotate_large_stack(mstack, mstack:get_count()+net.bias[listname][stack:get_name()])
inv:set_stack(listname, slot, mstack)
end
return ret, slot
end
local mstack = inv:get_stack(listname, slot)
-- me.log(listname.." slot "..tostring(slot).." has "..mstack:get_name().." in it", "error")
if mstack:is_empty() then
-- me.log("adding items to stack in microexpansion network was going to fail, fixing", "error")
net.byname[listname][stack:get_name()][stack:get_wear()] = nil
goto was_empty
end
-- me.log("init insert_item "..stack:get_name(), "error")
-- me.log("init insert_item "..mstack:get_name(), "error")
if mstack:get_name() ~= stack:get_name() then
-- me.log("adding items to stack in microexpansion network was going to fail, wrong item, fixing", "error")
net.byname[listname][stack:get_name()][stack:get_wear()] = nil
goto was_empty
end
local mbias = (net.bias and net.bias[listname] and net.bias[listname][stack:get_name()]) or 0
local total_count = mstack:get_count() + mbias + stack:get_count() + (bias or 0)
-- me.log("insert_item "..mstack:get_name().." "..tostring(total_count), "error")
-- bigger item count is not possible, we only have unsigned 16 bit
if total_count > math.pow(2,15) then
-- We handle overflows by storing the excess stores into a bias number for the slot.
-- This give us lua max int (2^48 or so) for the actual count.
if not net.bias then
net.bias = {}
end
if not net.bias[listname] then
net.bias[listname] = {}
end
if not net.bias[listname][stack:get_name()] then
net.bias[listname][stack:get_name()] = 0
end
net.bias[listname][stack:get_name()] = total_count - math.pow(2,15)
me.log("LARGE: overflow bias "..stack:get_count().." for "..stack:get_name(), "error")
total_count = math.pow(2,15)
end
local addition_real_loaned = total_count - mstack:get_count()
mstack:set_count(total_count)
inv:set_stack(listname, slot, mstack)
-- if there is one or more loans for the item, add stack:get_count()
-- + (bias or 0) to them and update those inventories, if they have room
local remaining = stack:get_count() + (bias or 0)
local loan_slot = net:find_loan(inv, stack)
if loan_slot then
me.loan.bump_loan(net, inv, loan_slot, remaining, addition_real_loaned)
end
return ItemStack(), slot
end
function me.add_capacity(ipos, count)
local int_meta = minetest.get_meta(ipos)
local prev = int_meta:get_int("capacity") or 0
int_meta:set_int("capacity", prev + count)
end
function me.remove_item(net, inv, listname, stack)
if not stack then return ItemStack() end
if stack == "" then return ItemStack() end
if type(stack) == string then
me.log("REMOVE: converting "..stack, "error")
stack = ItemStack(stack)
end
-- stack = ItemStack(stack):set_count(stack:get_count())
-- this can dump...
me.log("type is "..type(stack), "error")
-- me.log("serial is "..minetest.serialize(stack), "error")
if stack:get_count() == 0 then return ItemStack() end
me.log("me.remove_item "..listname, "error")
me.log("me.remove_item "..listname.." "..stack:get_count().." "..stack:get_name(), "error")
-- me.log("me.remove_item "..listname.." "..stack:get_wear(), "error")
if listname ~= "main" then
foobar()
return inv:remove_item(listname, stack)
end
if not net.byname[listname] or not net.byname[listname][stack:get_name()] then
return ItemStack()
end
local slot = net.byname[listname][stack:get_name()][stack:get_wear()]
if not slot then
me.log("wanted to remove "..stack:get_name().." from "..listname..", but didn't find anything", "error")
return ItemStack()
end
local mstack = inv:get_stack(listname, slot)
me.log("init remove item "..tostring(stack:get_count()).." "..stack:get_name(), "error")
if stack:get_name() ~= mstack:get_name()
or stack:get_wear() ~= mstack:get_wear()
or not stack:get_meta():equals(mstack:get_meta()) then
me.log("wanted to remove "..stack:get_name().." from "..listname..", but had "..mstack:get_name().." in slot "..slot, "error")
-- foobar()
net.byname[listname][stack:get_name()][stack:get_wear()] = nil
return ItemStack()
end
local mbias = 0
if net.bias and net.bias[listname] then
mbias = net.bias[listname][stack:get_name()] or 0
end
local on_loan = net.counts[stack:get_name()]
local remaining = mstack:get_count() + mbias - stack:get_count()
me.log("init me.remove_item has "..(on_loan or 0).." on loan and "..remaining.." remaining", "error")
if on_loan and remaining < on_loan then
me.log("init me.remove_item has "..on_loan.." on loan and "..remaining.." remaining", "error")
local loan_slot = net:find_loan(inv, stack)
-- find_loan can update main, refetch
mstack = inv:get_stack(listname, slot)
if not loan_slot then
-- someone updated the real inventories, there are no more of these
return ItemStack()
end
local lstack = me.loan.get_stack(net, inv, loan_slot)
if lstack:is_empty() then
return ItemStack()
end
local ref = me.network.get_ref(lstack)
local real_count = nil
if ref.drawer then
local c = drawers.drawer_get_content(ref.pos, ref.slot)
c.count = math.max(c.count-1,0) -- Poor man's locking
local count = math.min(stack:get_count(), c.count)
me.log("init removing "..count.." items from drawer", "error")
stack:set_count(count)
local take = stack
drawers.drawer_take_large_item(ref.pos, take)
c.count = c.count - count
real_count = c.count
if real_count > math.pow(2,15) then
real_count = math.pow(2,15)
end
-- me.log("CAPACITY: drawer "..-count, "error")
-- me.add_capacity(ref.ipos, -count)
else
local rinv = minetest.get_meta(ref.pos):get_inventory()
local real_stack = rinv:get_stack(ref.invname, ref.slot)
local count = math.min(stack:get_count(), real_stack:get_count())
me.log("init removing "..count.." items from chest", "error")
stack:set_count(count)
real_count = real_stack:get_count()-count
real_stack:set_count(real_count)
rinv:set_stack(ref.invname, ref.slot, real_stack)
-- me.log("CAPACITY: chest "..-count, "error")
-- me.add_capacity(ref.ipos, -count)
end
me.log("init now down to "..real_count.." items", "error")
local lcount = lstack:get_count() -- it was mc 1 excess 0 lcount 1 rc 1
local excess = real_count - lcount -- it was mc 1 excess -1 lcount 2 rc 1
me.log("it was "..mstack:get_count().." "..excess.." "..lcount.." "..real_count, "error") -- fails with: 1 -1
if real_count == 0 then
me.log("init me.remove_item before remove_loan chest", "error")
net:remove_loan(ref.pos, inv, lstack, loan_slot, ref)
elseif excess ~= 0 then
-- update loan and main with new excess
lstack:set_count(lcount + excess)
me.log("LOAN: me.remove_item loan to "..lstack:get_count(), "error")
me.loan.set_stack(net, inv, loan_slot, lstack)
mstack:set_count(mstack:get_count() + excess)
inv:set_stack(listname, slot, mstack)
if mstack:is_empty() then
me.maybemove(net, inv, listname, slot, stack)
end
-- TODO: Think this is wrong, only adjust by excess no stack:get_count()
--net.counts[lstack:get_name()] = net.counts[lstack:get_name()] - stack:get_count()
--me.add_capacity(ref.ipos, -stack:get_count())
me.log("CAPACITY: is "..excess, "error")
net.counts[lstack:get_name()] = net.counts[lstack:get_name()] + excess
me.add_capacity(ref.ipos, excess)
end
return stack
end
if remaining > 0 then
if remaining > math.pow(2,15) then
if not net.bias then
net.bias = {}
end
if not net.bias[listname] then
net.bias[listname] = {}
end
me.log("LARGE: total count "..remaining.." for "..stack:get_name(), "error")
annotate_large_stack(mstack, remaining)
net.bias[listname][stack:get_name()] = remaining - math.pow(2,15)
remaining = math.pow(2,15)
end
mstack:set_count(remaining)
inv:set_stack(listname, slot, mstack)
return stack
end
if remaining < 0 then
me.log("init wow, missing "..tostring(-remaining).." "..stack:get_name().." during removal, taking less", "error")
-- take fewer
stack:set_count(mstack:get_count() + mbias)
remaining = 0
end
mstack:set_count(remaining)
inv:set_stack(listname, slot, mstack)
me.log("init me.remove_item bottom before", "error")
me.maybemove(net, inv, listname, slot, stack)
me.log("init me.remove_item bottom after", "error")
return stack
end
function me.maybemove(net, inv, listname, slot, stack)
if stack == nil then
-- me.log("init nil stack", "error")
return
end
if net == nil then
-- me.log("init nil net", "error")
return
end
if inv == nil then
-- me.log("init nil inv", "error")
return
end
-- me.log("init maybemove "..stack:get_name(), "error")
if not net.byname or not net.byname[listname] or not net.byname[listname][stack:get_name()] then
me.log("byname on "..listname.." is already nil", "error")
else
-- slot has been completely removed, deindex it
net.byname[listname][stack:get_name()][stack:get_wear()] = nil
end
if net.bias and net.bias[listname] then
net.bias[listname][stack:get_name()] = nil
end
-- Move the last or the second to the last if the last is empty,
-- back to the hole left by the removed items
local main_size = inv:get_size(listname)
-- me.log("CALC main_size"..main_size.." slot "..slot, "error")
if slot < main_size and main_size > 1 then
local orig_slot = main_size
local mstack = inv:get_stack(listname, orig_slot)
-- me.log("CALC count "..mstack:get_count().." orig_slot-1 "..orig_slot-1, "error")
if mstack:is_empty() and slot < orig_slot-1 and orig_slot-1 > 1 then
orig_slot = orig_slot-1
mstack = inv:get_stack(listname, orig_slot)
end
if not mstack:is_empty() then
inv:set_stack(listname, orig_slot, ItemStack())
inv:set_stack(listname, slot, mstack)
-- [meta]
-- me.log("CALC not empty, old "..stack:get_name().." new "..mstack:get_name(), "error")
if net.byname and net.byname[listname] and net.byname[listname][stack:get_name()] then
net.byname[listname][stack:get_name()][stack:get_wear()] = nil
end
net.byname[listname][mstack:get_name()][mstack:get_wear()] = slot
else
inv:set_stack(listname, slot, mstack)
-- me.log("CALC empty, old "..stack:get_name(), "error")
if net.byname and net.byname[listname] and net.byname[listname][stack:get_name()] then
net.byname[listname][stack:get_name()][stack:get_wear()] = nil
end
end
end
inv:set_size(listname, main_size-1)
end
dofile(path.."/loan.lua") -- Loan Management
dofile(path.."/network.lua") -- Network Management dofile(path.."/network.lua") -- Network Management
dofile(path.."/autocraft.lua") -- Autocrafting
-- generate iterator to find all connected nodes -- generate iterator to find all connected nodes
function me.connected_nodes(start_pos,include_ctrl) function me.connected_nodes(start_pos,include_ctrl)
@ -110,18 +409,18 @@ end
function me.get_connected_network(start_pos) function me.get_connected_network(start_pos)
for npos,nn in me.connected_nodes(start_pos,true) do for npos,nn in me.connected_nodes(start_pos,true) do
if nn == "microexpansion:ctrl" then if nn == "microexpansion:ctrl" then
local network = me.get_network(npos) local net = me.get_network(npos)
if network then if net then
return network,npos return net,npos
end end
end end
end end
end end
function me.update_connected_machines(start_pos,event,include_start) function me.update_connected_machines(start_pos,event,include_start)
microexpansion.log("updating connected machines","action") me.log("updating connected machines", "action")
local ev = event or {type = "n/a"} local ev = event or {type = "n/a"}
local sn = microexpansion.get_node(start_pos) local sn = me.get_node(start_pos)
local sd = minetest.registered_nodes[sn.name] local sd = minetest.registered_nodes[sn.name]
local sm = sd.machine or {} local sm = sd.machine or {}
ev.origin = { ev.origin = {
@ -181,8 +480,13 @@ me.load()
-- save networks -- save networks
function me.save() function me.save()
local data = {} local data = {}
for _,v in pairs(me.networks) do for _,net in pairs(me.networks) do
table.insert(data,v:serialize()) -- We rebuild this data by walking as they contain non-serializable content.
-- All other data is serialized and survives.
net.autocrafters = nil
net.autocrafters_by_pos = nil
net.process = nil
table.insert(data,net:serialize())
end end
local f = io.open(me.worldpath.."/microexpansion_networks", "w") local f = io.open(me.worldpath.."/microexpansion_networks", "w")
f:write(minetest.serialize(data)) f:write(minetest.serialize(data))

108
modules/network/loan.lua Normal file

@ -0,0 +1,108 @@
local me = microexpansion
local loan = {
}
me.loan = loan
function me.loan.get_stack(net, inv, loan_slot)
local lstack = inv:get_stack("loan", loan_slot)
return lstack
end
function me.loan.set_stack(net, inv, loan_slot, lstack)
inv:set_stack("loan", loan_slot, lstack)
end
function me.loan.get_size(net, inv)
return inv:get_size("loan")
end
function me.loan.set_size(net, inv, size)
return inv:set_size("loan", size)
end
function me.loan.bump_loan(net, inv, loan_slot, remaining, addition_real_loaned)
local lstack = me.loan.get_stack(net, inv, loan_slot)
local ref
local real_count
if lstack:is_empty() then
-- TODO: should never happen, verify and remove
return
end
ref = me.network.get_ref(lstack)
real_count = nil
local prev_lstack_count = lstack:get_count()
if ref.drawer then
local stack = ItemStack(lstack)
-- the drawer api only allow up to 2^16-1, ask them for a better api, 2^48 max
local count = math.pow(2,16)-1
-- ensure that lstack:get_count() + spare below is at most 2^16-1
count = math.min(count, math.pow(2,16)-1 - lstack:get_count())
-- ensure at most 2^16-1 as the stack api doesn't allow more
count = math.min(count, remaining)
-- if the loan size is already maximal, then we can't update the loan any
if count > 0 then
stack:set_count(count)
-- bump up the actual inventory, spare is how many fit
local excess = drawers.drawer_insert_object(ref.pos, stack, ref.slot)
local spare = count - excess:get_count()
-- bump loan by spare
lstack:set_count(lstack:get_count() + spare)
me.log("LOAN: bump_loan to "..lstack:get_count(), "error")
me.loan.set_stack(net, inv, loan_slot, lstack)
net.counts[lstack:get_name()] = net.counts[lstack:get_name()] + spare
me.log("COUNT: loan now to "..net.counts[lstack:get_name()].." "..lstack:get_name()..", "..spare.." more", "error")
me.log("LOAN: adril "..addition_real_loaned.." loan "..prev_lstack_count.." and adjustment(spare) "..spare, "error")
addition_real_loaned = math.min(addition_real_loaned, spare)
me.add_capacity(ref.ipos, addition_real_loaned)
-- reduce remaining by spare
remaining = remaining - spare
end
else
local rinv = minetest.get_meta(ref.pos):get_inventory()
local real_stack = rinv:get_stack(ref.invname, ref.slot)
local max = real_stack:get_stack_max()
if real_stack:get_count() < max then
local spare = max - real_stack:get_count()
spare = math.min(spare, remaining)
-- me.log("bumping "..lstack:get_name().." by "..spare, "error")
-- bump up the actual inventory by spare
real_stack:set_count(real_stack:get_count() + spare)
rinv:set_stack(ref.invname, ref.slot, real_stack)
-- bump loan by spare
lstack:set_count(lstack:get_count() + spare)
-- me.log("bumping "..lstack:get_name().." to "..lstack:get_count(), "error")
me.log("LOAN: bump_loan to "..lstack:get_count(), "error")
me.loan.set_stack(net, inv, loan_slot, lstack)
-- me.log("COUNTS: "..minetest.serialize(net.counts), "error")
net.counts[lstack:get_name()] = net.counts[lstack:get_name()] + spare
me.log("COUNT: bump_loan loan now to "..net.counts[lstack:get_name()].." "..lstack:get_name()..", "..spare.." more", "error")
me.log("LOAN: adril "..addition_real_loaned.." loan "..prev_lstack_count.." and adjustment(spare) "..spare, "error")
addition_real_loaned = math.min(addition_real_loaned, spare)
me.add_capacity(ref.ipos, addition_real_loaned)
-- reduce remaining by spare
remaining = remaining - spare
end
end
-- This code is misguided. We've already added them as real, we can
-- only add them to a loan if those items are added to that
-- inventory being loaded. This is only possible if there is room.
if false and remaining > 0 then
if not net.bias then
net.bias = {}
end
if not net.bias["loan"] then
net.bias["loan"] = {}
end
if not net.bias["loan"][loan_slot] then
net.bias["loan"][loan_slot] = 0
end
net.bias["loan"][loan_slot] = net.bias["loan"][loan_slot] + remaining
me.log("LARGE: bump_loan bias is "..net.bias["loan"][loan_slot].." "..lstack:get_name()..", "..remaining.." more", "error")
end
end

@ -12,10 +12,12 @@ me.network = network
--- construct a new network --- construct a new network
-- @function [parent=#network] new -- @function [parent=#network] new
-- @param #table o the object to become a network or nil -- @param #table or the object to become a network or nil
-- @return #table the new network object -- @return #table the new network object
function network.new(o) function network.new(o)
return setmetatable(o or {}, {__index = network}) local ret = setmetatable(o or {}, {__index = network})
ret.counts = {}
return ret
end end
--- check if a node can be connected --- check if a node can be connected
@ -38,6 +40,24 @@ function network.can_connect(np)
return minetest.get_item_group(nn, "me_connect") > 0 return minetest.get_item_group(nn, "me_connect") > 0
end end
function network.get_ref(lstack)
local tref = lstack:get_meta():get_string("me_store_reference")
if tref == "" then
-- me.log("REF: we tried, "..lstack:get_name(), "error")
-- me.log("REF: fallback", "error")
local location = lstack:get_location()
if location.type == "undefined" then
me.log("REF: we tried", "error")
end
local foo = minetest.get_inventory(location)
local ref = nil
-- TODO: Do we need this?
return nil
end
local ref = minetest.deserialize(tref)
return ref
end
--- get all adjacent connected nodes --- get all adjacent connected nodes
-- @function [parent=#network] adjacent_connected_nodes -- @function [parent=#network] adjacent_connected_nodes
-- @param #table pos the position of the base node -- @param #table pos the position of the base node
@ -104,7 +124,7 @@ end
function network:remove_power_capacity(power) function network:remove_power_capacity(power)
self.power_storage = self.power_storage - power self.power_storage = self.power_storage - power
if self.power_storage < 0 then if self.power_storage < 0 then
microexpansion.log("power storage of network "..self.." dropped below zero","warning") me.log("power storage of network "..self.." dropped below zero","warning")
end end
end end
@ -129,6 +149,12 @@ local function get_drive_capacity(pos)
return cap return cap
end end
local function get_interface_capacity(pos)
local cap = 0
local meta = minetest.get_meta(pos)
return meta:get_int("capacity") or 0
end
--- get the item capacity of a network --- get the item capacity of a network
-- @function [parent=#network] get_item_capacity -- @function [parent=#network] get_item_capacity
-- @return #number the total number of items that can be stored in the network -- @return #number the total number of items that can be stored in the network
@ -138,44 +164,60 @@ function network:get_item_capacity()
if me.get_node(npos).name == "microexpansion:drive" then if me.get_node(npos).name == "microexpansion:drive" then
cap = cap + get_drive_capacity(npos) cap = cap + get_drive_capacity(npos)
end end
if me.get_node(npos).name == "microexpansion:interface" then
cap = cap + get_interface_capacity(npos)
end
end end
self.capacity_cache = cap self.capacity_cache = cap
me.log("total capacity is "..cap, "error")
return cap return cap
end end
local function remove_slots(inv,ln,target,csize) function network:remove_slots(inv, listname, target, csize)
for i = target, csize do -- me.log("removing slots on "..listname..", target is "..target.." and csize is "..csize, "error")
local s = inv:get_stack(ln,i) for i = target+1, csize do
if not s:is_empty() then local stack = inv:get_stack(listname, i)
inv:set_stack(ln, i, "") -- me.log("network remove_slot "..listname..":"..i.." "..stack:get_name(), "error")
me.insert_item(s, inv, ln) if not stack:is_empty() then
-- me.log("BROKEN: after remove_slot, still "..stack:get_count().." "..stack:get_name().." left", "error")
if true then return end
foobar() -- This can't happen, we can only remove empty slots, and only 1 at that, at the end
--inv:set_stack(listname, i, "")
--me.insert_item(stack, self, inv, listname)
me.remove_item(self, inv, listname, stack)
if inv:get_stack("main", i):get_count() ~= 0 then
-- me.log("BROKEN: after remove_slot, still "..inv:get_stack("main", i):get_count().." left", "error")
end
end end
end end
--perhaps allow list removal --perhaps allow list removal
if target < 0 then if target < 0 then
-- TODO: audit, this sould be made impossible if it is, or removed.
target = 1 target = 1
end end
inv:set_size(ln, target) inv:set_size(listname, target)
end end
function network:set_storage_space(count,listname) function network:set_storage_space(count, listname)
local c = count or 1 local c = count or 1
local ln = listname or "main" listname = listname or "main"
local inv = self:get_inventory() local inv = self:get_inventory()
local csize = inv:get_size(ln) local csize = inv:get_size(listname)
local space = 0 local space = 0
local inside = 0 local inside = 0
local contents = inv:get_list(ln) or {} local contents = inv:get_list(listname) or {}
for _,s in pairs(contents) do for i,stack in pairs(contents) do
if s:is_empty() then if stack:is_empty() then
space = space + 1 space = space + 1
-- me.log("STORAGE: found space at "..i.." now "..space, "error")
else else
inside = inside + s:get_count() inside = inside + stack:get_count()
end end
end end
local cap = self:get_item_capacity() local cap = self:get_item_capacity()
--the capacity is allocated outside of the condition because it updates the cached capacity --the capacity is allocated outside of the condition because it updates the cached capacity
if count == true then if count == true then
me.log("STORAGE: current: "..inside.." capacity: "..cap, "error")
if inside < cap then if inside < cap then
c = 1 c = 1
else else
@ -183,12 +225,13 @@ function network:set_storage_space(count,listname)
end end
end end
local needed = c - space local needed = c - space
-- me.log("STORAGE: needed: "..needed, "error")
if needed > 0 then if needed > 0 then
needed = needed + csize needed = needed + csize
inv:set_size(ln, needed) inv:set_size(listname, needed)
elseif needed < 0 then elseif needed < 0 then
needed = needed + csize needed = needed + csize
remove_slots(inv,ln,needed,csize) self:remove_slots(inv, listname, needed, csize)
end end
end end
@ -202,6 +245,39 @@ function network:get_inventory_name()
return "microexpansion_storage_"..cp.x.."_"..cp.y.."_"..cp.z return "microexpansion_storage_"..cp.x.."_"..cp.y.."_"..cp.z
end end
function network:find_loan(inv, stack)
if not self.byname["loan"] then
self.byname["loan"] = {}
end
if not self.byname["loan"][stack:get_name()] then
self.byname["loan"][stack:get_name()] = {}
end
local loan_slot = self.byname["loan"][stack:get_name()][stack:get_wear()]
if loan_slot then
-- me.log("init me.remove_item before update_loan", "error")
self:update_loan(inv, loan_slot)
-- me.log("init me.remove_item after update_loan", "error")
end
loan_slot = self.byname["loan"][stack:get_name()][stack:get_wear()]
if loan_slot then
local lstack = me.loan.get_stack(self, inv, loan_slot)
if lstack:get_name() == stack:get_name() and lstack:get_wear() == stack:get_wear() then
return loan_slot
end
self.byname["loan"][stack:get_name()][stack:get_wear()] = nil
end
-- me.log("init network find_loan searching all loans", "error")
for loan_slot = 1, me.loan.get_size(self, inv) do
local lstack = me.loan.get_stack(self, inv, loan_slot)
if lstack:get_name() == stack:get_name() and lstack:get_wear() == stack:get_wear() then
-- TODO: Same meta_data...
self.byname["loan"][stack:get_name()][stack:get_wear()] = loan_slot
return loan_slot
end
end
return nil
end
local function create_inventory(net) local function create_inventory(net)
local invname = net:get_inventory_name() local invname = net:get_inventory_name()
net.inv = minetest.create_detached_inventory(invname, { net.inv = minetest.create_detached_inventory(invname, {
@ -235,15 +311,125 @@ local function create_inventory(net)
end, end,
on_put = function(inv, listname, _, stack) on_put = function(inv, listname, _, stack)
inv:remove_item(listname, stack) inv:remove_item(listname, stack)
me.insert_item(stack, inv, listname) me.insert_item(stack, net, inv, listname)
net:set_storage_space(true) net:set_storage_space(true)
end, end,
allow_take = function(_, _, _, stack) allow_take = function(inv, listname, slot, stack, player)
return math.min(stack:get_count(),stack:get_stack_max()) -- This is not remove_item, we only remove the loan. This real
-- item will be removed.
me.log("REMOVE: network allow taking of "..stack:get_name().." from "..listname, "error")
--me.log("COUNTS: allow_take "..minetest.serialize(net.counts), "error")
--print(dump2(inv:get_list("loan")))
-- me.log("LOANS: allow_take "..minetest.serialize(inv:get_list("loan")), "error")
-- net:dump_loans(inv)
local count = math.min(stack:get_count(),stack:get_stack_max())
local orig_count = count
if listname ~= "main" then
return count
end
local on_loan = net.counts[stack:get_name()]
local main_slot = nil
local main_count = 0
if on_loan then
local found = false
local mstack = inv:get_stack("main", slot)
main_slot = slot
local mbias = 0
if net.bias and net.bias["main"] and net.bias["main"][stack:get_name()] then
mbias = net.bias["main"][stack:get_name()]
end
main_count = mstack:get_count() + mbias
if main_count - on_loan >= 1 then
return math.min(mstack:get_count() + mbias - on_loan, count)
end
local update = false
--me.log("COUNTS: allow_take2 "..minetest.serialize(net.counts), "error")
--print(dump2(inv:get_list("loan")))
-- net:dump_loans(inv)
local loan_slot = net:find_loan(inv, stack)
if loan_slot then
-- me.log("found loan", "error")
local lstack = me.loan.get_stack(net, inv, loan_slot)
local loan_count = lstack:get_count()
local ref = network.get_ref(lstack)
local rinv = minetest.get_meta(ref.pos):get_inventory()
local real_stack = nil
if ref.drawer then
local c = drawers.drawer_get_content(ref.pos, ref.slot)
real_stack = ItemStack(c.name)
real_stack:set_count(math.max(c.count-1,0))
else
real_stack = rinv:get_stack(ref.invname, ref.slot)
end
local same_name = real_stack:get_name() == stack:get_name()
local same_wear = real_stack:get_wear() == stack:get_wear()
-- TODO: Same meta_data...
if same_name and same_wear and real_stack:get_count() >= 1 then
found = true
count = math.min(real_stack:get_count(), count)
if real_stack:get_count() ~= loan_count then
update = true
-- The partial update won't be able to update the count of
-- this slot as we are taking the whole thing, updating now
-- before the contents are removed, so the loan remains
if count == main_count then
local mstack = inv:get_stack("main", main_slot)
-- adding 1 is enough to keep it from disappearing
mstack:set_count(main_count + 1)
inv:set_stack("main", main_slot, mstack)
loan_count = loan_count + 1
lstack:set_count(loan_count)
me.log("LOAN: allow_take to "..lstack:get_count(), "error")
me.loan.set_stack(net, inv, loan_slot, lstack)
end
end
if ref.drawer then
local take = real_stack
take:set_count(count)
local c = drawers.drawer_get_content(ref.pos, "")
drawers.drawer_take_item(ref.pos, take) -- TODO: unify with me.remove_item loan refilling code
local c = drawers.drawer_get_content(ref.pos, "")
else
real_stack:set_count(real_stack:get_count() - count)
rinv:set_stack(ref.invname, ref.slot, real_stack)
end
if loan_count - count == 0 then
net:maybemoveloan(inv, loan_slot)
-- me.log("LOAN: removed "..minetest.serialize(net.counts), "error")
else
lstack:set_count(loan_count - count)
me.log("LOAN: allow_take loan to "..lstack:get_count(), "error")
me.loan.set_stack(net, inv, loan_slot, lstack)
end
net.counts[stack:get_name()] = net.counts[stack:get_name()] - count
me.add_capacity(ref.ipos, -count)
else
update = true
end
if not found then
update = true
-- me.log("Ouch, lost track, someone almost got free shit from us: "..stack:get_name().." "..tostring(count), "error")
count = 0
end
if update then
-- a little weighty, but someone touched the count and it
-- wasn't us, make em pay for it.
net:update_counts()
end
end
end
return count
end, end,
on_take = function() on_take = function(inv, listname, index, stack, player)
me.log("REMOVE: net taking of "..stack:get_count().." "..stack:get_name().." from "..listname, "error")
local func = function()
if inv:get_stack(listname, index):get_count() == 0 then
me.maybemove(net, inv, listname, index, stack)
end
net:set_storage_space(true) -- TODO: remove, should not be necessary anymore, maybemove should do it
end
--update the inventory size in the next step as it is not allowed in on_take --update the inventory size in the next step as it is not allowed in on_take
minetest.after(0, function() net:set_storage_space(true) end) minetest.after(0, func)
end end
}) })
end end
@ -259,9 +445,61 @@ end
function network:load_inventory(lists) function network:load_inventory(lists)
local inv = self:get_inventory() local inv = self:get_inventory()
for listname,c in pairs(lists) do for listname,c in pairs(lists) do
local empties = 0
inv:set_size(listname, #c) inv:set_size(listname, #c)
for i,s in pairs(c) do for i,stack in pairs(c) do
inv:set_stack(listname,i,s) stack = ItemStack(stack) -- and now we try it here
inv:set_stack(listname, i-empties, stack)
-- me.log("LOAD META0: "..stack:get_count().." "..stack:get_name(), "error")
if listname == "loan" then
-- local tref = stack:get_meta():get_string("me_store_reference")
-- me.log("LOAD META1: tref was "..tref, "error")
-- inv:get_stack(listname, i-empties):get_meta():set_string("me_store_reference", tref)
-- local inv_stack = inv:get_stack(listname, i-empties)
-- local inv_meta = inv_stack:get_meta()
-- inv_meta:set_string("me_store_reference", tref)
-- inv:set_stack(listname, i-empties, inv_stack)
-- inv_stack = inv:get_stack(listname, i-empties)
-- me.log("LOAD META1.1: tref was "..inv_meta:get_string("me_store_reference"), "error")
-- me.log("LOAD META1.2: tref was "..inv_stack:get_meta():get_string("me_store_reference"), "error")
-- me.log("LOAD META1.3: tref was "..inv:get_stack(listname, i-empties):get_meta():get_string("me_store_reference"), "error")
-- inv:set_stack(listname, i-empties, ItemStack(inv_stack))
--me.log("LOAD META1.4: tref was "..inv:get_stack(listname, i-empties):get_meta():get_string("me_store_reference"), "error")
--me.log("LOAD META1.5: tref was "..inv_stack:get_meta():get_string("me_store_reference"), "error")
--local meta = inv:get_stack(listname, i-empties)
--meta = meta:get_meta():get_string("me_store_reference")
--me.log("LOAD META2: ref was "..minetest.serialize(meta), "error")
--stack:get_meta():set_string("me_store_reference", tref)
--meta = inv:get_stack(listname, i-empties)
--meta = meta:get_meta():get_string("me_store_reference")
--me.log("LOAD META3: ref was "..minetest.serialize(meta), "error")
--me.log("loading network1: "..minetest.serialize(stack:get_meta():get_string("me_store_reference")), "error")
-- stack = ItemStack(stack) -- tried moving this, was here
-- me.log("loading network2: "..minetest.serialize(stack), "error")
end
-- me.log("loading network: "..minetest.serialize(stack), "error")
if stack and not stack:is_empty() then
-- me.log("loading network "..listname.." "..stack:get_name().." slot "..i, "error")
-- TODO: missing bias
-- does this load loans? Yes.
if not self.byname then
self.byname = {}
end
if not self.byname[listname] then
self.byname[listname] = {}
end
if not self.byname[listname][stack:get_name()] then
self.byname[listname][stack:get_name()] = {}
end
self.byname[listname][stack:get_name()][stack:get_wear()] = i-empties
else
empties = empties + 1
end
end end
end end
end end
@ -281,10 +519,30 @@ end
function network:load() function network:load()
if self.strinv then if self.strinv then
-- me.log("LOADING: "..minetest.serialize(self.strinv), "error")
self:load_inventory(self.strinv) self:load_inventory(self.strinv)
end end
end end
-- We don't save this data, rather we rewalk upon first use. If 1% of
-- the people play per reboot, then this saves 99% of the work.
-- Also, we don't actually read or write any of this data normally,
-- only for active users, using 1% of the memory.
-- TODO: I think all the storage for me should be handled the same way.
-- As it is, we needlessly read and write all the networks for all the users and
-- writing isn't crash friendly, whereas rewalking is crash friendly.
-- We don't reload the loans, that is saved and restored already.
function network:reload_network()
self.autocrafters = {}
self.autocrafters_by_pos = {}
self.process = {}
for ipos in me.connected_nodes(self.controller_pos) do
if me.get_node(ipos).name == "microexpansion:interface" then
me.reload_interface(self, ipos, nil)
end
end
end
function network:serialize() function network:serialize()
local sert = {} local sert = {}
for i,v in pairs(self) do for i,v in pairs(self) do
@ -306,3 +564,428 @@ function network:destruct()
self.controller_pos = nil self.controller_pos = nil
self.inv = nil self.inv = nil
end end
-- This ensure main_slot is right, and if not, update it
function network:find_main(inv, ref, stack)
local main_slot = ref.main_slot
local mstack = inv:get_stack("main", ref.main_slot)
if mstack:get_name() == stack:get_name() and mstack:get_wear() == stack:get_wear() then
return mstack
end
for i = 1, inv:get_size("main") do
mstack = inv:get_stack("main", i)
if mstack:get_name() == stack:get_name() and mstack:get_wear() == stack:get_wear() then
ref.main_slot = i
return mstack
end
end
end
-- we can't update a loan with a bias, cause we don't know the old bias for this specific loan,
-- therefore we have no clue about changes to the item count, therefore we can't update the
-- item count.
function network:update_loan(inv, loan_slot)
me.log("LOAN: updating some 4", "error")
local lstack = me.loan.get_stack(self, inv, loan_slot)
local tref = lstack:get_meta():get_string("me_store_reference")
me.log("LOAN: update_loan "..lstack:get_count().." "..lstack:get_name().." with ref "..tref, "error")
local rinv = nil
local real_stack = nil
local same_name = nil
local same_wear = nil
local excess = nil
if tref == nil then me.log("LOAN: bad tref in loan", "error") end
-- if tref == nil then return end
local ref = minetest.deserialize(tref)
if ref == nil then me.log("LOAN: bad ref in loan", "error") end
if ref == nil then return end
rinv = minetest.get_meta(ref.pos):get_inventory()
local rbias = 0
local lbias = (self.bias and self.bias["loan"] and self.bias["loan"][loan_slot]) or 0
if ref.drawer then
local c = drawers.drawer_get_content(ref.pos, ref.slot)
if c.name ~= lstack:get_name() then me.log("LOAN: bad drawer item in loan "..c.name.." "..lstack:get_name(), "error") end
c.count = math.max(c.count-1,0) -- Poor man's locking
real_stack = ItemStack(c.name)
if c.count > math.pow(2,15) then
rbias = rbias + c.count - math.pow(2,15)
c.count = math.pow(2,15)
end
real_stack:set_count(c.count)
else
real_stack = rinv:get_stack(ref.invname, ref.slot)
if real_stack:get_name() ~= lstack:get_name() then me.log("LOAN: bad chest item in loan "..real_stack:get_name().." "..lstack:get_name(), "error") end
end
-- local excess = (real_stack:get_count() + rbias) - (lstack:get_count() + lbias)
-- TODO: This assumes meta is the same save "me_store_reference".
same_name = real_stack:get_name() == lstack:get_name()
same_wear = real_stack:get_wear() == lstack:get_wear()
-- If someone updates the chest, update loan and counts
if not same_name or not same_wear then
me.log("inventory out of sync", "error")
-- Not at all the same, remove the loan entirely
if not inv:contains_item("main", lstack) then
-- TODO: Update anything that can remove to have allow_metadata check
-- remote inventories first so this cannot happen
me.log("missing items on loan #1", "error")
end
me.remove_item(self, inv, "main", lstack) -- FIXME remove_loan or change_loan? lstack, remove tref and remove that from main.
lstack:set_count(0)
if not real_stack:is_empty() then
local llstack = ItemStack(real_stack)
llstack:get_meta():set_string("me_store_reference", tref)
me.log("LOAN: update_loan to "..llstack:get_count()..llstack:get_name(), "error")
me.loan.set_stack(self, inv, loan_slot, llstack) -- FIXME change loan?
if rbias > 0 then
if not self.bias then
self.bias = {}
end
if not self.bias["loan"] then
self.bias["loan"] = {}
end
self.bias["loan"][loan_slot] = rbias -- FIXME, lstack or llstack? was lstack, wrong
end
end
return
end
excess = real_stack:get_count() - lstack:get_count()
me.log("update_loan exess is "..excess..", lc is "..lstack:get_count()..", rc is "..real_stack:get_count(), "error")
local prev = self.counts[lstack:get_name()] or 0
self.counts[lstack:get_name()] = prev + excess
me.log("COUNT: update_loan loan now to "..self.counts[lstack:get_name()].." "..lstack:get_name()..", "..excess.." more", "error")
if not real_stack:is_empty() then
me.log("LOAN: updating some", "error")
real_stack:get_meta():set_string("me_store_reference", tref)
local llstack = ItemStack(real_stack)
me.log("LOAN: update_loan to "..llstack:get_count().." "..llstack:get_name(), "error")
me.loan.set_stack(self, inv, loan_slot, llstack)
else
me.log("LOAN: updating some 2", "error")
remove_loan(ref.pos, inv, lstack, loan_slot, ref)
end
if excess > 0 then
local extra = lstack
extra:set_count(excess)
extra:get_meta():set_string("me_store_reference", "")
me.log("update_loan adding "..excess.." "..lstack:get_name().." to main", "error")
local mstack = self:find_main(inv, ref, extra)
if mstack and not mstack:is_empty() then
mstack:set_count(mstack:get_count()+excess)
else
me.log("INV: went missing, readding", "error")
me.insert_item(extra, self, inv, "main")
end
inv:set_stack("main", ref.main_slot, mstack)
me.add_capacity(ref.ipos, excess)
elseif excess < 0 then
local deficit = lstack
deficit:set_count(-excess)
me.log("update_loan removing "..-excess.." "..lstack:get_name().." from main", "error")
-- We should fix this so that this cannot happen. See above.
-- For now, let's see if we can fix it up.
if not inv:contains_item("main", deficit) then
me.log("network no extra items to meet deficit, free items", "error")
end
-- TODO: This isn't a copy of the original meta, it is the original meta, and screws other loans, maybe?
deficit:get_meta():set_string("me_store_reference", "")
-- was: local mstack = inv:get_stack("main", ref.main_slot)
local mstack = self:find_main(inv, ref, deficit)
if mstack then
mstack:set_count(mstack:get_count()+excess)
inv:set_stack("main", ref.main_slot, mstack)
if false and mstack:get_count() == 0 then -- should be impossible, we have an outstanding loan still?
foobar()
end
me.add_capacity(ref.ipos, excess)
end
end
end
function network:update_loan_count(inv, loan_slot)
me.log("COUNT: loan_slot "..loan_slot, "error")
local lstack = me.loan.get_stack(self, inv, loan_slot)
local lbias = (self.bias and self.bias["loan"] and self.bias["loan"][loan_slot]) or 0
local prev = self.counts[lstack:get_name()] or 0
self.counts[lstack:get_name()] = prev + lstack:get_count() + lbias
me.log("COUNT: update_loan_count loan now to "..self.counts[lstack:get_name()].." "..lstack:get_name()..", "..(lstack:get_count() + lbias).." more, bias is "..lbias, "error")
end
-- Check everything on loan. This is the cheapest possible version.
-- We don't rewalk, we don't pick up new slots that now have contents.
-- We don't even verify the type of node is the same type of node.
function network:update_counts()
local inv = self:get_inventory()
for loan_slot = 1, me.loan.get_size(self, inv) do
self:update_loan(inv, loan_slot)
end
-- Since we are rescanning 100%, we start with no old counts and we
-- rebuild the counts
self.counts = {}
-- no, update_loan_count doesn't read from the inventory, if this is done, it has to be done
-- by a inventory reader that will re-create it.
--if self.bias and self.bias["loan"] then
-- self.bias["loan"] = nil
--end
for loan_slot = 1, me.loan.get_size(self, inv) do
self:update_loan_count(inv, loan_slot)
end
end
function network:sync_main(inv)
local listname = "main"
for i = 1, inv:get_size(listname) do
local mstack = inv:get_stack(listname, i)
if mstack:is_empty() and i < inv:get_size("main") then
me.log("network sync_main, empty stack at pos "..i, "error")
me.maybemove(self, inv, "main", i, mstack)
else
if not net.byname then
net.byname = {}
end
if not net.byname[listname] then
net.byname[listname] = {}
end
if not net.byname[listname][stack:get_name()] then
net.byname[listname][stack:get_name()] = {}
end
--if not net.byname[listname][stack:get_name()][stack:get_wear()] then
-- net.byname[listname][stack:get_name()][stack:get_wear()] = {}
--end
local slot = self.byname[listname][mstack:get_name()][mstack:get_wear()]
if not slot then
me.log("network sync_main, missing "..mstack:get_name().." at pos "..i, "error")
self.byname[listname][mstack:get_name()][mstack:get_wear()] = i
elseif slot ~= i then
me.log("network sync_main, wrong pos for "..mstack:get_name().." at pos "..i", found "..slot, "error")
self.byname[listname][mstack:get_name()][mstack:get_wear()] = i
end
end
end
end
function network:remove_real(ref, inv, stack, count)
if ref.drawer then
while count > math.pow(2,16)-1 do
stack:set_count(math.pow(2,16)-1)
drawers.drawer_take_large_item(ref.pos, stack)
count = count - math.pow(2,16)-1
end
stack:set_count(count)
drawers.drawer_take_large_item(ref.pos, stack)
else
local rinv = minetest.get_meta(ref.pos):get_inventory()
stack:set_count(count)
local rstack = rinv:get_stack(ref.invname, ref.slot)
rstack:take_item(count)
rinv:set_stack(ref.invname, ref.slot, rstack)
end
end
-- Callers have to ensure net.counts exists before calling.
-- Loans with bias only work for no wear and no metadata items. Only
-- drawers can create bias loans and they don't support wear or
-- metadata items. One should add chests first, then drawers, this will top off
-- items in the drawers. If one wants to empty the chests, do the chests after the
-- drawer.
function network:create_loan(stack, ref, inv, int_meta, bias)
local listname = "loan"
local on_loan = self.counts[stack:get_name()] or 0
local lbias = bias or 0
inv = inv or get_inventory()
local mstack = ItemStack(stack)
local prev = int_meta:get_int("capacity") or 0
local count = stack:get_count() + (bias or 0)
int_meta:set_int("capacity", prev + count)
-- me.log("total loaned items: "..tostring(prev + count))
self:set_storage_space(true)
local _, main_slot = me.insert_item(mstack, self, inv, "main", bias)
local now_on_loan = self.counts[stack:get_name()] or 0
local items_taken = now_on_loan - on_loan
if items_taken > 0 then
local loan_slot = self:find_loan(inv, stack)
self:remove_real(ref, inv, stack, items_taken)
prev = int_meta:get_int("capacity") or 0
int_meta:set_int("capacity", prev - items_taken)
count = count - items_taken
if count == 0 then
-- no actual loan in this case.
return
end
if count > math.pow(2,15) then
bias = count - math.pow(2,15)
lbias = bias
count = math.pow(2,15)
else
bias = nil
lbias = 0
stack:set_count(count)
end
end
if main_slot == 1 then
me.log("LARGE: creating loan for "..mstack:get_name()..", mc "..mstack:get_count()..", lc "..stack:get_count()..", lbias "..(bias or 0), "error")
end
self:set_storage_space(true)
ref.main_slot = main_slot
stack:get_meta():set_string("me_store_reference", minetest.serialize(ref))
local loan_slot = me.loan.get_size(self, inv)+1
me.loan.set_size(self, inv, loan_slot)
-- me.log("loan size is now "..me.loan.get_size(self, inv))
me.log("LOAN: create_loan to "..stack:get_count().." "..stack:get_name(), "error")
me.loan.set_stack(self, inv, loan_slot, stack)
me.log("INV: slot "..loan_slot.." now has "..stack:get_count().." "..stack:get_name().." in it", "error")
if bias then
if not self.bias then
self.bias = {}
end
if not self.bias[listname] then
self.bias[listname] = {}
end
self.bias[listname][loan_slot] = bias
me.log("LARGE: "..self.bias[listname][loan_slot].." "..stack:get_name()..", added "..bias, "error")
end
prev = self.counts[stack:get_name()] or 0 -- TODO: Contemplate wear and meta for counts. Add wear, meta; no limit large counts to no wear, no meta
self.counts[stack:get_name()] = prev + count
me.log("COUNT: create_loan loan now to "..self.counts[stack:get_name()].." "..stack:get_name()..", "..count.." more", "error")
end
-- This removes the entire loan slot, always
function network:remove_loan(pos, inv, lstack, loan_slot, ref)
me.log("LOAN: updating some 3", "error")
lstack:get_meta():set_string("me_store_reference", "")
local mstack = self:find_main(inv, ref, lstack)
local main_slot = ref.main_slot
if mstack == nil then mstack = ItemStack() end
local omstack = ItemStack(mstack)
me.log("network remove_loan of "..omstack:get_name()..", at "..main_slot, "error")
local lbias = (self.bias and self.bias["loan"] and self.bias["loan"][loan_slot]) or 0
local excess = 0
local mbias = (self.bias and self.bias["main"] and self.bias["main"][mstack:get_name()]) or 0
excess = (mstack:get_count() + mbias) - (lstack:get_count() + lbias)
me.log("LOAN: remove_loan "..(mstack:get_count() + mbias).." "..mstack:get_name().." "..(lstack:get_count() + lbias).." on loan, lbias is "..lbias, "error")
if lstack:get_name() == "default:steel_ingot" then
me.log("LARGE: remove_loan excess "..excess.." for "..(lstack:get_name())..", mc "..mstack:get_count()..", mbias "..mbias..", lc "..lstack:get_count()..", lbias "..lbias..", slot "..loan_slot, "error")
end
if excess < 0 then
me.log("network missing "..tostring(-excess).." "..lstack:get_name().." from loan, free items", "error")
mstack:set_count(0)
mbias = 0
if self.bias and self.bias["main"] then
self.bias["main"][mstack:get_name()] = nil
end
lbias = 0
if self.bias and self.bias["loan"] then
self.bias["loan"][loan_slot] = nil
end
excess = 0
end
if excess > math.pow(2,15) then
me.log("LARGE: remove_loan remaining "..excess.." for "..lstack:get_name(), "error")
mstack:set_count(math.pow(2,15))
self.bias["main"][mstack:get_name()] = excess - math.pow(2,15)
if self.bias and self.bias["loan"] then
lbias = 0
self.bias["loan"][loan_slot] = nil
end
else
mstack:set_count(excess)
if self.bias and self.bias["main"] then
self.bias["main"][omstack:get_name()] = nil
end
if self.bias and self.bias["loan"] then
lbias = 0
self.bias["loan"][loan_slot] = nil
end
end
inv:set_stack("main", main_slot, mstack)
if mstack:get_count() == 0 then
me.log("network cleaning up empty main slot now", "error")
me.maybemove(self, inv, "main", main_slot, omstack)
me.log("network cleaning up empty main slot now, done", "error")
end
-- me.loan.set_stack(self, inv, loan_slot, ItemStack())
if self.bias and self.bias["loan"] then
self.bias["loan"][loan_slot] = nil
end
local on_loan = self.counts[lstack:get_name()]
if on_loan and on_loan >= lstack:get_count() + lbias then
self.counts[lstack:get_name()] = on_loan - lstack:get_count() - lbias
me.log("LOAN: remove_loan down to count "..self.counts[lstack:get_name()], "error")
else
me.log("wow, free items, network remove_loan fails to find previous loan counts, "..self.counts[lstack:get_name()].." "..lstack:get_name(), "error")
self.counts[lstack:get_name()] = 0
end
self:maybemoveloan(inv, loan_slot)
end
-- like me.maybemove
function network:maybemoveloan(inv, loan_slot)
local loan_size = me.loan.get_size(self, inv)
local stack = me.loan.get_stack(self, inv, loan_size)
if stack:is_empty() then
me.loan.set_size(self, inv, loan_size-1)
return self:maybemoveloan(inv, loan_slot)
end
local do_update = false
if loan_size > 1 and loan_slot < loan_size then
local stack = me.loan.get_stack(self, inv, loan_size)
if stack:is_empty() then -- should not be necessary
me.log("network BAD loan", "error")
-- This trips with interface place remove on main drawers
foobar()
end
local prev = me.loan.get_stack(self, inv, loan_slot)
if prev:get_count() ~= 0 then
-- TODO: Should not be necessary, find real problem, full remove, refile check, replace interface
if self.byname["loan"][prev:get_name()] then
self.byname["loan"][prev:get_name()][prev:get_wear()] = nil
end
else
-- don't remove the loan before, we need the name
foobar()
end
me.log("LOAN: maybemoveloan to "..stack:get_count(), "error")
me.loan.set_stack(self, inv, loan_slot, stack)
-- me.log("maybemoveloan "..stack:get_name(), "error")
-- me.log("maybemoveloan "..stack:get_count(), "error")
-- me.log("maybemoveloan "..stack:get_wear(), "error")
-- me.log("maybemoveloan "..loan_slot, "error")
-- me.log("maybemoveloan "..minetest.serialize(self.byname), "error")
-- me.log("maybemoveloan "..minetest.serialize(self.byname["loan"]), "error")
-- TODO: Should not be necessary, find real problem, full remove, refile check, replace interface
if self.byname["loan"][stack:get_name()] then
self.byname["loan"][stack:get_name()][stack:get_wear()] = loan_slot
end
do_update = true
else
local stack = me.loan.get_stack(self, inv, loan_size)
if stack:get_count() ~= 0 then
-- me.log("maybemoveloan "..stack:get_name(), "error")
-- me.log("maybemoveloan "..stack:get_count(), "error")
-- me.log("maybemoveloan "..stack:get_wear(), "error")
-- me.log("maybemoveloan "..loan_slot, "error")
-- me.log("maybemoveloan "..minetest.serialize(self.byname), "error")
-- me.log("maybemoveloan "..minetest.serialize(self.byname["loan"]), "error")
-- TODO: Should not be necessary, find real problem, full remove, refile check, replace interface
if self.byname["loan"] and self.byname["loan"][stack:get_name()] then
self.byname["loan"][stack:get_name()][stack:get_wear()] = nil
end
else
-- don't remove the loan before, we need the name
foobar()
end
end
me.loan.set_size(self, inv, loan_size-1)
-- me.log("loan size is now "..me.loan.get_size(self, inv))
if do_update then
-- update_loan calls us and needs this updated by the time we return
self:update_loan(inv, loan_slot)
end
end
function network:dump_loans(inv)
me.log("COUNTS: dump_loans "..minetest.serialize(self.counts), "error")
for i, j in pairs(inv:get_list("loan") or {}) do
print(" slot "..dump(i).." "..j:to_string().." "..j:get_meta():get_string("me_store_reference"))
end
end

@ -48,7 +48,7 @@ function microexpansion.int_to_pagenum(int)
end end
-- [function] Move items from inv to inv -- [function] Move items from inv to inv
function microexpansion.move_inv(inv1, inv2, max) function microexpansion.move_inv(net, inv1, inv2, max)
if max <= 0 then return end if max <= 0 then return end
local finv, tinv = inv1.inv, inv2.inv local finv, tinv = inv1.inv, inv2.inv
local fname, tname = inv1.name, inv2.name local fname, tname = inv1.name, inv2.name
@ -66,14 +66,14 @@ function microexpansion.move_inv(inv1, inv2, max)
end end
if tinv and tinv:room_for_item(tname, v) then if tinv and tinv:room_for_item(tname, v) then
if huge then if huge then
microexpansion.insert_item(v, tinv, tname) microexpansion.insert_item(v, net, tinv, tname)
finv:remove_item(fname, v) finv:remove_item(fname, v)
else else
local leftover = tinv:add_item(tname, v) local leftovers = tinv:add_item(tname, v)
finv:remove_item(fname, v) finv:remove_item(fname, v)
if leftover and not(leftover:is_empty()) then if leftovers and not leftovers:is_empty() then
microexpansion.log("leftover items when transferring inventory","warning") microexpansion.log("leftover items when transferring inventory", "warning")
finv:add_item(fname, leftover) finv:add_item(fname, leftovers)
end end
end end
inserted = inserted + v:get_count() inserted = inserted + v:get_count()

@ -0,0 +1,756 @@
-- crafting terminal
-- microexpansion/cterminal.lua
-- TODO: Bugs, can't craft sticks, oil extract by using the
-- output. Does work when updating the recipe. Groups are hanky. We
-- only use the last recipe registered. Would be nice to be able to
-- cycle trough them. We handle this by merely requiring the user to
-- select the input recipe they want.
-- TODO: Bugs in original, if you remove controller, this wipes all drives
-- Spacing sucks.
-- The search list doesn't update when main updates or when autocrafting updates.
local autocrafterCache = {} -- caches some recipe data to avoid to call the slow function minetest.get_craft_result() every second
local me = microexpansion
local pipeworks_enabled = minetest.get_modpath("pipeworks") and true or false
-- [me chest] Get formspec
local function chest_formspec(pos, start_id, listname, page_max, q)
local list
local page_number = ""
local buttons = ""
local query = q or ""
local net,cpos = me.get_connected_network(pos)
if cpos then
local inv = net:get_inventory()
if listname and (inv:get_size(listname) > 0 or net:get_item_capacity() > 0) then
local ctrlinvname = net:get_inventory_name()
if listname == "main" then
list = "list[detached:"..ctrlinvname..";"
.. listname .. ";0,0.3;8,4;" .. (start_id - 1) .. "]"
else
list = "list[context;" .. listname .. ";0,0.3;8,4;" .. (start_id - 1) .. "]"
end
if minetest.get_modpath("i3") then
list = list .. [[
list[current_player;main;0,8.5;9,4;]
]]
else
list = list .. [[
list[current_player;main;0,8.5;8,1;]
list[current_player;main;0,9.73;8,3;8]
]]
end
list = list .. [[
list[context;recipe;0.22,5.22;3,3;]
list[context;output;4,6.22;1,1;]
]]
list = list .. [[
listring[current_player;main]
listring[detached:]]..ctrlinvname..[[;main]
listring[current_player;main]
listring[context;recipe]
listring[current_player;main]
listring[context;output]
listring[current_player;main]
]]
buttons = [[
button[3.56,4.35;1.8,0.9;tochest;To Drive]
tooltip[tochest;Move everything from your inventory to the ME network.]
button[5.4,4.35;0.8,0.9;prev;<]
button[7.25,4.35;0.8,0.9;next;>]
tooltip[prev;Previous]
tooltip[next;Next]
field[0.29,4.6;2.2,1;filter;;]]..query..[[]
button[2.1,4.5;0.8,0.5;search;?]
button[2.75,4.5;0.8,0.5;clear;X]
tooltip[search;Search]
tooltip[clear;Reset]
field[6,5.42;2,1;autocraft;;1]
tooltip[autocraft;Number of items to Craft]
]]
else
list = "label[3,2;" .. minetest.colorize("red", "No connected storage!") .. "]"
end
else
list = "label[3,2;" .. minetest.colorize("red", "No connected network!") .. "]"
end
if page_max then
page_number = "label[6.15,4.5;" .. math.floor((start_id / 32)) + 1 ..
"/" .. page_max .."]"
end
return [[
size[9,12.5]
]]..
microexpansion.gui_bg ..
microexpansion.gui_slots ..
list ..
[[
label[0,-0.23;ME Crafting Terminal]
field_close_on_enter[filter;false]
field_close_on_enter[autocraft;false]
]]..
page_number ..
buttons
end
local function update_chest(pos,_,ev)
--for now all events matter
local net = me.get_connected_network(pos)
local meta = minetest.get_meta(pos)
if net == nil then
meta:set_int("page", 1)
meta:set_string("formspec", chest_formspec(pos, 1))
return
end
local size = net:get_item_capacity()
local page_max = me.int_to_pagenum(size) + 1
meta:set_string("inv_name", "main")
meta:set_string("formspec", chest_formspec(pos, 1, "main", page_max))
end
-- From pipeworks/autocrafter.lua
local function count_index(invlist)
local index = {}
for _, stack in pairs(invlist) do
if not stack:is_empty() then
local stack_name = stack:get_name()
index[stack_name] = (index[stack_name] or 0) + stack:get_count()
end
end
return index
end
-- From pipeworks/autocrafter.lua
function me.get_craft(pos, inventory, hash)
local hash = hash or minetest.hash_node_position(pos)
local craft = autocrafterCache[hash]
if not craft then
local recipe = inventory:get_list("recipe")
for i = 1, 9 do
if recipe[i]:get_count() > 1 then
recipe[i] = ItemStack(recipe[i]:get_name())
end
end
local output, decremented_input = minetest.get_craft_result({method = "normal", width = 3, items = recipe})
craft = {recipe = recipe, consumption=count_index(recipe), output = output, decremented_input = decremented_input}
autocrafterCache[hash] = craft
end
return craft
end
-- From pipeworks/autocrafter.lua
-- note, that this function assumes allready being updated to virtual items
-- and doesn't handle recipes with stacksizes > 1
local function after_recipe_change(pos, inventory)
local meta = minetest.get_meta(pos)
-- if we emptied the grid, there's no point in keeping it running or cached
if inventory:is_empty("recipe") then
autocrafterCache[minetest.hash_node_position(pos)] = nil
inventory:set_stack("output", 1, "")
return
end
local recipe = inventory:get_list("recipe")
local hash = minetest.hash_node_position(pos)
local craft = autocrafterCache[hash]
if craft then
-- check if it changed
local cached_recipe = craft.recipe
for i = 1, 9 do
if recipe[i]:get_name() ~= cached_recipe[i]:get_name() then
autocrafterCache[hash] = nil -- invalidate recipe
craft = nil
break
end
end
end
craft = craft or me.get_craft(pos, inventory, hash)
local output_item = craft.output.item
inventory:set_stack("output", 1, output_item)
end
-- From pipeworks/autocrafter.lua
-- clean out unknown items and groups, which would be handled like unknown items in the crafting grid
-- if minetest supports query by group one day, this might replace them
-- with a canonical version instead
local function normalize(item_list)
for i = 1, #item_list do
local name = item_list[i]
if not minetest.registered_items[name] then
item_list[i] = ""
if name == "group:stick" then
item_list[i] = "default:stick"
elseif name == "group:glass" then
item_list[i] = "default:glass"
elseif name == "group:wood" then
name = "moretrees:oak_planks"
if minetest.registered_items[name] then
item_list[i] = name
else
item_list[i] = "default:wood"
end
elseif name == "group:wood" then
item_list[i] = "moretrees:oak_trunk"
elseif name == "group:wool" then
item_list[i] = "wool:white"
elseif name == "group:sand" then
item_list[i] = "default:sand"
elseif name == "group:stone" then
item_list[i] = "default:cobble"
elseif name == "group:leaves" then
name = "moretrees:sequoia_leaves"
if minetest.registered_items[name] then
item_list[i] = name
else
item_list[i] = "default:leaves"
end
elseif name == "group:coal" then
item_list[i] = "default:coal"
elseif name == "group:tree" then
item_list[i] = "default:tree"
end
end
end
return item_list
end
-- 0 to 34
function me.uranium_dust(p)
return "technic:uranium"..(p == 7 and "" or p).."_dust"
end
for pa = 0, 34 do
-- uranium_dust(pa)
-- me.uranium_dust(pa-1).." 2"
-- No, this would require a billion uranium to do this. :-(
-- Make a uranium centrifuge controller and have it be smart.
--me.register_output_by_typename("separating", "")
end
me.output_by_typename = {
-- aka me.register_output_by_typename("cooking", "default:stone")
["cooking"] = { "default:stone" }
}
-- Used to register what machine types (typename) produce which outputs.
-- Used to figure out what machine to use to create the given output.
-- If multiple outputs are produced, only use the main output, not the
-- incidental output.
function me.register_output_by_typename(typename, output)
if not me.output_by_typename[typename] then
me.output_by_typename[typename] = {}
end
table.insert(me.output_by_typename[typename], output)
end
function me.register_output_to_inputs(output, inputs)
me.log("REG: output "..output.." from inputs "..dump(inputs))
me.map_output_to_inputs[output] = inputs
end
me.map_output_to_inputs = {
-- furnace ("cooking")
["default:stone"] = { ItemStack("default:cobble") },
}
function me.find_by_output(name)
-- TODO: we'd love to be able to look this stuff up in the recipes.
-- technic.recipes["technic:doped_silicon_wafer"].alloy.recipes[4].input[1]
return me.map_output_to_inputs[name]
end
function me.register_inventory(name, func)
-- me.log("INVENTORY: registering "..name, "error")
if not me.registered_inventory then
me.registered_inventory = {}
end
me.registered_inventory[name] = func
end
-- Allow any type of machine process to be registered. For example,
-- "alloy" for an alloy furnace for example. These must be done
-- before specific me.register_inventory calls, as that one needs to
-- override this call.
function me.register_typename(name, typename)
me.block_to_typename_map[name] = typename
me.register_inventory(name, function() end)
end
me.block_to_typename_map = {
}
-- default wiring
me.register_typename("default:furnace", "cooking")
function me.get_recipe(typename, inputs)
return technic.get_recipe(typename, inputs)
end
-- TODO: Removing an output when the recipe is empty that is in
-- net.autocrafters should not be allowed as the output in that case
-- is virtual. It can be removed if rinv:"output" has the item iff
-- that item is removed from the autocrafter's output.
local function on_output_change(pos, linv, stack)
local name = stack:get_name()
-- me.log("PROCESS: "..name.." was found0", "error")
local net = me.get_connected_network(pos)
local inv = net:get_inventory()
local has_enough = true
local function clear_recipe()
local has_enough = true
for i = 1, 9 do
local prev = linv:get_stack("recipe", i)
if prev and prev:get_name() ~= "" and not inv:room_for_item("main", prev) then
-- full, no room to remove
has_enough = false
elseif prev and prev:get_name() ~= "" and me.insert_item(prev, net, inv, "main"):get_count() > 0 then
net:set_storage_space(true)
-- full, no room to remove
-- push into player inventory?
-- Don't have to worry about this happening until minetest is fully multithreaded
has_enough = false
else
net:set_storage_space(true)
linv:set_stack("recipe", i, ItemStack(""))
end
end
return has_enough
end
if not net.process then
-- rewalk the interfaces on the network to rebuild loans and machines.
net:reload_network()
end
if net and net.process[name] then
-- me.log("PROCESS: "..name.." was found1", "error")
has_enough = clear_recipe()
local pos,ipos = next(net.process[name])
if has_enough and pos then
-- me.log("PROCESS: "..name.." was found2", "error")
local inputs = me.find_by_output(name)
-- me.log("PROCESS: inputs are "..dump(inputs), "error")
local machine_name = minetest.get_node(pos).name
local typename = me.block_to_typename_map[machine_name]
local recip = typename and me.get_recipe(typename, inputs)
if recip and recip.output then
recip.intput = inputs
-- me.log("PROCESS: "..name.." was found for "..typename.." on a "..machine_name, "error")
-- freezer can produce two outputs, we only care about the first.
if recip.output[1] then
recip.output = recip.output[1]
end
stack = ItemStack(recip.output)
linv:set_stack("output", 1, stack)
-- me.log("PROCESS: and the output is "..minetest.serialize(recip.output), "error")
-- me.log("PROCESS: and the output is "..stack:get_name(), "error")
else
me.log("PROCESS: "..name.." was missing from recipe on a "..machine_name, "error")
linv:set_stack("output", 1, ItemStack())
end
end
return 0
elseif net and net.autocrafters[name] then
has_enough = clear_recipe()
if has_enough then
local pos,ipos = next(net.autocrafters[name])
if pos then
local rinv = minetest.get_meta(pos):get_inventory()
stack = ItemStack(rinv:get_stack("output", 1))
linv:set_stack("output", 1, stack)
else
-- me.log("pos in autocrafters was missing", "error")
linv:set_stack("output", 1, ItemStack())
end
else
linv:set_stack("output", 1, ItemStack())
end
return 0
end
local input = minetest.get_craft_recipe(name)
if not input.items or input.type ~= "normal" then return 0 end
local items, width = normalize(input.items), input.width
local item_idx, width_idx = 1, 1
for i = 1, 9 do
local prev = linv:get_stack("recipe", i)
if prev and prev:get_name() ~= "" and not inv:room_for_item("main", prev) then
-- full, no room to remove
has_enough = false
if width_idx <= width then
item_idx = item_idx + 1
end
elseif prev and prev:get_name() ~= "" and me.insert_item(prev, net, inv, "main"):get_count() > 0 then
net:set_storage_space(true)
-- full, no room to remove
-- push into player inventory?
-- Don't have to worry about this happening until minetest is fully multithreaded
has_enough = false
if width_idx <= width then
item_idx = item_idx + 1
end
elseif width_idx <= width then
net:set_storage_space(true)
if inv:contains_item("main", items[item_idx]) then
me.remove_item(net, inv, "main", ItemStack(items[item_idx]))
linv:set_stack("recipe", i, items[item_idx])
else
has_enough = false
linv:set_stack("recipe", i, ItemStack(""))
end
item_idx = item_idx + 1
else
linv:set_stack("recipe", i, ItemStack(""))
end
width_idx = (width_idx < 3) and (width_idx + 1) or 1
end
-- we'll set the output slot in after_recipe_change to the actual result of the new recipe
after_recipe_change(pos, linv)
return 0
end
-- [me cterminal] Register node
me.register_node("cterminal", {
description = "ME Crafting Terminal",
usedfor = "Can interact with storage cells in ME networks",
tiles = {
"chest_top",
"chest_top",
"chest_side",
"chest_side",
"chest_side",
"chest_front",
},
recipe = {
{ 1, {
{"microexpansion:term", "default:chest"},
},
}
},
is_ground_content = false,
groups = { cracky = 1, me_connect = 1, tubedevice = 1, tubedevice_receiver = 1 },
paramtype = "light",
paramtype2 = "facedir",
me_update = update_chest,
on_construct = function(pos)
local meta = minetest.get_meta(pos)
meta:set_string("formspec", chest_formspec(pos, 1))
meta:set_string("inv_name", "none")
meta:set_int("page", 1)
local own_inv = meta:get_inventory()
own_inv:set_size("src", 1)
own_inv:set_size("recipe", 3*3)
own_inv:set_size("output", 1)
local net = me.get_connected_network(pos)
me.send_event(pos,"connect",{net=net})
if net then
update_chest(pos)
end
end,
after_destruct = function(pos)
me.send_event(pos,"disconnect")
end,
can_dig = function(pos, player)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return inv:is_empty("recipe")
end,
allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
me.log("Allow a move from "..from_list.." to "..to_list, "error")
local meta = minetest.get_meta(pos)
if to_list == "search" then
local net = me.get_connected_network(pos)
local linv = minetest.get_meta(pos):get_inventory()
local inv = net:get_inventory()
local stack = linv:get_stack(from_list, from_index)
stack:set_count(count)
-- local meta = minetest.get_meta(pos)
-- meta:set_string("infotext", "allow moving: "..stack:get_name())
-- TODO: Check capacity? Test.
me.insert_item(stack, net, inv, "main")
return count
end
if to_list == "recipe" and from_list == "search" then
local net = me.get_connected_network(pos)
local linv = minetest.get_meta(pos):get_inventory()
local inv = net:get_inventory()
local stack = linv:get_stack(from_list, from_index)
local meta = minetest.get_meta(pos)
count = math.min(count, stack:get_stack_max())
stack:set_count(count)
me.remove_item(net, inv, "main", stack)
return count
end
if to_list == "output" then
local linv = minetest.get_meta(pos):get_inventory()
local stack = linv:get_stack(from_list, from_index)
return on_output_change(pos, linv, stack)
end
return count
end,
allow_metadata_inventory_take = function(pos, listname, index, stack, player)
-- This is used for removing items from "search", "recipe" and "output".
--me.log("Allow a take from "..listname, "error")
local count = stack:get_count()
if listname == "search" or listname == "recipe" then
count = math.min(count, stack:get_stack_max())
end
--[[if listname == "main" then
-- This should be unused, we don't have a local inventory called main.
local net = me.get_connected_network(pos)
local inv = net:get_inventory()
local ret = me.remove_item(net, inv, "main", stack)
me.log("REMOVE: after remove count is "..ret:get_count(), "error")
return ret:get_count()
end
]]
return count
end,
allow_metadata_inventory_put = function(pos, listname, index, stack, player)
if listname == "output" then
local linv = minetest.get_meta(pos):get_inventory()
return on_output_change(pos, linv, stack)
elseif listname == "search" or listname == "main" then
local net = me.get_connected_network(pos)
local inv = net:get_inventory()
-- TODO: Check full inv, should be fixed now, confirm.
local leftovers = me.insert_item(stack, net, inv, "main")
return stack:get_count() - leftovers:get_count()
end
return stack:get_count()
end,
on_metadata_inventory_put = function(pos, listname, _, stack)
if listname == "recipe" then
local linv = minetest.get_meta(pos):get_inventory()
after_recipe_change(pos, linv)
elseif listname == "search" or listname == "main" then
-- done above in allow, nothing left to do here
elseif listname == output then
-- done above
else
local net = me.get_connected_network(pos)
local inv = net:get_inventory()
local leftovers = me.insert_item(stack, net, inv, "main")
if leftovers:get_count() > 0 then
fixme()
end
net:set_storage_space(true)
end
end,
on_metadata_inventory_take = function(pos, listname, index, stack)
me.log("A taking of "..stack:get_name().." from "..listname, "error")
if listname == "output" then
local linv = minetest.get_meta(pos):get_inventory()
local num_left = linv:get_stack("output", 1):get_count()
-- We only need to consume the recipe if there are no more items
-- if num_left ~= 0 then return end
if num_left > 1 then return end
local net = me.get_connected_network(pos)
local inv = net:get_inventory()
local replace = true
-- This assumes that all inputs are only just 1 item, always true?
for i = 1, 9 do
local inp = linv:get_stack("recipe", i)
if inp and inp:get_name() ~= "" then
local consume = ItemStack(inp:get_name())
replace = replace and (inp:get_count() > 1 or inv:contains_item("main", consume))
end
end
for i = 1, 9 do
local inp = linv:get_stack("recipe", i)
if inp and inp:get_name() ~= "" then
if inp:get_count() == 1 then
if inv:contains_item("main", inp) then
local r = me.remove_item(net, inv, "main", inp)
if r:get_count() ~= 1 then
linv:set_stack("recipe", i, ItemStack(""))
replace = false
end
else
linv:set_stack("recipe", i, ItemStack(""))
replace = false
end
else
local stack_copy = ItemStack(inp)
stack_copy:set_count(inp:get_count()-1)
linv:set_stack("recipe", i, stack_copy)
end
end
end
-- deal with replacements
local hash = minetest.hash_node_position(pos)
local craft = autocrafterCache[hash] or me.get_craft(pos, linv, hash)
for i = 1, 9 do
if (craft.decremented_input.items[i]:get_count() ~= linv:get_stack("recipe", i):get_count()
or craft.decremented_input.items[i]:get_name() ~= linv:get_stack("recipe", i):get_name())
and not craft.decremented_input.items[i]:is_empty() then
local leftovers = me.insert_item(craft.decremented_input.items[i], net, inv, "main")
if leftovers:get_count() > 0 then
-- Ick, no room, just drop on the floor. Maybe player inventory?
minetest.add_item(pos, leftovers)
end
end
if replace then
linv:set_stack("output", 1, craft.output.item)
else
linv:set_list("output", {})
end
end
elseif listname == "recipe" then
local linv = minetest.get_meta(pos):get_inventory()
after_recipe_change(pos, linv)
elseif listname ~= "main" then
local net = me.get_connected_network(pos)
local inv = net:get_inventory()
me.remove_item(net, inv, "main", stack)
end
end,
on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
me.log("A move from "..from_list.." to "..to_list, "error")
if to_list == "recipe" or from_list == "recipe" then
local inv = minetest.get_meta(pos):get_inventory()
after_recipe_change(pos, inv)
end
end,
tube = {
can_insert = function(pos, _, stack) --pos, node, stack, direction
-- TODO: update to use capacity_cache?
local net = me.get_connected_network(pos)
local inv = net:get_inventory()
local max_slots = inv:get_size("main")
local max_items = net.capacity_cache
local slots, items = 0, 0
-- Get amount of items in drive
for i = 1, max_slots do
local dstack = inv:get_stack("main", i)
if dstack:get_name() ~= "" then
slots = slots + 1
local num = dstack:get_count()
if num == 0 then num = 1 end
items = items + num
end
end
items = items + stack:get_count()
return max_items > items
end,
insert_object = function(pos, _, stack)
local net = me.get_connected_network(pos)
local inv = net:get_inventory()
local leftovers = me.insert_item(stack, net, inv, "main")
net:set_storage_space(true)
return leftovers
end,
connect_sides = {left=1, right=1, front=1, back=1, top=1, bottom=1},
},
after_place_node = pipeworks_enabled and pipeworks.after_place,
after_dig_node = pipeworks_enabled and pipeworks.after_dig,
on_receive_fields = function(pos, _, fields, sender)
local net,cpos = me.get_connected_network(pos)
if net then
if cpos then
me.log("network and ctrl_pos","info")
else
me.log("network but no ctrl_pos","warning")
end
else
if cpos then
me.log("no network but ctrl_pos","warning")
else
me.log("no network and no ctrl_pos","info")
end
end
local meta = minetest.get_meta(pos)
local page = meta:get_int("page")
local inv_name = meta:get_string("inv_name")
local own_inv = meta:get_inventory()
local ctrl_inv
if cpos then
ctrl_inv = net:get_inventory()
else
me.log("no network connected","warning")
return
end
local inv
if inv_name == "main" then
inv = ctrl_inv
assert(inv,"no control inv")
else
inv = own_inv
assert(inv,"no own inv")
end
local page_max = math.floor(inv:get_size(inv_name) / 32) + 1
if inv_name == "none" then
return
end
if fields.next then
if page + 32 > inv:get_size(inv_name) then
return
end
meta:set_int("page", page + 32)
meta:set_string("formspec", chest_formspec(pos, page + 32, inv_name, page_max))
elseif fields.prev then
if page - 32 < 1 then
return
end
meta:set_int("page", page - 32)
meta:set_string("formspec", chest_formspec(pos, page - 32, inv_name, page_max))
elseif fields.search or fields.key_enter_field == "filter" then
own_inv:set_size("search", 0)
if fields.filter == "" then
meta:set_int("page", 1)
meta:set_string("inv_name", "main")
meta:set_string("formspec", chest_formspec(pos, 1, "main", page_max))
else
local tab = {}
for i = 1, ctrl_inv:get_size("main") do
local match = ctrl_inv:get_stack("main", i):get_name():find(fields.filter)
if match then
tab[#tab + 1] = ctrl_inv:get_stack("main", i)
end
end
own_inv:set_list("search", tab)
meta:set_int("page", 1)
meta:set_string("inv_name", "search")
meta:set_string("formspec", chest_formspec(pos, 1, "search", page_max, fields.filter))
end
elseif fields.clear then
own_inv:set_size("search", 0)
meta:set_int("page", 1)
meta:set_string("inv_name", "main")
meta:set_string("formspec", chest_formspec(pos, 1, "main", page_max))
elseif fields.tochest then
local pinv = minetest.get_inventory({type="player", name=sender:get_player_name()})
-- TODO: test and fix, net:set_storage_space(pinv:get_size("main"))
local space = net:get_item_capacity()
local contents = ctrl_inv:get_list("main") or {}
for _,s in pairs(contents) do
if not s:is_empty() then
space = space - s:get_count()
end
end
me.move_inv(net, { inv=pinv, name="main" }, { inv=ctrl_inv, name="main", huge=true }, space)
net:set_storage_space(true)
elseif fields.autocraft or fields.key_enter_field == "autocraft" then
if fields.autocraft ~= "" and tonumber(fields.autocraft) ~= nil then
local count = tonumber(fields.autocraft)
fields.autocraft = nil
if not own_inv:get_stack("output", 1):is_empty() and count < math.pow(2,16) then
me.autocraft(autocrafterCache, pos, net, own_inv, ctrl_inv, count)
end
end
end
end,
})

@ -0,0 +1,42 @@
--[[
Minetest Mod Storage Drawers - A Mod adding storage drawers
Copyright (C) 2017-2020 Linus Jahn <lnj@kaidan.im>
Copyright (C) 2016 Mango Tango <mtango688@gmail.com>
MIT License
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.
]]
function drawers.drawer_take_large_item(pos, itemstack)
local drawer_visuals = drawers.drawer_visuals[core.hash_node_position(pos)]
if not drawer_visuals then
return ItemStack("")
end
for _, visual in pairs(drawer_visuals) do
if visual.itemName == itemstack:get_name() then
return visual:take_items(itemstack:get_count())
end
end
return ItemStack()
end

@ -0,0 +1,39 @@
-- Interoperability file for drawers support.
local me = microexpansion
me.register_inventory("drawers:wood1", function(net, ctrl_inv, int_meta, n, pos, doinventories)
if not doinventories then return end
local c = drawers.drawer_get_content(n.pos, "")
if c.name ~= "" and c.count > 1 then
-- A poor man's locking system will have us never remove the last item from a drawer.
c.count = c.count-1
local stack = ItemStack(c.name)
local bias = nil
if c.count > math.pow(2,15) then -- assumes me.settings.huge_stacks == true
bias = c.count - math.pow(2,15)
c.count = math.pow(2,15)
end
stack:set_count(c.count)
net:create_loan(stack, {pos=n.pos, drawer=true, slot="", ipos=pos}, ctrl_inv, int_meta, bias)
end
-- local rest = drawers.drawer_insert_object(n.pos, ItemStack("default:stone"), "")
-- meta:set_int("count", meta:get_int("count")+1)
-- drawers.remove_visuals(n.pos)
-- drawers.spawn_visuals(n.pos)
end)
me.register_inventory("drawers:wood2", function(net, ctrl_inv, int_meta, n, pos, doinventories)
if not doinventories then return end
-- local c = drawers.drawer_get_content(n.pos, "")
-- local rest = drawers.drawer_insert_object(n.pos, ItemStack("default:stone"), "")
end)
me.register_inventory("drawers:wood4", function(net, ctrl_inv, int_meta, n, pos, doinventories)
if not doinventories then return end
-- local c = drawers.drawer_get_content(n.pos, "")
-- local rest = drawers.drawer_insert_object(n.pos, ItemStack("default:stone"), "")
end)
me.register_inventory("drawers:controller", function(net, ctrl_inv, int_meta, n, pos)
-- inv:add_item("src", ItemStack("default:stone"))
end)

@ -88,13 +88,13 @@ local function write_to_cell(cell, items, item_count)
return cell return cell
end end
local function write_drive_cells(pos,network) local function write_drive_cells(pos, net)
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
local own_inv = meta:get_inventory() local own_inv = meta:get_inventory()
if network == nil then if net == nil then
return false return false
end end
local ctrl_inv = network:get_inventory() local ctrl_inv = net:get_inventory()
local cells = {} local cells = {}
for i = 1, own_inv:get_size("main") do for i = 1, own_inv:get_size("main") do
local cell = own_inv:get_stack("main", i) local cell = own_inv:get_stack("main", i)
@ -111,17 +111,60 @@ local function write_drive_cells(pos,network)
local items_in_cell_count = 0 local items_in_cell_count = 0
local cell_items = {} local cell_items = {}
net:update_counts()
if not net.counts then
net.counts = {}
end
for i = 1, ctrl_inv:get_size("main") do for i = 1, ctrl_inv:get_size("main") do
local stack_inside = ctrl_inv:get_stack("main", i) local stack = ctrl_inv:get_stack("main", i)
local item_string = stack_inside:to_string() local item_string = stack:to_string()
if item_string ~= "" then if item_string ~= "" then
item_string = item_string:split(" ") item_string = item_string:split(" ")
local item_count = stack_inside:get_count() lbias = (net.counts and net.counts[stack:get_name()]) or 0
local mbias = (net.bias and net.bias["main"] and net.bias["main"][stack:get_name()]) or 0
if mbias > lbias and lbias > 0 then
mbias = mbias - lbias
lbias = 0
net.bias["main"][stack:get_name()] = mbias
net.counts[stack:get_name()] = nil
elseif lbias > mbias and mbias > 0 then
lbias = lbias - mbias
mbias = 0
net.counts[stack:get_name()] = lbias
net.bias["main"][stack:get_name()] = nil
elseif mbias == lbias and mbias > 0 then
net.bias["main"][stack:get_name()] = nil
net.counts[stack:get_name()] = nil
end
local item_count = stack:get_count() + mbias
-- TODO: on_loan includes the >32k bias in it, so, therefore the
-- stack:get_count() + mbias should have it in it.
local on_loan = net.counts[stack:get_name()] or 0
item_count = item_count - on_loan
if item_count < 0 then
me.log("LOAN: drive "..item_count.." "..stack:get_name().." "..on_loan.." on loan", "error")
-- TODO: we need to update the count faster and we need to update counts from actual inventories
-- and not allow taking unless there is an actual item there, mostly done now
-- TODO: In theory this should be impossible now, but we have
-- bugs with cell removal and insert, see wow
me.log("wow, free items "..stack:get_name().." during drive write, "..tostring(-item_count).." extra, with a loan of "..on_loan, "error")
item_count = 0
if mbias > 0 then
net.bias["main"][stack:get_name()] = nil
end
end
if item_count > 1 and item_string[2] ~= tostring(item_count) then if item_count > 1 and item_string[2] ~= tostring(item_count) then
microexpansion.log("stack count differs from second field of the item string","warning") me.log("stack count differs from second field of the item string","warning")
end end
while item_count ~= 0 and cell_idx ~= nil do while item_count ~= 0 and cell_idx ~= nil do
--print(("stack to store: %q"):format(table.concat(item_string," "))) --print(("stack to store: %q"):format(table.concat(item_string," ")))
-- TODO: This should fail if we write 64k items onto a 64k
-- drive (or larger). Fix by writting the number first, that's
-- the bias and then the name. This requires that no node in
-- the storage system starts with a number. Then in read,
-- support that.
if size < items_in_cell_count + item_count then if size < items_in_cell_count + item_count then
local space = size - items_in_cell_count local space = size - items_in_cell_count
item_string[2] = tostring(space) item_string[2] = tostring(space)
@ -132,7 +175,7 @@ local function write_drive_cells(pos,network)
cell_idx = next(cells, cell_idx) cell_idx = next(cells, cell_idx)
if cell_idx == nil then if cell_idx == nil then
--there may be other drives within the network --there may be other drives within the network
microexpansion.log("too many items to store in drive","info") me.log("too many items to store in drive","info")
break break
end end
size = microexpansion.get_cell_size(cells[cell_idx]:get_name()) size = microexpansion.get_cell_size(cells[cell_idx]:get_name())
@ -162,6 +205,7 @@ local function write_drive_cells(pos,network)
end end
local function take_all(pos,net) local function take_all(pos,net)
-- me.log("take_all", "error")
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
local own_inv = meta:get_inventory() local own_inv = meta:get_inventory()
local ctrl_inv = net:get_inventory() local ctrl_inv = net:get_inventory()
@ -178,8 +222,11 @@ local function take_all(pos,net)
end end
for _,ostack in pairs(items) do for _,ostack in pairs(items) do
--this returns 99 (max count) even if it removes more --this returns 99 (max count) even if it removes more
ctrl_inv:remove_item("main", ostack) --ctrl_inv:remove_item("main", ostack)
print(ostack) local postack = ItemStack(ostack)
-- me.log("drive take_all remove_item "..minetest.serialize(ostack), "error")
me.log("DRIVE: take_all remove_item "..tostring(postack:get_count()).." "..postack:get_name(), "error")
me.remove_item(net, ctrl_inv, "main", postack)
end end
net:update() net:update()
@ -187,6 +234,7 @@ local function take_all(pos,net)
end end
local function add_all(pos,net) local function add_all(pos,net)
-- me.log("add_all", "error")
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
local own_inv = meta:get_inventory() local own_inv = meta:get_inventory()
local ctrl_inv = net:get_inventory() local ctrl_inv = net:get_inventory()
@ -204,8 +252,7 @@ local function add_all(pos,net)
end end
end end
for _,ostack in pairs(items) do for _,ostack in pairs(items) do
me.insert_item(ostack, ctrl_inv, "main") me.insert_item(ostack, net, ctrl_inv, "main")
print(ostack)
end end
net:update() net:update()
@ -213,7 +260,7 @@ local function add_all(pos,net)
end end
function me.disconnect_drive(pos,ncpos) function me.disconnect_drive(pos,ncpos)
microexpansion.log("disconnecting drive at "..minetest.pos_to_string(pos),"action") me.log("disconnecting drive at "..minetest.pos_to_string(pos),"action")
local fc,i = get_drive_controller(pos) local fc,i = get_drive_controller(pos)
if not fc.cpos then if not fc.cpos then
return return
@ -228,7 +275,7 @@ function me.disconnect_drive(pos,ncpos)
if fnet then if fnet then
take_all(pos,fnet) take_all(pos,fnet)
else else
microexpansion.log("drive couldn't take items from its former network","warning") me.log("drive couldn't take items from its former network","warning")
end end
end end
@ -240,15 +287,15 @@ local function update_drive(pos,_,ev)
local cnet = ev.net or me.get_connected_network(pos) local cnet = ev.net or me.get_connected_network(pos)
if cnet then if cnet then
if not fc then if not fc then
microexpansion.log("connecting drive at "..minetest.pos_to_string(pos),"action") me.log("connecting drive at "..minetest.pos_to_string(pos), "action")
set_drive_controller(pos,true,cnet.controller_pos,i) set_drive_controller(pos,true,cnet.controller_pos,i)
add_all(pos,cnet) add_all(pos,cnet)
elseif not fc.cpos then elseif not fc.cpos then
microexpansion.log("connecting drive at "..minetest.pos_to_string(pos),"action") me.log("connecting drive at "..minetest.pos_to_string(pos), "action")
set_drive_controller(pos,false,cnet.controller_pos,i) set_drive_controller(pos,false,cnet.controller_pos,i)
add_all(pos,cnet) add_all(pos,cnet)
elseif not vector.equals(fc.cpos,cnet.controller_pos) then elseif not vector.equals(fc.cpos,cnet.controller_pos) then
microexpansion.log("reconnecting drive at "..minetest.pos_to_string(pos),"action") me.log("reconnecting drive at "..minetest.pos_to_string(pos), "action")
write_drive_cells(pos,me.get_network(fc.cpos)) write_drive_cells(pos,me.get_network(fc.cpos))
set_drive_controller(pos,false,cnet.controller_pos,i) set_drive_controller(pos,false,cnet.controller_pos,i)
add_all(pos,cnet) add_all(pos,cnet)
@ -258,13 +305,11 @@ local function update_drive(pos,_,ev)
me.disconnect_drive(pos,false) me.disconnect_drive(pos,false)
end end
end end
else elseif fc then
if fc then
if fc.cpos then if fc.cpos then
me.disconnect_drive(pos,false) me.disconnect_drive(pos,false)
end end
end end
end
end end
-- [me chest] Register node -- [me chest] Register node
@ -295,14 +340,14 @@ microexpansion.register_node("drive", {
on_construct = function(pos) on_construct = function(pos)
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
meta:set_string("formspec", meta:set_string("formspec",
"size[9,9.5]".. "size[9,7.5]"..
microexpansion.gui_bg .. microexpansion.gui_bg ..
microexpansion.gui_slots .. microexpansion.gui_slots ..
[[ [[
label[0,-0.23;ME Drive] label[0,-0.23;ME Drive]
list[context;main;0,0.3;8,4] list[context;main;0,0.3;5,2]
list[current_player;main;0,5.5;8,1;] list[current_player;main;0,3.5;8,1;]
list[current_player;main;0,6.73;8,3;8] list[current_player;main;0,4.73;8,3;8]
listring[current_name;main] listring[current_name;main]
listring[current_player;main] listring[current_player;main]
field_close_on_enter[filter;false] field_close_on_enter[filter;false]
@ -322,12 +367,12 @@ microexpansion.register_node("drive", {
after_destruct = function(pos) after_destruct = function(pos)
me.send_event(pos,"disconnect") me.send_event(pos,"disconnect")
end, end,
allow_metadata_inventory_put = function(_, _, _, stack) allow_metadata_inventory_put = function(pos, _, _, stack, player)
if minetest.is_protected(pos, player) or minetest.get_item_group(stack:get_name(), "microexpansion_cell") == 0 then if minetest.is_protected(pos, player)
or minetest.get_item_group(stack:get_name(), "microexpansion_cell") == 0 then
return 0 return 0
else
return 1
end end
return 1
end, end,
on_metadata_inventory_put = function(pos, _, _, stack) on_metadata_inventory_put = function(pos, _, _, stack)
me.send_event(pos,"item_cap") me.send_event(pos,"item_cap")
@ -342,10 +387,12 @@ microexpansion.register_node("drive", {
me.send_event(pos,"items",{net=network}) me.send_event(pos,"items",{net=network})
return return
end end
network:set_storage_space(#items) -- network:set_storage_space(#items)
for _,s in pairs(items) do for _,stack in pairs(items) do
me.insert_item(s, ctrl_inv, "main") network:set_storage_space(true)
me.insert_item(stack, network, ctrl_inv, "main")
end end
network:set_storage_space(true)
me.send_event(pos,"items",{net=network}) me.send_event(pos,"items",{net=network})
end, end,
allow_metadata_inventory_take = function(pos,_,_,stack, player) --args: pos, listname, index, stack, player allow_metadata_inventory_take = function(pos,_,_,stack, player) --args: pos, listname, index, stack, player
@ -369,8 +416,13 @@ microexpansion.register_node("drive", {
return return
end end
for _,ostack in pairs(items) do for _,ostack in pairs(items) do
local postack = ItemStack(ostack)
-- me.log("drive meta_inv_take remove_item "..tostring(postack:get_count()).." "..postack:get_name(), "error")
-- TODO: this was here, but did nothing? me.remove_item(network, ctrl_inv, "main", postack)
-- No, this is the main item removal on cell removal from drive
me.remove_item(network, ctrl_inv, "main", postack)
--this returns 99 (max count) even if it removes more --this returns 99 (max count) even if it removes more
ctrl_inv:remove_item("main", ostack) --ctrl_inv:remove_item("main", ostack)
end end
--print(stack:to_string()) --print(stack:to_string())

@ -13,3 +13,18 @@ dofile(module_path.."/storage.lua")
-- Load machines -- Load machines
dofile(module_path.."/drive.lua") dofile(module_path.."/drive.lua")
dofile(module_path.."/terminal.lua") dofile(module_path.."/terminal.lua")
dofile(module_path.."/cterminal.lua")
dofile(module_path.."/interface.lua")
local drawers_enabled = minetest.get_modpath("drawers") and true or false
if drawers_enabled then
dofile(module_path.."/drawer-api.lua") -- Extra Drawer api
dofile(module_path.."/drawer-interop.lua")
end
local technic_enabled = minetest.get_modpath("technic") and true or false
if technic_enabled then
dofile(module_path.."/technic-interop.lua")
end
local pipeworks_enabled = minetest.get_modpath("pipeworks") and true or false
if pipeworks_enabled then
dofile(module_path.."/pipeworks-interop.lua")
end

@ -0,0 +1,313 @@
-- interface
-- microexpansion/interface.lua
local me = microexpansion
local pipeworks_enabled = minetest.get_modpath("pipeworks") and true or false
-- Interfaces work by walking all connected blocks. We walk machines and inventories.
-- This explains which nodes are connected.
-- Missing from technic_plus, the generators, might be nice to be able
-- to feed them wood when the power is low.
-- Odd machines like the reactor or the force field aren't supported.
-- We'd have to figure out what we'd want to do with them.
local function can_connect(name)
if me.registered_inventory[name] then
return true
end
return false
end
function me.walk_connected(pos)
local nodes = {}
local visited = {}
visited[pos.x] = {}
visited[pos.x][pos.y] = {}
visited[pos.x][pos.y][pos.z] = true
local to_visit = {pos}
while #to_visit > 0 do
local pos = table.remove(to_visit)
local adjacent = {
{x=pos.x+1, y=pos.y, z=pos.z},
{x=pos.x-1, y=pos.y, z=pos.z},
{x=pos.x, y=pos.y+1, z=pos.z},
{x=pos.x, y=pos.y-1, z=pos.z},
{x=pos.x, y=pos.y, z=pos.z+1},
{x=pos.x, y=pos.y, z=pos.z-1},
}
for _,apos in pairs(adjacent) do
if not visited[apos.x] then
visited[apos.x] = {}
end
if not visited[apos.x][apos.y] then
visited[apos.x][apos.y] = {}
end
if visited[apos.x][apos.y][apos.z] ~= true then
visited[apos.x][apos.y][apos.z] = true
local napos = minetest.get_node(apos)
local nn = napos.name
if can_connect(nn) then
table.insert(nodes, {pos=apos, name=nn})
table.insert(to_visit, apos)
end
end
end
end
return nodes
end
local function update(pos,_,ev)
if ev.type == "connect" then
-- net.update_counts()
elseif ev.type == "disconnect" then
--
end
end
function me.reload_inventory(name, net, ctrl_inv, int_meta, n, pos, doinventories)
local func = me.registered_inventory and me.registered_inventory[name]
if func then
func(net, ctrl_inv, int_meta, n, pos, doinventories)
end
end
function me.chest_reload(net, ctrl_inv, int_meta, n, pos, doinventories)
if not doinventories then return end
local meta = minetest.get_meta(n.pos)
local inv = meta:get_inventory()
for i = 1, inv:get_size("main") do
local stack = inv:get_stack("main", i)
if not stack:is_empty() then
net:create_loan(stack, {pos=n.pos, invname="main", slot=i, ipos=pos}, ctrl_inv, int_meta)
end
end
end
me.register_inventory("default:chest", me.chest_reload)
-- This never rewalks connected machines. To do that add a gui
-- rewalk and/or remove, replace.
function me.reload_interface(net, pos, doinventories)
if not net then return end
local ctrl_inv = net:get_inventory()
local int_meta = minetest.get_meta(pos)
local inv = int_meta:get_inventory()
local inventories = minetest.deserialize(int_meta:get_string("connected"))
-- not appropriate
-- me.send_event(pos,"connect")
int_meta:set_string("infotext", "chests: "..#inventories)
if not net.counts then
net.counts = {}
end
if not net.autocrafters then
net.autocrafters = {}
end
if not net.autocrafters_by_pos then
net.autocrafters_by_pos = {}
end
if not net.process then
net.process = {}
end
for _, n in pairs(inventories) do
local node = minetest.get_node(n.pos)
local name = node.name
-- me.log("INT: found a "..name, "error")
local outputs = me.output_by_typename[me.block_to_typename_map[name]]
if outputs then
for _, name in pairs(outputs) do
if not net.process[name] then
net.process[name] = {}
end
-- me.log("INT: registering "..name.." for the "..node.name, "error")
net.process[name][n.pos] = pos
end
else
me.reload_inventory(name, net, ctrl_inv, int_meta, n, pos, doinventories)
end
end
-- me.send_event(pos,...
end
-- [me chest] Register node
me.register_node("interface", {
description = "ME Interface",
usedfor = "Interface for ME system",
tiles = {
"chest_top",
"chest_top",
"chest_side",
"chest_side",
"chest_side",
"chest_side", -- TODO: Maybe customize it?
},
recipe = {
{ 1, {
{"default:steel_ingot", "microexpansion:machine_casing", "default:steel_ingot" },
{"default:steel_ingot", "microexpansion:machine_casing", "default:steel_ingot" },
{"default:steel_ingot", "default:chest", "default:steel_ingot" },
},
}
},
is_ground_content = false,
groups = { cracky = 1, me_connect = 1, tubedevice = 1, tubedevice_receiver = 1 },
paramtype = "light",
paramtype2 = "facedir",
me_update = update,
on_construct = function(pos)
local int_meta = minetest.get_meta(pos)
int_meta:set_string("formspec",
"size[9,7.5]"..
microexpansion.gui_bg ..
microexpansion.gui_slots ..
[[
label[0,-0.23;ME Interface]
list[context;import;0,0.3;9,1]
list[context;export;0,0.3;9,1]
list[current_player;main;0,3.5;8,1;]
list[current_player;main;0,4.73;8,3;8]
listring[current_name;import]
listring[current_player;main]
field_close_on_enter[filter;false]
]])
local inv = int_meta:get_inventory()
inv:set_size("export", 3)
inv:set_size("import", 3)
local inventories = me.walk_connected(pos)
int_meta:set_string("connected", minetest.serialize(inventories))
local net = me.get_connected_network(pos)
if net == nil then
int_meta:set_string("infotext", "No Network")
return
end
me.reload_interface(net, pos, true)
end,
can_dig = function(pos)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return inv:is_empty("import")
end,
on_destruct = function(pos)
local net = me.get_connected_network(pos)
if net == nil then return end
local inv = net:get_inventory()
local int_meta = minetest.get_meta(pos)
local inventories = minetest.deserialize(int_meta:get_string("connected"))
int_meta:set_string("infotext", "")
local to_remove = {}
for _, n in pairs(inventories) do
local pos = n.pos
if not to_remove[pos.x] then
to_remove[pos.x] = {}
end
if not to_remove[pos.x][pos.y] then
to_remove[pos.x][pos.y] = {}
end
if not to_remove[pos.x][pos.y][pos.z] then
to_remove[pos.x][pos.y][pos.z] = true
end
end
net:update_counts()
local loan_slot = me.loan.get_size(net, inv)
local ref
while loan_slot > 0 do
local lstack = me.loan.get_stack(net, inv, loan_slot)
if lstack:is_empty() then
-- TODO: Don't think this can happen now, update_counts
-- me.log("interface empty loan at "..loan_slot, "error")
goto continue
foobar()
end
-- me.log("interface removing loan at "..loan_slot, "error")
ref = me.network.get_ref(lstack)
if ref and to_remove[ref.pos.x] and to_remove[ref.pos.x][ref.pos.y] and to_remove[ref.pos.x][ref.pos.y][ref.pos.z] then
net:remove_loan(ref.pos, inv, lstack, loan_slot, ref)
end
::continue::
loan_slot = loan_slot - 1
end
net:update_counts()
-- pos is a table and does not have value semantics.
if net.autocrafters_by_pos then
for k, v in pairs(net.autocrafters_by_pos) do
if k.x == pos.x and k.y == pos.y and k.z == pos.z then
pos = k
break
end
end
if net.autocrafters_by_pos[pos] then
for name, apos in pairs(net.autocrafters_by_pos[pos]) do
-- deindex these upon removal of the interface controlling them
net.autocrafters_by_pos[pos][name] = nil
net.autocrafters[name][apos] = nil
end
end
end
end,
after_destruct = function(pos)
me.send_event(pos,"disconnect")
end,
allow_metadata_inventory_put = function(pos, listname, index, stack)
return stack:get_count()
end,
on_metadata_inventory_put = function(pos, _, _, stack)
local net = me.get_connected_network(pos)
if net == nil then
return
end
local ctrl_inv = net:get_inventory()
end,
allow_metadata_inventory_take = function(pos,_,_,stack) --args: pos, listname, index, stack, player
local net = me.get_connected_network(pos)
return stack:get_count()
end,
on_metadata_inventory_take = function(pos, _, _, stack)
local net = me.get_connected_network(pos)
if net == nil then
return
end
local ctrl_inv = net:get_inventory()
end,
-- tube connection
tube = {
can_insert = function(pos, _, stack) --pos, node, stack, direction
local net = me.get_connected_network(pos)
local inv = net:get_inventory()
local max_slots = inv:get_size("main")
local max_items = net.capacity_cache
local slots, items = 0, 0
-- Get amount of items in drive
for i = 1, max_slots do
local mstack = inv:get_stack("main", i)
if mstack:get_name() ~= "" then
slots = slots + 1
local num = mstack:get_count()
if num == 0 then num = 1 end
items = items + num
end
end
items = items + stack:get_count()
return max_items > items
end,
insert_object = function(pos, _, stack)
local net = me.get_connected_network(pos)
local leftovers = stack
if net then
local inv = net:get_inventory()
leftovers = me.insert_item(stack, net, inv, "main")
net:set_storage_space(true)
end
return leftovers
end,
connect_sides = {left=1, right=1, front=1, back=1, top=1, bottom=1},
},
after_place_node = pipeworks_enabled and pipeworks.after_place,
after_dig_node = pipeworks_enabled and pipeworks.after_dig,
})

@ -0,0 +1,25 @@
-- Interoperability file for pipework support.
local me = microexpansion
me.register_inventory("pipeworks:autocrafter", function(net, ctrl_inv, int_meta, n, pos)
local meta = minetest.get_meta(n.pos)
local rinv = meta:get_inventory()
-- Autoinsert all the outputs
--for i = 1, rinv:get_size("dst")
-- local stack = rinv:get_stack("dst", i)
-- local leftovers = me.insert_item(stack, net, ctrl_inv, "main")
-- rinv:set_stack("dst", i, leftovers)
--end
-- register the crafted items so the autocrafter can use them
local craft = rinv:get_stack("output", 1)
if not craft:is_empty() then
if not net.autocrafters_by_pos[pos] then
net.autocrafters_by_pos[pos] = {}
end
net.autocrafters_by_pos[pos][craft:get_name()] = n.pos
if not net.autocrafters[craft:get_name()] then
net.autocrafters[craft:get_name()] = {}
end
net.autocrafters[craft:get_name()][n.pos] = pos
end
end)

@ -2,7 +2,7 @@
--TODO: use storagecomp for crafting --TODO: use storagecomp for crafting
-- [drive] 8k -- [drive] 1k
microexpansion.register_cell("cell_1k", { microexpansion.register_cell("cell_1k", {
description = "1k ME Storage Cell", description = "1k ME Storage Cell",
capacity = 1000, capacity = 1000,
@ -16,7 +16,7 @@ microexpansion.register_cell("cell_1k", {
}, },
}) })
-- [drive] 8k -- [drive] 2k
microexpansion.register_cell("cell_2k", { microexpansion.register_cell("cell_2k", {
description = "2k ME Storage Cell", description = "2k ME Storage Cell",
capacity = 2000, capacity = 2000,
@ -31,7 +31,7 @@ microexpansion.register_cell("cell_2k", {
}, },
}) })
-- [drive] 16k -- [drive] 4k
microexpansion.register_cell("cell_4k", { microexpansion.register_cell("cell_4k", {
description = "4k ME Storage Cell", description = "4k ME Storage Cell",
capacity = 4000, capacity = 4000,
@ -44,7 +44,7 @@ microexpansion.register_cell("cell_4k", {
}, },
}) })
-- [drive] 16k -- [drive] 8k
microexpansion.register_cell("cell_8k", { microexpansion.register_cell("cell_8k", {
description = "8k ME Storage Cell", description = "8k ME Storage Cell",
capacity = 8000, capacity = 8000,
@ -53,7 +53,7 @@ microexpansion.register_cell("cell_8k", {
}, },
}) })
-- [drive] 32k -- [drive] 16k
microexpansion.register_cell("cell_16k", { microexpansion.register_cell("cell_16k", {
description = "16k ME Storage Cell", description = "16k ME Storage Cell",
capacity = 16000, capacity = 16000,

@ -0,0 +1,234 @@
-- Interoperability file for technic and technic_plus support.
local me = microexpansion
-- technic_plus doesn't export machine speed, so wire it in here. We
-- use this to know exactly how long a machine will take to process
-- anything, after that time, we know it is done and we can grab the
-- outputs, no polling. We do this for efficiency.
-- The speeds of the various machines:
me.set_speed("technic:electric_furnace", 2)
me.set_speed("technic:mv_electric_furnace", 4)
me.set_speed("technic:hv_electric_furnace", 12)
me.set_speed("technic:lv_alloy_furnace", 1)
me.set_speed("technic:mv_alloy_furnace", 1.5)
me.set_speed("technic:lv_compressor", 1)
me.set_speed("technic:mv_compressor", 2)
me.set_speed("technic:hv_compressor", 5)
me.set_speed("technic:lv_extractor", 1)
me.set_speed("technic:mv_extractor", 2)
me.set_speed("technic:lv_grinder", 1)
me.set_speed("technic:mv_grinder", 2)
me.set_speed("technic:hv_grinder", 5)
me.set_speed("technic:mv_centrifuge", 2)
me.set_speed("technic:mv_freezer", 0.5)
-- ======================================================================== --
-- Register maximal output sizes for all the ingredients we produce.
-- We also break up deeply recursive crafts that would blow a pipeworks
-- autocrafter if it tried to make something.
-- might not be necessary, but maybe it is. It might limit
-- oversupply of inputs to batteries.
me.register_max("technic:lv_battery_box0", 3)
me.register_max("technic:battery", 12)
-- HV furnace only has 4 output slots
me.register_max("technic:cast_iron_ingot", 20)
me.register_max("mesecons_materials:glue", 380)
me.register_max("mesecons_materials:fiber", 380)
me.register_max("default:stone", 20)
me.register_max("basic_materials:plastic_sheet", 380)
me.register_max("basic_materials:paraffin", 380)
-- HV grinder only has 4 output slots
me.register_max("technic:coal_dust", 380)
me.register_max("technic:gold_dust", 380)
me.register_max("technic:sulfur_dust", 380)
me.register_max("technic:stone_dust", 380)
me.register_max("default:gravel", 380)
me.register_max("default:sand", 380)
me.register_max("default:snowblock", 380)
me.register_max("technic:rubber_tree_grindings", 380)
-- MV alloy furnace only has 4 output slots
me.register_max("technic:doped_silicon_wafer", 380)
me.register_max("technic:silicon_wafer", 380)
me.register_max("basic_materials:brass_ingot", 380)
me.register_max("default:bronze_ingot", 380)
me.register_max("technic:stainless_steel_ingot", 380)
me.register_max("technic:rubber", 380)
me.register_max("bucket:bucket_lava", 4)
me.register_max("technic:carbon_steel_ingot", 380)
-- LV extractor only has 4 output slots
me.register_max("technic:raw_latex", 380)
-- HV compressor only has 4 output slots
me.register_max("technic:composite_plate", 380)
me.register_max("technic:copper_plate", 380)
me.register_max("technic:graphite", 380)
me.register_max("technic:carbon_plate", 380)
me.register_max("technic:uranium_fuel", 380)
me.register_max("default:diamond", 380)
-- freezer only has 4 output slots
me.register_max("default:ice", 380)
-- ======================================================================== --
-- The type of machines all the machines are: We have to list these
-- before me.register_inventory.
me.register_typename("technic:electric_furnace", "cooking")
me.register_typename("technic:mv_electric_furnace", "cooking")
me.register_typename("technic:hv_electric_furnace", "cooking")
me.register_typename("technic:lv_grinder", "grinding")
me.register_typename("technic:mv_grinder", "grinding")
me.register_typename("technic:hv_grinder", "grinding")
me.register_typename("technic:coal_alloy_furnace", "alloy")
me.register_typename("technic:lv_alloy_furnace", "alloy")
me.register_typename("technic:mv_alloy_furnace", "alloy")
me.register_typename("technic:lv_extractor", "extracting")
me.register_typename("technic:mv_extractor", "extracting")
me.register_typename("technic:lv_compressor", "compressing")
me.register_typename("technic:mv_compressor", "compressing")
me.register_typename("technic:hv_compressor", "compressing")
me.register_typename("technic:mv_centrifuge", "separating")
me.register_typename("technic:mv_freezer", "freezing")
-- ======================================================================== --
-- The various blocks and how to interface to them:
me.register_inventory("technic:gold_chest", me.chest_reload)
me.register_inventory("technic:mithril_chest", me.chest_reload)
me.register_inventory("technic:quarry", function(net, ctrl_inv, int_meta, n, pos)
local meta = minetest.get_meta(n.pos)
local rinv = meta:get_inventory()
for i = 1, rinv:get_size("cache") do
local stack = rinv:get_stack("cache", i)
if not stack:is_empty() then
local leftovers = me.insert_item(stack, net, ctrl_inv, "main")
rinv:set_stack("cache", i, leftovers)
end
end
-- we can set up a timer to recheck the cache every 30 seconds and
-- clean it out for example.
end)
me.register_inventory("technic:mv_centrifuge", function(net, ctrl_inv, int_meta, n, pos)
local meta = minetest.get_meta(n.pos)
local rinv = meta:get_inventory()
for i = 1, rinv:get_size("dst") do
local stack = rinv:get_stack("dst", i)
if not stack:is_empty() then
local leftovers = me.insert_item(stack, net, ctrl_inv, "main")
rinv:set_stack("dst", i, leftovers)
end
end
for i = 1, rinv:get_size("dst2") do
local stack = rinv:get_stack("dst2", i)
if not stack:is_empty() then
local leftovers = me.insert_item(stack, net, ctrl_inv, "main")
rinv:set_stack("dst2", i, leftovers)
end
end
end)
-- ======================================================================== --
-- The various outputs the various machine types can generate:
me.register_output_by_typename("cooking", "technic:cast_iron_ingot")
me.register_output_by_typename("cooking", "mesecons_materials:glue")
me.register_output_by_typename("cooking", "mesecons_materials:fiber")
me.register_output_by_typename("cooking", "basic_materials:plastic_sheet")
me.register_output_by_typename("cooking", "basic_materials:paraffin")
me.register_output_by_typename("grinding", "technic:coal_dust")
me.register_output_by_typename("grinding", "technic:gold_dust")
me.register_output_by_typename("grinding", "technic:sulfur_dust")
me.register_output_by_typename("grinding", "technic:stone_dust")
me.register_output_by_typename("grinding", "default:gravel")
me.register_output_by_typename("grinding", "default:sand")
me.register_output_by_typename("grinding", "default:snowblock")
me.register_output_by_typename("grinding", "technic:rubber_tree_grindings")
me.register_output_by_typename("alloy", "technic:doped_silicon_wafer")
me.register_output_by_typename("alloy", "technic:silicon_wafer")
me.register_output_by_typename("alloy", "basic_materials:brass_ingot")
me.register_output_by_typename("alloy", "default:bronze_ingot")
me.register_output_by_typename("alloy", "technic:stainless_steel_ingot")
me.register_output_by_typename("alloy", "technic:rubber")
me.register_output_by_typename("alloy", "bucket:bucket_lava")
me.register_output_by_typename("alloy", "technic:carbon_steel_ingot")
me.register_output_by_typename("extracting", "technic:raw_latex")
me.register_output_by_typename("compressing", "technic:composite_plate")
me.register_output_by_typename("compressing", "technic:copper_plate")
me.register_output_by_typename("compressing", "technic:graphite")
me.register_output_by_typename("compressing", "technic:carbon_plate")
me.register_output_by_typename("compressing", "technic:uranium_fuel")
me.register_output_by_typename("compressing", "default:diamond")
-- Any of these worth doing? TODO: Uranium, sure.
--me.register_output_by_typename("separating", "")
me.register_output_by_typename("freezing", "default:ice")
-- ======================================================================== --
-- The inputs required for the given output. The inputs are exact count, the output it just
-- for 1. We'll figure out how many are actually produced later. For multiple outputs
-- only list the more interesting one.
-- furnace ("cooking")
me.register_output_to_inputs("technic:cast_iron_ingot", { ItemStack("default:steel_ingot") })
me.register_output_to_inputs("mesecons_materials:glue", { ItemStack("technic:raw_latex") })
me.register_output_to_inputs("mesecons_materials:fiber", { ItemStack("mesecons_materials:glue") })
me.register_output_to_inputs("basic_materials:plastic_sheet", { ItemStack("basic_materials:paraffin") })
me.register_output_to_inputs("basic_materials:paraffin", { ItemStack("basic_materials:oil_extract") })
-- grinder ("grinding")
me.register_output_to_inputs("technic:coal_dust", { ItemStack("default:coal_lump") })
me.register_output_to_inputs("technic:gold_dust", { ItemStack("default:gold_lump") })
me.register_output_to_inputs("technic:sulfur_dust", { ItemStack("technic:sulfur_lump") })
me.register_output_to_inputs("technic:stone_dust", { ItemStack("default:stone") })
me.register_output_to_inputs("default:gravel", { ItemStack("default:cobble") })
me.register_output_to_inputs("default:sand", { ItemStack("default:gravel") })
me.register_output_to_inputs("default:snowblock", { ItemStack("default:ice") })
-- TODO: Something about this doesn't work: recheck, think it is fixed now
me.register_output_to_inputs("technic:rubber_tree_grindings", { ItemStack("moretrees:rubber_tree_trunk") })
-- alloy_furnace ("alloy")
-- The most useful alloy recipes. We don't do the less useful ones as we don't yet have
-- a way for the user to say, no, don't do that.
me.register_output_to_inputs("technic:doped_silicon_wafer", { ItemStack("technic:gold_dust"), ItemStack("technic:silicon_wafer") })
me.register_output_to_inputs("technic:silicon_wafer", { ItemStack("default:sand 2"), ItemStack("technic:coal_dust 2") })
me.register_output_to_inputs("basic_materials:brass_ingot", { ItemStack("default:copper_ingot 2"), ItemStack("technic:zinc_ingot") })
me.register_output_to_inputs("default:bronze_ingot", { ItemStack("default:copper_ingot 7"), ItemStack("default:tin_ingot") })
me.register_output_to_inputs("technic:stainless_steel_ingot", { ItemStack("technic:carbon_steel_ingot 4"), ItemStack("technic:chromium_ingot") })
me.register_output_to_inputs("technic:rubber", { ItemStack("technic:raw_latex 4"), ItemStack("technic:coal_dust 2") })
me.register_output_to_inputs("bucket:bucket_lava", { ItemStack("default:obsidian"), ItemStack("bucket:bucket_empty") })
me.register_output_to_inputs("technic:carbon_steel_ingot", { ItemStack("default:steel_ingot 2"), ItemStack("technic:coal_dust") })
-- extractor ("extracting")
me.register_output_to_inputs("technic:raw_latex", { ItemStack("technic:rubber_tree_grindings 4") })
-- compressor ("compressing")
me.register_output_to_inputs("technic:composite_plate", { ItemStack("technic:mixed_metal_ingot") })
me.register_output_to_inputs("technic:copper_plate", { ItemStack("default:copper_ingot 5") })
me.register_output_to_inputs("technic:graphite", { ItemStack("technic:coal_dust 4") })
me.register_output_to_inputs("technic:carbon_plate", { ItemStack("technic:carbon_cloth") })
me.register_output_to_inputs("technic:uranium_fuel", { ItemStack("technic:uranium35_ingot 5") })
me.register_output_to_inputs("default:diamond", { ItemStack("technic:graphite 25") })
-- centrifuge ("separating")
-- freezer ("freezing")
me.register_output_to_inputs("default:ice", { ItemStack("bucket:bucket_water") })

@ -1,3 +1,4 @@
-- terminal
-- microexpansion/machines.lua -- microexpansion/machines.lua
local me = microexpansion local me = microexpansion
@ -20,9 +21,17 @@ local function chest_formspec(pos, start_id, listname, page_max, q)
else else
list = "list[context;" .. listname .. ";0,0.3;8,4;" .. (start_id - 1) .. "]" list = "list[context;" .. listname .. ";0,0.3;8,4;" .. (start_id - 1) .. "]"
end end
if minetest.get_modpath("i3") then
list = list .. [[
list[current_player;main;0,5.5;9,4;]
]]
else
list = list .. [[ list = list .. [[
list[current_player;main;0,5.5;8,1;] list[current_player;main;0,5.5;8,1;]
list[current_player;main;0,6.73;8,3;8] list[current_player;main;0,6.73;8,3;8]
]]
end
list = list .. [[
listring[detached:]]..ctrlinvname..[[;main] listring[detached:]]..ctrlinvname..[[;main]
listring[current_player;main] listring[current_player;main]
]] ]]
@ -40,7 +49,7 @@ local function chest_formspec(pos, start_id, listname, page_max, q)
tooltip[clear;Reset] tooltip[clear;Reset]
]] ]]
else else
list = "label[3,2;" .. minetest.colorize("red", "No connected drives!") .. "]" list = "label[3,2;" .. minetest.colorize("red", "No connected storage!") .. "]"
end end
else else
list = "label[3,2;" .. minetest.colorize("red", "No connected network!") .. "]" list = "label[3,2;" .. minetest.colorize("red", "No connected network!") .. "]"
@ -82,7 +91,7 @@ local function update_chest(pos,_,ev)
end end
-- [me chest] Register node -- [me chest] Register node
microexpansion.register_node("term", { me.register_node("term", {
description = "ME Terminal", description = "ME Terminal",
usedfor = "Can interact with storage cells in ME networks", usedfor = "Can interact with storage cells in ME networks",
tiles = { tiles = {
@ -124,15 +133,20 @@ microexpansion.register_node("term", {
after_destruct = function(pos) after_destruct = function(pos)
me.send_event(pos,"disconnect") me.send_event(pos,"disconnect")
end, end,
allow_metadata_inventory_put = function(pos, listname, index, stack, player)
-- TODO: Check capacity, only allow if room
return stack:get_count()
end,
on_metadata_inventory_put = function(pos, listname, _, stack) on_metadata_inventory_put = function(pos, listname, _, stack)
local net = me.get_connected_network(pos) local net = me.get_connected_network(pos)
local inv = net:get_inventory() local inv = net:get_inventory()
me.insert_item(stack, inv, "main") me.insert_item(stack, net, inv, "main")
net:set_storage_space(true)
end, end,
on_metadata_inventory_take = function(pos, listname, _, stack) on_metadata_inventory_take = function(pos, listname, _, stack)
local net = me.get_connected_network(pos) local net = me.get_connected_network(pos)
local inv = net:get_inventory() local inv = net:get_inventory()
inv:remove_item("main", stack) me.remove_item(net, inv, "main", stack)
end, end,
tube = { tube = {
can_insert = function(pos, _, stack) --pos, node, stack, direction can_insert = function(pos, _, stack) --pos, node, stack, direction
@ -158,10 +172,9 @@ microexpansion.register_node("term", {
insert_object = function(pos, _, stack) insert_object = function(pos, _, stack)
local net = me.get_connected_network(pos) local net = me.get_connected_network(pos)
local inv = net:get_inventory() local inv = net:get_inventory()
me.insert_item(stack, inv, "main") local leftovers = me.insert_item(stack, net, inv, "main")
net:set_storage_space(true) net:set_storage_space(true)
--TODO: leftover return leftovers
return ItemStack()
end, end,
connect_sides = {left=1, right=1, front=1, back=1, top=1, bottom=1}, connect_sides = {left=1, right=1, front=1, back=1, top=1, bottom=1},
}, },
@ -171,15 +184,15 @@ microexpansion.register_node("term", {
local net,cp = me.get_connected_network(pos) local net,cp = me.get_connected_network(pos)
if net then if net then
if cp then if cp then
microexpansion.log("network and ctrl_pos","info") me.log("network and ctrl_pos","info")
else else
microexpansion.log("network but no ctrl_pos","warning") me.log("network but no ctrl_pos","warning")
end end
else else
if cp then if cp then
microexpansion.log("no network but ctrl_pos","warning") me.log("no network but ctrl_pos","warning")
else else
microexpansion.log("no network and no ctrl_pos","info") me.log("no network and no ctrl_pos","info")
end end
end end
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
@ -190,7 +203,7 @@ microexpansion.register_node("term", {
if cp then if cp then
ctrl_inv = net:get_inventory() ctrl_inv = net:get_inventory()
else else
microexpansion.log("no network connected","warning") me.log("no network connected","warning")
return return
end end
local inv local inv
@ -243,7 +256,7 @@ microexpansion.register_node("term", {
meta:set_string("formspec", chest_formspec(pos, 1, "main", page_max)) meta:set_string("formspec", chest_formspec(pos, 1, "main", page_max))
elseif fields.tochest then elseif fields.tochest then
local pinv = minetest.get_inventory({type="player", name=sender:get_player_name()}) local pinv = minetest.get_inventory({type="player", name=sender:get_player_name()})
net:set_storage_space(pinv:get_size("main")) -- TODO: test and fix, net:set_storage_space(pinv:get_size("main"))
local space = net:get_item_capacity() local space = net:get_item_capacity()
local contents = ctrl_inv:get_list("main") or {} local contents = ctrl_inv:get_list("main") or {}
for _,s in pairs(contents) do for _,s in pairs(contents) do
@ -251,7 +264,7 @@ microexpansion.register_node("term", {
space = space - s:get_count() space = space - s:get_count()
end end
end end
microexpansion.move_inv({ inv=pinv, name="main" }, { inv=ctrl_inv, name="main",huge=true }, space) me.move_inv(net, { inv=pinv, name="main" }, { inv=ctrl_inv, name="main", huge=true }, space)
net:set_storage_space(true) net:set_storage_space(true)
end end
end, end,