code:
-- See also technic/doc/api.md

technic.networks = {}
technic.cables = {}
technic.redundant_warn = {}

local switch_max_range = tonumber(minetest.settings:get("technic.switch_max_range") or "256")

local mesecons_path = minetest.get_modpath("mesecons")
local digilines_path = minetest.get_modpath("digilines")

local S = technic.getter

local cable_entry = "^technic_cable_connection_overlay.png"

minetest.register_craft({
	output = "technic:switching_station",
	recipe = {
		{"",                     "technic:lv_transformer", ""},
		{"default:copper_ingot", "technic:machine_casing", "default:copper_ingot"},
		{"technic:lv_cable",     "technic:lv_cable",       "technic:lv_cable"}
	}
})

local mesecon_def
if mesecons_path then
	mesecon_def = {effector = {
		rules = mesecon.rules.default,
	}}
end

minetest.register_node("technic:switching_station",{
	description = S("Switching Station"),
	tiles  = {
		"technic_water_mill_top_active.png",
		"technic_water_mill_top_active.png"..cable_entry,
		"technic_water_mill_top_active.png",
		"technic_water_mill_top_active.png",
		"technic_water_mill_top_active.png",
		"technic_water_mill_top_active.png"},
	groups = {snappy=2, choppy=2, oddly_breakable_by_hand=2, technic_all_tiers=1},
	connect_sides = {"bottom"},
	sounds = default.node_sound_wood_defaults(),
	on_construct = function(pos)
		local meta = minetest.get_meta(pos)
		meta:set_string("infotext", S("Switching Station"))
		meta:set_string("active", 1)
		meta:set_string("channel", "switching_station"..minetest.pos_to_string(pos))
		meta:set_string("formspec", "field[channel;Channel;${channel}]")
		local poshash = minetest.hash_node_position(pos)
		technic.redundant_warn.poshash = nil
	end,
	after_dig_node = function(pos)
		pos.y = pos.y - 1
		local poshash = minetest.hash_node_position(pos)
		technic.redundant_warn.poshash = nil
	end,
	on_receive_fields = function(pos, formname, fields, sender)
		if not fields.channel then
			return
		end
		local plname = sender:get_player_name()
		if minetest.is_protected(pos, plname) then
			minetest.record_protection_violation(pos, plname)
			return
		end
		local meta = minetest.get_meta(pos)
		meta:set_string("channel", fields.channel)
	end,
	mesecons = mesecon_def,
	digiline = {
		receptor = {
			rules = technic.digilines.rules,
			action = function() end
		},
		effector = {
			rules = technic.digilines.rules,
			action = function(pos, node, channel, msg)
				if msg ~= "GET" and msg ~= "get" then
					return
				end
				local meta = minetest.get_meta(pos)
				if channel ~= meta:get_string("channel") then
					return
				end
				digilines.receptor_send(pos, technic.digilines.rules, channel, {
					supply = meta:get_int("supply"),
					demand = meta:get_int("demand"),
					lag = meta:get_int("lag")
				})
			end
		},
	},
})

--------------------------------------------------
-- Functions to traverse the electrical network
--------------------------------------------------
local function flatten(map)
	local list = {}
	for key, value in pairs(map) do
		list[#list + 1] = value
	end
	return list
end

-- Add a wire node to the LV/MV/HV network
local function add_network_node(nodes, pos, network_id)
	local node_id = minetest.hash_node_position(pos)
	technic.cables[node_id] = network_id
	if nodes[node_id] then
		return false
	end
	nodes[node_id] = pos
	return true
end

local function add_cable_node(nodes, pos, network_id, queue)
	if add_network_node(nodes, pos, network_id) then
		queue[#queue + 1] = pos
	end
end

-- Generic function to add found connected nodes to the right classification array
local check_node_subp = function(PR_nodes, RE_nodes, BA_nodes, SP_nodes, all_nodes, pos, machines, tier, sw_pos, from_below, network_id, queue)

	local distance_to_switch = vector.distance(pos, sw_pos)
	if distance_to_switch > switch_max_range then
		-- max range exceeded
		return
	end

	technic.get_or_load_node(pos)
	local name = minetest.get_node(pos).name

	if technic.is_tier_cable(name, tier) then
		add_cable_node(all_nodes, pos,network_id, queue)
	elseif machines[name] then
		--dprint(name.." is a "..machines[name])
		local meta = minetest.get_meta(pos)
		meta:set_string(tier.."_network",minetest.pos_to_string(sw_pos))
		if     machines[name] == technic.producer then
			add_network_node(PR_nodes, pos, network_id)
		elseif machines[name] == technic.receiver then
			add_network_node(RE_nodes, pos, network_id)
		elseif machines[name] == technic.producer_receiver then
			add_network_node(PR_nodes, pos, network_id)
			add_network_node(RE_nodes, pos, network_id)
		elseif machines[name] == "SPECIAL" and
				(pos.x ~= sw_pos.x or pos.y ~= sw_pos.y or pos.z ~= sw_pos.z) and
				from_below then
			-- Another switching station -> disable it
			add_network_node(SP_nodes, pos, network_id)
			meta:set_int("active", 0)
		elseif machines[name] == technic.battery then
			add_network_node(BA_nodes, pos, network_id)
		end

		meta:set_int(tier.."_EU_timeout", 2) -- Touch node
	end
end

-- Traverse a network given a list of machines and a cable type name
local traverse_network = function(PR_nodes, RE_nodes, BA_nodes, SP_nodes, all_nodes, pos, machines, tier, sw_pos, network_id, queue)
	local positions = {
		{x=pos.x+1, y=pos.y,   z=pos.z},
		{x=pos.x-1, y=pos.y,   z=pos.z},
		{x=pos.x,   y=pos.y+1, z=pos.z},
		{x=pos.x,   y=pos.y-1, z=pos.z},
		{x=pos.x,   y=pos.y,   z=pos.z+1},
		{x=pos.x,   y=pos.y,   z=pos.z-1}}
	for i, cur_pos in pairs(positions) do
		check_node_subp(PR_nodes, RE_nodes, BA_nodes, SP_nodes, all_nodes, cur_pos, machines, tier, sw_pos, i == 3, network_id, queue)
	end
end

local touch_nodes = function(list, tier)
	for _, pos in ipairs(list) do
		local meta = minetest.get_meta(pos)
		meta:set_int(tier.."_EU_timeout", 2) -- Touch node
	end
end

local get_network = function(sw_pos, pos1, tier)
	local network_id = minetest.hash_node_position(pos1)
	local cached = technic.networks[network_id]
	if cached and cached.tier == tier then
		touch_nodes(cached.PR_nodes, tier)
		touch_nodes(cached.BA_nodes, tier)
		touch_nodes(cached.RE_nodes, tier)
		for _, pos in ipairs(cached.SP_nodes) do
			local meta = minetest.get_meta(pos)
			meta:set_int("active", 0)
			meta:set_string("active_pos", minetest.serialize(sw_pos))
		end
		return cached.PR_nodes, cached.BA_nodes, cached.RE_nodes
	end
	local PR_nodes = {}
	local BA_nodes = {}
	local RE_nodes = {}
	local SP_nodes = {}
	local all_nodes = {}
	local queue = {}
	add_cable_node(all_nodes, pos1, network_id, queue)
	while next(queue) do
		local to_visit = {}
		for _, pos in ipairs(queue) do
			traverse_network(PR_nodes, RE_nodes, BA_nodes, SP_nodes, all_nodes,
					pos, technic.machines[tier], tier, sw_pos, network_id, to_visit)
		end
		queue = to_visit
	end
	PR_nodes = flatten(PR_nodes)
	BA_nodes = flatten(BA_nodes)
	RE_nodes = flatten(RE_nodes)
	SP_nodes = flatten(SP_nodes)
	all_nodes = flatten(all_nodes)
	technic.networks[network_id] = {tier = tier, all_nodes = all_nodes, SP_nodes = SP_nodes,
			PR_nodes = PR_nodes, RE_nodes = RE_nodes, BA_nodes = BA_nodes}
	return PR_nodes, BA_nodes, RE_nodes
end

-----------------------------------------------
-- The action code for the switching station --
-----------------------------------------------

technic.powerctrl_state = true

minetest.register_chatcommand("powerctrl", {
	params = "state",
	description = "Enables or disables technic's switching station ABM",
	privs = { basic_privs = true },
	func = function(name, state)
		if state == "on" then
			technic.powerctrl_state = true
		else
			technic.powerctrl_state = false
		end
	end
})

-- Run all the nodes
local function run_nodes(list, run_stage)
	for _, pos in ipairs(list) do
		technic.get_or_load_node(pos)
		local node = minetest.get_node_or_nil(pos)
		if node and node.name then
			local nodedef = minetest.registered_nodes[node.name]
			if nodedef and nodedef.technic_run then
				nodedef.technic_run(pos, node, run_stage)
			end
		end
	end
end

technic.switching_station_run = function(pos)
	if not technic.powerctrl_state then return end

	local t0 	       = minetest.get_us_time()
	local meta             = minetest.get_meta(pos)
	local meta1
	local pos1             = {}

	local tier      = ""
	local PR_nodes
	local BA_nodes
	local RE_nodes
	local machine_name = S("Switching Station")

	-- Which kind of network are we on:
	pos1 = {x=pos.x, y=pos.y-1, z=pos.z}

	--Disable if necessary
	if meta:get_int("active") ~= 1 then
		meta:set_string("infotext",S("%s Already Present"):format(machine_name))

		local poshash = minetest.hash_node_position(pos)

		if not technic.redundant_warn[poshash] then
			technic.redundant_warn[poshash] = true
			print("[TECHNIC] Warning: redundant switching station found near "..minetest.pos_to_string(pos))
		end
		return
	end

	local name = minetest.get_node(pos1).name
	local tier = technic.get_cable_tier(name)
	if tier then
		PR_nodes, BA_nodes, RE_nodes = get_network(pos, pos1, tier)
	else
		--dprint("Not connected to a network")
		meta:set_string("infotext", S("%s Has No Network"):format(machine_name))
		return
	end

	run_nodes(PR_nodes, technic.producer)
	run_nodes(RE_nodes, technic.receiver)
	run_nodes(BA_nodes, technic.battery)

	-- Strings for the meta data
	local eu_demand_str    = tier.."_EU_demand"
	local eu_input_str     = tier.."_EU_input"
	local eu_supply_str    = tier.."_EU_supply"

	-- Distribute charge equally across multiple batteries.
	local charge_total = 0
	local battery_count = 0

	local BA_charge = 0
	local BA_charge_max = 0

	for n, pos1 in pairs(BA_nodes) do
		meta1 = minetest.get_meta(pos1)
		local charge = meta1:get_int("internal_EU_charge")
		local charge_max = meta1:get_int("internal_EU_charge_max")

		BA_charge = BA_charge + charge
		BA_charge_max = BA_charge_max + charge_max

		if (meta1:get_int(eu_demand_str) ~= 0) then
			charge_total = charge_total + charge
			battery_count = battery_count + 1
		end
	end

	local charge_distributed = math.floor(charge_total / battery_count)

	for n, pos1 in pairs(BA_nodes) do
		meta1 = minetest.get_meta(pos1)

		if (meta1:get_int(eu_demand_str) ~= 0) then
			meta1:set_int("internal_EU_charge", charge_distributed)
		end
	end

	-- Get all the power from the PR nodes
	local PR_eu_supply = 0 -- Total power
	for _, pos1 in pairs(PR_nodes) do
		meta1 = minetest.get_meta(pos1)
		PR_eu_supply = PR_eu_supply + meta1:get_int(eu_supply_str)
	end
	--dprint("Total PR supply:"..PR_eu_supply)

	-- Get all the demand from the RE nodes
	local RE_eu_demand = 0
	for _, pos1 in pairs(RE_nodes) do
		meta1 = minetest.get_meta(pos1)
		RE_eu_demand = RE_eu_demand + meta1:get_int(eu_demand_str)
	end
	--dprint("Total RE demand:"..RE_eu_demand)

	-- Get all the power from the BA nodes
	local BA_eu_supply = 0
	for _, pos1 in pairs(BA_nodes) do
		meta1 = minetest.get_meta(pos1)
		BA_eu_supply = BA_eu_supply + meta1:get_int(eu_supply_str)
	end
	--dprint("Total BA supply:"..BA_eu_supply)

	-- Get all the demand from the BA nodes
	local BA_eu_demand = 0
	for _, pos1 in pairs(BA_nodes) do
		meta1 = minetest.get_meta(pos1)
		BA_eu_demand = BA_eu_demand + meta1:get_int(eu_demand_str)
	end
	--dprint("Total BA demand:"..BA_eu_demand)

	meta:set_string("infotext", S("@1. Supply: @2 Demand: @3",
			machine_name, technic.EU_string(PR_eu_supply),
			technic.EU_string(RE_eu_demand)))

	-- If mesecon signal and power supply or demand changed then
	-- send them via digilines.
	if mesecons_path and digilines_path and mesecon.is_powered(pos) then
		if PR_eu_supply ~= meta:get_int("supply") or
				RE_eu_demand ~= meta:get_int("demand") then
			local channel = meta:get_string("channel")
			digilines.receptor_send(pos, technic.digilines.rules, channel, {
				supply = PR_eu_supply,
				demand = RE_eu_demand
			})
		end
	end

	-- Data that will be used by the power monitor
	meta:set_int("supply",PR_eu_supply)
	meta:set_int("demand",RE_eu_demand)
	meta:set_int("battery_count",#BA_nodes)
	meta:set_int("battery_charge",BA_charge)
	meta:set_int("battery_charge_max",BA_charge_max)

	-- If the PR supply is enough for the RE demand supply them all
	if PR_eu_supply >= RE_eu_demand then
	--dprint("PR_eu_supply"..PR_eu_supply.." >= RE_eu_demand"..RE_eu_demand)
		for _, pos1 in pairs(RE_nodes) do
			meta1 = minetest.get_meta(pos1)
			local eu_demand = meta1:get_int(eu_demand_str)
			meta1:set_int(eu_input_str, eu_demand)
		end
		-- We have a surplus, so distribute the rest equally to the BA nodes
		-- Let's calculate the factor of the demand
		PR_eu_supply = PR_eu_supply - RE_eu_demand
		local charge_factor = 0 -- Assume all batteries fully charged
		if BA_eu_demand > 0 then
			charge_factor = PR_eu_supply / BA_eu_demand
		end
		for n, pos1 in pairs(BA_nodes) do
			meta1 = minetest.get_meta(pos1)
			local eu_demand = meta1:get_int(eu_demand_str)
			meta1:set_int(eu_input_str, math.floor(eu_demand * charge_factor))
			--dprint("Charging battery:"..math.floor(eu_demand*charge_factor))
		end
		local t1 = minetest.get_us_time()
		local diff = t1 - t0
		if diff > 50000 then
			minetest.log("warning", "[technic] [+supply] switching station abm took " .. diff .. " us at " .. minetest.pos_to_string(pos))
		end

		return
	end

	-- If the PR supply is not enough for the RE demand we will discharge the batteries too
	if PR_eu_supply + BA_eu_supply >= RE_eu_demand then
		--dprint("PR_eu_supply "..PR_eu_supply.."+BA_eu_supply "..BA_eu_supply.." >= RE_eu_demand"..RE_eu_demand)
		for _, pos1 in pairs(RE_nodes) do
			meta1  = minetest.get_meta(pos1)
			local eu_demand = meta1:get_int(eu_demand_str)
			meta1:set_int(eu_input_str, eu_demand)
		end
		-- We have a deficit, so distribute to the BA nodes
		-- Let's calculate the factor of the supply
		local charge_factor = 0 -- Assume all batteries depleted
		if BA_eu_supply > 0 then
			charge_factor = (PR_eu_supply - RE_eu_demand) / BA_eu_supply
		end
		for n,pos1 in pairs(BA_nodes) do
			meta1 = minetest.get_meta(pos1)
			local eu_supply = meta1:get_int(eu_supply_str)
			meta1:set_int(eu_input_str, math.floor(eu_supply * charge_factor))
			--dprint("Discharging battery:"..math.floor(eu_supply*charge_factor))
		end
		local t1 = minetest.get_us_time()
		local diff = t1 - t0
		if diff > 50000 then
			minetest.log("warning", "[technic] [-supply] switching station abm took " .. diff .. " us at " .. minetest.pos_to_string(pos))
		end

		return
	end

	-- If the PR+BA supply is not enough for the RE demand: Power only the batteries
	local charge_factor = 0 -- Assume all batteries fully charged
	if BA_eu_demand > 0 then
		charge_factor = PR_eu_supply / BA_eu_demand
	end
	for n, pos1 in pairs(BA_nodes) do
		meta1 = minetest.get_meta(pos1)
		local eu_demand = meta1:get_int(eu_demand_str)
		meta1:set_int(eu_input_str, math.floor(eu_demand * charge_factor))
	end
	for n, pos1 in pairs(RE_nodes) do
		meta1 = minetest.get_meta(pos1)
		meta1:set_int(eu_input_str, 0)
	end

	local t1 = minetest.get_us_time()
	local diff = t1 - t0
	if diff > 50000 then
		minetest.log("warning", "[technic] switching station abm took " .. diff .. " us at " .. minetest.pos_to_string(pos))
	end

end

-- Timeout ABM
-- Timeout for a node in case it was disconnected from the network
-- A node must be touched by the station continuously in order to function
local function switching_station_timeout_count(pos, tier)
	local meta = minetest.get_meta(pos)
	local timeout = meta:get_int(tier.."_EU_timeout")
	if timeout <= 0 then
		meta:set_int(tier.."_EU_input", 0) -- Not needed anymore <-- actually, it is for supply converter
		return true
	else
		meta:set_int(tier.."_EU_timeout", timeout - 1)
		return false
	end
end
minetest.register_abm({
	label = "Machines: timeout check",
	nodenames = {"group:technic_machine"},
	interval   = 1.9,
	chance     = 3,
	action = function(pos, node, active_object_count, active_object_count_wider)
		for tier, machines in pairs(technic.machines) do
			if machines[node.name] and switching_station_timeout_count(pos, tier) then
				local nodedef = minetest.registered_nodes[node.name]
				if nodedef and nodedef.technic_disabled_machine_name then
					node.name = nodedef.technic_disabled_machine_name
					minetest.swap_node(pos, node)
				elseif nodedef and nodedef.technic_on_disable then
					nodedef.technic_on_disable(pos, node)
				end
				if nodedef then
					local meta = minetest.get_meta(pos)
					meta:set_string("infotext", S("%s Has No Network"):format(nodedef.description))
				end
			end
		end
	end,
})

--Re-enable disabled switching station if necessary, similar to the timeout above
minetest.register_abm({
	label = "Machines: re-enable check",
	nodenames = {"technic:switching_station"},
	interval   = 1,
	chance     = 1,
	action = function(pos, node, active_object_count, active_object_count_wider)
		local pos1 = {x=pos.x,y=pos.y-1,z=pos.z}
		local tier = technic.get_cable_tier(minetest.get_node(pos1).name)
		if not tier then return end
		if switching_station_timeout_count(pos, tier) then
			local meta = minetest.get_meta(pos)
			meta:set_int("active",1)
		end
	end,
})

for tier, machines in pairs(technic.machines) do
	-- SPECIAL will not be traversed
	technic.register_machine(tier, "technic:switching_station", "SPECIAL")
end


code:
local has_monitoring_mod = minetest.get_modpath("monitoring")

local switches = {} -- pos_hash -> { time = time_us }

local function get_switch_data(pos)
	local hash = minetest.hash_node_position(pos)
	local switch = switches[hash]

	if not switch then
		switch = {
			time = 0,
			skip = 0
		}
		switches[hash] = switch
	end

	return switch
end

local active_switching_stations_metric, switching_stations_usage_metric

if has_monitoring_mod then
	active_switching_stations_metric = monitoring.gauge(
		"technic_active_switching_stations",
		"Number of active switching stations"
	)

	switching_stations_usage_metric = monitoring.counter(
		"technic_switching_stations_usage",
		"usage in microseconds cpu time"
	)
end

-- collect all active switching stations
minetest.register_abm({
	nodenames = {"technic:switching_station"},
	label = "Switching Station",
	interval   = 1,
	chance     = 1,
	action = function(pos)
		local switch = get_switch_data(pos)
		switch.time = minetest.get_us_time()
	end
})

-- the interval between technic_run calls
local technic_run_interval = 1.0

-- iterate over all collected switching stations and execute the technic_run function
local timer = 0
minetest.register_globalstep(function(dtime)
	timer = timer + dtime
	if timer < technic_run_interval then
		return
	end
	timer = 0

	local max_lag = technic.get_max_lag()
	-- slow down technic execution if the lag is higher than usual
	if max_lag > 5.0 then
		technic_run_interval = 5.0
	elseif max_lag > 2.0 then
			technic_run_interval = 4.0
	elseif max_lag > 1.5 then
			technic_run_interval = 3.0
	elseif max_lag > 1.0 then
			technic_run_interval = 1.5
	else
		-- normal run_interval
		technic_run_interval = 1.0
	end

	local now = minetest.get_us_time()

	local off_delay_seconds = tonumber(minetest.settings:get("technic.switch.off_delay_seconds") or "1800")
	local off_delay_micros = off_delay_seconds*1000*1000

	local active_switches = 0

	for hash, switch in pairs(switches) do
		local pos = minetest.get_position_from_hash(hash)
		local diff = now - switch.time

		minetest.get_voxel_manip(pos, pos)
		local node = minetest.get_node(pos)

		if node.name ~= "technic:switching_station" then
			-- station vanished
			switches[hash] = nil

		elseif diff < off_delay_micros then
			-- station active
			active_switches = active_switches + 1

			if switch.skip < 1 then

				local start = minetest.get_us_time()
				technic.switching_station_run(pos)
				local switch_diff = minetest.get_us_time() - start

				local meta = minetest.get_meta(pos)

				-- set lag in microseconds into the "lag" meta field
				meta:set_int("lag", switch_diff)

				-- overload detection
				if switch_diff > 250000 then
					switch.skip = 30
				elseif switch_diff > 150000 then
					switch.skip = 20
				elseif switch_diff > 75000 then
					switch.skip = 10
				elseif switch_diff > 50000 then
					switch.skip = 2
				end

				if switch.skip > 0 then
					-- calculate efficiency in percent and display it
					local efficiency = math.floor(1/switch.skip*100)
					meta:set_string("infotext", "Polyfuse triggered, current efficiency: " ..
						efficiency .. "% generated lag : " .. math.floor(switch_diff/1000) .. " ms")

					-- remove laggy switching station from active index
					-- it will be reactivated when a player is near it
					-- laggy switching stations won't work well in unloaded areas this way
					switches[hash] = nil
				end

			else
				switch.skip = math.max(switch.skip - 1, 0)
			end

		else
			-- station timed out
			switches[hash] = nil

		end
	end

	local time_usage = minetest.get_us_time() - now

	if has_monitoring_mod then
		active_switching_stations_metric.set(active_switches)
		switching_stations_usage_metric.inc(time_usage)
	end

end)

minetest.register_chatcommand("technic_flush_switch_cache", {
	description = "removes all loaded switching stations from the cache",
	privs = { server = true },
	func = function()
		switches = {}
	end
})

repo_name: Desour/PlayMinetestServer

license: mit


code:
local function explode(sep, input)
        local t={}
        local i=0
        for k in string.gmatch(input,"([^"..sep.."]+)") do
            t[i]=k
            i=i+1
        end
        return t
end

function technic.get_max_lag()
	local arrayoutput = explode(", ",minetest.get_server_status())
	local arrayoutput2 = explode("=",arrayoutput[4])
	return tonumber(arrayoutput2[1])
end


code:
name = technic
depends = default, pipeworks, technic_worldgen, basic_materials, logging
optional_depends = bucket, screwdriver, mesecons, mesecons_mvps, digilines, digiline_remote, intllib, unified_inventory, vector_extras, dye, monitoring


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


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