code:
Changelog
---------

2017-10-19 (thetaepsilon)
Directional flowables are now implemented.
All devices for which it is relevant (valve, flow sensor etc.) have been converted so that they only flow on their connecting sides, so pressure propogation now works as expected for these devices when pressure logic is enabled.
Classic mode continues to be preserved by default as before.

2017-10-14 (thetaepsilon, VanessaE)
Node breakers have been updated to not have a tool by default, and determine if the node that they are trying to break can be dug with the tool in it's inventory slot.
The crafting recipe for the node breakers has been updated, using a new gear crafting item that requires iron instead of mese, which should be a more accessible cost in most cases.
Existing node breakers in worlds will get their mese pick back if their slot is empty via LBM - the mese pick will show up in the inventory slot so you can reclaim your hard-earned mese crystals.
Gear item texture and updated node breaker textures provided by VanessaE.

2017-10-08 (thetaepsilon)
A lot more of the new flow logic work.
There are two sub-modes of this now,  non-finite and finite mode.
Non-finite mode most closely resembles "classic mode", whereas finite mode is more intended for use with mods such as dynamic_liquids which enable water sources to move themselves.
Everything that was functional in classic mode more or less works correctly now.
Still TODO:
+ Flow directionality - things like flow sensors and airtight panels will flow in directions that don't make sense from their visuals.
Possible feature requests:
+ Making tanks and gratings do something useful.

2017-09-27 (thetaepsilon)
Start of new flow logic re-implementation.
This mode is current *very* incomplete, and requires a per-world setting to enable.
Adds a pressure value stored in all pipe node metadata, and a mechanism to balance it out with nearby nodes on ABM trigger.
Currently, this inhibits the old behaviour when enabled, and (again WHEN ENABLED) breaks pretty much everything but normal pipes, spigots and pumps.
For this reason it is far from being intended as the default for some time to come yet.
What *does* work:
+ Pumps will try to take in water (and removes it!) as long as internal pressure does not exceed a threshold.
	- a TODO is to make this pressure threshold configurable.
+ Pipes will balance this pressure between themselves, and will slowly average out over time.
+ Spigots will try to make the node beneath them a water source if the pressure is great enough and the existing node is flowing water or air.
	- This is admittedly of fairly limited use with default water mechanics; those looking for more realistic mechanics might want to look at the dynamic_liquid mod, though that mod comes with it's own caveats (most notably drastic changes to previous worlds...).
What *does not* work:
+ Flow sensors, valves. Valves in particular currently do not function as a barrier to water's path under the experimental logic.
	- TODO: internal code to allow this to be overriden.

*seems this hasn't been updated in a while*

2013-01-13: Tubes can transport items now! Namely, I added Novatux/Nore's item
transport mod as a default part of this mod, to make tubes do something useful!
Thanks to Nore and RealBadAngel for the code contributions!

2013-01-05: made storage tanks connect from top/bottom, made storage tank and
pipe textures use the ^ combine operator so they can show the actual liquid
going through the pipes/tanks.

2013-01-04 (a bit later): Made pipes able to carry water! It was just a minor
logic error resulting from moving the water flowing code into it's own file
when I originally imported it.  Many thanks to Mauvebic for writing it!

2013-01-04: First stage of integrating Mauvebic's water flowing code.  This is
experimental and doesn't move water yet - but at least it doesn't break
anything :-)

2013-01-01: Various minor tweaks to textures, facedir settings, some other
stuff.  Changed crafting recipes to account for revamped pumps, valves, etc.
Now requires the moreores mod and most recent git (for mese crystal fragments)
to craft a pump.  Added a "sealed" entry/exit panel (really just a horizontal
pipe with a metal panel overlayed into the middle).  Also, tweaked pipes to
always drop the empty ones.  Revamped pumps so that now they should sit in/on
liquid and be connected only from the top, relegated grates to decorational-
only, added outlet spigot.  Got rid of a few obsolete textures.  Got rid of
that whole _x and _z naming thing - now all directional devices (pumps, valves,
spigots, tanks) use facedir.  Valves, spigots no longer auto-rotate to find
nearby pipes.

2012-09-17: Added test object for pneumatic tube autorouting code, made tubes
connect to it and any object that bears groups={tubedevice=1} (connects to any
side)

2012-09-05: All recipes doubled except for junglegrass -> plastic sheet (since
that is derived from home decor)

2012-09-02: Fixed plastic sheeting recipe.  Added crafting recipes for various
objects, with options: If homedecor is installed, use the plastic sheeting
therein.  If not, we define it manually.  If the Technic mod is installed,
don't define any recipes at all.  Also removed the extra "loaded!" messages and
tweaked the default pipe alias to point to something that is actually visible
:-)

2012-09-01:  flattened wielded pipe segment.

2012-08-24: Added square-ish pneumatic tubes with their own autoplace code
(does not connect to steel pipes or pipe-oriented devices), then revised their
textures shortly after.  Fixed a recursion bug that sometimes caused a stack
overflow.  Old pipes were overriding the pipeworks:pipe defintion that belongs
with the new pipes.

2012-08-22: Added outlet grate, made it participate in autoplace algorithm.
Extended storage tank to show fill level in 10% steps (0% to 100%).  Added
"expansion tank" that appears if the user stacks tanks upwards.  (Downwards is
not checked).

2012-08-21: Made storage tank participate in autoplace algorithm.  Tuned API a
little to allow for more flexible placement.  Re-organized code a bit to allow
for some upcoming rules changes.  Made storage tanks' upper/lower fittins and
intake grate participate in autoplace algorithm.

2012-08-20: Added temporary nodes for storage tank and intake grating, but
without autoplace.

2012-08-19: Pumps and valves now fully participate in the
auto-rotate/auto-place algorithm.

2012-08-18: Total rewrite again.  All pipes are now nice and round-looking, and
they auto-connect!  Also added temporary nodes for pump and valve (each with an
on/off setting - punch to change).  No crafting recipes yet and the pipes still
don't do anything useful yet.  Soon.

2012-08-06:  Moved this changelog off the forum post and into a separate file.

2012-08-05 (multiple updates): Rewrote pipeworks to use loops and tables to
create the nodes. Requires far less code now. Added -X, +X, -Y, +Y, -Z, +Z
capped stubs and a short centered horizontal segment. Changed node definitions
so that the aforementioned "short centered" segment is given on dig/drop.
Renamed it to just "pipeworks:pipe" (and pipe_loaded). Added empty/loaded
indicator images to the capped ends, removed some redundant comments. Made the
empty/loaded indication at the capped end more prominent.

2012-07-21: Added screenshot showing pipes as they look now that nodebox
texture rotation is fixed.

2012-07-18: Changed the mod name and all internals to 'pipeworks' instead of
'pipes'... after a couple of mistakes :-)

2012-07-12: moved project to github.

2012-06-23: Initial release, followed by reworking the textures a bit.


code:
----------------------
-- Vector functions --
----------------------

function pipeworks.vector_cross(a, b)
	return {
		x = a.y * b.z - a.z * b.y,
		y = a.z * b.x - a.x * b.z,
		z = a.x * b.y - a.y * b.x
	}
end

function pipeworks.vector_dot(a, b)
	return a.x * b.x + a.y * b.y + a.z * b.z
end

-----------------------
-- Facedir functions --
-----------------------

function pipeworks.facedir_to_top_dir(facedir)
	return 	({[0] = {x =  0, y =  1, z =  0},
	                {x =  0, y =  0, z =  1},
	                {x =  0, y =  0, z = -1},
	                {x =  1, y =  0, z =  0},
	                {x = -1, y =  0, z =  0},
	                {x =  0, y = -1, z =  0}})
		[math.floor(facedir / 4)]
end

function pipeworks.facedir_to_right_dir(facedir)
	return pipeworks.vector_cross(
		pipeworks.facedir_to_top_dir(facedir),
		minetest.facedir_to_dir(facedir)
	)
end

local directions = {}
pipeworks.directions = directions
function directions.side_to_dir(side)
	return ({[0] = vector.new(),
		vector.new( 0,  1,  0),
		vector.new( 0, -1,  0),
		vector.new( 1,  0,  0),
		vector.new(-1,  0,  0),
		vector.new( 0,  0,  1),
		vector.new( 0,  0, -1)
	})[side]
end

function directions.dir_to_side(dir)
	local c = pipeworks.vector_dot(dir, vector.new(1, 2, 3)) + 4
	return ({6, 2, 4, 0, 3, 1, 5})[c]
end

----------------------
-- String functions --
----------------------

--[[function pipeworks.string_split(str, sep)
	local fields = {}
	local index = 1
	local expr = "([^"..sep.."])+"
	string.gsub(str, expr, function(substring)
		fields[index] = substring
		index = index + 1
	end)
	return fields
end]]

function pipeworks.string_startswith(str, substr)
	return str:sub(1, substr:len()) == substr
end

---------------------
-- Table functions --
---------------------

function pipeworks.table_contains(tbl, element)
	for _, elt in pairs(tbl) do
		if elt == element then
			return true
		end
	end
	return false
end

function pipeworks.table_extend(tbl, tbl2)
	local index = #tbl + 1
	for _, elt in ipairs(tbl2) do
		tbl[index] = elt
		index = index + 1
	end
end

function pipeworks.table_recursive_replace(tbl, pattern, replace_with)
	if type(tbl) == "table" then
		local tbl2 = {}
		for key, value in pairs(tbl) do
			tbl2[key] = pipeworks.table_recursive_replace(value, pattern, replace_with)
		end
		return tbl2
	elseif type(tbl) == "string" then
		return tbl:gsub(pattern, replace_with)
	else
		return tbl
	end
end

------------------------
-- Formspec functions --
------------------------

local fs_helpers = {}
pipeworks.fs_helpers = fs_helpers
function fs_helpers.on_receive_fields(pos, fields)
	local meta = minetest.get_meta(pos)
	for field, value in pairs(fields) do
		if pipeworks.string_startswith(field, "fs_helpers_cycling:") then
			local l = field:split(":")
			local new_value = tonumber(l[2])
			local meta_name = l[3]
			meta:set_int(meta_name, new_value)
		end
	end
end

function fs_helpers.cycling_button(meta, base, meta_name, values)
	local current_value = meta:get_int(meta_name)
	local new_value = (current_value + 1) % (#values)
	local val = values[current_value + 1]
	local text
	local texture_name = nil
	local addopts = nil
	--when we get a table, we know the caller wants an image_button
	if type(val) == "table" then
		text = val["text"]
		texture_name = val["texture"]
		addopts = val["addopts"]
	else
		text = val
	end
	local field = "fs_helpers_cycling:"..new_value..":"..meta_name
	return base..";"..(texture_name and texture_name..";" or "")..field..";"..minetest.formspec_escape(text)..(addopts and ";"..addopts or "").."]"
end

---------
-- Env --
---------

function pipeworks.load_position(pos)
	if pos.x < -30912 or pos.y < -30912 or pos.z < -30912 or
	   pos.x >  30927 or pos.y >  30927 or pos.z >  30927 then return end
	if minetest.get_node_or_nil(pos) then
		return
	end
	local vm = minetest.get_voxel_manip()
	vm:read_from_map(pos, pos)
end

local function delay(...)
	local args = {...}
	return (function() return unpack(args) end)
end

local function get_set_wrap(name, is_dynamic)
	return (function(self)
		return self["_" .. name]
	end), (function(self, value)
		if is_dynamic then
			self["_" .. name] = type(value) == "table"
				and table.copy(value) or value
		end
	end)
end

function pipeworks.create_fake_player(def, is_dynamic)
	local wielded_item = ItemStack("")
	if def.inventory and def.wield_list then
		wielded_item = def.inventory:get_stack(def.wield_list, def.wield_index or 1)
	end
	local p = {
		get_player_name = delay(def.name),
		is_player = delay(true),
		is_fake_player = true,

		_formspec = def.formspec or default.gui_survival_form,
		_hp = def.hp or 20,
		_breath = 11,
		_pos = def.position and table.copy(def.position) or vector.new(),
		_properties = def.properties or { eye_height = def.eye_height or 1.47 },
		_inventory = def.inventory,
		_wield_index = def.wield_index or 1,
		_wielded_item = wielded_item,

		-- Model and view
		_eye_offset1 = vector.new(),
		_eye_offset3 = vector.new(),
		set_eye_offset = function(self, first, third)
			self._eye_offset1 = table.copy(first)
			self._eye_offset3 = table.copy(third)
		end,
		get_eye_offset = function(self)
			return self._eye_offset1, self._eye_offset3
		end,
		get_look_dir = delay(def.look_dir or {x=0, y=0, z=1}),
		get_look_pitch = delay(def.look_pitch or 0),
		get_look_yaw = delay(def.look_yaw or 0),
		get_look_horizontal = delay(def.look_yaw or 0),
		get_look_vertical = delay(-(def.look_pitch or 0)),
		set_animation = delay(),

		-- Controls
		get_player_control = delay({
			jump=false, right=false, left=false, LMB=false, RMB=false,
			sneak=def.sneak, aux1=false, down=false, up=false
		}),
		get_player_control_bits = delay(def.sneak and 64 or 0),

		-- Inventory and ItemStacks
		get_inventory = delay(def.inventory),
		set_wielded_item = function(self, item)
			if self._inventory and def.wield_list then
				return self._inventory:set_stack(def.wield_list,
					self._wield_index, item)
			end
			_wielded_item = ItemStack(item)
		end,
		get_wielded_item = function(self, item)
			if self._inventory and def.wield_list then
				return self._inventory:get_stack(def.wield_list,
					self._wield_index)
			end
			return ItemStack(self._wielded_item)
		end,
		get_wield_list = delay(def.wield_list),

		punch = delay(),
		remove = delay(),
		right_click = delay(),
		set_attach = delay(),
		set_detach = delay(),
		set_bone_position = delay(),
		hud_change = delay(),
	}
	local _trash
	-- Getter & setter functions
	p.get_inventory_formspec, p.set_inventory_formspec
		= get_set_wrap("formspec", is_dynamic)
	p.get_breath, p.set_breath = get_set_wrap("breath", is_dynamic)
	p.get_hp, p.set_hp = get_set_wrap("hp", is_dynamic)
	p.get_pos, p.set_pos = get_set_wrap("pos", is_dynamic)
	_trash, p.move_to = get_set_wrap("pos", is_dynamic)
	p.get_wield_index, p.set_wield_index = get_set_wrap("wield_index", true)
	p.get_properties, p.set_properties = get_set_wrap("properties", false)

	-- Backwards compatibilty
	p.getpos = p.get_pos
	p.setpos = p.set_pos
	p.moveto = p.move_to

	-- TODO "implement" all these
	-- set_armor_groups
	-- get_armor_groups
	-- get_animation
	-- get_bone_position
	-- get_player_velocity
	-- set_look_pitch
	-- set_look_yaw
	-- set_physics_override
	-- get_physics_override
	-- hud_add
	-- hud_remove
	-- hud_get
	-- hud_set_flags
	-- hud_get_flags
	-- hud_set_hotbar_itemcount
	-- hud_get_hotbar_itemcount
	-- hud_set_hotbar_image
	-- hud_get_hotbar_image
	-- hud_set_hotbar_selected_image
	-- hud_get_hotbar_selected_image
	-- hud_replace_builtin
	-- set_sky
	-- get_sky
	-- override_day_night_ratio
	-- get_day_night_ratio
	-- set_local_animation
	return p
end

repo_name: Desour/PlayMinetestServer

license: mit


code:
-- this bit of code modifies the default chests and furnaces to be compatible
-- with pipeworks.
--
-- the formspecs found here are basically copies of the ones from minetest_game
-- plus bits from pipeworks' sorting tubes

-- Pipeworks Specific
local fs_helpers = pipeworks.fs_helpers
local tube_entry = "^pipeworks_tube_connection_wooden.png"

-- Chest Locals
local open_chests = {}

local function get_chest_formspec(pos)
	local spos = pos.x .. "," .. pos.y .. "," .. pos.z
	local formspec =
		"size[8,9]" ..
		default.gui_bg ..
		default.gui_bg_img ..
		default.gui_slots ..
		"list[nodemeta:" .. spos .. ";main;0,0.3;8,4;]" ..
		"list[current_player;main;0,4.85;8,1;]" ..
		"list[current_player;main;0,6.08;8,3;8]" ..
		"listring[nodemeta:" .. spos .. ";main]" ..
		"listring[current_player;main]" ..
		default.get_hotbar_bg(0,4.85)

	-- Pipeworks Switch
	formspec = formspec ..
		fs_helpers.cycling_button(
			minetest.get_meta(pos),
			pipeworks.button_base,
			"splitstacks",
			{
				pipeworks.button_off,
				pipeworks.button_on
			}
		)..pipeworks.button_label

	return formspec
end

local function chest_lid_obstructed(pos)
	local above = { x = pos.x, y = pos.y + 1, z = pos.z }
	local def = minetest.registered_nodes[minetest.get_node(above).name]
	-- allow ladders, signs, wallmounted things and torches to not obstruct
	if not def then return true end
	if def.drawtype == "airlike" or
			def.drawtype == "signlike" or
			def.drawtype == "torchlike" or
			(def.drawtype == "nodebox" and def.paramtype2 == "wallmounted") then
		return false
	end
	return true
end

minetest.register_on_player_receive_fields(function(player, formname, fields)
	if formname == "pipeworks:chest_formspec" and player then
		local pn = player:get_player_name()
		if open_chests[pn] then
			local pos = open_chests[pn].pos
			if fields.quit then
				local sound = open_chests[pn].sound
				local swap = open_chests[pn].swap
				local node = minetest.get_node(pos)

				open_chests[pn] = nil
				for k, v in pairs(open_chests) do
					if v.pos.x == pos.x and v.pos.y == pos.y and v.pos.z == pos.z then
						return true
					end
				end
				minetest.after(0.2, function()
					minetest.swap_node(pos, { name = "default:" .. swap, param2 = node.param2 })

					-- Pipeworks notification
					pipeworks.after_place(pos)
				end)
				minetest.sound_play(sound, {gain = 0.3, pos = pos, max_hear_distance = 10})
			end

			-- Pipeworks Switch
			if pipeworks.may_configure(pos, player) and not fields.quit then
				fs_helpers.on_receive_fields(pos, fields)
				minetest.show_formspec(player:get_player_name(), "pipeworks:chest_formspec", get_chest_formspec(pos))
			end
			return true
		end
	end
end)

-- Original Definitions
local old_chest_def = table.copy(minetest.registered_items["default:chest"])
local old_chest_open_def = table.copy(minetest.registered_items["default:chest_open"])
local old_chest_locked_def = table.copy(minetest.registered_items["default:chest_locked"])
local old_chest_locked_open_def = table.copy(minetest.registered_items["default:chest_locked_open"])

-- Override Construction
local override_protected, override, override_open, override_protected_open
override_protected = {
	tiles = {
		"default_chest_top.png"..tube_entry,
		"default_chest_top.png"..tube_entry,
		"default_chest_side.png"..tube_entry,
		"default_chest_side.png"..tube_entry,
		"default_chest_lock.png",
		"default_chest_inside.png"
	},
	after_place_node = function(pos, placer)
		old_chest_locked_def.after_place_node(pos, placer)
		pipeworks.after_place(pos)
	end,
	on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
		if not default.can_interact_with_node(clicker, pos) then
			return itemstack
		end

		minetest.sound_play(old_chest_locked_def.sound_open, {gain = 0.3,
				pos = pos, max_hear_distance = 10})
		if not chest_lid_obstructed(pos) then
			minetest.swap_node(pos,
					{ name = "default:" .. "chest_locked" .. "_open",
					param2 = node.param2 })
		end
		minetest.after(0.2, minetest.show_formspec,
				clicker:get_player_name(),
				"pipeworks:chest_formspec", get_chest_formspec(pos))
		open_chests[clicker:get_player_name()] = { pos = pos,
				sound = old_chest_locked_def.sound_close, swap = "chest_locked" }
	end,
	groups = table.copy(old_chest_locked_def.groups),
	tube = {
		insert_object = function(pos, node, stack, direction)
			local meta = minetest.get_meta(pos)
			local inv = meta:get_inventory()
			return inv:add_item("main", stack)
		end,
		can_insert = function(pos, node, stack, direction)
			local meta = minetest.get_meta(pos)
			local inv = meta:get_inventory()
			if meta:get_int("splitstacks") == 1 then
				stack = stack:peek_item(1)
			end
			return inv:room_for_item("main", stack)
		end,
		input_inventory = "main",
		connect_sides = {left = 1, right = 1, back = 1, bottom = 1, top = 1}
	},
	after_dig_node = pipeworks.after_dig,
	on_rotate = pipeworks.on_rotate
}
override = {
	tiles = {
		"default_chest_top.png"..tube_entry,
		"default_chest_top.png"..tube_entry,
		"default_chest_side.png"..tube_entry,
		"default_chest_side.png"..tube_entry,
		"default_chest_front.png",
		"default_chest_inside.png"
	},
	on_rightclick = function(pos, node, clicker)
		minetest.sound_play(old_chest_def.sound_open, {gain = 0.3, pos = pos,
				max_hear_distance = 10})
		if not chest_lid_obstructed(pos) then
			minetest.swap_node(pos, {
					name = "default:" .. "chest" .. "_open",
					param2 = node.param2 })
		end
		minetest.after(0.2, minetest.show_formspec,
				clicker:get_player_name(),
				"pipeworks:chest_formspec", get_chest_formspec(pos))
		open_chests[clicker:get_player_name()] = { pos = pos,
				sound = old_chest_def.sound_close, swap = "chest" }
	end,
	groups = table.copy(old_chest_def.groups),
	tube = {
		insert_object = function(pos, node, stack, direction)
			local meta = minetest.get_meta(pos)
			local inv = meta:get_inventory()
			return inv:add_item("main", stack)
		end,
		can_insert = function(pos, node, stack, direction)
			local meta = minetest.get_meta(pos)
			local inv = meta:get_inventory()
			if meta:get_int("splitstacks") == 1 then
				stack = stack:peek_item(1)
			end
			return inv:room_for_item("main", stack)
		end,
		input_inventory = "main",
		connect_sides = {left = 1, right = 1, back = 1, bottom = 1, top = 1}
	},
	after_place_node = pipeworks.after_place,
	after_dig_node = pipeworks.after_dig,
	on_rotate = pipeworks.on_rotate
}
--[[local override_common = {

}
for k,v in pairs(override_common) do
	override_protected[k] = v
	override[k] = v
end]]

override_open = table.copy(override)
override_open.groups = table.copy(old_chest_open_def.groups)
override_open.tube = table.copy(override.tube)
override_open.tube.connect_sides = table.copy(override.tube.connect_sides)
override_open.tube.connect_sides.top = nil

override_protected_open = table.copy(override_protected)
override_protected_open.groups = table.copy(old_chest_locked_open_def.groups)
override_protected_open.tube = table.copy(override_protected.tube)
override_protected_open.tube.connect_sides = table.copy(override_protected.tube.connect_sides)
override_protected_open.tube.connect_sides.top = nil

override_protected.tiles = { -- Rearranged according to the chest registration in Minetest_Game.
	"default_chest_top.png"..tube_entry,
	"default_chest_top.png"..tube_entry,
	"default_chest_side.png"..tube_entry.."^[transformFX",
	"default_chest_side.png"..tube_entry,
	"default_chest_side.png"..tube_entry,
	"default_chest_lock.png",
}
override.tiles = {
	"default_chest_top.png"..tube_entry,
	"default_chest_top.png"..tube_entry,
	"default_chest_side.png"..tube_entry.."^[transformFX",
	"default_chest_side.png"..tube_entry,
	"default_chest_side.png"..tube_entry,
	"default_chest_front.png",
}

-- Add the extra groups
for i,v in ipairs({override_protected, override, override_open, override_protected_open}) do
	v.groups.tubedevice = 1
	v.groups.tubedevice_receiver = 1
end

-- Override with the new modifications.
minetest.override_item("default:chest", override)
minetest.override_item("default:chest_open", override_open)
minetest.override_item("default:chest_locked", override_protected)
minetest.override_item("default:chest_locked_open", override_protected_open)


code:
-- this file is basically a modified copy of
-- minetest_game/mods/default/furnaces.lua

-- translation support
local S = minetest.get_translator("pipeworks")
local DS = minetest.get_translator("default")

local fs_helpers = pipeworks.fs_helpers

tube_entry = "^pipeworks_tube_connection_stony.png"

local function active_formspec(fuel_percent, item_percent, pos, meta)
	local formspec =
		"size[8,8.5]"..
		default.gui_bg..
		default.gui_bg_img..
		default.gui_slots..
		"list[current_name;src;2.75,0.5;1,1;]"..
		"list[current_name;fuel;2.75,2.5;1,1;]"..
		"image[2.75,1.5;1,1;default_furnace_fire_bg.png^[lowpart:"..
		(100-fuel_percent)..":default_furnace_fire_fg.png]"..
		"image[3.75,1.5;1,1;gui_furnace_arrow_bg.png^[lowpart:"..
		(item_percent)..":gui_furnace_arrow_fg.png^[transformR270]"..
		"list[current_name;dst;4.75,0.96;2,2;]"..
		"list[current_player;main;0,4.25;8,1;]"..
		"list[current_player;main;0,5.5;8,3;8]"..
		"listring[current_name;dst]"..
		"listring[current_player;main]"..
		"listring[current_name;src]"..
		"listring[current_player;main]"..
		"listring[current_name;fuel]"..
		"listring[current_player;main]"..
		default.get_hotbar_bg(0, 4.25) ..
			fs_helpers.cycling_button(
				meta,
				"image_button[0,3.5;1,0.6",
				"split_material_stacks",
				{
					pipeworks.button_off,
					pipeworks.button_on
				}
			).."label[0.9,3.51;"..S("Allow splitting incoming material (not fuel) stacks from tubes").."]"
	return formspec
end

local function inactive_formspec(pos, meta)
	local formspec = "size[8,8.5]"..
	default.gui_bg..
	default.gui_bg_img..
	default.gui_slots..
	"list[current_name;src;2.75,0.5;1,1;]"..
	"list[current_name;fuel;2.75,2.5;1,1;]"..
	"image[2.75,1.5;1,1;default_furnace_fire_bg.png]"..
	"image[3.75,1.5;1,1;gui_furnace_arrow_bg.png^[transformR270]"..
	"list[current_name;dst;4.75,0.96;2,2;]"..
	"list[current_player;main;0,4.25;8,1;]"..
	"list[current_player;main;0,5.5;8,3;8]"..
	"listring[current_name;dst]"..
	"listring[current_player;main]"..
	"listring[current_name;src]"..
	"listring[current_player;main]"..
	"listring[current_name;fuel]"..
	"listring[current_player;main]"..
	default.get_hotbar_bg(0, 4.25) ..
		fs_helpers.cycling_button(
			meta,
			"image_button[0,3.5;1,0.6",
			"split_material_stacks",
			{
				pipeworks.button_off,
				pipeworks.button_on
			}
		).."label[0.9,3.51;"..S("Allow splitting incoming material (not fuel) stacks from tubes").."]"
	return formspec
end

--
-- Node callback functions that are the same for active and inactive furnace
--

local function can_dig(pos, player)
	local meta = minetest.get_meta(pos);
	local inv = meta:get_inventory()
	return inv:is_empty("fuel") and inv:is_empty("dst") and inv:is_empty("src")
end

local function allow_metadata_inventory_put(pos, listname, index, stack, player)
	if minetest.is_protected(pos, player:get_player_name()) then
		return 0
	end
	local meta = minetest.get_meta(pos)
	local inv = meta:get_inventory()
	if listname == "fuel" then
		if minetest.get_craft_result({method="fuel", width=1, items={stack}}).time ~= 0 then
			if inv:is_empty("src") then
				meta:set_string("infotext", DS("Furnace is empty"))
			end
			return stack:get_count()
		else
			return 0
		end
	elseif listname == "src" then
		return stack:get_count()
	elseif listname == "dst" then
		return 0
	end
end

local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player)
	local meta = minetest.get_meta(pos)
	local inv = meta:get_inventory()
	local stack = inv:get_stack(from_list, from_index)
	return allow_metadata_inventory_put(pos, to_list, to_index, stack, player)
end

local function allow_metadata_inventory_take(pos, listname, index, stack, player)
	if minetest.is_protected(pos, player:get_player_name()) then
		return 0
	end
	return stack:get_count()
end

local function swap_node(pos, name)
	local node = minetest.get_node(pos)
	if node.name == name then
		return
	end
	node.name = name
	minetest.swap_node(pos, node)
end

local function furnace_node_timer(pos, elapsed)
	--
	-- Inizialize metadata
	--
	local meta = minetest.get_meta(pos)
	local fuel_time = meta:get_float("fuel_time") or 0
	local src_time = meta:get_float("src_time") or 0
	local fuel_totaltime = meta:get_float("fuel_totaltime") or 0

	local inv = meta:get_inventory()
	local srclist, fuellist

	local cookable, cooked
	local fuel

	local update = true
	while update do
		update = false

		srclist = inv:get_list("src")
		fuellist = inv:get_list("fuel")

		--
		-- Cooking
		--

		-- Check if we have cookable content
		local aftercooked
		cooked, aftercooked = minetest.get_craft_result({method = "cooking", width = 1, items = srclist})
		cookable = cooked.time ~= 0

		-- Check if we have enough fuel to burn
		if fuel_time < fuel_totaltime then
			-- The furnace is currently active and has enough fuel
			fuel_time = fuel_time + elapsed
			-- If there is a cookable item then check if it is ready yet
			if cookable then
				src_time = src_time + elapsed
				if src_time >= cooked.time then
					-- Place result in dst list if possible
					if inv:room_for_item("dst", cooked.item) then
						inv:add_item("dst", cooked.item)
						inv:set_stack("src", 1, aftercooked.items[1])
						src_time = src_time - cooked.time
						update = true
					end
				end
			end
		else
			-- Furnace ran out of fuel
			if cookable then
				-- We need to get new fuel
				local afterfuel
				fuel, afterfuel = minetest.get_craft_result({method = "fuel", width = 1, items = fuellist})

				if fuel.time == 0 then
					-- No valid fuel in fuel list
					fuel_totaltime = 0
					src_time = 0
				else
					-- Take fuel from fuel list
					inv:set_stack("fuel", 1, afterfuel.items[1])
					update = true
					fuel_totaltime = fuel.time + (fuel_time - fuel_totaltime)
					src_time = src_time + elapsed
				end
			else
				-- We don't need to get new fuel since there is no cookable item
				fuel_totaltime = 0
				src_time = 0
			end
			fuel_time = 0
		end

		elapsed = 0
	end

	if fuel and fuel_totaltime > fuel.time then
		fuel_totaltime = fuel.time
	end
	if srclist[1]:is_empty() then
		src_time = 0
	end

	--
	-- Update formspec, infotext and node
	--
	local formspec = inactive_formspec(pos, meta)
	local item_state
	local item_percent = 0
	if cookable then
		item_percent = math.floor(src_time / cooked.time * 100)
		if item_percent > 100 then
			item_state = DS("100% (output full)")
		else
			item_state = DS("@1%", item_percent)
		end
	else
		if srclist[1]:is_empty() then
			item_state = DS("Empty")
		else
			item_state = DS("Not cookable")
		end
	end

	local fuel_state = DS("Empty")
	local active = DS("Furnace inactive")
	local result = false

	if fuel_totaltime ~= 0 then
		active = DS("Furnace active")
		local fuel_percent = math.floor(fuel_time / fuel_totaltime * 100)
		fuel_state = DS("@1%", fuel_percent)
		formspec = active_formspec(fuel_percent, item_percent, pos, meta)
		swap_node(pos, "default:furnace_active")
		-- make sure timer restarts automatically
		result = true
	else
		if not fuellist[1]:is_empty() then
			fuel_state = DS("@1%", "0")
		end
		swap_node(pos, "default:furnace")
		-- stop timer on the inactive furnace
		minetest.get_node_timer(pos):stop()
	end

	local infotext = active.." "..DS("(Item: @1; Fuel: @2)", item_state, fuel_state)

	--
	-- Set meta values
	--
	meta:set_float("fuel_totaltime", fuel_totaltime)
	meta:set_float("fuel_time", fuel_time)
	meta:set_float("src_time", src_time)
	meta:set_string("formspec", formspec)
	meta:set_string("infotext", infotext)

	return result
end

--
-- Node definitions
--

minetest.register_node(":default:furnace", {
	description = DS("Furnace"),
	tiles = {
		"default_furnace_top.png"..tube_entry,
		"default_furnace_bottom.png"..tube_entry,
		"default_furnace_side.png"..tube_entry,
		"default_furnace_side.png"..tube_entry,
		"default_furnace_side.png"..tube_entry,
		"default_furnace_front.png"
	},
	groups = {cracky = 2, tubedevice = 1, tubedevice_receiver = 1},
	tube = {
		insert_object = function(pos, node, stack, direction)
			local meta = minetest.get_meta(pos)
			local inv = meta:get_inventory()
			local timer = minetest.get_node_timer(pos)
			if not timer:is_started() then
				timer:start(1.0)
			end
			if direction.y == 1 then
				return inv:add_item("fuel", stack)
			else
				return inv:add_item("src", stack)
			end
		end,
		can_insert = function(pos,node,stack,direction)
			local meta = minetest.get_meta(pos)
			local inv = meta:get_inventory()
			if direction.y == 1 then
				return inv:room_for_item("fuel", stack)
			else
				if meta:get_int("split_material_stacks") == 1 then
					stack = stack:peek_item(1)
				end
				return inv:room_for_item("src", stack)
			end
		end,
		input_inventory = "dst",
		connect_sides = {left = 1, right = 1, back = 1, front = 1, bottom = 1, top = 1}
	},
	paramtype2 = "facedir",
	legacy_facedir_simple = true,
	is_ground_content = false,
	sounds = default.node_sound_stone_defaults(),

	can_dig = can_dig,

	on_timer = furnace_node_timer,

	on_construct = function(pos)
		local meta = minetest.get_meta(pos)
		meta:set_string("formspec", inactive_formspec(pos, meta))
		local inv = meta:get_inventory()
		inv:set_size('src', 1)
		inv:set_size('fuel', 1)
		inv:set_size('dst', 4)
	end,

	on_metadata_inventory_move = function(pos)
		minetest.get_node_timer(pos):start(1.0)
	end,
	on_metadata_inventory_put = function(pos)
		-- start timer function, it will sort out whether furnace can burn or not.
		minetest.get_node_timer(pos):start(1.0)
	end,
	on_blast = function(pos)
		local drops = {}
		default.get_inventory_drops(pos, "src", drops)
		default.get_inventory_drops(pos, "fuel", drops)
		default.get_inventory_drops(pos, "dst", drops)
		drops[#drops+1] = "default:furnace"
		minetest.remove_node(pos)
		return drops
	end,
	allow_metadata_inventory_put = allow_metadata_inventory_put,
	allow_metadata_inventory_move = allow_metadata_inventory_move,
	allow_metadata_inventory_take = allow_metadata_inventory_take,
	on_receive_fields = function(pos, formname, fields, sender)
		if not pipeworks.may_configure(pos, sender) then return end
		fs_helpers.on_receive_fields(pos, fields)
		local meta = minetest.get_meta(pos)
		local formspec = inactive_formspec(pos, meta)
		meta:set_string("formspec", formspec)
	end,
	after_place_node = pipeworks.after_place,
	after_dig_node = pipeworks.after_dig,
	on_rotate = pipeworks.on_rotate
})

minetest.register_node(":default:furnace_active", {
	description = DS("Furnace"),
	tiles = {
		"default_furnace_top.png"..tube_entry,
		"default_furnace_bottom.png"..tube_entry,
		"default_furnace_side.png"..tube_entry,
		"default_furnace_side.png"..tube_entry,
		"default_furnace_side.png"..tube_entry,
		{
			image = "default_furnace_front_active.png",
			backface_culling = false,
			animation = {
				type = "vertical_frames",
				aspect_w = 16,
				aspect_h = 16,
				length = 1.5
			},
		}
	},
	groups = {cracky = 2, tubedevice = 1, tubedevice_receiver = 1, not_in_creative_inventory = 1},
	tube = {
		insert_object = function(pos,node,stack,direction)
			local meta = minetest.get_meta(pos)
			local inv = meta:get_inventory()
			local timer = minetest.get_node_timer(pos)
			if not timer:is_started() then
				timer:start(1.0)
			end
			if direction.y == 1 then
				return inv:add_item("fuel", stack)
			else
				return inv:add_item("src", stack)
			end
		end,
		can_insert = function(pos, node, stack, direction)
			local meta = minetest.get_meta(pos)
			local inv = meta:get_inventory()
			if direction.y == 1 then
				return inv:room_for_item("fuel", stack)
			else
				if meta:get_int("split_material_stacks") == 1 then
					stack = stack:peek_item(1)
				end
				return inv:room_for_item("src", stack)
			end
		end,
		input_inventory = "dst",
		connect_sides = {left = 1, right = 1, back = 1, front = 1, bottom = 1, top = 1}
	},
	paramtype2 = "facedir",
	light_source = 8,
	drop = "default:furnace",
	legacy_facedir_simple = true,
	is_ground_content = false,
	sounds = default.node_sound_stone_defaults(),
	on_timer = furnace_node_timer,

	can_dig = can_dig,

	allow_metadata_inventory_put = allow_metadata_inventory_put,
	allow_metadata_inventory_move = allow_metadata_inventory_move,
	allow_metadata_inventory_take = allow_metadata_inventory_take,
	on_receive_fields = function(pos, formname, fields, sender)
		if not pipeworks.may_configure(pos, sender) then return end
		fs_helpers.on_receive_fields(pos, fields)
		local meta = minetest.get_meta(pos)
		local formspec = active_formspec(0, 0, pos, meta)
		meta:set_string("formspec", formspec)
	end,
	after_place_node = pipeworks.after_place,
	after_dig_node = pipeworks.after_dig,
	on_rotate = pipeworks.on_rotate
})


code:
-- Crafting recipes for pipes

minetest.register_craft( {
        output = "pipeworks:pipe_1_empty 12",
        recipe = {
                { "default:steel_ingot", "default:steel_ingot", "default:steel_ingot" },
                { "", "", "" },
                { "default:steel_ingot", "default:steel_ingot", "default:steel_ingot" }
        },
})

minetest.register_craft( {
        output = "pipeworks:straight_pipe_empty 3",
        recipe = {
                { "pipeworks:pipe_1_empty", "pipeworks:pipe_1_empty", "pipeworks:pipe_1_empty" },
        },
})

minetest.register_craft( {
        output = "pipeworks:spigot 3",
        recipe = {
                { "pipeworks:pipe_1_empty", "" },
                { "", "pipeworks:pipe_1_empty" },
        },
})

minetest.register_craft( {
	output = "pipeworks:entry_panel_empty 2",
	recipe = {
		{ "", "default:steel_ingot", "" },
		{ "", "pipeworks:pipe_1_empty", "" },
		{ "", "default:steel_ingot", "" },
	},
})

-- Various ancillary pipe devices

minetest.register_craft( {
        output = "pipeworks:pump_off 2",
        recipe = {
                { "default:stone", "default:steel_ingot", "default:stone" },
                { "default:copper_ingot", "default:mese_crystal_fragment", "default:copper_ingot" },
                { "default:steel_ingot", "default:steel_ingot", "default:steel_ingot" }
        },
})

minetest.register_craft( {
        output = "pipeworks:valve_off_empty 2",
        recipe = {
                { "", "group:stick", "" },
                { "default:steel_ingot", "default:steel_ingot", "default:steel_ingot" },
                { "", "default:steel_ingot", "" }
        },
})

minetest.register_craft( {
        output = "pipeworks:storage_tank_0 2",
        recipe = {
                { "", "default:steel_ingot", "default:steel_ingot" },
                { "default:steel_ingot", "default:glass", "default:steel_ingot" },
                { "default:steel_ingot", "default:steel_ingot", "" }
        },
})

minetest.register_craft( {
        output = "pipeworks:grating 2",
        recipe = {
                { "default:steel_ingot", "", "default:steel_ingot" },
                { "", "pipeworks:pipe_1_empty", "" },
                { "default:steel_ingot", "", "default:steel_ingot" }
        },
})

minetest.register_craft( {
        output = "pipeworks:flow_sensor_empty 2",
        recipe = {
                { "pipeworks:pipe_1_empty", "mesecons:mesecon", "pipeworks:pipe_1_empty" },
        },
})

minetest.register_craft( {
        output = "pipeworks:fountainhead 2",
        recipe = {
                { "pipeworks:pipe_1_empty" },
				{ "pipeworks:pipe_1_empty" }
        },
})


code:
local S = minetest.get_translator("pipeworks")

local straight = function(pos, node, velocity, stack) return {velocity} end

minetest.register_node("pipeworks:steel_block_embedded_tube", {
	description = S("Airtight steelblock embedded tube"),
	tiles = {
		"default_steel_block.png", "default_steel_block.png",
		"default_steel_block.png", "default_steel_block.png",
		"default_steel_block.png^pipeworks_tube_connection_metallic.png",
		"default_steel_block.png^pipeworks_tube_connection_metallic.png",
		},
	paramtype = "light",
	paramtype2 = "facedir",
	groups = {cracky=1, oddly_breakable_by_hand = 1, tubedevice = 1},
	legacy_facedir_simple = true,
	sounds = default.node_sound_stone_defaults(),
	tube = {
		connect_sides = {front = 1, back = 1,},
		priority = 50,
		can_go = straight,
		can_insert = function(pos, node, stack, direction)
			local dir = minetest.facedir_to_dir(node.param2)
			return vector.equals(dir, direction) or vector.equals(vector.multiply(dir, -1), direction)
		end,
	},
	after_place_node = pipeworks.after_place,
	after_dig_node = pipeworks.after_dig,
	on_rotate = pipeworks.on_rotate,
})

minetest.register_craft( {
	output = "pipeworks:steel_block_embedded_tube 1",
	recipe = {
		{ "default:steel_ingot", "default:steel_ingot", "default:steel_ingot" },
		{ "default:steel_ingot", "pipeworks:tube_1", "default:steel_ingot" },
		{ "default:steel_ingot", "default:steel_ingot", "default:steel_ingot" }
	},
})

local pane_box = {
	type = "fixed",
	fixed = {
		{ -9/64, -9/64, -8/16, 9/64, 9/64, 8/16 }, -- tube
		{ -8/16, -8/16, -1/16, 8/16, 8/16, 1/16 } -- pane
	}
}
minetest.register_node("pipeworks:steel_pane_embedded_tube", {
	drawtype = "nodebox",
	description = S("Airtight panel embedded tube"),
	tiles = {
		"pipeworks_pane_embedded_tube_sides.png^[transformR90",
		"pipeworks_pane_embedded_tube_sides.png^[transformR90",
		"pipeworks_pane_embedded_tube_sides.png",
		"pipeworks_pane_embedded_tube_sides.png",
		"pipeworks_pane_embedded_tube_ends.png", "pipeworks_pane_embedded_tube_ends.png",
		},
	node_box = pane_box,
	selection_box = pane_box,
	collision_box = pane_box,
	paramtype = "light",
	paramtype2 = "facedir",
	groups = {cracky=1, oddly_breakable_by_hand = 1, tubedevice = 1},
	legacy_facedir_simple = true,
	sounds = default.node_sound_stone_defaults(),
	tube = {
		connect_sides = {front = 1, back = 1,},
		priority = 50,
		can_go = straight,
		can_insert = function(pos, node, stack, direction)
			local dir = minetest.facedir_to_dir(node.param2)
			return vector.equals(dir, direction) or vector.equals(vector.multiply(dir, -1), direction)
		end,
	},
	after_place_node = pipeworks.after_place,
	after_dig_node = pipeworks.after_dig,
	on_rotate = pipeworks.on_rotate,
})

minetest.register_craft( {
	output = "pipeworks:steel_pane_embedded_tube 1",
	recipe = {
		{ "", "default:steel_ingot", "" },
		{ "", "pipeworks:tube_1", "" },
		{ "", "default:steel_ingot", "" }
	},
})


code:
-- Various settings

local prefix = "pipeworks_"

local settings = {
	enable_pipes = true,
	enable_lowpoly = false,
	enable_autocrafter = true,
	enable_deployer = true,
	enable_dispenser = true,
	enable_node_breaker = true,
	enable_teleport_tube = true,
	enable_pipe_devices = true,
	enable_redefines = true,
	enable_mese_tube = true,
	enable_detector_tube = true,
	enable_digiline_detector_tube = true,
	enable_conductor_tube = true,
	enable_digiline_conductor_tube = true,
	enable_accelerator_tube = true,
	enable_crossing_tube = true,
	enable_sand_tube = true,
	enable_mese_sand_tube = true,
	enable_one_way_tube = true,
	enable_priority_tube = true,
	enable_lua_tube = true,
	enable_cyclic_mode = true,
	drop_on_routing_fail = false,
	delete_item_on_clearobject = true,
}

pipeworks.toggles = {}
-- documentation for toggles controlling pressure logic features.
-- do not edit this file directly;
-- instead, create pipeworks_settings.txt in your world directory,
-- and copy the uncommented lines from the block comments below into it.
--[[
-- flow logic implementation.
-- set to one of the following strings.
-- "classic": classic mode written by VanessaE
-- "pressure": pressure metadata based, written by thetaepsilon.
--	has caveats such as water speed issues though.
-- setting to nil inhibits all flow logic, useful for debugging ABM crashes,
-- or for rendering the pipes purely decorative.
]]
pipeworks.toggles.pipe_mode = "classic"
--[[
-- force-enable finite water handling mode.
-- this changes the way that water node placement is handled;
-- volume will always be preserved,
-- and water is assumed to move itself downwards.
-- nil (the default) means autodetect from installed finite liquid mods,
-- true is force-on, false is force-off.
-- note that you should NOT normally explicitly set this to true/false,
-- unless the mod you want this for is not covered by auto-detection
-- (please see autodetect-finite-water.lua).
-- please file an issue if you have a finite water mod not covered there,
-- and feel it necessary to explicitly set this toggle
pipeworks.toggles.finite_water = nil
]]

for name, value in pairs(settings) do
	local setting_type = type(value)
	if setting_type == "boolean" then
		pipeworks[name] = minetest.settings:get_bool(prefix..name)
		if pipeworks[name] == nil then
			pipeworks[name] = value
		end
	else
		pipeworks[name] = value
	end
end


code:
local S = minetest.get_translator("pipeworks")
local new_flow_logic_register = pipeworks.flowables.register

local polys = ""
if pipeworks.enable_lowpoly then polys = "_lowpoly" end

-- rotation handlers

function pipeworks.fix_after_rotation(pos, node, user, mode, new_param2)

	if string.find(node.name, "spigot") then new_param2 = new_param2 % 4 end

	newnode = string.gsub(node.name, "_on", "_off")
	minetest.swap_node(pos, { name = newnode, param2 = new_param2 })
	pipeworks.scan_for_pipe_objects(pos)

	return true
end

function pipeworks.rotate_on_place(itemstack, placer, pointed_thing)

	local playername = placer:get_player_name()
	if not minetest.is_protected(pointed_thing.under, playername)
	   and not minetest.is_protected(pointed_thing.above, playername) then

		local node = minetest.get_node(pointed_thing.under)

		if (not placer:get_player_control().sneak)
		  and minetest.registered_nodes[node.name]
		  and minetest.registered_nodes[node.name].on_rightclick then
			minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack)
		else

			local pitch = placer:get_look_pitch()
			local above = pointed_thing.above
			local under = pointed_thing.under
			local fdir = minetest.dir_to_facedir(placer:get_look_dir())
			local undernode = minetest.get_node(under)
			local abovenode = minetest.get_node(above)
			local uname = undernode.name
			local aname = abovenode.name
			local isabove = (above.x == under.x) and (above.z == under.z) and (pitch > 0)
			local pos1 = above

			-- check if the object should be turned vertically
			if above.x == under.x
				and above.z == under.z
				and (
				  string.find(uname, "pipeworks:pipe_")
				  or string.find(uname, "pipeworks:storage_")
				  or string.find(uname, "pipeworks:expansion_")
				  or ( string.find(uname, "pipeworks:grating") and not isabove )
				  or ( string.find(uname, "pipeworks:pump_") and not isabove )

				  or (
						( string.find(uname, "pipeworks:valve")
						  or string.find(uname, "pipeworks:entry_panel")
						  or string.find(uname, "pipeworks:flow_sensor") )
						and minetest.facedir_to_dir(undernode.param2).y ~= 0 )
					)
			then
				fdir = 17
			end

			if minetest.registered_nodes[uname]
			  and minetest.registered_nodes[uname]["buildable_to"] then
				pos1 = under
			end

			if minetest.registered_nodes[minetest.get_node(pos1).name]
			  and not minetest.registered_nodes[minetest.get_node(pos1).name]["buildable_to"] then return end

			local placednode = string.gsub(itemstack:get_name(), "_loaded", "_empty")
			placednode = string.gsub(placednode, "_on", "_off")

			minetest.swap_node(pos1, {name = placednode, param2 = fdir })
			pipeworks.scan_for_pipe_objects(pos1)

			if not pipeworks.expect_infinite_stacks then
				itemstack:take_item()
			end
		end
	end
	return itemstack
end

-- List of devices that should participate in the autoplace algorithm

local pipereceptor_on = nil
local pipereceptor_off = nil

if minetest.get_modpath("mesecons") then
	pipereceptor_on = {
		receptor = {
			state = mesecon.state.on,
			rules = pipeworks.mesecons_rules
		}
	}

	pipereceptor_off = {
		receptor = {
			state = mesecon.state.off,
			rules = pipeworks.mesecons_rules
		}
	}
end

local pipes_devicelist = {
	"pump",
	"valve",
	"storage_tank_0",
	"storage_tank_1",
	"storage_tank_2",
	"storage_tank_3",
	"storage_tank_4",
	"storage_tank_5",
	"storage_tank_6",
	"storage_tank_7",
	"storage_tank_8",
	"storage_tank_9",
	"storage_tank_10"
}

-- Now define the nodes.

local states = { "on", "off" }
local dgroups = ""

for s in ipairs(states) do

	if states[s] == "off" then
		dgroups = {snappy=3, pipe=1}
	else
		dgroups = {snappy=3, pipe=1, not_in_creative_inventory=1}
	end

	local pumpname = "pipeworks:pump_"..states[s]
	minetest.register_node(pumpname, {
		description = S("Pump/Intake Module"),
		drawtype = "mesh",
		mesh = "pipeworks_pump"..polys..".obj",
		tiles = { "pipeworks_pump_"..states[s]..".png" },
		paramtype = "light",
		paramtype2 = "facedir",
		groups = dgroups,
		sounds = default.node_sound_metal_defaults(),
		walkable = true,
		pipe_connections = { top = 1 },
		after_place_node = function(pos)
			pipeworks.scan_for_pipe_objects(pos)
		end,
		after_dig_node = function(pos)
			pipeworks.scan_for_pipe_objects(pos)
		end,
		drop = "pipeworks:pump_off",
		mesecons = {effector = {
			action_on = function (pos, node)
				minetest.swap_node(pos,{name="pipeworks:pump_on", param2 = node.param2})
			end,
			action_off = function (pos, node)
				minetest.swap_node(pos,{name="pipeworks:pump_off", param2 = node.param2})
			end
		}},
		on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
			local fdir = node.param2
			minetest.swap_node(pos, { name = "pipeworks:pump_"..states[3-s], param2 = fdir })
		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_vertical_fixed(pumpname, true)
	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 = S("Valve"),
		drawtype = "mesh",
		mesh = "pipeworks_valve_"..states[s]..polys..".obj",
		tiles = { "pipeworks_valve.png" },
		sunlight_propagates = true,
		paramtype = "light",
		paramtype2 = "facedir",
		selection_box = {
	             	type = "fixed",
			fixed = { -5/16, -4/16, -8/16, 5/16, 5/16, 8/16 }
		},
		collision_box = {
	             	type = "fixed",
			fixed = { -5/16, -4/16, -8/16, 5/16, 5/16, 8/16 }
		},
		groups = dgroups,
		sounds = default.node_sound_metal_defaults(),
		walkable = true,
		on_place = pipeworks.rotate_on_place,
		after_dig_node = function(pos)
			pipeworks.scan_for_pipe_objects(pos)
		end,
		drop = "pipeworks:valve_off_empty",
		mesecons = {effector = {
			action_on = function (pos, node)
				minetest.swap_node(pos,{name="pipeworks:valve_on_empty", param2 = node.param2})
			end,
			action_off = function (pos, node)
				minetest.swap_node(pos,{name="pipeworks:valve_off_empty", param2 = node.param2})
			end
		}},
		on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
			local fdir = node.param2
			minetest.swap_node(pos, { name = "pipeworks:valve_"..states[3-s].."_empty", param2 = fdir })
		end,
		on_rotate = pipeworks.fix_after_rotation
	})
	-- only register flow logic for the "on" ABM.
	-- this means that the off state automatically blocks flow by not participating in the balancing operation.
	if states[s] ~= "off" then
		new_flow_logic_register.directional_horizonal_rotate(nodename_valve_empty, true)
	end
end

local nodename_valve_loaded = "pipeworks:valve_on_loaded"
minetest.register_node(nodename_valve_loaded, {
	description = S("Valve"),
	drawtype = "mesh",
	mesh = "pipeworks_valve_on"..polys..".obj",
	tiles = { "pipeworks_valve.png" },
	sunlight_propagates = true,
	paramtype = "light",
	paramtype2 = "facedir",
	selection_box = {
             	type = "fixed",
		fixed = { -5/16, -4/16, -8/16, 5/16, 5/16, 8/16 }
	},
	collision_box = {
             	type = "fixed",
		fixed = { -5/16, -4/16, -8/16, 5/16, 5/16, 8/16 }
	},
	groups = {snappy=3, pipe=1, not_in_creative_inventory=1},
	sounds = default.node_sound_metal_defaults(),
	walkable = true,
	on_place = pipeworks.rotate_on_place,
	after_dig_node = function(pos)
		pipeworks.scan_for_pipe_objects(pos)
	end,
	drop = "pipeworks:valve_off_empty",
	mesecons = {effector = {
		action_on = function (pos, node)
			minetest.swap_node(pos,{name="pipeworks:valve_on_empty", param2 = node.param2})
		end,
		action_off = function (pos, node)
			minetest.swap_node(pos,{name="pipeworks:valve_off_empty", param2 = node.param2})
		end
	}},
	on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
		local fdir = node.param2
		minetest.swap_node(pos, { name = "pipeworks:valve_off_empty", param2 = fdir })
	end,
	on_rotate = pipeworks.fix_after_rotation
})
-- register this the same as the on-but-empty variant, so existing nodes of this type work also.
-- note that as new_flow_logic code does not distinguish empty/full in node states,
-- right-clicking a "loaded" valve (becoming an off valve) then turning it on again will yield a on-but-empty valve,
-- but the flow logic will still function.
-- thus under new_flow_logic this serves as a kind of migration.
new_flow_logic_register.directional_horizonal_rotate(nodename_valve_loaded, true)

-- grating

-- FIXME: should this do anything useful in the new flow logic?
minetest.register_node("pipeworks:grating", {
	description = S("Decorative grating"),
	tiles = {
		"pipeworks_grating_top.png",
		"pipeworks_grating_sides.png",
		"pipeworks_grating_sides.png",
		"pipeworks_grating_sides.png",
		"pipeworks_grating_sides.png",
		"pipeworks_grating_sides.png"
	},
	drawtype = "nodebox",
	node_box = {
		type = "fixed",
		fixed = { -0.49, -0.49, -0.49, 0.49, 0.5, 0.49 }
	},
	sunlight_propagates = true,
	paramtype = "light",
	groups = {snappy=3, pipe=1},
	sounds = default.node_sound_metal_defaults(),
	walkable = true,
	pipe_connections = { top = 1 },
	after_place_node = function(pos)
		pipeworks.scan_for_pipe_objects(pos)
	end,
	after_dig_node = function(pos)
		pipeworks.scan_for_pipe_objects(pos)
	end,
	on_rotate = false
})

-- outlet spigot

local nodename_spigot_empty = "pipeworks:spigot"
minetest.register_node(nodename_spigot_empty, {
	description = S("Spigot outlet"),
	drawtype = "mesh",
	mesh = "pipeworks_spigot"..polys..".obj",
	tiles = { "pipeworks_spigot.png" },
	sunlight_propagates = true,
	paramtype = "light",
	paramtype2 = "facedir",
	groups = {snappy=3, pipe=1},
	sounds = default.node_sound_metal_defaults(),
	walkable = true,
	pipe_connections = { left=1, right=1, front=1, back=1,
						 left_param2 = 3, right_param2 = 1, front_param2 = 2, back_param2 = 0 },
	after_place_node = function(pos)
		pipeworks.scan_for_pipe_objects(pos)
	end,
	after_dig_node = function(pos)
		pipeworks.scan_for_pipe_objects(pos)
	end,
	selection_box = {
		type = "fixed",
		fixed = { -2/16, -6/16, -2/16, 2/16, 2/16, 8/16 }
	},
	collision_box = {
		type = "fixed",
		fixed = { -2/16, -6/16, -2/16, 2/16, 2/16, 8/16 }
	},
	on_rotate = pipeworks.fix_after_rotation
})

local nodename_spigot_loaded = "pipeworks:spigot_pouring"
minetest.register_node(nodename_spigot_loaded, {
	description = S("Spigot outlet"),
	drawtype = "mesh",
	mesh = "pipeworks_spigot_pouring"..polys..".obj",
	tiles = {
		{
			name = "default_water_flowing_animated.png",
			animation = {
				type = "vertical_frames",
				aspect_w = 16,
				aspect_h = 16,
				length = 0.8,
			},
		},
		{ name = "pipeworks_spigot.png" }
	},
	sunlight_propagates = true,
	paramtype = "light",
	paramtype2 = "facedir",
	groups = {snappy=3, pipe=1, not_in_creative_inventory=1},
	sounds = default.node_sound_metal_defaults(),
	walkable = true,
	pipe_connections = { left=1, right=1, front=1, back=1,
						 left_param2 = 3, right_param2 = 1, front_param2 = 2, back_param2 = 0 },
	after_place_node = function(pos)
		minetest.set_node(pos, { name = "pipeworks:spigot", param2 = minetest.get_node(pos).param2 })
		pipeworks.scan_for_pipe_objects(pos)
	end,
	after_dig_node = function(pos)
		pipeworks.scan_for_pipe_objects(pos)
	end,
	selection_box = {
		type = "fixed",
		fixed = { -2/16, -6/16, -2/16, 2/16, 2/16, 8/16 }
	},
	collision_box = {
		type = "fixed",
		fixed = { -2/16, -6/16, -2/16, 2/16, 2/16, 8/16 }
	},
	drop = "pipeworks:spigot",
	on_rotate = pipeworks.fix_after_rotation
})
-- new flow logic does not currently distinguish between these two visual states.
-- register both so existing flowing spigots continue to work (even if the visual doesn't match the spigot's behaviour).
new_flow_logic_register.directional_horizonal_rotate(nodename_spigot_empty, false)
new_flow_logic_register.directional_horizonal_rotate(nodename_spigot_loaded, false)
local spigot_upper = 1.0
local spigot_lower = 1.0
local spigot_neighbours={{x=0, y=-1, z=0}}
new_flow_logic_register.output_simple(nodename_spigot_empty, spigot_upper, spigot_lower, spigot_neighbours)
new_flow_logic_register.output_simple(nodename_spigot_loaded, spigot_upper, spigot_lower, spigot_neighbours)

-- sealed pipe entry/exit (horizontal pipe passing through a metal
-- wall, for use in places where walls should look like they're airtight)

local panel_cbox = {
	type = "fixed",
	fixed = {
		{ -2/16, -2/16, -8/16, 2/16, 2/16, 8/16 },
		{ -8/16, -8/16, -1/16, 8/16, 8/16, 1/16 }
	}
}

local nodename_panel_empty = "pipeworks:entry_panel_empty"
minetest.register_node(nodename_panel_empty, {
	description = S("Airtight Pipe entry/exit"),
	drawtype = "mesh",
	mesh = "pipeworks_entry_panel"..polys..".obj",
	tiles = { "pipeworks_entry_panel.png" },
	paramtype = "light",
	paramtype2 = "facedir",
	groups = {snappy=3, pipe=1},
	sounds = default.node_sound_metal_defaults(),
	walkable = true,
	on_place = pipeworks.rotate_on_place,
	after_dig_node = function(pos)
		pipeworks.scan_for_pipe_objects(pos)
	end,
	selection_box = panel_cbox,
	collision_box = panel_cbox,
	on_rotate = pipeworks.fix_after_rotation
})

local nodename_panel_loaded = "pipeworks:entry_panel_loaded"
minetest.register_node(nodename_panel_loaded, {
	description = S("Airtight Pipe entry/exit"),
	drawtype = "mesh",
	mesh = "pipeworks_entry_panel"..polys..".obj",
	tiles = { "pipeworks_entry_panel.png" },
	paramtype = "light",
	paramtype2 = "facedir",
	groups = {snappy=3, pipe=1, not_in_creative_inventory=1},
	sounds = default.node_sound_metal_defaults(),
	walkable = true,
	on_place = pipeworks.rotate_on_place,
	after_dig_node = function(pos)
		pipeworks.scan_for_pipe_objects(pos)
	end,
	selection_box = panel_cbox,
	collision_box = panel_cbox,
	drop = "pipeworks:entry_panel_empty",
	on_rotate = pipeworks.fix_after_rotation
})
-- TODO: AFAIK the two panels have no visual difference, so are redundant under new flow logic - alias?
new_flow_logic_register.directional_horizonal_rotate(nodename_panel_empty, true)
new_flow_logic_register.directional_horizonal_rotate(nodename_panel_loaded, true)

local nodename_sensor_empty = "pipeworks:flow_sensor_empty"
minetest.register_node(nodename_sensor_empty, {
	description = S("Flow Sensor"),
	drawtype = "mesh",
	mesh = "pipeworks_flow_sensor"..polys..".obj",
	tiles = { "pipeworks_flow_sensor_off.png" },
	sunlight_propagates = true,
	paramtype = "light",
	paramtype2 = "facedir",
	groups = {snappy=3, pipe=1},
	sounds = default.node_sound_metal_defaults(),
	walkable = true,
	on_place = pipeworks.rotate_on_place,
	after_dig_node = function(pos)
		pipeworks.scan_for_pipe_objects(pos)
	end,
	on_construct = function(pos)
		if mesecon then
			mesecon.receptor_off(pos, rules)
		end
	end,
	selection_box = {
		type = "fixed",
		fixed = {
			{ -2/16, -2/16, -8/16, 2/16, 2/16, 8/16 },
			{ -3/16, -3/16, -4/16, 3/16, 3/16, 4/16 },
		}
	},
	collision_box = {
		type = "fixed",
		fixed = {
			{ -2/16, -2/16, -8/16, 2/16, 2/16, 8/16 },
			{ -3/16, -3/16, -4/16, 3/16, 3/16, 4/16 },
		}
	},
	mesecons = pipereceptor_off,
	on_rotate = pipeworks.fix_after_rotation
})

local nodename_sensor_loaded = "pipeworks:flow_sensor_loaded"
minetest.register_node(nodename_sensor_loaded, {
	description = S("Flow sensor (on)"),
	drawtype = "mesh",
	mesh = "pipeworks_flow_sensor"..polys..".obj",
	tiles = { "pipeworks_flow_sensor_on.png" },
	sunlight_propagates = true,
	paramtype = "light",
	paramtype2 = "facedir",
	groups = {snappy=3, pipe=1, not_in_creative_inventory=1},
	sounds = default.node_sound_metal_defaults(),
	walkable = true,
	on_place = pipeworks.rotate_on_place,
	after_dig_node = function(pos)
		pipeworks.scan_for_pipe_objects(pos)
	end,
	on_construct = function(pos)
		if mesecon then
			mesecon.receptor_on(pos, rules)
		end
	end,
	selection_box = {
		type = "fixed",
		fixed = {
			{ -2/16, -2/16, -8/16, 2/16, 2/16, 8/16 },
			{ -3/16, -3/16, -4/16, 3/16, 3/16, 4/16 },
		}
	},
	collision_box = {
		type = "fixed",
		fixed = {
			{ -2/16, -2/16, -8/16, 2/16, 2/16, 8/16 },
			{ -3/16, -3/16, -4/16, 3/16, 3/16, 4/16 },
		}
	},
	drop = "pipeworks:flow_sensor_empty",
	mesecons = pipereceptor_on,
	on_rotate = pipeworks.fix_after_rotation
})
new_flow_logic_register.directional_horizonal_rotate(nodename_sensor_empty, true)
new_flow_logic_register.directional_horizonal_rotate(nodename_sensor_loaded, true)
-- activate flow sensor at roughly half the pressure pumps drive pipes
local sensor_pressure_set = { { nodename_sensor_empty, 0.0 }, { nodename_sensor_loaded, 1.0 } }
new_flow_logic_register.transition_simple_set(sensor_pressure_set, { mesecons=pipeworks.mesecons_rules })

-- tanks

-- TODO flow-logic-stub: these don't currently do anything under the new flow logic.
for fill = 0, 10 do
	local filldesc=S("empty")
	local sgroups = {snappy=3, pipe=1, tankfill=fill+1}
	local image = nil

	if fill ~= 0 then
		filldesc=S("@1% full", 10*fill)
		sgroups = {snappy=3, pipe=1, tankfill=fill+1, not_in_creative_inventory=1}
		image = "pipeworks_storage_tank_fittings.png"
	end

	minetest.register_node("pipeworks:expansion_tank_"..fill, {
		description = S("Expansion Tank (@1)", filldesc),
		tiles = {
			"pipeworks_storage_tank_fittings.png",
			"pipeworks_storage_tank_fittings.png",
			"pipeworks_storage_tank_back.png",
			"pipeworks_storage_tank_back.png",
			"pipeworks_storage_tank_back.png",
			pipeworks.liquid_texture.."^pipeworks_storage_tank_front_"..fill..".png"
		},
		inventory_image = image,
		paramtype = "light",
		paramtype2 = "facedir",
		groups = {snappy=3, pipe=1, tankfill=fill+1, not_in_creative_inventory=1},
		sounds = default.node_sound_metal_defaults(),
		walkable = true,
		drop = "pipeworks:storage_tank_0",
		pipe_connections = { top = 1, bottom = 1},
		after_place_node = function(pos)
			pipeworks.look_for_stackable_tanks(pos)
			pipeworks.scan_for_pipe_objects(pos)
		end,
		after_dig_node = function(pos)
			pipeworks.scan_for_pipe_objects(pos)
		end,
		on_rotate = false
	})

	minetest.register_node("pipeworks:storage_tank_"..fill, {
		description = S("Fluid Storage Tank (@1)", filldesc),
		tiles = {
			"pipeworks_storage_tank_fittings.png",
			"pipeworks_storage_tank_fittings.png",
			"pipeworks_storage_tank_back.png",
			"pipeworks_storage_tank_back.png",
			"pipeworks_storage_tank_back.png",
			pipeworks.liquid_texture.."^pipeworks_storage_tank_front_"..fill..".png"
		},
		inventory_image = image,
		paramtype = "light",
		paramtype2 = "facedir",
		groups = sgroups,
		sounds = default.node_sound_metal_defaults(),
		walkable = true,
		drop = "pipeworks:storage_tank_0",
		pipe_connections = { top = 1, bottom = 1},
		after_place_node = function(pos)
			pipeworks.look_for_stackable_tanks(pos)
			pipeworks.scan_for_pipe_objects(pos)
		end,
		after_dig_node = function(pos)
			pipeworks.scan_for_pipe_objects(pos)
		end,
		on_rotate = false
	})
end

-- fountainhead

local nodename_fountain_empty = "pipeworks:fountainhead"
minetest.register_node(nodename_fountain_empty, {
	description = S("Fountainhead"),
	drawtype = "mesh",
	mesh = "pipeworks_fountainhead"..polys..".obj",
	tiles = { "pipeworks_fountainhead.png" },
	sunlight_propagates = true,
	paramtype = "light",
	groups = {snappy=3, pipe=1},
	sounds = default.node_sound_metal_defaults(),
	walkable = true,
	pipe_connections = { bottom = 1 },
	after_place_node = function(pos)
		pipeworks.scan_for_pipe_objects(pos)
	end,
	after_dig_node = function(pos)
		pipeworks.scan_for_pipe_objects(pos)
	end,
	on_construct = function(pos)
		if mesecon then
			mesecon.receptor_on(pos, rules)
		end
	end,
	selection_box = {
		type = "fixed",
		fixed = { -2/16, -8/16, -2/16, 2/16, 8/16, 2/16 }
	},
	collision_box = {
		type = "fixed",
		fixed = { -2/16, -8/16, -2/16, 2/16, 8/16, 2/16 }
	},
	on_rotate = false
})

local nodename_fountain_loaded = "pipeworks:fountainhead_pouring"
minetest.register_node(nodename_fountain_loaded, {
	description = S("Fountainhead"),
	drawtype = "mesh",
	mesh = "pipeworks_fountainhead"..polys..".obj",
	tiles = { "pipeworks_fountainhead.png" },
	sunlight_propagates = true,
	paramtype = "light",
	groups = {snappy=3, pipe=1, not_in_creative_inventory=1},
	sounds = default.node_sound_metal_defaults(),
	walkable = true,
	pipe_connections = { bottom = 1 },
	after_place_node = function(pos)
		minetest.set_node(pos, { name = "pipeworks:fountainhead", param2 = minetest.get_node(pos).param2 })
		pipeworks.scan_for_pipe_objects(pos)
	end,
	after_dig_node = function(pos)
		pipeworks.scan_for_pipe_objects(pos)
	end,
	on_construct = function(pos)
		if mesecon then
			mesecon.receptor_on(pos, rules)
		end
	end,
	selection_box = {
		type = "fixed",
		fixed = { -2/16, -8/16, -2/16, 2/16, 8/16, 2/16 }
	},
	collision_box = {
		type = "fixed",
		fixed = { -2/16, -8/16, -2/16, 2/16, 8/16, 2/16 }
	},
	drop = "pipeworks:fountainhead",
	on_rotate = false
})
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}}
new_flow_logic_register.output_simple(nodename_fountain_empty, fountain_upper, fountain_lower, fountain_neighbours)
new_flow_logic_register.output_simple(nodename_fountain_loaded, fountain_upper, fountain_lower, fountain_neighbours)

local sp_cbox = {
	type = "fixed",
	fixed = {
		{ -2/16, -2/16, -8/16, 2/16, 2/16, 8/16 }
	}
}

local nodename_sp_empty = "pipeworks:straight_pipe_empty"
minetest.register_node(nodename_sp_empty, {
	description = S("Straight-only Pipe"),
	drawtype = "mesh",
	mesh = "pipeworks_straight_pipe"..polys..".obj",
	tiles = { "pipeworks_straight_pipe_empty.png" },
	paramtype = "light",
	paramtype2 = "facedir",
	groups = {snappy=3, pipe=1},
	sounds = default.node_sound_metal_defaults(),
	walkable = true,
	on_place = pipeworks.rotate_on_place,
	after_dig_node = function(pos)
		pipeworks.scan_for_pipe_objects(pos)
	end,
	selection_box = sp_cbox,
	collision_box = sp_cbox,
	on_rotate = pipeworks.fix_after_rotation,
	check_for_pole = pipeworks.check_for_vert_pipe,
	check_for_horiz_pole = pipeworks.check_for_horiz_pipe
})

local nodename_sp_loaded = "pipeworks:straight_pipe_loaded"
minetest.register_node(nodename_sp_loaded, {
	description = S("Straight-only Pipe"),
	drawtype = "mesh",
	mesh = "pipeworks_straight_pipe"..polys..".obj",
	tiles = { "pipeworks_straight_pipe_loaded.png" },
	paramtype = "light",
	paramtype2 = "facedir",
	groups = {snappy=3, pipe=1, not_in_creative_inventory=1},
	sounds = default.node_sound_metal_defaults(),
	walkable = true,
	on_place = pipeworks.rotate_on_place,
	after_dig_node = function(pos)
		pipeworks.scan_for_pipe_objects(pos)
	end,
	selection_box = sp_cbox,
	collision_box = sp_cbox,
	drop = "pipeworks:straight_pipe_empty",
	on_rotate = pipeworks.fix_after_rotation,
	check_for_pole = pipeworks.check_for_vert_pipe,
	check_for_horiz_pole = pipeworks.check_for_horiz_pipe
})

new_flow_logic_register.directional_horizonal_rotate(nodename_sp_empty, true)
new_flow_logic_register.directional_horizonal_rotate(nodename_sp_loaded, true)

-- Other misc stuff

minetest.register_alias("pipeworks:valve_off_loaded", "pipeworks:valve_off_empty")
minetest.register_alias("pipeworks:entry_panel", "pipeworks:entry_panel_empty")


code:
local S = minetest.get_translator("pipeworks")
local fs_helpers = pipeworks.fs_helpers

local function delay(x)
	return (function() return x end)
end

local function set_filter_infotext(data, meta)
	local infotext = S("@1 Filter-Injector", data.wise_desc)
	if meta:get_int("slotseq_mode") == 2 then
		infotext = infotext .. " "..S("(slot #@1 next)", meta:get_int("slotseq_index"))
	end
	meta:set_string("infotext", infotext)
end

local function set_filter_formspec(data, meta)
	local itemname = S("@1 Filter-Injector", data.wise_desc)

	local formspec
	if data.digiline then
		formspec = "size[8,2.7]"..
			"item_image[0,0;1,1;pipeworks:"..data.name.."]"..
			"label[1,0;"..minetest.formspec_escape(itemname).."]"..
			"field[0.3,1.5;8.0,1;channel;"..S("Channel")..";${channel}]"..
			fs_helpers.cycling_button(meta, "button[0,2;4,1", "slotseq_mode",
				{S("Sequence slots by Priority"),
				 S("Sequence slots Randomly"),
				 S("Sequence slots by Rotation")})..
			fs_helpers.cycling_button(meta, "button[4,2;4,1", "exmatch_mode",
				{S("Exact match - off"),
				 S("Exact match - on")})
	else
		local exmatch_button = ""
		if data.stackwise then
			exmatch_button =
				fs_helpers.cycling_button(meta, "button[4,3.5;4,1", "exmatch_mode",
					{S("Exact match - off"),
					 S("Exact match - on")})
		end

		formspec = "size[8,8.5]"..
			"item_image[0,0;1,1;pipeworks:"..data.name.."]"..
			"label[1,0;"..minetest.formspec_escape(itemname).."]"..
			"label[0,1;"..S("Prefer item types:").."]"..
			"list[context;main;0,1.5;8,2;]"..
			fs_helpers.cycling_button(meta, "button[0,3.5;4,1", "slotseq_mode",
				{S("Sequence slots by Priority"),
				 S("Sequence slots Randomly"),
				 S("Sequence slots by Rotation")})..
			exmatch_button..
			"list[current_player;main;0,4.5;8,4;]" ..
			"listring[]"
	end
	meta:set_string("formspec", formspec)
end

local function punch_filter(data, filtpos, filtnode, msg)
	local filtmeta = minetest.get_meta(filtpos)
	local filtinv = filtmeta:get_inventory()
	local owner = filtmeta:get_string("owner")
	local fakePlayer = pipeworks.create_fake_player({
		name = owner
	})
	local dir = pipeworks.facedir_to_right_dir(filtnode.param2)
	local frompos = vector.subtract(filtpos, dir)
	local fromnode = minetest.get_node(frompos)
	if not fromnode then return end
	local fromdef = minetest.registered_nodes[fromnode.name]
	if not fromdef then return end
	local fromtube = fromdef.tube
	local input_special_cases = {
		["technic:mv_electric_furnace"] = "dst",
		["technic:mv_electric_furnace_active"] = "dst",
		["technic:mv_alloy_furnace"] = "dst",
		["technic:mv_alloy_furnace_active"] = "dst",
		["technic:mv_centrifuge"] = "dst",
		["technic:mv_centrifuge_active"] = "dst",
		["technic:mv_compressor"] = "dst",
		["technic:mv_compressor_active"] = "dst",
		["technic:mv_extractor"] = "dst",
		["technic:mv_extractor_active"] = "dst",
		["technic:mv_grinder"] = "dst",
		["technic:mv_grinder_active"] = "dst",
		["technic:tool_workshop"] = "src",
		["technic:mv_freezer"] = "dst",
		["technic:mv_freezer_active"] = "dst",
		["technic:hv_electric_furnace"] = "dst",
		["technic:hv_electric_furnace_active"] = "dst",
		["technic:hv_compressor"] = "dst",
		["technic:hv_compressor_active"] = "dst",
		["technic:hv_grinder"] = "dst",
		["technic:hv_grinder_active"] = "dst"
	}

	-- make sure there's something appropriate to inject the item into
	local todir = pipeworks.facedir_to_right_dir(filtnode.param2)
	local topos = vector.add(filtpos, todir)
	local tonode = minetest.get_node(topos)
	local todef = minetest.registered_nodes[tonode.name]

	if not todef
	  or not (minetest.get_item_group(tonode.name, "tube") == 1
			  or minetest.get_item_group(tonode.name, "tubedevice") == 1
			  or minetest.get_item_group(tonode.name, "tubedevice_receiver") == 1) then
		return
	end

	if fromtube then fromtube.input_inventory = input_special_cases[fromnode.name] or fromtube.input_inventory end
	if not (fromtube and fromtube.input_inventory) then return end

	local slotseq_mode
	local exmatch_mode

	local filters = {}
	if data.digiline then
		local function add_filter(name, group, count, wear, metadata)
			table.insert(filters, {name = name, group = group, count = tonumber(count), wear = wear, metadata = metadata})
		end

		local function add_itemstring_filter(filter)
			local filterstack = ItemStack(filter)
			local filtername = filterstack:get_name()
			local filtercount = filterstack:get_count()
			local filterwear = string.match(filter, "%S*:%S*%s%d%s(%d)") and filterstack:get_wear()
			local filtermetadata = string.match(filter, "%S*:%S*%s%d%s%d(%s.*)") and filterstack:get_metadata()

			add_filter(filtername, nil, filtercount, filterwear, filtermetadata)
		end

		local t_msg = type(msg)
		if t_msg == "table" then
			local slotseq = msg.slotseq
			local t_slotseq = type(slotseq)
			if t_slotseq == "number" and slotseq >= 0 and slotseq <= 2 then
				slotseq_mode = slotseq
			elseif t_slotseq == "string" then
				slotseq = string.lower(slotseq)
				if slotseq == "priority" then
					slotseq_mode = 0
				elseif slotseq == "random" then
					slotseq_mode = 1
				elseif slotseq == "rotation" then
					slotseq_mode = 2
				end
			end

			local exmatch = msg.exmatch
			local t_exmatch = type(exmatch)
			if t_exmatch == "number" and (exmatch == 0 or exmatch == 1) then
				exmatch_mode = exmatch
			elseif t_exmatch == "boolean" then
				exmatch_mode = exmatch and 1 or 0
			end

			local slotseq_index = msg.slotseq_index
			if type(slotseq_index) == "number" then
				-- This should allow any valid index, but I'm not completely sure what
				-- constitutes a valid index, so I'm only allowing resetting it to 1.
				if slotseq_index == 1 then
					filtmeta:set_int("slotseq_index", slotseq_index)
					set_filter_infotext(data, filtmeta)
				end
			end

			if slotseq_mode ~= nil then
				filtmeta:set_int("slotseq_mode", slotseq_mode)
			end

			if exmatch_mode ~= nil then
				filtmeta:set_int("exmatch_mode", exmatch_mode)
			end

			if slotseq_mode ~= nil or exmatch_mode ~= nil then
				set_filter_formspec(data, filtmeta)
			end

			if msg.nofire then
				return
			end

			if msg.name or msg.group or msg.count or msg.wear or msg.metadata then
				add_filter(msg.name, msg.group, msg.count, msg.wear, msg.metadata)
			else
				for _, filter in ipairs(msg) do
					local t_filter = type(filter)
					if t_filter == "table" then
						if filter.name or filter.group or filter.count or filter.wear or filter.metadata then
							add_filter(filter.name, filter.group, filter.count, filter.wear, filter.metadata)
						end
					elseif t_filter == "string" then
						add_itemstring_filter(filter)
					end
				end
			end
		elseif t_msg == "string" then
			add_itemstring_filter(msg)
		end
	else
		for _, filterstack in ipairs(filtinv:get_list("main")) do
			local filtername = filterstack:get_name()
			local filtercount = filterstack:get_count()
			if filtername ~= "" then table.insert(filters, {name = filtername, count = filtercount}) end
		end
	end
	if #filters == 0 then table.insert(filters, "") end

	if slotseq_mode == nil then
		slotseq_mode = filtmeta:get_int("slotseq_mode")
	end

	if exmatch_mode == nil then
		exmatch_mode = filtmeta:get_int("exmatch_mode")
	end

	local frominv
	if fromtube.return_input_invref then
		frominv = fromtube.return_input_invref(frompos, fromnode, dir, owner)
		if not frominv then
			return
		end
	else
		local frommeta = minetest.get_meta(frompos)
		frominv = frommeta:get_inventory()
	end
	if fromtube.before_filter then fromtube.before_filter(frompos) end

	local function grabAndFire(frominvname, filterfor)
		local sposes = {}
		if not frominvname or not frominv:get_list(frominvname) then return end
		for spos,stack in ipairs(frominv:get_list(frominvname)) do
			local matches
			if filterfor == "" then
				matches = stack:get_name() ~= ""
			else
				local fname = filterfor.name
				local fgroup = filterfor.group
				local fwear = filterfor.wear
				local fmetadata = filterfor.metadata
				matches = (not fname                                             -- If there's a name filter,
				           or stack:get_name() == fname)                         --  it must match.

				          and (not fgroup                                        -- If there's a group filter,
				               or (type(fgroup) == "string"                      --  it must be a string
				                   and minetest.get_item_group(                  --  and it must match.
				                                stack:get_name(), fgroup) ~= 0))

				          and (not fwear                                         -- If there's a wear filter:
				               or (type(fwear) == "number"                       --  If it's a number,
				                   and stack:get_wear() == fwear)                --   it must match.
				               or (type(fwear) == "table"                        --  If it's a table:
				                   and (not fwear[1]                             --   If there's a lower bound,
				                        or (type(fwear[1]) == "number"           --    it must be a number
				                            and fwear[1] <= stack:get_wear()))   --    and it must be <= the actual wear.
				                   and (not fwear[2]                             --   If there's an upper bound
				                        or (type(fwear[2]) == "number"           --    it must be a number
				                            and stack:get_wear() < fwear[2]))))  --    and it must be > the actual wear.
				                                                                 --  If the wear filter is of any other type, fail.

				          and (not fmetadata                                     -- If there's a metadata filter,
				               or (type(fmetadata) == "string"                   --  it must be a string
				                   and stack:get_metadata() == fmetadata))       --  and it must match.
			end
			if matches then table.insert(sposes, spos) end
		end
		if #sposes == 0 then return false end
		if slotseq_mode == 1 then
			for i = #sposes, 2, -1 do
				local j = math.random(i)
				local t = sposes[j]
				sposes[j] = sposes[i]
				sposes[i] = t
			end
		elseif slotseq_mode == 2 then
			local headpos = filtmeta:get_int("slotseq_index")
			table.sort(sposes, function (a, b)
				if a >= headpos then
					if b < headpos then return true end
				else
					if b >= headpos then return false end
				end
				return a < b
			end)
		end
		for _, spos in ipairs(sposes) do
			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, frominvname, spos)
			elseif fromdef.allow_metadata_inventory_take then
				doRemove = fromdef.allow_metadata_inventory_take(frompos, frominvname,spos, stack, fakePlayer)
			end
			-- stupid lack of continue statements grumble
			if doRemove > 0 then
				if slotseq_mode == 2 then
					local nextpos = spos + 1
					if nextpos > frominv:get_size(frominvname) then
						nextpos = 1
					end
					filtmeta:set_int("slotseq_index", nextpos)
					set_filter_infotext(data, filtmeta)
				end
				local item
				local count
				if data.stackwise then
					count = math.min(stack:get_count(), doRemove)
					if filterfor.count and (filterfor.count > 1 or data.digiline) then
						if exmatch_mode ~= 0 and filterfor.count > count then
							return false -- not enough, fail
						else
							-- limit quantity to filter amount
							count = math.min(filterfor.count, count)
						end
					end
				else
					count = 1
				end
				if fromtube.remove_items then
					-- it could be the entire stack...
					item = fromtube.remove_items(frompos, fromnode, stack, dir, count, frominvname, spos)
				else
					item = stack:take_item(count)
					frominv:set_stack(frominvname, spos, stack)
					if fromdef.on_metadata_inventory_take then
						fromdef.on_metadata_inventory_take(frompos, frominvname, spos, item, fakePlayer)
					end
				end
				local pos = vector.add(frompos, vector.multiply(dir, 1.4))
				local start_pos = vector.add(frompos, dir)
				local item1 = pipeworks.tube_inject_item(pos, start_pos, dir, item, fakePlayer:get_player_name())
				return true -- only fire one item, please
			end
		end
		return false
	end

	for _, frominvname in ipairs(type(fromtube.input_inventory) == "table" and fromtube.input_inventory or {fromtube.input_inventory}) do
		local done = false
		for _, filterfor in ipairs(filters) do
			if grabAndFire(frominvname, filterfor) then
				done = true
				break
			end
		end
		if done then break end
	end
	if fromtube.after_filter then fromtube.after_filter(frompos) end
end

for _, data in ipairs({
	{
		name = "filter",
		wise_desc = S("Itemwise"),
		stackwise = false,
	},
	{
		name = "mese_filter",
		wise_desc = S("Stackwise"),
		stackwise = true,
	},
	{ -- register even if no digilines
		name = "digiline_filter",
		wise_desc = S("Digiline"),
		stackwise = true,
		digiline = true,
	},
}) do
	local node = {
		description = S("@1 Filter-Injector", data.wise_desc),
		tiles = {
			"pipeworks_"..data.name.."_top.png",
			"pipeworks_"..data.name.."_top.png",
			"pipeworks_"..data.name.."_output.png",
			"pipeworks_"..data.name.."_input.png",
			"pipeworks_"..data.name.."_side.png",
			"pipeworks_"..data.name.."_top.png",
		},
		paramtype2 = "facedir",
		groups = {snappy = 2, choppy = 2, oddly_breakable_by_hand = 2, mesecon = 2},
		legacy_facedir_simple = true,
		sounds = default.node_sound_wood_defaults(),
		on_construct = function(pos)
			local meta = minetest.get_meta(pos)
			set_filter_formspec(data, meta)
			set_filter_infotext(data, meta)
			local inv = meta:get_inventory()
			inv:set_size("main", 8*2)
		end,
		after_place_node = function (pos, placer)
			minetest.get_meta(pos):set_string("owner", placer:get_player_name())
			pipeworks.after_place(pos)
		end,
		after_dig_node = pipeworks.after_dig,
		on_rotate = pipeworks.on_rotate,
		allow_metadata_inventory_put = function(pos, listname, index, stack, player)
			if not pipeworks.may_configure(pos, player) then
				return 0
			end
			local inv = minetest.get_meta(pos):get_inventory()
			inv:set_stack("main", index, stack)
			return 0
		end,
		allow_metadata_inventory_take = function(pos, listname, index, stack, player)
			if not pipeworks.may_configure(pos, player) then
				return 0
			end
			local inv = minetest.get_meta(pos):get_inventory()
			local fake_stack = inv:get_stack("main", index)
			fake_stack:take_item(stack:get_count())
			inv:set_stack("main", index, fake_stack)
			return 0
		end,
		allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
			if not pipeworks.may_configure(pos, player) then return 0 end
			return count
		end,
		can_dig = function(pos, player)
			local meta = minetest.get_meta(pos)
			local inv = meta:get_inventory()
			return inv:is_empty("main")
		end,
		tube = {connect_sides = {right = 1}},
	}

	if data.digiline then
		node.groups.mesecon = nil
		if not minetest.get_modpath("digilines") then
			node.groups.not_in_creative_inventory = 1
		end

		node.on_receive_fields = function(pos, formname, fields, sender)
			if not pipeworks.may_configure(pos, sender) then return end
			fs_helpers.on_receive_fields(pos, fields)

			if fields.channel then
				minetest.get_meta(pos):set_string("channel", fields.channel)
			end

			local meta = minetest.get_meta(pos)
			--meta:set_int("slotseq_index", 1)
			set_filter_formspec(data, meta)
			set_filter_infotext(data, meta)
		end
		node.digiline = {
			effector = {
				action = function(pos, node, channel, msg)
					local meta = minetest.get_meta(pos)
					local setchan = meta:get_string("channel")
					if setchan ~= channel then return end

					punch_filter(data, pos, node, msg)
				end,
			},
		}
	else
		node.on_receive_fields = function(pos, formname, fields, sender)
			if not pipeworks.may_configure(pos, sender) then return end
			fs_helpers.on_receive_fields(pos, fields)
			local meta = minetest.get_meta(pos)
			meta:set_int("slotseq_index", 1)
			set_filter_formspec(data, meta)
			set_filter_infotext(data, meta)
		end
		node.mesecons = {
			effector = {
				action_on = function(pos, node)
					punch_filter(data, pos, node)
				end,
			},
		}
		node.on_punch = function (pos, node, puncher)
			punch_filter(data, pos, node)
		end
	end

	minetest.register_node("pipeworks:"..data.name, node)
end

minetest.register_craft( {
	output = "pipeworks:filter 2",
	recipe = {
	        { "default:steel_ingot", "default:steel_ingot", "basic_materials:plastic_sheet" },
	        { "group:stick", "default:mese_crystal", "basic_materials:plastic_sheet" },
	        { "default:steel_ingot", "default:steel_ingot", "basic_materials:plastic_sheet" }
	},
})

minetest.register_craft( {
	output = "pipeworks:mese_filter 2",
	recipe = {
	        { "default:steel_ingot", "default:steel_ingot", "basic_materials:plastic_sheet" },
	        { "group:stick", "default:mese", "basic_materials:plastic_sheet" },
	        { "default:steel_ingot", "default:steel_ingot", "basic_materials:plastic_sheet" }
	},
})

if minetest.get_modpath("digilines") then
	minetest.register_craft( {
		output = "pipeworks:digiline_filter 2",
		recipe = {
			{ "default:steel_ingot", "default:steel_ingot", "basic_materials:plastic_sheet" },
			{ "group:stick", "digilines:wire_std_00000000", "basic_materials:plastic_sheet" },
			{ "default:steel_ingot", "default:steel_ingot", "basic_materials:plastic_sheet" }
		},
	})
end

--[[
In the past the filter-injectors had real items in their inventories. This code
puts them to the input to the filter-injector if possible. Else the items are
dropped.
]]
local function put_to_inputinv(pos, node, filtmeta, list)
	local dir = pipeworks.facedir_to_right_dir(node.param2)
	local frompos = vector.subtract(pos, dir)
	local fromnode = minetest.get_node(frompos)
	local fromdef = minetest.registered_nodes[fromnode.name]
	if not fromdef or not fromdef.tube then
		return
	end
	local fromtube = fromdef.tube
	local frominv
	if fromtube.return_input_invref then
		local owner = filtmeta:get_string("owner")
		frominv = fromtube.return_input_invref(frompos, fromnode, dir, owner)
		if not frominv then
			return
		end
	else
		frominv = minetest.get_meta(frompos):get_inventory()
	end
	local listname = type(fromtube.input_inventory) == "table" and
			fromtube.input_inventory[1] or fromtube.input_inventory
	if not listname then
		return
	end
	for i = 1, #list do
		local item = list[i]
		if not item:is_empty() then
			local leftover = frominv:add_item(listname, item)
			if not leftover:is_empty() then
				minetest.add_item(pos, leftover)
			end
		end
	end
	return true
end
minetest.register_lbm({
	label = "Give back items of old filters that had real inventories",
	name = "pipeworks:give_back_old_filter_items",
	nodenames = {"pipeworks:filter", "pipeworks:mese_filter"},
	run_at_every_load = false,
	action = function(pos, node)
		local meta = minetest.get_meta(pos)
		local list = meta:get_inventory():get_list("main")
		if put_to_inputinv(pos, node, meta, list) then
			return
		end
		pos.y = pos.y + 1
		for i = 1, #list do
			local item = list[i]
			if not item:is_empty() then
				minetest.add_item(pos, item)
			end
		end
	end,
})


code:
-- This file provides the actual flow and pathfinding logic that makes water
-- move through the pipes.
--
-- Contributed by mauvebic, 2013-01-03, rewritten a bit by Vanessa Ezekowitz
--

local finitewater = minetest.settings:get_bool("liquid_finite")

pipeworks.check_for_liquids = function(pos)
	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},	}
	for i =1,6 do
		local name = minetest.get_node(coords[i]).name
		if name and string.find(name,"water") then
			if finitewater then minetest.remove_node(coords[i]) end
			return true
		end
	end
	return false
end

pipeworks.check_for_inflows = function(pos,node)
	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},
	}
	local newnode = false
	local source = false
	for i = 1, 6 do
		if newnode then break end
		local testnode = minetest.get_node(coords[i])
		local name = testnode.name
		if name and (name == "pipeworks:pump_on" and pipeworks.check_for_liquids(coords[i])) or string.find(name,"_loaded") then
			if string.find(name,"_loaded") then
				source = minetest.get_meta(coords[i]):get_string("source")
				if source == minetest.pos_to_string(pos) then break end
			end
			if string.find(name, "valve") or string.find(name, "sensor")
			  or string.find(name, "straight_pipe") or string.find(name, "panel") then

				if ((i == 3 or i == 4) and minetest.facedir_to_dir(testnode.param2).x ~= 0)
				  or ((i == 5 or i == 6) and minetest.facedir_to_dir(testnode.param2).z ~= 0)
				  or ((i == 1 or i == 2) and minetest.facedir_to_dir(testnode.param2).y ~= 0) then

					newnode = string.gsub(node.name,"empty","loaded")
					source = {x=coords[i].x,y=coords[i].y,z=coords[i].z}
				end
			else
				newnode = string.gsub(node.name,"empty","loaded")
				source = {x=coords[i].x,y=coords[i].y,z=coords[i].z}
			end
		end
	end
	if newnode then
		minetest.add_node(pos,{name=newnode, param2 = node.param2})
		minetest.get_meta(pos):set_string("source",minetest.pos_to_string(source))
	end
end

pipeworks.check_sources = function(pos,node)
	local sourcepos = minetest.string_to_pos(minetest.get_meta(pos):get_string("source"))
	if not sourcepos then return end
	local source = minetest.get_node(sourcepos).name
	local newnode = false
	if source and not ((source == "pipeworks:pump_on" and pipeworks.check_for_liquids(sourcepos)) or string.find(source,"_loaded") or source == "ignore" ) then
		newnode = string.gsub(node.name,"loaded","empty")
	end

	if newnode then
		minetest.add_node(pos,{name=newnode, param2 = node.param2})
		minetest.get_meta(pos):set_string("source","")
	end
end

pipeworks.spigot_check = function(pos, node)
	local belowname = minetest.get_node({x=pos.x,y=pos.y-1,z=pos.z}).name
	if belowname and (belowname == "air" or belowname == "default:water_flowing" or belowname == "default:water_source") then
		local spigotname = minetest.get_node(pos).name
		local fdir=node.param2 % 4
		local check = {
			{x=pos.x,y=pos.y,z=pos.z+1},
			{x=pos.x+1,y=pos.y,z=pos.z},
			{x=pos.x,y=pos.y,z=pos.z-1},
			{x=pos.x-1,y=pos.y,z=pos.z}
		}
		local near_node = minetest.get_node(check[fdir+1])
		if near_node and string.find(near_node.name, "_loaded") then
			if spigotname and spigotname == "pipeworks:spigot" then
				minetest.add_node(pos,{name = "pipeworks:spigot_pouring", param2 = fdir})
				if finitewater or belowname ~= "default:water_source" then
					minetest.add_node({x=pos.x,y=pos.y-1,z=pos.z},{name = "default:water_source"})
				end
			end
		else
			if spigotname == "pipeworks:spigot_pouring" then
				minetest.add_node({x=pos.x,y=pos.y,z=pos.z},{name = "pipeworks:spigot", param2 = fdir})
				if belowname == "default:water_source" and not finitewater then
					minetest.remove_node({x=pos.x,y=pos.y-1,z=pos.z})
				end
			end
		end
	end
end

pipeworks.fountainhead_check = function(pos, node)
	local abovename = minetest.get_node({x=pos.x,y=pos.y+1,z=pos.z}).name
	if abovename and (abovename == "air" or abovename == "default:water_flowing" or abovename == "default:water_source") then
		local fountainhead_name = minetest.get_node(pos).name
		local near_node = minetest.get_node({x=pos.x,y=pos.y-1,z=pos.z})
		if near_node and string.find(near_node.name, "_loaded") then
			if fountainhead_name and fountainhead_name == "pipeworks:fountainhead" then
				minetest.add_node(pos,{name = "pipeworks:fountainhead_pouring"})
				if finitewater or abovename ~= "default:water_source" then
					minetest.add_node({x=pos.x,y=pos.y+1,z=pos.z},{name = "default:water_source"})
				end
			end
		else
			if fountainhead_name == "pipeworks:fountainhead_pouring" then
				minetest.add_node({x=pos.x,y=pos.y,z=pos.z},{name = "pipeworks:fountainhead"})
				if abovename == "default:water_source" and not finitewater then
					minetest.remove_node({x=pos.x,y=pos.y+1,z=pos.z})
				end
			end
		end
	end
end


code:
-- Pipeworks mod by Vanessa Ezekowitz - 2013-07-13
--
-- This mod supplies various steel pipes and plastic pneumatic tubes
-- and devices that they can connect to.
--

pipeworks = {}

local DEBUG = false

pipeworks.worldpath = minetest.get_worldpath()
pipeworks.modpath = minetest.get_modpath("pipeworks")
local S = minetest.get_translator("pipeworks")

dofile(pipeworks.modpath.."/default_settings.lua")
-- Read the external config file if it exists.
local worldsettingspath = pipeworks.worldpath.."/pipeworks_settings.txt"
local worldsettingsfile = io.open(worldsettingspath, "r")
if worldsettingsfile then
	worldsettingsfile:close()
	dofile(worldsettingspath)
end
if pipeworks.toggles.pipe_mode == "pressure" then
	minetest.log("warning", "pipeworks pressure logic mode comes with caveats and differences in behaviour, you have been warned!")
end

-- Random variables

pipeworks.expect_infinite_stacks = true
if minetest.get_modpath("unified_inventory") or not minetest.settings:get_bool("creative_mode") then
	pipeworks.expect_infinite_stacks = false
end

pipeworks.meseadjlist={{x=0,y=0,z=1},{x=0,y=0,z=-1},{x=0,y=1,z=0},{x=0,y=-1,z=0},{x=1,y=0,z=0},{x=-1,y=0,z=0}}

pipeworks.rules_all = {{x=0, y=0, z=1},{x=0, y=0, z=-1},{x=1, y=0, z=0},{x=-1, y=0, z=0},
		{x=0, y=1, z=1},{x=0, y=1, z=-1},{x=1, y=1, z=0},{x=-1, y=1, z=0},
		{x=0, y=-1, z=1},{x=0, y=-1, z=-1},{x=1, y=-1, z=0},{x=-1, y=-1, z=0},
		{x=0, y=1, z=0}, {x=0, y=-1, z=0}}

pipeworks.mesecons_rules={{x=0,y=0,z=1},{x=0,y=0,z=-1},{x=1,y=0,z=0},{x=-1,y=0,z=0},{x=0,y=1,z=0},{x=0,y=-1,z=0}}
pipeworks.digilines_rules={{x=0,y=0,z=1},{x=0,y=0,z=-1},{x=1,y=0,z=0},{x=-1,y=0,z=0},{x=0,y=1,z=0},{x=0,y=-1,z=0}}

pipeworks.liquid_texture = "default_water.png"

pipeworks.button_off   = {text="", texture="pipeworks_button_off.png", addopts="false;false;pipeworks_button_interm.png"}
pipeworks.button_on    = {text="", texture="pipeworks_button_on.png",  addopts="false;false;pipeworks_button_interm.png"}
pipeworks.button_base  = "image_button[0,4.3;1,0.6"
pipeworks.button_label = "label[0.9,4.31;"..S("Allow splitting incoming stacks from tubes").."]"

-- Helper functions

function pipeworks.fix_image_names(table, replacement)
	local outtable={}
	for i in ipairs(table) do
		outtable[i]=string.gsub(table[i], "_XXXXX", replacement)
	end

	return outtable
end

function pipeworks.add_node_box(t, b)
	if not t or not b then return end
	for i in ipairs(b)
		do table.insert(t, b[i])
	end
end

function pipeworks.may_configure(pos, player)
	local name = player:get_player_name()
	local meta = minetest.get_meta(pos)
	local owner = meta:get_string("owner")

	if owner ~= "" then -- wielders and filters
		return owner == name
	end
	return not minetest.is_protected(pos, name)
end

function pipeworks.replace_name(tbl,tr,name)
	local ntbl={}
	for key,i in pairs(tbl) do
		if type(i)=="string" then
			ntbl[key]=string.gsub(i,tr,name)
		elseif type(i)=="table" then
			ntbl[key]=pipeworks.replace_name(i,tr,name)
		else
			ntbl[key]=i
		end
	end
	return ntbl
end

pipeworks.logger = function(msg)
	print("[pipeworks] "..msg)
end

-------------------------------------------
-- Load the various other parts of the mod

-- early auto-detection for finite water mode if not explicitly disabled
if pipeworks.toggles.finite_water == nil then
	dofile(pipeworks.modpath.."/autodetect-finite-water.lua")
end

if minetest.get_modpath("signs_lib") then
	dofile(pipeworks.modpath.."/signs_compat.lua")
end

dofile(pipeworks.modpath.."/common.lua")
dofile(pipeworks.modpath.."/models.lua")
dofile(pipeworks.modpath.."/autoplace_pipes.lua")
dofile(pipeworks.modpath.."/autoplace_tubes.lua")
dofile(pipeworks.modpath.."/luaentity.lua")
dofile(pipeworks.modpath.."/item_transport.lua")
dofile(pipeworks.modpath.."/flowing_logic.lua")
dofile(pipeworks.modpath.."/crafts.lua")
dofile(pipeworks.modpath.."/tube_registration.lua")
dofile(pipeworks.modpath.."/routing_tubes.lua")
dofile(pipeworks.modpath.."/sorting_tubes.lua")
dofile(pipeworks.modpath.."/vacuum_tubes.lua")
dofile(pipeworks.modpath.."/signal_tubes.lua")
dofile(pipeworks.modpath.."/decorative_tubes.lua")
dofile(pipeworks.modpath.."/filter-injector.lua")
dofile(pipeworks.modpath.."/trashcan.lua")
dofile(pipeworks.modpath.."/wielder.lua")

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,
-- unless the toggle flag is specifically enabled in the per-world settings flag.
dofile(pipeworks.modpath..logicdir.."flowable_node_registry.lua")
dofile(pipeworks.modpath..logicdir.."abms.lua")
dofile(pipeworks.modpath..logicdir.."abm_register.lua")
dofile(pipeworks.modpath..logicdir.."flowable_node_registry_install.lua")

if pipeworks.enable_pipes then dofile(pipeworks.modpath.."/pipes.lua") end
if pipeworks.enable_teleport_tube then dofile(pipeworks.modpath.."/teleport_tube.lua") end
if pipeworks.enable_pipe_devices then dofile(pipeworks.modpath.."/devices.lua") end
if pipeworks.enable_redefines then
	dofile(pipeworks.modpath.."/compat-chests.lua")
	dofile(pipeworks.modpath.."/compat-furnaces.lua")
end
if pipeworks.enable_autocrafter then dofile(pipeworks.modpath.."/autocrafter.lua") end
if pipeworks.enable_lua_tube and
		(minetest.get_modpath("mesecons") or minetest.get_modpath("digilines")) then
	dofile(pipeworks.modpath.."/lua_tube.lua")
end

minetest.register_alias("pipeworks:pipe", "pipeworks:pipe_110000_empty")

print("Pipeworks loaded!")