minetest_bitumen/pipes.lua

670 lines
17 KiB
Lua

local networks = {}
local net_members = {}
local netname = 1
bitumen.mod_storage = minetest.get_mod_storage()
local mod_storage = bitumen.mod_storage
networks = minetest.deserialize(mod_storage:get_string("networks")) or {}
net_members = minetest.deserialize(mod_storage:get_string("net_members")) or {}
netname = mod_storage:get_int("netname") or 1
local function save_data()
--print("saving")
mod_storage:set_string("networks", minetest.serialize(networks))
mod_storage:set_string("net_members", minetest.serialize(net_members))
mod_storage:set_int("netname", netname)
end
-- centralized network creation for consistency
local function new_network(pos)
local hash = minetest.hash_node_position(pos)
print("new network: hash: ".. hash .." name: " ..netname);
networks[hash] = {
hash = hash,
pos = {x=pos.x, y=pos.y, z=pos.z},
fluid = 'air',
name = netname,
count = 1,
inputs = {
[hash] = 1,
},
outputs = {},
buffer = 0,
in_pressure = -32000,
}
net_members[hash] = hash
netname = netname + 1
return networks[hash], hash
end
-- check nearby nodes for existing networks
local function check_merge(pos)
local hash = minetest.hash_node_position(pos)
local merge_list = {}
local current_net = nil
local found_net = 0
local check_net = function(npos)
local nhash = minetest.hash_node_position(npos)
local nphash = net_members[nhash]
if nphash ~= nil then
local pnet = networks[nphash]
if nil == current_net then
print("joining existing network: ".. pnet.name)
net_members[hash] = nphash
current_net = nphash
pnet.count = pnet.count + 1
pnet.inputs[hash] = 1
table.insert(merge_list, pnet)
elseif current_net == nphash then
print("alternate connection to existing network")
else
print("found seconday network: "..pnet.name)
table.insert(merge_list, pnet)
end
found_net = 1
end
end
check_net({x=pos.x, y=pos.y - 1, z=pos.z})
check_net({x=pos.x, y=pos.y + 1, z=pos.z})
check_net({x=pos.x + 1, y=pos.y, z=pos.z})
check_net({x=pos.x - 1, y=pos.y, z=pos.z})
check_net({x=pos.x, y=pos.y, z=pos.z + 1})
check_net({x=pos.x, y=pos.y, z=pos.z - 1})
return found_net, merge_list
end
-- merge a list of networks, if the are multiple nets in the list
local function try_merge(merge_list)
if #merge_list > 1 then
print("\n merging "..#merge_list.." networks")
local biggest = {count = 0}
local mlookup = {}
for _,n in ipairs(merge_list) do
mlookup[n.hash] = 1
if n.count > biggest.count then
biggest = n
end
end
mlookup[biggest.hash] = 0
for k,v in pairs(net_members) do
if mlookup[v] == 1 then
net_members[k] = biggest.hash
end
end
for _,n in ipairs(merge_list) do
if n.hash ~= biggest.hash then
biggest.count = biggest.count + n.count
networks[n.hash] = nil -- delete old networks
end
end
return biggest
end
return merge_list[1]
end
local function pnet_for(pos)
local hash = minetest.hash_node_position(pos)
local ph = net_members[hash]
if ph == nil then
return nil, hash
end
return networks[ph], hash
end
local function walk_net(opos)
local members = {}
local count = 0
local opnet = pnet_for(opos)
if opnet == nil then
return nil, 0, nil
end
local stack = {}
table.insert(stack, opos)
while #stack > 0 do
local pos = table.remove(stack)
local pnet, hash = pnet_for(pos)
if pnet ~= nil and members[hash] == nil then
-- only count members of the original node's network
if pnet.name == opnet.name then
members[hash] = pos
count = count + 1
-- print(" found net node: " .. minetest.pos_to_string(pos))
table.insert(stack, {x=pos.x-1, y=pos.y, z=pos.z})
table.insert(stack, {x=pos.x+1, y=pos.y, z=pos.z})
table.insert(stack, {x=pos.x, y=pos.y-1, z=pos.z})
table.insert(stack, {x=pos.x, y=pos.y+1, z=pos.z})
table.insert(stack, {x=pos.x, y=pos.y, z=pos.z-1})
table.insert(stack, {x=pos.x, y=pos.y, z=pos.z+1})
end
end
end
return members, count, opnet
end
-- crawls the network and assigns found nodes to the new network
local function rebase_network(pos)
local net, phash, hash = bitumen.pipes.get_net(pos)
-- print(dump(pos))
if hash == phash then
print("trying to rebase network to the same spot")
return {name = 9999999}
end
local pipes = walk_net(pos)
-- print(dump(pipes))
local new_net, new_phash = new_network(pos)
-- switch all the members
for h,p in pairs(pipes) do
--print("reassigning " .. minetest.pos_to_string(p))
net_members[h] = new_phash
new_net.count = new_net.count + 1
end
return new_net, new_phash
end
bitumen.pipes = {}
-- used by external machines to connect to a network in their on_construct callback
bitumen.pipes.on_construct = function(pos)
local found_net, merge_list = check_merge(pos)
if found_net == 0 then
local hash = minetest.hash_node_position(pos)
local net = new_network(pos)
end
local pnet = try_merge(merge_list)
--pnet.count = pnet.count + 1 -- TODO: this might be implicit somewhere else
save_data()
end
bitumen.pipes.after_destruct = function(pos)
local hash = minetest.hash_node_position(pos)
local phash = net_members[hash]
if phash == nil then
print("wtf: pipe has no network in after_destruct")
return
end
local pnet = networks[phash]
if pnet == nil then
print("wtf: no network in after_destruct for pipe")
return
end
-- remove this node from the network
net_members[hash] = nil
pnet.count = pnet.count - 1
-- neighboring nodes
local check_pos = {
{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},
}
local stack = {}
local found = 0
-- check neighbors for network membership
for _,p in ipairs(check_pos) do
local h = minetest.hash_node_position(p)
local lphash = net_members[h]
if lphash ~= nil then
local lpnet = networks[lphash]
-- only process pipes/fixtures on the same network as the destroyed pipe
if lpnet and lpnet.name == pnet.name then
stack[h] = vector.new(p)
found = found + 1
--print("check stack: "..p.x..","..p.y..","..p.z)
else
print("no lpnet")
end
else
print("no lphash "..p.x..","..p.y..","..p.z)
end
end
-- don't need to split the network if this was just on the end
if found > 1 then
--print("check to split the network")
for h,p in pairs(stack) do
print(dump(p))
print(dump(h))
-- BUG: spouts and intakes can be counted as pipes when walking the network
-- just rename the net
local new_pnet = rebase_network(p)
-- print("split off pnet ".. new_pnet.name .. " at " .. minetest.pos_to_string(p))
-- all fluid is lost in the network atm
-- some networks might get orphaned, for example, the first
-- net to be rebased in a loop
end
end
save_data()
end
-- used by external machines to find the network for a node
bitumen.pipes.get_net = function(pos)
local hash = minetest.hash_node_position(pos)
local phash = net_members[hash]
if phash == nil then
return nil, nil, hash
end
return networks[phash], phash, hash
end
-- used by external machines to add fluid into the pipe network
-- returns amount of fluid successfully pushed into the network
bitumen.pipes.push_fluid = function(pos, fluid, amount, extra_pressure)
local hash = minetest.hash_node_position(pos)
local phash = net_members[hash]
if phash == nil then
--print("no network to push to")
return 0 -- no network
end
local pnet = networks[phash]
--print("fluid: "..pnet.fluid.. ", buf: "..pnet.buffer)
if pnet.fluid == 'air' or pnet.buffer == 0 then
if minetest.registered_nodes[fluid]
and minetest.registered_nodes[fluid].groups.petroleum ~= nil then
-- BUG: check for "full" nodes
pnet.fluid = fluid
else
--print("here "..fluid.." ".. dump(minetest.registered_nodes[fluid]))
return 0 -- no available liquids
end
else -- only suck in existing fluid
if fluid ~= pnet.fluid and fluid ~= pnet.fluid.."_full" then
--print("wrong fluid")
return 0
end
end
if amount < 0 then
print("!!!!!!!!!!!! push amount less than zero?")
return 0
end
local input_pres = math.floor(pos.y + extra_pressure + .5)
pnet.in_pressure = pnet.in_pressure or -32000
if math.floor(pnet.in_pressure + .5) > input_pres then
--print("backflow at intake: " .. pnet.in_pressure.. " > " ..input_pres )
return 0
end
pnet.in_pressure = math.max(pnet.in_pressure, input_pres)
--print("net pressure: ".. pnet.in_pressure)
local rate = amount --math.max(1, math.ceil(ulevel / 2))
local cap = 64
local take = math.max(0, math.min(amount, cap - pnet.buffer))
pnet.buffer = pnet.buffer + take
return take
end
-- used by external machines to remove fluid from the pipe network
-- returns amount and fluid type
bitumen.pipes.take_fluid = function(pos, max_amount, backpressure)
local hash = minetest.hash_node_position(pos)
local phash = net_members[hash]
if phash == nil then
return 0, "air" -- no network
end
local pnet = networks[phash]
if pnet.buffer <= 0 then
--print("spout: no water in pipe")
return 0, "air" -- no water in the pipe
end
-- hack
pnet.in_pressure = pnet.in_pressure or -32000
if pnet.in_pressure <= pos.y then
--print("insufficient pressure at spout: ".. pnet.in_pressure .. " < " ..pos.y )
return 0, "air"
end
local take = math.min(pnet.buffer, max_amount)
pnet.buffer = math.max(pnet.buffer - take, 0)
if pnet.buffer == 0 then
-- print("pipe drained " .. pnet.name) -- BUG: there might be a bug where a low pressure input can add fluid to the network with a higher spout, then a higher intake with low flow can raise the pressure of the previous fluid to the level of the comparatively lower spout
pnet.in_pressure = backpressure or pos.y
end
return take, pnet.fluid
end
-- take or push into the network, based on given pressure
-- returns change in fluid and fluid type
-- negative if fluid was pushed into the network
-- positive if fluid was taken from the network
bitumen.pipes.buffer = function(pos, fluid, my_pres, avail_push, cap_take, can_change_fluid)
local hash = minetest.hash_node_position(pos)
local phash = net_members[hash]
if phash == nil then
--print("no net")
return 0, "air" -- no network
end
local pnet = networks[phash]
-- print("pressure ["..pnet.name.."] ".. pnet.in_pressure .. " - " .. my_pres)
if pnet.in_pressure <= my_pres then
-- push into the network
-- print("push")
return -bitumen.pipes.push_fluid(pos, fluid, avail_push, my_pres), fluid
else
-- print("take")
if pnet.fluid == fluid or can_change_fluid then
-- print("can take " .. cap_take)
return bitumen.pipes.take_fluid(pos, cap_take, my_pres)
else
-- print("wrong type ".. pnet.fluid .. " - ".. fluid)
return 0, "air" -- wrong fluid type
end
end
end
minetest.register_node("bitumen:intake", {
description = "Petroleum Intake",
drawtype = "nodebox",
node_box = {
type = "connected",
fixed = {{-.1, -.1, -.1, .1, .1, .1}},
-- connect_bottom =
connect_front = {{-.1, -.1, -.5, .1, .1, .1}},
connect_left = {{-.5, -.1, -.1, -.1, .1, .1}},
connect_back = {{-.1, -.1, .1, .1, .1, .5}},
connect_right = {{ .1, -.1, -.1, .5, .1, .1}},
connect_bottom = {{ -.1, -.5, -.1, .1, .1, .1}},
},
connects_to = { "group:petroleum_pipe"--[[, "group:petroleum_fixture"]]},
paramtype = "light",
is_ground_content = false,
tiles = { "default_tin_block.png" },
walkable = true,
groups = { cracky = 3, petroleum_fixture = 1, },
on_construct = function(pos)
print("\nintake placed at "..pos.x..","..pos.y..","..pos.z)
local found_net, merge_list = check_merge(pos)
if found_net == 0 then
local hash = minetest.hash_node_position(pos)
local net = new_network(pos)
net.in_pressure = pos.y
net.inputs[hash] = 1
end
try_merge(merge_list)
save_data()
end,
after_destruct = bitumen.pipes.after_destruct,
})
minetest.register_abm({
nodenames = {"bitumen:intake"},
neighbors = {"group:petroleum"},
interval = 1,
chance = 1,
action = function(pos)
local hash = minetest.hash_node_position(pos)
pos.y = pos.y + 1
local unode = minetest.get_node(pos)
local phash = net_members[hash]
local pnet = networks[phash]
if pnet.fluid == 'air' or pnet.buffer == 0 then
if minetest.registered_nodes[unode.name].groups.petroleum ~= nil then
-- BUG: check for "full" nodes
pnet.fluid = minetest.registered_nodes[unode.name].nonfull_name
--print("intake: here".. (pnet.fluid or "<nil>"))
else
--print("intake: no fluid available ".. pos.x.. ",".. pos.y..",".. pos.z)
return -- no available liquids
end
else -- only suck in existing fluid
if unode.name ~= pnet.fluid and unode.name ~= pnet.fluid.."_full" then
--print("bitumen: no fluid near intake: " .. unode.name .. " != " .. pnet.fluid)
return
end
end
local ulevel = minetest.get_node_level(pos)
if ulevel < 1 then
print("!!!!!!!!!!!! intake level less than one?")
return
end
pnet.in_pressure = pnet.in_pressure or -32000
if pnet.in_pressure > pos.y - 1 then
--print("petroleum backflow at intake: " .. pnet.in_pressure.. " > " ..(pos.y - 1) )
return
end
pnet.in_pressure = math.max(pnet.in_pressure, pos.y - 1)
local rate = math.max(1, math.ceil(ulevel / 2))
local cap = 64
local take = math.max(0, math.min(ulevel, cap - pnet.buffer))
pnet.buffer = pnet.buffer + take
--print("intake took "..take.. " water")
local nl = math.floor(ulevel - take + 0.5)
if nl > 0 then
minetest.set_node_level(pos, nl)
else
minetest.set_node(pos, {name = "air"})
end
end
})
minetest.register_node("bitumen:spout", {
description = "Petroleum Spout",
drawtype = "nodebox",
node_box = {
type = "connected",
fixed = {{-.1, -.1, -.1, .1, .1, .1}},
-- connect_bottom =
connect_front = {{-.1, -.1, -.5, .1, .1, .1}},
connect_left = {{-.5, -.1, -.1, -.1, .1, .1}},
connect_back = {{-.1, -.1, .1, .1, .1, .5}},
connect_right = {{ .1, -.1, -.1, .5, .1, .1}},
connect_top = {{ -.1, -.1, -.1, .1, .5, .1}},
},
connects_to = { "group:petroleum_pipe",--[[ "group:petroleum_fixture" ]]},
paramtype = "light",
is_ground_content = false,
tiles = { "default_copper_block.png" },
walkable = true,
groups = { cracky = 3, petroleum_fixture = 1, },
on_construct = function(pos)
print("\nspout placed at "..pos.x..","..pos.y..","..pos.z)
local found_net, merge_list = check_merge(pos)
if found_net == 0 then
local hash = minetest.hash_node_position(pos)
local pnet = new_network(pos)
pnet.outputs[hash] = 1
end
try_merge(merge_list)
save_data()
end,
after_destruct = bitumen.pipes.after_destruct,
})
minetest.register_abm({
nodenames = {"bitumen:spout"},
-- neighbors = {"group:fresh_water"},
interval = 1,
chance = 1,
action = function(pos)
local hash = minetest.hash_node_position(pos)
local phash = net_members[hash]
local pnet = networks[phash]
if pnet.buffer <= 0 then
--print("spout: no water in pipe")
return -- no water in the pipe
end
-- hack
pnet.in_pressure = pnet.in_pressure or -32000
if pnet.in_pressure <= pos.y then
--print("insufficient pressure at spout: ".. pnet.in_pressure .. " < " ..pos.y )
return
end
pos.y = pos.y - 1
local bnode = minetest.get_node(pos)
local avail = math.min(16, pnet.buffer) -- pnet.buffer / #pnet.outputs
if bnode.name == pnet.fluid then
local blevel = minetest.get_node_level(pos)
local cap = 64 - blevel
local out = math.min(cap, math.min(avail, cap))
--print("cap: ".. cap .." avail: ".. avail .. " out: "..out)
pnet.buffer = pnet.buffer - out
minetest.set_node_level(pos, blevel + out)
elseif bnode.name == "air" or bnode.name == "bitumen:vapor_1" or bnode.name == "bitumen:vapor_2" then
local out = math.min(64, math.max(0, avail))
pnet.buffer = pnet.buffer - out
minetest.set_node(pos, {name = pnet.fluid})
minetest.set_node_level(pos, out)
end
end
})
minetest.register_node("bitumen:pipe", {
description = "petroleum pipe",
drawtype = "nodebox",
node_box = {
type = "connected",
fixed = {{-.1, -.1, -.1, .1, .1, .1}},
-- connect_bottom =
connect_front = {{-.1, -.1, -.5, .1, .1, .1}},
connect_left = {{-.5, -.1, -.1, -.1, .1, .1}},
connect_back = {{-.1, -.1, .1, .1, .1, .5}},
connect_right = {{ .1, -.1, -.1, .5, .1, .1}},
connect_top = {{ -.1, -.1, -.1, .1, .5, .1}},
connect_bottom = {{ -.1, -.5, -.1, .1, .1, .1}},
},
connects_to = { "group:petroleum_pipe", "group:petroleum_fixture" },
paramtype = "light",
is_ground_content = false,
tiles = { "default_steel_block.png" },
walkable = true,
groups = { cracky = 3, petroleum_pipe = 1, },
on_construct = function(pos)
print("\npipe placed at "..pos.x..","..pos.y..","..pos.z)
local found_net, merge_list = check_merge(pos)
if found_net == 0 then
local net = new_network(pos)
end
try_merge(merge_list)
save_data()
end,
after_destruct = bitumen.pipes.after_destruct,
})