code:
local S = minetest.get_translator("pipeworks")
-- the default tube and default textures
pipeworks.register_tube("pipeworks:tube", S("Pneumatic tube segment"))
minetest.register_craft( {
	output = "pipeworks:tube_1 6",
	recipe = {
	        { "basic_materials:plastic_sheet", "basic_materials:plastic_sheet", "basic_materials:plastic_sheet" },
	        { "", "", "" },
	        { "basic_materials:plastic_sheet", "basic_materials:plastic_sheet", "basic_materials:plastic_sheet" }
	},
})

local nodecolor = 0xffff3030

pipeworks.register_tube("pipeworks:broken_tube", {
	description = S("Broken Tube"),
	plain = { { name = "pipeworks_broken_tube_plain.png", backface_culling = false, color = nodecolor } },
	noctr = { { name = "pipeworks_broken_tube_plain.png", backface_culling = false, color = nodecolor } },
	ends  = { { name = "pipeworks_broken_tube_end.png",   color = nodecolor } },
	short =   { name = "pipeworks_broken_tube_short.png", color = nodecolor },
	node_def = {
		drop = "pipeworks:tube_1",
		groups = {not_in_creative_inventory = 1, tubedevice_receiver = 1},
		tube = {
			insert_object = function(pos, node, stack, direction)
				minetest.item_drop(stack, nil, pos)
				return ItemStack("")
			end,
			can_insert = function(pos,node,stack,direction)
				return true
			end,
			priority = 50,
		},
		on_punch = function(pos, node, puncher, pointed_thing)
			local itemstack = puncher:get_wielded_item()
			local wieldname = itemstack:get_name()
			local playername = puncher:get_player_name()
			local log_msg = playername.." struck a broken tube at "..minetest.pos_to_string(pos).."\n"
			if   wieldname == "anvil:hammer"
			  or wieldname == "cottages:hammer"
			  or wieldname == "glooptest:hammer_steel"
			  or wieldname == "glooptest:hammer_bronze"
			  or wieldname == "glooptest:hammer_diamond"
			  or wieldname == "glooptest:hammer_mese"
			  or wieldname == "glooptest:hammer_alatro"
			  or wieldname == "glooptest:hammer_arol" then
				local meta = minetest.get_meta(pos)
				local was_node = minetest.deserialize(meta:get_string("the_tube_was"))
				if was_node and was_node ~= "" then
					pipeworks.logger(log_msg.."            with "..wieldname.." to repair it.")
					minetest.swap_node(pos, { name = was_node.name, param2 = was_node.param2 })
					pipeworks.scan_for_tube_objects(pos)
					itemstack:add_wear(1000)
					puncher:set_wielded_item(itemstack)
					return itemstack
				else
					pipeworks.logger(log_msg.."            but it can't be repaired.")
				end
			else
				pipeworks.logger(log_msg.."            with "..wieldname.." but that tool is too weak.")
			end
		end
	}
})

-- the high priority tube is a low-cpu replacement for sorting tubes in situations
-- where players would use them for simple routing (turning off paths)
-- without doing actual sorting, like at outputs of tubedevices that might both accept and eject items
if pipeworks.enable_priority_tube then
	local color = "#ff3030:128"
	pipeworks.register_tube("pipeworks:priority_tube", {
			description = S("High Priority Tube Segment"),
			inventory_image = "pipeworks_tube_inv.png^[colorize:" .. color,
			plain = { { name = "pipeworks_tube_plain.png", color = nodecolor } },
			noctr = { { name = "pipeworks_tube_noctr.png", color = nodecolor } },
			ends  = { { name = "pipeworks_tube_end.png",   color = nodecolor } },
			short =   { name = "pipeworks_tube_short.png", color = nodecolor },
			node_def = {
				tube = { priority = 150 } -- higher than tubedevices (100)
			},
	})
	minetest.register_craft( {
		output = "pipeworks:priority_tube_1 6",
		recipe = {
			{ "basic_materials:plastic_sheet", "basic_materials:plastic_sheet", "basic_materials:plastic_sheet" },
			{ "default:gold_ingot", "", "default:gold_ingot" },
			{ "basic_materials:plastic_sheet", "basic_materials:plastic_sheet", "basic_materials:plastic_sheet" }
		},
	})
end

if pipeworks.enable_accelerator_tube then
	pipeworks.register_tube("pipeworks:accelerator_tube", {
			description = S("Accelerating Pneumatic Tube Segment"),
			inventory_image = "pipeworks_accelerator_tube_inv.png",
			plain = { "pipeworks_accelerator_tube_plain.png" },
			noctr = { "pipeworks_accelerator_tube_noctr.png" },
			ends = { "pipeworks_accelerator_tube_end.png" },
			short = "pipeworks_accelerator_tube_short.png",
			node_def = {
				tube = {can_go = function(pos, node, velocity, stack)
						 velocity.speed = velocity.speed+1
						 return pipeworks.notvel(pipeworks.meseadjlist, velocity)
					end}
			},
	})
	minetest.register_craft( {
		output = "pipeworks:accelerator_tube_1 2",
		recipe = {
			{ "basic_materials:plastic_sheet", "basic_materials:plastic_sheet", "basic_materials:plastic_sheet" },
			{ "default:mese_crystal_fragment", "default:steel_ingot", "default:mese_crystal_fragment" },
			{ "basic_materials:plastic_sheet", "basic_materials:plastic_sheet", "basic_materials:plastic_sheet" }
		},
	})
end

if pipeworks.enable_crossing_tube then
	pipeworks.register_tube("pipeworks:crossing_tube", {
			description = S("Crossing Pneumatic Tube Segment"),
			inventory_image = "pipeworks_crossing_tube_inv.png",
			plain = { "pipeworks_crossing_tube_plain.png" },
			noctr = { "pipeworks_crossing_tube_noctr.png" },
			ends = { "pipeworks_crossing_tube_end.png" },
			short = "pipeworks_crossing_tube_short.png",
			node_def = {
				tube = {can_go = function(pos, node, velocity, stack) return {velocity} end }
			},
	})
	minetest.register_craft( {
		output = "pipeworks:crossing_tube_1 5",
		recipe = {
			{ "", "pipeworks:tube_1", "" },
			{ "pipeworks:tube_1", "pipeworks:tube_1", "pipeworks:tube_1" },
			{ "", "pipeworks:tube_1", "" }
		},
	})
end

if pipeworks.enable_one_way_tube then
	minetest.register_node("pipeworks:one_way_tube", {
		description = S("One way tube"),
		tiles = {"pipeworks_one_way_tube_top.png", "pipeworks_one_way_tube_top.png", "pipeworks_one_way_tube_output.png",
			"pipeworks_one_way_tube_input.png", "pipeworks_one_way_tube_side.png", "pipeworks_one_way_tube_top.png"},
		paramtype2 = "facedir",
		drawtype = "nodebox",
		paramtype = "light",
		node_box = {type = "fixed",
			fixed = {{-1/2, -9/64, -9/64, 1/2, 9/64, 9/64}}},
		groups = {snappy = 2, choppy = 2, oddly_breakable_by_hand = 2, tubedevice = 1},
		sounds = default.node_sound_wood_defaults(),
		tube = {
			connect_sides = {left = 1, right = 1},
			can_go = function(pos, node, velocity, stack)
				return {velocity}
			end,
			can_insert = function(pos, node, stack, direction)
				local dir = pipeworks.facedir_to_right_dir(node.param2)
				return vector.equals(dir, direction)
			end,
			priority = 75 -- Higher than normal tubes, but lower than receivers
		},
		after_place_node = pipeworks.after_place,
		after_dig_node = pipeworks.after_dig,
		on_rotate = pipeworks.on_rotate,
		check_for_pole = pipeworks.check_for_vert_tube,
		check_for_horiz_pole = pipeworks.check_for_horiz_tube
	})
	minetest.register_craft({
		output = "pipeworks:one_way_tube 2",
		recipe = {
			{ "basic_materials:plastic_sheet", "basic_materials:plastic_sheet", "basic_materials:plastic_sheet" },
			{ "group:stick", "default:mese_crystal", "basic_materials:plastic_sheet" },
			{ "basic_materials:plastic_sheet", "basic_materials:plastic_sheet", "basic_materials:plastic_sheet" }
		},
	})
end


code:
#Enable pipes.
pipeworks_enable_pipes (Enable Pipes) bool true

#Enable autocrafter.
pipeworks_enable_autocrafter (Enable Autocrafter) bool true

#Enable deployer.
pipeworks_enable_deployer (Enable Deployer) bool true

#Enable dispenser.
pipeworks_enable_dispenser (Enable Dispenser) bool true

#Enable node breaker.
pipeworks_enable_node_breaker (Enable Node Breaker) bool true

#Enable teleport tube.
pipeworks_enable_teleport_tube (Enable Teleport Tube) bool true

#Enable pipe devices.
pipeworks_enable_pipe_devices (Enable Pipe Devices) bool true

#Enable redefines.
pipeworks_enable_redefines (Enable Node Redefines) bool true

#Enable sorting tube.
pipeworks_enable_mese_tube (Enable Sorting Tube) bool true

#Enable detector tube.
pipeworks_enable_detector_tube (Enable Detector Tube) bool true

#Enable digiline detector tube.
pipeworks_enable_digiline_detector_tube (Enable Digiline Detector Tube) bool true

#Enable mesecon signal conducting tube.
pipeworks_enable_conductor_tube (Enable Conductor Tube) bool true

#Enable digiline signal conducting tube.
pipeworks_enable_digiline_conductor_tube (Enable Digiline Conductor Tube) bool true

#Enable accelerator tube.
pipeworks_enable_accelerator_tube (Enable Accelerator Tube) bool true

#Enable crossing tube.
#It sends all incoming items to the other side, or if there is no other tube, it sends them back.
pipeworks_enable_crossing_tube (Enable Crossing Tube) bool true

#Enable vacuum tube.
#It picks up all items that lay around next to it.
pipeworks_enable_sand_tube (Enable Vacuum Tube) bool true

#Enable mese vacuum tube.
#It's like the normal vacuum tube with the
#differance that you can set the radius up to 8 nodes.
pipeworks_enable_mese_sand_tube (Enable Mese Vacuum Tube) bool true

#Enable one way tube.
#It sends items only in one direction.
#Use it to drop items out of tubes.
pipeworks_enable_one_way_tube (Enable One Way Tube) bool true

#Enable high priority tube.
#It has a very high priority and so, on crossings, the items will
#always go to it if there are multible ways.
pipeworks_enable_priority_tube (Enable High Priority Tube) bool true

#Enable lua controlled tube.
#It is comparable with mesecons luacontroller.
pipeworks_enable_lua_tube (Enable Lua controlled Tube) bool true

#Enable cyclic mode.
pipeworks_enable_cyclic_mode (Enable Cyclic Mode) bool true

#Drop on routing fail.
pipeworks_drop_on_routing_fail (Drop On Routing Fail) bool false

#Delete item on clearobject.
pipeworks_delete_item_on_clearobject (Delete Item On Clearobject) bool true

repo_name: Desour/PlayMinetestServer

license: mit


code:
local S = minetest.get_translator("pipeworks")
if pipeworks.enable_detector_tube then
	local detector_tube_step = 5 * tonumber(minetest.settings:get("dedicated_server_step"))
	pipeworks.register_tube("pipeworks:detector_tube_on", {
			description = S("Detecting Pneumatic Tube Segment on"),
			inventory_image = "pipeworks_detector_tube_inv.png",
			plain = { "pipeworks_detector_tube_plain.png" },
			node_def = {
				tube = {can_go = function(pos, node, velocity, stack)
						 local meta = minetest.get_meta(pos)
						 local name = minetest.get_node(pos).name
						 local nitems = meta:get_int("nitems")+1
						 meta:set_int("nitems", nitems)
						 local saved_pos = vector.new(pos)
						 minetest.after(detector_tube_step, minetest.registered_nodes[name].item_exit, saved_pos)
						 return pipeworks.notvel(pipeworks.meseadjlist,velocity)
					end},
				groups = {mesecon = 2, not_in_creative_inventory = 1},
				drop = "pipeworks:detector_tube_off_1",
				mesecons = {receptor = {state = "on", rules = pipeworks.mesecons_rules}},
				item_exit = function(pos)
					local meta = minetest.get_meta(pos)
					local nitems = meta:get_int("nitems")-1
					local node = minetest.get_node(pos)
					local name = node.name
					local fdir = node.param2
					if nitems == 0 then
						 minetest.set_node(pos, {name = string.gsub(name, "on", "off"), param2 = fdir})
						 mesecon.receptor_off(pos, pipeworks.mesecons_rules)
					else
						 meta:set_int("nitems", nitems)
					end
				end,
				on_construct = function(pos)
					 local meta = minetest.get_meta(pos)
					 meta:set_int("nitems", 1)
					 local name = minetest.get_node(pos).name
					 local saved_pos = vector.new(pos)
					 minetest.after(detector_tube_step, minetest.registered_nodes[name].item_exit, saved_pos)
				end,
			},
	})
	pipeworks.register_tube("pipeworks:detector_tube_off", {
			description = S("Detecting Pneumatic Tube Segment"),
			inventory_image = "pipeworks_detector_tube_inv.png",
			plain = { "pipeworks_detector_tube_plain.png" },
			node_def = {
				tube = {can_go = function(pos, node, velocity, stack)
						local node = minetest.get_node(pos)
						local name = node.name
						local fdir = node.param2
						minetest.set_node(pos,{name = string.gsub(name, "off", "on"), param2 = fdir})
						mesecon.receptor_on(pos, pipeworks.mesecons_rules)
						return pipeworks.notvel(pipeworks.meseadjlist, velocity)
					end},
				groups = {mesecon = 2},
				mesecons = {receptor = {state = "off", rules = pipeworks.mesecons_rules }},
			},
	})

	minetest.register_craft( {
		output = "pipeworks:detector_tube_off_1 2",
		recipe = {
			{ "basic_materials:plastic_sheet", "basic_materials:plastic_sheet", "basic_materials:plastic_sheet" },
			{ "mesecons:mesecon", "mesecons_materials:silicon", "mesecons:mesecon" },
			{ "basic_materials:plastic_sheet", "basic_materials:plastic_sheet", "basic_materials:plastic_sheet" }
		},
	})
end

local digiline_enabled = minetest.get_modpath("digilines") ~= nil
if digiline_enabled and pipeworks.enable_digiline_detector_tube then
	pipeworks.register_tube("pipeworks:digiline_detector_tube", {
			description = S("Digiline Detecting Pneumatic Tube Segment"),
			inventory_image = "pipeworks_digiline_detector_tube_inv.png",
			plain = { "pipeworks_digiline_detector_tube_plain.png" },
			node_def = {
				tube = {can_go = function(pos, node, velocity, stack)
						local meta = minetest.get_meta(pos)

						local setchan = meta:get_string("channel")

						digiline:receptor_send(pos, digiline.rules.default, setchan, stack:to_table())

						return pipeworks.notvel(pipeworks.meseadjlist, velocity)
					end},
				on_construct = function(pos)
					local meta = minetest.get_meta(pos)
					meta:set_string("formspec",
						"size[8.6,2.2]"..
						"field[0.6,0.6;8,1;channel;"..S("Channel")..";${channel}]"..
						"image[0.3,1.3;1,1;pipeworks_digiline_detector_tube_inv.png]"..
						"label[1.6,1.2;"..S("Digiline Detecting Tube").."]"
					)
				end,
				on_receive_fields = function(pos, formname, fields, sender)
					if fields.channel then
						minetest.get_meta(pos):set_string("channel", fields.channel)
					end
				end,
				groups = {},
				digiline = {
					receptor = {},
					effector = {
						action = function(pos,node,channel,msg) end
					},
					wire = {
						rules = pipeworks.digilines_rules
					},
				},
			},
	})

	minetest.register_craft( {
		output = "pipeworks:digiline_detector_tube_1 2",
		recipe = {
			{ "basic_materials:plastic_sheet", "basic_materials:plastic_sheet", "basic_materials:plastic_sheet" },
			{ "digilines:wire_std_00000000", "mesecons_materials:silicon", "digilines:wire_std_00000000" },
			{ "basic_materials:plastic_sheet", "basic_materials:plastic_sheet", "basic_materials:plastic_sheet" }
		},
	})
end

if pipeworks.enable_conductor_tube then
	pipeworks.register_tube("pipeworks:conductor_tube_off", {
			description = S("Conducting Pneumatic Tube Segment"),
			inventory_image = "pipeworks_conductor_tube_inv.png",
			short = "pipeworks_conductor_tube_short.png",
			plain = { "pipeworks_conductor_tube_plain.png" },
			noctr = { "pipeworks_conductor_tube_noctr.png" },
			ends = { "pipeworks_conductor_tube_end.png" },
			node_def = {
				groups = {mesecon = 2},
				mesecons = {conductor = {state = "off",
							  rules = pipeworks.mesecons_rules,
							  onstate = "pipeworks:conductor_tube_on_#id"}}
			},
	})
	pipeworks.register_tube("pipeworks:conductor_tube_on", {
			description = S("Conducting Pneumatic Tube Segment on"),
			inventory_image = "pipeworks_conductor_tube_inv.png",
			short = "pipeworks_conductor_tube_short.png",
			plain = { "pipeworks_conductor_tube_on_plain.png" },
			noctr = { "pipeworks_conductor_tube_on_noctr.png" },
			ends = { "pipeworks_conductor_tube_on_end.png" },
			node_def = {
				groups = {mesecon = 2, not_in_creative_inventory = 1},
				drop = "pipeworks:conductor_tube_off_1",
				mesecons = {conductor = {state = "on",
							  rules = pipeworks.mesecons_rules,
							  offstate = "pipeworks:conductor_tube_off_#id"}}
			},
	})

	minetest.register_craft({
		type = "shapeless",
		output = "pipeworks:conductor_tube_off_1",
		recipe = {"pipeworks:tube_1", "mesecons:mesecon"}
	})
end

if digiline_enabled and pipeworks.enable_digiline_conductor_tube then
	pipeworks.register_tube("pipeworks:digiline_conductor_tube", {
		description = S("Digiline Conducting Pneumatic Tube Segment"),
		inventory_image = "pipeworks_tube_inv.png^pipeworks_digiline_conductor_tube_inv.png",
		short = "pipeworks_tube_short.png^pipeworks_digiline_conductor_tube_short.png",
		plain = {"pipeworks_tube_plain.png^pipeworks_digiline_conductor_tube_plain.png"},
		noctr = {"pipeworks_tube_noctr.png^pipeworks_digiline_conductor_tube_noctr.png"},
		ends = {"pipeworks_tube_end.png^pipeworks_digiline_conductor_tube_end.png"},
		node_def = {digiline = {wire = {rules = pipeworks.digilines_rules}}},
	})
	minetest.register_craft({
		type = "shapeless",
		output = "pipeworks:digiline_conductor_tube_1",
		recipe = {"pipeworks:tube_1", "digilines:wire_std_00000000"}
	})
end

if digiline_enabled and pipeworks.enable_digiline_conductor_tube and
		pipeworks.enable_conductor_tube then
	pipeworks.register_tube("pipeworks:mesecon_and_digiline_conductor_tube_off", {
		description = S("Mesecon and Digiline Conducting Pneumatic Tube Segment"),
		inventory_image = "pipeworks_conductor_tube_inv.png^pipeworks_digiline_conductor_tube_inv.png",
		short = "pipeworks_conductor_tube_short.png^pipeworks_digiline_conductor_tube_short.png",
		plain = {"pipeworks_conductor_tube_plain.png^pipeworks_digiline_conductor_tube_plain.png"},
		noctr = {"pipeworks_conductor_tube_noctr.png^pipeworks_digiline_conductor_tube_noctr.png"},
		ends = {"pipeworks_conductor_tube_end.png^pipeworks_digiline_conductor_tube_end.png"},
		node_def = {
			digiline = {wire = {rules = pipeworks.digilines_rules}},
			groups = {mesecon = 2},
			mesecons = {conductor = {
				state = "off",
				rules = pipeworks.mesecons_rules,
				onstate = "pipeworks:mesecon_and_digiline_conductor_tube_on_#id"
			}},
		},
	})
	pipeworks.register_tube("pipeworks:mesecon_and_digiline_conductor_tube_on", {
		description = S("Mesecon and Digiline Conducting Pneumatic Tube Segment on"),
		inventory_image = "pipeworks_conductor_tube_inv.png^pipeworks_digiline_conductor_tube_inv.png",
		short = "pipeworks_conductor_tube_short.png^pipeworks_digiline_conductor_tube_short.png",
		plain = {"pipeworks_conductor_tube_on_plain.png^pipeworks_digiline_conductor_tube_plain.png"},
		noctr = {"pipeworks_conductor_tube_on_noctr.png^pipeworks_digiline_conductor_tube_noctr.png"},
		ends = {"pipeworks_conductor_tube_on_end.png^pipeworks_digiline_conductor_tube_end.png"},
		node_def = {
			digiline = {wire = {rules = pipeworks.digilines_rules}},
			groups = {mesecon = 2, not_in_creative_inventory = 1},
			drop = "pipeworks:mesecon_and_digiline_conductor_tube_off_1",
			mesecons = {conductor = {
				state = "on",
				rules = pipeworks.mesecons_rules,
				offstate = "pipeworks:mesecon_and_digiline_conductor_tube_off_#id"}
			},
		},
	})
	minetest.register_craft({
		type = "shapeless",
		output = "pipeworks:mesecon_and_digiline_conductor_tube_off_1",
		recipe = {"pipeworks:tube_1", "mesecons:mesecon", "digilines:wire_std_00000000"}
	})
	minetest.register_craft({
		type = "shapeless",
		output = "pipeworks:mesecon_and_digiline_conductor_tube_off_1",
		recipe = {"pipeworks:conductor_tube_off_1", "digilines:wire_std_00000000"}
	})
	minetest.register_craft({
		type = "shapeless",
		output = "pipeworks:mesecon_and_digiline_conductor_tube_off_1",
		recipe = {"pipeworks:digiline_conductor_tube_1", "mesecons:mesecon"}
	})
end


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

if pipeworks.enable_mese_tube then
	local function update_formspec(pos)
		local meta = minetest.get_meta(pos)
		local old_formspec = meta:get_string("formspec")
		if string.find(old_formspec, "button1") then -- Old version
			local inv = meta:get_inventory()
			for i = 1, 6 do
				for _, stack in ipairs(inv:get_list("line"..i)) do
					minetest.add_item(pos, stack)
				end
			end
		end
		local buttons_formspec = ""
		for i = 0, 5 do
			buttons_formspec = buttons_formspec .. fs_helpers.cycling_button(meta,
				"image_button[7,"..(i+0.2)..";1,0.6", "l"..(i+1).."s",
				{
					pipeworks.button_off,
					pipeworks.button_on
				}
			)
		end
		meta:set_string("formspec",
			"size[8,11]"..
			"list[context;line1;1,0;6,1;]"..
			"list[context;line2;1,1;6,1;]"..
			"list[context;line3;1,2;6,1;]"..
			"list[context;line4;1,3;6,1;]"..
			"list[context;line5;1,4;6,1;]"..
			"list[context;line6;1,5;6,1;]"..
			"image[0,0;1,1;pipeworks_white.png]"..
			"image[0,1;1,1;pipeworks_black.png]"..
			"image[0,2;1,1;pipeworks_green.png]"..
			"image[0,3;1,1;pipeworks_yellow.png]"..
			"image[0,4;1,1;pipeworks_blue.png]"..
			"image[0,5;1,1;pipeworks_red.png]"..
			buttons_formspec..
			"list[current_player;main;0,7;8,4;]" ..
			"listring[current_player;main]" ..
			"listring[current_player;main]" ..
			"listring[context;line1]" ..
			"listring[current_player;main]" ..
			"listring[context;line2]" ..
			"listring[current_player;main]" ..
			"listring[context;line3]" ..
			"listring[current_player;main]" ..
			"listring[context;line4]" ..
			"listring[current_player;main]" ..
			"listring[context;line5]" ..
			"listring[current_player;main]" ..
			"listring[context;line6]"
			)
	end

	pipeworks.register_tube("pipeworks:mese_tube", {
			description = S("Sorting Pneumatic Tube Segment"),
			inventory_image = "pipeworks_mese_tube_inv.png",
			noctr = {"pipeworks_mese_tube_noctr_1.png", "pipeworks_mese_tube_noctr_2.png", "pipeworks_mese_tube_noctr_3.png",
				"pipeworks_mese_tube_noctr_4.png", "pipeworks_mese_tube_noctr_5.png", "pipeworks_mese_tube_noctr_6.png"},
			plain = {"pipeworks_mese_tube_plain_1.png", "pipeworks_mese_tube_plain_2.png", "pipeworks_mese_tube_plain_3.png",
				"pipeworks_mese_tube_plain_4.png", "pipeworks_mese_tube_plain_5.png", "pipeworks_mese_tube_plain_6.png"},
			ends = { "pipeworks_mese_tube_end.png" },
			short = "pipeworks_mese_tube_short.png",
			no_facedir = true,  -- Must use old tubes, since the textures are rotated with 6d ones
			node_def = {
				tube = {can_go = function(pos, node, velocity, stack)
						 local tbl, tbln = {}, 0
						 local found, foundn = {}, 0
						 local meta = minetest.get_meta(pos)
						 local inv = meta:get_inventory()
						 local name = stack:get_name()
						 for i, vect in ipairs(pipeworks.meseadjlist) do
							local npos = vector.add(pos, vect)
							local node = minetest.get_node(npos)
							local reg_node = minetest.registered_nodes[node.name]
							if meta:get_int("l"..i.."s") == 1 and reg_node then
								local tube_def = reg_node.tube
								if not tube_def or not tube_def.can_insert or
								tube_def.can_insert(npos, node, stack, vect) then
									local invname = "line"..i
									local is_empty = true
									for _, st in ipairs(inv:get_list(invname)) do
										if not st:is_empty() then
											is_empty = false
											if st:get_name() == name then
												foundn = foundn + 1
												found[foundn] = vect
											end
										end
									end
									if is_empty then
										tbln = tbln + 1
										tbl[tbln] = vect
									end
								end
							 end
						 end
						 return (foundn > 0) and found or tbl
					end},
				on_construct = function(pos)
					local meta = minetest.get_meta(pos)
					local inv = meta:get_inventory()
					for i = 1, 6 do
						meta:set_int("l"..tostring(i).."s", 1)
						inv:set_size("line"..tostring(i), 6*1)
					end
					update_formspec(pos)
					meta:set_string("infotext", S("Sorting pneumatic tube"))
				end,
				on_punch = update_formspec,
				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)
					update_formspec(pos)
				end,
				can_dig = function(pos, player)
					update_formspec(pos) -- so non-virtual items would be dropped for old tubes
					return true
				end,
				allow_metadata_inventory_put = function(pos, listname, index, stack, player)
					if not pipeworks.may_configure(pos, player) then return 0 end
					update_formspec(pos) -- For old tubes
					local inv = minetest.get_meta(pos):get_inventory()
					local stack_copy = ItemStack(stack)
					stack_copy:set_count(1)
					inv:set_stack(listname, index, stack_copy)
					return 0
				end,
				allow_metadata_inventory_take = function(pos, listname, index, stack, player)
					if not pipeworks.may_configure(pos, player) then return 0 end
					update_formspec(pos) -- For old tubes
					local inv = minetest.get_meta(pos):get_inventory()
					inv:set_stack(listname, index, ItemStack(""))
					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
					update_formspec(pos) -- For old tubes
					local inv = minetest.get_meta(pos):get_inventory()

					if from_list:match("line%d") and to_list:match("line%d") then
						return count
					else
						inv:set_stack(from_list, from_index, ItemStack(""))
						return 0
					end
				end,
			},
	})

	minetest.register_craft( {
		output = "pipeworks:mese_tube_000000 1",
		recipe = {
			{ "basic_materials:plastic_sheet", "basic_materials:plastic_sheet", "basic_materials:plastic_sheet" },
			{ "technic:control_logic_unit", "default:mese", "technic:control_logic_unit" },
			{ "basic_materials:plastic_sheet", "basic_materials:plastic_sheet", "basic_materials:plastic_sheet" }
		},
	})

	minetest.register_craft( {
		type = "shapeless",
		output = "pipeworks:mese_tube_000000",
		recipe = {
			"pipeworks:tube_1",
			"default:mese_crystal_fragment",
			"default:mese_crystal_fragment",
			"default:mese_crystal_fragment",
			"default:mese_crystal_fragment"
		},
	})
end


code:
local S = minetest.get_translator("pipeworks")
local filename=minetest.get_worldpath() .. "/teleport_tubes"

local tp_tube_db = nil -- nil forces a read
local tp_tube_db_version = 2.0

local function hash(pos)
	return string.format("%.30g", minetest.hash_node_position(pos))
end

local function save_tube_db()
	local file, err = io.open(filename, "w")
	if file then
		tp_tube_db.version = tp_tube_db_version
		file:write(minetest.serialize(tp_tube_db))
		tp_tube_db.version = nil
		io.close(file)
	else
		error(err)
	end
end

local function migrate_tube_db()
		local tmp_db = {}
		tp_tube_db.version = nil
		for key, val in pairs(tp_tube_db) do
			if(val.channel ~= "") then -- skip unconfigured tubes
				tmp_db[hash(val)] = val
			end
		end
		tp_tube_db = tmp_db
		save_tube_db()
end

local function read_tube_db()
	local file = io.open(filename, "r")
	if file ~= nil then
		local file_content = file:read("*all")
		io.close(file)

		if file_content and file_content ~= "" then
			tp_tube_db = minetest.deserialize(file_content)
			if(not tp_tube_db.version or tonumber(tp_tube_db.version) < tp_tube_db_version) then
				migrate_tube_db()
			end
			tp_tube_db.version = nil -- we add it back when saving
			return tp_tube_db -- we read sucessfully
		end
	end
	tp_tube_db = {}
	return tp_tube_db
end

-- debug formatter for coordinates used below
local fmt = function(pos)
	return pos.x..", "..pos.y..", "..pos.z
end

-- updates or adds a tube
local function set_tube(pos, channel, can_receive)
	local tubes = tp_tube_db or read_tube_db()
	local hash = hash(pos)
	local tube = tubes[hash]
	if tube then
		tube.channel = channel
		tube.cr = can_receive
		save_tube_db()
		return
	end

	-- we haven't found any tp tube to update, so lets add it
	-- but sanity check that the hash has not already been inserted.
	-- if so, complain very loudly and refuse the update so the player knows something is amiss.
	-- to catch regressions of https://github.com/minetest-mods/pipeworks/issues/166
	local existing = tp_tube_db[hash]
	if  existing ~= nil then
		local e = "error"
		minetest.log(e, "pipeworks teleport tube update refused due to position hash collision")
		minetest.log(e, "collided hash: "..hash)
		minetest.log(e, "tried-to-place tube: "..fmt(pos))
		minetest.log(e, "existing tube: "..fmt(existing))
		return
	end

	tp_tube_db[hash] = {x=pos.x,y=pos.y,z=pos.z,channel=channel,cr=can_receive}
	save_tube_db()
end

local function remove_tube(pos)
	local tubes = tp_tube_db or read_tube_db()
	tubes[hash(pos)] = nil
	save_tube_db()
end

local function read_node_with_vm(pos)
	local vm = VoxelManip()
	local MinEdge, MaxEdge = vm:read_from_map(pos, pos)
	local data = vm:get_data()
	local area = VoxelArea:new({MinEdge = MinEdge, MaxEdge = MaxEdge})
	return minetest.get_name_from_content_id(data[area:index(pos.x, pos.y, pos.z)])
end

local function get_receivers(pos, channel)
	local tubes = tp_tube_db or read_tube_db()
	local receivers = {}
	local dirty = false
	for key, val in pairs(tubes) do
		-- skip all non-receivers and the tube that it came from as early as possible, as this is called often
		if (val.cr == 1 and val.channel == channel and (val.x ~= pos.x or val.y ~= pos.y or val.z ~= pos.z)) then
			local is_loaded = (minetest.get_node_or_nil(val) ~= nil)
			local node_name = is_loaded and minetest.get_node(pos).name or read_node_with_vm(val)

			if minetest.registered_nodes[node_name] and minetest.registered_nodes[node_name].is_teleport_tube then
				table.insert(receivers, val)
			else
				tp_tube_db[key] = nil
				dirty = true
			end
		end
	end
	if dirty then
		save_tube_db()
	end
	return receivers
end

local function update_meta(meta, can_receive)
	meta:set_int("can_receive", can_receive and 1 or 0)
	local cr_state = can_receive and "on" or "off"
	meta:set_string("formspec","size[8.6,2.2]"..
			"field[0.6,0.6;7,1;channel;"..S("Channel")..";${channel}]"..
			"label[7.3,0;"..S("Receive").."]"..
			"image_button[7.3,0.3;1,0.6;pipeworks_button_" .. cr_state .. ".png;cr" .. (can_receive and 0 or 1) .. ";;;false;pipeworks_button_interm.png]"..
			"image[0.3,1.3;1,1;pipeworks_teleport_tube_inv.png]"..
			"label[1.6,1.2;"..S("channels are public by default").."]" ..
			"label[1.6,1.5;"..S("use <player>:<channel> for fully private channels").."]" ..
			"label[1.6,1.8;"..S("use <player>\\;<channel> for private receivers").."]" ..
			default.gui_bg..
			default.gui_bg_img)
end

pipeworks.register_tube("pipeworks:teleport_tube", {
	description = S("Teleporting Pneumatic Tube Segment"),
	inventory_image = "pipeworks_teleport_tube_inv.png",
	noctr = { "pipeworks_teleport_tube_noctr.png" },
	plain = { "pipeworks_teleport_tube_plain.png" },
	ends = { "pipeworks_teleport_tube_end.png" },
	short = "pipeworks_teleport_tube_short.png",
	node_def = {
		is_teleport_tube = true,
		tube = {
			can_go = function(pos,node,velocity,stack)
				velocity.x = 0
				velocity.y = 0
				velocity.z = 0

				local channel = minetest.get_meta(pos):get_string("channel")
				if channel == "" then return {} end

				local target = get_receivers(pos, channel)
				if target[1] == nil then return {} end

				local d = math.random(1,#target)
				pos.x = target[d].x
				pos.y = target[d].y
				pos.z = target[d].z
				return pipeworks.meseadjlist
			end
		},
		on_construct = function(pos)
			local meta = minetest.get_meta(pos)
			update_meta(meta, true)
			meta:set_string("infotext", S("unconfigured Teleportation Tube"))
		end,
		on_receive_fields = function(pos,formname,fields,sender)
			if not fields.channel -- ignore escaping or clientside manipulation of the form
			or not pipeworks.may_configure(pos, sender) then
				return
			end
			local new_channel = tostring(fields.channel):trim()

			local meta = minetest.get_meta(pos)
			local can_receive = meta:get_int("can_receive")

			-- check for private channels each time before actually changing anything
			-- to not even allow switching between can_receive states of private channels
			if new_channel ~= "" then
				local sender_name = sender:get_player_name()
				local name, mode = new_channel:match("^([^:;]+)([:;])")
				if name and mode and name ~= sender_name then
					--channels starting with '[name]:' can only be used by the named player
					if mode == ":" then
						minetest.chat_send_player(sender_name, S("Sorry, channel '@1' is reserved for exclusive use by @2",
							new_channel, name))
						return

					--channels starting with '[name];' can be used by other players, but cannot be received from
					elseif mode == ";" and (fields.cr1 or (can_receive ~= 0 and not fields.cr0)) then
						minetest.chat_send_player(sender_name, S("Sorry, receiving from channel '@1' is reserved for @2",
							new_channel, name))
						return
					end
				end
			end

			local dirty = false

			-- was the channel changed?
			local channel = meta:get_string("channel")
			if new_channel ~= channel then
				channel = new_channel
				meta:set_string("channel", channel)
				dirty = true
			end

			-- test if a can_receive button was pressed
			if fields.cr0 and can_receive ~= 0 then
				can_receive = 0
				update_meta(meta, false)
				dirty = true
			elseif fields.cr1 and can_receive ~= 1 then
				can_receive = 1
				update_meta(meta, true)
				dirty = true
			end

			-- save if we changed something, handle the empty channel while we're at it
			if dirty then
				if channel ~= "" then
					set_tube(pos, channel, can_receive)
					local cr_description = (can_receive == 1) and "sending and receiving" or "sending"
					meta:set_string("infotext", S("Teleportation Tube @1 on '@2'", cr_description, channel))
				else
					-- remove empty channel tubes, to not have to search through them
					remove_tube(pos)
					meta:set_string("infotext", S("unconfigured Teleportation Tube"))
				end
			end
		end,
		on_destruct = function(pos)
			remove_tube(pos)
		end
	},
})
minetest.register_craft( {
	output = "pipeworks:teleport_tube_1 2",
	recipe = {
	        { "basic_materials:plastic_sheet", "basic_materials:plastic_sheet", "basic_materials:plastic_sheet" },
	        { "", "default:mese_crystal", "" },
	        { "basic_materials:plastic_sheet", "basic_materials:plastic_sheet", "basic_materials:plastic_sheet" }
	},
})

if minetest.get_modpath("mesecons_mvps") ~= nil then
	mesecon.register_on_mvps_move(function(moved_nodes)
		for _, n in ipairs(moved_nodes) do
			if string.find(n.node.name, "pipeworks:teleport_tube") ~= nil then
				local meta = minetest.get_meta(n.pos)
				set_tube(n.pos, meta:get_string("channel"), meta:get_int("can_receive"))
			end
		end
	end)
end


code:
-- (may not be possible) stop removing water nodes that were not placed by outputs when off
In non-finite mode, spigots and fountainheads will vanish water sources in their output positions, even if those output nodes did not place them there.
This is annoying though not game-breaking in non-finite mode, where water sources can at least be easily replenished.
Fixing this would require some kind of metadata marker on water nodes placed by spigots and fountains, such that only water sources placed while the device is "on" are removed when it is "off".
It is debateable whether existing water sources should be marked for removal when the device turns on again.

-- Make spigots and fountainheads deactivate by placing a flowing water node
Currently, in non-finite mode, the spigots and fountainheads react to pressure dropping below threshold by deleting the water source they placed.
VanessaE would like this changed so that these nodes replace the water source with a flowing water source, such that it drains away at the same rate as surrounding water.
This should be a simple case of modifying the cleanup handler that is installed for these nodes by the helper that they use, to place flowing water instead of air in the situations where it would normally do so.

--  Decorative grating functionality
The decorative grating block currently is just that - purely decorative, it serves no purpose.
VanessaE would like this to function as an output with the following properties:
* While on, tries to randomly place a water source in an adjacent node every ABM interval, preferring to place it downwards first.
* Even with multiple water source nodes placed, only drains 1 unit pressure per ABM interval in non-finite mode. Finite mode will cause it to drain 1 unit per water source node placed and simply stop placing sources when below threshold pressure, like spigots and fountainheads already do.
* When turning off in non-finite mode, for all neighbour nodes, replace the water sources with flowing water as discussed above, but *only* if those neighbouring sources do not have any water source neighbours of their own in turn - this will prevent the block from creating a "hole" in a uniform pool of water sources.

-- Support for other fluids in pipes (Feature request/wish list)
Various sources from IRC and github issues have indicated that the ability to carry amounts of substance other than water through pipes would see uses appear for it if it were implemented (there does not appear to be anything trying to do so right now).
Extending the pressure mechanism to handle new fluids would be simple enough, it would just have to deal with more variables.
However, this feature raises the question of how to handle mixtures of fluids in pipes.

Two possible solutions appear evident:
+ Don't mix at all. For each flowable registered, either a variant would be created for each supported liquid, or each node would declare which fluid it carries explicitly. Flowable nodes for different fluids would not interact with each other at all.

+ Equalise "pressure" of multiple fluid variables in a similar manner to how the single water pressure value is currently balanced out, however this raises the issue of how to deal with mixtures - for instance, how is a spigot to function with a mixed liquid? does it simply refuse to function if it doesn't know how to deal with a certain mixture (as it can only output whole nodes)? likewise for certain mixtures in pipes, should it be allowed for lava and water for instance to mix like they don't interact at all?

This mechanism also hints at a weakness of the pressure logic mechanism as it currently stands - namely that liquids are handled like gases and assumed to be compressible.


code:
local S = minetest.get_translator("pipeworks")
minetest.register_node("pipeworks:trashcan", {
	description = S("Trash Can"),
	drawtype = "normal",
	tiles = {
		"pipeworks_trashcan_bottom.png",
		"pipeworks_trashcan_bottom.png",
		"pipeworks_trashcan_side.png",
		"pipeworks_trashcan_side.png",
		"pipeworks_trashcan_side.png",
		"pipeworks_trashcan_side.png",
	},
	groups = {snappy = 3, tubedevice = 1, tubedevice_receiver = 1},
	tube = {
		insert_object = function(pos, node, stack, direction)
			return ItemStack("")
		end,
		connect_sides = {left = 1, right = 1, front = 1, back = 1, top = 1, bottom = 1},
		priority = 1, -- Lower than anything else
	},
	on_construct = function(pos)
		local meta = minetest.get_meta(pos)
		meta:set_string("formspec",
				"size[8,7]"..
				"item_image[0,0;1,1;pipeworks:trashcan]"..
				"label[1,0;"..S("Trash Can").."]"..
				"list[context;trash;3.5,1;1,1;]"..
				default.gui_bg..
				default.gui_bg_img..
				default.gui_slots..
				default.get_hotbar_bg(0,3) ..
				"list[current_player;main;0,3;8,4;]" ..
				"listring[]")
		meta:set_string("infotext", S("Trash Can"))
		meta:get_inventory():set_size("trash", 1)
	end,
	after_place_node = pipeworks.after_place,
	after_dig_node = pipeworks.after_dig,
	on_metadata_inventory_put = function(pos, listname, index, stack, player)
		minetest.get_meta(pos):get_inventory():set_stack(listname, index, ItemStack(""))
	end,
})

minetest.register_craft({
	output = "pipeworks:trashcan",
	recipe = {
		{ "basic_materials:plastic_sheet", "basic_materials:plastic_sheet", "basic_materials:plastic_sheet" },
		{ "default:steel_ingot", "", "default:steel_ingot" },
		{ "default:steel_ingot", "default:steel_ingot", "default:steel_ingot" },
	},
})


code:
-- This file supplies the various kinds of pneumatic tubes
local S = minetest.get_translator("pipeworks")

local tubenodes = {}
pipeworks.tubenodes = tubenodes

minetest.register_alias("pipeworks:tube", "pipeworks:tube_000000")

-- now, a function to define the tubes

local REGISTER_COMPATIBILITY = true

local vti = {4, 3, 2, 1, 6, 5}

local default_noctrs = { "pipeworks_tube_noctr.png" }
local default_plain = { "pipeworks_tube_plain.png" }
local default_ends = { "pipeworks_tube_end.png" }

local texture_mt = {
	__index = function(table, key)
		local size, idx = #table, tonumber(key)
		if size > 0 then -- avoid endless loops with empty tables
			while idx > size do idx = idx - size end
			return table[idx]
		end
	end
}

local register_one_tube = function(name, tname, dropname, desc, plain, noctrs, ends, short, inv, special, connects, style)
	noctrs = noctrs or default_noctrs
	setmetatable(noctrs, texture_mt)
	plain = plain or default_plain
	setmetatable(plain, texture_mt)
	ends = ends or default_ends
	setmetatable(ends, texture_mt)
	short = short or "pipeworks_tube_short.png"
	inv = inv or "pipeworks_tube_inv.png"

	local outboxes = {}
	local outsel = {}
	local outimgs = {}

	for i = 1, 6 do
		outimgs[vti[i]] = plain[i]
	end

	for _, v in ipairs(connects) do
		pipeworks.table_extend(outboxes, pipeworks.tube_boxes[v])
		table.insert(outsel, pipeworks.tube_selectboxes[v])
		outimgs[vti[v]] = noctrs[v]
	end

	if #connects == 1 then
		local v = connects[1]
		v = v-1 + 2*(v%2) -- Opposite side
		outimgs[vti[v]] = ends[v]
	end

	local tgroups = {snappy = 3, tube = 1, tubedevice = 1, not_in_creative_inventory = 1}
	local tubedesc = string.format("%s %s", desc, dump(connects))
	local iimg = type(plain[1]) == "table" and plain[1].name or plain[1]
	local wscale = {x = 1, y = 1, z = 1}

	if #connects == 0 then
		tgroups = {snappy = 3, tube = 1, tubedevice = 1}
		tubedesc = desc
		iimg=inv
		outimgs = {
			short, short,
			ends[3],ends[4],
			short, short
		}
		outboxes = { -24/64, -9/64, -9/64, 24/64, 9/64, 9/64 }
		outsel = { -24/64, -10/64, -10/64, 24/64, 10/64, 10/64 }
		wscale = {x = 1, y = 1, z = 0.01}
	end

	local rname = string.format("%s_%s", name, tname)
	table.insert(tubenodes, rname)

	local nodedef = {
		description = tubedesc,
		drawtype = "nodebox",
		tiles = outimgs,
		sunlight_propagates = true,
		inventory_image = iimg,
		wield_image = iimg,
		wield_scale = wscale,
		paramtype = "light",
		selection_box = {
			type = "fixed",
			fixed = outsel
		},
		node_box = {
			type = "fixed",
			fixed = outboxes
		},
		groups = tgroups,
		sounds = default.node_sound_wood_defaults(),
		walkable = true,
		stack_max = 99,
		basename = name,
		style = style,
		drop = string.format("%s_%s", name, dropname),
		tubelike = 1,
		tube = {
			connect_sides = {front = 1, back = 1, left = 1, right = 1, top = 1, bottom = 1},
			priority = 50
		},
		after_place_node = pipeworks.after_place,
		after_dig_node = pipeworks.after_dig,
		on_rotate = false,
		on_blast = function(pos, intensity)
			if not intensity or intensity > 1 + 3^0.5 then
				minetest.remove_node(pos)
				return {string.format("%s_%s", name, dropname)}
			end
			minetest.swap_node(pos, {name = "pipeworks:broken_tube_1"})
			pipeworks.scan_for_tube_objects(pos)
		end,
		check_for_pole = pipeworks.check_for_vert_tube,
		check_for_horiz_pole = pipeworks.check_for_horiz_tube,
		tubenumber = tonumber(tname)
	}
	if style == "6d" then
		nodedef.paramtype2 = "facedir"
	end

	if special == nil then special = {} end

	for key, value in pairs(special) do
		--if key == "after_dig_node" or key == "after_place_node" then
		--	nodedef[key.."_"] = value
		if key == "groups" then
			for group, val in pairs(value) do
				nodedef.groups[group] = val
			end
		elseif key == "tube" then
			for key, val in pairs(value) do
				nodedef.tube[key] = val
			end
		else
			nodedef[key] = pipeworks.table_recursive_replace(value, "#id", tname)
		end
	end

	minetest.register_node(rname, nodedef)
end

local register_all_tubes = function(name, desc, plain, noctrs, ends, short, inv, special, old_registration)
	if old_registration then
		for xm = 0, 1 do
		for xp = 0, 1 do
		for ym = 0, 1 do
		for yp = 0, 1 do
		for zm = 0, 1 do
		for zp = 0, 1 do
			local connects = {}
			if xm == 1 then
				connects[#connects+1] = 1
			end
			if xp == 1 then
				connects[#connects+1] = 2
			end
			if ym == 1 then
				connects[#connects+1] = 3
			end
			if yp == 1 then
				connects[#connects+1] = 4
			end
			if zm == 1 then
				connects[#connects+1] = 5
			end
			if zp == 1 then
				connects[#connects+1] = 6
			end
			local tname = xm..xp..ym..yp..zm..zp
			register_one_tube(name, tname, "000000", desc, plain, noctrs, ends, short, inv, special, connects, "old")
		end
		end
		end
		end
		end
		end
	else
		-- 6d tubes: uses only 10 nodes instead of 64, but the textures must be rotated
		local cconnects = {{}, {1}, {1, 2}, {1, 3}, {1, 3, 5}, {1, 2, 3}, {1, 2, 3, 5}, {1, 2, 3, 4}, {1, 2, 3, 4, 5}, {1, 2, 3, 4, 5, 6}}
		for index, connects in ipairs(cconnects) do
			register_one_tube(name, tostring(index), "1", desc, plain, noctrs, ends, short, inv, special, connects, "6d")
		end
		if REGISTER_COMPATIBILITY then
			local cname = name.."_compatibility"
			minetest.register_node(cname, {
				drawtype = "airlike",
				style = "6d",
				basename = name,
				inventory_image = inv,
				wield_image = inv,
				paramtype = "light",
				sunlight_propagates = true,
				description = S("Pneumatic tube segment (legacy)"),
				after_place_node = pipeworks.after_place,
				groups = {not_in_creative_inventory = 1, tube_to_update = 1, tube = 1},
				tube = {connect_sides = {front = 1, back = 1, left = 1, right = 1, top = 1, bottom = 1}},
				drop = name.."_1",
			})
			table.insert(tubenodes, cname)
			for xm = 0, 1 do
			for xp = 0, 1 do
			for ym = 0, 1 do
			for yp = 0, 1 do
			for zm = 0, 1 do
			for zp = 0, 1 do
				local tname = xm..xp..ym..yp..zm..zp
				minetest.register_alias(name.."_"..tname, cname)
			end
			end
			end
			end
			end
			end
		end
	end
end

pipeworks.register_tube = function(name, def, ...)
	if type(def) == "table" then
		register_all_tubes(name, def.description,
				def.plain, def.noctr, def.ends, def.short,
				def.inventory_image, def.node_def, def.no_facedir)
	else
		-- we assert to be the old function with the second parameter being the description
		-- function(name, desc, plain, noctrs, ends, short, inv, special, old_registration)
		assert(type(def) == "string", "invalid arguments to pipeworks.register_tube")
		register_all_tubes(name, def, ...)
	end
end

if REGISTER_COMPATIBILITY then
	minetest.register_abm({
		nodenames = {"group:tube_to_update"},
		interval = 1,
		chance = 1,
		action = function(pos, node, active_object_count, active_object_count_wider)
			local minp = vector.subtract(pos, 1)
			local maxp = vector.add(pos, 1)
			if table.getn(minetest.find_nodes_in_area(minp, maxp, "ignore")) == 0 then
				pipeworks.scan_for_tube_objects(pos)
			end
		end
	})
end


code:
local S = minetest.get_translator("pipeworks")
if pipeworks.enable_sand_tube then
	pipeworks.register_tube("pipeworks:sand_tube", {
		description = S("Vacuuming Pneumatic Tube Segment"),
		inventory_image = "pipeworks_sand_tube_inv.png",
		short = "pipeworks_sand_tube_short.png",
		noctr = {"pipeworks_sand_tube_noctr.png"},
		plain = {"pipeworks_sand_tube_plain.png"},
		ends  = {"pipeworks_sand_tube_end.png"},
		node_def = {groups = {vacuum_tube = 1}},
	})

	minetest.register_craft( {
		output = "pipeworks:sand_tube_1 2",
		recipe = {
			{"basic_materials:plastic_sheet", "basic_materials:plastic_sheet", "basic_materials:plastic_sheet"},
			{"group:sand",                 "group:sand",                 "group:sand"},
			{"basic_materials:plastic_sheet", "basic_materials:plastic_sheet", "basic_materials:plastic_sheet"}
		},
	})

	minetest.register_craft( {
		output = "pipeworks:sand_tube_1",
		recipe = {
			{"group:sand", "pipeworks:tube_1", "group:sand"},
		},
	})
end

if pipeworks.enable_mese_sand_tube then
	pipeworks.register_tube("pipeworks:mese_sand_tube", {
			description = S("Adjustable Vacuuming Pneumatic Tube Segment"),
			inventory_image = "pipeworks_mese_sand_tube_inv.png",
			short = "pipeworks_mese_sand_tube_short.png",
			noctr = {"pipeworks_mese_sand_tube_noctr.png"},
			plain = {"pipeworks_mese_sand_tube_plain.png"},
			ends  = {"pipeworks_mese_sand_tube_end.png"},
			node_def = {
				groups = {vacuum_tube = 1},
				on_construct = function(pos)
					local meta = minetest.get_meta(pos)
					meta:set_int("dist", 0)
					meta:set_string("formspec", "size[2.1,0.8]"..
							"image[0,0;1,1;pipeworks_mese_sand_tube_inv.png]"..
							"field[1.3,0.4;1,1;dist;radius;${dist}]"..
							default.gui_bg..
							default.gui_bg_img)
					meta:set_string("infotext", S("Adjustable Vacuuming Pneumatic Tube Segment"))
				end,
				on_receive_fields = function(pos,formname,fields,sender)
					if not pipeworks.may_configure(pos, sender) then return end
					local meta = minetest.get_meta(pos)
					local dist = tonumber(fields.dist)
					if dist then
						dist = math.max(0, dist)
						dist = math.min(8, dist)
						meta:set_int("dist", dist)
						meta:set_string("infotext", (S("Adjustable Vacuuming Pneumatic Tube Segment (@1m)", dist)))
					end
				end,
			},
	})

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

	minetest.register_craft( {
		type = "shapeless",
		output = "pipeworks:mese_sand_tube_1",
		recipe = {
			"pipeworks:sand_tube_1",
			"default:mese_crystal_fragment",
			"default:mese_crystal_fragment",
			"default:mese_crystal_fragment",
			"default:mese_crystal_fragment"
		},
	})
end

local function vacuum(pos, radius)
	radius = radius + 0.5
	for _, object in pairs(minetest.get_objects_inside_radius(pos, math.sqrt(3) * radius)) do
		local lua_entity = object:get_luaentity()
		if not object:is_player() and lua_entity and lua_entity.name == "__builtin:item" then
			local obj_pos = object:get_pos()
			local minpos = vector.subtract(pos, radius)
			local maxpos = vector.add(pos, radius)
			if  obj_pos.x >= minpos.x and obj_pos.x <= maxpos.x
			and obj_pos.y >= minpos.y and obj_pos.y <= maxpos.y
			and obj_pos.z >= minpos.z and obj_pos.z <= maxpos.z then
				if lua_entity.itemstring ~= "" then
					pipeworks.tube_inject_item(pos, pos, vector.new(0, 0, 0), lua_entity.itemstring)
					lua_entity.itemstring = ""
				end
				object:remove()
			end
		end
	end
end

minetest.register_abm({nodenames = {"group:vacuum_tube"},
	interval = 1,
	chance = 1,
	label = "Vacuum tubes",
	action = function(pos, node, active_object_count, active_object_count_wider)
		if node.name:find("pipeworks:sand_tube") then
			vacuum(pos, 2)
		else
			local radius = minetest.get_meta(pos):get_int("dist")
			vacuum(pos, radius)
		end
	end
})


code:
local S = minetest.get_translator("pipeworks")
local assumed_eye_pos = vector.new(0, 1.5, 0)

local function vector_copy(v)
	return { x = v.x, y = v.y, z = v.z }
end

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

local function set_wielder_formspec(data, meta)
	meta:set_string("formspec",
			"invsize[8,"..(6+data.wield_inv_height)..";]"..
			"item_image[0,0;1,1;"..data.name_base.."_off]"..
			"label[1,0;"..minetest.formspec_escape(data.description).."]"..
			"list[current_name;"..minetest.formspec_escape(data.wield_inv_name)..";"..((8-data.wield_inv_width)*0.5)..",1;"..data.wield_inv_width..","..data.wield_inv_height..";]"..
			"list[current_player;main;0,"..(2+data.wield_inv_height)..";8,4;]" ..
			"listring[]")
	meta:set_string("infotext", data.description)
end

local can_tool_dig_node = function(nodename, toolcaps, toolname)
	--pipeworks.logger("can_tool_dig_node() STUB nodename="..tostring(nodename).." toolname="..tostring(toolname).." toolcaps: "..dump(toolcaps))
	-- brief documentation of minetest.get_dig_params() as it's not yet documented in lua_api.txt:
	-- takes two arguments, a node's block groups and a tool's capabilities,
	-- both as they appear in their respective definitions.
	-- returns a table with the following fields:
	-- diggable: boolean, can this tool dig this node at all
	-- time: float, time needed to dig with this tool
	-- wear: int, number of wear points to inflict on the item
	local nodedef = minetest.registered_nodes[nodename]
	-- don't explode due to nil def in event of unknown node!
	if (nodedef == nil) then return false end

	local nodegroups = nodedef.groups
	local diggable = minetest.get_dig_params(nodegroups, toolcaps).diggable
	if not diggable then
		-- a pickaxe can't actually dig leaves based on it's groups alone,
		-- but a player holding one can - the game seems to fall back to the hand.
		-- fall back to checking the hand's properties if the tool isn't the correct one.
		local hand_caps = minetest.registered_items[""].tool_capabilities
		diggable = minetest.get_dig_params(nodegroups, hand_caps)
	end
	return diggable
end

local function wielder_on(data, wielder_pos, wielder_node)
	data.fixup_node(wielder_pos, wielder_node)
	if wielder_node.name ~= data.name_base.."_off" then return end
	wielder_node.name = data.name_base.."_on"
	minetest.swap_node(wielder_pos, wielder_node)
	minetest.check_for_falling(wielder_pos)
	local wielder_meta = minetest.get_meta(wielder_pos)
	local inv = wielder_meta:get_inventory()
	local wield_inv_name = data.wield_inv_name
	local wieldindex
	for i, stack in ipairs(inv:get_list(wield_inv_name)) do
		if not stack:is_empty() then
			wieldindex = i
			break
		end
	end
	if not wieldindex then
		if not data.ghost_inv_name then return end
		wield_inv_name = data.ghost_inv_name
		inv:set_stack(wield_inv_name, 1, ItemStack(data.ghost_tool))
		wieldindex = 1
	end
	local dir = minetest.facedir_to_dir(wielder_node.param2)
	-- under/above is currently intentionally left switched
	-- even though this causes some problems with deployers and e.g. seeds
	-- as there are some issues related to nodebreakers otherwise breaking 2 nodes afar.
	-- solidity would have to be checked as well,
	-- but would open a whole can of worms related to difference in nodebreaker/deployer behavior
	-- and the problems of wielders acting on themselves if below is solid
	local under_pos = vector.subtract(wielder_pos, dir)
	local above_pos = vector.subtract(under_pos, dir)
	local pitch
	local yaw
	if dir.z < 0 then
		yaw = 0
		pitch = 0
	elseif dir.z > 0 then
		yaw = math.pi
		pitch = 0
	elseif dir.x < 0 then
		yaw = 3*math.pi/2
		pitch = 0
	elseif dir.x > 0 then
		yaw = math.pi/2
		pitch = 0
	elseif dir.y > 0 then
		yaw = 0
		pitch = -math.pi/2
	else
		yaw = 0
		pitch = math.pi/2
	end
	local virtplayer = pipeworks.create_fake_player({
		name = data.masquerade_as_owner and wielder_meta:get_string("owner")
			or ":pipeworks:" .. minetest.pos_to_string(wielder_pos),
		formspec = wielder_meta:get_string("formspec"),
		look_dir = vector.multiply(dir, -1),
		look_pitch = pitch,
		look_yaw = yaw,
		sneak = data.sneak,
		position = vector.subtract(wielder_pos, assumed_eye_pos),
		inventory = inv,
		wield_index = wieldindex,
		wield_list = wield_inv_name
	})

	local pointed_thing = { type="node", under=under_pos, above=above_pos }
	data.act(virtplayer, pointed_thing)
	if data.eject_drops then
		for i, stack in ipairs(inv:get_list("main")) do
			if not stack:is_empty() then
				pipeworks.tube_inject_item(wielder_pos, wielder_pos, dir, stack)
				inv:set_stack("main", i, ItemStack(""))
			end
		end
	end
end

local function wielder_off(data, pos, node)
	if node.name == data.name_base.."_on" then
		node.name = data.name_base.."_off"
		minetest.swap_node(pos, node)
		minetest.check_for_falling(pos)
	end
end

local function register_wielder(data)
	data.fixup_node = data.fixup_node or function (pos, node) end
	data.fixup_oldmetadata = data.fixup_oldmetadata or function (m) return m end
	for _, state in ipairs({ "off", "on" }) do
		local groups = { snappy=2, choppy=2, oddly_breakable_by_hand=2, mesecon=2, tubedevice=1, tubedevice_receiver=1 }
		if state == "on" then groups.not_in_creative_inventory = 1 end
		local tile_images = {}
		for _, face in ipairs({ "top", "bottom", "side2", "side1", "back", "front" }) do
			table.insert(tile_images, data.texture_base.."_"..face..(data.texture_stateful[face] and "_"..state or "")..".png")
		end
		minetest.register_node(data.name_base.."_"..state, {
			description = data.description,
			tiles = tile_images,
			mesecons = {
				effector = {
					rules = pipeworks.rules_all,
					action_on = function (pos, node)
						wielder_on(data, pos, node)
					end,
					action_off = function (pos, node)
						wielder_off(data, pos, node)
					end,
				},
			},
			tube = {
				can_insert = function(pos, node, stack, tubedir)
					if not data.tube_permit_anteroposterior_insert then
						local nodedir = minetest.facedir_to_dir(node.param2)
						if vector.equals(tubedir, nodedir) or vector.equals(tubedir, vector.multiply(nodedir, -1)) then
							return false
						end
					end
					local meta = minetest.get_meta(pos)
					local inv = meta:get_inventory()
					return inv:room_for_item(data.wield_inv_name, stack)
				end,
				insert_object = function(pos, node, stack, tubedir)
					if not data.tube_permit_anteroposterior_insert then
						local nodedir = minetest.facedir_to_dir(node.param2)
						if vector.equals(tubedir, nodedir) or vector.equals(tubedir, vector.multiply(nodedir, -1)) then
							return stack
						end
					end
					local meta = minetest.get_meta(pos)
					local inv = meta:get_inventory()
					return inv:add_item(data.wield_inv_name, stack)
				end,
				input_inventory = data.wield_inv_name,
				connect_sides = data.tube_connect_sides,
				can_remove = function(pos, node, stack, tubedir)
					return stack:get_count()
				end,
			},
			is_ground_content = true,
			paramtype2 = "facedir",
			tubelike = 1,
			groups = groups,
			sounds = default.node_sound_stone_defaults(),
			drop = data.name_base.."_off",
			on_construct = function(pos)
				local meta = minetest.get_meta(pos)
				set_wielder_formspec(data, meta)
				local inv = meta:get_inventory()
				inv:set_size(data.wield_inv_name, data.wield_inv_width*data.wield_inv_height)
				if data.ghost_inv_name then
					inv:set_size(data.ghost_inv_name, 1)
				end
				if data.eject_drops then
					inv:set_size("main", 100)
				end
			end,
			after_place_node = function (pos, placer)
				pipeworks.scan_for_tube_objects(pos)
				local placer_pos = placer:get_pos()
				if placer_pos and placer:is_player() then placer_pos = vector.add(placer_pos, assumed_eye_pos) end
				if placer_pos then
					local dir = vector.subtract(pos, placer_pos)
					local node = minetest.get_node(pos)
					node.param2 = minetest.dir_to_facedir(dir, true)
					minetest.set_node(pos, node)
					minetest.log("action", "real (6d) facedir: " .. node.param2)
				end
				minetest.get_meta(pos):set_string("owner", placer:get_player_name())
			end,
			can_dig = (data.can_dig_nonempty_wield_inv and delay(true) or function(pos, player)
				local meta = minetest.get_meta(pos)
				local inv = meta:get_inventory()
				return inv:is_empty(data.wield_inv_name)
			end),
			after_dig_node = function(pos, oldnode, oldmetadata, digger)
				-- The legacy-node fixup is done here in a
				-- different form from the standard fixup,
				-- rather than relying on a standard fixup
				-- in an on_dig callback, because some
				-- non-standard diggers (such as technic's
				-- mining drill) don't respect on_dig.
				oldmetadata = data.fixup_oldmetadata(oldmetadata)
				for _, stack in ipairs(oldmetadata.inventory[data.wield_inv_name] or {}) do
					if not stack:is_empty() then
						minetest.add_item(pos, stack)
					end
				end
				pipeworks.scan_for_tube_objects(pos)
			end,
			on_rotate = pipeworks.on_rotate,
			on_punch = data.fixup_node,
			allow_metadata_inventory_put = function(pos, listname, index, stack, player)
				if not pipeworks.may_configure(pos, player) then return 0 end
				return stack:get_count()
			end,
			allow_metadata_inventory_take = function(pos, listname, index, stack, player)
				if not pipeworks.may_configure(pos, player) then return 0 end
				return stack:get_count()
			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
		})
	end
end

if pipeworks.enable_node_breaker then
	local data
	-- see after end of data table for other use of these variables
	local name_base = "pipeworks:nodebreaker"
	local wield_inv_name = "pick"
	data = {
		name_base = name_base,
		description = S("Node Breaker"),
		texture_base = "pipeworks_nodebreaker",
		texture_stateful = { top = true, bottom = true, side2 = true, side1 = true, front = true },
		tube_connect_sides = { top=1, bottom=1, left=1, right=1, back=1 },
		tube_permit_anteroposterior_insert = false,
		wield_inv_name = wield_inv_name,
		wield_inv_width = 1,
		wield_inv_height = 1,
		can_dig_nonempty_wield_inv = true,
		ghost_inv_name = "ghost_pick",
		ghost_tool = ":",	-- hand by default
		fixup_node = function (pos, node)
			local meta = minetest.get_meta(pos)
			local inv = meta:get_inventory()
			-- Node breakers predating the visible pick slot
			-- may have been partially updated.  This code
			-- fully updates them.	Some have been observed
			-- to have no pick slot at all; first add one.
			if inv:get_size("pick") ~= 1 then
				inv:set_size("pick", 1)
			end
			-- Originally, they had a ghost pick in a "pick"
			-- inventory, no other inventory, and no form.
			-- The partial update of early with-form node
			-- breaker code gives them "ghost_pick" and "main"
			-- inventories, but leaves the old ghost pick in
			-- the "pick" inventory, and doesn't add a form.
			-- First perform that partial update.
			if inv:get_size("ghost_pick") ~= 1 then
				inv:set_size("ghost_pick", 1)
				inv:set_size("main", 100)
			end
			-- If the node breaker predates the visible pick
			-- slot, which we can detect by it not having a
			-- form, then the pick slot needs to be cleared
			-- of the old ghost pick.
			if (meta:get_string("formspec") or "") == "" then
				inv:set_stack("pick", 1, ItemStack(""))
			end
			-- Finally, unconditionally set the formspec
			-- and infotext.  This not only makes the
			-- pick slot visible for node breakers where
			-- it wasn't before; it also updates the form
			-- for node breakers that had an older version
			-- of the form, and sets infotext where it was
			-- missing for early with-form node breakers.
			set_wielder_formspec(data, meta)
		end,
		fixup_oldmetadata = function (oldmetadata)
			-- Node breakers predating the visible pick slot,
			-- with node form, kept their ghost pick in an
			-- inventory named "pick", the same name as the
			-- later visible pick slot.  The pick must be
			-- removed to avoid spilling it.
			if not oldmetadata.fields.formspec then
				return { inventory = { pick = {} }, fields = oldmetadata.fields }
			else
				return oldmetadata
			end
		end,
		masquerade_as_owner = true,
		sneak = false,
		act = function(virtplayer, pointed_thing)
			--local dname = "nodebreaker.act() "
			local wieldstack = virtplayer:get_wielded_item()
			local oldwieldstack = ItemStack(wieldstack)
			local on_use = (minetest.registered_items[wieldstack:get_name()] or {}).on_use
			if on_use then
				--pipeworks.logger(dname.."invoking on_use "..tostring(on_use))
				wieldstack = on_use(wieldstack, virtplayer, pointed_thing) or wieldstack
				virtplayer:set_wielded_item(wieldstack)
			else
				local under_node = minetest.get_node(pointed_thing.under)
				local def = minetest.registered_nodes[under_node.name]
				if not def then
					-- do not dig an unknown node
					return
				end
				-- check that the current tool is capable of destroying the
				-- target node.
				-- if we can't, don't dig, and leave the wield stack unchanged.
				-- note that wieldstack:get_tool_capabilities() returns hand
				-- properties if the item has none of it's own.
				if can_tool_dig_node(under_node.name,
						wieldstack:get_tool_capabilities(),
						wieldstack:get_name()) then
					def.on_dig(pointed_thing.under, under_node, virtplayer)
					local sound = def.sounds and def.sounds.dug
					if sound then
						minetest.sound_play(sound.name,
							{pos=pointed_thing.under, gain=sound.gain})
					end
					wieldstack = virtplayer:get_wielded_item()
				else
					--pipeworks.logger(dname.."couldn't dig node!")
				end
			end
			local wieldname = wieldstack:get_name()
			if wieldname == oldwieldstack:get_name() then
				-- don't mechanically wear out tool
				if wieldstack:get_count() == oldwieldstack:get_count() and
						wieldstack:get_metadata() == oldwieldstack:get_metadata() and
						((minetest.registered_items[wieldstack:get_name()] or {}).wear_represents or "mechanical_wear") == "mechanical_wear" then
					virtplayer:set_wielded_item(oldwieldstack)
				end
			elseif wieldname ~= "" then
				-- tool got replaced by something else:
				-- treat it as a drop
				virtplayer:get_inventory():add_item("main", wieldstack)
				virtplayer:set_wielded_item(ItemStack(""))
			end
		end,
		eject_drops = true,
	}
	register_wielder(data)
	minetest.register_craft({
		output = "pipeworks:nodebreaker_off",
		recipe = {
			{ "pipeworks:gear", "pipeworks:gear",   "pipeworks:gear"    },
			{ "default:stone", "mesecons:piston",   "default:stone" },
			{ "group:wood",    "mesecons:mesecon",  "group:wood" },
		}
	})
	-- aliases for when someone had technic installed, but then uninstalled it but not pipeworks
	minetest.register_alias("technic:nodebreaker_off", "pipeworks:nodebreaker_off")
	minetest.register_alias("technic:nodebreaker_on", "pipeworks:nodebreaker_on")
	minetest.register_alias("technic:node_breaker_off", "pipeworks:nodebreaker_off")
	minetest.register_alias("technic:node_breaker_on", "pipeworks:nodebreaker_on")
	-- turn legacy auto-tree-taps into node breakers
	dofile(pipeworks.modpath.."/legacy.lua")

	-- register LBM for transition to cheaper node breakers
	local lbm_id = "pipeworks:refund_node_breaker_pick"
	minetest.register_lbm({
		name = lbm_id,
		label = "Give back mese pick for pre-transition node breakers",
		run_at_every_load = false,
		nodenames = { name_base.."_on", name_base.."_off" },
		action = function(pos, node)
			pipeworks.logger(lbm_id.." entry, nodename="..node.name)
			local invref = minetest.get_meta(pos):get_inventory()
			invref:add_item(wield_inv_name, ItemStack("default:pick_mese"))
		end
	})
end

if pipeworks.enable_deployer then
	register_wielder({
		name_base = "pipeworks:deployer",
		description = S("Deployer"),
		texture_base = "pipeworks_deployer",
		texture_stateful = { front = true },
		tube_connect_sides = { back=1 },
		tube_permit_anteroposterior_insert = true,
		wield_inv_name = "main",
		wield_inv_width = 3,
		wield_inv_height = 3,
		can_dig_nonempty_wield_inv = false,
		masquerade_as_owner = true,
		sneak = false,
		act = function(virtplayer, pointed_thing)
			local wieldstack = virtplayer:get_wielded_item()
			virtplayer:set_wielded_item((minetest.registered_items[wieldstack:get_name()] or {on_place=minetest.item_place}).on_place(wieldstack, virtplayer, pointed_thing) or wieldstack)
		end,
		eject_drops = false,
	})
	minetest.register_craft({
		output = "pipeworks:deployer_off",
		recipe = {
			{ "group:wood",    "default:chest",    "group:wood"    },
			{ "default:stone", "mesecons:piston",  "default:stone" },
			{ "default:stone", "mesecons:mesecon", "default:stone" },
		}
	})
	-- aliases for when someone had technic installed, but then uninstalled it but not pipeworks
	minetest.register_alias("technic:deployer_off", "pipeworks:deployer_off")
	minetest.register_alias("technic:deployer_on", "pipeworks:deployer_on")
end

if pipeworks.enable_dispenser then
	register_wielder({
		name_base = "pipeworks:dispenser",
		description = S("Dispenser"),
		texture_base = "pipeworks_dispenser",
		texture_stateful = { front = true },
		tube_connect_sides = { back=1 },
		tube_permit_anteroposterior_insert = true,
		wield_inv_name = "main",
		wield_inv_width = 3,
		wield_inv_height = 3,
		can_dig_nonempty_wield_inv = false,
		masquerade_as_owner = false,
		sneak = true,
		act = function(virtplayer, pointed_thing)
			local wieldstack = virtplayer:get_wielded_item()
			virtplayer:set_wielded_item((minetest.registered_items[wieldstack:get_name()] or
				{on_drop=minetest.item_drop}).on_drop(wieldstack, virtplayer, virtplayer:get_pos()) or
				wieldstack)
		end,
		eject_drops = false,
	})
	minetest.register_craft({
		output = "pipeworks:dispenser_off",
		recipe = {
			{ "default:desert_sand", "default:chest",    "default:desert_sand" },
			{ "default:stone",       "mesecons:piston",  "default:stone"       },
			{ "default:stone",       "mesecons:mesecon", "default:stone"       },
		}
	})
end


code:
Protector Redo mod [protect]

Protector redo for minetest is a mod that protects a players builds by placing
a block that stops other players from digging or placing blocks in that area.

based on glomie's mod, remade by Zeg9 and rewritten by TenPlus1.

https://forum.minetest.net/viewtopic.php?f=11&t=9376

Change log:

- 0.1 - Initial release
- 0.2 - Texture update
- 0.3 - Added Protection Logo to blend in with player builds
- 0.4 - Code tweak for 0.4.10+
- 0.5 - Added protector.radius variable in init.lua (default: 5)
- 0.6 - Added Protected Doors (wood and steel) and Protected Chest
- 0.7 - Protected Chests now have "To Chest" and "To Inventory" buttons to copy
      contents across, also chests can be named
- 0.8 - Updated to work with Minetest 0.4.12, simplified textures
- 0.9 - Tweaked code
- 1.0 - Only owner can remove protector
- 1.1 - Set 'protector_pvp = true' in minetest.conf to disable pvp in protected
      areas except your own, also setting protector_pvp_spawn higher than 0 will
      disable pvp around spawn area with the radius you entered
- 1.2 - Shift and click support added with Minetest 0.4.13 to quickly copy stacks
      to and from protected chest
- 1.3 - Moved protector on_place into node itself, protector zone display changed
      from 10 to 5 seconds, general code tidy
- 1.4 - Changed protector recipes to give single item instead of 4, added + button
      to interface, tweaked and tidied code, added admin command /delprot to remove
      protectors in bulk from banned/old players
- 1.5 - Added much requested protected trapdoor
- 1.6 - Added protector_drop (true or false) and protector_hurt (hurt by this num)
      variables to minetest.conf settings to stop players breaking protected
      areas by dropping tools and hurting player.
- 1.7 - Included an edited version of WTFPL doors mod since protected doors didn't
      work with the doors mod in the latest daily build... Now it's fine :)
      added support for "protection_bypass" privelage.
- 1.8 - Added 'protector_flip' setting to stop players using lag to grief into
      another players house, it flips them around to stop them digging.
- 1.9 - Renamed 'protector_pvp_spawn' setting to 'protector_spawn' which protects
      an area around static spawnpoint and disables pvp if active.
      (note: previous name can still be used)
- 2.0 - Added protector placement tool (thanks to Shara) so that players can easily
      stand on a protector, face in a direction and it places a new one at a set
      distance to cover protection radius.  Added /protector_show command (thanks agaran)
      Protectors and chest cannot be moved by mesecon pistons or machines.
- 2.1 - Added 'protector_night_pvp' setting so night-time becomes a free for all and
      players can hurt one another even inside protected areas (not spawn protected)
- 2.2 - Updated protector tool so that player only needs to stand nearby (2 block radius)
      It can also place vertically (up and down) as well.  New protector recipe added.
- 2.3 - Localise many of the protector functions and tidy code.
- 2.4 - Update to newer functions, Minetest 0.4.16 needed to run now.
- 2.5 - Added HUD text to show when player is inside a protected area (updates every 5 seconds)
- 2.6 - Add protection against CSM tampering, updated Intllib support (thanks codexp), tweaked block textures
- 2.7 - Remove protection field entity when protector has been dug
- 2.8 - Added 'protector_show_interval' setting to minetest.conf [default is 5], make protection field glow in dark.
- 2.9 - Added MineClone2 recipes for protection block but no official support as yet
- 3.0 - Added PlayerFactions support, 'protector_hud_interval' setting and listing in advanced settings for mod values.
- 3.1 - Ability to hide protection blocks using /protector_hide and /protector_show , italian local added (thanks Hamlet)
- 3.2 - Defaults to Minetest translation if found, otherwise intllib fallback if loaded, locale files updated for both.

Lucky Blocks: 10

Usage: (requires server privelage)

list names to remove

	/protector_remove

remove specific user names

	/protector_remove name1 name2

remove all names from list

	/protector_remove -

Whenever a player is near any protectors with name1 or name2 then it will be
replaced by an air block.

show owner name to replace

	/protector_replace

replace owner with new name

	/protector_replace owner new_owner

reset name list

	/protector_replace -

show protected areas of your nearby protectors (max of 5)
	/protector_show_area

A players own protection blocks can be hidden and shown using the following:
	/protector_hide
	/protector_show

The following lines can be added to your minetest.conf file to configure specific features of the mod:

protector_radius = 5
- Sets the area around each protection node so that other players cannot dig, place or enter through protected doors or chests.

protector_pvp = true
- true or false this setting disabled pvp inside of protected areas for all players apart from those listed on the protector node.

protector_night_pvp = false
- when true this setting enables pvp at night time only, even inside protected areas, requires protector_pvp to be active to work.

protector_spawn = 10
- Sets an area 10 nodes around static spawnpoint that is protected.

protector_hurt = 2
- When set to above 0, players digging in protected areas will be hurt by 2 health points (or whichever number it's set to)

protector_flip = true
- When true players who dig inside a protected area will flipped around to stop them using lag to grief into someone else's build

protector_show_interval
- Number of seconds the protection field is visible, defaults to 5 seconds.

Protector Tool

Can be crafted with a protector surrounded by steel ingots and is used to place new protectors at a set distance of protector.radius in all directions including up and down simply by looking in a direction.

Use by standing near an existing protector, looking in a direction and using as a tool, hold sneak/shift to place new protector containing member list from inside nearest one.


code:
local S = protector.intllib
local removal_names = ""
local replace_names = ""

minetest.register_chatcommand("protector_remove", {
	params = S("<names list>"),
	description = S("Remove Protectors around players (separate names with spaces)"),
	privs = {server = true},
	func = function(name, param)

		if not param or param == "" then

			minetest.chat_send_player(name,
				S("Protector Names to remove: @1",
					removal_names))

			return
		end

		if param == "-" then

			minetest.chat_send_player(name,
				S("Name List Reset"))

			removal_names = ""

			return
		end

		removal_names = param
	end,
})

minetest.register_chatcommand("protector_replace", {
	params = S("<owner name> <name to replace with>"),
	description = S("Replace Protector Owner with name provided"),
	privs = {server = true},
	func = function(name, param)

		-- reset list to empty
		if param == "-" then

			minetest.chat_send_player(name, S("Name List Reset"))

			replace_names = ""

			return
		end

		-- show name info
		if param == ""
		and replace_names ~= "" then

			local names = replace_names:split(" ")

			minetest.chat_send_player(name,
				S("Replacing Protector name @1 with @2",
					names[1] or "", names[2] or ""))

			return
		end

		replace_names = param
	end,
})

minetest.register_abm({
	nodenames = {"protector:protect", "protector:protect2", "protector:protect_hidden"},
	interval = 8,
	chance = 1,
	catch_up = false,
	action = function(pos, node)

		if removal_names == ""
		and replace_names == "" then
			return
		end

		local meta = minetest.get_meta(pos)

		if not meta then return end

		local owner = meta:get_string("owner")

		if removal_names ~= "" then

			local names = removal_names:split(" ")

			for _, n in pairs(names) do
				if n == owner then
					minetest.set_node(pos, {name = "air"})
				end
			end
		end

		if replace_names ~= "" then

			local names = replace_names:split(" ")

			if names[1] and names[2] and owner == names[1] then
				meta:set_string("owner", names[2])
				meta:set_string("infotext", S("Protection (owned by @1)", names[2]))
			end
		end
	end
})

-- get protection radius
local r = tonumber(minetest.settings:get("protector_radius")) or 5

-- show protection areas of nearby protectors owned by you (thanks agaran)
minetest.register_chatcommand("protector_show_area", {
	params = "",
	description = S("Show protected areas of your nearby protectors"),
	privs = {},
	func = function(name, param)

		local player = minetest.get_player_by_name(name)
		local pos = player:get_pos()

		-- find the protector nodes
		local pos = minetest.find_nodes_in_area(
			{x = pos.x - r, y = pos.y - r, z = pos.z - r},
			{x = pos.x + r, y = pos.y + r, z = pos.z + r},
			{"protector:protect", "protector:protect2", "protector:protect_hidden"})

		local meta, owner

		-- show a maximum of 5 protected areas only
		for n = 1, math.min(#pos, 5) do

			meta = minetest.get_meta(pos[n])
			owner = meta:get_string("owner") or ""

			if owner == name
			or minetest.check_player_privs(name, {protection_bypass = true}) then
				minetest.add_entity(pos[n], "protector:display")
			end
		end
	end
})

-- ability to hide protection blocks (borrowed from doors mod :)
minetest.register_node("protector:protect_hidden", {
	description = "Hidden Protector",
	drawtype = "airlike",
	paramtype = "light",
	paramtype2 = "facedir",
	sunlight_propagates = true,
	-- has to be walkable for falling nodes to stop falling
	walkable = true,
	pointable = false,
	diggable = false,
	buildable_to = false,
	floodable = false,
	drop = "",
	groups = {not_in_creative_inventory = 1, unbreakable = 1},
	on_blast = function() end,
	-- 1px block inside door hinge near node top
	collision_box = {
		type = "fixed",
		fixed = {-15/32, 13/32, -15/32, -13/32, 1/2, -13/32},
	},
})

minetest.register_chatcommand("protector_show", {
	params = "",
	description = S("Show your nearby protection blocks"),
	privs = {interact = true},

	func = function(name, param)

		local player = minetest.get_player_by_name(name)

		if not player then
			return false, "Player not found"
		end

		local pos = player:get_pos()

		local a = minetest.find_nodes_in_area(
			{x = pos.x - r, y = pos.y - r, z = pos.z - r},
			{x = pos.x + r, y = pos.y + r, z = pos.z + r},
			{"protector:protect_hidden"})

		local meta, owner

		for _, row in pairs(a) do

			meta = minetest.get_meta(row)
			owner = meta:get_string("owner") or ""

			if owner == name
			or minetest.check_player_privs(name, {protection_bypass = true}) then
				minetest.swap_node(row, {name = "protector:protect"})
			end
		end
	end
})

minetest.register_chatcommand("protector_hide", {
	params = "",
	description = S("Hide your nearby protection blocks"),
	privs = {interact = true},

	func = function(name, param)

		local player = minetest.get_player_by_name(name)

		if not player then
			return false, "Player not found"
		end

		local pos = player:get_pos()

		local a = minetest.find_nodes_in_area(
			{x = pos.x - r, y = pos.y - r, z = pos.z - r},
			{x = pos.x + r, y = pos.y + r, z = pos.z + r},
			{"protector:protect", "protector:protect2"})

		local meta, owner

		for _, row in pairs(a) do

			meta = minetest.get_meta(row)
			owner = meta:get_string("owner") or ""

			if owner == name
			or minetest.check_player_privs(name, {protection_bypass = true}) then
				minetest.swap_node(row, {name = "protector:protect_hidden"})
			end
		end
	end
})


code:
default?
intllib?
lucky_block?
mesecons_mvps?
playerfactions?
logging


code: Lets players craft special blocks to protect their builds or disable PVP in areas.


code:
-- Since the doors mod has changed in the latest daily builds I have taken the
-- WTFPL licenced code from the old doors mod and included an edited version
-- within this mod for local use.

local S = protector.intllib
local F = minetest.formspec_escape

-- MineClone2 support
local mcl = not minetest.registered_items["default:steel_ingot"]

-- Registers a door
function register_door(name, def)
	def.groups.not_in_creative_inventory = 1

	local box = {{-0.5, -0.5, -0.5, 0.5, 0.5, -0.5+1.5/16}}

	def.node_box_bottom = box
	def.node_box_top = box
	def.selection_box_bottom = box
	def.selection_box_top = box
	def.sound_close_door  = "doors_door_close"
	def.sound_open_door = "doors_door_open"

	minetest.register_craftitem(name, {
		description = def.description,
		inventory_image = def.inventory_image,

		on_place = function(itemstack, placer, pointed_thing)
			if not pointed_thing.type == "node" then
				return itemstack
			end

			local ptu = pointed_thing.under
			local nu = minetest.get_node(ptu)
			if minetest.registered_nodes[nu.name]
			and minetest.registered_nodes[nu.name].on_rightclick then
				return minetest.registered_nodes[nu.name].on_rightclick(ptu, nu, placer, itemstack)
			end

			local pt = pointed_thing.above
			local pt2 = {x=pt.x, y=pt.y, z=pt.z}
			pt2.y = pt2.y+1
			if
				not minetest.registered_nodes[minetest.get_node(pt).name].buildable_to or
				not minetest.registered_nodes[minetest.get_node(pt2).name].buildable_to or
				not placer or
				not placer:is_player()
			then
				return itemstack
			end

			if minetest.is_protected(pt, placer:get_player_name()) or
					minetest.is_protected(pt2, placer:get_player_name()) then
				minetest.record_protection_violation(pt, placer:get_player_name())
				return itemstack
			end

			local p2 = minetest.dir_to_facedir(placer:get_look_dir())
			local pt3 = {x=pt.x, y=pt.y, z=pt.z}
			if p2 == 0 then
				pt3.x = pt3.x-1
			elseif p2 == 1 then
				pt3.z = pt3.z+1
			elseif p2 == 2 then
				pt3.x = pt3.x+1
			elseif p2 == 3 then
				pt3.z = pt3.z-1
			end
			if minetest.get_item_group(minetest.get_node(pt3).name, "door") == 0 then
				minetest.set_node(pt, {name=name.."_b_1", param2=p2})
				minetest.set_node(pt2, {name=name.."_t_1", param2=p2})
			else
				minetest.set_node(pt, {name=name.."_b_2", param2=p2})
				minetest.set_node(pt2, {name=name.."_t_2", param2=p2})
				minetest.get_meta(pt):set_int("right", 1)
				minetest.get_meta(pt2):set_int("right", 1)
			end

			if not minetest.setting_getbool("creative_mode") then
				itemstack:take_item()
			end
			return itemstack
		end,
	})

	local tt = def.tiles_top
	local tb = def.tiles_bottom

	local function after_dig_node(pos, name, digger)
		local node = minetest.get_node(pos)
		if node.name == name then
			minetest.node_dig(pos, node, digger)
		end
	end

	local function on_rightclick(pos, dir, check_name, replace, replace_dir, params)
		pos.y = pos.y+dir
		if minetest.get_node(pos).name ~= check_name then
			return
		end
		local p2 = minetest.get_node(pos).param2
		p2 = params[p2+1]

		minetest.swap_node(pos, {name=replace_dir, param2=p2})

		pos.y = pos.y-dir
		minetest.swap_node(pos, {name=replace, param2=p2})

		local snd_1 = def.sound_close_door
		local snd_2 = def.sound_open_door
		if params[1] == 3 then
			snd_1 = def.sound_open_door
			snd_2 = def.sound_close_door
		end

		if minetest.get_meta(pos):get_int("right") ~= 0 then
			minetest.sound_play(snd_1, {pos = pos, gain = 0.3, max_hear_distance = 10})
		else
			minetest.sound_play(snd_2, {pos = pos, gain = 0.3, max_hear_distance = 10})
		end
	end

	local function on_rotate(pos, node, dir, user, check_name, mode, new_param2)

		if mode ~= screwdriver.ROTATE_FACE then
			return false
		end

		pos.y = pos.y + dir
		if not minetest.get_node(pos).name == check_name then
			return false
		end
		if minetest.is_protected(pos, user:get_player_name()) then
			minetest.record_protection_violation(pos, user:get_player_name())
			return false
		end

		local node2 = minetest.get_node(pos)
		node2.param2 = (node2.param2 + 1) % 4
		minetest.swap_node(pos, node2)

		pos.y = pos.y - dir
		node.param2 = (node.param2 + 1) % 4
		minetest.swap_node(pos, node)
		return true
	end

	minetest.register_node(name.."_b_1", {
		tiles = {tb[2], tb[2], tb[2], tb[2], tb[1], tb[1].."^[transformfx"},
		paramtype = "light",
		paramtype2 = "facedir",
		is_ground_content = false,
		drop = name,
		drawtype = "nodebox",
		node_box = {
			type = "fixed",
			fixed = def.node_box_bottom
		},
		selection_box = {
			type = "fixed",
			fixed = def.selection_box_bottom
		},
		groups = def.groups,

		after_dig_node = function(pos, oldnode, oldmetadata, digger)
			pos.y = pos.y+1
			after_dig_node(pos, name.."_t_1", digger)
		end,

		on_rightclick = function(pos, node, clicker)
			if not minetest.is_protected(pos, clicker:get_player_name()) then
				on_rightclick(pos, 1, name.."_t_1", name.."_b_2", name.."_t_2", {1,2,3,0})
			end
		end,

		on_rotate = function(pos, node, user, mode, new_param2)
			return on_rotate(pos, node, 1, user, name.."_t_1", mode)
		end,

		sounds = def.sounds,
		sunlight_propagates = def.sunlight,
		on_blast = function() end,
	})

	minetest.register_node(name.."_t_1", {
		tiles = {tt[2], tt[2], tt[2], tt[2], tt[1], tt[1].."^[transformfx"},
		paramtype = "light",
		paramtype2 = "facedir",
		is_ground_content = false,
		drop = "",
		drawtype = "nodebox",
		node_box = {
			type = "fixed",
			fixed = def.node_box_top
		},
		selection_box = {
			type = "fixed",
			fixed = def.selection_box_top
		},
		groups = def.groups,

		after_dig_node = function(pos, oldnode, oldmetadata, digger)
			pos.y = pos.y-1
			after_dig_node(pos, name.."_b_1", digger)
		end,

		on_rightclick = function(pos, node, clicker)
			if not minetest.is_protected(pos, clicker:get_player_name()) then
				on_rightclick(pos, -1, name.."_b_1", name.."_t_2", name.."_b_2", {1,2,3,0})
			end
		end,

		on_rotate = function(pos, node, user, mode, new_param2)
			return on_rotate(pos, node, -1, user, name.."_b_1", mode)
		end,

		sounds = def.sounds,
		sunlight_propagates = def.sunlight,
		on_blast = function() end,
	})

	minetest.register_node(name.."_b_2", {
		tiles = {tb[2], tb[2], tb[2], tb[2], tb[1].."^[transformfx", tb[1]},
		paramtype = "light",
		paramtype2 = "facedir",
		is_ground_content = false,
		drop = name,
		drawtype = "nodebox",
		node_box = {
			type = "fixed",
			fixed = def.node_box_bottom
		},
		selection_box = {
			type = "fixed",
			fixed = def.selection_box_bottom
		},
		groups = def.groups,

		after_dig_node = function(pos, oldnode, oldmetadata, digger)
			pos.y = pos.y+1
			after_dig_node(pos, name.."_t_2", digger)
		end,

		on_rightclick = function(pos, node, clicker)
			if not minetest.is_protected(pos, clicker:get_player_name()) then
				on_rightclick(pos, 1, name.."_t_2", name.."_b_1", name.."_t_1", {3,0,1,2})
			end
		end,

		on_rotate = function(pos, node, user, mode, new_param2)
			return on_rotate(pos, node, 1, user, name.."_t_2", mode)
		end,

		sounds = def.sounds,
		sunlight_propagates = def.sunlight,
		on_blast = function() end,
	})

	minetest.register_node(name.."_t_2", {
		tiles = {tt[2], tt[2], tt[2], tt[2], tt[1].."^[transformfx", tt[1]},
		paramtype = "light",
		paramtype2 = "facedir",
		is_ground_content = false,
		drop = "",
		drawtype = "nodebox",
		node_box = {
			type = "fixed",
			fixed = def.node_box_top
		},
		selection_box = {
			type = "fixed",
			fixed = def.selection_box_top
		},
		groups = def.groups,

		after_dig_node = function(pos, oldnode, oldmetadata, digger)
			pos.y = pos.y-1
			after_dig_node(pos, name.."_b_2", digger)
		end,

		on_rightclick = function(pos, node, clicker)
			if not minetest.is_protected(pos, clicker:get_player_name()) then
				on_rightclick(pos, -1, name.."_b_2", name.."_t_1", name.."_b_1", {3,0,1,2})
			end
		end,

		on_rotate = function(pos, node, user, mode, new_param2)
			return on_rotate(pos, node, -1, user, name.."_b_2", mode)
		end,

		sounds = def.sounds,
		sunlight_propagates = def.sunlight,
		on_blast = function() end,
	})

end

-- Protected Wooden Door

local name = "protector:door_wood"

register_door(name, {
	description = S("Protected Wooden Door"),
	inventory_image = "doors_wood.png^protector_logo.png",
	groups = {
		snappy = 1, choppy = 2, oddly_breakable_by_hand = 2,
		unbreakable = 1, --door = 1
	},
	tiles_bottom = {"doors_wood_b.png^protector_logo.png", "doors_brown.png"},
	tiles_top = {"doors_wood_a.png", "doors_brown.png"},
	sounds = default.node_sound_wood_defaults(),
	sunlight = false,
})

if mcl then
minetest.register_craft({
	output = name,
	recipe = {
		{"mcl_doors:wooden_door", "mcl_core:gold_ingot"}
	}
})
else
minetest.register_craft({
	output = name,
	recipe = {
		{"group:wood", "group:wood"},
		{"group:wood", "default:copper_ingot"},
		{"group:wood", "group:wood"}
	}
})

minetest.register_craft({
	output = name,
	recipe = {
		{"doors:door_wood", "default:copper_ingot"}
	}
})
end

-- Protected Steel Door

local name = "protector:door_steel"

register_door(name, {
	description = S("Protected Steel Door"),
	inventory_image = "doors_steel.png^protector_logo.png",
	groups = {
		snappy = 1, bendy = 2, cracky = 1,
		level = 2, unbreakable = 1, -- door = 1
	},
	tiles_bottom = {"doors_steel_b.png^protector_logo.png", "doors_grey.png"},
	tiles_top = {"doors_steel_a.png", "doors_grey.png"},
	sounds = default.node_sound_wood_defaults(),
	sunlight = false,
})

if mcl then
minetest.register_craft({
	output = name,
	recipe = {
		{"mcl_doors:iron_door", "mcl_core:gold_ingot"}
	}
})
else
minetest.register_craft({
	output = name,
	recipe = {
		{"default:steel_ingot", "default:steel_ingot"},
		{"default:steel_ingot", "default:copper_ingot"},
		{"default:steel_ingot", "default:steel_ingot"}
	}
})

minetest.register_craft({
	output = name,
	recipe = {
		{"doors:door_steel", "default:copper_ingot"}
	}
})
end

----trapdoor----

function register_trapdoor(name, def)
	local name_closed = name
	local name_opened = name.."_open"

	def.on_rightclick = function (pos, node, clicker, itemstack, pointed_thing)
		if minetest.is_protected(pos, clicker:get_player_name()) then
			return
		end
		local newname = node.name == name_closed and name_opened or name_closed
		local sound = false
		if node.name == name_closed then sound = "doors_door_open" end
		if node.name == name_opened then sound = "doors_door_close" end
		if sound then
			minetest.sound_play(sound, {pos = pos, gain = 0.3, max_hear_distance = 10})
		end
		minetest.swap_node(pos, {name = newname, param1 = node.param1, param2 = node.param2})
	end

	-- Common trapdoor configuration
	def.drawtype = "nodebox"
	def.paramtype = "light"
	def.paramtype2 = "facedir"
	def.is_ground_content = false

	local def_opened = table.copy(def)
	local def_closed = table.copy(def)

	def_closed.node_box = {
		type = "fixed",
		fixed = {-0.5, -0.5, -0.5, 0.5, -6/16, 0.5}
	}
	def_closed.selection_box = {
		type = "fixed",
		fixed = {-0.5, -0.5, -0.5, 0.5, -6/16, 0.5}
	}
	def_closed.tiles = { def.tile_front, def.tile_front, def.tile_side, def.tile_side,
		def.tile_side, def.tile_side }

	def_opened.node_box = {
		type = "fixed",
		fixed = {-0.5, -0.5, 6/16, 0.5, 0.5, 0.5}
	}
	def_opened.selection_box = {
		type = "fixed",
		fixed = {-0.5, -0.5, 6/16, 0.5, 0.5, 0.5}
	}
	def_opened.tiles = { def.tile_side, def.tile_side,
			def.tile_side .. "^[transform3",
			def.tile_side .. "^[transform1",
			def.tile_front, def.tile_front }

	def_opened.drop = name_closed
	def_opened.groups.not_in_creative_inventory = 1

	minetest.register_node(name_opened, def_opened)
	minetest.register_node(name_closed, def_closed)
end

-- Protected Wooden Trapdoor

register_trapdoor("protector:trapdoor", {
	description = S("Protected Trapdoor"),
	inventory_image = "doors_trapdoor.png^protector_logo.png",
	wield_image = "doors_trapdoor.png^protector_logo.png",
	tile_front = "doors_trapdoor.png^protector_logo.png",
	tile_side = "doors_trapdoor_side.png",
	groups = {
		snappy = 1, choppy = 2, oddly_breakable_by_hand = 2,
		unbreakable = 1, --door = 1
	},
	sounds = default.node_sound_wood_defaults(),
})

if mcl then
minetest.register_craft({
	output = "protector:trapdoor",
	recipe = {
		{"mcl_doors:trapdoor", "mcl_core:gold_ingot"}
	}
})
else
minetest.register_craft({
	output = "protector:trapdoor 2",
	recipe = {
		{"group:wood", "default:copper_ingot", "group:wood"},
		{"group:wood", "group:wood", "group:wood"},
	}
})

minetest.register_craft({
	output = "protector:trapdoor",
	recipe = {
		{"doors:trapdoor", "default:copper_ingot"}
	}
})
end

-- Protected Steel Trapdoor

register_trapdoor("protector:trapdoor_steel", {
	description = S("Protected Steel Trapdoor"),
	inventory_image = "doors_trapdoor_steel.png^protector_logo.png",
	wield_image = "doors_trapdoor_steel.png^protector_logo.png",
	tile_front = "doors_trapdoor_steel.png^protector_logo.png",
	tile_side = "doors_trapdoor_steel_side.png",
	groups = {
		snappy = 1, bendy = 2, cracky = 1, melty = 2, level = 2,
		unbreakable = 1, --door = 1
	},
	sounds = default.node_sound_wood_defaults(),
})

if mcl then
minetest.register_craft({
	output = "protector:trapdoor_steel",
	recipe = {
		{"mcl_doors:iron_trapdoor", "mcl_core:gold_ingot"}
	}
})
else
minetest.register_craft({
	output = "protector:trapdoor_steel",
	recipe = {
		{"default:copper_ingot", "default:steel_ingot"},
		{"default:steel_ingot", "default:steel_ingot"},
	}
})

minetest.register_craft({
	output = "protector:trapdoor_steel",
	recipe = {
		{"doors:trapdoor_steel", "default:copper_ingot"}
	}
})
end

-- Protected Chest

minetest.register_node("protector:chest", {
	description = S("Protected Chest"),
	tiles = {
		"default_chest_top.png", "default_chest_top.png",
		"default_chest_side.png", "default_chest_side.png",
		"default_chest_side.png", "default_chest_front.png^protector_logo.png"
	},
	paramtype2 = "facedir",
	groups = {choppy = 2, oddly_breakable_by_hand = 2, unbreakable = 1},
	legacy_facedir_simple = true,
	is_ground_content = false,
	sounds = default.node_sound_wood_defaults(),

	on_construct = function(pos)

		local meta = minetest.get_meta(pos)
		local inv = meta:get_inventory()

		meta:set_string("infotext", S("Protected Chest"))
		meta:set_string("name", "")
		inv:set_size("main", 8 * 4)
	end,

	can_dig = function(pos,player)

		local meta = minetest.get_meta(pos)
		local inv = meta:get_inventory()

		if inv:is_empty("main") then

			if not minetest.is_protected(pos, player:get_player_name()) then
				return true
			end
		end
	end,

	on_metadata_inventory_put = function(pos, listname, index, stack, player)

		minetest.log("action", player:get_player_name() ..
			" moves stuff to protected chest at " ..
			minetest.pos_to_string(pos))
	end,

	on_metadata_inventory_take = function(pos, listname, index, stack, player)

		minetest.log("action", player:get_player_name() ..
			" takes stuff from protected chest at " ..
			minetest.pos_to_string(pos))
	end,

	on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)

		minetest.log("action", player:get_player_name() ..
			" moves stuff inside protected chest at " ..
			minetest.pos_to_string(pos))
	end,

	allow_metadata_inventory_put = function(pos, listname, index, stack, player)

		if minetest.is_protected(pos, player:get_player_name()) then
			return 0
		end

		return stack:get_count()
	end,

	allow_metadata_inventory_take = function(pos, listname, index, stack, player)

		if minetest.is_protected(pos, player:get_player_name()) then
			return 0
		end

		return stack:get_count()
	end,

	allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)

		if minetest.is_protected(pos, player:get_player_name()) then
			return 0
		end

		return count
	end,

	on_rightclick = function(pos, node, clicker)

		if minetest.is_protected(pos, clicker:get_player_name()) then
			return
		end

		local meta = minetest.get_meta(pos)

		if not meta then
			return
		end

		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;]"
			.. "button[0,4.5;2,0.25;toup;" .. F(S("To Chest")) .. "]"
			.. "field[2.3,4.8;4,0.25;chestname;;"
			.. meta:get_string("name") .. "]"
			.. "button[6,4.5;2,0.25;todn;" .. F(S("To Inventory")) .. "]"
			.. "list[current_player;main;0,5;8,1;]"
			.. "list[current_player;main;0,6.08;8,3;8]"
			.. "listring[nodemeta:" .. spos .. ";main]"
			.. "listring[current_player;main]"

			minetest.show_formspec(
				clicker:get_player_name(),
				"protector:chest_" .. minetest.pos_to_string(pos),
				formspec)
	end,

	on_blast = function() end,
})

-- Protected Chest formspec buttons

minetest.register_on_player_receive_fields(function(player, formname, fields)

	if string.sub(formname, 0, string.len("protector:chest_")) ~= "protector:chest_" then
		return
	end

	local pos_s = string.sub(formname,string.len("protector:chest_") + 1)
	local pos = minetest.string_to_pos(pos_s)

	if minetest.is_protected(pos, player:get_player_name()) then
		return
	end

	local meta = minetest.get_meta(pos) ; if not meta then return end
	local chest_inv = meta:get_inventory() ; if not chest_inv then return end
	local player_inv = player:get_inventory()
	local leftover

	if fields.toup then

		-- copy contents of players inventory to chest
		for i, v in ipairs(player_inv:get_list("main") or {}) do

			if chest_inv:room_for_item("main", v) then

				leftover = chest_inv:add_item("main", v)

				player_inv:remove_item("main", v)

				if leftover
				and not leftover:is_empty() then
					player_inv:add_item("main", v)
				end
			end
		end

	elseif fields.todn then

		-- copy contents of chest to players inventory
		for i, v in ipairs(chest_inv:get_list("main") or {}) do

			if player_inv:room_for_item("main", v) then

				leftover = player_inv:add_item("main", v)

				chest_inv:remove_item("main", v)

				if leftover
				and not leftover:is_empty() then
					chest_inv:add_item("main", v)
				end
			end
		end

	elseif fields.chestname then

		-- change chest infotext to display name
		if fields.chestname ~= "" then

			meta:set_string("name", fields.chestname)
			meta:set_string("infotext",
				S("Protected Chest (@1)", fields.chestname))
		else
			meta:set_string("infotext", S("Protected Chest"))
		end

	end
end)

-- Protected Chest recipes

if mcl then
minetest.register_craft({
	output = "protector:chest",
	recipe = {
		{"mcl_chests:chest", "mcl_core:gold_ingot"},
	}
})
else
minetest.register_craft({
	output = "protector:chest",
	recipe = {
		{"group:wood", "group:wood", "group:wood"},
		{"group:wood", "default:copper_ingot", "group:wood"},
		{"group:wood", "group:wood", "group:wood"},
	}
})

minetest.register_craft({
	output = "protector:chest",
	recipe = {
		{"default:chest", "default:copper_ingot"},
	}
})
end