code:
--[[
Radioactivity

Radiation resistance represents the extent to which a material
attenuates radiation passing through it; i.e., how good a radiation
shield it is.  This is identified per node type.  For materials that
exist in real life, the radiation resistance value that this system
uses for a node type consisting of a solid cube of that material is the
(approximate) number of halvings of ionising radiation that is achieved
by a meter of the material in real life.  This is approximately
proportional to density, which provides a good way to estimate it.
Homogeneous mixtures of materials have radiation resistance computed
by a simple weighted mean.  Note that the amount of attenuation that
a material achieves in-game is not required to be (and is not) the
same as the attenuation achieved in real life.

Radiation resistance for a node type may be specified in the node
definition, under the key "radiation_resistance".  As an interim
measure, until node definitions widely include this, this code
knows a bunch of values for particular node types in several mods,
and values for groups of node types.  The node definition takes
precedence if it specifies a value.  Nodes for which no value at
all is known are taken to provide no radiation resistance at all;
this is appropriate for the majority of node types.  Only node types
consisting of a fairly homogeneous mass of material should report
non-zero radiation resistance; anything with non-uniform geometry
or complex internal structure should show no radiation resistance.
Fractional resistance values are permitted.
--]]

local MP = minetest.get_modpath("technic")
local throttle = dofile(MP .. "/util/throttle.lua")

local S = technic.getter

local rad_resistance_node = {
	["default:brick"] = 13,
	["default:bronzeblock"] = 45,
	["default:clay"] = 15,
	["default:coalblock"] = 9.6,
	["default:cobble"] = 15,
	["default:copperblock"] = 46,
	["default:desert_cobble"] = 15,
	["default:desert_sand"] = 10,
	["default:desert_stone"] = 17,
	["default:desert_stonebrick"] = 17,
	["default:diamondblock"] = 24,
	["default:dirt"] = 8.2,
	["default:dirt_with_grass"] = 8.2,
	["default:dirt_with_grass_footsteps"] = 8.2,
	["default:dirt_with_snow"] = 8.2,
	["default:glass"] = 17,
	["default:goldblock"] = 170,
	["default:gravel"] = 10,
	["default:ice"] = 5.6,
	["default:lava_flowing"] = 8.5,
	["default:lava_source"] = 17,
	["default:mese"] = 21,
	["default:mossycobble"] = 15,
	["default:tinblock"] = 37,
	["pbj_pup:pbj_pup"] = 10000,
	["pbj_pup:pbj_pup_candies"] = 10000,
	["gloopblocks:rainbow_block_diagonal"] = 5000,
	["gloopblocks:rainbow_block_horizontal"] = 10000,
	["default:nyancat"] = 10000,
	["default:nyancat_rainbow"] = 10000,
	["nyancat:nyancat"] = 10000,
	["nyancat:nyancat_rainbow"] = 10000,
	["default:obsidian"] = 18,
	["default:obsidian_glass"] = 18,
	["default:sand"] = 10,
	["default:sandstone"] = 15,
	["default:sandstonebrick"] = 15,
	["default:snowblock"] = 1.7,
	["default:steelblock"] = 40,
	["default:stone"] = 17,
	["default:stone_with_coal"] = 16,
	["default:stone_with_copper"] = 20,
	["default:stone_with_diamond"] = 18,
	["default:stone_with_gold"] = 34,
	["default:stone_with_iron"] = 20,
	["default:stone_with_mese"] = 17,
	["default:stone_with_tin"] = 19,
	["default:stonebrick"] = 17,
	["default:water_flowing"] = 2.8,
	["default:water_source"] = 5.6,
	["farming:desert_sand_soil"] = 10,
	["farming:desert_sand_soil_wet"] = 10,
	["farming:soil"] = 8.2,
	["farming:soil_wet"] = 8.2,
	["glooptest:akalin_crystal_glass"] = 21,
	["glooptest:akalinblock"] = 40,
	["glooptest:alatro_crystal_glass"] = 21,
	["glooptest:alatroblock"] = 40,
	["glooptest:amethystblock"] = 18,
	["glooptest:arol_crystal_glass"] = 21,
	["glooptest:crystal_glass"] = 21,
	["glooptest:emeraldblock"] = 19,
	["glooptest:heavy_crystal_glass"] = 21,
	["glooptest:mineral_akalin"] = 20,
	["glooptest:mineral_alatro"] = 20,
	["glooptest:mineral_amethyst"] = 17,
	["glooptest:mineral_arol"] = 20,
	["glooptest:mineral_desert_coal"] = 16,
	["glooptest:mineral_desert_iron"] = 20,
	["glooptest:mineral_emerald"] = 17,
	["glooptest:mineral_kalite"] = 20,
	["glooptest:mineral_ruby"] = 18,
	["glooptest:mineral_sapphire"] = 18,
	["glooptest:mineral_talinite"] = 20,
	["glooptest:mineral_topaz"] = 18,
	["glooptest:reinforced_crystal_glass"] = 21,
	["glooptest:rubyblock"] = 27,
	["glooptest:sapphireblock"] = 27,
	["glooptest:talinite_crystal_glass"] = 21,
	["glooptest:taliniteblock"] = 40,
	["glooptest:topazblock"] = 24,
	["mesecons_extrawires:mese_powered"] = 21,
	["moreblocks:cactus_brick"] = 13,
	["moreblocks:cactus_checker"] = 8.5,
	["moreblocks:circle_stone_bricks"] = 17,
	["moreblocks:clean_glass"] = 17,
	["moreblocks:coal_checker"] = 9.0,
	["moreblocks:coal_glass"] = 17,
	["moreblocks:coal_stone"] = 17,
	["moreblocks:coal_stone_bricks"] = 17,
	["moreblocks:glow_glass"] = 17,
	["moreblocks:grey_bricks"] = 15,
	["moreblocks:iron_checker"] = 11,
	["moreblocks:iron_glass"] = 17,
	["moreblocks:iron_stone"] = 17,
	["moreblocks:iron_stone_bricks"] = 17,
	["moreblocks:plankstone"] = 9.3,
	["moreblocks:split_stone_tile"] = 15,
	["moreblocks:split_stone_tile_alt"] = 15,
	["moreblocks:stone_tile"] = 15,
	["moreblocks:super_glow_glass"] = 17,
	["moreblocks:tar"] = 7.0,
	["moreblocks:wood_tile"] = 1.7,
	["moreblocks:wood_tile_center"] = 1.7,
	["moreblocks:wood_tile_down"] = 1.7,
	["moreblocks:wood_tile_flipped"] = 1.7,
	["moreblocks:wood_tile_full"] = 1.7,
	["moreblocks:wood_tile_left"] = 1.7,
	["moreblocks:wood_tile_right"] = 1.7,
	["moreblocks:wood_tile_up"] = 1.7,
	["moreores:mineral_mithril"] = 18,
	["moreores:mineral_silver"] = 21,
	["moreores:mithril_block"] = 26,
	["moreores:silver_block"] = 53,
	["snow:snow_brick"] = 2.8,
	["basic_materials:brass_block"] = 43,
	["technic:carbon_steel_block"] = 40,
	["technic:cast_iron_block"] = 40,
	["technic:chernobylite_block"] = 40,
	["technic:chromium_block"] = 37,
	["technic:corium_flowing"] = 40,
	["technic:corium_source"] = 80,
	["technic:granite"] = 18,
	["technic:lead_block"] = 80,
	["technic:marble"] = 18,
	["technic:marble_bricks"] = 18,
	["technic:mineral_chromium"] = 19,
	["technic:mineral_uranium"] = 71,
	["technic:mineral_zinc"] = 19,
	["technic:stainless_steel_block"] = 40,
	["technic:zinc_block"] = 36,
	["tnt:tnt"] = 11,
	["tnt:tnt_burning"] = 11,
}
local rad_resistance_group = {
	concrete = 16,
	tree = 3.4,
	uranium_block = 500,
	wood = 1.7,
}
local cache_radiation_resistance = {}
local function node_radiation_resistance(node_name)
	local resistance = cache_radiation_resistance[node_name]
	if resistance then
		return resistance
	end
	local def = minetest.registered_nodes[node_name]
	if not def then
		cache_radiation_resistance[node_name] = 0
		return 0
	end
	resistance = def.radiation_resistance or
			rad_resistance_node[node_name]
	if not resistance then
		resistance = 0
		for g, v in pairs(def.groups) do
			if v > 0 and rad_resistance_group[g] then
				resistance = resistance + rad_resistance_group[g]
			end
		end
	end
	resistance = math.sqrt(resistance)
	cache_radiation_resistance[node_name] = resistance
	return resistance
end

--[[
Radioactive nodes cause damage to nearby players.  The damage
effect depends on the intrinsic strength of the radiation source,
the distance between the source and the player, and the shielding
effect of the intervening material.  These determine a rate of damage;
total damage caused is the integral of this over time.

In the absence of effective shielding, for a specific source the
damage rate varies realistically in inverse proportion to the square
of the distance.  (Distance is measured to the player's abdomen,
not to the nominal player position which corresponds to the foot.)
However, if the player is inside a non-walkable (liquid or gaseous)
radioactive node, the nominal distance could go to zero, yielding
infinite damage.  In that case, the player's body is displacing the
radioactive material, so the effective distance should remain non-zero.
We therefore apply a lower distance bound of sqrt(0.75), which is
the maximum distance one can get from the node center within the node.

A radioactive node is identified by being in the "radioactive" group,
and the group value signifies the strength of the radiation source.
The group value is the distance from a node at which an unshielded
player will be damaged by 1 HP/s.  Or, equivalently, it is the square
root of the damage rate in HP/s that an unshielded player one node
away will take.

Shielding is assessed by adding the shielding values of all nodes
between the source node and the player, ignoring the source node itself.
As in reality, shielding causes exponential attenuation of radiation.
However, the effect is scaled down relative to real life.  A node with
radiation resistance value R yields attenuation of sqrt(R) * 0.1 nepers.
(In real life it would be about R * 0.69 nepers, by the definition
of the radiation resistance values.)  The sqrt part of this formula
scales down the differences between shielding types, reflecting the
game's simplification of making expensive materials such as gold
readily available in cubes.  The multiplicative factor in the
formula scales down the difference between shielded and unshielded
safe distances, avoiding the latter becoming impractically large.

Damage is processed at rates down to 0.2 HP/s, which in the absence of
shielding is attained at the distance specified by the "radioactive"
group value.  Computed damage rates below 0.2 HP/s result in no
damage at all to the player.  This gives the player an opportunity
to be safe, and limits the range at which source/player interactions
need to be considered.
--]]
local abdomen_offset = 1
local rad_dmg_cutoff = 0.2
local radiated_players = {}

-- radiation callback function for external use
-- (radiation counters, etc)
-- parameters: object / damage (before armor calculations)
function technic.radiation_callback(object, damage)
end

local armor_enabled = technic.config:get_bool("enable_radiation_protection")
local entity_damage = technic.config:get_bool("enable_entity_radiation_damage")
local longterm_damage = technic.config:get_bool("enable_longterm_radiation_damage")

local function apply_fractional_damage(o, dmg)
	local dmg_int = math.floor(dmg)
	-- The closer you are to getting one more damage point,
	-- the more likely it will be added.
	if math.random() < dmg - dmg_int then
		dmg_int = dmg_int + 1
	end
	if dmg_int > 0 then
		local new_hp = math.max(o:get_hp() - dmg_int, 0)
		o:set_hp(new_hp)
		return new_hp == 0
	end
	return false
end

local function calculate_base_damage(node_pos, object_pos, strength)
	local shielding = 0
	local dist = vector.distance(node_pos, object_pos)

	for ray_pos in technic.trace_node_ray(node_pos,
			vector.direction(node_pos, object_pos), dist) do
		local shield_name = minetest.get_node(ray_pos).name
		shielding = shielding + node_radiation_resistance(shield_name) * 0.025
	end

	local dmg = (strength * strength) /
		(math.max(0.75, dist * dist) * math.exp(shielding))

	if dmg < rad_dmg_cutoff then return end
	return dmg
end

local function calculate_damage_multiplier(object)
	local ag = object.get_armor_groups and object:get_armor_groups()
	if not ag then
		return 0
	end
	if ag.immortal then
		return 0
	end
	if ag.radiation then
		return 0.01 * ag.radiation
	elseif armor_enabled then
		return 0
	end
	if ag.fleshy then
		return math.sqrt(0.01 * ag.fleshy)
	end
	return 0
end

local function calculate_object_center(object)
	if object:is_player() then
		return {x=0, y=abdomen_offset, z=0}
	end
	return {x=0, y=0, z=0}
end

local function dmg_object(pos, object, strength)
	local obj_pos = vector.add(object:get_pos(), calculate_object_center(object))
	local mul
	if armor_enabled or entity_damage then
		-- we need to check may the object be damaged even if armor is disabled
		mul = calculate_damage_multiplier(object)
		if mul == 0 then
			return
		end
	end
	local dmg = calculate_base_damage(pos, obj_pos, strength)
	if not dmg then
		return
	end

	-- execute radiation callback
	technic.radiation_callback(object, dmg)

	if armor_enabled then
		dmg = dmg * mul
	end
	apply_fractional_damage(object, dmg)
	if longterm_damage and object:is_player() then
		local pn = object:get_player_name()
		radiated_players[pn] = (radiated_players[pn] or 0) + dmg
	end
end

local enable_radiation_throttling = minetest.settings:get_bool("technic.radiation.enable_throttling")

-- max lag tracker
local last_max_lag = 0

if enable_radiation_throttling then
	local function trackMaxLag()
		last_max_lag = technic.get_max_lag()
		minetest.after(5, trackMaxLag)
	end

	-- kick off lag tracking function
	trackMaxLag()
end

local rad_dmg_mult_sqrt = math.sqrt(1 / rad_dmg_cutoff)
local function dmg_abm(pos, node)
	if enable_radiation_throttling and last_max_lag > 1.5 then
		-- too much lag, skip radiation check entirely
		return
	end

	local strength = minetest.get_item_group(node.name, "radioactive")
	local max_dist = strength * rad_dmg_mult_sqrt
	for _, o in pairs(minetest.get_objects_inside_radius(pos,
			max_dist + abdomen_offset)) do
		if (entity_damage or o:is_player()) and o:get_hp() > 0 then
			dmg_object(pos, o, strength)
		end
	end
end

if minetest.settings:get_bool("enable_damage") then
	if enable_radiation_throttling then
		dmg_abm = throttle(100, dmg_abm)
	end

	minetest.register_abm({
		label = "Radiation damage",
		nodenames = {"group:radioactive"},
		interval = 1,
		chance = 2,
		action = dmg_abm,
	})

	if longterm_damage then
		minetest.register_globalstep(function(dtime)
			for pn, dmg in pairs(radiated_players) do
				dmg = dmg - (dtime / 8)
				local player = minetest.get_player_by_name(pn)
				local killed
				if player and dmg > rad_dmg_cutoff then
					killed = apply_fractional_damage(player, (dmg * dtime) / 8)
				else
					dmg = nil
				end
				-- on_dieplayer will have already set this if the player died
				if not killed then
					radiated_players[pn] = dmg
				end
			end
		end)

		minetest.register_on_dieplayer(function(player)
			radiated_players[player:get_player_name()] = nil
		end)
	end
end

-- Radioactive materials that can result from destroying a reactor
local griefing = technic.config:get_bool("enable_corium_griefing")

for _, state in pairs({"flowing", "source"}) do
	minetest.register_node("technic:corium_"..state, {
		description = S(state == "source" and "Corium Source" or "Flowing Corium"),
		drawtype = (state == "source" and "liquid" or "flowingliquid"),
		tiles = {{
			name = "technic_corium_"..state.."_animated.png",
			animation = {
				type = "vertical_frames",
				aspect_w = 16,
				aspect_h = 16,
				length = 3.0,
			},
		}},
		special_tiles = {
			{
				name = "technic_corium_"..state.."_animated.png",
				backface_culling = false,
				animation = {
					type = "vertical_frames",
					aspect_w = 16,
					aspect_h = 16,
					length = 3.0,
				},
			},
			{
				name = "technic_corium_"..state.."_animated.png",
				backface_culling = true,
				animation = {
					type = "vertical_frames",
					aspect_w = 16,
					aspect_h = 16,
					length = 3.0,
				},
			},
		},
		paramtype = "light",
		paramtype2 = (state == "flowing" and "flowingliquid" or nil),
		light_source = (state == "source" and 8 or 5),
		walkable = false,
		pointable = false,
		diggable = false,
		buildable_to = true,
		drop = "",
		drowning = 1,
		liquidtype = state,
		liquid_alternative_flowing = "technic:corium_flowing",
		liquid_alternative_source = "technic:corium_source",
		liquid_viscosity = LAVA_VISC,
		liquid_renewable = false,
		damage_per_second = 6,
		post_effect_color = {a=192, r=80, g=160, b=80},
		groups = {
			liquid = 2,
			hot = 3,
			igniter = (griefing and 1 or 0),
			radioactive = (state == "source" and 12 or 6),
			not_in_creative_inventory = (state == "flowing" and 1 or nil),
		},
	})
end

if rawget(_G, "bucket") and bucket.register_liquid then
	bucket.register_liquid(
		"technic:corium_source",
		"technic:corium_flowing",
		"technic:bucket_corium",
		"technic_bucket_corium.png",
		"Corium Bucket"
	)
end

minetest.register_node("technic:chernobylite_block", {
        description = S("Chernobylite Block"),
	tiles = {"technic_chernobylite_block.png"},
	is_ground_content = true,
	groups = {cracky=1, radioactive=4, level=2},
	sounds = default.node_sound_stone_defaults(),
	light_source = 2,
})

minetest.register_abm({
	label = "Corium: boil-off water (sources)",
	nodenames = {"group:water"},
	neighbors = {"technic:corium_source"},
	interval = 1,
	chance = 3,
	action = function(pos, node)
		minetest.remove_node(pos)
	end,
})

minetest.register_abm({
	label = "Corium: boil-off water (flowing)",
	nodenames = {"technic:corium_flowing"},
	neighbors = {"group:water"},
	interval = 1,
	chance = 3,
	action = function(pos, node)
		minetest.set_node(pos, {name="technic:chernobylite_block"})
	end,
})

minetest.register_abm({
	label = "Corium: become chernobylite",
	nodenames = {"technic:corium_flowing"},
	interval = 5,
	chance = (griefing and 10 or 1),
	action = function(pos, node)
		minetest.set_node(pos, {name="technic:chernobylite_block"})
	end,
})

if griefing then
	minetest.register_abm({
		label = "Corium: griefing",
		nodenames = {"technic:corium_source", "technic:corium_flowing"},
		interval = 4,
		chance = 4,
		action = function(pos, node)
			for _, offset in ipairs({
				vector.new(1,0,0),
				vector.new(-1,0,0),
				vector.new(0,0,1),
				vector.new(0,0,-1),
				vector.new(0,-1,0),
			}) do
				if math.random(8) == 1 then
					minetest.dig_node(vector.add(pos, offset))
				end
			end
		end,
	})
end


code:
-- This file includes the functions and data structures for registering machines and tools for LV, MV, HV types.
-- We use the technic namespace for these functions and data to avoid eventual conflict.

technic.receiver = "RE"
technic.producer = "PR"
technic.producer_receiver = "PR_RE"
technic.battery  = "BA"

technic.machines    = {}
technic.power_tools = {}
technic.networks = {}

function technic.register_tier(tier, description)
	technic.machines[tier] = {}
end

function technic.register_machine(tier, nodename, machine_type)
	if not technic.machines[tier] then
		return
	end
	technic.machines[tier][nodename] = machine_type
end

function technic.register_power_tool(craftitem, max_charge)
	technic.power_tools[craftitem] = max_charge
end

-- Utility functions. Not sure exactly what they do.. water.lua uses the two first.
function technic.get_RE_item_load(load1, max_load)
	if load1 == 0 then load1 = 65535 end
	local temp = 65536 - load1
	temp = temp / 65535 * max_load
	return math.floor(temp + 0.5)
end

function technic.set_RE_item_load(load1, max_load)
	if load1 == 0 then return 65535 end
	local temp = load1 / max_load * 65535
	temp = 65536 - temp
	return math.floor(temp)
end

-- Wear down a tool depending on the remaining charge.
function technic.set_RE_wear(itemstack, item_load, max_load)
	if (minetest.registered_items[itemstack:get_name()].wear_represents or "mechanical_wear") ~= "technic_RE_charge" then return itemstack end
	local temp
	if item_load == 0 then
		temp = 0
	else
		temp = 65536 - math.floor(item_load / max_load * 65535)
		if temp > 65535 then temp = 65535 end
		if temp < 1 then temp = 1 end
	end
	itemstack:set_wear(temp)
	return itemstack
end

repo_name: Desour/PlayMinetestServer

license: mit


code:
local S = technic.getter

local function set_can_wear(itemstack, level, max_level)
	local temp
	if level == 0 then
		temp = 0
	else
		temp = 65536 - math.floor(level / max_level * 65535)
		if temp > 65535 then temp = 65535 end
		if temp < 1 then temp = 1 end
	end
	itemstack:set_wear(temp)
end

local function get_can_level(itemstack)
	if itemstack:get_metadata() == "" then
		return 0
	else
		return tonumber(itemstack:get_metadata())
	end
end

function technic.register_can(d)
	local data = {}
	for k, v in pairs(d) do data[k] = v end
	minetest.register_tool(data.can_name, {
		description = data.can_description,
		inventory_image = data.can_inventory_image,
		stack_max = 1,
		wear_represents = "content_level",
		liquids_pointable = true,
		on_use = function(itemstack, user, pointed_thing)
			if pointed_thing.type ~= "node" then return end
			local node = minetest.get_node(pointed_thing.under)
			if node.name ~= data.liquid_source_name then return end
			local charge = get_can_level(itemstack)
			if charge == data.can_capacity then return end
			if minetest.is_protected(pointed_thing.under, user:get_player_name()) then
				minetest.log("action", user:get_player_name().." tried to take "..node.name.." at protected position "..minetest.pos_to_string(pointed_thing.under).." with a "..data.can_name)
				return
			end
			minetest.remove_node(pointed_thing.under)
			charge = charge + 1
			itemstack:set_metadata(tostring(charge))
			set_can_wear(itemstack, charge, data.can_capacity)
			return itemstack
		end,
		on_place = function(itemstack, user, pointed_thing)
			if pointed_thing.type ~= "node" then return end
			local pos = pointed_thing.under
			local node_name = minetest.get_node(pos).name
			local def = minetest.registered_nodes[node_name] or {}
			if def.on_rightclick and user and not user:get_player_control().sneak then
				return def.on_rightclick(pos, minetest.get_node(pos), user, itemstack, pointed_thing)
			end
			if not def.buildable_to or node_name == data.liquid_source_name then
				pos = pointed_thing.above
				node_name = minetest.get_node(pos).name
				def = minetest.registered_nodes[node_name] or {}
				-- Try to place node above the pointed source, or abort.
				if not def.buildable_to or node_name == data.liquid_source_name then return end
			end
			local charge = get_can_level(itemstack)
			if charge == 0 then return end
			if minetest.is_protected(pos, user:get_player_name()) then
				minetest.log("action", user:get_player_name().." tried to place "..data.liquid_source_name.." at protected position "..minetest.pos_to_string(pos).." with a "..data.can_name)
				return
			end
			minetest.set_node(pos, {name=data.liquid_source_name})
			charge = charge - 1
			itemstack:set_metadata(tostring(charge))
			set_can_wear(itemstack, charge, data.can_capacity)
			return itemstack
		end,
		on_refill = function(stack)
			stack:set_metadata(tostring(data.can_capacity))
			set_can_wear(stack, data.can_capacity, data.can_capacity)
			return stack
		end,
	})
end

technic.register_can({
	can_name = "technic:water_can",
	can_description = S("Water Can"),
	can_inventory_image = "technic_water_can.png",
	can_capacity = 16,
	liquid_source_name = "default:water_source",
	liquid_flowing_name = "default:water_flowing",
})

minetest.register_craft({
	output = 'technic:water_can 1',
	recipe = {
		{'technic:zinc_ingot', 'technic:rubber','technic:zinc_ingot'},
		{'technic:carbon_steel_ingot', '', 'technic:carbon_steel_ingot'},
		{'technic:zinc_ingot', 'technic:carbon_steel_ingot', 'technic:zinc_ingot'},
	}
})

technic.register_can({
	can_name = "technic:lava_can",
	can_description = S("Lava Can"),
	can_inventory_image = "technic_lava_can.png",
	can_capacity = 8,
	liquid_source_name = "default:lava_source",
	liquid_flowing_name = "default:lava_flowing",
})

minetest.register_craft({
	output = 'technic:lava_can 1',
	recipe = {
		{'technic:zinc_ingot', 'technic:stainless_steel_ingot','technic:zinc_ingot'},
		{'technic:stainless_steel_ingot', '', 'technic:stainless_steel_ingot'},
		{'technic:zinc_ingot', 'technic:stainless_steel_ingot', 'technic:zinc_ingot'},
	}
})

technic.register_can({
	can_name = 'technic:river_water_can',
	can_description = S("River Water Can"),
	can_inventory_image = "technic_river_water_can.png",
	can_capacity = 16,
	liquid_source_name = "default:river_water_source",
	liquid_flowing_name = "default:river_water_flowing",
})

minetest.register_craft({
	output = 'technic:river_water_can 1',
	recipe = {
		{'technic:zinc_ingot', 'technic:rubber', 'technic:zinc_ingot'},
		{'default:steel_ingot', '', 'default:steel_ingot'},
		{'technic:zinc_ingot', 'default:steel_ingot', 'technic:zinc_ingot'},
	}
})


code:
-- Configuration

local chainsaw_max_charge = 150000 -- Maximum charge of the saw
-- Gives 2500 nodes on a single charge (about 50 complete normal trees)
local chainsaw_charge_per_node = 12

local chainsaw_leaves = true -- Cut down tree leaves.
-- Leaf decay may cause slowness on large trees if this is disabled.

local chainsaw_vines = true -- Cut down vines

local timber_nodenames = {} -- Cuttable nodes

local max_saw_radius = 12 -- max x/z distance away from starting position to allow cutting
-- Prevents forest destruction, increase for extra wide trees

-- Support for nodes not in any supported node groups (tree, leaves, leafdecay, leafdecay_drop)

timber_nodenames["default:papyrus"] = true
timber_nodenames["default:cactus"] = true
timber_nodenames["default:bush_stem"] = true
timber_nodenames["default:acacia_bush_stem"] = true
timber_nodenames["default:pine_bush_stem"] = true

if minetest.get_modpath("growing_trees") then
	timber_nodenames["growing_trees:branch_sprout"] = true
	if chainsaw_leaves then
		timber_nodenames["growing_trees:leaves"] = true
	end
end

if minetest.get_modpath("snow") then
	if chainsaw_leaves then
		timber_nodenames["snow:needles"] = true
		timber_nodenames["snow:needles_decorated"] = true
		timber_nodenames["snow:star"] = true
	end
end

if minetest.get_modpath("trunks") then
	if chainsaw_leaves then
		timber_nodenames["trunks:moss"] = true
		timber_nodenames["trunks:moss_fungus"] = true
		timber_nodenames["trunks:treeroot"] = true
	end
end

local S = technic.getter

technic.register_power_tool("technic:chainsaw", chainsaw_max_charge)

-- Table for saving what was sawed down
local produced = {}

-- Save the items sawed down so that we can drop them in a nice single stack
local function handle_drops(drops)
	for _, item in ipairs(drops) do
		local stack = ItemStack(item)
		local name = stack:get_name()
		local p = produced[name]
		if not p then
			produced[name] = stack
		else
			p:set_count(p:get_count() + stack:get_count())
		end
	end
end

-- This function does all the hard work. Recursively we dig the node at hand
-- if it is in the table and then search the surroundings for more stuff to dig.
local function recursive_dig(pos, origin, remaining_charge)
	if remaining_charge < chainsaw_charge_per_node then
		return remaining_charge
	end
	local node = minetest.get_node(pos)

	if not timber_nodenames[node.name] then
		return remaining_charge
	end

	-- Wood found - cut it
	handle_drops(minetest.get_node_drops(node.name, ""))
	minetest.remove_node(pos)
	remaining_charge = remaining_charge - chainsaw_charge_per_node

	-- Check for snow on pine trees, sand/gravel on leaves, etc
	minetest.check_for_falling(pos)

	-- Check surroundings and run recursively if any charge left
	for y=-1, 1 do
		if (pos.y + y) >= origin.y then
			for x=-1, 1 do
				if (pos.x + x) <= (origin.x + max_saw_radius) and (pos.x + x) >= (origin.x - max_saw_radius) then
					for z=-1, 1 do
						if (pos.z + z) <= (origin.z + max_saw_radius) and (pos.z + z) >= (origin.z - max_saw_radius) then
							local npos = {x=pos.x+x, y=pos.y+y, z=pos.z+z}
							if remaining_charge < chainsaw_charge_per_node then
								return remaining_charge
							end
							if timber_nodenames[minetest.get_node(npos).name] then
								remaining_charge = recursive_dig(npos, origin, remaining_charge)
							end
						end
					end
				end
			end
		end
	end
	return remaining_charge
end

-- Function to randomize positions for new node drops
local function get_drop_pos(pos)
	local drop_pos = {}

	for i = 0, 8 do
		-- Randomize position for a new drop
		drop_pos.x = pos.x + math.random(-3, 3)
		drop_pos.y = pos.y - 1
		drop_pos.z = pos.z + math.random(-3, 3)

		-- Move the randomized position upwards until
		-- the node is air or unloaded.
		for y = drop_pos.y, drop_pos.y + 5 do
			drop_pos.y = y
			local node = minetest.get_node_or_nil(drop_pos)

			if not node then
				-- If the node is not loaded yet simply drop
				-- the item at the original digging position.
				return pos
			elseif node.name == "air" then
				-- Add variation to the entity drop position,
				-- but don't let drops get too close to the edge
				drop_pos.x = drop_pos.x + (math.random() * 0.8) - 0.5
				drop_pos.z = drop_pos.z + (math.random() * 0.8) - 0.5
				return drop_pos
			end
		end
	end

	-- Return the original position if this takes too long
	return pos
end

-- Chainsaw entry point
local function chainsaw_dig(pos, current_charge)
	-- Start sawing things down
	local remaining_charge = recursive_dig(pos, pos, current_charge)
	minetest.sound_play("chainsaw", {pos = pos, gain = 1.0,
			max_hear_distance = 10})

	-- Now drop items for the player
	for name, stack in pairs(produced) do
		-- Drop stacks of stack max or less
		local count, max = stack:get_count(), stack:get_stack_max()
		stack:set_count(max)
		while count > max do
			minetest.add_item(get_drop_pos(pos), stack)
			count = count - max
		end
		stack:set_count(count)
		minetest.add_item(get_drop_pos(pos), stack)
	end

	-- Clean up
	produced = {}

	return remaining_charge
end

minetest.register_tool("technic:chainsaw", {
	description = S("Chainsaw"),
	inventory_image = "technic_chainsaw.png",
	stack_max = 1,
	wear_represents = "technic_RE_charge",
	on_refill = technic.refill_RE_charge,
	on_use = function(itemstack, user, pointed_thing)
		if pointed_thing.type ~= "node" then
			return itemstack
		end

		local meta = minetest.deserialize(itemstack:get_metadata())
		if not meta or not meta.charge or
				meta.charge < chainsaw_charge_per_node then
			return
		end

		local name = user:get_player_name()
		if minetest.is_protected(pointed_thing.under, name) then
			minetest.record_protection_violation(pointed_thing.under, name)
			return
		end

		-- Send current charge to digging function so that the
		-- chainsaw will stop after digging a number of nodes
		meta.charge = chainsaw_dig(pointed_thing.under, meta.charge)
		if not technic.creative_mode then
			technic.set_RE_wear(itemstack, meta.charge, chainsaw_max_charge)
			itemstack:set_metadata(minetest.serialize(meta))
		end
		return itemstack
	end,
})

local mesecons_button = minetest.get_modpath("mesecons_button")
local trigger = mesecons_button and "mesecons_button:button_off" or "default:mese_crystal_fragment"

minetest.register_craft({
	output = "technic:chainsaw",
	recipe = {
		{"technic:stainless_steel_ingot", trigger,                      "technic:battery"},
		{"basic_materials:copper_wire",      "basic_materials:motor",              "technic:battery"},
		{"",                              "",                           "technic:stainless_steel_ingot"},
	},
	replacements = { {"basic_materials:copper_wire", "basic_materials:empty_spool"}, },

})

-- Add cuttable nodes after all mods loaded
minetest.after(0, function ()
	for k, v in pairs(minetest.registered_nodes) do
		if v.groups.tree then
			timber_nodenames[k] = true
		elseif chainsaw_leaves and (v.groups.leaves or v.groups.leafdecay or v.groups.leafdecay_drop) then
			timber_nodenames[k] = true
		elseif chainsaw_vines and v.groups.vines then
			timber_nodenames[k] = true
		end
	end
end)


code:
-- Original code comes from walkin_light mod by Echo
-- http://minetest.net/forum/viewtopic.php?id=2621

local flashlight_max_charge = 30000

local S = technic.getter

technic.register_power_tool("technic:flashlight", flashlight_max_charge)

minetest.register_alias("technic:light_off", "air")

minetest.register_tool("technic:flashlight", {
	description = S("Flashlight"),
	inventory_image = "technic_flashlight.png",
	stack_max = 1,
	wear_represents = "technic_RE_charge",
	on_refill = technic.refill_RE_charge,
})

minetest.register_craft({
	output = "technic:flashlight",
	recipe = {
		{"technic:rubber",                "default:glass",   "technic:rubber"},
		{"technic:stainless_steel_ingot", "technic:battery", "technic:stainless_steel_ingot"},
		{"",                              "technic:battery", ""}
	}
})

local player_positions = {}
local was_wielding = {}

local function check_for_flashlight(player)
	if player == nil then
		return false
	end
	local inv = player:get_inventory()
	local hotbar = inv:get_list("main")
	for i = 1, 8 do
		if hotbar[i]:get_name() == "technic:flashlight" then
			local meta = minetest.deserialize(hotbar[i]:get_metadata())
			if meta and meta.charge and meta.charge >= 2 then
				if not technic.creative_mode then
					meta.charge = meta.charge - 2;
					technic.set_RE_wear(hotbar[i], meta.charge, flashlight_max_charge)
					hotbar[i]:set_metadata(minetest.serialize(meta))
					inv:set_stack("main", i, hotbar[i])
				end
				return true
			end
		end
	end
	return false
end

minetest.register_on_joinplayer(function(player)
	local player_name = player:get_player_name()
	local pos = player:get_pos()
	local rounded_pos = vector.round(pos)
	rounded_pos.y = rounded_pos.y + 1
	player_positions[player_name] = rounded_pos
	was_wielding[player_name] = true
end)

minetest.register_on_leaveplayer(function(player)
	local player_name = player:get_player_name()
	local pos = player_positions[player_name]
	local nodename = minetest.get_node(pos).name
	if nodename == "technic:light" then
		minetest.remove_node(pos)
	end
	player_positions[player_name] = nil
end)

minetest.register_globalstep(function(dtime)
	for i, player in pairs(minetest.get_connected_players()) do
		local player_name = player:get_player_name()
		local flashlight_weared = check_for_flashlight(player)
		local pos = player:get_pos()
		local rounded_pos = vector.round(pos)
		rounded_pos.y = rounded_pos.y + 1
		local old_pos = player_positions[player_name]
		local player_moved = old_pos and not vector.equals(old_pos, rounded_pos)
		if not old_pos then
			old_pos = rounded_pos
			player_moved = true
		end

		-- Remove light, flashlight weared out or was removed from hotbar
		if was_wielding[player_name] and not flashlight_weared then
			was_wielding[player_name] = false
			local node = minetest.get_node_or_nil(old_pos)
			if node and node.name == "technic:light" then
				minetest.remove_node(old_pos)
			end
		elseif (player_moved or not was_wielding[player_name]) and flashlight_weared then
			local node = minetest.get_node_or_nil(rounded_pos)
			if node and node.name == "air" then
				minetest.set_node(rounded_pos, {name="technic:light"})
			end
			node = minetest.get_node_or_nil(old_pos)
			if node and node.name == "technic:light" then
				minetest.remove_node(old_pos)
			end
			player_positions[player_name] = rounded_pos
			was_wielding[player_name] = true
		end
	end
end)

minetest.register_node("technic:light", {
	drawtype = "glasslike",
	tiles = {"technic_light.png"},
	paramtype = "light",
	groups = {not_in_creative_inventory = 1},
	drop = "",
	walkable = false,
	buildable_to = true,
	sunlight_propagates = true,
	light_source = minetest.LIGHT_MAX,
	pointable = false,
})


code:
local path = technic.modpath.."/tools"

if technic.config:get_bool("enable_mining_drill") then
	dofile(path.."/mining_drill.lua")
end
if technic.config:get_bool("enable_mining_laser") then
	dofile(path.."/mining_lasers.lua")
end
if technic.config:get_bool("enable_flashlight") then
	dofile(path.."/flashlight.lua")
end
dofile(path.."/cans.lua")
dofile(path.."/chainsaw.lua")
dofile(path.."/tree_tap.lua")
dofile(path.."/sonic_screwdriver.lua")
dofile(path.."/prospector.lua")
dofile(path.."/vacuum.lua")

if minetest.get_modpath("screwdriver") then
	-- compatibility alias
	minetest.register_alias("technic:screwdriver", "screwdriver:screwdriver")
end


code:
local max_charge = {100000, 250000, 1000000}
local power_usage_per_node = {200, 170, 100}

local S = technic.getter

minetest.register_craft({
	output = 'technic:mining_drill',
	recipe = {
		{'default:tin_ingot',             'technic:diamond_drill_head', 'default:tin_ingot'},
		{'technic:stainless_steel_ingot', 'basic_materials:motor',              'technic:stainless_steel_ingot'},
		{'',                              'technic:red_energy_crystal', 'default:copper_ingot'},
	}
})
minetest.register_craft({
	output = 'technic:mining_drill_mk2',
	recipe = {
		{'technic:diamond_drill_head',    'technic:diamond_drill_head',   'technic:diamond_drill_head'},
		{'technic:stainless_steel_ingot', 'technic:mining_drill',         'technic:stainless_steel_ingot'},
		{'',                              'technic:green_energy_crystal', ''},
	}
})
minetest.register_craft({
	output = 'technic:mining_drill_mk3',
	recipe = {
		{'technic:diamond_drill_head',    'technic:diamond_drill_head',  'technic:diamond_drill_head'},
		{'technic:stainless_steel_ingot', 'technic:mining_drill_mk2',    'technic:stainless_steel_ingot'},
		{'',                              'technic:blue_energy_crystal', ''},
	}
})
for i = 1, 4 do
	minetest.register_craft({
		output = 'technic:mining_drill_mk3',
		recipe = {
			{'technic:diamond_drill_head',    'technic:diamond_drill_head',   'technic:diamond_drill_head'},
			{'technic:stainless_steel_ingot', 'technic:mining_drill_mk2_'..i, 'technic:stainless_steel_ingot'},
			{'',                              'technic:blue_energy_crystal',  ''},
		}
	})
end

local mining_drill_mode_text = {
	{S("Single node.")},
	{S("3 nodes deep.")},
	{S("3 nodes wide.")},
	{S("3 nodes tall.")},
	{S("3x3 nodes.")},
}

local function drill_dig_it0 (pos,player)
	if minetest.is_protected(pos, player:get_player_name()) then
		minetest.record_protection_violation(pos, player:get_player_name())
		return
	end
	local node = minetest.get_node(pos)
	if node.name == "air" or node.name == "ignore" then return end
	if node.name == "default:lava_source" then return end
	if node.name == "default:lava_flowing" then return end
	if node.name == "default:water_source" then minetest.remove_node(pos) return end
	if node.name == "default:water_flowing" then minetest.remove_node(pos) return end
	local def = minetest.registered_nodes[node.name]
	if not def then return end
	def.on_dig(pos, node, player)
end

local function drill_dig_it1 (player)
	local dir=player:get_look_dir()
	if math.abs(dir.x)>math.abs(dir.z) then
		if dir.x>0 then return 0 end
		return 1
	end
	if dir.z>0 then return 2 end
	return 3
end

local function drill_dig_it2 (pos,player)
	pos.y=pos.y+1
	drill_dig_it0 (pos,player)
	pos.z=pos.z+1
	drill_dig_it0 (pos,player)
	pos.z=pos.z-2
	drill_dig_it0 (pos,player)
	pos.z=pos.z+1
	pos.y=pos.y-1
	drill_dig_it0 (pos,player)
	pos.z=pos.z+1
	drill_dig_it0 (pos,player)
	pos.z=pos.z-2
	drill_dig_it0 (pos,player)
	pos.z=pos.z+1
	pos.y=pos.y-1
	drill_dig_it0 (pos,player)
	pos.z=pos.z+1
	drill_dig_it0 (pos,player)
	pos.z=pos.z-2
	drill_dig_it0 (pos,player)
end

local function drill_dig_it3 (pos,player)
	pos.y=pos.y+1
	drill_dig_it0 (pos,player)
	pos.x=pos.x+1
	drill_dig_it0 (pos,player)
	pos.x=pos.x-2
	drill_dig_it0 (pos,player)
	pos.x=pos.x+1
	pos.y=pos.y-1
	drill_dig_it0 (pos,player)
	pos.x=pos.x+1
	drill_dig_it0 (pos,player)
	pos.x=pos.x-2
	drill_dig_it0 (pos,player)
	pos.x=pos.x+1
	pos.y=pos.y-1
	drill_dig_it0 (pos,player)
	pos.x=pos.x+1
	drill_dig_it0 (pos,player)
	pos.x=pos.x-2
	drill_dig_it0 (pos,player)
end

local function drill_dig_it4 (pos,player)
	drill_dig_it0 (pos,player)
	pos.x=pos.x+1
	drill_dig_it0 (pos,player)
	pos.x=pos.x-2
	drill_dig_it0 (pos,player)
	pos.x=pos.x+1
	pos.z=pos.z+1
	drill_dig_it0 (pos,player)
	pos.x=pos.x+1
	drill_dig_it0 (pos,player)
	pos.x=pos.x-2
	drill_dig_it0 (pos,player)
	pos.x=pos.x+1
	pos.z=pos.z-2
	drill_dig_it0 (pos,player)
	pos.x=pos.x+1
	drill_dig_it0 (pos,player)
	pos.x=pos.x-2
	drill_dig_it0 (pos,player)
end

local function cost_to_use(drill_type, mode)
	local mult
	if mode == 1 then
		mult = 1
	elseif mode <= 4 then
		mult = 3
	else
		mult = 9
	end
	return power_usage_per_node[drill_type] * mult
end

local function drill_dig_it(pos, player, mode)
	if mode == 1 then
		drill_dig_it0(pos, player)
	end

	if mode == 2 then -- 3 deep
		local dir = drill_dig_it1(player)
		if dir == 0 then -- x+
			drill_dig_it0(pos, player)
			pos.x = pos.x + 1
			drill_dig_it0(pos, player)
			pos.x = pos.x + 1
			drill_dig_it0(pos, player)
		end
		if dir == 1 then  -- x-
			drill_dig_it0(pos, player)
			pos.x=pos.x-1
			drill_dig_it0 (pos,player)
			pos.x=pos.x-1
			drill_dig_it0 (pos,player)
		end
		if dir==2 then  -- z+
			drill_dig_it0 (pos,player)
			pos.z=pos.z+1
			drill_dig_it0 (pos,player)
			pos.z=pos.z+1
			drill_dig_it0 (pos,player)
		end
		if dir==3 then  -- z-
			drill_dig_it0 (pos,player)
			pos.z=pos.z-1
			drill_dig_it0 (pos,player)
			pos.z=pos.z-1
			drill_dig_it0 (pos,player)
		end
	end

	if mode==3 then -- 3 wide
		local dir = drill_dig_it1(player)
		if dir==0 or dir==1 then -- x
			drill_dig_it0 (pos,player)
			pos.z=pos.z+1
			drill_dig_it0 (pos,player)
			pos.z=pos.z-2
			drill_dig_it0 (pos,player)
		end
		if dir==2 or dir==3 then  -- z
			drill_dig_it0 (pos,player)
			pos.x=pos.x+1
			drill_dig_it0 (pos,player)
			pos.x=pos.x-2
			drill_dig_it0 (pos,player)
		end
	end

	if mode==4 then -- 3 tall, selected in the middle
		drill_dig_it0 (pos,player)
		pos.y=pos.y-1
		drill_dig_it0 (pos,player)
		pos.y=pos.y-1
		drill_dig_it0 (pos,player)
	end

	if mode==5 then -- 3 x 3
		local dir=player:get_look_dir()
		if math.abs(dir.y)<0.5 then
			dir=drill_dig_it1(player)
				if dir==0 or dir==1 then -- x
					drill_dig_it2(pos,player)
				end
				if dir==2 or dir==3 then  -- z
					drill_dig_it3(pos,player)
				end
		else
		drill_dig_it4(pos,player)
		end
	end

	minetest.sound_play("mining_drill", {pos = pos, gain = 1.0, max_hear_distance = 10,})
end

local function pos_is_pointable(pos)
	local node = minetest.get_node(pos)
	local nodedef = minetest.registered_nodes[node.name]
	return nodedef and nodedef.pointable
end

local function mining_drill_mk2_setmode(user,itemstack)
	local player_name=user:get_player_name()
	local item=itemstack:to_table()
	local mode = nil
	local meta=minetest.deserialize(item["metadata"])
	if meta==nil then
		meta={}
		mode=0
	end
	if meta["mode"]==nil then
		minetest.chat_send_player(player_name, S("Use while sneaking to change Mining Drill Mk%d modes."):format(2))
		meta["mode"]=0
		mode=0
	end
	mode=(meta["mode"])
	mode=mode+1
	if mode>=5 then mode=1 end
	minetest.chat_send_player(player_name, S("Mining Drill Mk%d Mode %d"):format(2, mode)..": "..mining_drill_mode_text[mode][1])
    itemstack:set_name("technic:mining_drill_mk2_"..mode);
	meta["mode"]=mode
    itemstack:set_metadata(minetest.serialize(meta))
	return itemstack
end

local function mining_drill_mk3_setmode(user,itemstack)
	local player_name=user:get_player_name()
	local item=itemstack:to_table()
	local meta=minetest.deserialize(item["metadata"])
	if meta==nil then
		meta={}
		mode=0
	end
	if meta["mode"]==nil then
		minetest.chat_send_player(player_name, S("Use while sneaking to change Mining Drill Mk%d modes."):format(3))
		meta["mode"]=0
		mode=0
	end
	mode=(meta["mode"])
	mode=mode+1
	if mode>=6 then mode=1 end
	minetest.chat_send_player(player_name, S("Mining Drill Mk%d Mode %d"):format(3, mode)..": "..mining_drill_mode_text[mode][1])
    itemstack:set_name("technic:mining_drill_mk3_"..mode);
	meta["mode"]=mode
    itemstack:set_metadata(minetest.serialize(meta))
	return itemstack
end

local function mining_drill_mk2_handler(itemstack, user, pointed_thing)
	local keys = user:get_player_control()
	local meta = minetest.deserialize(itemstack:get_metadata())
	if not meta or not meta.mode or keys.sneak then
		return mining_drill_mk2_setmode(user, itemstack)
	end
	if pointed_thing.type ~= "node" or not pos_is_pointable(pointed_thing.under) or not meta.charge then
		return
	end
	local charge_to_take = cost_to_use(2, meta.mode)
	if meta.charge >= charge_to_take then
		local pos = minetest.get_pointed_thing_position(pointed_thing, false)
		drill_dig_it(pos, user, meta.mode)
		if not technic.creative_mode then
			meta.charge = meta.charge - charge_to_take
			itemstack:set_metadata(minetest.serialize(meta))
			technic.set_RE_wear(itemstack, meta.charge, max_charge[2])
		end
	end
	return itemstack
end

local function mining_drill_mk3_handler(itemstack, user, pointed_thing)
	local keys = user:get_player_control()
	local meta = minetest.deserialize(itemstack:get_metadata())
	if not meta or not meta.mode or keys.sneak then
		return mining_drill_mk3_setmode(user, itemstack)
	end
	if pointed_thing.type ~= "node" or not pos_is_pointable(pointed_thing.under) or not meta.charge then
		return
	end
	local charge_to_take = cost_to_use(3, meta.mode)
	if meta.charge >= charge_to_take then
		local pos = minetest.get_pointed_thing_position(pointed_thing, false)
		drill_dig_it(pos, user, meta.mode)
		if not technic.creative_mode then
			meta.charge = meta.charge - charge_to_take
			itemstack:set_metadata(minetest.serialize(meta))
			technic.set_RE_wear(itemstack, meta.charge, max_charge[3])
		end
	end
	return itemstack
end

technic.register_power_tool("technic:mining_drill", max_charge[1])

minetest.register_tool("technic:mining_drill", {
	description = S("Mining Drill Mk%d"):format(1),
	inventory_image = "technic_mining_drill.png",
	stack_max = 1,
	wear_represents = "technic_RE_charge",
	on_refill = technic.refill_RE_charge,
	on_use = function(itemstack, user, pointed_thing)
		if pointed_thing.type ~= "node" or not pos_is_pointable(pointed_thing.under) then
			return itemstack
		end
		local meta = minetest.deserialize(itemstack:get_metadata())
		if not meta or not meta.charge then
			return
		end
		local charge_to_take = cost_to_use(1, 1)
		if meta.charge >= charge_to_take then
			local pos = minetest.get_pointed_thing_position(pointed_thing, false)
			drill_dig_it(pos, user, 1)
			if not technic.creative_mode then
				meta.charge = meta.charge - charge_to_take
				itemstack:set_metadata(minetest.serialize(meta))
				technic.set_RE_wear(itemstack, meta.charge, max_charge[1])
			end
		end
		return itemstack
	end,
})

minetest.register_tool("technic:mining_drill_mk2", {
	description = S("Mining Drill Mk%d"):format(2),
	inventory_image = "technic_mining_drill_mk2.png",
	wear_represents = "technic_RE_charge",
	on_refill = technic.refill_RE_charge,
	on_use = function(itemstack, user, pointed_thing)
		mining_drill_mk2_handler(itemstack, user, pointed_thing)
		return itemstack
	end,
})

technic.register_power_tool("technic:mining_drill_mk2", max_charge[2])

for i = 1, 4 do
	technic.register_power_tool("technic:mining_drill_mk2_"..i, max_charge[2])
	minetest.register_tool("technic:mining_drill_mk2_"..i, {
		description = S("Mining Drill Mk%d Mode %d"):format(2, i),
		inventory_image = "technic_mining_drill_mk2.png^technic_tool_mode"..i..".png",
		wield_image = "technic_mining_drill_mk2.png",
		wear_represents = "technic_RE_charge",
		on_refill = technic.refill_RE_charge,
		groups = {not_in_creative_inventory=1},
		on_use = function(itemstack, user, pointed_thing)
			mining_drill_mk2_handler(itemstack, user, pointed_thing)
			return itemstack
		end,
	})
end

minetest.register_tool("technic:mining_drill_mk3", {
	description = S("Mining Drill Mk%d"):format(3),
	inventory_image = "technic_mining_drill_mk3.png",
	wear_represents = "technic_RE_charge",
	on_refill = technic.refill_RE_charge,
	on_use = function(itemstack, user, pointed_thing)
	mining_drill_mk3_handler(itemstack,user,pointed_thing)
	return itemstack
	end,
})

technic.register_power_tool("technic:mining_drill_mk3", max_charge[3])

for i=1,5,1 do
	technic.register_power_tool("technic:mining_drill_mk3_"..i, max_charge[3])
	minetest.register_tool("technic:mining_drill_mk3_"..i, {
		description = S("Mining Drill Mk%d Mode %d"):format(3, i),
		inventory_image = "technic_mining_drill_mk3.png^technic_tool_mode"..i..".png",
		wield_image = "technic_mining_drill_mk3.png",
		wear_represents = "technic_RE_charge",
		on_refill = technic.refill_RE_charge,
		groups = {not_in_creative_inventory=1},
		on_use = function(itemstack, user, pointed_thing)
		mining_drill_mk3_handler(itemstack,user,pointed_thing)
		return itemstack
		end,
	})
end


code:
local mining_lasers_list = {
--	{<num>, <range of the laser shots>, <max_charge>, <charge_per_shot>},
	{"1", 7, 50000, 1000},
	{"2", 14, 200000, 2000},
	{"3", 21, 650000, 3000},
}
local allow_entire_discharging = true

local S = technic.getter

minetest.register_craft({
	output = "technic:laser_mk1",
	recipe = {
		{"default:diamond", "basic_materials:brass_ingot",        "default:obsidian_glass"},
		{"",                "basic_materials:brass_ingot",        "technic:red_energy_crystal"},
		{"",                "",                           "default:copper_ingot"},
	}
})
minetest.register_craft({
	output = "technic:laser_mk2",
	recipe = {
		{"default:diamond", "technic:carbon_steel_ingot", "technic:laser_mk1"},
		{"",                "technic:carbon_steel_ingot", "technic:green_energy_crystal"},
		{"",                "",                           "default:copper_ingot"},
	}
})
minetest.register_craft({
	output = "technic:laser_mk3",
	recipe = {
		{"default:diamond", "technic:carbon_steel_ingot", "technic:laser_mk2"},
		{"",                "technic:carbon_steel_ingot", "technic:blue_energy_crystal"},
		{"",                "",                           "default:copper_ingot"},
	}
})

local function laser_node(pos, node, player)
	local def = minetest.registered_nodes[node.name]
	if def.liquidtype ~= "none" and def.buildable_to then
		minetest.remove_node(pos)
		minetest.add_particle({
			pos = pos,
			velocity = {x = 0, y = 1.5 + math.random(), z = 0},
			acceleration = {x = 0, y = -1, z = 0},
			size = 6 + math.random() * 2,
			texture = "smoke_puff.png^[transform" .. math.random(0, 7),
		})
		return
	end
	def.on_dig(pos, node, player)
end

local keep_node = {air = true}
local function can_keep_node(name)
	if keep_node[name] ~= nil then
		return keep_node[name]
	end
	keep_node[name] = minetest.get_item_group(name, "hot") ~= 0
	return keep_node[name]
end

local function laser_shoot(player, range, particle_texture, sound)
	local player_pos = player:get_pos()
	local player_name = player:get_player_name()
	local dir = player:get_look_dir()

	local start_pos = vector.new(player_pos)
	-- Adjust to head height
	start_pos.y = start_pos.y + (player:get_properties().eye_height or 1.625)
	minetest.add_particle({
		pos = start_pos,
		velocity = dir,
		acceleration = vector.multiply(dir, 50),
		expirationtime = (math.sqrt(1 + 100 * (range + 0.4)) - 1) / 50,
		size = 1,
		texture = particle_texture .. "^[transform" .. math.random(0, 7),
	})
	minetest.sound_play(sound, {pos = player_pos, max_hear_distance = range})
	for pos in technic.trace_node_ray_fat(start_pos, dir, range) do
		if minetest.is_protected(pos, player_name) then
			minetest.record_protection_violation(pos, player_name)
			break
		end
		local node = minetest.get_node(pos)
		if node.name == "ignore"
				or not minetest.registered_nodes[node.name] then
			break
		end
		if not can_keep_node(node.name) then
			laser_node(pos, node, player)
		end
	end
end

for _, m in pairs(mining_lasers_list) do
	technic.register_power_tool("technic:laser_mk"..m[1], m[3])
	minetest.register_tool("technic:laser_mk"..m[1], {
		description = S("Mining Laser Mk%d"):format(m[1]),
		inventory_image = "technic_mining_laser_mk"..m[1]..".png",
		range = 0,
		stack_max = 1,
		wear_represents = "technic_RE_charge",
		on_refill = technic.refill_RE_charge,
		on_use = function(itemstack, user)
			local meta = minetest.deserialize(itemstack:get_metadata())
			if not meta or not meta.charge or meta.charge == 0 then
				return
			end

			local range = m[2]
			if meta.charge < m[4] then
				if not allow_entire_discharging then
					return
				end
				-- If charge is too low, give the laser a shorter range
				range = range * meta.charge / m[4]
			end
			laser_shoot(user, range, "technic_laser_beam_mk" .. m[1] .. ".png",
				"technic_laser_mk" .. m[1])
			if not technic.creative_mode then
				meta.charge = math.max(meta.charge - m[4], 0)
				technic.set_RE_wear(itemstack, meta.charge, m[3])
				itemstack:set_metadata(minetest.serialize(meta))
			end
			return itemstack
		end,
	})
end


code:
local S = technic.getter

technic.register_power_tool("technic:prospector", 300000)

local function get_metadata(toolstack)
	local m = minetest.deserialize(toolstack:get_metadata())
	if not m then m = {} end
	if not m.charge then m.charge = 0 end
	if not m.target then m.target = "" end
	if not m.look_depth then m.look_depth = 7 end
	if not m.look_radius then m.look_radius = 1 end
	return m
end

minetest.register_tool("technic:prospector", {
	description = S("Prospector"),
	inventory_image = "technic_prospector.png",
	wear_represents = "technic_RE_charge",
	on_refill = technic.refill_RE_charge,
	on_use = function(toolstack, user, pointed_thing)
		if not user or not user:is_player() or user.is_fake_player then return end
		if pointed_thing.type ~= "node" then return end
		local toolmeta = get_metadata(toolstack)
		local look_diameter = toolmeta.look_radius * 2 + 1
		local charge_to_take = toolmeta.look_depth * (toolmeta.look_depth + 1) * look_diameter * look_diameter
		if toolmeta.charge < charge_to_take then return end
		if toolmeta.target == "" then
			minetest.chat_send_player(user:get_player_name(), "Right-click to set target block type")
			return
		end
		if not technic.creative_mode then
			toolmeta.charge = toolmeta.charge - charge_to_take
			toolstack:set_metadata(minetest.serialize(toolmeta))
			technic.set_RE_wear(toolstack, toolmeta.charge, technic.power_tools[toolstack:get_name()])
		end
		local start_pos = pointed_thing.under
		local forward = minetest.facedir_to_dir(minetest.dir_to_facedir(user:get_look_dir(), true))
		local right = forward.x ~= 0 and { x=0, y=1, z=0 } or (forward.y ~= 0 and { x=0, y=0, z=1 } or { x=1, y=0, z=0 })
		local up = forward.x ~= 0 and { x=0, y=0, z=1 } or (forward.y ~= 0 and { x=1, y=0, z=0 } or { x=0, y=1, z=0 })
		local base_pos = vector.add(start_pos, vector.multiply(vector.add(right, up), - toolmeta.look_radius))
		local found = false
		for f = 0, toolmeta.look_depth-1 do
			for r = 0, look_diameter-1 do
				for u = 0, look_diameter-1 do
					if minetest.get_node(vector.add(vector.add(vector.add(base_pos, vector.multiply(forward, f)), vector.multiply(right, r)), vector.multiply(up, u))).name == toolmeta.target then found = true end
				end
			end
		end
		if math.random() < 0.02 then found = not found end
		minetest.chat_send_player(user:get_player_name(), minetest.registered_nodes[toolmeta.target].description.." is "..(found and "present" or "absent").." in "..look_diameter.."x"..look_diameter.."x"..toolmeta.look_depth.." region")
		minetest.sound_play("technic_prospector_"..(found and "hit" or "miss"), { pos = vector.add(user:get_pos(), { x = 0, y = 1, z = 0 }), gain = 1.0, max_hear_distance = 10 })
		return toolstack
	end,
	on_place = function(toolstack, user, pointed_thing)
		if not user or not user:is_player() or user.is_fake_player then return end
		local toolmeta = get_metadata(toolstack)
		local pointed
		if pointed_thing.type == "node" then
			local pname = minetest.get_node(pointed_thing.under).name
			local pdef = minetest.registered_nodes[pname]
			if pdef and (pdef.groups.not_in_creative_inventory or 0) == 0 and pname ~= toolmeta.target then
				pointed = pname
			end
		end
		local look_diameter = toolmeta.look_radius * 2 + 1
		minetest.show_formspec(user:get_player_name(), "technic:prospector_control",
			"size[7,8.5]"..
			"item_image[0,0;1,1;"..toolstack:get_name().."]"..
			"label[1,0;"..minetest.formspec_escape(toolstack:get_definition().description).."]"..
			(toolmeta.target ~= "" and
				"label[0,1.5;Current target:]"..
				"label[0,2;"..minetest.formspec_escape(minetest.registered_nodes[toolmeta.target].description).."]"..
				"item_image[0,2.5;1,1;"..toolmeta.target.."]" or
				"label[0,1.5;No target set]")..
			(pointed and
				"label[3.5,1.5;May set new target:]"..
				"label[3.5,2;"..minetest.formspec_escape(minetest.registered_nodes[pointed].description).."]"..
				"item_image[3.5,2.5;1,1;"..pointed.."]"..
				"button_exit[3.5,3.65;2,0.5;target_"..pointed..";Set target]" or
				"label[3.5,1.5;No new target available]")..
			"label[0,4.5;Region cross section:]"..
			"label[0,5;"..look_diameter.."x"..look_diameter.."]"..
			"label[3.5,4.5;Set region cross section:]"..
			"button_exit[3.5,5.15;1,0.5;look_radius_0;1x1]"..
			"button_exit[4.5,5.15;1,0.5;look_radius_1;3x3]"..
			"button_exit[5.5,5.15;1,0.5;look_radius_3;7x7]"..
			"label[0,6;Region depth:]"..
			"label[0,6.5;"..toolmeta.look_depth.."]"..
			"label[3.5,6;Set region depth:]"..
			"button_exit[3.5,6.65;1,0.5;look_depth_7;7]"..
			"button_exit[4.5,6.65;1,0.5;look_depth_14;14]"..
			"button_exit[5.5,6.65;1,0.5;look_depth_21;21]"..
			"label[0,7.5;Accuracy:]"..
			"label[0,8;98%]")
		return
	end,
})

minetest.register_on_player_receive_fields(function(user, formname, fields)
        if formname ~= "technic:prospector_control" then return false end
	if not user or not user:is_player() or user.is_fake_player then return end
	local toolstack = user:get_wielded_item()
	if toolstack:get_name() ~= "technic:prospector" then return true end
	local toolmeta = get_metadata(toolstack)
	for field, value in pairs(fields) do
		if field:sub(1, 7) == "target_" then
			toolmeta.target = field:sub(8)
		end
		if field:sub(1, 12) == "look_radius_" then
			toolmeta.look_radius = field:sub(13)
		end
		if field:sub(1, 11) == "look_depth_" then
			toolmeta.look_depth = field:sub(12)
		end
	end
	toolstack:set_metadata(minetest.serialize(toolmeta))
	user:set_wielded_item(toolstack)
	return true
end)

minetest.register_craft({
	output = "technic:prospector",
	recipe = {
		{"moreores:pick_silver", "moreores:mithril_block", "pipeworks:teleport_tube_1"},
		{"basic_materials:brass_ingot", "technic:control_logic_unit", "basic_materials:brass_ingot"},
		{"", "technic:blue_energy_crystal", ""},
	}
})


code:
local sonic_screwdriver_max_charge = 15000

local S = technic.getter

technic.register_power_tool("technic:sonic_screwdriver", sonic_screwdriver_max_charge)

-- screwdriver handler code reused from minetest/minetest_game screwdriver @a9ac480
local ROTATE_FACE = 1
local ROTATE_AXIS = 2

local function nextrange(x, max)
	x = x + 1
	if x > max then
		x = 0
	end
	return x
end

-- Handles rotation
local function screwdriver_handler(itemstack, user, pointed_thing, mode)
	if pointed_thing.type ~= "node" then
		return
	end

	local pos = pointed_thing.under

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

	local node = minetest.get_node(pos)
	local ndef = minetest.registered_nodes[node.name]
	if not ndef or not ndef.paramtype2 == "facedir" or
			(ndef.drawtype == "nodebox" and
			not ndef.node_box.type == "fixed") or
			node.param2 == nil then
		return
	end

	-- contrary to the default screwdriver, do not check for can_dig, to allow rotating machines with CLU's in them
	-- this is consistent with the previous sonic screwdriver

	local meta1 = minetest.deserialize(itemstack:get_metadata())
	if not meta1 or not meta1.charge or meta1.charge < 100 then
		return
	end

	minetest.sound_play("technic_sonic_screwdriver", {pos = pos, gain = 0.3, max_hear_distance = 10})

	-- Set param2
	local rotationPart = node.param2 % 32 -- get first 4 bits
	local preservePart = node.param2 - rotationPart

	local axisdir = math.floor(rotationPart / 4)
	local rotation = rotationPart - axisdir * 4
	if mode == ROTATE_FACE then
		rotationPart = axisdir * 4 + nextrange(rotation, 3)
	elseif mode == ROTATE_AXIS then
		rotationPart = nextrange(axisdir, 5) * 4
	end

	node.param2 = preservePart + rotationPart
	minetest.swap_node(pos, node)

	if not technic.creative_mode then
		meta1.charge = meta1.charge - 100
		itemstack:set_metadata(minetest.serialize(meta1))
		technic.set_RE_wear(itemstack, meta1.charge, sonic_screwdriver_max_charge)
	end

	return itemstack
end

minetest.register_tool("technic:sonic_screwdriver", {
	description = S("Sonic Screwdriver (left-click rotates face, right-click rotates axis)"),
	inventory_image = "technic_sonic_screwdriver.png",
	wear_represents = "technic_RE_charge",
	on_refill = technic.refill_RE_charge,
	on_use = function(itemstack, user, pointed_thing)
		screwdriver_handler(itemstack, user, pointed_thing, ROTATE_FACE)
		return itemstack
	end,
	on_place = function(itemstack, user, pointed_thing)
		screwdriver_handler(itemstack, user, pointed_thing, ROTATE_AXIS)
		return itemstack
	end,
})

minetest.register_craft({
	output = "technic:sonic_screwdriver",
	recipe = {
		{"",                         "default:diamond",        ""},
		{"mesecons_materials:fiber", "technic:battery",        "mesecons_materials:fiber"},
		{"mesecons_materials:fiber", "moreores:mithril_ingot", "mesecons_materials:fiber"}
	}
})


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

minetest.register_tool("technic:treetap", {
	description = S("Tree Tap"),
	inventory_image = "technic_tree_tap.png",
	on_use = function(itemstack, user, pointed_thing)
		if pointed_thing.type ~= "node" then
			return
		end
		local pos = pointed_thing.under
		if minetest.is_protected(pos, user:get_player_name()) then
			minetest.record_protection_violation(pos, user:get_player_name())
			return
		end
		local node = minetest.get_node(pos)
		local node_name = node.name
		if node_name ~= "moretrees:rubber_tree_trunk" then
			return
		end
		node.name = "moretrees:rubber_tree_trunk_empty"
		minetest.swap_node(pos, node)
		minetest.handle_node_drops(pointed_thing.above, {"technic:raw_latex"}, user)
		if not technic.creative_mode then
			local item_wear = tonumber(itemstack:get_wear())
			item_wear = item_wear + 819
			if item_wear > 65535 then
				itemstack:clear()
				return itemstack
			end
			itemstack:set_wear(item_wear)
		end
		return itemstack
	end,
})

minetest.register_craft({
	output = "technic:treetap",
	recipe = {
		{"pipeworks:tube_1", "group:wood",    "default:stick"},
		{"",               "default:stick", "default:stick"}
	},
})

minetest.register_craftitem("technic:raw_latex", {
	description = S("Raw Latex"),
	inventory_image = "technic_raw_latex.png",
})

if mesecons_materials then
	minetest.register_craft({
		type = "cooking",
		recipe = "technic:raw_latex",
		output = "mesecons_materials:glue",
	})
end

minetest.register_craftitem("technic:rubber", {
	description = S("Rubber Fiber"),
	inventory_image = "technic_rubber.png",
})

minetest.register_abm({
	label = "Tools: tree tap",
	nodenames = {"moretrees:rubber_tree_trunk_empty"},
	interval = 60,
	chance = 15,
	action = function(pos, node)
		if minetest.find_node_near(pos, (moretrees and moretrees.leafdecay_radius) or 5, {"moretrees:rubber_tree_leaves"}) then
			node.name = "moretrees:rubber_tree_trunk"
			minetest.swap_node(pos, node)
		end
	end
})


code:
-- Configuration
local vacuum_max_charge        = 10000 -- 10000 - Maximum charge of the vacuum cleaner
local vacuum_charge_per_object = 100   -- 100   - Capable of picking up 50 objects
local vacuum_range             = 8     -- 8     - Area in which to pick up objects

local S = technic.getter

technic.register_power_tool("technic:vacuum", vacuum_max_charge)

minetest.register_tool("technic:vacuum", {
	description = S("Vacuum Cleaner"),
	inventory_image = "technic_vacuum.png",
	stack_max = 1,
	wear_represents = "technic_RE_charge",
	on_refill = technic.refill_RE_charge,
	on_use = function(itemstack, user, pointed_thing)
		local meta = minetest.deserialize(itemstack:get_metadata())
		if not meta or not meta.charge then
			return
		end
		if meta.charge > vacuum_charge_per_object then
			minetest.sound_play("vacuumcleaner", {
				to_player = user:get_player_name(),
				gain = 0.4,
			})
		end
		local pos = user:get_pos()
		local inv = user:get_inventory()
		for _, object in ipairs(minetest.get_objects_inside_radius(pos, vacuum_range)) do
			local luaentity = object:get_luaentity()
			if not object:is_player() and luaentity and luaentity.name == "__builtin:item" and luaentity.itemstring ~= "" then
				if inv and inv:room_for_item("main", ItemStack(luaentity.itemstring)) then
					meta.charge = meta.charge - vacuum_charge_per_object
					if meta.charge < vacuum_charge_per_object then
						return
					end
					inv:add_item("main", ItemStack(luaentity.itemstring))
					minetest.sound_play("item_drop_pickup", {
						to_player = user:get_player_name(),
						gain = 0.4,
					})
					luaentity.itemstring = ""
					object:remove()
				end
			end
		end

		technic.set_RE_wear(itemstack, meta.charge, vacuum_max_charge)
		itemstack:set_metadata(minetest.serialize(meta))
		return itemstack
	end,
})

minetest.register_craft({
	output = 'technic:vacuum',
	recipe = {
		{'pipeworks:tube_1',              'pipeworks:filter', 'technic:battery'},
		{'pipeworks:tube_1',              'basic_materials:motor',    'technic:battery'},
		{'technic:stainless_steel_ingot', '',                 ''},
	}
})


code:
local function throttle(callspersecond, fn)
	local time = 0
	local count = 0

	return function(...)
		local now = minetest.get_us_time()
		if (now - time) > 1000000 then
			-- reset time
			time = now
			count = 0
		else
			-- check max calls
			count = count + 1
			if count > callspersecond then
				return
			end
		end

		return pcall(fn, ...)
	end

end

return throttle


code:
Technic chests
==============

License
-------

Copyright (C) 2012-2014 Maciej Kasatkin (RealBadAngel)

Technic chests code is licensed under the GNU LGPLv2+.

Texture licenses:

VanessaE: (WTFPL)
  * technic\_pencil\_icon.png
  * technic\_checkmark\_icon.png
  * technic\_chest\_overlay\_*.png
  * technic\_*\_chest\_lock\_overlay.png

sdzen (Elise Staudter) modified by VanessaE (CC BY-SA 3.0):
  * copper, iron, silver, gold, mithril chest textures 16x16

RealBadAngel: (WTFPL)
  * Everything else.


code:
technic.chests.groups = {snappy=2, choppy=2, oddly_breakable_by_hand=2,
		tubedevice=1, tubedevice_receiver=1}
technic.chests.groups_noinv = {snappy=2, choppy=2, oddly_breakable_by_hand=2,
		tubedevice=1, tubedevice_receiver=1, not_in_creative_inventory=1}

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

technic.chests.can_dig = function(pos, player)
	local meta = minetest.get_meta(pos)
	local inv = meta:get_inventory()
	return inv:is_empty("main")
end

-- utils for locked chest

local function inv_change(pos, count, player)
	-- Skip check for pipeworks (fake player)
	if minetest.is_player(player) and
			not default.can_interact_with_node(player, pos) then
		return 0
	end
	return count
end

function technic.chests.inv_move(pos, from_list, from_index, to_list, to_index, count, player)
	return inv_change(pos, count, player)
end
function technic.chests.inv_put(pos, listname, index, stack, player)
	return inv_change(pos, stack:get_count(), player)
end
function technic.chests.inv_take(pos, listname, index, stack, player)
	return inv_change(pos, stack:get_count(), player)
end

-- utils for protected chest

local function inv_change_protected(pos, count, player)
	if minetest.is_protected(pos, player:get_player_name()) then
		return 0
	end
	return count
end

function technic.chests.inv_move_protected(pos, from_list, from_index, to_list, to_index, count, player)
	return inv_change_protected(pos, count, player)
end
function technic.chests.inv_put_protected(pos, listname, index, stack, player)
	return inv_change_protected(pos, stack:get_count(), player)
end
function technic.chests.inv_take_protected(pos, listname, index, stack, player)
	return inv_change_protected(pos, stack:get_count(), player)
end

-- logging utils

function technic.chests.on_inv_move(pos, from_list, from_index, to_list, to_index, count, player)
	minetest.log("action", player:get_player_name()..
		" moves stuff in chest at "
		..minetest.pos_to_string(pos))
end

function technic.chests.on_inv_put(pos, listname, index, stack, player)
	minetest.log("action", player:get_player_name() ..
			" moves " .. stack:get_name() ..
			" to chest at " .. minetest.pos_to_string(pos))
end

function technic.chests.on_inv_take(pos, listname, index, stack, player)
	minetest.log("action", player:get_player_name() ..
			" takes " .. stack:get_name()  ..
			" from chest at " .. minetest.pos_to_string(pos))
end


code:
minetest.register_craft({
	output = 'technic:copper_chest 1',
	recipe = {
		{'default:copper_ingot','default:copper_ingot','default:copper_ingot'},
		{'default:copper_ingot','technic:iron_chest','default:copper_ingot'},
		{'default:copper_ingot','default:copper_ingot','default:copper_ingot'},
	}
})

minetest.register_craft({
	output = 'technic:copper_locked_chest 1',
	recipe = {
		{'default:copper_ingot','default:copper_ingot','default:copper_ingot'},
		{'default:copper_ingot','technic:iron_locked_chest','default:copper_ingot'},
		{'default:copper_ingot','default:copper_ingot','default:copper_ingot'},
	}
})

minetest.register_craft({
	output = 'technic:copper_locked_chest 1',
	type = "shapeless",
	recipe = {
		'basic_materials:padlock',
		'technic:copper_chest',
	}
})

technic.chests:register("Copper", {
	width = 12,
	height = 5,
	sort = true,
	autosort = true,
	infotext = false,
	color = false,
	locked = false,
})

technic.chests:register("Copper", {
	width = 12,
	height = 5,
	sort = true,
	autosort = true,
	infotext = false,
	color = false,
	locked = true,
})


code:
default
basic_materials
moreblocks?
moreores?
pipeworks?
intllib?
protector?
areas?
tubelib?


code:
local material_list
if minetest.get_modpath("moreores") then
	material_list = { 'silver' }
else
	-- Make the gold chest obtainable for mere mortals (the silver chest is not obtainable)
	material_list = { 'copper', 'silver' }
end

for _, material in ipairs(material_list) do
	minetest.register_craft({
		output = 'technic:gold_chest',
		recipe = {
			{'default:gold_ingot','default:gold_ingot','default:gold_ingot'},
			{'default:gold_ingot',"technic:"..material.."_chest",'default:gold_ingot'},
			{'default:gold_ingot','default:gold_ingot','default:gold_ingot'},
		}
	})

	minetest.register_craft({
		output = 'technic:gold_locked_chest',
		recipe = {
			{'default:gold_ingot','default:gold_ingot','default:gold_ingot'},
			{'default:gold_ingot',"technic:"..material.."_locked_chest",'default:gold_ingot'},
			{'default:gold_ingot','default:gold_ingot','default:gold_ingot'},
		}
	})
end

minetest.register_craft({
	output = 'technic:gold_locked_chest',
	type = "shapeless",
	recipe = {
		'basic_materials:padlock',
		'technic:gold_chest',
	}
})

technic.chests:register("Gold", {
	width = 15,
	height = 6,
	sort = true,
	autosort = true,
	infotext = true,
	color = true,
	locked = false,
})

technic.chests:register("Gold", {
	width = 15,
	height = 6,
	sort = true,
	autosort = true,
	infotext = true,
	color = true,
	locked = true,
})


code:
-- Minetest 0.4.6 mod: technic_chests
-- namespace: technic
-- (c) 2012-2013 by RealBadAngel <mk@realbadangel.pl>

local modpath = minetest.get_modpath("technic_chests")

technic = rawget(_G, "technic") or {}
technic.chests = {}

dofile(modpath.."/common.lua")
dofile(modpath.."/register.lua")
dofile(modpath.."/iron_chest.lua")
dofile(modpath.."/copper_chest.lua")
dofile(modpath.."/silver_chest.lua")
dofile(modpath.."/gold_chest.lua")
dofile(modpath.."/mithril_chest.lua")

-- undo all of the locked wooden chest recipes created by default and
-- moreblocks, and just make them use a padlock.

if minetest.get_modpath("moreblocks") then
	minetest.clear_craft({
		type = "shapeless",
		recipe = {
			"default:chest",
			"default:gold_ingot",
		}
	})

	minetest.clear_craft({
		type = "shapeless",
		recipe = {
			"default:chest",
			"default:bronze_ingot",
		}
	})

	minetest.clear_craft({
		type = "shapeless",
		recipe = {
			"default:chest",
			"default:copper_ingot",
		}
	})
end

minetest.clear_craft({
	type = "shapeless",
	recipe = {
		"default:chest",
		"default:steel_ingot",
	}
})

minetest.clear_craft({output = "default:chest_locked"})

minetest.register_craft({
	output = "default:chest_locked",
	recipe = {
		{ "group:wood", "group:wood", "group:wood" },
		{ "group:wood", "basic_materials:padlock", "group:wood" },
		{ "group:wood", "group:wood", "group:wood" }
	}
})

minetest.register_craft({
	output = "default:chest_locked",
	type = "shapeless",
	recipe = {
		"default:chest",
		"basic_materials:padlock"
	}
})

minetest.register_lbm({
	name = "technic_chests:fix_wooden_chests",
	nodenames = {"default:chest"},
	action = function(pos, node)
		local meta = minetest.get_meta(pos)
		meta:set_string("formspec", "")
	end
})


code:
local cast_iron_ingot
if minetest.get_modpath("technic_worldgen") then
	cast_iron_ingot = "technic:cast_iron_ingot"
else
	cast_iron_ingot = "default:steel_ingot"
end

minetest.register_craft({
	output = 'technic:iron_chest 1',
	recipe = {
		{cast_iron_ingot,cast_iron_ingot,cast_iron_ingot},
		{cast_iron_ingot,'default:chest',cast_iron_ingot},
		{cast_iron_ingot,cast_iron_ingot,cast_iron_ingot},
	}
})

minetest.register_craft({
	output = 'technic:iron_locked_chest 1',
	recipe = {
		{cast_iron_ingot,cast_iron_ingot,cast_iron_ingot},
		{cast_iron_ingot,'default:chest_locked',cast_iron_ingot},
		{cast_iron_ingot,cast_iron_ingot,cast_iron_ingot},
	}
})

minetest.register_craft({
	output = 'technic:iron_locked_chest 1',
	type = "shapeless",
	recipe = {
		'basic_materials:padlock',
		'technic:iron_chest',
	}
})

technic.chests:register("Iron", {
	width = 9,
	height = 5,
	sort = true,
	autosort = false,
	infotext = false,
	color = false,
	locked = false,
})

technic.chests:register("Iron", {
	width = 9,
	height = 5,
	sort = true,
	autosort = false,
	infotext = false,
	color = false,
	locked = true,
})


code:
# German Translation for technic_chests
# Deutsche Übersetzung von technic_chests
# by Xanthin

%s Chest = %struhe
%s Locked Chest = Verschlossene %struhe
%s Locked Chest (owned by %s) = Verschlossene %struhe (gehoert %s)
Color Filter: %s = Farbfilter: %s
Edit chest description: = Bearbeite die Beschreibung

# Colors
Black = Schwarz
Blue = Blau
Brown = Braun
Cyan = Tuerkis
Dark Green = Dunkelgruen
Dark Grey = Dunkelgrau
Green = Gruen
Grey = Grau
Magenta = Magenta
Orange = Orange
Pink = Rosa
Red = Rot
Violet = Violett
White = Weiss
Yellow = Gelb
None = Farblos

# Materials
Copper = Kupfer
Gold = Gold
Iron = Eisen
Mithril = Mithril
Silver = Silber
Wooden = Holz

# Sorting
Sort =
Auto-sort is %s =
Off =
On =


code:
# technic_chests translation template

%s Chest = Cofre de %s
%s Locked Chest = Cofre de %s Bloqueado
%s Locked Chest (owned by %s) = Cofre de %s Bloqueado (propiedad de %s)
Color Filter: %s = Filtro por Color: %s
Edit chest description: = Editar la descripción del cofre

# Colors
Black = Negro
Blue = Azul
Brown = Café
Cyan = Cian
Dark Green = Verde Oscuro
Dark Grey = Gris Oscuro
Green = Verde
Grey = Gris
Magenta = Magenta
Orange = Naranja
Pink = Rosado
Red = Rojo
Violet = Violeta
White = Blanco
Yellow = Amarillo
None = Ninguno

# Materials
Copper = Cobre
Gold = Oro
Iron = Hierro
Mithril = Mitrilo
Silver = Plata
Wooden = Madera

# Sorting
Sort = Ordenar
Auto-sort is %s = El orden Automático esta %s
Off = Apagado
On = Encendido


code:
# technic_chests translation template

%s Chest                      = Coffre en %s
%s Locked Chest               = Coffre verrouillé en %s
%s Locked Chest (owned by %s) = Coffre verrouillé en %s (appartient à %s)
Color Filter: %s              = Filtre couleur : %s
Edit chest description:       = Editer la description du coffre

# Colors
Black                         = Noir
Blue                          = Bleu
Brown                         = Marron
Cyan                          = Cyan
Dark Green                    = Vert foncé
Dark Grey                     = Gris foncé
Green                         = Vert
Grey                          = Gris
Magenta                       = Magenta
Orange                        = Orange
Pink                          = Rose
Red                           = Rouge
Violet                        = Violet
White                         = Blanc
Yellow                        = Jaune
None                          = Rien

# Materials
Copper                        = cuivre
Gold                          = or
Iron                          = fer
Mithril                       = mithril
Silver                        = argent
Wooden                        = bois

# Sorting
Sort                          = Tri
Auto-sort is %s               = Tri automatique %s
Off                           = désactivé
On                            = activé


code:
# Polish Translation for technic_chests
# Polskie tłumaczenie technic_chests
# by mat9117

%s Chest = %s Skrzynia
%s Locked Chest = %s Zamknięta skrzynia
%s Locked Chest (owned by %s) = %s Zamknięta skrzynia (należy do %s)
Color Filter: %s = Filtr kolorów: %s
Edit chest description: = Edytuj opis skrzyni

# Colors
Black = Czarny
Blue = Niebieski
Brown = Brązowy
Cyan = Cyan
Dark Green = Ciemnozielony
Dark Grey = Ciemnoszary
Green = Zielony
Grey = Szary
Magenta = Magenta
Orange = Pomarańczowy
Pink = Różowy
Red = Czerwony
Violet = Fioletowy
White = Biały
Yellow = Żółty
None = Żaden

# Materials
Copper = Miedziana
Gold = Złota
Iron = Żelazna
Mithril = Mithrilowa
Silver = Srebrna
Wooden = Drewniana

# Sorting
Sort = Sortuj
Auto-sort is %s = Autosortowanie jest %s
Off = Wyłaczone
On = Włączone


code:
# Braziliam portuguese translation for technic_chests
# Tradução portuguesa brasileira para technic_chests
# By Sires

%s Chest = Baú %s
%s Locked Chest = Baú Trancado %s
%s Locked Chest (owned by %s) = Baú Trancado %s (pertence a/à %s)
Color Filter: %s = Filtro de Cor: %s
Edit chest description: = Editar descrição do baú:

# Colors
Black = Preto
Blue = Azul
Brown = Marrom
Cyan = Ciano
Dark Green = Verde Escuro
Dark Grey = Cinza Escuro
Green = Verde
Grey = Cinza
Magenta = Magenta
Orange = Laranja
Pink = Rosa
Red = Vermelho
Violet = Violeta
White = Branco
Yellow = Amarelo
None = Nada

# Materials
Copper = Cobre
Gold = Ouro
Iron = Ferro
Mithril = Mithril
Silver = Prata
Wooden = de Madeira

# Sorting
Sort = Ordenar
Auto-sort is %s = Auto-ordenação está %s
Off = Desligada
On = Ligada


code:
# technic_chests translation template

%s Chest =
%s Locked Chest =
%s Locked Chest (owned by %s) =
Color Filter: %s =
Edit chest description: =

# Colors
Black =
Blue =
Brown =
Cyan =
Dark Green =
Dark Grey =
Green =
Grey =
Magenta =
Orange =
Pink =
Red =
Violet =
White =
Yellow =
None =

# Materials
Copper =
Gold =
Iron =
Mithril =
Silver =
Wooden =

# Sorting
Sort =
Auto-sort is %s =
Off =
On =


code:
# Turkish translation
# mahmutelmas06@hotmail.com
# Türkçe çeviri

%s Chest = %s Sandık
%s Locked Chest = %s Kilitli sandık
%s Locked Chest (owned by %s) = %s Kilitli sandık (Sahibi %s)
Color Filter: %s = Renk süzgeci: %s
Edit chest description: = Sandık bilgilerini düzenle

# Colors - Renkler
Black = Siyah
Blue = Mavi
Brown = Kahverengi
Cyan = Cam göbeği
Dark Green = Koyu yeşil
Dark Grey = Koyu gri
Green = Yeşil
Grey = Gri
Magenta = Mor
Orange = Turuncu
Pink = Pembe
Red = Kırmızı
Violet = Koyu mor
White = Beyaz
Yellow = Sarı
None = Hiç

# Materials - Metaryeller
Copper = Bakır
Gold = Altın
Iron = Demir
Mithril = Mithril
Silver = Gümüş
Wooden = Ahşap

# Sorting  - Sıralama
Sort = Sırala
Auto-sort is %s = Otomatik sıralama %s
Off = Kapalı
On =  Açık


code:
if minetest.get_modpath("moreores") then
	minetest.register_craft({
		output = 'technic:mithril_chest 1',
		recipe = {
			{'moreores:mithril_ingot','moreores:mithril_ingot','moreores:mithril_ingot'},
			{'moreores:mithril_ingot','technic:gold_chest','moreores:mithril_ingot'},
			{'moreores:mithril_ingot','moreores:mithril_ingot','moreores:mithril_ingot'},
		}
	})

	minetest.register_craft({
		output = 'technic:mithril_locked_chest 1',
		recipe = {
			{'moreores:mithril_ingot','moreores:mithril_ingot','moreores:mithril_ingot'},
			{'moreores:mithril_ingot','technic:gold_locked_chest','moreores:mithril_ingot'},
			{'moreores:mithril_ingot','moreores:mithril_ingot','moreores:mithril_ingot'},
		}
	})
end

minetest.register_craft({
	output = 'technic:mithril_locked_chest 1',
	type = "shapeless",
	recipe = {
		'basic_materials:padlock',
		'technic:mithril_chest',
	}
})

-- plain chest
technic.chests:register("Mithril", {
	width = 15,
	height = 6,
	sort = true,
	autosort = true,
	infotext = false,
	color = false,
	locked = false,
})

-- owned locked chest
technic.chests:register("Mithril", {
	width = 15,
	height = 6,
	sort = true,
	autosort = true,
	infotext = false,
	color = false,
	locked = true,
})

if minetest.get_modpath("protector") or minetest.get_modpath("areas") then
	-- protected chest (works with any protection mod that overrides minetest.is_protected)
	technic.chests:register("Mithril", {
		width = 15,
		height = 6,
		sort = true,
		autosort = true,
		infotext = false,
		color = false,
		protected = true,
	})

	minetest.register_craft({
		output = 'technic:mithril_protected_chest 1',
		recipe = {
			{'basic_materials:padlock'},
			{'technic:mithril_locked_chest'},
		}
	})
end


code:
name = technic_chests
depends = default, basic_materials
optional_depends = moreblocks, moreores, pipeworks, intllib, tubelib


code:
local S = rawget(_G, "intllib") and intllib.Getter() or function(s) return s end

local pipeworks = rawget(_G, "pipeworks")
local fs_helpers = rawget(_G, "fs_helpers")
local tubelib_exists = minetest.global_exists("tubelib")

local has_protector_mod = minetest.get_modpath("protector")

local allow_label = ""
local tube_entry = ""
local shift_edit_field = 0

if not minetest.get_modpath("pipeworks") then
	-- Pipeworks is not installed. Simulate using a dummy table...
	pipeworks = {}
	fs_helpers = {}
	local pipeworks_meta = {}
	setmetatable(pipeworks, pipeworks_meta)
	local dummy = function()
		end
	pipeworks_meta.__index = function(table, key)
			print("[technic_chests] WARNING: variable or method '"..key.."' not present in dummy pipeworks table - assuming it is a method...")
			pipeworks[key] = dummy
			return dummy
		end
	pipeworks.after_place = dummy
	pipeworks.after_dig = dummy
	fs_helpers.cycling_button = function() return "" end
else
	fs_helpers = pipeworks.fs_helpers
	allow_label = "label[0.9,0.36;Allow splitting incoming stacks from tubes]"
	shift_edit_field = 3
	tube_entry = "^pipeworks_tube_connection_metallic.png"
end

local chest_mark_colors = {
	{"black", S("Black")},
	{"blue", S("Blue")},
	{"brown", S("Brown")},
	{"cyan", S("Cyan")},
	{"dark_green", S("Dark Green")},
	{"dark_grey", S("Dark Grey")},
	{"green", S("Green")},
	{"grey", S("Grey")},
	{"magenta", S("Magenta")},
	{"orange", S("Orange")},
	{"pink", S("Pink")},
	{"red", S("Red")},
	{"violet", S("Violet")},
	{"white", S("White")},
	{"yellow", S("Yellow")},
}

local function colorid_to_postfix(id)
	return chest_mark_colors[id] and "_"..chest_mark_colors[id][1] or ""
end

local function get_color_buttons(coleft, lotop)
	local buttons_string = ""
	for y = 0, 3 do
		for x = 0, 3 do
			local file_name = "technic_colorbutton"..(y * 4 + x)..".png"
			buttons_string = buttons_string.."image_button["
				..(coleft + 0.1 + x * 0.7)..","..(lotop + 0.1 + y * 0.7)
				..";0.8,0.8;"..file_name..";color_button"
				..(y * 4 + x + 1)..";]"
		end
	end
	return buttons_string
end

local function check_color_buttons(pos, meta, chest_name, fields)
	for i = 1, 16 do
		if fields["color_button"..i] then
			local node = minetest.get_node(pos)
			node.name = chest_name..colorid_to_postfix(i)
			minetest.swap_node(pos, node)
			meta:set_string("color", i)
			return
		end
	end
end

local function set_formspec(pos, data, page)
	local meta = minetest.get_meta(pos)
	local formspec = data.base_formspec
	formspec = formspec..fs_helpers.cycling_button(
				meta,
				"image_button[0,0.35;1,0.6",
				"splitstacks",
				{
					pipeworks.button_off,
					pipeworks.button_on
				}
			)..allow_label

	if data.autosort then
		local status = meta:get_int("autosort")
		formspec = formspec.."button["..(data.hileft+2)..","..(data.height+1.1)..";3,0.8;autosort_to_"..(1-status)..";"..S("Auto-sort is %s"):format(status == 1 and S("On") or S("Off")).."]"
	end
	if data.infotext then
		local formspec_infotext = minetest.formspec_escape(meta:get_string("infotext"))
		if page == "main" then
			formspec = formspec.."image_button["..(shift_edit_field+data.hileft+2.1)..",0.1;0.8,0.8;"
					.."technic_pencil_icon.png;edit_infotext;]"
					.."label["..(shift_edit_field+data.hileft+3)..",0;"..formspec_infotext.."]"
		elseif page == "edit_infotext" then
			formspec = formspec.."image_button["..(shift_edit_field+data.hileft+2.1)..",0.1;0.8,0.8;"
					.."technic_checkmark_icon.png;save_infotext;]"
					.."field["..(shift_edit_field+data.hileft+3.3)..",0.2;4.8,1;"
					.."infotext_box;"..S("Edit chest description:")..";"
					..formspec_infotext.."]"
		end
	end
	if data.color then
		local colorID = meta:get_int("color")
		local colorName
		if chest_mark_colors[colorID] then
			colorName = chest_mark_colors[colorID][2]
		else
			colorName = S("None")
		end
		formspec = formspec.."label["..(data.coleft+0.2)..","..(data.lotop+3)..";"..S("Color Filter: %s"):format(colorName).."]"
	end
	meta:set_string("formspec", formspec)
end

local function sort_inventory(inv)
	local inlist = inv:get_list("main")
	local typecnt = {}
	local typekeys = {}
	for _, st in ipairs(inlist) do
		if not st:is_empty() then
			local n = st:get_name()
			local w = st:get_wear()
			local m = st:get_metadata()
			local k = string.format("%s %05d %s", n, w, m)
			if not typecnt[k] then
				typecnt[k] = {st}
				table.insert(typekeys, k)
			else
				table.insert(typecnt[k], st)
			end
		end
	end
	table.sort(typekeys)
	inv:set_list("main", {})
	for _, k in ipairs(typekeys) do
		for _, item in ipairs(typecnt[k]) do
			inv:add_item("main", item)
		end
	end
end

local function get_receive_fields(name, data, locked)
	local lname = name:lower()
	return function(pos, formname, fields, sender)
		local meta = minetest.get_meta(pos)

		if locked then
			-- locked chest, check accessing player
			local owner = meta:get_string("owner")
			if owner ~= sender:get_player_name() then
				-- non-owner, abort
				return
			end
		end

		local page = "main"

		local owner = meta:get_string("owner")
		if owner ~= "" then
			-- prevent modification of locked chests
			if owner ~= sender:get_player_name() then return end
		elseif not fields.quit then
			-- prevent modification of protected chests
			if minetest.is_protected(pos, sender:get_player_name()) then return end
		end

		if fields.sort or (data.autosort and fields.quit and meta:get_int("autosort") == 1) then
			sort_inventory(meta:get_inventory())
		end
		if fields.edit_infotext then
			page = "edit_infotext"
		end
		if fields.autosort_to_1 then meta:set_int("autosort", 1) end
		if fields.autosort_to_0 then meta:set_int("autosort", 0) end
		if fields.infotext_box then
			meta:set_string("infotext", fields.infotext_box)
		end
		if data.color then
			-- This sets the node
			local nn = "technic:"..lname..(data.locked and "_locked" or "").."_chest"
			check_color_buttons(pos, meta, nn, fields)
		end
		if fields["fs_helpers_cycling:0:splitstacks"]
		  or fields["fs_helpers_cycling:1:splitstacks"] then
			if not pipeworks.may_configure(pos, sender) then return end
			fs_helpers.on_receive_fields(pos, fields)
		end

		meta:get_inventory():set_size("main", data.width * data.height)
		set_formspec(pos, data, page)
	end
end

function technic.chests:definition(name, data)
	local lname = name:lower()
	name = S(name)
	local d = {}
	for k, v in pairs(data) do d[k] = v end
	data = d

	data.lowidth = 8
	data.ovwidth = math.max(data.lowidth, data.width)
	data.hileft = (data.ovwidth - data.width) / 2
	data.loleft = (data.ovwidth - data.lowidth) / 2
	if data.color then
		if data.lowidth + 3 <= data.ovwidth then
			data.coleft = data.ovwidth - 3
			if data.loleft + data.lowidth > data.coleft then
				data.loleft = data.coleft - data.lowidth
			end
		else
			data.loleft = 0
			data.coleft = data.lowidth
			data.ovwidth = data.lowidth + 3
		end
	end
	data.lotop = data.height + 2
	data.ovheight = data.lotop + 4

	local locked_after_place = nil
	local front = {"technic_"..lname.."_chest_front.png"}
	data.base_formspec = "size["..data.ovwidth..","..data.ovheight.."]"..
			"label[0,0;"..S("%s Chest"):format(name).."]"..
			"list[context;main;"..data.hileft..",1;"..data.width..","..data.height..";]"..
			"list[current_player;main;"..data.loleft..","..data.lotop..";8,4;]"..
			"background[-0.19,-0.25;"..(data.ovwidth+0.4)..","..(data.ovheight+0.75)..";technic_chest_form_bg.png]"..
			"background["..data.hileft..",1;"..data.width..","..data.height..";technic_"..lname.."_chest_inventory.png]"..
			"background["..data.loleft..","..data.lotop..";8,4;technic_main_inventory.png]"..
			"listring[]"

	if data.sort then
		data.base_formspec = data.base_formspec.."button["..data.hileft..","..(data.height+1.1)..";1,0.8;sort;"..S("Sort").."]"
	end
	if data.color then
		data.base_formspec = data.base_formspec..get_color_buttons(data.coleft, data.lotop)
	end

	if data.locked then
		locked_after_place = function(pos, placer)
			local meta = minetest.get_meta(pos)
			meta:set_string("owner", placer:get_player_name() or "")
			meta:set_string("infotext",
					S("%s Locked Chest (owned by %s)")
					:format(name, meta:get_string("owner")))
			pipeworks.after_place(pos)
		end
	else
		locked_after_place = pipeworks.after_place
	end

	if data.locked then
		table.insert(front, "technic_"..lname.."_chest_lock_overlay.png")
	end

	if data.protected and has_protector_mod then
		-- use overlay from protector mod
		table.insert(front, "protector_logo.png")
	end

	local desc
	if data.locked then
		desc = S("%s Locked Chest"):format(name)
	elseif data.protected then
		desc = S("%s Protected Chest"):format(name)
	else
		desc = S("%s Chest"):format(name)
	end

	local tentry = tube_entry
	if tube_entry ~= "" then
		if lname == "wooden" then
			tentry = "^pipeworks_tube_connection_wooden.png"
		elseif lname == "mithril" then
			tentry = "^pipeworks_tube_connection_stony.png"
		end
	end
	local def = {
		description = desc,
		tiles = {
			"technic_"..lname.."_chest_top.png"..tentry,
			"technic_"..lname.."_chest_top.png"..tentry,
			"technic_"..lname.."_chest_side.png"..tentry,
			"technic_"..lname.."_chest_side.png"..tentry,
			"technic_"..lname.."_chest_side.png"..tentry,
			table.concat(front, "^")
		},
		paramtype2 = "facedir",
		groups = self.groups,
		tube = self.tube,
		legacy_facedir_simple = true,
		sounds = default.node_sound_wood_defaults(),
		after_place_node = locked_after_place,
		after_dig_node = pipeworks.after_dig,

		on_construct = function(pos)
			local meta = minetest.get_meta(pos)
			meta:set_string("infotext", S("%s Chest"):format(name))
			set_formspec(pos, data, "main")
			local inv = meta:get_inventory()
			inv:set_size("main", data.width * data.height)
		end,
		can_dig = self.can_dig,
		on_receive_fields = get_receive_fields(name, data, data.locked),
		on_metadata_inventory_move = self.on_inv_move,
		on_metadata_inventory_put = self.on_inv_put,
		on_metadata_inventory_take = self.on_inv_take,
		on_blast = function(pos)
			local drops = {}
			default.get_inventory_drops(pos, "main", drops)
			drops[#drops+1] = "technic:"..name:lower()..(data.locked and "_locked" or "").."_chest"
			minetest.remove_node(pos)
			return drops
		end,
	}
	if data.locked then
		def.allow_metadata_inventory_move = self.inv_move
		def.allow_metadata_inventory_put = self.inv_put
		def.allow_metadata_inventory_take = self.inv_take
		def.on_blast = function() end
		def.can_dig = function(pos,player)
			local meta = minetest.get_meta(pos);
			local inv = meta:get_inventory()
			return inv:is_empty("main") and player and player:is_player() and default.can_interact_with_node(player, pos)
		end
		def.on_skeleton_key_use = function(pos, player, newsecret)
			local meta = minetest.get_meta(pos)
			local owner = meta:get_string("owner")
			local name = player:get_player_name()

			-- verify placer is owner of lockable chest
			if owner ~= name then
				minetest.record_protection_violation(pos, name)
				minetest.chat_send_player(name, "You do not own this chest.")
				return nil
			end

			local secret = meta:get_string("key_lock_secret")
			if secret == "" then
				secret = newsecret
				meta:set_string("key_lock_secret", secret)
			end

			return secret, "a locked chest", owner
		end
	elseif data.protected then
		def.allow_metadata_inventory_move = self.inv_move_protected
		def.allow_metadata_inventory_put = self.inv_put_protected
		def.allow_metadata_inventory_take = self.inv_take_protected
		def.on_blast = function() end
		def.can_dig = function(pos,player)
			local meta = minetest.get_meta(pos);
			local inv = meta:get_inventory()
			return inv:is_empty("main") and not minetest.is_protected(pos, player:get_player_name())
		end
	end
	return def
end

local _TUBELIB_CALLBACKS = {
	on_pull_item = function(pos, side, player_name)
		if not minetest.is_protected(pos, player_name) then
			local inv = minetest.get_meta(pos):get_inventory()
			for _, stack in pairs(inv:get_list("main")) do
				if not stack:is_empty() then
					return inv:remove_item("main", stack:get_name())
				end
			end
		end
		return nil
	end,
	on_push_item = function(pos, side, item, player_name)
		local inv = minetest.get_meta(pos):get_inventory()
		if inv:room_for_item("main", item) then
			inv:add_item("main", item)
			return true
		end
		return false
	end,
	on_unpull_item = function(pos, side, item, player_name)
		local inv = minetest.get_meta(pos):get_inventory()
		if inv:room_for_item("main", item) then
			inv:add_item("main", item)
			return true
		end
		return false
	end,
}

function technic.chests:register(name, data)
	local def = technic.chests:definition(name, data)

	-- prefix
	local nn = "technic:"..name:lower()

	if data.locked then
		-- locked chest
		nn = nn .. "_locked"

	elseif data.protected then
		-- protected chest
		nn = nn .. "_protected"
	end

	-- suffix
	nn = nn .. "_chest"

	minetest.register_node(":"..nn, def)

	if tubelib_exists then
		tubelib.register_node(nn, {}, _TUBELIB_CALLBACKS)
	end

	if data.color then
		local mk_front
		if string.find(def.tiles[6], "%^") then
			mk_front = function (overlay) return def.tiles[6]:gsub("%^", "^"..overlay.."^") end
		else
			mk_front = function (overlay) return def.tiles[6].."^"..overlay end
		end
		for i = 1, 15 do
			local postfix = colorid_to_postfix(i)
			local colordef = {}
			for k, v in pairs(def) do
				colordef[k] = v
			end
			colordef.drop = nn
			colordef.groups = self.groups_noinv
			colordef.tiles = { def.tiles[1], def.tiles[2], def.tiles[3], def.tiles[4], def.tiles[5], mk_front("technic_chest_overlay"..postfix..".png") }
			minetest.register_node(":"..nn..postfix, colordef)
			if tubelib_exists then
				tubelib.register_node(nn..postfix, {}, _TUBELIB_CALLBACKS)
			end
		end
	end
end


code:
if minetest.get_modpath("moreores") then
	minetest.register_craft({
		output = 'technic:silver_chest',
		recipe = {
			{'moreores:silver_ingot','moreores:silver_ingot','moreores:silver_ingot'},
			{'moreores:silver_ingot','technic:copper_chest','moreores:silver_ingot'},
			{'moreores:silver_ingot','moreores:silver_ingot','moreores:silver_ingot'},
		}
	})

	minetest.register_craft({
		output = 'technic:silver_locked_chest',
		recipe = {
			{'moreores:silver_ingot','moreores:silver_ingot','moreores:silver_ingot'},
			{'moreores:silver_ingot','technic:copper_locked_chest','moreores:silver_ingot'},
			{'moreores:silver_ingot','moreores:silver_ingot','moreores:silver_ingot'},
		}
	})
end

minetest.register_craft({
	output = 'technic:silver_locked_chest',
	type = "shapeless",
	recipe = {
		'basic_materials:padlock',
		'technic:silver_chest',
	}
})

technic.chests:register("Silver", {
	width = 12,
	height = 6,
	sort = true,
	autosort = true,
	infotext = true,
	color = false,
	locked = false,
})

technic.chests:register("Silver", {
	width = 12,
	height = 6,
	sort = true,
	autosort = true,
	infotext = true,
	color = false,
	locked = true,
})