microexpansion/modules/network/init.lua
2023-12-30 15:52:43 -08:00

498 lines
18 KiB
Lua

local me = microexpansion
me.networks = {}
local networks = me.networks
local path = me.get_module_path("network")
--deprecated: use ItemStack(x) instead
--[[
local function split_stack_values(stack)
if type(stack) == "string" then
local split_string = stack:split(" ")
if (#split_string < 1) then
return "",0,0,nil
end
local stack_name = split_string[1]
if (#split_string < 2) then
return stack_name,1,0,nil
end
local stack_count = tonumber(split_string[2])
if (#split_string < 3) then
return stack_name,stack_count,0,nil
end
local stack_wear = tonumber(split_string[3])
if (#split_string < 4) then
return stack_name,stack_count,stack_wear,nil
end
return stack_name,stack_count,stack_wear,true
else
return stack:get_name(), stack:get_count(), stack:get_wear(), stack:get_meta()
end
end
--]]
local annotate_large_stack = function(stack, count)
local description = minetest.registered_items[stack:get_name()]
if description then
-- steel is an alias and won't be found in here, skip it
description = description.description
--stack:set_count(1)
--This screw up everything, autocrafting, item removal and more
--stack:get_meta():set_string("description", description.." "..count)
stack:get_meta():set_string("description", "")
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.."/autocraft.lua") -- Autocrafting
-- generate iterator to find all connected nodes
function me.connected_nodes(start_pos,include_ctrl)
-- nodes to be checked
local open_list = {{pos = start_pos}}
-- nodes that were checked
local closed_set = {}
-- local connected nodes function to reduce table lookups
local adjacent_connected_nodes = me.network.adjacent_connected_nodes
-- return the generated iterator
return function ()
-- start looking for next pos
local open = false
-- pos to be checked
local current
-- find next unclosed
while not open do
-- get unchecked pos
current = table.remove(open_list)
-- none are left
if current == nil then return end
-- assume it's open
open = true
-- check the closed positions
for _,closed in pairs(closed_set) do
-- if current is unclosed
if vector.equals(closed,current.pos) then
--found one was closed
open = false
end
end
end
-- get all connected nodes
local nodes = adjacent_connected_nodes(current.pos,include_ctrl)
-- iterate through them
for _,n in pairs(nodes) do
-- mark position to be checked
table.insert(open_list,n)
end
-- add this one to the closed set
table.insert(closed_set,current.pos)
-- return the one to be checked
return current.pos,current.name
end
end
-- get network connected to position
function me.get_connected_network(start_pos)
for npos,nn in me.connected_nodes(start_pos,true) do
if nn == "microexpansion:ctrl" then
local net = me.get_network(npos)
if net then
return net,npos
end
end
end
end
function me.update_connected_machines(start_pos,event,include_start)
me.log("updating connected machines", "action")
local ev = event or {type = "n/a"}
local sn = me.get_node(start_pos)
local sd = minetest.registered_nodes[sn.name]
local sm = sd.machine or {}
ev.origin = {
pos = start_pos,
name = sn.name,
type = sm.type
}
--print(dump2(ev,"event"))
for npos in me.connected_nodes(start_pos) do
if include_start or not vector.equals(npos,start_pos) then
me.update_node(npos,ev)
end
end
end
function me.send_event(spos,type,data)
local d = data or {}
local event = {
type = type,
net = d.net,
payload = d.payload
}
me.update_connected_machines(spos,event,false)
end
function me.get_network(pos)
for i,net in pairs(networks) do
if net.controller_pos then
if vector.equals(pos, net.controller_pos) then
return net,i
end
end
end
end
dofile(path.."/ctrl.lua") -- Controller/wires
-- load networks
function me.load()
local f = io.open(me.worldpath.."/microexpansion_networks", "r")
if f then
local res = minetest.deserialize(f:read("*all"))
f:close()
if type(res) == "table" then
for _,n in pairs(res) do
local net = me.network.new(n)
net:load()
table.insert(me.networks,net)
end
end
end
end
-- load now
me.load()
-- save networks
function me.save()
local data = {}
for _,net in pairs(me.networks) do
-- 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
local f = io.open(me.worldpath.."/microexpansion_networks", "w")
f:write(minetest.serialize(data))
f:close()
end
-- save on server shutdown
minetest.register_on_shutdown(me.save)