205 lines
5.8 KiB
Lua
205 lines
5.8 KiB
Lua
|
local pressure_margin = 20
|
||
|
|
||
|
local pipe_cache = {}
|
||
|
|
||
|
local cardinal_dirs = {
|
||
|
{x= 0, y=0, z= 1},
|
||
|
{x= 1, y=0, z= 0},
|
||
|
{x= 0, y=0, z=-1},
|
||
|
{x=-1, y=0, z= 0},
|
||
|
{x= 0, y=-1, z= 0},
|
||
|
{x= 0, y=1, z= 0},
|
||
|
}
|
||
|
|
||
|
local sort_by_pressure = function(first, second)
|
||
|
local first_pressure = first.pressure
|
||
|
local second_pressure = second.pressure
|
||
|
if first_pressure == nil or second_pressure == nil then
|
||
|
minetest.log("error", "[waterworks] attempted to sort something by pressure that had no pressure value: " .. dump(first) .. "\n" .. dump(second))
|
||
|
return
|
||
|
end
|
||
|
|
||
|
return first_pressure > second_pressure
|
||
|
end
|
||
|
|
||
|
local valid_sink = function(node_name)
|
||
|
return node_name == "air" or node_name == "default:water_flowing"
|
||
|
end
|
||
|
local valid_source = function(node_name)
|
||
|
return node_name == "default:water_source"
|
||
|
end
|
||
|
|
||
|
-- breadth-first search passing through water searching for air or flowing water, limited to y <= pressure.
|
||
|
-- I could try to be fancy about water flowing downward preferentially, let's leave that as a TODO for now.
|
||
|
local flood_search_outlet = function(start_pos, pressure)
|
||
|
local start_node = minetest.get_node(start_pos)
|
||
|
local start_node_name = start_node.name
|
||
|
if valid_sink(start_node_name) then
|
||
|
return start_pos
|
||
|
end
|
||
|
|
||
|
local visited = {}
|
||
|
visited[minetest.hash_node_position(start_pos)] = true
|
||
|
local queue = {start_pos}
|
||
|
local queue_pointer = 1
|
||
|
|
||
|
while #queue >= queue_pointer do
|
||
|
local current_pos = queue[queue_pointer]
|
||
|
queue_pointer = queue_pointer + 1
|
||
|
for _, cardinal_dir in ipairs(cardinal_dirs) do
|
||
|
local new_pos = vector.add(current_pos, cardinal_dir)
|
||
|
local new_hash = minetest.hash_node_position(new_pos)
|
||
|
if visited[new_hash] == nil and new_pos.y <= pressure then
|
||
|
local new_node = minetest.get_node(new_pos)
|
||
|
local new_node_name = new_node.name
|
||
|
if valid_sink(new_node_name) then
|
||
|
return new_pos
|
||
|
end
|
||
|
visited[new_hash] = true
|
||
|
if valid_source(new_node_name) then
|
||
|
table.insert(queue, new_pos)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
return nil
|
||
|
end
|
||
|
|
||
|
|
||
|
local upward_dirs = {
|
||
|
{x= 0, y=0, z= 1},
|
||
|
{x= 1, y=0, z= 0},
|
||
|
{x= 0, y=0, z=-1},
|
||
|
{x=-1, y=0, z= 0},
|
||
|
{x= 0, y=1, z= 0},
|
||
|
}
|
||
|
|
||
|
local shuffle = function(tbl)
|
||
|
for i = #tbl, 2, -1 do
|
||
|
local rand = math.random(i)
|
||
|
tbl[i], tbl[rand] = tbl[rand], tbl[i]
|
||
|
end
|
||
|
return tbl
|
||
|
end
|
||
|
|
||
|
-- depth-first random-walk search trending in an upward direction, returns when it gets cornered
|
||
|
local find_source = function(start_pos)
|
||
|
local current_node = minetest.get_node(start_pos)
|
||
|
local current_node_name = current_node.name
|
||
|
if not valid_source(current_node_name) then
|
||
|
return nil
|
||
|
end
|
||
|
|
||
|
local visited = {[minetest.hash_node_position(start_pos)] = true}
|
||
|
local current_pos = start_pos
|
||
|
|
||
|
local continue = true
|
||
|
while continue do
|
||
|
continue = false
|
||
|
shuffle(upward_dirs)
|
||
|
for _, dir in ipairs(upward_dirs) do
|
||
|
local next_pos = vector.add(current_pos, dir)
|
||
|
local next_hash = minetest.hash_node_position(next_pos)
|
||
|
if visited[next_hash] == nil then
|
||
|
visited[next_hash] = true
|
||
|
local next_node = minetest.get_node(next_pos)
|
||
|
local next_node_name = next_node.name
|
||
|
if valid_source(next_node_name) then
|
||
|
current_pos = next_pos
|
||
|
continue = true
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
return current_pos
|
||
|
end
|
||
|
|
||
|
|
||
|
waterworks.execute_pipes = function(net_index, net_capacity)
|
||
|
local net = waterworks.pipe_networks[net_index]
|
||
|
if net == nil then
|
||
|
minetest.log("error", "[waterworks] Invalid net index given to execute: " .. tostring(net_index))
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local inlets
|
||
|
local outlets
|
||
|
|
||
|
if net.cache_valid then
|
||
|
-- We don't need to recalculate, nothing about the pipe network has changed since last time
|
||
|
inlets = pipe_cache[net_index].inlets
|
||
|
outlets = pipe_cache[net_index].outlets
|
||
|
else
|
||
|
-- Find all the inlets and outlets and sort them by pressure
|
||
|
inlets = {}
|
||
|
if net.connected.inlet ~= nil then
|
||
|
for _, inlet_set in pairs(net.connected.inlet) do
|
||
|
for _, inlet in pairs(inlet_set) do
|
||
|
table.insert(inlets, inlet)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
table.sort(inlets, sort_by_pressure)
|
||
|
|
||
|
outlets = {}
|
||
|
if net.connected.outlet ~= nil then
|
||
|
for _, outlet_set in pairs(net.connected.outlet) do
|
||
|
for _, outlet in pairs(outlet_set) do
|
||
|
table.insert(outlets, outlet)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
table.sort(outlets, sort_by_pressure)
|
||
|
|
||
|
-- Cache the results
|
||
|
pipe_cache[net_index] = {}
|
||
|
pipe_cache[net_index].inlets = inlets
|
||
|
pipe_cache[net_index].outlets = outlets
|
||
|
|
||
|
net.cache_valid = true
|
||
|
end
|
||
|
|
||
|
local inlet_index = 1
|
||
|
local outlet_index = #outlets
|
||
|
local inlet_count = #inlets
|
||
|
|
||
|
local count = 0
|
||
|
|
||
|
-- Starting with the highest-pressure inlet and the lowest-pressure outlet, attempt to move water.
|
||
|
-- We then proceed to steadily lower-pressure inlets and higher-pressure outlets until we meet in the middle, at which point
|
||
|
-- the system is in equilibrium.
|
||
|
while inlet_index <= inlet_count and outlet_index > 0 and count < net_capacity do
|
||
|
local source = inlets[inlet_index]
|
||
|
local sink = outlets[outlet_index]
|
||
|
|
||
|
--minetest.debug("source: " .. dump(source))
|
||
|
--minetest.debug("sink: " .. dump(sink))
|
||
|
|
||
|
-- pressure_margin allows us to check sources that are a little bit below sinks,
|
||
|
-- in case the extra pressure from their water depth is sufficient to force water through
|
||
|
if source.pressure + pressure_margin >= sink.pressure then
|
||
|
local source_pos = find_source(source.target)
|
||
|
local sink_pos
|
||
|
if source_pos ~= nil then
|
||
|
sink_pos = flood_search_outlet(sink.target, math.max(source.pressure, source_pos.y))
|
||
|
if sink_pos ~= nil then
|
||
|
minetest.swap_node(sink_pos, {name="default:water_source"})
|
||
|
minetest.swap_node(source_pos, {name="air"})
|
||
|
count = count + 1
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if source_pos == nil then
|
||
|
-- the outlet had available space but the inlet didn't provide
|
||
|
inlet_index = inlet_index + 1
|
||
|
elseif sink_pos == nil then
|
||
|
-- the inlet provided but the outlet didn't have space
|
||
|
outlet_index = outlet_index - 1
|
||
|
end
|
||
|
else
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
end
|