local offsets = { { x = 0, z = 1, }, { x = 0, z = -1, }, { x = 1, z = 0, }, { x = -1, z = 0, } } local number_of_offsets = table.getn(offsets) --Nodes which will be checked local nodes_to_check = {} --Adds node to check local function add_node_to_check(pos) local h = core.hash_node_position(pos) nodes_to_check[h] = pos end --This node will no longer be checked -- --"Freezes" node local function remove_node_to_check(pos) local h = core.hash_node_position(pos) nodes_to_check[h] = nil end --Adds offset to pos local function add_offset_to_pos(pos, offset) return { x = pos.x + offset.x, z = pos.z + offset.z, y = pos.y } end --Returns new Liquid Physics Node (lpn) --A LPN contains all necessary information for processing --A liquid_level of -1 means, this node is not a liquid local function new_lpn(h, pos) local node = core.get_node(pos) local liquid_id = liquid_physics.get_liquid_id(node.name) local liquid_level = -1 if liquid_id then liquid_level = liquid_physics.get_liquid_level(liquid_id, node.name) end return { hash = h, pos = pos, node_name = node.name, liquid_id = liquid_id, init_liquid_level = liquid_level, liquid_level = liquid_level, } end --Sets liquid_level and liquid_id of LPN --Allows for transmution local function set_lpn(liquid_id, lpn, liquid_level) lpn.liquid_level = liquid_level lpn.liquid_id = liquid_id end --Reduce calls to Engine and reduce calculations of liquid_level local function get_lpn_buffered(b, pos) local h = core.hash_node_position(pos) if b[h] ~= nil then return b[h] end b[h] = new_lpn(h, pos) return b[h] end --Checks if liquid_id of curr and lpn are the same. Also if the liquid_level is above -1 local function is_lpn_relevant(curr, lpn) return lpn.liquid_level >= 0 and (lpn.liquid_level == 0 or curr.liquid_id == lpn.liquid_id) end --Gets all adjacent liquid nodes with the same liquid id, or air nodes local function get_valid_neighbors(b, curr) local neighbors = {} local number_of_neighbors = 0 for i = 1, number_of_offsets do local pos = add_offset_to_pos(curr.pos, offsets[i]) local lpn = get_lpn_buffered(b, pos) if is_lpn_relevant(curr, lpn) then number_of_neighbors = number_of_neighbors + 1 table.insert(neighbors, math.random(number_of_neighbors, 1), lpn) end end return neighbors, number_of_neighbors end --Gets neighbors of curr and calculates the pressure --This function combines get_valid_neighbors and get_pressure_straight local function get_valid_neigbor_pressure(b, curr) local number_of_neighbors = 0 local total_liquid_level = curr.liquid_level for i = 1, number_of_offsets do local pos = add_offset_to_pos(curr.pos, offsets[i]) local lpn = get_lpn_buffered(b, pos) if is_lpn_relevant(curr, lpn) then total_liquid_level = total_liquid_level + lpn.liquid_level / 2 number_of_neighbors = number_of_neighbors + 1 end end return total_liquid_level / (number_of_neighbors + 1) end -- Gets the pressure of curr from the neighbors given local function get_pressure_straight(b, neighbors, number_of_neighbors, curr) local total_liquid_level = curr.liquid_level for i = 1, number_of_neighbors do total_liquid_level = total_liquid_level + neighbors[i].liquid_level / 2 end return total_liquid_level / (number_of_neighbors + 1) end --Tries to move amount from "from" to "to" -- --Returns amount moved local function try_move(from, to, amount) if to.liquid_level >= 8 then return 0 end local max_allowed = amount + 8 - (to.liquid_level + amount) local max_allowed_clamped = math.min(max_allowed, amount, from.liquid_level) set_lpn(from.liquid_id, from, from.liquid_level - max_allowed_clamped) set_lpn(from.liquid_id, to, to.liquid_level + max_allowed_clamped) if max_allowed_clamped ~= amount then return max_allowed_clamped end return amount end --Moves curr_pos local function move(b, curr_pos) local curr_nbs local curr_nnbs local up_lpn --check down first local down_lpn = get_lpn_buffered(b, { x = curr_pos.x, y = curr_pos.y - 1, z = curr_pos.z }) local curr_lpn = get_lpn_buffered(b, curr_pos) if curr_lpn.liquid_level <= 0 then remove_node_to_check(curr_lpn.pos) return end curr_nbs, curr_nnbs = get_valid_neighbors(b, curr_lpn) if is_lpn_relevant(curr_lpn, down_lpn) then local moved = try_move(curr_lpn, down_lpn, curr_lpn.liquid_level) if moved > 0 then up_lpn = get_lpn_buffered(b, { x = curr_pos.x, y = curr_pos.y + 1, z = curr_pos.z }) if up_lpn.liquid_level >= 0 then add_node_to_check(up_lpn.pos) end add_node_to_check(down_lpn.pos) end if moved == curr_lpn.liquid_level then remove_node_to_check(curr_lpn.pos) for i = 1, curr_nnbs do add_node_to_check(curr_nbs[i].pos) end return end end --Every neighbor will be higher (except for air) so processing is done if curr_lpn.liquid_level <= 1 then remove_node_to_check(curr_lpn.pos) return end local curr_prs = get_pressure_straight(b, curr_nbs, curr_nnbs, curr_lpn) if curr_prs >= 4.8 then --4.8 == ((8*4/2)+8)/5 -> meaning maximum pressure, implies no way to move remove_node_to_check(curr_lpn.pos) return end --TODO: Understand what is happening here and optimize local number_of_swaps = 0 for i = 1, curr_nnbs do local next_lpn = curr_nbs[i] local next_prs = get_valid_neigbor_pressure(b, next_lpn) if curr_prs > next_prs and next_lpn.liquid_level < curr_lpn.liquid_level then if try_move(curr_lpn, next_lpn, 1) > 0 then add_node_to_check(next_lpn.pos) add_node_to_check(get_lpn_buffered(b, { x = curr_pos.x, y = curr_pos.y + 1, z = curr_pos.z }).pos) number_of_swaps = number_of_swaps + 1 end elseif curr_lpn.liquid_level < next_lpn.liquid_level then add_node_to_check(next_lpn.pos) end end if number_of_swaps == 0 then remove_node_to_check(curr_lpn.pos) end end --Get placed liquids core.register_on_placenode(function(pos, newnode, placer, oldnode, itemstack, pointed_thing) if liquid_physics.get_liquid_id(core.get_node(pos).name) ~= nil then add_node_to_check(pos) end end) --Remove "air" from registered_liquids for lbm and abm local source_names = {} for key, value in pairs(liquid_physics.registered_liquids) do for i = 2, table.getn(value) do table.insert(source_names, value[i]) end end core.register_lbm { name = "liquid_physics:update", nodenames = source_names, run_at_every_load = true, action = function(pos, node, dtime_s) add_node_to_check(pos) end, } core.register_abm({ nodenames = source_names, neighbors = { "air" }, interval = 0.2, chance = 0, action = function(pos, node, active_object_count, active_object_count_wider) add_node_to_check(pos) end }) core.register_on_mapblocks_changed(function(modified_blocks, modified_blocks_count) --Buffer local b = {} for hpos, pos in pairs(nodes_to_check) do if core.compare_block_status(pos, "active") then move(b, pos) else remove_node_to_check(pos) end end --Update only changed nodes from buffer for _, lpn in pairs(b) do if lpn.init_liquid_level ~= lpn.liquid_level then --TODO: Find out whether set_node or set_node_level is faster if lpn.liquid_level >= 0 or lpn.liquid_level >= 8 then core.set_node(lpn.pos, { name = liquid_physics.get_liquid_node_name(lpn.liquid_id, lpn.liquid_level) }) elseif lpn.liquid_level < 8 then core.set_node_level(lpn.pos, lpn.liquid_level) end end end end)