From 15bb7b129c7d9dabba3617a5f17cf3ddbf0d6154 Mon Sep 17 00:00:00 2001 From: Christopher Head Date: Sun, 15 Oct 2017 22:56:06 -0700 Subject: Enhance params to can_remove and remove_item MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit By passing the list name and the slot index, these functions now receive all data related to removal of an item from an inventory: the side on which the removal is taking place, as well as which stack is being pulled from. This means it’s no longer necessary to choose between implementing `on_metadata_inventory_take` (which tells you which item stack was pulled from but not from which side of the node) or `remove_item` (which tells you which side the filter is on but not which item stack it wants to take). --- filter-injector.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/filter-injector.lua b/filter-injector.lua index c9d132f..0f651fb 100644 --- a/filter-injector.lua +++ b/filter-injector.lua @@ -115,7 +115,7 @@ local function grabAndFire(data,slotseq_mode,exmatch_mode,filtmeta,frominv,fromi local stack = frominv:get_stack(frominvname, spos) local doRemove = stack:get_count() if fromtube.can_remove then - doRemove = fromtube.can_remove(frompos, fromnode, stack, dir) + doRemove = fromtube.can_remove(frompos, fromnode, stack, dir, frominvname, spos) elseif fromdef.allow_metadata_inventory_take then doRemove = fromdef.allow_metadata_inventory_take(frompos, frominvname,spos, stack, fakePlayer) end @@ -146,7 +146,7 @@ local function grabAndFire(data,slotseq_mode,exmatch_mode,filtmeta,frominv,fromi end if fromtube.remove_items then -- it could be the entire stack... - item = fromtube.remove_items(frompos, fromnode, stack, dir, count) + item = fromtube.remove_items(frompos, fromnode, stack, dir, count, frominvname, spos) else item = stack:take_item(count) frominv:set_stack(frominvname, spos, stack) -- cgit v1.2.3 From 0e74978a73578d15fb75ed1d6948581ddf970d9c Mon Sep 17 00:00:00 2001 From: thetaepsilon-gamedev Date: Mon, 16 Oct 2017 21:01:09 +0100 Subject: new flow logic: abm_register.lua: give core ABM a label --- new_flow_logic/abm_register.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/new_flow_logic/abm_register.lua b/new_flow_logic/abm_register.lua index 1d038d6..a8e3abc 100644 --- a/new_flow_logic/abm_register.lua +++ b/new_flow_logic/abm_register.lua @@ -12,6 +12,7 @@ local flowlogic = pipeworks.flowlogic 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, -- cgit v1.2.3 From e41167813ba15344e56a291f2cc03706cb62590e Mon Sep 17 00:00:00 2001 From: thetaepsilon-gamedev Date: Mon, 16 Oct 2017 23:18:00 +0100 Subject: new flow logic: flowable node registry: add directional flow type class --- new_flow_logic/flowable_node_registry.lua | 8 ++++++++ new_flow_logic/flowable_node_registry_install.lua | 16 +++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/new_flow_logic/flowable_node_registry.lua b/new_flow_logic/flowable_node_registry.lua index 2523803..c60a39e 100644 --- a/new_flow_logic/flowable_node_registry.lua +++ b/new_flow_logic/flowable_node_registry.lua @@ -17,6 +17,14 @@ pipeworks.flowables.list.all = {} 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 = {} diff --git a/new_flow_logic/flowable_node_registry_install.lua b/new_flow_logic/flowable_node_registry_install.lua index c8f6889..a49c31a 100644 --- a/new_flow_logic/flowable_node_registry_install.lua +++ b/new_flow_logic/flowable_node_registry_install.lua @@ -20,6 +20,9 @@ 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) @@ -35,12 +38,19 @@ 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 +-- 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 -- cgit v1.2.3 From fd978204dd5c72d350ba0d31ec43c628c46599f3 Mon Sep 17 00:00:00 2001 From: thetaepsilon-gamedev Date: Mon, 16 Oct 2017 23:27:04 +0100 Subject: new flow logic: abms.lua: use directional callback function for direcional nodes to obtain neighbour list --- new_flow_logic/abms.lua | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/new_flow_logic/abms.lua b/new_flow_logic/abms.lua index 0b0b799..e5e2e6e 100644 --- a/new_flow_logic/abms.lua +++ b/new_flow_logic/abms.lua @@ -23,6 +23,16 @@ 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 @@ -138,6 +148,13 @@ flowlogic.balance_pressure = function(pos, node, currentpressure) 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 + 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 -- cgit v1.2.3 From c55374cdfadd9cc04db63908c7ac9cf8ec6aff21 Mon Sep 17 00:00:00 2001 From: thetaepsilon-gamedev Date: Mon, 16 Oct 2017 23:39:30 +0100 Subject: devices.lua: convert pump to use new directional flow class --- devices.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/devices.lua b/devices.lua index 5203bf3..f1d1dad 100644 --- a/devices.lua +++ b/devices.lua @@ -161,11 +161,10 @@ for s in ipairs(states) do local fdir = node.param2 minetest.swap_node(pos, { name = "pipeworks:pump_"..states[3-s], param2 = fdir }) end, - -- FIXME - does this preserve metadata? need to look at this on_rotate = screwdriver.rotate_simple }) - -- FIXME: currently a simple flow device, but needs directionality checking - new_flow_logic_register.simple(pumpname) + -- FIXME: this currently assumes that pumps can only rotate around the fixed axis pointing Y+. + new_flow_logic_register.directional(pumpname, function(node) return { {x=0,y=1,z=0} } end) local pump_drive = 4 if states[s] ~= "off" then new_flow_logic_register.intake_simple(pumpname, pump_drive) -- cgit v1.2.3 From 0dd1dbc901a6885e74c26ce80fa95fb91dc6c6f1 Mon Sep 17 00:00:00 2001 From: thetaepsilon-gamedev Date: Mon, 16 Oct 2017 23:41:03 +0100 Subject: new flow logic: abms.lua: directional flow logic trace log points for local debugging --- new_flow_logic/abms.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/new_flow_logic/abms.lua b/new_flow_logic/abms.lua index e5e2e6e..c14c124 100644 --- a/new_flow_logic/abms.lua +++ b/new_flow_logic/abms.lua @@ -131,7 +131,7 @@ end flowlogic.balance_pressure = function(pos, node, currentpressure) - -- debuglog("balance_pressure() "..node.name.." at "..pos.x.." "..pos.y.." "..pos.z) + -- local dname = "flowlogic.balance_pressure()@"..formatvec(pos).." " -- check the pressure of all nearby flowable nodes, and average it out. -- pressure handles to average over @@ -152,6 +152,7 @@ flowlogic.balance_pressure = function(pos, node, currentpressure) -- 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 -- cgit v1.2.3 From 7bacbdf0150d4aee1244ec2ad7076ccd2da7956d Mon Sep 17 00:00:00 2001 From: thetaepsilon-gamedev Date: Mon, 16 Oct 2017 23:44:17 +0100 Subject: todo: new flow logic: mark directionality code WIP --- todo/new_flow_logic.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/todo/new_flow_logic.txt b/todo/new_flow_logic.txt index b6787e9..20609ab 100644 --- a/todo/new_flow_logic.txt +++ b/todo/new_flow_logic.txt @@ -1,4 +1,4 @@ --- Directionality code +-- Directionality code (in progress) 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. -- cgit v1.2.3 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. --- init.lua | 2 +- new_flow_logic/abm_register.lua | 27 -- new_flow_logic/abms.lua | 342 ---------------------- new_flow_logic/flowable_node_registry.lua | 56 ---- new_flow_logic/flowable_node_registry_install.lua | 198 ------------- 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 +++++++++++++ 9 files changed, 624 insertions(+), 624 deletions(-) delete mode 100644 new_flow_logic/abm_register.lua delete mode 100644 new_flow_logic/abms.lua delete mode 100644 new_flow_logic/flowable_node_registry.lua delete mode 100644 new_flow_logic/flowable_node_registry_install.lua 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 diff --git a/init.lua b/init.lua index 8974005..f4276f8 100644 --- a/init.lua +++ b/init.lua @@ -126,7 +126,7 @@ dofile(pipeworks.modpath.."/filter-injector.lua") dofile(pipeworks.modpath.."/trashcan.lua") dofile(pipeworks.modpath.."/wielder.lua") -local logicdir = "/new_flow_logic/" +local logicdir = "/pressure_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, diff --git a/new_flow_logic/abm_register.lua b/new_flow_logic/abm_register.lua deleted file mode 100644 index a8e3abc..0000000 --- a/new_flow_logic/abm_register.lua +++ /dev/null @@ -1,27 +0,0 @@ --- 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/new_flow_logic/abms.lua b/new_flow_logic/abms.lua deleted file mode 100644 index c14c124..0000000 --- a/new_flow_logic/abms.lua +++ /dev/null @@ -1,342 +0,0 @@ --- 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/new_flow_logic/flowable_node_registry.lua b/new_flow_logic/flowable_node_registry.lua deleted file mode 100644 index c60a39e..0000000 --- a/new_flow_logic/flowable_node_registry.lua +++ /dev/null @@ -1,56 +0,0 @@ --- 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/new_flow_logic/flowable_node_registry_install.lua b/new_flow_logic/flowable_node_registry_install.lua deleted file mode 100644 index a49c31a..0000000 --- a/new_flow_logic/flowable_node_registry_install.lua +++ /dev/null @@ -1,198 +0,0 @@ --- 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 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 a7c171940e2a8ac5333095837a6ede80644453a6 Mon Sep 17 00:00:00 2001 From: thetaepsilon-gamedev Date: Tue, 17 Oct 2017 14:53:32 +0100 Subject: todo: add item for supporting other fluid types --- todo/new_flow_logic.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/todo/new_flow_logic.txt b/todo/new_flow_logic.txt index 20609ab..41f7498 100644 --- a/todo/new_flow_logic.txt +++ b/todo/new_flow_logic.txt @@ -20,3 +20,19 @@ VanessaE would like this to function as an output with the following properties: * While on, tries to randomly place a water source in an adjacent node every ABM interval, preferring to place it downwards first. * Even with multiple water source nodes placed, only drains 1 unit pressure per ABM interval in non-finite mode. Finite mode will cause it to drain 1 unit per water source node placed and simply stop placing sources when below threshold pressure, like spigots and fountainheads already do. * When turning off in non-finite mode, for all neighbour nodes, replace the water sources with flowing water as discussed above, but *only* if those neighbouring sources do not have any water source neighbours of their own in turn - this will prevent the block from creating a "hole" in a uniform pool of water sources. + + + +-- Support for other fluids in pipes (Feature request/wish list) +Various sources from IRC and github issues have indicated that the ability to carry amounts of substance other than water through pipes would see uses appear for it if it were implemented (there does not appear to be anything trying to do so right now). +Extending the pressure mechanism to handle new fluids would be simple enough, it would just have to deal with more variables. +However, this feature raises the question of how to handle mixtures of fluids in pipes. + +Two possible solutions appear evident: ++ Don't mix at all. For each flowable registered, either a variant would be created for each supported liquid, or each node would declare which fluid it carries explicitly. Flowable nodes for different fluids would not interact with each other at all. + ++ Equalise "pressure" of multiple fluid variables in a similar manner to how the single water pressure value is currently balanced out, however this raises the issue of how to deal with mixtures - for instance, how is a spigot to function with a mixed liquid? does it simply refuse to function if it doesn't know how to deal with a certain mixture (as it can only output whole nodes)? likewise for certain mixtures in pipes, should it be allowed for lava and water for instance to mix like they don't interact at all? + +This mechanism also hints at a weakness of the pressure logic mechanism as it currently stands - namely that liquids are handled like gases and assumed to be compressible. + + -- 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(-) 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(-) 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(-) 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 a69c5e24a9771dc1818a01092730104c62703908 Mon Sep 17 00:00:00 2001 From: thetaepsilon-gamedev Date: Tue, 17 Oct 2017 23:42:05 +0100 Subject: devices.lua: implement directionfn for pump registration --- devices.lua | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/devices.lua b/devices.lua index f1d1dad..d9edcad 100644 --- a/devices.lua +++ b/devices.lua @@ -163,13 +163,22 @@ for s in ipairs(states) do end, on_rotate = screwdriver.rotate_simple }) + -- FIXME: this currently assumes that pumps can only rotate around the fixed axis pointing Y+. - new_flow_logic_register.directional(pumpname, function(node) return { {x=0,y=1,z=0} } end) + -- TODO: these directionality functions should be behind a helper so the fountainhead can use something similar. + local upwards = {x=0,y=1,z=0} + local neighbourfn = function(node) return { upwards } end + local directionfn = function(node, direction) + return vector.equals(direction, upwards) + end + new_flow_logic_register.directional(pumpname, neighbourfn, directionfn) local pump_drive = 4 if states[s] ~= "off" then new_flow_logic_register.intake_simple(pumpname, pump_drive) end + + local nodename_valve_empty = "pipeworks:valve_"..states[s].."_empty" minetest.register_node(nodename_valve_empty, { description = "Valve", -- 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(-) 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(-) 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(-) 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 38a893ec829e834ab0f24c709abda13b38fba205 Mon Sep 17 00:00:00 2001 From: thetaepsilon-gamedev Date: Wed, 18 Oct 2017 12:06:00 +0100 Subject: todo: update item for pressure logic directionality code --- todo/new_flow_logic.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/todo/new_flow_logic.txt b/todo/new_flow_logic.txt index 41f7498..41ce2f8 100644 --- a/todo/new_flow_logic.txt +++ b/todo/new_flow_logic.txt @@ -1,7 +1,9 @@ -- Directionality code (in progress) -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. +The flowable node class for directional nodes now exists and is hooked up in the code for determining valid neighbours in the flowable node ABM routines. +Pumps have been converted to this as part of the testing. +However, currently only the "raw" registration for this is available, and the pump definition registers it's own callback routines. +Helpers need to be added to flowable_node_registry_install.lua to abstract away the expression of which nodes can flow which ways - valves, flow sensors, spigots and entry panels, for instance, all currently rotate the same way using an upwards facedir, so a helper for these nodes would prevent code duplication. + -- (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. -- cgit v1.2.3 From 7b141fb0ea216e2ca49eb4d2246bca9f8689c5d0 Mon Sep 17 00:00:00 2001 From: thetaepsilon-gamedev Date: Wed, 18 Oct 2017 12:06:48 +0100 Subject: todo: rename file for new flow logic in line with aacd5ec --- todo/new_flow_logic.txt | 40 ---------------------------------------- todo/pressure_logic.txt | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 40 deletions(-) delete mode 100644 todo/new_flow_logic.txt create mode 100644 todo/pressure_logic.txt diff --git a/todo/new_flow_logic.txt b/todo/new_flow_logic.txt deleted file mode 100644 index 41ce2f8..0000000 --- a/todo/new_flow_logic.txt +++ /dev/null @@ -1,40 +0,0 @@ --- Directionality code (in progress) -The flowable node class for directional nodes now exists and is hooked up in the code for determining valid neighbours in the flowable node ABM routines. -Pumps have been converted to this as part of the testing. -However, currently only the "raw" registration for this is available, and the pump definition registers it's own callback routines. -Helpers need to be added to flowable_node_registry_install.lua to abstract away the expression of which nodes can flow which ways - valves, flow sensors, spigots and entry panels, for instance, all currently rotate the same way using an upwards facedir, so a helper for these nodes would prevent code duplication. - - --- (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. - --- Make spigots and fountainheads deactivate by placing a flowing water node -Currently, in non-finite mode, the spigots and fountainheads react to pressure dropping below threshold by deleting the water source they placed. -VanessaE would like this changed so that these nodes replace the water source with a flowing water source, such that it drains away at the same rate as surrounding water. -This should be a simple case of modifying the cleanup handler that is installed for these nodes by the helper that they use, to place flowing water instead of air in the situations where it would normally do so. - --- Decorative grating functionality -The decorative grating block currently is just that - purely decorative, it serves no purpose. -VanessaE would like this to function as an output with the following properties: -* While on, tries to randomly place a water source in an adjacent node every ABM interval, preferring to place it downwards first. -* Even with multiple water source nodes placed, only drains 1 unit pressure per ABM interval in non-finite mode. Finite mode will cause it to drain 1 unit per water source node placed and simply stop placing sources when below threshold pressure, like spigots and fountainheads already do. -* When turning off in non-finite mode, for all neighbour nodes, replace the water sources with flowing water as discussed above, but *only* if those neighbouring sources do not have any water source neighbours of their own in turn - this will prevent the block from creating a "hole" in a uniform pool of water sources. - - - --- Support for other fluids in pipes (Feature request/wish list) -Various sources from IRC and github issues have indicated that the ability to carry amounts of substance other than water through pipes would see uses appear for it if it were implemented (there does not appear to be anything trying to do so right now). -Extending the pressure mechanism to handle new fluids would be simple enough, it would just have to deal with more variables. -However, this feature raises the question of how to handle mixtures of fluids in pipes. - -Two possible solutions appear evident: -+ Don't mix at all. For each flowable registered, either a variant would be created for each supported liquid, or each node would declare which fluid it carries explicitly. Flowable nodes for different fluids would not interact with each other at all. - -+ Equalise "pressure" of multiple fluid variables in a similar manner to how the single water pressure value is currently balanced out, however this raises the issue of how to deal with mixtures - for instance, how is a spigot to function with a mixed liquid? does it simply refuse to function if it doesn't know how to deal with a certain mixture (as it can only output whole nodes)? likewise for certain mixtures in pipes, should it be allowed for lava and water for instance to mix like they don't interact at all? - -This mechanism also hints at a weakness of the pressure logic mechanism as it currently stands - namely that liquids are handled like gases and assumed to be compressible. - - diff --git a/todo/pressure_logic.txt b/todo/pressure_logic.txt new file mode 100644 index 0000000..41ce2f8 --- /dev/null +++ b/todo/pressure_logic.txt @@ -0,0 +1,40 @@ +-- Directionality code (in progress) +The flowable node class for directional nodes now exists and is hooked up in the code for determining valid neighbours in the flowable node ABM routines. +Pumps have been converted to this as part of the testing. +However, currently only the "raw" registration for this is available, and the pump definition registers it's own callback routines. +Helpers need to be added to flowable_node_registry_install.lua to abstract away the expression of which nodes can flow which ways - valves, flow sensors, spigots and entry panels, for instance, all currently rotate the same way using an upwards facedir, so a helper for these nodes would prevent code duplication. + + +-- (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. + +-- Make spigots and fountainheads deactivate by placing a flowing water node +Currently, in non-finite mode, the spigots and fountainheads react to pressure dropping below threshold by deleting the water source they placed. +VanessaE would like this changed so that these nodes replace the water source with a flowing water source, such that it drains away at the same rate as surrounding water. +This should be a simple case of modifying the cleanup handler that is installed for these nodes by the helper that they use, to place flowing water instead of air in the situations where it would normally do so. + +-- Decorative grating functionality +The decorative grating block currently is just that - purely decorative, it serves no purpose. +VanessaE would like this to function as an output with the following properties: +* While on, tries to randomly place a water source in an adjacent node every ABM interval, preferring to place it downwards first. +* Even with multiple water source nodes placed, only drains 1 unit pressure per ABM interval in non-finite mode. Finite mode will cause it to drain 1 unit per water source node placed and simply stop placing sources when below threshold pressure, like spigots and fountainheads already do. +* When turning off in non-finite mode, for all neighbour nodes, replace the water sources with flowing water as discussed above, but *only* if those neighbouring sources do not have any water source neighbours of their own in turn - this will prevent the block from creating a "hole" in a uniform pool of water sources. + + + +-- Support for other fluids in pipes (Feature request/wish list) +Various sources from IRC and github issues have indicated that the ability to carry amounts of substance other than water through pipes would see uses appear for it if it were implemented (there does not appear to be anything trying to do so right now). +Extending the pressure mechanism to handle new fluids would be simple enough, it would just have to deal with more variables. +However, this feature raises the question of how to handle mixtures of fluids in pipes. + +Two possible solutions appear evident: ++ Don't mix at all. For each flowable registered, either a variant would be created for each supported liquid, or each node would declare which fluid it carries explicitly. Flowable nodes for different fluids would not interact with each other at all. + ++ Equalise "pressure" of multiple fluid variables in a similar manner to how the single water pressure value is currently balanced out, however this raises the issue of how to deal with mixtures - for instance, how is a spigot to function with a mixed liquid? does it simply refuse to function if it doesn't know how to deal with a certain mixture (as it can only output whole nodes)? likewise for certain mixtures in pipes, should it be allowed for lava and water for instance to mix like they don't interact at all? + +This mechanism also hints at a weakness of the pressure logic mechanism as it currently stands - namely that liquids are handled like gases and assumed to be compressible. + + -- 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 --- devices.lua | 8 +------- pressure_logic/flowable_node_registry_install.lua | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/devices.lua b/devices.lua index d9edcad..6dd9617 100644 --- a/devices.lua +++ b/devices.lua @@ -165,13 +165,7 @@ for s in ipairs(states) do }) -- FIXME: this currently assumes that pumps can only rotate around the fixed axis pointing Y+. - -- TODO: these directionality functions should be behind a helper so the fountainhead can use something similar. - local upwards = {x=0,y=1,z=0} - local neighbourfn = function(node) return { upwards } end - local directionfn = function(node, direction) - return vector.equals(direction, upwards) - end - new_flow_logic_register.directional(pumpname, neighbourfn, directionfn) + new_flow_logic_register.directional_vertical_fixed(pumpname, true) local pump_drive = 4 if states[s] ~= "off" then new_flow_logic_register.intake_simple(pumpname, pump_drive) 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 3a85152e4d4b6aa192e80bddfad174734ba737d5 Mon Sep 17 00:00:00 2001 From: thetaepsilon-gamedev Date: Wed, 18 Oct 2017 21:44:36 +0100 Subject: devices.lua: make fountainheads directional using fixed vertical helper --- devices.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devices.lua b/devices.lua index 6dd9617..a2c0c80 100644 --- a/devices.lua +++ b/devices.lua @@ -674,8 +674,8 @@ minetest.register_node(nodename_fountain_loaded, { drop = "pipeworks:fountainhead", on_rotate = false }) -new_flow_logic_register.simple(nodename_fountain_empty) -new_flow_logic_register.simple(nodename_fountain_loaded) +new_flow_logic_register.directional_vertical_fixed(nodename_fountain_empty, false) +new_flow_logic_register.directional_vertical_fixed(nodename_fountain_loaded, false) local fountain_upper = 1.0 local fountain_lower = 1.0 local fountain_neighbours={{x=0, y=1, z=0}} -- 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(-) 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(+) 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 From fd4bd8eadcdbd12d119b69b591bfe66a238f14f8 Mon Sep 17 00:00:00 2001 From: thetaepsilon-gamedev Date: Thu, 19 Oct 2017 12:30:28 +0100 Subject: devices.lua: make flow sensor use the horizontally rotating flowable class --- devices.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/devices.lua b/devices.lua index a2c0c80..7dad816 100644 --- a/devices.lua +++ b/devices.lua @@ -523,9 +523,8 @@ minetest.register_node(nodename_sensor_loaded, { mesecons = pipereceptor_on, 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) +new_flow_logic_register.directional_horizonal_rotate(nodename_sensor_empty) +new_flow_logic_register.directional_horizonal_rotate(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 }) -- cgit v1.2.3 From 9df0ec7edb55ba443acc68df9ed63fdd53c66bfa Mon Sep 17 00:00:00 2001 From: thetaepsilon-gamedev Date: Thu, 19 Oct 2017 13:05:16 +0100 Subject: devices.lua: convert entry panel and valve to horizontal rotation flowable class --- devices.lua | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/devices.lua b/devices.lua index 7dad816..093137e 100644 --- a/devices.lua +++ b/devices.lua @@ -215,8 +215,7 @@ for s in ipairs(states) do -- 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) + new_flow_logic_register.directional_horizonal_rotate(nodename_valve_empty) end end @@ -264,7 +263,7 @@ minetest.register_node(nodename_valve_loaded, { -- 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) +new_flow_logic_register.directional_horizonal_rotate(nodename_valve_loaded) -- grating @@ -438,10 +437,9 @@ minetest.register_node(nodename_panel_loaded, { drop = "pipeworks:entry_panel_empty", 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) +new_flow_logic_register.directional_horizonal_rotate(nodename_panel_empty) +new_flow_logic_register.directional_horizonal_rotate(nodename_panel_loaded) -- cgit v1.2.3