From aacd5ec829e531c808881021d5fe36aeedcfc2fd Mon Sep 17 00:00:00 2001 From: thetaepsilon-gamedev Date: Tue, 17 Oct 2017 14:20:55 +0100 Subject: rename new_flow_logic subdirectory to a less ambiguous name The "new flow logic" name was supposed to indicate that it was a continuation of the old branch by the same name, but it is beginning to become clear that it's not "new" any more and it might lead to confusion with "classic mode" flow logic while that still co-exists. Explicitly name the subdirectory "pressure logic" to give a better idea of what goes in it, init.lua edited accordingly. --- pressure_logic/abm_register.lua | 27 ++ pressure_logic/abms.lua | 342 ++++++++++++++++++++++ pressure_logic/flowable_node_registry.lua | 56 ++++ pressure_logic/flowable_node_registry_install.lua | 198 +++++++++++++ 4 files changed, 623 insertions(+) create mode 100644 pressure_logic/abm_register.lua create mode 100644 pressure_logic/abms.lua create mode 100644 pressure_logic/flowable_node_registry.lua create mode 100644 pressure_logic/flowable_node_registry_install.lua (limited to 'pressure_logic') diff --git a/pressure_logic/abm_register.lua b/pressure_logic/abm_register.lua new file mode 100644 index 0000000..a8e3abc --- /dev/null +++ b/pressure_logic/abm_register.lua @@ -0,0 +1,27 @@ +-- 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({ + label = "pipeworks new_flow_logic run", + 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 diff --git a/pressure_logic/abms.lua b/pressure_logic/abms.lua new file mode 100644 index 0000000..c14c124 --- /dev/null +++ b/pressure_logic/abms.lua @@ -0,0 +1,342 @@ +-- 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 + +-- create positions from list of offsets +-- see in use of directional flow logic below +local apply_coords_offsets = function(pos, offsets) + local result = {} + for index, offset in ipairs(offsets) do + table.insert(result, vector.add(pos, offset)) + end + return result +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) + -- local dname = "flowlogic.balance_pressure()@"..formatvec(pos).." " + -- 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 + + -- get list of node neighbours. + -- if this node is directional and only flows on certain sides, + -- invoke the callback to retrieve the set. + -- for simple flowables this is just an auto-gen'd list of all six possible neighbours. + local candidates = {} + if pipeworks.flowables.list.simple[node.name] then + candidates = make_coords_offsets(pos, false) + else + -- directional flowables: call the callback to get the list + local directional = pipeworks.flowables.list.directional[node.name] + if directional then + --pipeworks.logger(dname.."invoking neighbourfn") + local offsets = directional.neighbourfn(node) + candidates = apply_coords_offsets(pos, offsets) + end + end + + -- then handle neighbours, but if not a pressure node don't consider them at all + for _, npos in ipairs(candidates) 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 diff --git a/pressure_logic/flowable_node_registry.lua b/pressure_logic/flowable_node_registry.lua new file mode 100644 index 0000000..c60a39e --- /dev/null +++ b/pressure_logic/flowable_node_registry.lua @@ -0,0 +1,56 @@ +-- 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 = {} + +-- directional flowables - can only flow on certain sides +-- format per entry is a table with the following fields: +-- neighbourfn: function(node), +-- called to determine which nodes to consider as neighbours. +-- can be used to e.g. inspect the node's param values for facedir etc. +-- returns: array of vector offsets to look for possible neighbours in +pipeworks.flowables.list.directional = {} + +-- 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 diff --git a/pressure_logic/flowable_node_registry_install.lua b/pressure_logic/flowable_node_registry_install.lua new file mode 100644 index 0000000..a49c31a --- /dev/null +++ b/pressure_logic/flowable_node_registry_install.lua @@ -0,0 +1,198 @@ +-- 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) + if pipeworks.toggles.pressure_logic then + abmregister.flowlogic(nodename) + end +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) + regwarning("simple", nodename) +end + +-- Register a node as a directional flowable: +-- has a helper function which determines which nodes to consider valid neighbours. +register.directional = function(nodename, neighbourfn) + insertbase(nodename) + pipeworks.flowables.list.directional[nodename] = { neighbourfn = neighbourfn } + regwarning("directional", 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 -- cgit v1.2.3 From d68d3d5852ae01048c6c2702ac25530d7fd293a0 Mon Sep 17 00:00:00 2001 From: thetaepsilon-gamedev Date: Tue, 17 Oct 2017 22:01:29 +0100 Subject: pressure logic: abms.lua: move neighbour candidates calculation to separate function --- pressure_logic/abms.lua | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) (limited to 'pressure_logic') diff --git a/pressure_logic/abms.lua b/pressure_logic/abms.lua index c14c124..b79050b 100644 --- a/pressure_logic/abms.lua +++ b/pressure_logic/abms.lua @@ -130,17 +130,7 @@ end -flowlogic.balance_pressure = function(pos, node, currentpressure) - -- local dname = "flowlogic.balance_pressure()@"..formatvec(pos).." " - -- 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 - +local get_neighbour_positions = function(pos, node) -- get list of node neighbours. -- if this node is directional and only flows on certain sides, -- invoke the callback to retrieve the set. @@ -158,6 +148,24 @@ flowlogic.balance_pressure = function(pos, node, currentpressure) end end + return candidates +end + + + +flowlogic.balance_pressure = function(pos, node, currentpressure) + -- local dname = "flowlogic.balance_pressure()@"..formatvec(pos).." " + -- 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 + + local candidates = get_neighbour_positions(pos, node) + -- then handle neighbours, but if not a pressure node don't consider them at all for _, npos in ipairs(candidates) do local nodename = minetest.get_node(npos).name -- cgit v1.2.3 From 909b321f3c6c866f686ae31dfc59a4a932f4d9c6 Mon Sep 17 00:00:00 2001 From: thetaepsilon-gamedev Date: Tue, 17 Oct 2017 22:20:13 +0100 Subject: pressure logic: abms.lua: refactor balance_pressure() to move responsiblity for checking neighbour flow classes to get_neighbour_positions --- pressure_logic/abms.lua | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) (limited to 'pressure_logic') diff --git a/pressure_logic/abms.lua b/pressure_logic/abms.lua index b79050b..d6152dc 100644 --- a/pressure_logic/abms.lua +++ b/pressure_logic/abms.lua @@ -148,7 +148,20 @@ local get_neighbour_positions = function(pos, node) end end - return candidates + -- then, check each possible neighbour to see if they can be reached from this node. + -- for now, just check if it's in the simple table. + -- TODO: will need to add a single-face direction checking function for directional nodes + local connections = {} + for index, npos in ipairs(candidates) do + local nodename = minetest.get_node(npos).name + local is_simple = (pipeworks.flowables.list.simple[nodename]) + if is_simple then + local neighbour = get_pressure_access(npos) + table.insert(connections, neighbour) + end + end + + return connections end @@ -164,24 +177,14 @@ flowlogic.balance_pressure = function(pos, node, currentpressure) local totalv = currentpressure local totalc = 1 - local candidates = get_neighbour_positions(pos, node) + local connections = get_neighbour_positions(pos, node) - -- then handle neighbours, but if not a pressure node don't consider them at all - for _, npos in ipairs(candidates) 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 + -- for each neighbour, add neighbour's pressure to the total to balance out + for _, neighbour in ipairs(connections) do + local n = neighbour.get() + totalv = totalv + n + totalc = totalc + 1 end - local average = totalv / totalc for _, target in ipairs(connections) do target.set(average) -- cgit v1.2.3 From 0a4d15d26ecc33315a5d088eace532ca3e539bbb Mon Sep 17 00:00:00 2001 From: thetaepsilon-gamedev Date: Tue, 17 Oct 2017 23:14:26 +0100 Subject: pressure logic: flowable node registry: add directionfn to directional flowable entries --- pressure_logic/flowable_node_registry.lua | 23 ++++++++++------------- pressure_logic/flowable_node_registry_install.lua | 7 +++++-- 2 files changed, 15 insertions(+), 15 deletions(-) (limited to 'pressure_logic') diff --git a/pressure_logic/flowable_node_registry.lua b/pressure_logic/flowable_node_registry.lua index c60a39e..6d7bf17 100644 --- a/pressure_logic/flowable_node_registry.lua +++ b/pressure_logic/flowable_node_registry.lua @@ -23,6 +23,16 @@ pipeworks.flowables.list.simple_nodenames = {} -- called to determine which nodes to consider as neighbours. -- can be used to e.g. inspect the node's param values for facedir etc. -- returns: array of vector offsets to look for possible neighbours in +-- directionfn: function(node, vector): +-- can this node flow in this direction? +-- called in the context of another node to check the matching entry returned by neighbourfn. +-- for every offset vector returned by neighbourfn, +-- the node at that absolute position is checked. +-- if that node is also a directional flowable, +-- then that node's vector is passed to that node's directionfn +-- (inverted, so that directionfn sees a vector pointing out from it back to the origin node). +-- if directionfn agrees that the neighbour node can currently flow in that direction, +-- the neighbour is to participate in pressure balancing. pipeworks.flowables.list.directional = {} -- simple intakes - try to absorb any adjacent water nodes @@ -41,16 +51,3 @@ 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 diff --git a/pressure_logic/flowable_node_registry_install.lua b/pressure_logic/flowable_node_registry_install.lua index a49c31a..3cd9c4d 100644 --- a/pressure_logic/flowable_node_registry_install.lua +++ b/pressure_logic/flowable_node_registry_install.lua @@ -43,9 +43,12 @@ end -- Register a node as a directional flowable: -- has a helper function which determines which nodes to consider valid neighbours. -register.directional = function(nodename, neighbourfn) +register.directional = function(nodename, neighbourfn, directionfn) insertbase(nodename) - pipeworks.flowables.list.directional[nodename] = { neighbourfn = neighbourfn } + pipeworks.flowables.list.directional[nodename] = { + neighbourfn = neighbourfn, + directionfn = directionfn + } regwarning("directional", nodename) end -- cgit v1.2.3 From 084bbc6c0b098235b6a402d04a3b57f69cede9ac Mon Sep 17 00:00:00 2001 From: thetaepsilon-gamedev Date: Wed, 18 Oct 2017 09:32:33 +0100 Subject: pressure logic: abms.lua: get_neighbour_positions: move calculation of absolute world position to neighbour probing for-loop This allows the raw offset to be visible inside that for-loop, which will be needed for calling the directionfn for directional neighbours to determine if they can flow in the given direction. --- pressure_logic/abms.lua | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) (limited to 'pressure_logic') diff --git a/pressure_logic/abms.lua b/pressure_logic/abms.lua index d6152dc..bed96b8 100644 --- a/pressure_logic/abms.lua +++ b/pressure_logic/abms.lua @@ -23,16 +23,6 @@ local make_coords_offsets = function(pos, include_base) return coords end --- create positions from list of offsets --- see in use of directional flow logic below -local apply_coords_offsets = function(pos, offsets) - local result = {} - for index, offset in ipairs(offsets) do - table.insert(result, vector.add(pos, offset)) - end - return result -end - -- local debuglog = function(msg) print("## "..msg) end @@ -41,6 +31,8 @@ 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. @@ -130,6 +122,14 @@ end +local simple_neighbour_offsets = { + {x=0, y=-1,z= 0}, + {x=0, y= 1,z= 0}, + {x=-1,y= 0,z= 0}, + {x= 1,y= 0,z= 0}, + {x= 0,y= 0,z=-1}, + {x= 0,y= 0,z= 1}, +} local get_neighbour_positions = function(pos, node) -- get list of node neighbours. -- if this node is directional and only flows on certain sides, @@ -137,14 +137,14 @@ local get_neighbour_positions = function(pos, node) -- for simple flowables this is just an auto-gen'd list of all six possible neighbours. local candidates = {} if pipeworks.flowables.list.simple[node.name] then - candidates = make_coords_offsets(pos, false) + candidates = simple_neighbour_offsets else -- directional flowables: call the callback to get the list local directional = pipeworks.flowables.list.directional[node.name] if directional then --pipeworks.logger(dname.."invoking neighbourfn") local offsets = directional.neighbourfn(node) - candidates = apply_coords_offsets(pos, offsets) + candidates = offsets end end @@ -152,7 +152,8 @@ local get_neighbour_positions = function(pos, node) -- for now, just check if it's in the simple table. -- TODO: will need to add a single-face direction checking function for directional nodes local connections = {} - for index, npos in ipairs(candidates) do + for index, offset in ipairs(candidates) do + local npos = vector.add(pos, offset) local nodename = minetest.get_node(npos).name local is_simple = (pipeworks.flowables.list.simple[nodename]) if is_simple then -- cgit v1.2.3 From 653aaffa3e56e0ac1dddd59f4604ca6e9b3fdcbe Mon Sep 17 00:00:00 2001 From: thetaepsilon-gamedev Date: Wed, 18 Oct 2017 11:32:16 +0100 Subject: pressure logic/abms.lua: rename local variables and retain node data in connection check for-loop --- pressure_logic/abms.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'pressure_logic') diff --git a/pressure_logic/abms.lua b/pressure_logic/abms.lua index bed96b8..16bdb2d 100644 --- a/pressure_logic/abms.lua +++ b/pressure_logic/abms.lua @@ -154,14 +154,15 @@ local get_neighbour_positions = function(pos, node) local connections = {} for index, offset in ipairs(candidates) do local npos = vector.add(pos, offset) - local nodename = minetest.get_node(npos).name + local neighbour = minetest.get_node(npos) + local nodename = neighbour.name local is_simple = (pipeworks.flowables.list.simple[nodename]) if is_simple then - local neighbour = get_pressure_access(npos) - table.insert(connections, neighbour) + local n = get_pressure_access(npos) + table.insert(connections, n) end end - + return connections end -- cgit v1.2.3 From 0a97abcaf64e4868cb9780172943d5b90b8da0a7 Mon Sep 17 00:00:00 2001 From: thetaepsilon-gamedev Date: Wed, 18 Oct 2017 11:57:31 +0100 Subject: pressure logic/abms.lua: implement testing of flow direction testing in get_neighbour_positions() --- pressure_logic/abms.lua | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) (limited to 'pressure_logic') diff --git a/pressure_logic/abms.lua b/pressure_logic/abms.lua index 16bdb2d..ba3906d 100644 --- a/pressure_logic/abms.lua +++ b/pressure_logic/abms.lua @@ -131,6 +131,7 @@ local simple_neighbour_offsets = { {x= 0,y= 0,z= 1}, } local get_neighbour_positions = function(pos, node) + -- local dname = "get_neighbour_positions@"..formatvec(pos).." " -- get list of node neighbours. -- if this node is directional and only flows on certain sides, -- invoke the callback to retrieve the set. @@ -149,8 +150,6 @@ local get_neighbour_positions = function(pos, node) end -- then, check each possible neighbour to see if they can be reached from this node. - -- for now, just check if it's in the simple table. - -- TODO: will need to add a single-face direction checking function for directional nodes local connections = {} for index, offset in ipairs(candidates) do local npos = vector.add(pos, offset) @@ -160,6 +159,20 @@ local get_neighbour_positions = function(pos, node) if is_simple then local n = get_pressure_access(npos) table.insert(connections, n) + else + -- if target node is also directional, check if it agrees it can flow in that direction + local directional = pipeworks.flowables.list.directional[nodename] + if directional then + --pipeworks.logger(dname.."directionality test for offset "..formatvec(offset)) + local towards_origin = vector.multiply(offset, -1) + --pipeworks.logger(dname.."vector passed to directionfn: "..formatvec(towards_origin)) + local result = directional.directionfn(node, towards_origin) + --pipeworks.logger(dname.."result: "..tostring(result)) + if result then + local n = get_pressure_access(npos) + table.insert(connections, n) + end + end end end -- cgit v1.2.3 From 7f7dfb79d5f81dfe09920b8c872e1a11f22bff43 Mon Sep 17 00:00:00 2001 From: thetaepsilon-gamedev Date: Wed, 18 Oct 2017 21:19:59 +0100 Subject: pressure logic/flowable node registry: move pump directionality code to dedicated fixed vertical helper --- pressure_logic/flowable_node_registry_install.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'pressure_logic') diff --git a/pressure_logic/flowable_node_registry_install.lua b/pressure_logic/flowable_node_registry_install.lua index 3cd9c4d..d8f945b 100644 --- a/pressure_logic/flowable_node_registry_install.lua +++ b/pressure_logic/flowable_node_registry_install.lua @@ -52,6 +52,20 @@ register.directional = function(nodename, neighbourfn, directionfn) regwarning("directional", nodename) end +-- register a node as a directional flowable that can only flow through either the top or bottom side. +-- used for fountainheads (bottom side) and pumps (top side). +-- this is in world terms, not facedir relative! +register.directional_vertical_fixed = function(nodename, topside) + local y + if topside then y = 1 else y = -1 end + local side = { x=0, y=y, z=0 } + local neighbourfn = function(node) return { side } end + local directionfn = function(node, direction) + return vector.equals(direction, side) + end + register.directional(nodename, neighbourfn, directionfn) +end + local checkbase = function(nodename) -- cgit v1.2.3 From efcec7bdcee9d4cb955ffb6bcc1cadaf342889ae Mon Sep 17 00:00:00 2001 From: thetaepsilon-gamedev Date: Thu, 19 Oct 2017 12:13:44 +0100 Subject: pressure logic: abms.lua: fix invocation bug for directionality callback accidentally passing origin data to neighbour's directionfn --- pressure_logic/abms.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pressure_logic') diff --git a/pressure_logic/abms.lua b/pressure_logic/abms.lua index ba3906d..083d8c3 100644 --- a/pressure_logic/abms.lua +++ b/pressure_logic/abms.lua @@ -166,7 +166,7 @@ local get_neighbour_positions = function(pos, node) --pipeworks.logger(dname.."directionality test for offset "..formatvec(offset)) local towards_origin = vector.multiply(offset, -1) --pipeworks.logger(dname.."vector passed to directionfn: "..formatvec(towards_origin)) - local result = directional.directionfn(node, towards_origin) + local result = directional.directionfn(neighbour, towards_origin) --pipeworks.logger(dname.."result: "..tostring(result)) if result then local n = get_pressure_access(npos) -- cgit v1.2.3 From 0913098a9de262050ccde0bdd221dd1a12d1a785 Mon Sep 17 00:00:00 2001 From: thetaepsilon-gamedev Date: Thu, 19 Oct 2017 12:28:33 +0100 Subject: pressure logic: add horizontally-rotating directional flowable helper --- pressure_logic/flowable_node_registry_install.lua | 37 +++++++++++++++++++++++ 1 file changed, 37 insertions(+) (limited to 'pressure_logic') diff --git a/pressure_logic/flowable_node_registry_install.lua b/pressure_logic/flowable_node_registry_install.lua index d8f945b..7b14fd3 100644 --- a/pressure_logic/flowable_node_registry_install.lua +++ b/pressure_logic/flowable_node_registry_install.lua @@ -66,6 +66,43 @@ register.directional_vertical_fixed = function(nodename, topside) register.directional(nodename, neighbourfn, directionfn) end +-- register a node as a directional flowable whose accepting sides depends upon param2 rotation. +-- used for entry panels, valves, flow sensors and spigots, +-- whose facing axis is always upwards and can only rotate horizontally. +local iseastwest = function(node) + local data = node.param2 + local rotation = data % 4 + -- rotation 0 and 2 is the same axis, as is 1 and 3. + -- 0-3 starts at north and proceeds clockwise. + local axis = rotation % 2 + --pipeworks.logger("iseastwest() rotation="..tostring(rotation).." axis="..tostring(axis)) + return (axis == 1) +end +register.directional_horizonal_rotate = function(nodename) + local north = {x= 0,y= 0,z= 1} + local south = {x= 0,y= 0,z=-1} + local east = {x= 1,y= 0,z= 0} + local west = {x=-1,y= 0,z= 0} + local neighbourfn = function(node) + if iseastwest(node) then + return { east, west } + else + return { north, south } + end + end + local directionfn = function(node, direction) + local result = false + if iseastwest(node) then + --pipeworks.logger("horizontal rotate directionfn() eastwest=true") + result = vector.equals(direction, east) or vector.equals(direction, west) + else + result = vector.equals(direction, north) or vector.equals(direction, south) + end + return result + end + register.directional(nodename, neighbourfn, directionfn) +end + local checkbase = function(nodename) -- cgit v1.2.3