Merge pull request #204 from thetaepsilon-gamedev/master

More pressure_logic work
This commit is contained in:
Vanessa Ezekowitz 2017-10-09 06:38:54 -04:00 committed by GitHub
commit a028aef9c9
13 changed files with 756 additions and 211 deletions

@ -0,0 +1,9 @@
-- enable finite liquid in the presence of dynamic liquid to preserve water volume.
local enable = false
if minetest.get_modpath("dynamic_liquid") then
pipeworks.logger("detected mod dynamic_liquid, enabling finite liquid flag")
enable = true
end
pipeworks.toggles.finite_water = enable

@ -1,6 +1,38 @@
Changelog Changelog
--------- ---------
2017-10-08 (thetaepsilon)
A lot more of the new flow logic work.
There are two sub-modes of this now, non-finite and finite mode.
Non-finite mode most closely resembles "classic mode", whereas finite mode is more intended for use with mods such as dynamic_liquids which enable water sources to move themselves.
Everything that was functional in classic mode more or less works correctly now.
Still TODO:
+ Flow directionality - things like flow sensors and airtight panels will flow in directions that don't make sense from their visuals.
Possible feature requests:
+ Making tanks and gratings do something useful.
2017-09-27 (thetaepsilon)
Start of new flow logic re-implementation.
This mode is current *very* incomplete, and requires a per-world setting to enable.
Adds a pressure value stored in all pipe node metadata, and a mechanism to balance it out with nearby nodes on ABM trigger.
Currently, this inhibits the old behaviour when enabled, and (again WHEN ENABLED) breaks pretty much everything but normal pipes, spigots and pumps.
For this reason it is far from being intended as the default for some time to come yet.
What *does* work:
+ Pumps will try to take in water (and removes it!) as long as internal pressure does not exceed a threshold.
- a TODO is to make this pressure threshold configurable.
+ Pipes will balance this pressure between themselves, and will slowly average out over time.
+ Spigots will try to make the node beneath them a water source if the pressure is great enough and the existing node is flowing water or air.
- This is admittedly of fairly limited use with default water mechanics; those looking for more realistic mechanics might want to look at the dynamic_liquid mod, though that mod comes with it's own caveats (most notably drastic changes to previous worlds...).
What *does not* work:
+ Flow sensors, valves. Valves in particular currently do not function as a barrier to water's path under the experimental logic.
- TODO: internal code to allow this to be overriden.
*seems this hasn't been updated in a while*
2013-01-13: Tubes can transport items now! Namely, I added Novatux/Nore's item 2013-01-13: Tubes can transport items now! Namely, I added Novatux/Nore's item
transport mod as a default part of this mod, to make tubes do something useful! transport mod as a default part of this mod, to make tubes do something useful!
Thanks to Nore and RealBadAngel for the code contributions! Thanks to Nore and RealBadAngel for the code contributions!

@ -27,8 +27,6 @@ local settings = {
drop_on_routing_fail = false, drop_on_routing_fail = false,
delete_item_on_clearobject = true, delete_item_on_clearobject = true,
enable_new_flow_logic = false,
} }
for name, value in pairs(settings) do for name, value in pairs(settings) do

@ -1,3 +1,4 @@
local new_flow_logic_register = pipeworks.flowables.register
-- rotation handlers -- rotation handlers
@ -129,7 +130,8 @@ for s in ipairs(states) do
dgroups = {snappy=3, pipe=1, not_in_creative_inventory=1} dgroups = {snappy=3, pipe=1, not_in_creative_inventory=1}
end end
minetest.register_node("pipeworks:pump_"..states[s], { local pumpname = "pipeworks:pump_"..states[s]
minetest.register_node(pumpname, {
description = "Pump/Intake Module", description = "Pump/Intake Module",
drawtype = "mesh", drawtype = "mesh",
mesh = "pipeworks_pump.obj", mesh = "pipeworks_pump.obj",
@ -162,8 +164,15 @@ for s in ipairs(states) do
-- FIXME - does this preserve metadata? need to look at this -- FIXME - does this preserve metadata? need to look at this
on_rotate = screwdriver.rotate_simple on_rotate = screwdriver.rotate_simple
}) })
-- FIXME: currently a simple flow device, but needs directionality checking
new_flow_logic_register.simple(pumpname)
local pump_drive = 4
if states[s] ~= "off" then
new_flow_logic_register.intake_simple(pumpname, pump_drive)
end
minetest.register_node("pipeworks:valve_"..states[s].."_empty", { local nodename_valve_empty = "pipeworks:valve_"..states[s].."_empty"
minetest.register_node(nodename_valve_empty, {
description = "Valve", description = "Valve",
drawtype = "mesh", drawtype = "mesh",
mesh = "pipeworks_valve_"..states[s]..".obj", mesh = "pipeworks_valve_"..states[s]..".obj",
@ -201,9 +210,16 @@ for s in ipairs(states) do
end, end,
on_rotate = pipeworks.fix_after_rotation on_rotate = pipeworks.fix_after_rotation
}) })
-- only register flow logic for the "on" ABM.
-- this means that the off state automatically blocks flow by not participating in the balancing operation.
if states[s] ~= "off" then
-- FIXME: this still a simple device, directionality not honoured
new_flow_logic_register.simple(nodename_valve_empty)
end
end end
minetest.register_node("pipeworks:valve_on_loaded", { local nodename_valve_loaded = "pipeworks:valve_on_loaded"
minetest.register_node(nodename_valve_loaded, {
description = "Valve", description = "Valve",
drawtype = "mesh", drawtype = "mesh",
mesh = "pipeworks_valve_on.obj", mesh = "pipeworks_valve_on.obj",
@ -241,9 +257,16 @@ minetest.register_node("pipeworks:valve_on_loaded", {
end, end,
on_rotate = pipeworks.fix_after_rotation on_rotate = pipeworks.fix_after_rotation
}) })
-- register this the same as the on-but-empty variant, so existing nodes of this type work also.
-- note that as new_flow_logic code does not distinguish empty/full in node states,
-- right-clicking a "loaded" valve (becoming an off valve) then turning it on again will yield a on-but-empty valve,
-- but the flow logic will still function.
-- thus under new_flow_logic this serves as a kind of migration.
new_flow_logic_register.simple(nodename_valve_loaded)
-- grating -- grating
-- FIXME: should this do anything useful in the new flow logic?
minetest.register_node("pipeworks:grating", { minetest.register_node("pipeworks:grating", {
description = "Decorative grating", description = "Decorative grating",
tiles = { tiles = {
@ -276,7 +299,8 @@ minetest.register_node("pipeworks:grating", {
-- outlet spigot -- outlet spigot
minetest.register_node("pipeworks:spigot", { local nodename_spigot_empty = "pipeworks:spigot"
minetest.register_node(nodename_spigot_empty, {
description = "Spigot outlet", description = "Spigot outlet",
drawtype = "mesh", drawtype = "mesh",
mesh = "pipeworks_spigot.obj", mesh = "pipeworks_spigot.obj",
@ -306,7 +330,8 @@ minetest.register_node("pipeworks:spigot", {
on_rotate = pipeworks.fix_after_rotation on_rotate = pipeworks.fix_after_rotation
}) })
minetest.register_node("pipeworks:spigot_pouring", { local nodename_spigot_loaded = "pipeworks:spigot_pouring"
minetest.register_node(nodename_spigot_loaded, {
description = "Spigot outlet", description = "Spigot outlet",
drawtype = "mesh", drawtype = "mesh",
mesh = "pipeworks_spigot_pouring.obj", mesh = "pipeworks_spigot_pouring.obj",
@ -348,6 +373,17 @@ minetest.register_node("pipeworks:spigot_pouring", {
drop = "pipeworks:spigot", drop = "pipeworks:spigot",
on_rotate = pipeworks.fix_after_rotation on_rotate = pipeworks.fix_after_rotation
}) })
-- new flow logic does not currently distinguish between these two visual states.
-- register both so existing flowing spigots continue to work (even if the visual doesn't match the spigot's behaviour).
new_flow_logic_register.simple(nodename_spigot_empty)
new_flow_logic_register.simple(nodename_spigot_loaded)
local spigot_upper = 1.0
local spigot_lower = 1.0
local spigot_neighbours={{x=0, y=-1, z=0}}
new_flow_logic_register.output_simple(nodename_spigot_empty, spigot_upper, spigot_lower, spigot_neighbours)
new_flow_logic_register.output_simple(nodename_spigot_loaded, spigot_upper, spigot_lower, spigot_neighbours)
-- sealed pipe entry/exit (horizontal pipe passing through a metal -- sealed pipe entry/exit (horizontal pipe passing through a metal
-- wall, for use in places where walls should look like they're airtight) -- wall, for use in places where walls should look like they're airtight)
@ -360,7 +396,8 @@ local panel_cbox = {
} }
} }
minetest.register_node("pipeworks:entry_panel_empty", { local nodename_panel_empty = "pipeworks:entry_panel_empty"
minetest.register_node(nodename_panel_empty, {
description = "Airtight Pipe entry/exit", description = "Airtight Pipe entry/exit",
drawtype = "mesh", drawtype = "mesh",
mesh = "pipeworks_entry_panel.obj", mesh = "pipeworks_entry_panel.obj",
@ -379,7 +416,8 @@ minetest.register_node("pipeworks:entry_panel_empty", {
on_rotate = pipeworks.fix_after_rotation on_rotate = pipeworks.fix_after_rotation
}) })
minetest.register_node("pipeworks:entry_panel_loaded", { local nodename_panel_loaded = "pipeworks:entry_panel_loaded"
minetest.register_node(nodename_panel_loaded, {
description = "Airtight Pipe entry/exit", description = "Airtight Pipe entry/exit",
drawtype = "mesh", drawtype = "mesh",
mesh = "pipeworks_entry_panel.obj", mesh = "pipeworks_entry_panel.obj",
@ -398,8 +436,15 @@ minetest.register_node("pipeworks:entry_panel_loaded", {
drop = "pipeworks:entry_panel_empty", drop = "pipeworks:entry_panel_empty",
on_rotate = pipeworks.fix_after_rotation on_rotate = pipeworks.fix_after_rotation
}) })
-- FIXME requires-directionality
-- TODO: AFAIK the two panels have no visual difference, so are redundant under new flow logic - alias?
new_flow_logic_register.simple(nodename_panel_empty)
new_flow_logic_register.simple(nodename_panel_loaded)
minetest.register_node("pipeworks:flow_sensor_empty", {
local nodename_sensor_empty = "pipeworks:flow_sensor_empty"
minetest.register_node(nodename_sensor_empty, {
description = "Flow Sensor", description = "Flow Sensor",
drawtype = "mesh", drawtype = "mesh",
mesh = "pipeworks_flow_sensor.obj", mesh = "pipeworks_flow_sensor.obj",
@ -437,7 +482,8 @@ minetest.register_node("pipeworks:flow_sensor_empty", {
on_rotate = pipeworks.fix_after_rotation on_rotate = pipeworks.fix_after_rotation
}) })
minetest.register_node("pipeworks:flow_sensor_loaded", { local nodename_sensor_loaded = "pipeworks:flow_sensor_loaded"
minetest.register_node(nodename_sensor_loaded, {
description = "Flow sensor (on)", description = "Flow sensor (on)",
drawtype = "mesh", drawtype = "mesh",
mesh = "pipeworks_flow_sensor.obj", mesh = "pipeworks_flow_sensor.obj",
@ -475,9 +521,18 @@ minetest.register_node("pipeworks:flow_sensor_loaded", {
mesecons = pipereceptor_on, mesecons = pipereceptor_on,
on_rotate = pipeworks.fix_after_rotation on_rotate = pipeworks.fix_after_rotation
}) })
-- FIXME requires-directionality
new_flow_logic_register.simple(nodename_sensor_empty)
new_flow_logic_register.simple(nodename_sensor_loaded)
-- activate flow sensor at roughly half the pressure pumps drive pipes
local sensor_pressure_set = { { nodename_sensor_empty, 0.0 }, { nodename_sensor_loaded, 1.0 } }
new_flow_logic_register.transition_simple_set(sensor_pressure_set, { mesecons=pipeworks.mesecons_rules })
-- tanks -- tanks
-- TODO flow-logic-stub: these don't currently do anything under the new flow logic.
for fill = 0, 10 do for fill = 0, 10 do
local filldesc="empty" local filldesc="empty"
local sgroups = {snappy=3, pipe=1, tankfill=fill+1} local sgroups = {snappy=3, pipe=1, tankfill=fill+1}
@ -548,7 +603,8 @@ end
-- fountainhead -- fountainhead
minetest.register_node("pipeworks:fountainhead", { local nodename_fountain_empty = "pipeworks:fountainhead"
minetest.register_node(nodename_fountain_empty, {
description = "Fountainhead", description = "Fountainhead",
drawtype = "mesh", drawtype = "mesh",
mesh = "pipeworks_fountainhead.obj", mesh = "pipeworks_fountainhead.obj",
@ -581,7 +637,8 @@ minetest.register_node("pipeworks:fountainhead", {
on_rotate = false on_rotate = false
}) })
minetest.register_node("pipeworks:fountainhead_pouring", { local nodename_fountain_loaded = "pipeworks:fountainhead_pouring"
minetest.register_node(nodename_fountain_loaded, {
description = "Fountainhead", description = "Fountainhead",
drawtype = "mesh", drawtype = "mesh",
mesh = "pipeworks_fountainhead.obj", mesh = "pipeworks_fountainhead.obj",
@ -615,6 +672,15 @@ minetest.register_node("pipeworks:fountainhead_pouring", {
drop = "pipeworks:fountainhead", drop = "pipeworks:fountainhead",
on_rotate = false on_rotate = false
}) })
new_flow_logic_register.simple(nodename_fountain_empty)
new_flow_logic_register.simple(nodename_fountain_loaded)
local fountain_upper = 1.0
local fountain_lower = 1.0
local fountain_neighbours={{x=0, y=1, z=0}}
new_flow_logic_register.output_simple(nodename_fountain_empty, fountain_upper, fountain_lower, fountain_neighbours)
new_flow_logic_register.output_simple(nodename_fountain_loaded, fountain_upper, fountain_lower, fountain_neighbours)
minetest.register_alias("pipeworks:valve_off_loaded", "pipeworks:valve_off_empty") minetest.register_alias("pipeworks:valve_off_loaded", "pipeworks:valve_off_empty")
minetest.register_alias("pipeworks:entry_panel", "pipeworks:entry_panel_empty") minetest.register_alias("pipeworks:entry_panel", "pipeworks:entry_panel_empty")

@ -17,14 +17,16 @@ dofile(pipeworks.modpath.."/default_settings.lua")
-- Read the external config file if it exists. -- Read the external config file if it exists.
-- please add any new feature toggles to be a flag in this table...
pipeworks.toggles = {}
local worldsettingspath = pipeworks.worldpath.."/pipeworks_settings.txt" local worldsettingspath = pipeworks.worldpath.."/pipeworks_settings.txt"
local worldsettingsfile = io.open(worldsettingspath, "r") local worldsettingsfile = io.open(worldsettingspath, "r")
if worldsettingsfile then if worldsettingsfile then
worldsettingsfile:close() worldsettingsfile:close()
dofile(worldsettingspath) dofile(worldsettingspath)
end end
if pipeworks.enable_new_flow_logic then if pipeworks.toggles.pressure_logic then
minetest.log("warning", "pipeworks new_flow_logic is WIP and incomplete!") minetest.log("warning", "pipeworks pressure-based logic is WIP and incomplete!")
end end
-- Random variables -- Random variables
@ -94,9 +96,18 @@ function pipeworks.replace_name(tbl,tr,name)
return ntbl return ntbl
end end
pipeworks.logger = function(msg)
print("[pipeworks] "..msg)
end
------------------------------------------- -------------------------------------------
-- Load the various other parts of the mod -- Load the various other parts of the mod
-- early auto-detection for finite water mode if not explicitly disabled
if pipeworks.toggles.finite_water == nil then
dofile(pipeworks.modpath.."/autodetect-finite-water.lua")
end
dofile(pipeworks.modpath.."/common.lua") dofile(pipeworks.modpath.."/common.lua")
dofile(pipeworks.modpath.."/models.lua") dofile(pipeworks.modpath.."/models.lua")
dofile(pipeworks.modpath.."/autoplace_pipes.lua") dofile(pipeworks.modpath.."/autoplace_pipes.lua")
@ -115,14 +126,19 @@ dofile(pipeworks.modpath.."/filter-injector.lua")
dofile(pipeworks.modpath.."/trashcan.lua") dofile(pipeworks.modpath.."/trashcan.lua")
dofile(pipeworks.modpath.."/wielder.lua") dofile(pipeworks.modpath.."/wielder.lua")
local logicdir = "/new_flow_logic/"
-- note that even with these files the new flow logic is not yet default.
-- registration will take place but no actual ABMs/node logic will be installed,
-- unless the toggle flag is specifically enabled in the per-world settings flag.
dofile(pipeworks.modpath..logicdir.."flowable_node_registry.lua")
dofile(pipeworks.modpath..logicdir.."abms.lua")
dofile(pipeworks.modpath..logicdir.."abm_register.lua")
dofile(pipeworks.modpath..logicdir.."flowable_node_registry_install.lua")
if pipeworks.enable_pipes then dofile(pipeworks.modpath.."/pipes.lua") end if pipeworks.enable_pipes then dofile(pipeworks.modpath.."/pipes.lua") end
if pipeworks.enable_teleport_tube then dofile(pipeworks.modpath.."/teleport_tube.lua") end if pipeworks.enable_teleport_tube then dofile(pipeworks.modpath.."/teleport_tube.lua") end
if pipeworks.enable_pipe_devices then dofile(pipeworks.modpath.."/devices.lua") end if pipeworks.enable_pipe_devices then dofile(pipeworks.modpath.."/devices.lua") end
-- individual enable flags also checked in register_flow_logic.lua
if pipeworks.enable_new_flow_logic then
dofile(pipeworks.modpath.."/new_flow_logic.lua")
dofile(pipeworks.modpath.."/register_flow_logic.lua")
end
if pipeworks.enable_redefines then if pipeworks.enable_redefines then
dofile(pipeworks.modpath.."/compat-chests.lua") dofile(pipeworks.modpath.."/compat-chests.lua")

@ -1,121 +0,0 @@
-- reimplementation of new_flow_logic branch: processing functions
-- written 2017 by thetaepsilon
-- global values and thresholds for water behaviour
-- TODO: add some way of setting this per-world
local thresholds = {}
-- limit on pump pressure - will not absorb more than can be taken
thresholds.pump_pressure = 2
-- borrowed from above: might be useable to replace the above coords tables
local make_coords_offsets = function(pos, include_base)
local coords = {
{x=pos.x,y=pos.y-1,z=pos.z},
{x=pos.x,y=pos.y+1,z=pos.z},
{x=pos.x-1,y=pos.y,z=pos.z},
{x=pos.x+1,y=pos.y,z=pos.z},
{x=pos.x,y=pos.y,z=pos.z-1},
{x=pos.x,y=pos.y,z=pos.z+1},
}
if include_base then table.insert(coords, pos) end
return coords
end
-- local debuglog = function(msg) print("## "..msg) end
-- new version of liquid check
-- accepts a limit parameter to only delete water blocks that the receptacle can accept,
-- and returns it so that the receptacle can update it's pressure values.
-- this should ensure that water blocks aren't vanished from existance.
-- will take care of zero or negative-valued limits.
pipeworks.check_for_liquids_v2 = function(pos, limit)
if not limit then
limit = 6
end
local coords = make_coords_offsets(pos, false)
local total = 0
for index, tpos in ipairs(coords) do
if total >= limit then break end
local name = minetest.get_node(tpos).name
if name == "default:water_source" then
minetest.remove_node(tpos)
total = total + 1
end
end
return total
end
local label_pressure = "pipeworks.water_pressure"
local label_haspressure = "pipeworks.is_pressure_node"
pipeworks.balance_pressure = function(pos, node)
-- debuglog("balance_pressure() "..node.name.." at "..pos.x.." "..pos.y.." "..pos.z)
-- check the pressure of all nearby nodes, and average it out.
-- for the moment, only balance neighbour nodes if it already has a pressure value.
-- XXX: maybe this could be used to add fluid behaviour to other mod's nodes too?
-- unconditionally include self in nodes to average over
local meta = minetest.get_meta(pos)
local currentpressure = meta:get_float(label_pressure)
meta:set_int(label_haspressure, 1)
local connections = { meta }
local totalv = currentpressure
local totalc = 1
-- then handle neighbours, but if not a pressure node don't consider them at all
for _, npos in ipairs(make_coords_offsets(pos, false)) do
local neighbour = minetest.get_meta(npos)
local haspressure = (neighbour:get_int(label_haspressure) ~= 0)
if haspressure then
local n = neighbour:get_float(label_pressure)
table.insert(connections, neighbour)
totalv = totalv + n
totalc = totalc + 1
end
end
local average = totalv / totalc
for _, targetmeta in ipairs(connections) do
targetmeta:set_float(label_pressure, average)
end
end
pipeworks.run_pump_intake = function(pos, node)
-- try to absorb nearby water nodes, but only up to limit.
-- NB: check_for_liquids_v2 handles zero or negative from the following subtraction
local meta = minetest.get_meta(pos)
local currentpressure = meta:get_float(label_pressure)
local intake_limit = thresholds.pump_pressure - currentpressure
local actual_intake = pipeworks.check_for_liquids_v2(pos, intake_limit)
local newpressure = actual_intake + currentpressure
-- debuglog("oldpressure "..currentpressure.." intake_limit "..intake_limit.." actual_intake "..actual_intake.." newpressure "..newpressure)
meta:set_float(label_pressure, newpressure)
end
pipeworks.run_spigot_output = function(pos, node)
-- try to output a water source node if there's enough pressure and space below.
local meta = minetest.get_meta(pos)
local currentpressure = meta:get_float(label_pressure)
if currentpressure > 1 then
local below = {x=pos.x, y=pos.y-1, z=pos.z}
local name = minetest.get_node(below).name
if (name == "air") or (name == "default:water_flowing") then
minetest.set_node(below, {name="default:water_source"})
meta:set_float(label_pressure, currentpressure - 1)
end
end
end

@ -0,0 +1,26 @@
-- register new flow logic ABMs
-- written 2017 by thetaepsilon
local register = {}
pipeworks.flowlogic.abmregister = register
local flowlogic = pipeworks.flowlogic
-- register node list for the main logic function.
-- see flowlogic.run() in abms.lua.
local register_flowlogic_abm = function(nodename)
if pipeworks.toggles.pressure_logic then
minetest.register_abm({
nodenames = { nodename },
interval = 1,
chance = 1,
action = function(pos, node, active_object_count, active_object_count_wider)
flowlogic.run(pos, node)
end
})
else
minetest.log("warning", "pipeworks pressure_logic not enabled but register.flowlogic() requested")
end
end
register.flowlogic = register_flowlogic_abm

315
new_flow_logic/abms.lua Normal file

@ -0,0 +1,315 @@
-- reimplementation of new_flow_logic branch: processing functions
-- written 2017 by thetaepsilon
local flowlogic = {}
flowlogic.helpers = {}
pipeworks.flowlogic = flowlogic
-- borrowed from above: might be useable to replace the above coords tables
local make_coords_offsets = function(pos, include_base)
local coords = {
{x=pos.x,y=pos.y-1,z=pos.z},
{x=pos.x,y=pos.y+1,z=pos.z},
{x=pos.x-1,y=pos.y,z=pos.z},
{x=pos.x+1,y=pos.y,z=pos.z},
{x=pos.x,y=pos.y,z=pos.z-1},
{x=pos.x,y=pos.y,z=pos.z+1},
}
if include_base then table.insert(coords, pos) end
return coords
end
-- local debuglog = function(msg) print("## "..msg) end
local formatvec = function(vec) local sep="," return "("..tostring(vec.x)..sep..tostring(vec.y)..sep..tostring(vec.z)..")" end
-- new version of liquid check
-- accepts a limit parameter to only delete water blocks that the receptacle can accept,
-- and returns it so that the receptacle can update it's pressure values.
local check_for_liquids_v2 = function(pos, limit)
local coords = make_coords_offsets(pos, false)
local total = 0
for index, tpos in ipairs(coords) do
if total >= limit then break end
local name = minetest.get_node(tpos).name
if name == "default:water_source" then
minetest.remove_node(tpos)
total = total + 1
end
end
--pipeworks.logger("check_for_liquids_v2@"..formatvec(pos).." total "..total)
return total
end
flowlogic.check_for_liquids_v2 = check_for_liquids_v2
local label_pressure = "pipeworks.water_pressure"
local get_pressure_access = function(pos)
local metaref = minetest.get_meta(pos)
return {
get = function()
return metaref:get_float(label_pressure)
end,
set = function(v)
metaref:set_float(label_pressure, v)
end
}
end
-- logging is unreliable when something is crashing...
local nilexplode = function(caller, label, value)
if value == nil then
error(caller..": "..label.." was nil")
end
end
local finitemode = pipeworks.toggles.finite_water
flowlogic.run = function(pos, node)
local nodename = node.name
-- get the current pressure value.
local nodepressure = get_pressure_access(pos)
local currentpressure = nodepressure.get()
local oldpressure = currentpressure
-- if node is an input: run intake phase
local inputdef = pipeworks.flowables.inputs.list[nodename]
if inputdef then
currentpressure = flowlogic.run_input(pos, node, currentpressure, inputdef)
--debuglog("post-intake currentpressure is "..currentpressure)
--nilexplode("run()", "currentpressure", currentpressure)
end
-- balance pressure with neighbours
currentpressure = flowlogic.balance_pressure(pos, node, currentpressure)
-- if node is an output: run output phase
local outputdef = pipeworks.flowables.outputs.list[nodename]
if outputdef then
currentpressure = flowlogic.run_output(
pos,
node,
currentpressure,
oldpressure,
outputdef,
finitemode)
end
-- if node has pressure transitions: determine new node
if pipeworks.flowables.transitions.list[nodename] then
local newnode = flowlogic.run_transition(node, currentpressure)
--pipeworks.logger("flowlogic.run()@"..formatvec(pos).." transition, new node name = "..dump(newnode).." pressure "..tostring(currentpressure))
minetest.swap_node(pos, newnode)
flowlogic.run_transition_post(pos, newnode)
end
-- set the new pressure
nodepressure.set(currentpressure)
end
flowlogic.balance_pressure = function(pos, node, currentpressure)
-- debuglog("balance_pressure() "..node.name.." at "..pos.x.." "..pos.y.." "..pos.z)
-- check the pressure of all nearby flowable nodes, and average it out.
-- pressure handles to average over
local connections = {}
-- unconditionally include self in nodes to average over.
-- result of averaging will be returned as new pressure for main flow logic callback
local totalv = currentpressure
local totalc = 1
-- then handle neighbours, but if not a pressure node don't consider them at all
for _, npos in ipairs(make_coords_offsets(pos, false)) do
local nodename = minetest.get_node(npos).name
-- for now, just check if it's in the simple table.
-- TODO: the "can flow from" logic in flowable_node_registry.lua
local haspressure = (pipeworks.flowables.list.simple[nodename])
if haspressure then
local neighbour = get_pressure_access(npos)
--pipeworks.logger("balance_pressure @ "..formatvec(pos).." "..nodename.." "..formatvec(npos).." added to neighbour set")
local n = neighbour.get()
table.insert(connections, neighbour)
totalv = totalv + n
totalc = totalc + 1
end
end
local average = totalv / totalc
for _, target in ipairs(connections) do
target.set(average)
end
return average
end
flowlogic.run_input = function(pos, node, currentpressure, inputdef)
-- intakefn allows a given input node to define it's own intake logic.
-- this function will calculate the maximum amount of water that can be taken in;
-- the intakefn will be given this and is expected to return the actual absorption amount.
local maxpressure = inputdef.maxpressure
local intake_limit = maxpressure - currentpressure
if intake_limit <= 0 then return currentpressure end
local actual_intake = inputdef.intakefn(pos, intake_limit)
--pipeworks.logger("run_input@"..formatvec(pos).." oldpressure "..currentpressure.." intake_limit "..intake_limit.." actual_intake "..actual_intake)
if actual_intake <= 0 then return currentpressure end
local newpressure = actual_intake + currentpressure
--debuglog("run_input() end, oldpressure "..currentpressure.." intake_limit "..intake_limit.." actual_intake "..actual_intake.." newpressure "..newpressure)
return newpressure
end
-- flowlogic output helper implementation:
-- outputs water by trying to place water nodes nearby in the world.
-- neighbours is a list of node offsets to try placing water in.
-- this is a constructor function, returning another function which satisfies the output helper requirements.
-- note that this does *not* take rotation into account.
flowlogic.helpers.make_neighbour_output_fixed = function(neighbours)
return function(pos, node, currentpressure, finitemode)
local taken = 0
for _, offset in pairs(neighbours) do
local npos = vector.add(pos, offset)
local name = minetest.get_node(npos).name
if currentpressure < 1 then break end
-- take pressure anyway in non-finite mode, even if node is water source already.
-- in non-finite mode, pressure has to be sustained to keep the sources there.
-- so in non-finite mode, placing water is dependent on the target node;
-- draining pressure is not.
local canplace = (name == "air") or (name == "default:water_flowing")
if canplace then
minetest.swap_node(npos, {name="default:water_source"})
end
if (not finitemode) or canplace then
taken = taken + 1
currentpressure = currentpressure - 1
end
end
return taken
end
end
-- complementary function to the above when using non-finite mode:
-- removes water sources from neighbor positions when the output is "off" due to lack of pressure.
flowlogic.helpers.make_neighbour_cleanup_fixed = function(neighbours)
return function(pos, node, currentpressure)
--pipeworks.logger("neighbour_cleanup_fixed@"..formatvec(pos))
for _, offset in pairs(neighbours) do
local npos = vector.add(pos, offset)
local name = minetest.get_node(npos).name
if (name == "default:water_source") then
--pipeworks.logger("neighbour_cleanup_fixed removing "..formatvec(npos))
minetest.remove_node(npos)
end
end
end
end
flowlogic.run_output = function(pos, node, currentpressure, oldpressure, outputdef, finitemode)
-- processing step for water output devices.
-- takes care of checking a minimum pressure value and updating the resulting pressure level
-- the outputfn is provided the current pressure and returns the pressure "taken".
-- as an example, using this with the above spigot function,
-- the spigot function tries to output a water source if it will fit in the world.
--pipeworks.logger("flowlogic.run_output() pos "..formatvec(pos).." old -> currentpressure "..tostring(oldpressure).." "..tostring(currentpressure).." finitemode "..tostring(finitemode))
local upper = outputdef.upper
local lower = outputdef.lower
local result = currentpressure
local threshold = nil
if finitemode then threshold = lower else threshold = upper end
if currentpressure > threshold then
local takenpressure = outputdef.outputfn(pos, node, currentpressure, finitemode)
local newpressure = currentpressure - takenpressure
if newpressure < 0 then newpressure = 0 end
result = newpressure
end
if (not finitemode) and (currentpressure < lower) and (oldpressure < lower) then
--pipeworks.logger("flowlogic.run_output() invoking cleanup currentpressure="..tostring(currentpressure))
outputdef.cleanupfn(pos, node, currentpressure)
end
return result
end
-- determine which node to switch to based on current pressure
flowlogic.run_transition = function(node, currentpressure)
local simplesetdef = pipeworks.flowables.transitions.simple[node.name]
local result = node
local found = false
-- simple transition sets: assumes all nodes in the set share param values.
if simplesetdef then
-- assumes that the set has been checked to contain at least one element...
local nodename_prev = simplesetdef[1].nodename
local result_nodename = node.name
for index, element in ipairs(simplesetdef) do
-- find the highest element that is below the current pressure.
local threshold = element.threshold
if threshold > currentpressure then
result_nodename = nodename_prev
found = true
break
end
nodename_prev = element.nodename
end
-- use last element if no threshold is greater than current pressure
if not found then
result_nodename = nodename_prev
found = true
end
-- preserve param1/param2 values
result = { name=result_nodename, param1=node.param1, param2=node.param2 }
end
if not found then
pipeworks.logger("flowlogic.run_transition() BUG no transition definitions found! nodename="..nodename.." currentpressure="..tostring(currentpressure))
end
return result
end
-- post-update hook for run_transition
-- among other things, updates mesecons if present.
-- node here means the new node, returned from run_transition() above
flowlogic.run_transition_post = function(pos, node)
local mesecons_def = minetest.registered_nodes[node.name].mesecons
local mesecons_rules = pipeworks.flowables.transitions.mesecons[node.name]
if minetest.global_exists("mesecon") and (mesecons_def ~= nil) and mesecons_rules then
if type(mesecons_def) ~= "table" then
pipeworks.logger("flowlogic.run_transition_post() BUG mesecons def for "..node.name.."not a table: got "..tostring(mesecons_def))
else
local receptor = mesecons_def.receptor
if receptor then
local state = receptor.state
if state == mesecon.state.on then
mesecon.receptor_on(pos, mesecons_rules)
elseif state == mesecon.state.off then
mesecon.receptor_off(pos, mesecons_rules)
end
end
end
end
end

@ -0,0 +1,48 @@
-- registry of flowable node behaviours in new flow logic
-- written 2017 by thetaepsilon
-- the actual registration functions which edit these tables can be found in flowable_node_registry_install.lua
-- this is because the ABM code needs to inspect these tables,
-- but the registration code needs to reference said ABM code.
-- so those functions were split out to resolve a circular dependency.
pipeworks.flowables = {}
pipeworks.flowables.list = {}
pipeworks.flowables.list.all = {}
-- pipeworks.flowables.list.nodenames = {}
-- simple flowables - balance pressure in any direction
pipeworks.flowables.list.simple = {}
pipeworks.flowables.list.simple_nodenames = {}
-- simple intakes - try to absorb any adjacent water nodes
pipeworks.flowables.inputs = {}
pipeworks.flowables.inputs.list = {}
pipeworks.flowables.inputs.nodenames = {}
-- outputs - takes pressure from pipes and update world to do something with it
pipeworks.flowables.outputs = {}
pipeworks.flowables.outputs.list = {}
-- not currently any nodenames arraylist for this one as it's not currently needed.
-- nodes with registered node transitions
-- nodes will be switched depending on pressure level
pipeworks.flowables.transitions = {}
pipeworks.flowables.transitions.list = {} -- master list
pipeworks.flowables.transitions.simple = {} -- nodes that change based purely on pressure
pipeworks.flowables.transitions.mesecons = {} -- table of mesecons rules to apply on transition
-- checks if a given node can flow in a given direction.
-- used to implement directional devices such as pumps,
-- which only visually connect in a certain direction.
-- node is the usual name + param structure.
-- direction is an x/y/z vector of the flow direction;
-- this function answers the question "can this node flow in this direction?"
pipeworks.flowables.flow_check = function(node, direction)
minetest.log("warning", "pipeworks.flowables.flow_check() stub!")
return true
end

@ -0,0 +1,188 @@
-- flowable node registry: add entries and install ABMs if new flow logic is enabled
-- written 2017 by thetaepsilon
-- use for hooking up ABMs as nodes are registered
local abmregister = pipeworks.flowlogic.abmregister
-- registration functions
pipeworks.flowables.register = {}
local register = pipeworks.flowables.register
-- some sanity checking for passed args, as this could potentially be made an external API eventually
local checkexists = function(nodename)
if type(nodename) ~= "string" then error("pipeworks.flowables nodename must be a string!") end
return pipeworks.flowables.list.all[nodename]
end
local insertbase = function(nodename)
if checkexists(nodename) then error("pipeworks.flowables duplicate registration!") end
pipeworks.flowables.list.all[nodename] = true
-- table.insert(pipeworks.flowables.list.nodenames, nodename)
end
local regwarning = function(kind, nodename)
local tail = ""
if not pipeworks.toggles.pressure_logic then tail = " but pressure logic not enabled" end
--pipeworks.logger(kind.." flow logic registry requested for "..nodename..tail)
end
-- Register a node as a simple flowable.
-- Simple flowable nodes have no considerations for direction of flow;
-- A cluster of adjacent simple flowables will happily average out in any direction.
register.simple = function(nodename)
insertbase(nodename)
pipeworks.flowables.list.simple[nodename] = true
table.insert(pipeworks.flowables.list.simple_nodenames, nodename)
if pipeworks.toggles.pressure_logic then
abmregister.flowlogic(nodename)
end
regwarning("simple", nodename)
end
local checkbase = function(nodename)
if not checkexists(nodename) then error("pipeworks.flowables node doesn't exist as a flowable!") end
end
local duplicateerr = function(kind, nodename) error(kind.." duplicate registration for "..nodename) end
-- Registers a node as a fluid intake.
-- intakefn is used to determine the water that can be taken in a node-specific way.
-- Expects node to be registered as a flowable (is present in flowables.list.all),
-- so that water can move out of it.
-- maxpressure is the maximum pipeline pressure that this node can drive;
-- if the input's node exceeds this the callback is not run.
-- possible WISHME here: technic-driven high-pressure pumps
register.intake = function(nodename, maxpressure, intakefn)
-- check for duplicate registration of this node
local list = pipeworks.flowables.inputs.list
checkbase(nodename)
if list[nodename] then duplicateerr("pipeworks.flowables.inputs", nodename) end
list[nodename] = { maxpressure=maxpressure, intakefn=intakefn }
regwarning("intake", nodename)
end
-- Register a node as a simple intake:
-- tries to absorb water source nodes from it's surroundings.
-- may exceed limit slightly due to needing to absorb whole nodes.
register.intake_simple = function(nodename, maxpressure)
register.intake(nodename, maxpressure, pipeworks.flowlogic.check_for_liquids_v2)
end
-- Register a node as an output.
-- Expects node to already be a flowable.
-- upper and lower thresholds have different meanings depending on whether finite liquid mode is in effect.
-- if not (the default unless auto-detected),
-- nodes above their upper threshold have their outputfn invoked (and pressure deducted),
-- nodes between upper and lower are left idle,
-- and nodes below lower have their cleanup fn invoked (to say remove water sources).
-- the upper and lower difference acts as a hysteresis to try and avoid "gaps" in the flow.
-- if finite mode is on, upper is ignored and lower is used to determine whether to run outputfn;
-- cleanupfn is ignored in this mode as finite mode assumes something causes water to move itself.
register.output = function(nodename, upper, lower, outputfn, cleanupfn)
if pipeworks.flowables.outputs.list[nodename] then
error("pipeworks.flowables.outputs duplicate registration!")
end
checkbase(nodename)
pipeworks.flowables.outputs.list[nodename] = {
upper=upper,
lower=lower,
outputfn=outputfn,
cleanupfn=cleanupfn,
}
-- output ABM now part of main flow logic ABM to preserve ordering.
-- note that because outputs have to be a flowable first
-- (and the installation of the flow logic ABM is conditional),
-- registered output nodes for new_flow_logic is also still conditional on the enable flag.
regwarning("output node", nodename)
end
-- register a simple output:
-- drains pressure by attempting to place water in nearby nodes,
-- which can be set by passing a list of offset vectors.
-- will attempt to drain as many whole nodes as there are positions in the offset list.
-- for meanings of upper and lower, see register.output() above.
-- non-finite mode:
-- above upper pressure: places water sources as appropriate, keeps draining pressure.
-- below lower presssure: removes it's neighbour water sources.
-- finite mode:
-- same as for above pressure in non-finite mode,
-- but only drains pressure when water source nodes are actually placed.
register.output_simple = function(nodename, upper, lower, neighbours)
local outputfn = pipeworks.flowlogic.helpers.make_neighbour_output_fixed(neighbours)
local cleanupfn = pipeworks.flowlogic.helpers.make_neighbour_cleanup_fixed(neighbours)
register.output(nodename, upper, lower, outputfn, cleanupfn)
end
-- common base checking for transition nodes
-- ensures the node has only been registered once as a transition.
local transition_list = pipeworks.flowables.transitions.list
local insert_transition_base = function(nodename)
checkbase(nodename)
if transition_list[nodename] then duplicateerr("base transition", nodename) end
transition_list[nodename] = true
end
-- register a simple transition set.
-- expects a table with nodenames as keys and threshold pressures as values.
-- internally, the table is sorted by value, and when one of these nodes needs to transition,
-- the table is searched starting from the lowest (even if it's value is non-zero),
-- until a value is found which is higher than or equal to the current node pressure.
-- ex. nodeset = { ["mod:level_0"] = 0, ["mod:level_1"] = 1, --[[ ... ]] }
local simpleseterror = function(msg)
error("register.transition_simple_set(): "..msg)
end
local simple_transitions = pipeworks.flowables.transitions.simple
register.transition_simple_set = function(nodeset, extras)
local set = {}
if extras == nil then extras = {} end
local length = #nodeset
if length < 2 then simpleseterror("nodeset needs at least two elements!") end
for index, element in ipairs(nodeset) do
if type(element) ~= "table" then simpleseterror("element "..tostring(index).." in nodeset was not table!") end
local nodename = element[1]
local value = element[2]
if type(nodename) ~= "string" then simpleseterror("nodename "..tostring(nodename).."was not a string!") end
if type(value) ~= "number" then simpleseterror("pressure value "..tostring(value).."was not a number!") end
insert_transition_base(nodename)
if simple_transitions[nodename] then duplicateerr("simple transition set", nodename) end
-- assigning set to table is done separately below
table.insert(set, { nodename=nodename, threshold=value })
end
-- sort pressure values, smallest first
local smallest_first = function(a, b)
return a.threshold < b.threshold
end
table.sort(set, smallest_first)
-- individual registration of each node, all sharing this set,
-- so each node in the set will transition to the correct target node.
for _, element in ipairs(set) do
--pipeworks.logger("register.transition_simple_set() after sort: nodename "..element.nodename.." value "..tostring(element.threshold))
simple_transitions[element.nodename] = set
end
-- handle extra options
-- if mesecons rules table was passed, set for each node
if extras.mesecons then
local mesecons_rules = pipeworks.flowables.transitions.mesecons
for _, element in ipairs(set) do
mesecons_rules[element.nodename] = extras.mesecons
end
end
end

@ -5,6 +5,8 @@ local REGISTER_COMPATIBILITY = true
local pipes_empty_nodenames = {} local pipes_empty_nodenames = {}
local pipes_full_nodenames = {} local pipes_full_nodenames = {}
local new_flow_logic_register = pipeworks.flowables.register
local vti = {4, 3, 2, 1, 6, 5} local vti = {4, 3, 2, 1, 6, 5}
local cconnects = {{}, {1}, {1, 2}, {1, 3}, {1, 3, 5}, {1, 2, 3}, {1, 2, 3, 5}, {1, 2, 3, 4}, {1, 2, 3, 4, 5}, {1, 2, 3, 4, 5, 6}} local cconnects = {{}, {1}, {1, 2}, {1, 3}, {1, 3, 5}, {1, 2, 3}, {1, 2, 3, 5}, {1, 2, 3, 4}, {1, 2, 3, 4, 5}, {1, 2, 3, 4, 5, 6}}
for index, connects in ipairs(cconnects) do for index, connects in ipairs(cconnects) do
@ -116,8 +118,12 @@ for index, connects in ipairs(cconnects) do
on_rotate = false on_rotate = false
}) })
table.insert(pipes_empty_nodenames, "pipeworks:pipe_"..index.."_empty") local emptypipe = "pipeworks:pipe_"..index.."_empty"
table.insert(pipes_full_nodenames, "pipeworks:pipe_"..index.."_loaded") local fullpipe = "pipeworks:pipe_"..index.."_loaded"
table.insert(pipes_empty_nodenames, emptypipe)
table.insert(pipes_full_nodenames, fullpipe)
new_flow_logic_register.simple(emptypipe)
new_flow_logic_register.simple(fullpipe)
end end
@ -182,14 +188,24 @@ if REGISTER_COMPATIBILITY then
}) })
end end
table.insert(pipes_empty_nodenames,"pipeworks:valve_on_empty")
table.insert(pipes_empty_nodenames,"pipeworks:valve_off_empty")
table.insert(pipes_empty_nodenames,"pipeworks:entry_panel_empty")
table.insert(pipes_empty_nodenames,"pipeworks:flow_sensor_empty")
table.insert(pipes_full_nodenames,"pipeworks:valve_on_loaded")
table.insert(pipes_full_nodenames,"pipeworks:entry_panel_loaded") local valve_on = "pipeworks:valve_on_empty"
table.insert(pipes_full_nodenames,"pipeworks:flow_sensor_loaded") local valve_off = "pipeworks:valve_off_empty"
local entry_panel_empty = "pipeworks:entry_panel_empty"
local flow_sensor_empty = "pipeworks:flow_sensor_empty"
-- XXX: why aren't these in devices.lua!?
table.insert(pipes_empty_nodenames, valve_on)
table.insert(pipes_empty_nodenames, valve_off)
table.insert(pipes_empty_nodenames, entry_panel_empty)
table.insert(pipes_empty_nodenames, flow_sensor_empty)
local valve_on_loaded = "pipeworks:valve_on_loaded"
local entry_panel_loaded = "pipeworks:entry_panel_loaded"
local flow_sensor_loaded = "pipeworks:flow_sensor_loaded"
table.insert(pipes_full_nodenames, valve_on_loaded)
table.insert(pipes_full_nodenames, entry_panel_loaded)
table.insert(pipes_full_nodenames, flow_sensor_loaded)
pipeworks.pipes_full_nodenames = pipes_full_nodenames pipeworks.pipes_full_nodenames = pipes_full_nodenames
pipeworks.pipes_empty_nodenames = pipes_empty_nodenames pipeworks.pipes_empty_nodenames = pipes_empty_nodenames
@ -197,8 +213,8 @@ pipeworks.pipes_empty_nodenames = pipes_empty_nodenames
if not pipeworks.enable_new_flow_logic then if not pipeworks.toggles.pressure_logic then
-- sorry, no indents... it messes with the patchlogs too much
minetest.register_abm({ minetest.register_abm({

@ -1,58 +0,0 @@
-- register new flow logic ABMs
-- written 2017 by thetaepsilon
local pipes_full_nodenames = pipeworks.pipes_full_nodenames
local pipes_empty_nodenames = pipeworks.pipes_empty_nodenames
-- run pressure balancing ABM over all water-moving nodes
-- FIXME: DRY principle, get this from elsewhere in the code
local pump_on = "pipeworks:pump_on"
local pump_off = "pipeworks:pump_off"
local spigot_off = "pipeworks:spigot"
local spigot_on = "pipeworks:spigot_pouring"
local pipes_all_nodenames = pipes_full_nodenames
for _, pipe in ipairs(pipes_empty_nodenames) do
table.insert(pipes_all_nodenames, pipe)
end
if pipeworks.enable_pipe_devices then
table.insert(pipes_all_nodenames, pump_off)
table.insert(pipes_all_nodenames, pump_on)
table.insert(pipes_all_nodenames, spigot_on)
table.insert(pipes_all_nodenames, spigot_off)
end
if pipeworks.enable_pipes then
minetest.register_abm({
nodenames = pipes_all_nodenames,
interval = 1,
chance = 1,
action = function(pos, node, active_object_count, active_object_count_wider)
pipeworks.balance_pressure(pos, node)
end
})
end
if pipeworks.enable_pipe_devices then
-- absorb water into pumps if it'll fit
minetest.register_abm({
nodenames = { pump_on },
interval = 1,
chance = 1,
action = function(pos, node, active_object_count, active_object_count_wider)
pipeworks.run_pump_intake(pos, node)
end
})
-- output water from spigots
-- add both "on/off" spigots so one can be used to indicate a certain level of fluid.
minetest.register_abm({
nodenames = { spigot_on, spigot_off },
interval = 1,
chance = 1,
action = function(pos, node, active_object_count, active_object_count_wider)
pipeworks.run_spigot_output(pos, node)
end
})
end

10
todo/new_flow_logic.txt Normal file

@ -0,0 +1,10 @@
-- Directionality code
Currently, only "simple" flowable nodes exist, and they will always equalise pressure with all six neighbours.
A more sophisticated behaviour for this would be flowable node registration with some kind of custom callback, such that water can only flow into or out of these nodes in certain directions.
This would enable devices such as the airtight panels, sensor tubes and valves to have correct flow behaviour.
-- (may not be possible) stop removing water nodes that were not placed by outputs when off
In non-finite mode, spigots and fountainheads will vanish water sources in their output positions, even if those output nodes did not place them there.
This is annoying though not game-breaking in non-finite mode, where water sources can at least be easily replenished.
Fixing this would require some kind of metadata marker on water nodes placed by spigots and fountains, such that only water sources placed while the device is "on" are removed when it is "off".
It is debateable whether existing water sources should be marked for removal when the device turns on again.