diff --git a/api.lua b/api.lua
index f786030..b11f5f3 100644
--- a/api.lua
+++ b/api.lua
@@ -13,56 +13,53 @@
-- You should have received a copy of the GNU Affero General Public License
-- along with this program. If not, see .
---Stores liquid_id and the corresponding liquid node names
-liquid_physics.registered_liquids = {}
+local modpath = core.get_modpath(core.get_current_modname())
+local internal = dofile(modpath .. "/internal.lua")
---Stores all liquid node names and their corresponding liquid_id
-local liquid_ids = {}
-
--- Returns liquid_id when node is a liquid, 0 when node is air
--- and nil when node is anything else
--- Note: 0 is not a valid ID
+-- Returns liquid_id of the node or
+-- nil when the node is not a liquid
+-- @param node_name string Name of the node, e.g. "default:water_source"
+-- @return number ID of the liquid
function liquid_physics.get_liquid_id(node_name)
- if node_name == "air" then
- return 0
- end
- return liquid_ids[node_name]
+ return internal.get_liquid_id(node_name)
end
---Returns name of liquid node. Note: if liquid_level == 0 then "air" will be returned
-function liquid_physics.get_liquid_node_name(liquid_id, liquid_level)
- if liquid_level == 0 then
- return "air"
+-- Returns the liquid of the position specified or
+-- nil when the node is not a liquid
+-- @param pos table Position of the Node
+-- @return table Table with liquid_id and liquid_level
+function liquid_physics.get_liquid_at(pos)
+ local lpn = internal.new_lpn(core.hash_node_position(pos), pos)
+ if not lpn.liquid_id then
+ return nil
end
-
- return liquid_physics.registered_liquids[liquid_id][liquid_level + 1]
+ return { liquid_id = lpn.liquid_id, liquid_level = lpn.liquid_level }
end
---Returns the liquid level. If the node is air, 0 will be returned.
--- -1 will be returned for any other node
-function liquid_physics.get_liquid_level(liquid_id, node_name)
- if node_name == "air" then
- return 0
+-- Sets the liquid at the position specified
+-- Returns true if the operation was successful
+-- @param pos table Position of the Node
+-- @param liquid_id number ID of the liquid
+-- @param liquid_level number [0..8] Level of the liquid
+-- @return bool Success
+function liquid_physics.set_liquid_at(pos, liquid_id, liquid_level)
+ if liquid_level > 8 then
+ return false
end
- if liquid_physics.registered_liquids[liquid_id] == nil then
- error(liquid_id)
- end
- for i = 1, 9 do
- if node_name == liquid_physics.registered_liquids[liquid_id][i] then
- return i - 1
- end
- end
-
- return -1
+ local lpn = internal.new_lpn(core.hash_node_position(pos), pos)
+ internal.set_lpn(liquid_id, lpn, liquid_level)
+ internal.add_node_to_check(pos)
+ return true
end
---@param namespace string Name of mod eg. default
---@param name string Name of former liquid source
--- Executing this function will override the liquid source specified.
+-- Executing this function will override the nodes specified.
-- New liquid sources will be "liquid_physics:namespace_name_level_1" through 7
+-- @param namespace string Name of mod e.g. "default"
+-- @param source_name string Name of the liquid source, e.g. "water_source"
+-- @param flowing_name string Name of the flowing liquid, e.g. "water_flowing"
function liquid_physics.register_liquid(namespace, source_name, flowing_name)
--Get next id
- local id = table.getn(liquid_physics.registered_liquids) + 1
+ local id = table.getn(liquid_physics._registered_liquids) + 1
--Get source definition
local source_liquid_name = namespace .. ":" .. source_name
@@ -89,7 +86,7 @@ function liquid_physics.register_liquid(namespace, source_name, flowing_name)
source_liquid_def.liquid_alternative_source = source_liquid_name
core.register_node(":" .. source_liquid_name, source_liquid_def)
- liquid_ids[source_liquid_name] = id
+ liquid_physics._liquid_ids[source_liquid_name] = id
local liquids = {}
table.insert(liquids, "air")
@@ -134,7 +131,7 @@ function liquid_physics.register_liquid(namespace, source_name, flowing_name)
}
core.register_node(node_name, level_def)
- liquid_ids[node_name] = id
+ liquid_physics._liquid_ids[node_name] = id
table.insert(liquids, node_name)
end
@@ -149,7 +146,7 @@ function liquid_physics.register_liquid(namespace, source_name, flowing_name)
flowing_liquid_def.liquid_alternative_source = source_liquid_name
core.register_node(":" .. flowing_liquid_name, flowing_liquid_def)
- liquid_ids[flowing_liquid_name] = id
+ liquid_physics._liquid_ids[flowing_liquid_name] = id
- liquid_physics.registered_liquids[id] = liquids
+ liquid_physics._registered_liquids[id] = liquids
end
diff --git a/init.lua b/init.lua
index 6759771..ccb32e9 100644
--- a/init.lua
+++ b/init.lua
@@ -17,6 +17,14 @@ local modpath = core.get_modpath(core.get_current_modname())
liquid_physics = {}
+--INTERNAL USE ONLY - Stores all nodes which need to checked
+liquid_physics._nodes_to_check = {}
+--INTERNAL USE ONLY - Stores liquid_id and the corresponding liquid node names
+liquid_physics._registered_liquids = {}
+--INTERNAL USE ONLY - Stores all liquid node names and their corresponding liquid_id
+liquid_physics._liquid_ids = {}
+
+local internal = dofile(modpath .. "/internal.lua")
dofile(modpath .. "/api.lua")
if core.get_modpath("default") then
@@ -29,4 +37,37 @@ if core.get_modpath("mcl_core") then
liquid_physics.register_liquid("mcl_core", "lava_source", "lava_flowing")
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_on_placenode(function(pos, newnode, placer, oldnode, itemstack, pointed_thing)
+ if internal.get_liquid_id(core.get_node(pos).name) ~= nil then
+ internal.add_node_to_check(pos)
+ end
+end)
+
+core.register_lbm {
+ name = "liquid_physics:update",
+ nodenames = source_names,
+ run_at_every_load = true,
+ action = function(pos, node, dtime_s)
+ internal.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)
+ internal.add_node_to_check(pos)
+ end
+})
+
dofile(modpath .. "/physics.lua")
diff --git a/internal.lua b/internal.lua
new file mode 100644
index 0000000..42b1f4a
--- /dev/null
+++ b/internal.lua
@@ -0,0 +1,113 @@
+-- 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 .
+
+local internal = {}
+
+-- Returns liquid_id when node is a liquid
+-- 0 when node is air and
+-- nil when node is anything else
+-- Note: 0 is not a valid ID
+-- @param node_name string The name of the node
+-- @return number The ID of the liquid
+function internal.get_liquid_id(node_name)
+ if node_name == "air" then
+ return 0
+ end
+ return liquid_physics._liquid_ids[node_name]
+end
+
+-- Returns name of liquid node or
+-- "air" for a liquid level of 0
+-- @param liquid_id number The ID of the liquid
+-- @param liquid_level number The level of the liquid
+-- @return string Name of the liquid node
+function internal.get_liquid_node_name(liquid_id, liquid_level)
+ if liquid_level == 0 then
+ return "air"
+ end
+
+ return liquid_physics._registered_liquids[liquid_id][liquid_level + 1]
+end
+
+-- Returns the liquid level of the current node, where:
+-- 0 will be returned for air and
+-- -1 will be returned for non-liquids.
+-- @param liquid_id number The ID of the liquid
+-- @param node_name string The name of the node
+-- @returns number Level of liquid
+function internal.get_liquid_level(liquid_id, node_name)
+ if node_name == "air" then
+ return 0
+ end
+ if liquid_physics._registered_liquids[liquid_id] == nil then
+ error("Invalid liquid_id: " .. liquid_id)
+ end
+ for i = 1, 9 do
+ if node_name == liquid_physics._registered_liquids[liquid_id][i] then
+ return i - 1
+ end
+ end
+
+ return -1
+end
+
+-- Returns new Liquid Physics Node (LPN)
+-- A LPN contains all necessary information for processing
+-- A liquid_level of -1 or liquid_id of nil means, this node is not a liquid
+-- @param hash string Hashed position of the node
+-- @param pos table Position of the node
+-- @return table LPN
+function internal.new_lpn(hash, pos)
+ local node = core.get_node(pos)
+ local liquid_id = internal.get_liquid_id(node.name)
+ local liquid_level = -1
+ if liquid_id then
+ liquid_level = internal.get_liquid_level(liquid_id, node.name)
+ end
+ return {
+ hash = hash,
+ 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
+-- Change liquid_id for transmution
+-- @param liquid_id number The new ID of the liquid
+-- @param lpn table Liquid Physics Node
+-- @param liquid_level number The new level of the liquid
+function internal.set_lpn(liquid_id, lpn, liquid_level)
+ lpn.liquid_level = liquid_level
+ lpn.liquid_id = liquid_id
+end
+
+-- This node will be checked in the next cycle
+-- @param pos table Position of the node
+function internal.add_node_to_check(pos)
+ local h = core.hash_node_position(pos)
+ liquid_physics._nodes_to_check[h] = pos
+end
+
+-- This node will no longer be checked
+-- @param pos table Position of the node
+function internal.remove_node_to_check(pos)
+ local h = core.hash_node_position(pos)
+ liquid_physics._nodes_to_check[h] = nil
+end
+
+return internal
diff --git a/physics.lua b/physics.lua
index d84b39b..62d5aa3 100644
--- a/physics.lua
+++ b/physics.lua
@@ -13,32 +13,23 @@
-- You should have received a copy of the GNU Affero General Public License
-- along with this program. If not, see .
+local modpath = core.get_modpath(core.get_current_modname())
+local internal = dofile(modpath .. "/internal.lua")
+
+-- A table of offsets for ease of use
local offsets = {
{ x = 0, z = 1, },
{ x = 0, z = -1, },
{ x = 1, z = 0, },
{ x = -1, z = 0, }
}
+-- Store number of offsets for less calculations
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
+-- Adds offset to pos
+-- @param pos table Position
+-- @param offset table Offset
+-- @return table Position with added offset
local function add_offset_to_pos(pos, offset)
return {
x = pos.x + offset.x,
@@ -47,49 +38,35 @@ local function add_offset_to_pos(pos, offset)
}
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
+-- 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
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)
+ b[h] = internal.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
+-- 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
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
+-- 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
local function get_valid_neighbors(b, curr)
local neighbors = {}
local number_of_neighbors = 0
@@ -105,8 +82,11 @@ local function get_valid_neighbors(b, curr)
return neighbors, number_of_neighbors
end
---Gets neighbors of curr and calculates the pressure
---This function combines get_valid_neighbors and get_pressure_straight
+-- 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
local function get_valid_neigbor_pressure(b, curr)
local number_of_neighbors = 0
local total_liquid_level = curr.liquid_level
@@ -123,6 +103,11 @@ local function get_valid_neigbor_pressure(b, curr)
end
-- Gets the pressure of curr from the neighbors given
+-- @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
local function get_pressure_straight(b, neighbors, number_of_neighbors, curr)
local total_liquid_level = curr.liquid_level
@@ -132,9 +117,12 @@ local function get_pressure_straight(b, neighbors, number_of_neighbors, curr)
return total_liquid_level / (number_of_neighbors + 1)
end
---Tries to move amount from "from" to "to"
---
---Returns amount moved
+-- 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
local function try_move(from, to, amount)
if to.liquid_level >= 8 then
return 0
@@ -142,8 +130,8 @@ local function try_move(from, to, amount)
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)
+ 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)
if max_allowed_clamped ~= amount then
return max_allowed_clamped
@@ -151,7 +139,9 @@ local function try_move(from, to, amount)
return amount
end
---Moves curr_pos
+-- Calculates how to move curr_pos
+-- @param b table Buffered LPN
+-- @param curr_pos table Current position
local function move(b, curr_pos)
local curr_nbs
local curr_nnbs
@@ -162,7 +152,7 @@ local function move(b, curr_pos)
local curr_lpn = get_lpn_buffered(b, curr_pos)
if curr_lpn.liquid_level <= 0 then
- remove_node_to_check(curr_lpn.pos)
+ internal.remove_node_to_check(curr_lpn.pos)
return
end
@@ -173,101 +163,69 @@ local function move(b, curr_pos)
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)
+ internal.add_node_to_check(up_lpn.pos)
end
- add_node_to_check(down_lpn.pos)
+ internal.add_node_to_check(down_lpn.pos)
end
if moved == curr_lpn.liquid_level then
- remove_node_to_check(curr_lpn.pos)
+ internal.remove_node_to_check(curr_lpn.pos)
for i = 1, curr_nnbs do
- add_node_to_check(curr_nbs[i].pos)
+ internal.add_node_to_check(curr_nbs[i].pos)
end
return
end
end
- --Every neighbor will be higher (except for air) so processing is done
+ -- 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)
+ internal.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)
+ internal.remove_node_to_check(curr_lpn.pos)
return
end
- --TODO: Understand what is happening here and optimize
+ -- 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)
+ 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
+ )
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)
+ internal.add_node_to_check(next_lpn.pos)
end
end
if number_of_swaps == 0 then
- remove_node_to_check(curr_lpn.pos)
+ internal.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
+ -- Buffered LPN
local b = {}
- for hpos, pos in pairs(nodes_to_check) do
+ for hpos, pos in pairs(liquid_physics._nodes_to_check) do
if core.compare_block_status(pos, "active") then
move(b, pos)
else
- remove_node_to_check(pos)
+ internal.remove_node_to_check(pos)
end
end
- --Update only changed nodes from buffer
+ -- Update only changed nodes from buffer
for _, lpn in pairs(b) do
if lpn.init_liquid_level ~= lpn.liquid_level then
- core.set_node(lpn.pos, { name = liquid_physics.get_liquid_node_name(lpn.liquid_id, lpn.liquid_level) })
+ core.set_node(lpn.pos, { name = internal.get_liquid_node_name(lpn.liquid_id, lpn.liquid_level) })
end
end
end)