code:
local digilines_path = minetest.get_modpath("digilines")

local S = technic.getter
local tube_entry = "^pipeworks_tube_connection_metallic.png"
local cable_entry = "^technic_cable_connection_overlay.png"

local fs_helpers = pipeworks.fs_helpers

technic.register_power_tool("technic:battery", 10000)
technic.register_power_tool("technic:red_energy_crystal", 50000)
technic.register_power_tool("technic:green_energy_crystal", 150000)
technic.register_power_tool("technic:blue_energy_crystal", 450000)

-- Battery recipes:
-- Tin-copper recipe:
minetest.register_craft({
	output = "technic:battery",
	recipe = {
		{"group:wood", "default:copper_ingot", "group:wood"},
		{"group:wood", "default:tin_ingot",    "group:wood"},
		{"group:wood", "default:copper_ingot", "group:wood"},
	}
})
-- Sulfur-lead-water recipes:
-- With sulfur lumps:
-- With water:
minetest.register_craft({
	output = "technic:battery",
	recipe = {
		{"group:wood",         "technic:sulfur_lump", "group:wood"},
		{"technic:lead_ingot", "bucket:bucket_water", "technic:lead_ingot"},
		{"group:wood",         "technic:sulfur_lump", "group:wood"},
	},
	replacements = {
		{"bucket:bucket_water", "bucket:bucket_empty"}
	}
})
-- With oil extract:
minetest.register_craft({
	output = "technic:battery",
	recipe = {
		{"group:wood",         "technic:sulfur_lump",   "group:wood"},
		{"technic:lead_ingot", "homedecor:oil_extract", "technic:lead_ingot"},
		{"group:wood",         "technic:sulfur_lump",   "group:wood"},
	}
})
-- With sulfur dust:
-- With water:
minetest.register_craft({
	output = "technic:battery",
	recipe = {
		{"group:wood",         "technic:sulfur_dust", "group:wood"},
		{"technic:lead_ingot", "bucket:bucket_water", "technic:lead_ingot"},
		{"group:wood",         "technic:sulfur_dust", "group:wood"},
	},
	replacements = {
		{"bucket:bucket_water", "bucket:bucket_empty"}
	}
})
-- With oil extract:
minetest.register_craft({
	output = "technic:battery",
	recipe = {
		{"group:wood",         "technic:sulfur_dust",   "group:wood"},
		{"technic:lead_ingot", "homedecor:oil_extract", "technic:lead_ingot"},
		{"group:wood",         "technic:sulfur_dust",   "group:wood"},
	}
})

minetest.register_tool("technic:battery", {
	description = S("RE Battery"),
	inventory_image = "technic_battery.png",
	wear_represents = "technic_RE_charge",
	on_refill = technic.refill_RE_charge,
	tool_capabilities = {
		charge = 0,
		max_drop_level = 0,
		groupcaps = {
			fleshy = {times={}, uses=10000, maxlevel=0}
		}
	}
})

-- x+2 + (z+2)*2
local dirtab = {
	[4] = 2,
	[5] = 3,
	[7] = 1,
	[8] = 0
}

local tube = {
	insert_object = function(pos, node, stack, direction)
		if direction.y == 1
			or (direction.y == 0 and dirtab[direction.x+2+(direction.z+2)*2] == node.param2) then
			return stack
		end
		local meta = minetest.get_meta(pos)
		local inv = meta:get_inventory()
		if direction.y == 0 then
			return inv:add_item("src", stack)
		else
			return inv:add_item("dst", stack)
		end
	end,
	can_insert = function(pos, node, stack, direction)
		if direction.y == 1
			or (direction.y == 0 and dirtab[direction.x+2+(direction.z+2)*2] == node.param2) then
			return false
		end
		local meta = minetest.get_meta(pos)
		local inv = meta:get_inventory()
		if direction.y == 0 then
			if meta:get_int("split_src_stacks") == 1 then
				stack = stack:peek_item(1)
			end
			return inv:room_for_item("src", stack)
		else
			if meta:get_int("split_dst_stacks") == 1 then
				stack = stack:peek_item(1)
			end
			return inv:room_for_item("dst", stack)
		end
	end,
	connect_sides = {left=1, right=1, back=1, top=1},
}

local function add_on_off_buttons(meta, ltier, charge_percent)
	local formspec = "image[1,1;1,2;technic_power_meter_bg.png"
			.."^[lowpart:"..charge_percent
			..":technic_power_meter_fg.png]"
	if ltier == "mv" or ltier == "hv" then
		formspec = formspec..
			fs_helpers.cycling_button(
				meta,
				"image_button[3,2.0;1,0.6",
				"split_src_stacks",
				{
					pipeworks.button_off,
					pipeworks.button_on
				}
			).."label[3.9,2.01;Allow splitting incoming 'charge' stacks from tubes]"..
			fs_helpers.cycling_button(
				meta,
				"image_button[3,2.5;1,0.6",
				"split_dst_stacks",
				{
					pipeworks.button_off,
					pipeworks.button_on
				}
			).."label[3.9,2.51;Allow splitting incoming 'discharge' stacks]"
	end
	return formspec
end

function technic.register_battery_box(data)
	local tier = data.tier
	local ltier = string.lower(tier)

	local formspec =
		"size[8,9]"..
		"image[1,1;1,2;technic_power_meter_bg.png]"..
		"list[context;src;3,1;1,1;]"..
		"image[4,1;1,1;technic_battery_reload.png]"..
		"list[context;dst;5,1;1,1;]"..
		"label[0,0;"..S("%s Battery Box"):format(tier).."]"..
		"label[3,0;"..S("Charge").."]"..
		"label[5,0;"..S("Discharge").."]"..
		"label[1,3;"..S("Power level").."]"..
		"list[current_player;main;0,5;8,4;]"..
		"listring[context;dst]"..
		"listring[current_player;main]"..
		"listring[context;src]"..
		"listring[current_player;main]"

	if digilines_path then
		formspec = formspec.."button[0.6,3.7;2,1;edit_channel;edit Channel]"
	end

	if data.upgrade then
		formspec = formspec..
			"list[context;upgrade1;3.5,3;1,1;]"..
			"list[context;upgrade2;4.5,3;1,1;]"..
			"label[3.5,4;"..S("Upgrade Slots").."]"..
			"listring[context;upgrade1]"..
			"listring[current_player;main]"..
			"listring[context;upgrade2]"..
			"listring[current_player;main]"
	end

	local run = function(pos, node)
		local below = minetest.get_node({x=pos.x, y=pos.y-1, z=pos.z})
		local meta           = minetest.get_meta(pos)

		if not technic.is_tier_cable(below.name, tier) then
			meta:set_string("infotext", S("%s Battery Box Has No Network"):format(tier))
			return
		end

		local eu_input       = meta:get_int(tier.."_EU_input")
		local current_charge = meta:get_int("internal_EU_charge")

		local EU_upgrade, tube_upgrade = 0, 0
		if data.upgrade then
			EU_upgrade, tube_upgrade = technic.handle_machine_upgrades(meta)
		end
		local max_charge = data.max_charge * (1 + EU_upgrade / 10)

		-- Charge/discharge the battery with the input EUs
		if eu_input >= 0 then
			current_charge = math.min(current_charge + eu_input, max_charge)
		else
			current_charge = math.max(current_charge + eu_input, 0)
		end

		-- Charging/discharging tools here
		local tool_full, tool_empty
		current_charge, tool_full = technic.charge_tools(meta,
				current_charge, data.charge_step)
		current_charge, tool_empty = technic.discharge_tools(meta,
				current_charge, data.discharge_step,
				max_charge)

		if data.tube then
			local inv = meta:get_inventory()
			technic.handle_machine_pipeworks(pos, tube_upgrade,
			function(pos, x_velocity, z_velocity)
				if tool_full and not inv:is_empty("src") then
					technic.send_items(pos, x_velocity, z_velocity, "src")
				elseif tool_empty and not inv:is_empty("dst") then
					technic.send_items(pos, x_velocity, z_velocity, "dst")
				end
			end)
		end

		-- We allow batteries to charge on less than the demand
		meta:set_int(tier.."_EU_demand",
				math.min(data.charge_rate, max_charge - current_charge))
		meta:set_int(tier.."_EU_supply",
				math.min(data.discharge_rate, current_charge))
			meta:set_int("internal_EU_charge", current_charge)
		meta:set_int("internal_EU_charge_max", max_charge)

		-- Select node textures
		local charge_count = math.ceil((current_charge / max_charge) * 8)
		charge_count = math.min(charge_count, 8)
		charge_count = math.max(charge_count, 0)
		local last_count = meta:get_float("last_side_shown")
		if charge_count ~= last_count then
			technic.swap_node(pos,"technic:"..ltier.."_battery_box"..charge_count)
			meta:set_float("last_side_shown", charge_count)
		end

		local charge_percent = math.floor(current_charge / max_charge * 100)
		meta:set_string("formspec", formspec..add_on_off_buttons(meta, ltier, charge_percent))
		local infotext = S("@1 Battery Box: @2 / @3", tier,
				technic.EU_string(current_charge),
				technic.EU_string(max_charge))
		if eu_input == 0 then
			infotext = S("%s Idle"):format(infotext)
		end
		meta:set_string("infotext", infotext)
	end

	for i = 0, 8 do
		local groups = {snappy=2, choppy=2, oddly_breakable_by_hand=2,
				technic_machine=1, ["technic_"..ltier]=1}
		if i ~= 0 then
			groups.not_in_creative_inventory = 1
		end

		if data.tube then
			groups.tubedevice = 1
			groups.tubedevice_receiver = 1
		end

		local top_tex = "technic_"..ltier.."_battery_box_top.png"..tube_entry
		local front_tex = "technic_"..ltier.."_battery_box_front.png^technic_power_meter"..i..".png"
		local side_tex = "technic_"..ltier.."_battery_box_side.png"..tube_entry
		local bottom_tex = "technic_"..ltier.."_battery_box_bottom.png"..cable_entry

		if ltier == "lv" then
			top_tex = "technic_"..ltier.."_battery_box_top.png"
			front_tex = "technic_"..ltier.."_battery_box_side.png^technic_power_meter"..i..".png"
			side_tex = "technic_"..ltier.."_battery_box_side.png^technic_power_meter"..i..".png"
		end

		minetest.register_node("technic:"..ltier.."_battery_box"..i, {
			description = S("%s Battery Box"):format(tier),
			tiles = {
				top_tex,
				bottom_tex,
				side_tex,
				side_tex,
				side_tex,
				front_tex},
			groups = groups,
			connect_sides = {"bottom"},
			tube = data.tube and tube or nil,
			paramtype2 = "facedir",
			sounds = default.node_sound_wood_defaults(),
			drop = "technic:"..ltier.."_battery_box0",
			on_construct = function(pos)
				local meta = minetest.get_meta(pos)
				local EU_upgrade, tube_upgrade = 0, 0
				if data.upgrade then
					EU_upgrade, tube_upgrade = technic.handle_machine_upgrades(meta)
				end
				local max_charge = data.max_charge * (1 + EU_upgrade / 10)
				local charge = meta:get_int("internal_EU_charge")
				local cpercent = math.floor(charge / max_charge * 100)
				local inv = meta:get_inventory()
				meta:set_string("infotext", S("%s Battery Box"):format(tier))
				meta:set_string("formspec", formspec..add_on_off_buttons(meta, ltier, cpercent))
				meta:set_string("channel", ltier.."_battery_box"..minetest.pos_to_string(pos))
				meta:set_int(tier.."_EU_demand", 0)
				meta:set_int(tier.."_EU_supply", 0)
				meta:set_int(tier.."_EU_input",  0)
				meta:set_float("internal_EU_charge", 0)
				inv:set_size("src", 1)
				inv:set_size("dst", 1)
				inv:set_size("upgrade1", 1)
				inv:set_size("upgrade2", 1)
			end,
			can_dig = technic.machine_can_dig,
			allow_metadata_inventory_put = technic.machine_inventory_put,
			allow_metadata_inventory_take = technic.machine_inventory_take,
			allow_metadata_inventory_move = technic.machine_inventory_move,
			technic_run = run,
			on_rotate = screwdriver.rotate_simple,
			after_place_node = data.tube and pipeworks.after_place,
			after_dig_node = technic.machine_after_dig_node,
			on_receive_fields = function(pos, formname, fields, sender)
				local meta = minetest.get_meta(pos)
				if fields.edit_channel then
					minetest.show_formspec(sender:get_player_name(),
						"technic:battery_box_edit_channel"..minetest.pos_to_string(pos),
						"field[channel;Digiline Channel;"..meta:get_string("channel").."]")
				elseif fields["fs_helpers_cycling:0:split_src_stacks"]
				  or   fields["fs_helpers_cycling:0:split_dst_stacks"]
				  or   fields["fs_helpers_cycling:1:split_src_stacks"]
				  or   fields["fs_helpers_cycling:1:split_dst_stacks"] then
					meta = minetest.get_meta(pos)
					if not pipeworks.may_configure(pos, sender) then return end
					fs_helpers.on_receive_fields(pos, fields)
					local EU_upgrade, tube_upgrade = 0, 0
					if data.upgrade then
						EU_upgrade, tube_upgrade = technic.handle_machine_upgrades(meta)
					end
					local max_charge = data.max_charge * (1 + EU_upgrade / 10)
					local charge = meta:get_int("internal_EU_charge")
					local cpercent = math.floor(charge / max_charge * 100)
					meta:set_string("formspec", formspec..add_on_off_buttons(meta, ltier, cpercent))
				end
			end,
			digiline = {
				receptor = {
					rules = technic.digilines.rules,
					action = function() end
				},
				effector = {
					rules = technic.digilines.rules,
					action = function(pos, node, channel, msg)
						if msg ~= "GET" and msg ~= "get" then
							return
						end
						local meta = minetest.get_meta(pos)
						if channel ~= meta:get_string("channel") then
							return
						end
						local inv = meta:get_inventory()
						digilines.receptor_send(pos, technic.digilines.rules, channel, {
							demand = meta:get_int(tier.."_EU_demand"),
							supply = meta:get_int(tier.."_EU_supply"),
							input  = meta:get_int(tier.."_EU_input"),
							charge = meta:get_int("internal_EU_charge"),
							max_charge = data.max_charge * (1 + technic.handle_machine_upgrades(meta) / 10),
							src      = inv:get_stack("src", 1):to_table(),
							dst      = inv:get_stack("dst", 1):to_table(),
							upgrade1 = inv:get_stack("upgrade1", 1):to_table(),
							upgrade2 = inv:get_stack("upgrade2", 1):to_table()
						})
					end
				},
			},
		})
	end

	-- Register as a battery type
	-- Battery type machines function as power reservoirs and can both receive and give back power
	for i = 0, 8 do
		technic.register_machine(tier, "technic:"..ltier.."_battery_box"..i, technic.battery)
	end

end -- End registration

minetest.register_on_player_receive_fields(
	function(player, formname, fields)
		if formname:sub(1, 32) ~= "technic:battery_box_edit_channel" or
				not fields.channel then
			return
		end
		local pos = minetest.string_to_pos(formname:sub(33))
		local plname = player:get_player_name()
		if minetest.is_protected(pos, plname) then
			minetest.record_protection_violation(pos, plname)
			return
		end
		local meta = minetest.get_meta(pos)
		meta:set_string("channel", fields.channel)
	end
)

function technic.charge_tools(meta, batt_charge, charge_step)
	local inv = meta:get_inventory()
	if inv:is_empty("src") then
		return batt_charge, false
	end
	local src_stack = inv:get_stack("src", 1)

	local tool_name = src_stack:get_name()
	if not technic.power_tools[tool_name] then
		return batt_charge, false
	end
	-- Set meta data for the tool if it didn't do it itself
	local src_meta = minetest.deserialize(src_stack:get_metadata()) or {}
	if not src_meta.charge then
		src_meta.charge = 0
	end
	-- Do the charging
	local item_max_charge = technic.power_tools[tool_name]
	local tool_charge     = src_meta.charge
	if tool_charge >= item_max_charge then
		return batt_charge, true
	elseif batt_charge <= 0 then
		return batt_charge, false
	end
	charge_step = math.min(charge_step, batt_charge)
	charge_step = math.min(charge_step, item_max_charge - tool_charge)
	tool_charge = tool_charge + charge_step
	batt_charge = batt_charge - charge_step
	technic.set_RE_wear(src_stack, tool_charge, item_max_charge)
	src_meta.charge = tool_charge
	src_stack:set_metadata(minetest.serialize(src_meta))
	inv:set_stack("src", 1, src_stack)
	return batt_charge, (tool_charge == item_max_charge)
end

function technic.discharge_tools(meta, batt_charge, charge_step, max_charge)
	local inv = meta:get_inventory()
	if inv:is_empty("dst") then
		return batt_charge, false
	end
	local srcstack = inv:get_stack("dst", 1)
	local toolname = srcstack:get_name()
	if technic.power_tools[toolname] == nil then
		return batt_charge, false
	end
	-- Set meta data for the tool if it didn't do it itself :-(
	local src_meta = minetest.deserialize(srcstack:get_metadata())
	src_meta = src_meta or {}
	if not src_meta.charge then
		src_meta.charge = 0
	end

	-- Do the discharging
	local item_max_charge = technic.power_tools[toolname]
	local tool_charge     = src_meta.charge
	if tool_charge <= 0 then
		return batt_charge, true
	elseif batt_charge >= max_charge then
		return batt_charge, false
	end
	charge_step = math.min(charge_step, max_charge - batt_charge)
	charge_step = math.min(charge_step, tool_charge)
	tool_charge = tool_charge - charge_step
	batt_charge = batt_charge + charge_step
	technic.set_RE_wear(srcstack, tool_charge, item_max_charge)
	src_meta.charge = tool_charge
	srcstack:set_metadata(minetest.serialize(src_meta))
	inv:set_stack("dst", 1, srcstack)
	return batt_charge, (tool_charge == 0)
end


code:
local S = technic.getter

local cable_tier = {}

function technic.is_tier_cable(name, tier)
	return cable_tier[name] == tier
end

function technic.get_cable_tier(name)
	return cable_tier[name]
end

local function check_connections(pos)
	-- Build a table of all machines
	local machines = {}
	for tier,list in pairs(technic.machines) do
		for k,v in pairs(list) do
			machines[k] = v
		end
	end
	local connections = {}
	local positions = {
		{x=pos.x+1, y=pos.y,   z=pos.z},
		{x=pos.x-1, y=pos.y,   z=pos.z},
		{x=pos.x,   y=pos.y+1, z=pos.z},
		{x=pos.x,   y=pos.y-1, z=pos.z},
		{x=pos.x,   y=pos.y,   z=pos.z+1},
		{x=pos.x,   y=pos.y,   z=pos.z-1}}
	for _,connected_pos in pairs(positions) do
		local name = minetest.get_node(connected_pos).name
		if machines[name] or technic.get_cable_tier(name) then
			table.insert(connections,connected_pos)
		end
	end
	return connections
end

local function clear_networks(pos)
	local node = minetest.get_node(pos)
	local meta = minetest.get_meta(pos)
	local placed = node.name ~= "air"
	local positions = check_connections(pos)
	if #positions < 1 then return end
	local dead_end = #positions == 1
	for _,connected_pos in pairs(positions) do
		local net = technic.cables[minetest.hash_node_position(connected_pos)]
		if net and technic.networks[net] then
			if dead_end and placed then
				-- Dead end placed, add it to the network
				-- Get the network
				local network_id = technic.cables[minetest.hash_node_position(positions[1])]
				if not network_id then
					-- We're evidently not on a network, nothing to add ourselves to
					return
				end
				local sw_pos = minetest.get_position_from_hash(network_id)
				sw_pos.y = sw_pos.y + 1
				local network = technic.networks[network_id]
				local tier = network.tier

				-- Actually add it to the (cached) network
				-- This is similar to check_node_subp
				technic.cables[minetest.hash_node_position(pos)] = network_id
				pos.visited = 1
				if technic.is_tier_cable(name, tier) then
					table.insert(network.all_nodes,pos)
				elseif technic.machines[tier][node.name] then
					meta:set_string(tier.."_network",minetest.pos_to_string(sw_pos))
					if     technic.machines[tier][node.name] == technic.producer then
						table.insert(network.PR_nodes,pos)
					elseif technic.machines[tier][node.name] == technic.receiver then
						table.insert(network.RE_nodes,pos)
					elseif technic.machines[tier][node.name] == technic.producer_receiver then
						table.insert(network.PR_nodes,pos)
						table.insert(network.RE_nodes,pos)
					elseif technic.machines[tier][node.name] == "SPECIAL" and
							(pos.x ~= sw_pos.x or pos.y ~= sw_pos.y or pos.z ~= sw_pos.z) and
							from_below then
						table.insert(network.SP_nodes,pos)
					elseif technic.machines[tier][node.name] == technic.battery then
						table.insert(network.BA_nodes,pos)
					end
				end
			elseif dead_end and not placed then
				-- Dead end removed, remove it from the network
				-- Get the network
				local network_id = technic.cables[minetest.hash_node_position(positions[1])]
				if not network_id then
					-- We're evidently not on a network, nothing to add ourselves to
					return
				end
				local network = technic.networks[network_id]

				-- Search for and remove machine
				technic.cables[minetest.hash_node_position(pos)] = nil
				for tblname,table in pairs(network) do
					if tblname ~= "tier" then
						for machinenum,machine in pairs(table) do
							if machine.x == pos.x
							and machine.y == pos.y
							and machine.z == pos.z then
								table[machinenum] = nil
							end
						end
					end
				end
			else
				-- Not a dead end, so the whole network needs to be recalculated
				for _,v in pairs(technic.networks[net].all_nodes) do
					local pos1 = minetest.hash_node_position(v)
					technic.cables[pos1] = nil
				end
				technic.networks[net] = nil
			end
		end
	end
end

local function item_place_override_node(itemstack, placer, pointed, node)
	-- Call the default on_place function with a fake itemstack
	local temp_itemstack = ItemStack(itemstack)
	temp_itemstack:set_name(node.name)
	local original_count = temp_itemstack:get_count()
	temp_itemstack =
		minetest.item_place(temp_itemstack, placer, pointed, node.param2) or
		temp_itemstack
	-- Remove the same number of items from the real itemstack
	itemstack:take_item(original_count - temp_itemstack:get_count())
	return itemstack
end

local function override_table(target, source)
	if target and source then
		for k,v in pairs(source) do
			target[k] = v
		end
	end
	return target
end

function technic.register_cable(tier, size, description, prefix, override_cable, override_cable_plate)
	prefix = prefix or ""
	override_cable_plate = override_cable_plate or override_cable
	local ltier = string.lower(tier)
	cable_tier["technic:"..ltier..prefix.."_cable"] = tier

	local groups = {snappy=2, choppy=2, oddly_breakable_by_hand=2,
			["technic_"..ltier.."_cable"] = 1}

	local node_box = {
		type = "connected",
		fixed          = {-size, -size, -size, size,  size, size},
		connect_top    = {-size, -size, -size, size,  0.5,  size}, -- y+
		connect_bottom = {-size, -0.5,  -size, size,  size, size}, -- y-
		connect_front  = {-size, -size, -0.5,  size,  size, size}, -- z-
		connect_back   = {-size, -size,  size, size,  size, 0.5 }, -- z+
		connect_left   = {-0.5,  -size, -size, size,  size, size}, -- x-
		connect_right  = {-size, -size, -size, 0.5,   size, size}, -- x+
	}

	minetest.register_node("technic:"..ltier..prefix.."_cable", override_table({
		description = S("%s Cable"):format(tier),
		tiles = {"technic_"..ltier..prefix.."_cable.png"},
		inventory_image = "technic_"..ltier..prefix.."_cable_wield.png",
		wield_image = "technic_"..ltier..prefix.."_cable_wield.png",
		groups = groups,
		sounds = default.node_sound_wood_defaults(),
		drop = "technic:"..ltier..prefix.."_cable",
		paramtype = "light",
		sunlight_propagates = true,
		drawtype = "nodebox",
		node_box = node_box,
		connects_to = {"group:technic_"..ltier.."_cable",
			"group:technic_"..ltier, "group:technic_all_tiers"},
		on_construct = clear_networks,
		on_destruct = clear_networks,
	}, override_cable))

	local xyz = {
		["-x"] = 1,
		["-y"] = 2,
		["-z"] = 3,
		["x"] = 4,
		["y"] = 5,
		["z"] = 6,
	}
	local notconnects = {
		[1] = "left",
		[2] = "bottom",
		[3] = "front",
		[4] = "right",
		[5] = "top",
		[6] = "back",
	}
	local function s(p)
		if p:find("-") then
			return p:sub(2)
		else
			return "-"..p
		end
	end
	for p, i in pairs(xyz) do
		local def = {
			description = S("%s Cable Plate"):format(tier),
			tiles = {"technic_"..ltier..prefix.."_cable.png"},
			groups = table.copy(groups),
			sounds = default.node_sound_wood_defaults(),
			drop = "technic:"..ltier..prefix.."_cable_plate_1",
			paramtype = "light",
			sunlight_propagates = true,
			drawtype = "nodebox",
			node_box = table.copy(node_box),
			connects_to = {"group:technic_"..ltier.."_cable",
				"group:technic_"..ltier, "group:technic_all_tiers"},
			on_construct = clear_networks,
			on_destruct = clear_networks,
		}
		def.node_box.fixed = {
			{-size, -size, -size, size, size, size},
			{-0.5, -0.5, -0.5, 0.5, 0.5, 0.5}
		}
		def.node_box.fixed[1][xyz[p]] = 7/16 * (i-3.5)/math.abs(i-3.5)
		def.node_box.fixed[2][xyz[s(p)]] = 3/8 * (i-3.5)/math.abs(i-3.5)
		def.node_box["connect_"..notconnects[i]] = nil
		if i == 1 then
			def.on_place = function(itemstack, placer, pointed_thing)
				local pointed_thing_diff = vector.subtract(pointed_thing.above, pointed_thing.under)
				local num = 1
				local changed
				for k, v in pairs(pointed_thing_diff) do
					if v ~= 0 then
						changed = k
						num = xyz[s(tostring(v):sub(-2, -2)..k)]
						break
					end
				end
				local crtl = placer:get_player_control()
				if (crtl.aux1 or crtl.sneak) and not (crtl.aux1 and crtl.sneak) and changed then
					local fine_pointed = minetest.pointed_thing_to_face_pos(placer, pointed_thing)
					fine_pointed = vector.subtract(fine_pointed, pointed_thing.above)
					fine_pointed[changed] = nil
					local ps = {}
					for p, _ in pairs(fine_pointed) do
						ps[#ps+1] = p
					end
					local bigger = (math.abs(fine_pointed[ps[1]]) > math.abs(fine_pointed[ps[2]]) and ps[1]) or ps[2]
					if math.abs(fine_pointed[bigger]) < 0.3 then
						num = num + 3
						num = (num <= 6 and num) or num - 6
					else
						num = xyz[((fine_pointed[bigger] < 0 and "-") or "") .. bigger]
					end
				end
				if num == nil then num = 1 end
				return item_place_override_node(
					itemstack, placer, pointed_thing,
					{name = "technic:"..ltier..prefix.."_cable_plate_"..num}
				)
			end
		else
			def.groups.not_in_creative_inventory = 1
		end
		def.on_rotate = function(pos, node, user, mode, new_param2)
			local dir = 0
			if mode == screwdriver.ROTATE_FACE then -- left-click
				dir = 1
			elseif mode == screwdriver.ROTATE_AXIS then -- right-click
				dir = -1
			end
			local num = tonumber(node.name:sub(-1))
			num = num + dir
			num = (num >= 1 and num) or num + 6
			num = (num <= 6 and num) or num - 6
			minetest.swap_node(pos, {name = "technic:"..ltier..prefix.."_cable_plate_"..num})
		end
		minetest.register_node("technic:"..ltier..prefix.."_cable_plate_"..i, override_table(def, override_cable_plate))
		cable_tier["technic:"..ltier..prefix.."_cable_plate_"..i] = tier
	end

	local c = "technic:"..ltier..prefix.."_cable"
	minetest.register_craft({
		output = "technic:"..ltier..prefix.."_cable_plate_1 5",
		recipe = {
			{"", "", c},
			{c , c , c},
			{"", "", c},
		}
	})

	minetest.register_craft({
		output = c,
		recipe = {
			{"technic:"..ltier..prefix.."_cable_plate_1"},
		}
	})
end

local function clear_nets_if_machine(pos, node)
	for tier, machine_list in pairs(technic.machines) do
		if machine_list[node.name] ~= nil then
			return clear_networks(pos)
		end
	end
end

minetest.register_on_placenode(clear_nets_if_machine)
minetest.register_on_dignode(clear_nets_if_machine)

repo_name: Desour/PlayMinetestServer

license: mit


code:
local S = technic.getter

function technic.register_centrifuge(data)
	data.typename = "separating"
	data.machine_name = "centrifuge"
	data.machine_desc = S("%s Centrifuge")
	technic.register_base_machine(data)
end


code:
local S = technic.getter

technic.register_recipe_type("separating", {
	description = S("Separating"),
	output_size = 2,
})

function technic.register_separating_recipe(data)
	data.time = data.time or 10
	technic.register_recipe("separating", data)
end

local recipes = {
	{ "technic:bronze_dust 8",             "technic:copper_dust 7",       "technic:tin_dust"      },
	{ "technic:stainless_steel_dust 5",    "technic:wrought_iron_dust 4", "technic:chromium_dust" },
	{ "technic:brass_dust 3",              "technic:copper_dust 2",       "technic:zinc_dust"     },
	{ "technic:chernobylite_dust",         "default:sand",                "technic:uranium3_dust" },
	{ "default:dirt 4",                    "default:sand",                "default:gravel",       "default:clay_lump 4" },
}

local function uranium_dust(p)
	return "technic:uranium"..(p == 7 and "" or p).."_dust"
end
for p = 1, 34 do
	table.insert(recipes, { uranium_dust(p).." 2", uranium_dust(p-1), uranium_dust(p+1) })
end

if minetest.get_modpath("bushes_classic") then
	for _, berry in ipairs({ "blackberry", "blueberry", "gooseberry", "raspberry", "strawberry" }) do
		table.insert(recipes, { "bushes:"..berry.."_bush", "default:stick 20", "bushes:"..berry.." 4" })
	end
end

if minetest.get_modpath("farming") then
	if minetest.get_modpath("cottages") then
		-- work as a mechanized threshing floor
		table.insert(recipes, { "farming:wheat", "farming:seed_wheat", "cottages:straw_mat" })
		table.insert(recipes, { "farming:barley", "farming:seed_barley", "cottages:straw_mat" })
	else
		-- work in a less fancy and less efficient manner
		table.insert(recipes, { "farming:wheat 4", "farming:seed_wheat 3", "default:dry_shrub 1" })
		table.insert(recipes, { "farming:barley 4", "farming:seed_barley 3", "default:dry_shrub 1" })
	end
end

for _, data in pairs(recipes) do
	technic.register_separating_recipe({ input = { data[1] }, output = { data[2], data[3], data[4] } })
end


code:
local S = technic.getter

-- handles the machine upgrades every tick
function technic.handle_machine_upgrades(meta)
	-- Get the names of the upgrades
	local inv = meta:get_inventory()

	local srcstack = inv:get_stack("upgrade1", 1)
	local upg_item1 = srcstack and srcstack:get_name()

	srcstack = inv:get_stack("upgrade2", 1)
	local upg_item2 = srcstack and srcstack:get_name()

	-- Save some power by installing battery upgrades.
	-- Tube loading speed can be upgraded using control logic units.
	local EU_upgrade = 0
	local tube_upgrade = 0

	if upg_item1 == "technic:control_logic_unit" then
		tube_upgrade = tube_upgrade + 1
	elseif upg_item1 == "technic:battery" then
		EU_upgrade = EU_upgrade + 1
	end

	if upg_item2 == "technic:control_logic_unit" then
		tube_upgrade = tube_upgrade + 1
	elseif  upg_item2 == "technic:battery" then
		EU_upgrade = EU_upgrade + 1
	end

	return EU_upgrade, tube_upgrade
end

-- handles the machine upgrades when set or removed
local function on_machine_upgrade(meta, stack)
	local stack_name = stack:get_name()
	if stack_name == "default:chest" then
		meta:set_int("public", 1)
		return 1
	elseif stack_name ~= "technic:control_logic_unit"
	   and stack_name ~= "technic:battery" then
		return 0
	end
	return 1
end

-- something is about to be removed
local function on_machine_downgrade(meta, stack, list)
	if stack:get_name() == "default:chest" then
		local inv = meta:get_inventory()
		local upg1, upg2 = inv:get_stack("upgrade1", 1), inv:get_stack("upgrade2", 1)

		-- only set 0 if theres not a nother chest in the other list too
		if (not upg1 or not upg2 or upg1:get_name() ~= upg2:get_name()) then
			meta:set_int("public", 0)
		end
	end
	return 1
end

function technic.send_items(pos, x_velocity, z_velocity, output_name)
	-- Send items on their way in the pipe system.
	if output_name == nil then
		output_name = "dst"
	end

	local meta = minetest.get_meta(pos)
	local inv = meta:get_inventory()
	local i = 0
	for _, stack in ipairs(inv:get_list(output_name)) do
		i = i + 1
		if stack then
			local item0 = stack:to_table()
			if item0 then
				item0["count"] = 1
				technic.tube_inject_item(pos, pos, vector.new(x_velocity, 0, z_velocity), item0)
				stack:take_item(1)
				inv:set_stack(output_name, i, stack)
				return
			end
		end
	end
end

function technic.smelt_item(meta, result, speed)
	local inv = meta:get_inventory()
	meta:set_int("cook_time", meta:get_int("cook_time") + 1)
	if meta:get_int("cook_time") < result.time / speed then
		return
	end
	local result
	local afterfuel
	result, afterfuel = minetest.get_craft_result({method = "cooking", width = 1, items = inv:get_list("src")})

	if result and result.item then
		meta:set_int("cook_time", 0)
		-- check if there's room for output in "dst" list
		if inv:room_for_item("dst", result.item) then
			inv:set_stack("src", 1, afterfuel.items[1])
			inv:add_item("dst", result.item)
		end
	end
end

function technic.handle_machine_pipeworks(pos, tube_upgrade, send_function)
	if send_function == nil then
		send_function = technic.send_items
	end

	local node = minetest.get_node(pos)
	local meta = minetest.get_meta(pos)
	local pos1 = vector.new(pos)
	local x_velocity = 0
	local z_velocity = 0

	-- Output is on the left side of the furnace
	if node.param2 == 3 then pos1.z = pos1.z - 1  z_velocity = -1 end
	if node.param2 == 2 then pos1.x = pos1.x - 1  x_velocity = -1 end
	if node.param2 == 1 then pos1.z = pos1.z + 1  z_velocity =  1 end
	if node.param2 == 0 then pos1.x = pos1.x + 1  x_velocity =  1 end

	local output_tube_connected = false
	local node1 = minetest.get_node(pos1)
	if minetest.get_item_group(node1.name, "tubedevice") > 0 then
		output_tube_connected = true
	end
	local tube_time = meta:get_int("tube_time") + tube_upgrade
	if tube_time >= 2 then
		tube_time = 0
		if output_tube_connected then
			send_function(pos, x_velocity, z_velocity)
		end
	end
	meta:set_int("tube_time", tube_time)
end

function technic.machine_can_dig(pos, player)
	local meta = minetest.get_meta(pos)
	local inv = meta:get_inventory()
	if not inv:is_empty("src") or not inv:is_empty("dst") then
		if player then
			minetest.chat_send_player(player:get_player_name(),
				S("Machine cannot be removed because it is not empty"))
		end
		return false
	end

	return true
end

function technic.machine_after_dig_node(pos, oldnode, oldmetadata, player)
	if oldmetadata.inventory then
		if oldmetadata.inventory.upgrade1 and oldmetadata.inventory.upgrade1[1] then
			local stack = ItemStack(oldmetadata.inventory.upgrade1[1])
			if not stack:is_empty() then
				minetest.add_item(pos, stack)
			end
		end
		if oldmetadata.inventory.upgrade2 and oldmetadata.inventory.upgrade2[1] then
			local stack = ItemStack(oldmetadata.inventory.upgrade2[1])
			if not stack:is_empty() then
				minetest.add_item(pos, stack)
			end
		end
	end

	if minetest.registered_nodes[oldnode.name].tube then
		pipeworks.after_dig(pos, oldnode, oldmetadata, player)
	end
end

local function inv_change(pos, player, count, from_list, to_list, stack)
	local playername = player:get_player_name()
	local meta = minetest.get_meta(pos);
	local public = (meta:get_int("public") == 1)
	local to_upgrade = to_list == "upgrade1" or to_list == "upgrade2"
	local from_upgrade = from_list == "upgrade1" or from_list == "upgrade2"

	if (not public or to_upgrade or from_upgrade) and minetest.is_protected(pos, playername) then
		minetest.chat_send_player(playername, S("Inventory move disallowed due to protection"))
		return 0
	end
	if to_upgrade then
		-- only place a single item into it, if it's empty
		local empty = meta:get_inventory():is_empty(to_list)
		if empty then
			return on_machine_upgrade(meta, stack)
		end
		return 0
	elseif from_upgrade then
		-- only called on take (not move)
		on_machine_downgrade(meta, stack, from_list)
	end
	return count
end

function technic.machine_inventory_put(pos, listname, index, stack, player)
	return inv_change(pos, player, stack:get_count(), nil, listname, stack)
end

function technic.machine_inventory_take(pos, listname, index, stack, player)
	return inv_change(pos, player, stack:get_count(), listname, nil, stack)
end

function technic.machine_inventory_move(pos, from_list, from_index,
		to_list, to_index, count, player)
	local stack = minetest.get_meta(pos):get_inventory():get_stack(from_list, from_index)
	return inv_change(pos, player, count, from_list, to_list, stack)
end


code:
local S = technic.getter

function technic.register_compressor(data)
	data.typename = "compressing"
	data.machine_name = "compressor"
	data.machine_desc = S("%s Compressor")
	technic.register_base_machine(data)
end


code:
local S = technic.getter

technic.register_recipe_type("compressing", { description = S("Compressing") })

function technic.register_compressor_recipe(data)
	data.time = data.time or 4
	technic.register_recipe("compressing", data)
end

local recipes = {
	{"default:snowblock",          "default:ice"},
	{"default:sand 2",             "default:sandstone"},
	{"default:desert_sand 2",      "default:desert_sandstone"},
	{"default:silver_sand 2",      "default:silver_sandstone"},
	{"default:desert_sandstone",   "default:desert_stone"},
	{"technic:mixed_metal_ingot",  "technic:composite_plate"},
	{"default:copper_ingot 5",     "technic:copper_plate"},
	{"technic:coal_dust 4",        "technic:graphite"},
	{"technic:carbon_cloth",       "technic:carbon_plate"},
	{"technic:uranium35_ingot 5",  "technic:uranium_fuel"},
	{"technic:graphite 25",        "default:diamond"}
}

if minetest.get_modpath("ethereal") then
	-- the density of charcoal is ~1/10 of coal, otherwise it's pure carbon
	table.insert(recipes, {"ethereal:charcoal_lump 10", "default:coal_lump 1"})
end

-- defuse the default sandstone recipe, since we have the compressor to take over in a more realistic manner
minetest.clear_craft({
	recipe = {
		{"default:sand", "default:sand"},
		{"default:sand", "default:sand"},
	},
})
minetest.clear_craft({
	recipe = {
		{"default:desert_sand", "default:desert_sand"},
		{"default:desert_sand", "default:desert_sand"},
	},
})
minetest.clear_craft({
	recipe = {
		{"default:silver_sand", "default:silver_sand"},
		{"default:silver_sand", "default:silver_sand"},
	},
})

for _, data in pairs(recipes) do
	technic.register_compressor_recipe({input = {data[1]}, output = data[2]})
end


code:
local S = technic.getter

function technic.register_electric_furnace(data)
	data.typename = "cooking"
	data.machine_name = "electric_furnace"
	data.machine_desc = S("%s Furnace")
	technic.register_base_machine(data)
end


code:
local S = technic.getter

function technic.register_extractor(data)
	data.typename = "extracting"
	data.machine_name = "extractor"
	data.machine_desc = S("%s Extractor")
	technic.register_base_machine(data)
end


code:
local S = technic.getter

technic.register_recipe_type("extracting", { description = S("Extracting") })

function technic.register_extractor_recipe(data)
	data.time = data.time or 4
	technic.register_recipe("extracting", data)
end

if minetest.get_modpath("dye") then
	-- check if we are using dye or unifieddyes
	local unifieddyes = minetest.get_modpath("unifieddyes")

	-- register recipes with the same crafting ratios as `dye` provides
	local dye_recipes = {
		{"technic:coal_dust",                 "dye:black 2"},
		{"default:blueberries",               "dye:violet 2"},
		{"default:grass_1",                   "dye:green 1"},
		{"default:dry_shrub",                 "dye:brown 4"},
		{"default:junglegrass",               "dye:green 2"},
		{"default:cactus",                    "dye:green 4"},
		{"flowers:chrysanthemum_green",       "dye:green 4"},
		{"flowers:geranium",                  "dye:blue 4"},
		{"flowers:dandelion_white",           "dye:white 4"},
		{"flowers:dandelion_yellow",          "dye:yellow 4"},
		{"flowers:tulip",                     "dye:orange 4"},
		{"flowers:tulip_black",               "dye:black 8"},
		{"flowers:rose",                      "dye:red 4"},
		{"flowers:viola",                     "dye:violet 4"},
		{"bushes:blackberry",                 unifieddyes and "unifieddyes:magenta_s50 4" or "dye:violet 4"},
		{"bushes:blueberry",                  unifieddyes and "unifieddyes:magenta_s50 4" or "dye:magenta 4"},
	}

	if minetest.get_modpath("hunger") and minetest.get_modpath("ethereal") then
		table.insert(dye_recipes, {"ethereal:willow_twig 12", "technic:aspirin_pill"})
	end

	if minetest.get_modpath("farming") then
		-- Dyes ---
		-- better recipes for farming's crafting methods (twice the output)
		table.insert(dye_recipes, {"farming:chili_pepper", "dye:red 4"})
		table.insert(dye_recipes, {"farming:beans", "dye:green 4"})
		table.insert(dye_recipes, {"farming:grapes", "dye:violet 4"})
		table.insert(dye_recipes, {"farming:cocoa_beans", "dye:brown 4"})
		-- Some extra recipes:
		-- Himalayan rhubarb root can give yellow dye IRL
		table.insert(dye_recipes, {"farming:rhubarb", "dye:yellow 4"})
		table.insert(dye_recipes, {"farming:onion", "dye:yellow 4"})
		table.insert(dye_recipes, {"farming:blueberries", "dye:blue 4"})
		table.insert(dye_recipes, {"farming:raspberries", "dye:red 4"})
	end

	if minetest.get_modpath("ethereal") then
		table.insert(dye_recipes, {"ethereal:seaweed", "dye:dark_green 6"})
		table.insert(dye_recipes, {"ethereal:coral2", "dye:cyan 6"})
		table.insert(dye_recipes, {"ethereal:coral3", "dye:orange 6"})
		table.insert(dye_recipes, {"ethereal:coral4", "dye:pink 6"})
		table.insert(dye_recipes, {"ethereal:coral5", "dye:green 6"})
		table.insert(dye_recipes, {"ethereal:fern", "dye:dark_green 4"})
		table.insert(dye_recipes, {"ethereal:snowygrass", "dye:grey 4"})
		table.insert(dye_recipes, {"ethereal:crystalgrass", "dye:blue 4"})
	end

	if minetest.get_modpath("bakedclay") then
		table.insert(dye_recipes, {"bakedclay:delphinium", "dye:cyan 8"})
		table.insert(dye_recipes, {"bakedclay:thistle", "dye:magenta 8"})
		table.insert(dye_recipes, {"bakedclay:lazarus", "dye:pink 8"})
		table.insert(dye_recipes, {"bakedclay:mannagrass", "dye:dark_green 8"})
	end

	if minetest.get_modpath("bonemeal") then
		table.insert(dye_recipes, {"bonemeal:bone", "dye:white 8"})
		table.insert(dye_recipes, {"bonemeal:bonemeal", "dye:white 4"})
	end

	for _, data in ipairs(dye_recipes) do
		technic.register_extractor_recipe({input = {data[1]}, output = data[2]})
	end

	-- overwrite the existing crafting recipes
	local dyes = {"white", "red", "yellow", "blue", "violet", "orange"}
	for _, color in ipairs(dyes) do
		minetest.clear_craft({
			type = "shapeless",
			recipe = {"group:flower,color_"..color},
		})
		minetest.register_craft({
			type = "shapeless",
			output = "dye:"..color.." 1",
			recipe = {"group:flower,color_"..color},
		})
	end

	minetest.clear_craft({
		type = "shapeless",
		recipe = {"group:coal"},
	})
	minetest.register_craft({
		type = "shapeless",
		output = "dye:black 1",
		recipe = {"group:coal"},
	})

	if unifieddyes then
		minetest.clear_craft({
			type = "shapeless",
			recipe = {"default:cactus"},
		})
		minetest.register_craft({
			type = "shapeless",
			output = "dye:green 1",
			recipe = {"default:cactus"},
		})
	end
end


code:
local S = technic.getter

function technic.register_freezer(data)
	data.typename = "freezing"
	data.machine_name = "freezer"
	data.machine_desc = S("%s Freezer")
	technic.register_base_machine(data)
end


code:
local S = technic.getter

technic.register_recipe_type("freezing", { description = S("Freezing") })

function technic.register_freezer_recipe(data)
	data.time = data.time or 5
	technic.register_recipe("freezing", data)
end

local recipes = {
	{"bucket:bucket_water", { "default:ice", "bucket:bucket_empty" } },
	{"bucket:bucket_river_water", { "default:ice", "bucket:bucket_empty" } },
	{"default:dirt", "default:dirt_with_snow" },
	{"bucket:bucket_lava", { "default:obsidian", "bucket:bucket_empty" } }
}

for _, data in pairs(recipes) do
	technic.register_freezer_recipe({input = {data[1]}, output = data[2]})
end


code:
local S = technic.getter

local fs_helpers = pipeworks.fs_helpers
local tube_entry = "^pipeworks_tube_connection_metallic.png"

local tube = {
	insert_object = function(pos, node, stack, direction)
		local meta = minetest.get_meta(pos)
		local inv = meta:get_inventory()
		return inv:add_item("src", stack)
	end,
	can_insert = function(pos, node, stack, direction)
		local meta = minetest.get_meta(pos)
		local inv = meta:get_inventory()
		if meta:get_int("splitstacks") == 1 then
			stack = stack:peek_item(1)
		end
		return inv:room_for_item("src", stack)
	end,
	connect_sides = {left=1, right=1, back=1, top=1, bottom=1},
}

function technic.register_generator(data)

	local tier = data.tier
	local ltier = string.lower(tier)

	local groups = {snappy=2, choppy=2, oddly_breakable_by_hand=2,
		technic_machine=1, ["technic_"..ltier]=1}
	if data.tube then
		groups.tubedevice = 1
		groups.tubedevice_receiver = 1
	end
	local active_groups = {not_in_creative_inventory = 1}
	for k, v in pairs(groups) do active_groups[k] = v end

	local generator_formspec =
		"size[8,9;]"..
		"label[0,0;"..S("Fuel-Fired %s Generator"):format(tier).."]"..
		"list[current_name;src;3,1;1,1;]"..
		"image[4,1;1,1;default_furnace_fire_bg.png]"..
		"list[current_player;main;0,5;8,4;]"..
		"listring[]"

	local desc = S("Fuel-Fired %s Generator"):format(tier)

	local run = function(pos, node)
		local meta = minetest.get_meta(pos)
		local burn_time = meta:get_int("burn_time")
		local burn_totaltime = meta:get_int("burn_totaltime")
		-- If more to burn and the energy produced was used: produce some more
		if burn_time > 0 then
			meta:set_int(tier.."_EU_supply", data.supply)
			burn_time = burn_time - 1
			meta:set_int("burn_time", burn_time)
		end
		-- Burn another piece of fuel
		if burn_time == 0 then
			local inv = meta:get_inventory()
			if not inv:is_empty("src") then
				local fuellist = inv:get_list("src")
				local fuel
				local afterfuel
				fuel, afterfuel = minetest.get_craft_result(
						{method = "fuel", width = 1,
						items = fuellist})
				if not fuel or fuel.time == 0 then
					meta:set_string("infotext", S("%s Out Of Fuel"):format(desc))
					technic.swap_node(pos, "technic:"..ltier.."_generator")
					meta:set_int(tier.."_EU_supply", 0)
					return
				end
				meta:set_int("burn_time", fuel.time)
				meta:set_int("burn_totaltime", fuel.time)
				inv:set_stack("src", 1, afterfuel.items[1])
				technic.swap_node(pos, "technic:"..ltier.."_generator_active")
				meta:set_int(tier.."_EU_supply", data.supply)
			else
				technic.swap_node(pos, "technic:"..ltier.."_generator")
				meta:set_int(tier.."_EU_supply", 0)
			end
		end
		if burn_totaltime == 0 then burn_totaltime = 1 end
		local percent = math.floor((burn_time / burn_totaltime) * 100)
		meta:set_string("infotext", desc.." ("..percent.."%)")

		local form_buttons = ""
		if ltier ~= "lv" then
			form_buttons = fs_helpers.cycling_button(
				meta,
				pipeworks.button_base,
				"splitstacks",
				{
					pipeworks.button_off,
					pipeworks.button_on
				}
			)..pipeworks.button_label
		end
		meta:set_string("formspec",
			"size[8, 9]"..
			"label[0, 0;"..minetest.formspec_escape(desc).."]"..
			"list[current_name;src;3, 1;1, 1;]"..
			"image[4, 1;1, 1;default_furnace_fire_bg.png^[lowpart:"..
			(percent)..":default_furnace_fire_fg.png]"..
			"list[current_player;main;0, 5;8, 4;]"..
			"listring[]"..
			form_buttons
		)
	end

	local tentry = tube_entry
	if ltier == "lv" then tentry = "" end

	minetest.register_node("technic:"..ltier.."_generator", {
		description = desc,
		tiles = {
				"technic_"..ltier.."_generator_top.png"..tentry,
				"technic_machine_bottom.png"..tentry,
				"technic_"..ltier.."_generator_side.png"..tentry,
				"technic_"..ltier.."_generator_side.png"..tentry,
				"technic_"..ltier.."_generator_side.png"..tentry,
				"technic_"..ltier.."_generator_front.png"
		},
		paramtype2 = "facedir",
		groups = groups,
		connect_sides = {"bottom", "back", "left", "right"},
		legacy_facedir_simple = true,
		sounds = default.node_sound_wood_defaults(),
		tube = data.tube and tube or nil,
		on_construct = function(pos)
			local meta = minetest.get_meta(pos)
			local node = minetest.get_node(pos)
			meta:set_string("infotext", desc)
			meta:set_int(data.tier.."_EU_supply", 0)
			meta:set_int("burn_time", 0)
			meta:set_int("tube_time",  0)
			local form_buttons = ""
			if not string.find(node.name, ":lv_") then
				form_buttons = fs_helpers.cycling_button(
						meta,
						pipeworks.button_base,
						"splitstacks",
						{
							pipeworks.button_off,
							pipeworks.button_on
						}
					)..pipeworks.button_label
			end
			meta:set_string("formspec", generator_formspec..form_buttons)
			local inv = meta:get_inventory()
			inv:set_size("src", 1)
		end,
		can_dig = technic.machine_can_dig,
		allow_metadata_inventory_put = technic.machine_inventory_put,
		allow_metadata_inventory_take = technic.machine_inventory_take,
		allow_metadata_inventory_move = technic.machine_inventory_move,
		technic_run = run,
		after_place_node = data.tube and pipeworks.after_place,
		after_dig_node = technic.machine_after_dig_node,
		on_receive_fields = function(pos, formname, fields, sender)
			if not pipeworks.may_configure(pos, sender) then return end
			fs_helpers.on_receive_fields(pos, fields)
			local meta = minetest.get_meta(pos)
			local node = minetest.get_node(pos)
			local form_buttons = ""
			if not string.find(node.name, ":lv_") then
				form_buttons = fs_helpers.cycling_button(
						meta,
						pipeworks.button_base,
						"splitstacks",
						{
							pipeworks.button_off,
							pipeworks.button_on
						}
					)..pipeworks.button_label
			end
			meta:set_string("formspec", generator_formspec..form_buttons)
		end,
	})

	minetest.register_node("technic:"..ltier.."_generator_active", {
		description = desc,
		tiles = {
			"technic_"..ltier.."_generator_top.png"..tube_entry,
			"technic_machine_bottom.png"..tube_entry,
			"technic_"..ltier.."_generator_side.png"..tube_entry,
			"technic_"..ltier.."_generator_side.png"..tube_entry,
			"technic_"..ltier.."_generator_side.png"..tube_entry,
			"technic_"..ltier.."_generator_front_active.png"
		},
		paramtype2 = "facedir",
		groups = active_groups,
		connect_sides = {"bottom"},
		legacy_facedir_simple = true,
		sounds = default.node_sound_wood_defaults(),
		tube = data.tube and tube or nil,
		drop = "technic:"..ltier.."_generator",
		can_dig = technic.machine_can_dig,
		after_dig_node = technic.machine_after_dig_node,
		allow_metadata_inventory_put = technic.machine_inventory_put,
		allow_metadata_inventory_take = technic.machine_inventory_take,
		allow_metadata_inventory_move = technic.machine_inventory_move,
		technic_run = run,
		technic_on_disable = function(pos, node)
			local timer = minetest.get_node_timer(pos)
			timer:start(1)
		end,
		on_timer = function(pos, node)
			local meta = minetest.get_meta(pos)
			local node = minetest.get_node(pos)

			-- Connected back?
			if meta:get_int(tier.."_EU_timeout") > 0 then return false end

			local burn_time = meta:get_int("burn_time") or 0

			if burn_time <= 0 then
				meta:set_int(tier.."_EU_supply", 0)
				meta:set_int("burn_time", 0)
				technic.swap_node(pos, "technic:"..ltier.."_generator")
				return false
			end

			local burn_totaltime = meta:get_int("burn_totaltime") or 0
			if burn_totaltime == 0 then burn_totaltime = 1 end
			burn_time = burn_time - 1
			meta:set_int("burn_time", burn_time)
			local percent = math.floor(burn_time / burn_totaltime * 100)

			local form_buttons = ""
			if not string.find(node.name, ":lv_") then
				form_buttons = fs_helpers.cycling_button(
					meta,
					pipeworks.button_base,
					"splitstacks",
					{
						pipeworks.button_off,
						pipeworks.button_on
					}
				)..pipeworks.button_label
			end
			meta:set_string("formspec",
				"size[8, 9]"..
				"label[0, 0;"..minetest.formspec_escape(desc).."]"..
				"list[current_name;src;3, 1;1, 1;]"..
				"image[4, 1;1, 1;default_furnace_fire_bg.png^[lowpart:"..
				(percent)..":default_furnace_fire_fg.png]"..
				"list[current_player;main;0, 5;8, 4;]"..
				"listring[]"..
				form_buttons
			)
			return true
		end,
		on_receive_fields = function(pos, formname, fields, sender)
			if not pipeworks.may_configure(pos, sender) then return end
			fs_helpers.on_receive_fields(pos, fields)
			local meta = minetest.get_meta(pos)
			local node = minetest.get_node(pos)
			local form_buttons = ""
			if not string.find(node.name, ":lv_") then
				form_buttons = fs_helpers.cycling_button(
						meta,
						pipeworks.button_base,
						"splitstacks",
						{
							pipeworks.button_off,
							pipeworks.button_on
						}
					)..pipeworks.button_label
			end

			local burn_totaltime = meta:get_int("burn_totaltime") or 0
			local burn_time = meta:get_int("burn_time")
			local percent = math.floor(burn_time / burn_totaltime * 100)

			meta:set_string("formspec",
				"size[8, 9]"..
				"label[0, 0;"..minetest.formspec_escape(desc).."]"..
				"list[current_name;src;3, 1;1, 1;]"..
				"image[4, 1;1, 1;default_furnace_fire_bg.png^[lowpart:"..
				(percent)..":default_furnace_fire_fg.png]"..
				"list[current_player;main;0, 5;8, 4;]"..
				"listring[]"..
				form_buttons
			)
		end,
	})

	technic.register_machine(tier, "technic:"..ltier.."_generator",        technic.producer)
	technic.register_machine(tier, "technic:"..ltier.."_generator_active", technic.producer)
end


code:
local S = technic.getter

function technic.register_grinder(data)
	data.typename = "grinding"
	data.machine_name = "grinder"
	data.machine_desc = S("%s Grinder")
	technic.register_base_machine(data)
end


code:
local S = technic.getter

technic.register_recipe_type("grinding", { description = S("Grinding") })

function technic.register_grinder_recipe(data)
	data.time = data.time or 3
	technic.register_recipe("grinding", data)
end

local recipes = {
	-- Dusts
	{"default:coal_lump",          "technic:coal_dust 2"},
	{"default:copper_lump",        "technic:copper_dust 2"},
	{"default:desert_stone",       "default:desert_sand"},
	{"default:gold_lump",          "technic:gold_dust 2"},
	{"default:iron_lump",          "technic:wrought_iron_dust 2"},
	{"default:tin_lump",           "technic:tin_dust 2"},
	{"technic:chromium_lump",      "technic:chromium_dust 2"},
	{"technic:uranium_lump",       "technic:uranium_dust 2"},
	{"technic:zinc_lump",          "technic:zinc_dust 2"},
	{"technic:lead_lump",          "technic:lead_dust 2"},
	{"technic:sulfur_lump",        "technic:sulfur_dust 2"},
	{"default:stone",              "technic:stone_dust"},
	{"default:sand",               "technic:stone_dust"},

	-- Other
	{"default:cobble",           "default:gravel"},
	{"default:gravel",           "default:sand"},
	{"default:sandstone",        "default:sand 2"}, -- reverse recipe can be found in the compressor
	{"default:desert_sandstone", "default:desert_sand 2"}, -- reverse recipe can be found in the compressor
	{"default:silver_sandstone", "default:silver_sand 2"}, -- reverse recipe can be found in the compressor

	{"default:ice",              "default:snowblock"},
}

if minetest.get_modpath("ethereal") then
	-- the density of charcoal is ~1/10 of coal, otherwise it's the same graphitic carbon
	table.insert(recipes, {"ethereal:charcoal_lump 5", "technic:coal_dust 1"})
end

-- defuse the sandstone -> 4 sand recipe to avoid infinite sand bugs (also consult the inverse compressor recipe)
minetest.clear_craft({
	recipe = {
		{"default:sandstone"}
	},
})
minetest.clear_craft({
	recipe = {
		{"default:desert_sandstone"}
	},
})
minetest.clear_craft({
	recipe = {
		{"default:silver_sandstone"}
	},
})

if minetest.get_modpath("farming") then
	table.insert(recipes, {"farming:seed_wheat",   "farming:flour 1"})
end

if minetest.get_modpath("moreores") then
	table.insert(recipes, {"moreores:mithril_lump",   "technic:mithril_dust 2"})
	table.insert(recipes, {"moreores:silver_lump",    "technic:silver_dust 2"})
end

if minetest.get_modpath("gloopores") or minetest.get_modpath("glooptest") then
	table.insert(recipes, {"gloopores:alatro_lump",   "technic:alatro_dust 2"})
	table.insert(recipes, {"gloopores:kalite_lump",   "technic:kalite_dust 2"})
	table.insert(recipes, {"gloopores:arol_lump",     "technic:arol_dust 2"})
	table.insert(recipes, {"gloopores:talinite_lump", "technic:talinite_dust 2"})
	table.insert(recipes, {"gloopores:akalin_lump",   "technic:akalin_dust 2"})
end

if minetest.get_modpath("homedecor") then
	table.insert(recipes, {"home_decor:brass_ingot", "technic:brass_dust 1"})
end

for _, data in pairs(recipes) do
	technic.register_grinder_recipe({input = {data[1]}, output = data[2]})
end

-- dusts
local function register_dust(name, ingot)
	local lname = string.lower(name)
	lname = string.gsub(lname, ' ', '_')
	minetest.register_craftitem("technic:"..lname.."_dust", {
		description = S("%s Dust"):format(S(name)),
		inventory_image = "technic_"..lname.."_dust.png",
	})
	if ingot then
		minetest.register_craft({
			type = "cooking",
			recipe = "technic:"..lname.."_dust",
			output = ingot,
		})
		technic.register_grinder_recipe({ input = {ingot}, output = "technic:"..lname.."_dust 1" })
	end
end

-- Sorted alphibeticaly
register_dust("Brass",           "basic_materials:brass_ingot")
register_dust("Bronze",          "default:bronze_ingot")
register_dust("Carbon Steel",    "technic:carbon_steel_ingot")
register_dust("Cast Iron",       "technic:cast_iron_ingot")
register_dust("Chernobylite",    "technic:chernobylite_block")
register_dust("Chromium",        "technic:chromium_ingot")
register_dust("Coal",            nil)
register_dust("Copper",          "default:copper_ingot")
register_dust("Lead",            "technic:lead_ingot")
register_dust("Gold",            "default:gold_ingot")
register_dust("Mithril",         "moreores:mithril_ingot")
register_dust("Silver",          "moreores:silver_ingot")
register_dust("Stainless Steel", "technic:stainless_steel_ingot")
register_dust("Stone",           "default:stone")
register_dust("Sulfur",          nil)
register_dust("Tin",             "default:tin_ingot")
register_dust("Wrought Iron",    "technic:wrought_iron_ingot")
register_dust("Zinc",            "technic:zinc_ingot")
if minetest.get_modpath("gloopores") or minetest.get_modpath("glooptest") then
	register_dust("Akalin",          "glooptest:akalin_ingot")
	register_dust("Alatro",          "glooptest:alatro_ingot")
	register_dust("Arol",            "glooptest:arol_ingot")
	register_dust("Kalite",          nil)
	register_dust("Talinite",        "glooptest:talinite_ingot")
end

for p = 0, 35 do
	local nici = (p ~= 0 and p ~= 7 and p ~= 35) and 1 or nil
	local psuffix = p == 7 and "" or p
	local ingot = "technic:uranium"..psuffix.."_ingot"
	local dust = "technic:uranium"..psuffix.."_dust"
	minetest.register_craftitem(dust, {
		description = S("%s Dust"):format(string.format(S("%.1f%%-Fissile Uranium"), p/10)),
		inventory_image = "technic_uranium_dust.png",
		on_place_on_ground = minetest.craftitem_place_item,
		groups = {uranium_dust=1, not_in_creative_inventory=nici},
	})
	minetest.register_craft({
		type = "cooking",
		recipe = dust,
		output = ingot,
	})
	technic.register_grinder_recipe({ input = {ingot}, output = dust })
end

local function uranium_dust(p)
	return "technic:uranium"..(p == 7 and "" or p).."_dust"
end
for pa = 0, 34 do
	for pb = pa+1, 35 do
		local pc = (pa+pb)/2
		if pc == math.floor(pc) then
			minetest.register_craft({
				type = "shapeless",
				recipe = { uranium_dust(pa), uranium_dust(pb) },
				output = uranium_dust(pc).." 2",
			})
		end
	end
end

minetest.register_craft({
	type = "fuel",
	recipe = "technic:coal_dust",
	burntime = 50,
})

if minetest.get_modpath("gloopores") or minetest.get_modpath("glooptest") then
	minetest.register_craft({
		type = "fuel",
		recipe = "technic:kalite_dust",
		burntime = 37.5,
	})
end


code:
local S = technic.getter
local moretrees = minetest.get_modpath("moretrees")
local dye = minetest.get_modpath("dye")

-- sawdust, the finest wood/tree grinding
local sawdust = "technic:sawdust"
minetest.register_craftitem(sawdust, {
	description = S("Sawdust"),
	inventory_image = "technic_sawdust.png",
})
minetest.register_craft({ type = "fuel", recipe = sawdust, burntime = 6 })
technic.register_compressor_recipe({ input = {sawdust .. " 4"}, output = "default:wood" })

-- tree/wood grindings
local function register_tree_grinding(name, tree, wood, extract, grinding_color)
	local lname = string.lower(name)
	lname = string.gsub(lname, ' ', '_')
	local grindings_name = "technic:"..lname.."_grindings"
	if not minetest.registered_craftitems[grindings_name] then
		local inventory_image = "technic_"..lname.."_grindings.png"
		if grinding_color then
			inventory_image = inventory_image .. "^[colorize:" .. grinding_color
		end
		minetest.register_craftitem(grindings_name, {
			description = S("%s Grinding"):format(S(name)),
			inventory_image = inventory_image,
		})
		minetest.register_craft({
			type = "fuel",
			recipe = grindings_name,
			burntime = 8,
		})
	end
	technic.register_grinder_recipe({ input = { tree }, output = grindings_name .. " 4" })
	technic.register_grinder_recipe({ input = { grindings_name }, output = sawdust .. " 4" })
	if wood then
		technic.register_grinder_recipe({ input = { wood }, output = grindings_name })
	end
	if extract then
		technic.register_extractor_recipe({ input = { grindings_name .. " 4" }, output = extract})
		technic.register_separating_recipe({
			input = { grindings_name .. " 4" },
			output = { sawdust .. " 4", extract }
		})
	end
end

local rubber_tree_planks = moretrees and "moretrees:rubber_tree_planks"
local default_extract = dye and "dye:brown 2"

-- https://en.wikipedia.org/wiki/Catechu ancient brown dye from the wood of acacia trees
local acacia_extract = dye and "dye:brown 8"

register_tree_grinding("Common Tree",	"group:tree", 						 "group:wood",			default_extract)
register_tree_grinding("Common Tree",	"default:tree",                      "default:wood",        default_extract)
register_tree_grinding("Common Tree",	"default:aspen_tree",                "default:aspen_wood",  default_extract)
register_tree_grinding("Common Tree",	"default:jungletree",                "default:junglewood",  default_extract)
register_tree_grinding("Common Tree",	"default:pine_tree",                 "default:pine_wood",   default_extract)
register_tree_grinding("Rubber Tree",	"moretrees:rubber_tree_trunk",  	 rubber_tree_planks, 	"technic:raw_latex")
register_tree_grinding("Rubber Tree", 	"moretrees:rubber_tree_trunk_empty", nil,                   "technic:raw_latex")

if moretrees then
	register_tree_grinding("Common Tree",	"moretrees:beech_tree_trunk",		"moretrees:beech_tree_planks",		default_extract)
	register_tree_grinding("Common Tree",	"moretrees:apple_tree_trunk",		"moretrees:apple_tree_planks",		default_extract)
	register_tree_grinding("Common Tree",	"moretrees:oak_tree_trunk",			"moretrees:oak_tree_planks",		default_extract)
	register_tree_grinding("Common Tree",	"moretrees:giant_sequoia_trunk",	"moretrees:giant_sequoia_planks",	default_extract)
	register_tree_grinding("Common Tree",	"moretrees:birch_tree_trunk",		"moretrees:birch_tree_planks",		default_extract)
	register_tree_grinding("Common Tree",	"moretrees:palm_tree_trunk",		"moretrees:palm_tree_planks",		default_extract)
	register_tree_grinding("Common Tree",	"moretrees:date_palm_tree_trunk",	"moretrees:date_palm_tree_planks",	default_extract)
	register_tree_grinding("Common Tree",	"moretrees:spruce_tree_trunk",		"moretrees:spruce_tree_planks",		default_extract)
	register_tree_grinding("Common Tree",	"moretrees:ceder_tree_trunk",		"moretrees:ceder_tree_planks",		default_extract)
	register_tree_grinding("Common Tree",	"moretrees:poplar_tree_trunk",		"moretrees:poplar_tree_planks",		default_extract)
	register_tree_grinding("Common Tree",	"moretrees:wollow_tree_trunk",		"moretrees:wollow_tree_planks",		default_extract)
	register_tree_grinding("Common Tree",	"moretrees:douglas_fir_trunk",		"moretrees:douglas_fir_planks",		default_extract)

	register_tree_grinding("Acacia", 		"moretrees:acacia_trunk", 			"moretrees:acacia_planks", 			acacia_extract)
else
	register_tree_grinding("Acacia", 		"default:acacia_tree", 				"default:acacia_wood", 				acacia_extract)
end


code:
local path = technic.modpath.."/machines/register"

dofile(path.."/common.lua")

-- Wiring stuff
dofile(path.."/cables.lua")
dofile(path.."/battery_box.lua")

-- Generators
dofile(path.."/solar_array.lua")
dofile(path.."/generator.lua")

-- API for machines
dofile(path.."/recipes.lua")
dofile(path.."/machine_base.lua")

-- Recipes
dofile(path.."/alloy_recipes.lua")
dofile(path.."/grinder_recipes.lua")
dofile(path.."/extractor_recipes.lua")
dofile(path.."/compressor_recipes.lua")
dofile(path.."/centrifuge_recipes.lua")
dofile(path.."/freezer_recipes.lua")

-- Multi-Machine Recipes
dofile(path.."/grindings.lua")

-- Machines
dofile(path.."/alloy_furnace.lua")
dofile(path.."/electric_furnace.lua")
dofile(path.."/grinder.lua")
dofile(path.."/extractor.lua")
dofile(path.."/compressor.lua")
dofile(path.."/centrifuge.lua")
dofile(path.."/freezer.lua")


code:
local S = technic.getter

local fs_helpers = pipeworks.fs_helpers
local tube_entry = "^pipeworks_tube_connection_metallic.png"

function technic.default_can_insert(pos, node, stack, direction)
	local meta = minetest.get_meta(pos)
	local inv = meta:get_inventory()
	if meta:get_int("splitstacks") == 1 then
		stack = stack:peek_item(1)
	end
	return inv:room_for_item("src", stack)
end

function technic.new_default_tube()
	return {
		insert_object = function(pos, node, stack, direction)
			local meta = minetest.get_meta(pos)
			local inv = meta:get_inventory()
			return inv:add_item("src", stack)
		end,
		can_insert = technic.default_can_insert,
		connect_sides = {left = 1, right = 1, back = 1, top = 1, bottom = 1},
	}
end

local connect_default = {"bottom", "back", "left", "right"}

local function round(v)
	return math.floor(v + 0.5)
end

function technic.register_base_machine(data)
	local typename = data.typename
	local input_size = technic.recipes[typename].input_size
	local machine_name = data.machine_name
	local machine_desc = data.machine_desc
	local tier = data.tier
	local ltier = string.lower(tier)

	data.modname = data.modname or minetest.get_current_modname()

	local groups = {cracky = 2, technic_machine = 1, ["technic_"..ltier] = 1}
	if data.tube then
		groups.tubedevice = 1
		groups.tubedevice_receiver = 1
	end
	local active_groups = {not_in_creative_inventory = 1}
	for k, v in pairs(groups) do active_groups[k] = v end

	local formspec =
		"size[8,9;]"..
		"list[current_name;src;"..(4-input_size)..",1;"..input_size..",1;]"..
		"list[current_name;dst;5,1;2,2;]"..
		"list[current_player;main;0,5;8,4;]"..
		"label[0,0;"..machine_desc:format(tier).."]"..
		"listring[current_name;dst]"..
		"listring[current_player;main]"..
		"listring[current_name;src]"..
		"listring[current_player;main]"
	if data.upgrade then
		formspec = formspec..
			"list[current_name;upgrade1;1,3;1,1;]"..
			"list[current_name;upgrade2;2,3;1,1;]"..
			"label[1,4;"..S("Upgrade Slots").."]"..
			"listring[current_name;upgrade1]"..
			"listring[current_player;main]"..
			"listring[current_name;upgrade2]"..
			"listring[current_player;main]"
	end

	local tube = technic.new_default_tube()
	if data.can_insert then
		tube.can_insert = data.can_insert
	end
	if data.insert_object then
		tube.insert_object = data.insert_object
	end

	local run = function(pos, node)
		local meta     = minetest.get_meta(pos)
		local inv      = meta:get_inventory()
		local eu_input = meta:get_int(tier.."_EU_input")

		local machine_desc_tier = machine_desc:format(tier)
		local machine_node      = data.modname..":"..ltier.."_"..machine_name
		local machine_demand    = data.demand

		-- Setup meta data if it does not exist.
		if not eu_input then
			meta:set_int(tier.."_EU_demand", machine_demand[1])
			meta:set_int(tier.."_EU_input", 0)
			return
		end

		local EU_upgrade, tube_upgrade = 0, 0
		if data.upgrade then
			EU_upgrade, tube_upgrade = technic.handle_machine_upgrades(meta)
		end
		if data.tube then
			technic.handle_machine_pipeworks(pos, tube_upgrade)
		end

		local powered = eu_input >= machine_demand[EU_upgrade+1]
		if powered then
			meta:set_int("src_time", meta:get_int("src_time") + round(data.speed*10))
		end
		while true do
			local result = inv:get_list("src") and technic.get_recipe(typename, inv:get_list("src"))
			if not result then
				technic.swap_node(pos, machine_node)
				meta:set_string("infotext", S("%s Idle"):format(machine_desc_tier))
				meta:set_int(tier.."_EU_demand", 0)
				meta:set_int("src_time", 0)
				return
			end
			meta:set_int(tier.."_EU_demand", machine_demand[EU_upgrade+1])
			technic.swap_node(pos, machine_node.."_active")
			meta:set_string("infotext", S("%s Active"):format(machine_desc_tier))
			if meta:get_int("src_time") < round(result.time*10) then
				if not powered then
					technic.swap_node(pos, machine_node)
					meta:set_string("infotext", S("%s Unpowered"):format(machine_desc_tier))
				end
				return
			end
			local output = result.output
			if type(output) ~= "table" then output = { output } end
			local output_stacks = {}
			for _, o in ipairs(output) do
				table.insert(output_stacks, ItemStack(o))
			end
			local room_for_output = true
			inv:set_size("dst_tmp", inv:get_size("dst"))
			inv:set_list("dst_tmp", inv:get_list("dst"))
			for _, o in ipairs(output_stacks) do
				if not inv:room_for_item("dst_tmp", o) then
					room_for_output = false
					break
				end
				inv:add_item("dst_tmp", o)
			end
			if not room_for_output then
				technic.swap_node(pos, machine_node)
				meta:set_string("infotext", S("%s Idle"):format(machine_desc_tier))
				meta:set_int(tier.."_EU_demand", 0)
				meta:set_int("src_time", round(result.time*10))
				return
			end
			meta:set_int("src_time", meta:get_int("src_time") - round(result.time*10))
			inv:set_list("src", result.new_input)
			inv:set_list("dst", inv:get_list("dst_tmp"))
		end
	end

	local tentry = tube_entry
	if ltier == "lv" then
		tentry = ""
	end

	minetest.register_node(data.modname..":"..ltier.."_"..machine_name, {
		description = machine_desc:format(tier),
		tiles = {
			data.modname.."_"..ltier.."_"..machine_name.."_top.png"..tentry,
			data.modname.."_"..ltier.."_"..machine_name.."_bottom.png"..tentry,
			data.modname.."_"..ltier.."_"..machine_name.."_side.png"..tentry,
			data.modname.."_"..ltier.."_"..machine_name.."_side.png"..tentry,
			data.modname.."_"..ltier.."_"..machine_name.."_side.png"..tentry,
			data.modname.."_"..ltier.."_"..machine_name.."_front.png"
		},
		paramtype2 = "facedir",
		groups = groups,
		tube = data.tube and tube or nil,
		connect_sides = data.connect_sides or connect_default,
		legacy_facedir_simple = true,
		sounds = default.node_sound_wood_defaults(),
		on_construct = function(pos)
			local node = minetest.get_node(pos)
			local meta = minetest.get_meta(pos)

			local form_buttons = ""
			if not string.find(node.name, ":lv_") then
				form_buttons = fs_helpers.cycling_button(
					meta,
					pipeworks.button_base,
					"splitstacks",
					{
						pipeworks.button_off,
						pipeworks.button_on
					}
				)..pipeworks.button_label
			end

			meta:set_string("infotext", machine_desc:format(tier))
			meta:set_int("tube_time",  0)
			meta:set_string("formspec", formspec..form_buttons)
			local inv = meta:get_inventory()
			inv:set_size("src", input_size)
			inv:set_size("dst", 4)
			inv:set_size("upgrade1", 1)
			inv:set_size("upgrade2", 1)
		end,
		can_dig = technic.machine_can_dig,
		allow_metadata_inventory_put = technic.machine_inventory_put,
		allow_metadata_inventory_take = technic.machine_inventory_take,
		allow_metadata_inventory_move = technic.machine_inventory_move,
		technic_run = run,
		after_place_node = data.tube and pipeworks.after_place,
		after_dig_node = technic.machine_after_dig_node,
		on_receive_fields = function(pos, formname, fields, sender)
			local node = minetest.get_node(pos)
			if not pipeworks.may_configure(pos, sender) then return end
			fs_helpers.on_receive_fields(pos, fields)
			local meta = minetest.get_meta(pos)
			local form_buttons = ""
			if not string.find(node.name, ":lv_") then
				form_buttons = fs_helpers.cycling_button(
					meta,
					pipeworks.button_base,
					"splitstacks",
					{
						pipeworks.button_off,
						pipeworks.button_on
					}
				)..pipeworks.button_label
			end
			meta:set_string("formspec", formspec..form_buttons)
		end,
	})

	minetest.register_node(data.modname..":"..ltier.."_"..machine_name.."_active",{
		description = machine_desc:format(tier),
		tiles = {
			data.modname.."_"..ltier.."_"..machine_name.."_top.png"..tentry,
			data.modname.."_"..ltier.."_"..machine_name.."_bottom.png"..tentry,
			data.modname.."_"..ltier.."_"..machine_name.."_side.png"..tentry,
			data.modname.."_"..ltier.."_"..machine_name.."_side.png"..tentry,
			data.modname.."_"..ltier.."_"..machine_name.."_side.png"..tentry,
			data.modname.."_"..ltier.."_"..machine_name.."_front_active.png"
		},
		paramtype2 = "facedir",
		drop = data.modname..":"..ltier.."_"..machine_name,
		groups = active_groups,
		connect_sides = data.connect_sides or connect_default,
		legacy_facedir_simple = true,
		sounds = default.node_sound_wood_defaults(),
		tube = data.tube and tube or nil,
		can_dig = technic.machine_can_dig,
		allow_metadata_inventory_put = technic.machine_inventory_put,
		allow_metadata_inventory_take = technic.machine_inventory_take,
		allow_metadata_inventory_move = technic.machine_inventory_move,
		technic_run = run,
		technic_disabled_machine_name = data.modname..":"..ltier.."_"..machine_name,
		on_receive_fields = function(pos, formname, fields, sender)
			local node = minetest.get_node(pos)
			if not pipeworks.may_configure(pos, sender) then return end
			fs_helpers.on_receive_fields(pos, fields)
			local meta = minetest.get_meta(pos)
			local form_buttons = ""
			if not string.find(node.name, ":lv_") then
				form_buttons = fs_helpers.cycling_button(
					meta,
					pipeworks.button_base,
					"splitstacks",
					{
						pipeworks.button_off,
						pipeworks.button_on
					}
				)..pipeworks.button_label
			end
			meta:set_string("formspec", formspec..form_buttons)
		end,
	})

	technic.register_machine(tier, data.modname..":"..ltier.."_"..machine_name,            technic.receiver)
	technic.register_machine(tier, data.modname..":"..ltier.."_"..machine_name.."_active", technic.receiver)

end -- End registration


code:
local have_ui = minetest.get_modpath("unified_inventory")

technic.recipes = { cooking = { input_size = 1, output_size = 1 } }
function technic.register_recipe_type(typename, origdata)
	local data = {}
	for k, v in pairs(origdata) do data[k] = v end
	data.input_size = data.input_size or 1
	data.output_size = data.output_size or 1
	if have_ui and unified_inventory.register_craft_type and data.output_size == 1 then
		unified_inventory.register_craft_type(typename, {
			description = data.description,
			width = data.input_size,
			height = 1,
		})
	end
	data.recipes = {}
	technic.recipes[typename] = data
end

local function get_recipe_index(items)
	if not items or type(items) ~= "table" then return false end
	local l = {}
	for i, stack in ipairs(items) do
		l[i] = ItemStack(stack):get_name()
	end
	table.sort(l)
	return table.concat(l, "/")
end

local function register_recipe(typename, data)
	-- Handle aliases
	for i, stack in ipairs(data.input) do
		data.input[i] = ItemStack(stack):to_string()
	end
	if type(data.output) == "table" then
		for i, v in ipairs(data.output) do
			data.output[i] = ItemStack(data.output[i]):to_string()
		end
	else
		data.output = ItemStack(data.output):to_string()
	end

	local recipe = {time = data.time, input = {}, output = data.output}
	local index = get_recipe_index(data.input)
	if not index then
		print("[Technic] ignored registration of garbage recipe!")
		return
	end
	for _, stack in ipairs(data.input) do
		recipe.input[ItemStack(stack):get_name()] = ItemStack(stack):get_count()
	end

	technic.recipes[typename].recipes[index] = recipe
	if have_ui and technic.recipes[typename].output_size == 1 then
		unified_inventory.register_craft({
			type = typename,
			output = data.output,
			items = data.input,
			width = 0,
		})
	end
end

function technic.register_recipe(typename, data)
	minetest.after(0.01, register_recipe, typename, data) -- Handle aliases
end

function technic.get_recipe(typename, items)
	if typename == "cooking" then -- Already builtin in Minetest, so use that
		local result, new_input = minetest.get_craft_result({
			method = "cooking",
			width = 1,
			items = items})
		-- Compatibility layer
		if not result or result.time == 0 then
			return nil
		-- Workaround for recipes with replacements
		elseif not new_input.items[1]:is_empty() and new_input.items[1]:get_name() ~= items[1]:get_name() then
			items[1]:take_item(1)
			return {time = result.time,
			        new_input = {items[1]},
			        output = {new_input.items[1], result.item}}
		else
			return {time = result.time,
			        new_input = new_input.items,
			        output = result.item}
		end
	end
	local index = get_recipe_index(items)
	if not index then
		print("[Technic] ignored registration of garbage recipe!")
		return
	end
	local recipe = technic.recipes[typename].recipes[index]
	if recipe then
		local new_input = {}
		for i, stack in ipairs(items) do
			if stack:get_count() < recipe.input[stack:get_name()] then
				return nil
			else
				new_input[i] = ItemStack(stack)
				new_input[i]:take_item(recipe.input[stack:get_name()])
			end
		end
		return {time = recipe.time,
		        new_input = new_input,
		        output = recipe.output}
	else
		return nil
	end
end


code:
local S = technic.getter

function technic.register_solar_array(data)
	local tier = data.tier
	local ltier = string.lower(tier)

	local run = function(pos, node)
		-- The action here is to make the solar array produce power
		-- Power is dependent on the light level and the height above ground
		-- There are many ways to cheat by using other light sources like lamps.
		-- As there is no way to determine if light is sunlight that is just a shame.
		-- To take care of some of it solar panels do not work outside daylight hours or if
		-- built below 0m
		local pos1 = {}
		local machine_name = S("Arrayed Solar %s Generator"):format(tier)
		pos1.y = pos.y + 1
		pos1.x = pos.x
		pos1.z = pos.z

		technic.get_or_load_node(pos1)
		local light = minetest.get_node_light(pos1, nil)
		local time_of_day = minetest.get_timeofday()
		local meta = minetest.get_meta(pos)
		light = light or 0

		-- turn on array only during day time and if sufficient light
		-- I know this is counter intuitive when cheating by using other light sources.
		if light >= 12 and time_of_day >= 0.24 and time_of_day <= 0.76 and pos.y > 0 then
			local charge_to_give = math.floor((light + pos.y) * data.power)
			charge_to_give = math.max(charge_to_give, 0)
			charge_to_give = math.min(charge_to_give, data.power * 50)
			meta:set_string("infotext", S("@1 Active (@2)", machine_name,
				technic.EU_string(charge_to_give)))
			meta:set_int(tier.."_EU_supply", charge_to_give)
		else
			meta:set_string("infotext", S("%s Idle"):format(machine_name))
			meta:set_int(tier.."_EU_supply", 0)
		end
	end

	minetest.register_node("technic:solar_array_"..ltier, {
		tiles = {"technic_"..ltier.."_solar_array_top.png",  "technic_"..ltier.."_solar_array_bottom.png",
			 "technic_"..ltier.."_solar_array_side.png", "technic_"..ltier.."_solar_array_side.png",
			 "technic_"..ltier.."_solar_array_side.png", "technic_"..ltier.."_solar_array_side.png"},
		groups = {snappy=2, choppy=2, oddly_breakable_by_hand=2, technic_machine=1, ["technic_"..ltier]=1},
		connect_sides = {"bottom"},
		sounds = default.node_sound_wood_defaults(),
		description = S("Arrayed Solar %s Generator"):format(tier),
		active = false,
		drawtype = "nodebox",
		paramtype = "light",
		node_box = {
			type = "fixed",
			fixed = {-0.5, -0.5, -0.5, 0.5, 0, 0.5},
		},
		on_construct = function(pos)
			local meta = minetest.get_meta(pos)
			meta:set_int(tier.."_EU_supply", 0)
		end,
		technic_run = run,
	})

	technic.register_machine(tier, "technic:solar_array_"..ltier, technic.producer)
end


code:
-- The supply converter is a generic device which can convert from
-- LV to MV and back, and HV to MV and back.
-- The machine is configured by the wiring below and above it.
--
-- It works like this:
--   The top side is setup as the receiver side, the bottom as the producer side.
--   Once the receiver side is powered it will deliver power to the other side.
--   Unused power is wasted just like any other producer!

local digilines_path = minetest.get_modpath("digilines")

local S = technic.getter

local cable_entry = "^technic_cable_connection_overlay.png"

local function set_supply_converter_formspec(meta)
	local formspec = "size[5,2.25]"..
		"field[0.3,0.5;2,1;power;"..S("Input Power")..";"..meta:get_int("power").."]"
	if digilines_path then
		formspec = formspec..
			"field[2.3,0.5;3,1;channel;Digiline Channel;"..meta:get_string("channel").."]"
	end
	-- The names for these toggle buttons are explicit about which
	-- state they'll switch to, so that multiple presses (arising
	-- from the ambiguity between lag and a missed press) only make
	-- the single change that the user expects.
	if meta:get_int("mesecon_mode") == 0 then
		formspec = formspec.."button[0,1;5,1;mesecon_mode_1;"..S("Ignoring Mesecon Signal").."]"
	else
		formspec = formspec.."button[0,1;5,1;mesecon_mode_0;"..S("Controlled by Mesecon Signal").."]"
	end
	if meta:get_int("enabled") == 0 then
		formspec = formspec.."button[0,1.75;5,1;enable;"..S("%s Disabled"):format(S("Supply Converter")).."]"
	else
		formspec = formspec.."button[0,1.75;5,1;disable;"..S("%s Enabled"):format(S("Supply Converter")).."]"
	end
	meta:set_string("formspec", formspec)
end

local supply_converter_receive_fields = function(pos, formname, fields, sender)
	local meta = minetest.get_meta(pos)
	local power = nil
	if fields.power then
		power = tonumber(fields.power) or 0
		power = math.max(power, 0)
		power = math.min(power, 10000)
		power = 100 * math.floor(power / 100)
		if power == meta:get_int("power") then power = nil end
	end
	if power then meta:set_int("power", power) end
	if fields.channel then meta:set_string("channel", fields.channel) end
	if fields.enable  then meta:set_int("enabled", 1) end
	if fields.disable then meta:set_int("enabled", 0) end
	if fields.mesecon_mode_0 then meta:set_int("mesecon_mode", 0) end
	if fields.mesecon_mode_1 then meta:set_int("mesecon_mode", 1) end
	set_supply_converter_formspec(meta)
end

local mesecons = {
	effector = {
		action_on = function(pos, node)
			minetest.get_meta(pos):set_int("mesecon_effect", 1)
		end,
		action_off = function(pos, node)
			minetest.get_meta(pos):set_int("mesecon_effect", 0)
		end
	}
}

local digiline_def = {
	receptor = {
		rules = technic.digilines.rules,
		action = function() end
	},
	effector = {
		rules = technic.digilines.rules,
		action = function(pos, node, channel, msg)
			if type(msg) ~= "string" then
				return
			end
			local meta = minetest.get_meta(pos)
			if channel ~= meta:get_string("channel") then
				return
			end
			msg = msg:lower()
			if msg == "get" then
				digilines.receptor_send(pos, technic.digilines.rules, channel, {
					enabled      = meta:get_int("enabled"),
					power        = meta:get_int("power"),
					mesecon_mode = meta:get_int("mesecon_mode")
				})
				return
			elseif msg == "off" then
				meta:set_int("enabled", 0)
			elseif msg == "on" then
				meta:set_int("enabled", 1)
			elseif msg == "toggle" then
				local onn = meta:get_int("enabled")
				onn = 1-onn -- Mirror onn with pivot 0.5, so switch between 1 and 0.
				meta:set_int("enabled", onn)
			elseif msg:sub(1, 5) == "power" then
				local power = tonumber(msg:sub(7))
				if not power then
					return
				end
				power = math.max(power, 0)
				power = math.min(power, 10000)
				power = 100 * math.floor(power / 100)
				meta:set_int("power", power)
			elseif msg:sub(1, 12) == "mesecon_mode" then
				meta:set_int("mesecon_mode", tonumber(msg:sub(14)))
			else
				return
			end
			set_supply_converter_formspec(meta)
		end
	},
}

local run = function(pos, node, run_stage)
	-- run only in producer stage.
	if run_stage == technic.receiver then
		return
	end

	local remain = 0.9
	-- Machine information
	local machine_name  = S("Supply Converter")
	local meta          = minetest.get_meta(pos)
	local enabled       = meta:get_string("enabled")
	if enabled == "" then
		-- Backwards compatibility
		minetest.registered_nodes["technic:supply_converter"].on_construct(pos)
		enabled = true
	else
		enabled = enabled == "1"
	end
	enabled = enabled and (meta:get_int("mesecon_mode") == 0 or meta:get_int("mesecon_effect") ~= 0)
	local demand = enabled and meta:get_int("power") or 0

	local pos_up        = {x=pos.x, y=pos.y+1, z=pos.z}
	local pos_down      = {x=pos.x, y=pos.y-1, z=pos.z}
	local name_up       = minetest.get_node(pos_up).name
	local name_down     = minetest.get_node(pos_down).name

	local from = technic.get_cable_tier(name_up)
	local to   = technic.get_cable_tier(name_down)

	if from and to then
		local input = meta:get_int(from.."_EU_input")
		meta:set_int(from.."_EU_demand", demand)
		meta:set_int(from.."_EU_supply", 0)
		meta:set_int(to.."_EU_demand", 0)
		meta:set_int(to.."_EU_supply", input * remain)
		meta:set_string("infotext", S("@1 (@2 @3 -> @4 @5)", machine_name,
			technic.EU_string(input), from,
			technic.EU_string(input * remain), to))
	else
		meta:set_string("infotext", S("%s Has Bad Cabling"):format(machine_name))
		if to then
			meta:set_int(to.."_EU_supply", 0)
		end
		if from then
			meta:set_int(from.."_EU_demand", 0)
		end
		return
	end

end

minetest.register_node("technic:supply_converter", {
	description = S("Supply Converter"),
	tiles  = {
		"technic_supply_converter_tb.png"..cable_entry,
		"technic_supply_converter_tb.png"..cable_entry,
		"technic_supply_converter_side.png",
		"technic_supply_converter_side.png",
		"technic_supply_converter_side.png",
		"technic_supply_converter_side.png"
		},
	groups = {snappy=2, choppy=2, oddly_breakable_by_hand=2,
		technic_machine=1, technic_all_tiers=1},
	connect_sides = {"top", "bottom"},
	sounds = default.node_sound_wood_defaults(),
	on_receive_fields = supply_converter_receive_fields,
	on_construct = function(pos)
		local meta = minetest.get_meta(pos)
		meta:set_string("infotext", S("Supply Converter"))
		if digilines_path then
			meta:set_string("channel", "supply_converter"..minetest.pos_to_string(pos))
		end
		meta:set_int("power", 10000)
		meta:set_int("enabled", 1)
		meta:set_int("mesecon_mode", 0)
		meta:set_int("mesecon_effect", 0)
		set_supply_converter_formspec(meta)
	end,
	mesecons = mesecons,
	digiline = digiline_def,
	technic_run = run,
	technic_on_disable = run,
})

minetest.register_craft({
	output = 'technic:supply_converter 1',
	recipe = {
		{'basic_materials:gold_wire', 'technic:rubber',         'technic:doped_silicon_wafer'},
		{'technic:mv_transformer', 'technic:machine_casing', 'technic:lv_transformer'},
		{'technic:mv_cable',       'technic:rubber',         'technic:lv_cable'},
	},
	replacements = { {"basic_materials:gold_wire", "basic_materials:empty_spool"}, },
})

for tier, machines in pairs(technic.machines) do
	technic.register_machine(tier, "technic:supply_converter", technic.producer_receiver)
end