liquid_physics/physics.lua

232 lines
8.1 KiB
Lua
Raw Normal View History

2025-01-05 18:19:30 +00:00
-- Copyright (C) 2025 snoutie
-- Authors: snoutie (copyright@achtarmig.org)
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Affero General Public License as published
-- by the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU Affero General Public License for more details.
-- You should have received a copy of the GNU Affero General Public License
-- along with this program. If not, see <https://www.gnu.org/licenses/>.
2025-01-06 21:18:15 +00:00
local modpath = core.get_modpath(core.get_current_modname())
local internal = dofile(modpath .. "/internal.lua")
-- A table of offsets for ease of use
2025-01-05 17:11:50 +00:00
local offsets = {
{ x = 0, z = 1, },
{ x = 0, z = -1, },
{ x = 1, z = 0, },
{ x = -1, z = 0, }
}
2025-01-06 21:18:15 +00:00
-- Store number of offsets for less calculations
2025-01-05 17:11:50 +00:00
local number_of_offsets = table.getn(offsets)
2025-01-06 21:18:15 +00:00
-- Adds offset to pos
-- @param pos table Position
-- @param offset table Offset
-- @return table Position with added offset
2025-01-05 17:11:50 +00:00
local function add_offset_to_pos(pos, offset)
return {
x = pos.x + offset.x,
z = pos.z + offset.z,
y = pos.y
}
end
2025-01-06 21:18:15 +00:00
-- Get a LPN from either the buffer or create a new one
-- and add it to the buffer
-- @param b table Buffered LPN
-- @param pos table Positon of the node
-- @return table LPN
2025-01-05 17:11:50 +00:00
local function get_lpn_buffered(b, pos)
local h = core.hash_node_position(pos)
if b[h] ~= nil then
return b[h]
end
2025-01-06 21:18:15 +00:00
b[h] = internal.new_lpn(h, pos)
2025-01-05 17:11:50 +00:00
return b[h]
end
2025-01-06 21:18:15 +00:00
-- Checks if liquid_id of curr and lpn are the same.
-- Checks if the liquid_level is above -1
-- @param curr table The current LPN
-- @param lpn table The LPN to compare against
-- @return bool Is this LPN relevant
2025-01-05 17:11:50 +00:00
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
2025-01-06 21:18:15 +00:00
-- Gets all adjacent liquid nodes with the same liquid_id as curr
-- Gets all adjacent air nodes
-- @param b table Buffered LPN
-- @param curr table The current LPN
-- @return table Table of neighboring LPN
-- @return number Number of neighbors
2025-01-05 17:11:50 +00:00
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
2025-01-06 21:18:15 +00:00
-- Gets neighbors of curr and calculates the pressure
-- This function combines get_valid_neighbors and get_pressure_straight
-- @param b table Buffered LPN
-- @param curr table The current LPN
-- @return float Pressure of curr calculated from valid neighbors
2025-01-05 17:11:50 +00:00
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
2025-01-06 21:18:15 +00:00
-- @param b table Buffered LPN
-- @param neighbors table Table of neighboring LPN
-- @param number_of_neighbors number Number of neighbors
-- @param curr table The current LPN
-- @return float Pressure of curr calculated from neighbors
2025-01-05 17:11:50 +00:00
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
2025-01-06 21:18:15 +00:00
-- Tries to move amount from "from" to "to"
-- Actual amount moved may not be the same as "amount"
-- therefore the actual amount moved is returned
-- @param from table LPN from which amount is deducted
-- @param to table LPN to which amount is added
-- @return number Actual amount moved
2025-01-05 17:11:50 +00:00
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)
2025-01-06 21:18:15 +00:00
internal.set_lpn(from.liquid_id, from, from.liquid_level - max_allowed_clamped)
internal.set_lpn(from.liquid_id, to, to.liquid_level + max_allowed_clamped)
2025-01-05 17:11:50 +00:00
if max_allowed_clamped ~= amount then
return max_allowed_clamped
end
return amount
end
2025-01-06 21:18:15 +00:00
-- Calculates how to move curr_pos
-- @param b table Buffered LPN
-- @param curr_pos table Current position
2025-01-05 17:11:50 +00:00
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
2025-01-06 21:18:15 +00:00
internal.remove_node_to_check(curr_lpn.pos)
2025-01-05 17:11:50 +00:00
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
2025-01-06 21:18:15 +00:00
internal.add_node_to_check(up_lpn.pos)
2025-01-05 17:11:50 +00:00
end
2025-01-06 21:18:15 +00:00
internal.add_node_to_check(down_lpn.pos)
2025-01-05 17:11:50 +00:00
end
if moved == curr_lpn.liquid_level then
2025-01-06 21:18:15 +00:00
internal.remove_node_to_check(curr_lpn.pos)
2025-01-05 17:11:50 +00:00
for i = 1, curr_nnbs do
2025-01-06 21:18:15 +00:00
internal.add_node_to_check(curr_nbs[i].pos)
2025-01-05 17:11:50 +00:00
end
return
end
end
2025-01-06 21:18:15 +00:00
-- Every neighbor will be higher (except for air) so processing is done
2025-01-05 17:11:50 +00:00
if curr_lpn.liquid_level <= 1 then
2025-01-06 21:18:15 +00:00
internal.remove_node_to_check(curr_lpn.pos)
2025-01-05 17:11:50 +00:00
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
2025-01-06 21:18:15 +00:00
internal.remove_node_to_check(curr_lpn.pos)
2025-01-05 17:11:50 +00:00
return
end
2025-01-06 21:18:15 +00:00
-- TODO: Understand what is happening here and optimize
2025-01-05 17:11:50 +00:00
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
2025-01-06 21:18:15 +00:00
internal.add_node_to_check(next_lpn.pos)
internal.add_node_to_check(
get_lpn_buffered(b, { x = curr_pos.x, y = curr_pos.y + 1, z = curr_pos.z }).pos
)
2025-01-05 17:11:50 +00:00
number_of_swaps = number_of_swaps + 1
end
elseif curr_lpn.liquid_level < next_lpn.liquid_level then
2025-01-06 21:18:15 +00:00
internal.add_node_to_check(next_lpn.pos)
2025-01-05 17:11:50 +00:00
end
end
if number_of_swaps == 0 then
2025-01-06 21:18:15 +00:00
internal.remove_node_to_check(curr_lpn.pos)
2025-01-05 17:11:50 +00:00
end
end
core.register_on_mapblocks_changed(function(modified_blocks, modified_blocks_count)
2025-01-06 21:18:15 +00:00
-- Buffered LPN
2025-01-05 17:11:50 +00:00
local b = {}
2025-01-06 21:18:15 +00:00
for hpos, pos in pairs(liquid_physics._nodes_to_check) do
2025-01-05 17:11:50 +00:00
if core.compare_block_status(pos, "active") then
move(b, pos)
else
2025-01-06 21:18:15 +00:00
internal.remove_node_to_check(pos)
2025-01-05 17:11:50 +00:00
end
end
2025-01-06 21:18:15 +00:00
-- Update only changed nodes from buffer
2025-01-05 17:11:50 +00:00
for _, lpn in pairs(b) do
if lpn.init_liquid_level ~= lpn.liquid_level then
2025-01-11 21:57:44 +00:00
internal.set_node(lpn)
2025-01-05 17:11:50 +00:00
end
end
end)