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