diff options
| author | Vanessa Ezekowitz <vanessaezekowitz@gmail.com> | 2017-10-09 06:38:54 -0400 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-10-09 06:38:54 -0400 | 
| commit | a028aef9c9d82fa43e872b0802b02dbcf0fbb62c (patch) | |
| tree | 173534203c946a9914dddc393361fb36ec7a8e01 /new_flow_logic | |
| parent | 879b4489b21946306004506bea51b32f6d9de6a2 (diff) | |
| parent | 467907602bec6f3a7adfa3058257732a8b903214 (diff) | |
Merge pull request #204 from thetaepsilon-gamedev/master
More pressure_logic work
Diffstat (limited to 'new_flow_logic')
| -rw-r--r-- | new_flow_logic/abm_register.lua | 26 | ||||
| -rw-r--r-- | new_flow_logic/abms.lua | 315 | ||||
| -rw-r--r-- | new_flow_logic/flowable_node_registry.lua | 48 | ||||
| -rw-r--r-- | new_flow_logic/flowable_node_registry_install.lua | 188 | 
4 files changed, 577 insertions, 0 deletions
| diff --git a/new_flow_logic/abm_register.lua b/new_flow_logic/abm_register.lua new file mode 100644 index 0000000..1d038d6 --- /dev/null +++ b/new_flow_logic/abm_register.lua @@ -0,0 +1,26 @@ +-- register new flow logic ABMs +-- written 2017 by thetaepsilon + +local register = {} +pipeworks.flowlogic.abmregister = register + +local flowlogic = pipeworks.flowlogic + +-- register node list for the main logic function. +-- see flowlogic.run() in abms.lua. + +local register_flowlogic_abm = function(nodename) +	if pipeworks.toggles.pressure_logic then +		minetest.register_abm({ +			nodenames = { nodename }, +			interval = 1, +			chance = 1, +			action = function(pos, node, active_object_count, active_object_count_wider) +				flowlogic.run(pos, node) +			end +		}) +	else +		minetest.log("warning", "pipeworks pressure_logic not enabled but register.flowlogic() requested") +	end +end +register.flowlogic = register_flowlogic_abm diff --git a/new_flow_logic/abms.lua b/new_flow_logic/abms.lua new file mode 100644 index 0000000..38ae4b6 --- /dev/null +++ b/new_flow_logic/abms.lua @@ -0,0 +1,315 @@ +-- reimplementation of new_flow_logic branch: processing functions +-- written 2017 by thetaepsilon + + + +local flowlogic = {} +flowlogic.helpers = {} +pipeworks.flowlogic = flowlogic + + + +-- borrowed from above: might be useable to replace the above coords tables +local make_coords_offsets = function(pos, include_base) +	local coords = { +		{x=pos.x,y=pos.y-1,z=pos.z}, +		{x=pos.x,y=pos.y+1,z=pos.z}, +		{x=pos.x-1,y=pos.y,z=pos.z}, +		{x=pos.x+1,y=pos.y,z=pos.z}, +		{x=pos.x,y=pos.y,z=pos.z-1}, +		{x=pos.x,y=pos.y,z=pos.z+1}, +	} +	if include_base then table.insert(coords, pos) end +	return coords +end + + + +-- local debuglog = function(msg) print("## "..msg) end + + + +local formatvec = function(vec) local sep="," return "("..tostring(vec.x)..sep..tostring(vec.y)..sep..tostring(vec.z)..")" end + +-- new version of liquid check +-- accepts a limit parameter to only delete water blocks that the receptacle can accept, +-- and returns it so that the receptacle can update it's pressure values. +local check_for_liquids_v2 = function(pos, limit) +	local coords = make_coords_offsets(pos, false) +	local total = 0 +	for index, tpos in ipairs(coords) do +		if total >= limit then break end +		local name = minetest.get_node(tpos).name +		if name == "default:water_source" then +			minetest.remove_node(tpos) +			total = total + 1 +		end +	end +	--pipeworks.logger("check_for_liquids_v2@"..formatvec(pos).." total "..total) +	return total +end +flowlogic.check_for_liquids_v2 = check_for_liquids_v2 + + + +local label_pressure = "pipeworks.water_pressure" +local get_pressure_access = function(pos) +	local metaref = minetest.get_meta(pos) +	return { +		get = function() +			return metaref:get_float(label_pressure) +		end, +		set = function(v) +			metaref:set_float(label_pressure, v) +		end +	} +end + + +-- logging is unreliable when something is crashing... +local nilexplode = function(caller, label, value) +	if value == nil then +		error(caller..": "..label.." was nil") +	end +end + + + +local finitemode = pipeworks.toggles.finite_water +flowlogic.run = function(pos, node) +	local nodename = node.name +	-- get the current pressure value. +	local nodepressure = get_pressure_access(pos) +	local currentpressure = nodepressure.get() +	local oldpressure = currentpressure + +	-- if node is an input: run intake phase +	local inputdef = pipeworks.flowables.inputs.list[nodename] +	if inputdef then +		currentpressure = flowlogic.run_input(pos, node, currentpressure, inputdef) +		--debuglog("post-intake currentpressure is "..currentpressure) +		--nilexplode("run()", "currentpressure", currentpressure) +	end + +	-- balance pressure with neighbours +	currentpressure = flowlogic.balance_pressure(pos, node, currentpressure) + +	-- if node is an output: run output phase +	local outputdef = pipeworks.flowables.outputs.list[nodename] +	if outputdef then +		currentpressure = flowlogic.run_output( +			pos, +			node, +			currentpressure, +			oldpressure, +			outputdef, +			finitemode) +	end + +	-- if node has pressure transitions: determine new node +	if pipeworks.flowables.transitions.list[nodename] then +		local newnode = flowlogic.run_transition(node, currentpressure) +		--pipeworks.logger("flowlogic.run()@"..formatvec(pos).." transition, new node name = "..dump(newnode).." pressure "..tostring(currentpressure)) +		minetest.swap_node(pos, newnode) +		flowlogic.run_transition_post(pos, newnode) +	end + +	-- set the new pressure +	nodepressure.set(currentpressure) +end + + + +flowlogic.balance_pressure = function(pos, node, currentpressure) +	-- debuglog("balance_pressure() "..node.name.." at "..pos.x.." "..pos.y.." "..pos.z) +	-- check the pressure of all nearby flowable nodes, and average it out. + +	-- pressure handles to average over +	local connections = {} +	-- unconditionally include self in nodes to average over. +	-- result of averaging will be returned as new pressure for main flow logic callback +	local totalv = currentpressure +	local totalc = 1 + +	-- then handle neighbours, but if not a pressure node don't consider them at all +	for _, npos in ipairs(make_coords_offsets(pos, false)) do +		local nodename = minetest.get_node(npos).name +		-- for now, just check if it's in the simple table. +		-- TODO: the "can flow from" logic in flowable_node_registry.lua +		local haspressure = (pipeworks.flowables.list.simple[nodename]) +		if haspressure then +			local neighbour = get_pressure_access(npos) +			--pipeworks.logger("balance_pressure @ "..formatvec(pos).." "..nodename.." "..formatvec(npos).." added to neighbour set") +			local n = neighbour.get() +			table.insert(connections, neighbour) +			totalv = totalv + n +			totalc = totalc + 1 +		end +	end + +	local average = totalv / totalc +	for _, target in ipairs(connections) do +		target.set(average) +	end + +	return average +end + + + +flowlogic.run_input = function(pos, node, currentpressure, inputdef) +	-- intakefn allows a given input node to define it's own intake logic. +	-- this function will calculate the maximum amount of water that can be taken in; +	-- the intakefn will be given this and is expected to return the actual absorption amount. + +	local maxpressure = inputdef.maxpressure +	local intake_limit = maxpressure - currentpressure +	if intake_limit <= 0 then return currentpressure end + +	local actual_intake = inputdef.intakefn(pos, intake_limit) +	--pipeworks.logger("run_input@"..formatvec(pos).." oldpressure "..currentpressure.." intake_limit "..intake_limit.." actual_intake "..actual_intake) +	if actual_intake <= 0 then return currentpressure end + +	local newpressure = actual_intake + currentpressure +	--debuglog("run_input() end, oldpressure "..currentpressure.." intake_limit "..intake_limit.." actual_intake "..actual_intake.." newpressure "..newpressure) +	return newpressure +end + + + +-- flowlogic output helper implementation: +-- outputs water by trying to place water nodes nearby in the world. +-- neighbours is a list of node offsets to try placing water in. +-- this is a constructor function, returning another function which satisfies the output helper requirements. +-- note that this does *not* take rotation into account. +flowlogic.helpers.make_neighbour_output_fixed = function(neighbours) +	return function(pos, node, currentpressure, finitemode) +		local taken = 0 +		for _, offset in pairs(neighbours) do +			local npos = vector.add(pos, offset) +			local name = minetest.get_node(npos).name +			if currentpressure < 1 then break end +			-- take pressure anyway in non-finite mode, even if node is water source already. +			-- in non-finite mode, pressure has to be sustained to keep the sources there. +			-- so in non-finite mode, placing water is dependent on the target node; +			-- draining pressure is not. +			local canplace = (name == "air") or (name == "default:water_flowing") +			if canplace then +				minetest.swap_node(npos, {name="default:water_source"}) +			end +			if (not finitemode) or canplace then +				taken = taken + 1 +				currentpressure = currentpressure - 1 +			end +		end +		return taken +	end +end + +-- complementary function to the above when using non-finite mode: +-- removes water sources from neighbor positions when the output is "off" due to lack of pressure. +flowlogic.helpers.make_neighbour_cleanup_fixed = function(neighbours) +	return function(pos, node, currentpressure) +		--pipeworks.logger("neighbour_cleanup_fixed@"..formatvec(pos)) +		for _, offset in pairs(neighbours) do +			local npos = vector.add(pos, offset) +			local name = minetest.get_node(npos).name +			if (name == "default:water_source") then +				--pipeworks.logger("neighbour_cleanup_fixed removing "..formatvec(npos)) +				minetest.remove_node(npos) +			end +		end +	end +end + + + +flowlogic.run_output = function(pos, node, currentpressure, oldpressure, outputdef, finitemode) +	-- processing step for water output devices. +	-- takes care of checking a minimum pressure value and updating the resulting pressure level +	-- the outputfn is provided the current pressure and returns the pressure "taken". +	-- as an example, using this with the above spigot function, +	-- the spigot function tries to output a water source if it will fit in the world. +	--pipeworks.logger("flowlogic.run_output() pos "..formatvec(pos).." old -> currentpressure "..tostring(oldpressure).." "..tostring(currentpressure).." finitemode "..tostring(finitemode)) +	local upper = outputdef.upper +	local lower = outputdef.lower +	local result = currentpressure +	local threshold = nil +	if finitemode then threshold = lower else threshold = upper end +	if currentpressure > threshold then +		local takenpressure = outputdef.outputfn(pos, node, currentpressure, finitemode) +		local newpressure = currentpressure - takenpressure +		if newpressure < 0 then newpressure = 0 end +		result = newpressure +	end +	if (not finitemode) and (currentpressure < lower) and (oldpressure < lower) then +		--pipeworks.logger("flowlogic.run_output() invoking cleanup currentpressure="..tostring(currentpressure)) +		outputdef.cleanupfn(pos, node, currentpressure) +	end +	return result +end + + + +-- determine which node to switch to based on current pressure +flowlogic.run_transition = function(node, currentpressure) +	local simplesetdef = pipeworks.flowables.transitions.simple[node.name] +	local result = node +	local found = false + +	-- simple transition sets: assumes all nodes in the set share param values. +	if simplesetdef then +		-- assumes that the set has been checked to contain at least one element... +		local nodename_prev = simplesetdef[1].nodename +		local result_nodename = node.name + +		for index, element in ipairs(simplesetdef) do +			-- find the highest element that is below the current pressure. +			local threshold = element.threshold +			if threshold > currentpressure then +				result_nodename = nodename_prev +				found = true +				break +			end +			nodename_prev = element.nodename +		end + +		-- use last element if no threshold is greater than current pressure +		if not found then +			result_nodename = nodename_prev +			found = true +		end + +		-- preserve param1/param2 values +		result = { name=result_nodename, param1=node.param1, param2=node.param2 } +	end + +	if not found then +		pipeworks.logger("flowlogic.run_transition() BUG no transition definitions found! nodename="..nodename.." currentpressure="..tostring(currentpressure)) +	end + +	return result +end + +-- post-update hook for run_transition +-- among other things, updates mesecons if present. +-- node here means the new node, returned from run_transition() above +flowlogic.run_transition_post = function(pos, node) +	local mesecons_def = minetest.registered_nodes[node.name].mesecons +	local mesecons_rules = pipeworks.flowables.transitions.mesecons[node.name] +	if minetest.global_exists("mesecon") and (mesecons_def ~= nil) and mesecons_rules then +		if type(mesecons_def) ~= "table" then +			pipeworks.logger("flowlogic.run_transition_post() BUG mesecons def for "..node.name.."not a table: got "..tostring(mesecons_def)) +		else +			local receptor = mesecons_def.receptor +			if receptor then +				local state = receptor.state +				if state == mesecon.state.on then +					mesecon.receptor_on(pos, mesecons_rules) +				elseif state == mesecon.state.off then +					mesecon.receptor_off(pos, mesecons_rules) +				end +			end +		end +	end +end diff --git a/new_flow_logic/flowable_node_registry.lua b/new_flow_logic/flowable_node_registry.lua new file mode 100644 index 0000000..2523803 --- /dev/null +++ b/new_flow_logic/flowable_node_registry.lua @@ -0,0 +1,48 @@ +-- registry of flowable node behaviours in new flow logic +-- written 2017 by thetaepsilon + +-- the actual registration functions which edit these tables can be found in flowable_node_registry_install.lua +-- this is because the ABM code needs to inspect these tables, +-- but the registration code needs to reference said ABM code. +-- so those functions were split out to resolve a circular dependency. + + + +pipeworks.flowables = {} +pipeworks.flowables.list = {} +pipeworks.flowables.list.all = {} +-- pipeworks.flowables.list.nodenames = {} + +-- simple flowables - balance pressure in any direction +pipeworks.flowables.list.simple = {} +pipeworks.flowables.list.simple_nodenames = {} + +-- simple intakes - try to absorb any adjacent water nodes +pipeworks.flowables.inputs = {} +pipeworks.flowables.inputs.list = {} +pipeworks.flowables.inputs.nodenames = {} + +-- outputs - takes pressure from pipes and update world to do something with it +pipeworks.flowables.outputs = {} +pipeworks.flowables.outputs.list = {} +-- not currently any nodenames arraylist for this one as it's not currently needed. + +-- nodes with registered node transitions +-- nodes will be switched depending on pressure level +pipeworks.flowables.transitions = {} +pipeworks.flowables.transitions.list = {}	-- master list +pipeworks.flowables.transitions.simple = {}	-- nodes that change based purely on pressure +pipeworks.flowables.transitions.mesecons = {}	-- table of mesecons rules to apply on transition + + + +-- checks if a given node can flow in a given direction. +-- used to implement directional devices such as pumps, +-- which only visually connect in a certain direction. +-- node is the usual name + param structure. +-- direction is an x/y/z vector of the flow direction; +-- this function answers the question "can this node flow in this direction?" +pipeworks.flowables.flow_check = function(node, direction) +	minetest.log("warning", "pipeworks.flowables.flow_check() stub!") +	return true +end diff --git a/new_flow_logic/flowable_node_registry_install.lua b/new_flow_logic/flowable_node_registry_install.lua new file mode 100644 index 0000000..c8f6889 --- /dev/null +++ b/new_flow_logic/flowable_node_registry_install.lua @@ -0,0 +1,188 @@ +-- flowable node registry: add entries and install ABMs if new flow logic is enabled +-- written 2017 by thetaepsilon + + + +-- use for hooking up ABMs as nodes are registered +local abmregister = pipeworks.flowlogic.abmregister + +-- registration functions +pipeworks.flowables.register = {} +local register = pipeworks.flowables.register + +-- some sanity checking for passed args, as this could potentially be made an external API eventually +local checkexists = function(nodename) +	if type(nodename) ~= "string" then error("pipeworks.flowables nodename must be a string!") end +	return pipeworks.flowables.list.all[nodename] +end + +local insertbase = function(nodename) +	if checkexists(nodename) then error("pipeworks.flowables duplicate registration!") end +	pipeworks.flowables.list.all[nodename] = true +	-- table.insert(pipeworks.flowables.list.nodenames, nodename) +end + +local regwarning = function(kind, nodename) +	local tail = "" +	if not pipeworks.toggles.pressure_logic then tail = " but pressure logic not enabled" end +	--pipeworks.logger(kind.." flow logic registry requested for "..nodename..tail) +end + +-- Register a node as a simple flowable. +-- Simple flowable nodes have no considerations for direction of flow; +-- A cluster of adjacent simple flowables will happily average out in any direction. +register.simple = function(nodename) +	insertbase(nodename) +	pipeworks.flowables.list.simple[nodename] = true +	table.insert(pipeworks.flowables.list.simple_nodenames, nodename) +	if pipeworks.toggles.pressure_logic then +		abmregister.flowlogic(nodename) +	end +	regwarning("simple", nodename) +end + +local checkbase = function(nodename) +	if not checkexists(nodename) then error("pipeworks.flowables node doesn't exist as a flowable!") end +end + +local duplicateerr = function(kind, nodename) error(kind.." duplicate registration for "..nodename) end + + + +-- Registers a node as a fluid intake. +-- intakefn is used to determine the water that can be taken in a node-specific way. +-- Expects node to be registered as a flowable (is present in flowables.list.all), +-- so that water can move out of it. +-- maxpressure is the maximum pipeline pressure that this node can drive; +-- if the input's node exceeds this the callback is not run. +-- possible WISHME here: technic-driven high-pressure pumps +register.intake = function(nodename, maxpressure, intakefn) +	-- check for duplicate registration of this node +	local list = pipeworks.flowables.inputs.list +	checkbase(nodename) +	if list[nodename] then duplicateerr("pipeworks.flowables.inputs", nodename) end +	list[nodename] = { maxpressure=maxpressure, intakefn=intakefn } +	regwarning("intake", nodename) +end + + + +-- Register a node as a simple intake: +-- tries to absorb water source nodes from it's surroundings. +-- may exceed limit slightly due to needing to absorb whole nodes. +register.intake_simple = function(nodename, maxpressure) +	register.intake(nodename, maxpressure, pipeworks.flowlogic.check_for_liquids_v2) +end + + + +-- Register a node as an output. +-- Expects node to already be a flowable. +-- upper and lower thresholds have different meanings depending on whether finite liquid mode is in effect. +-- if not (the default unless auto-detected), +-- nodes above their upper threshold have their outputfn invoked (and pressure deducted), +-- nodes between upper and lower are left idle, +-- and nodes below lower have their cleanup fn invoked (to say remove water sources). +-- the upper and lower difference acts as a hysteresis to try and avoid "gaps" in the flow. +-- if finite mode is on, upper is ignored and lower is used to determine whether to run outputfn; +-- cleanupfn is ignored in this mode as finite mode assumes something causes water to move itself. +register.output = function(nodename, upper, lower, outputfn, cleanupfn) +	if pipeworks.flowables.outputs.list[nodename] then +		error("pipeworks.flowables.outputs duplicate registration!") +	end +	checkbase(nodename) +	pipeworks.flowables.outputs.list[nodename] = { +		upper=upper, +		lower=lower, +		outputfn=outputfn, +		cleanupfn=cleanupfn, +	} +	-- output ABM now part of main flow logic ABM to preserve ordering. +	-- note that because outputs have to be a flowable first +	-- (and the installation of the flow logic ABM is conditional), +	-- registered output nodes for new_flow_logic is also still conditional on the enable flag. +	regwarning("output node", nodename) +end + +-- register a simple output: +-- drains pressure by attempting to place water in nearby nodes, +-- which can be set by passing a list of offset vectors. +-- will attempt to drain as many whole nodes as there are positions in the offset list. +-- for meanings of upper and lower, see register.output() above. +-- non-finite mode: +--	above upper pressure: places water sources as appropriate, keeps draining pressure. +--	below lower presssure: removes it's neighbour water sources. +-- finite mode: +--	same as for above pressure in non-finite mode, +--	but only drains pressure when water source nodes are actually placed. +register.output_simple = function(nodename, upper, lower, neighbours) +	local outputfn = pipeworks.flowlogic.helpers.make_neighbour_output_fixed(neighbours) +	local cleanupfn = pipeworks.flowlogic.helpers.make_neighbour_cleanup_fixed(neighbours) +	register.output(nodename, upper, lower, outputfn, cleanupfn) +end + + + +-- common base checking for transition nodes +-- ensures the node has only been registered once as a transition. +local transition_list = pipeworks.flowables.transitions.list +local insert_transition_base = function(nodename) +	checkbase(nodename) +	if transition_list[nodename] then duplicateerr("base transition", nodename) end +	transition_list[nodename] = true +end + + + +-- register a simple transition set. +-- expects a table with nodenames as keys and threshold pressures as values. +-- internally, the table is sorted by value, and when one of these nodes needs to transition, +-- the table is searched starting from the lowest (even if it's value is non-zero), +-- until a value is found which is higher than or equal to the current node pressure. +-- ex. nodeset = { ["mod:level_0"] = 0, ["mod:level_1"] = 1, --[[ ... ]] } +local simpleseterror = function(msg) +	error("register.transition_simple_set(): "..msg) +end +local simple_transitions = pipeworks.flowables.transitions.simple + +register.transition_simple_set = function(nodeset, extras) +	local set = {} +	if extras == nil then extras = {} end + +	local length = #nodeset +	if length < 2 then simpleseterror("nodeset needs at least two elements!") end +	for index, element in ipairs(nodeset) do +		if type(element) ~= "table" then simpleseterror("element "..tostring(index).." in nodeset was not table!") end +		local nodename = element[1] +		local value = element[2] +		if type(nodename) ~= "string" then simpleseterror("nodename "..tostring(nodename).."was not a string!") end +		if type(value) ~= "number" then simpleseterror("pressure value "..tostring(value).."was not a number!") end +		insert_transition_base(nodename) +		if simple_transitions[nodename] then duplicateerr("simple transition set", nodename) end +		-- assigning set to table is done separately below + +		table.insert(set, { nodename=nodename, threshold=value }) +	end + +	-- sort pressure values, smallest first +	local smallest_first = function(a, b) +		return a.threshold < b.threshold +	end +	table.sort(set, smallest_first) + +	-- individual registration of each node, all sharing this set, +	-- so each node in the set will transition to the correct target node. +	for _, element in ipairs(set) do +		--pipeworks.logger("register.transition_simple_set() after sort: nodename "..element.nodename.." value "..tostring(element.threshold)) +		simple_transitions[element.nodename] = set +	end + +	-- handle extra options +	-- if mesecons rules table was passed, set for each node +	if extras.mesecons then +		local mesecons_rules = pipeworks.flowables.transitions.mesecons +		for _, element in ipairs(set) do +			mesecons_rules[element.nodename] = extras.mesecons +		end +	end +end | 
