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

local function show_selection_formspec(player)
	local context = skins.get_formspec_context(player)
	local name = player:get_player_name()
	local skin = skins.get_player_skin(player)
	local formspec = "size[8,8]"..skins.get_skin_info_formspec(skin)
	formspec = formspec..skins.get_skin_selection_formspec(player, context, 3.5)
	minetest.show_formspec(name, 'skinsdb_show_ui', formspec)
end

minetest.register_chatcommand("skinsdb", {
	params = "[set] <skin key> | show [<skin key>] | list | list private | list public | [ui]",
	description = S("Show, list or set player's skin"),
	func = function(name, param)
		local player = minetest.get_player_by_name(name)
		if not player then
			return false, S("Player not found")
		end

		-- parse command line
		local command, parameter
		local words = param:split(" ")
		local word = words[1]
		if word == 'set' or word == 'list' or word == 'show' or word == 'ui' then
			command = word
			parameter = words[2]
		elseif skins.get(word) then
			command = 'set'
			parameter = word
		elseif not word then
			command = 'ui'
		else
			return false, S("unknown command").." "..word..", "..S("see /help skinsdb for supported parameters")
		end

		if command == "set" then
			local success = skins.set_player_skin(player, parameter)
			if success then
				return true, S("skin set to").." "..parameter
			else
				return false, S("invalid skin").." "..parameter
			end
		elseif command == "list" then
			local list
			if parameter == "private" then
				list = skins.get_skinlist_with_meta("playername", name)
			elseif parameter == "public" then
				list = skins.get_skinlist_for_player()
			elseif not parameter then
				list = skins.get_skinlist_for_player(name)
			else
				return false, S("unknown parameter"), parameter
			end

			local current_skin_key = skins.get_player_skin(player):get_key()
			for _, skin in ipairs(list) do
				local info = skin:get_key().." - "
						..S("Name").."="..skin:get_meta_string("name").." "
						..S("Author").."="..skin:get_meta_string("author").." "
						..S("License").."="..skin:get_meta_string("license")
				if skin:get_key() == current_skin_key then
					info = minetest.colorize("#00FFFF", info)
				end
				minetest.chat_send_player(name, info)
			end
		elseif command == "show" then
			local skin
			if parameter then
				skin = skins.get(parameter)
			else
				skin = skins.get_player_skin(player)
			end
			if not skin then
				return false, S("invalid skin")
			end
			local formspec = "size[8,3]"..skins.get_skin_info_formspec(skin)
			minetest.show_formspec(name, 'skinsdb_show_skin', formspec)
		elseif command == "ui" then
			show_selection_formspec(player)
		end
	end,
})

minetest.register_on_player_receive_fields(function(player, formname, fields)
	if formname ~= "skinsdb_show_ui" then
		return
	end

	local context = skins.get_formspec_context(player)

	local action = skins.on_skin_selection_receive_fields(player, context, fields)
	if action == 'set' then
		minetest.close_formspec(player:get_player_name(), formname)
	elseif action == 'page' then
		show_selection_formspec(player)
	end
end)


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

function skins.get_formspec_context(player)
	if player then
		local playername = player:get_player_name()
		skins.ui_context[playername] = skins.ui_context[playername] or {}
		return skins.ui_context[playername]
	else
		return {}
	end
end

-- Show skin info
function skins.get_skin_info_formspec(skin)
	local texture = skin:get_texture()
	local m_name = skin:get_meta_string("name")
	local m_author = skin:get_meta_string("author")
	local m_license = skin:get_meta_string("license")
	local m_format = skin:get_meta("format")
	-- overview page
	local raw_size = m_format == "1.8" and "2,2" or "2,1"

	local formspec = "image[0.8,.6;1,2;"..minetest.formspec_escape(skin:get_preview()).."]"
	if texture then
		formspec = formspec.."label[6,.5;"..S("Raw texture")..":]"
		.."image[6,1;"..raw_size..";"..texture.."]"
	end
	if m_name ~= "" then
		formspec = formspec.."label[2,.5;"..S("Name")..": "..minetest.formspec_escape(m_name).."]"
	end
	if m_author ~= "" then
		formspec = formspec.."label[2,1;"..S("Author")..": "..minetest.formspec_escape(m_author).."]"
	end
	if m_license ~= "" then
		formspec = formspec.."label[2,1.5;"..S("License")..": "..minetest.formspec_escape(m_license).."]"
	end
	return formspec
end

function skins.get_skin_selection_formspec(player, context, y_delta)
	context.skins_list = skins.get_skinlist_for_player(player:get_player_name())
	context.total_pages = 1
	for i, skin in ipairs(context.skins_list ) do
		local page = math.floor((i-1) / 16)+1
		skin:set_meta("inv_page", page)
		skin:set_meta("inv_page_index", (i-1)%16+1)
		context.total_pages = page
	end
	context.skins_page = context.skins_page or skins.get_player_skin(player):get_meta("inv_page") or 1
	context.dropdown_values = nil

	local page = context.skins_page
	local formspec = ""
	for i = (page-1)*16+1, page*16 do
		local skin = context.skins_list[i]
		if not skin then
			break
		end

		local index_p = skin:get_meta("inv_page_index")
		local x = (index_p-1) % 8
		local y
		if index_p > 8 then
			y = y_delta + 1.9
		else
			y = y_delta
		end
		formspec = formspec.."image_button["..x..","..y..";1,2;"..
			minetest.formspec_escape(skin:get_preview())..";skins_set$"..i..";]"..
			"tooltip[skins_set$"..i..";"..minetest.formspec_escape(skin:get_meta_string("name")).."]"
	end

	if context.total_pages > 1 then
		local page_prev = page - 1
		local page_next = page + 1
		if page_prev < 1 then
			page_prev = context.total_pages
		end
		if page_next > context.total_pages then
			page_next = 1
		end
		local page_list = ""
		context.dropdown_values = {}
		for pg=1, context.total_pages do
			local pagename = S("Page").." "..pg.."/"..context.total_pages
			context.dropdown_values[pagename] = pg
			if pg > 1 then page_list = page_list.."," end
			page_list = page_list..pagename
		end
		formspec = formspec
			.."button[0,"..(y_delta+4.0)..";1,.5;skins_page$"..page_prev..";<<]"
			.."dropdown[0.9,"..(y_delta+3.88)..";6.5,.5;skins_selpg;"..page_list..";"..page.."]"
			.."button[7,"..(y_delta+4.0)..";1,.5;skins_page$"..page_next..";>>]"
	end
	return formspec
end

function skins.on_skin_selection_receive_fields(player, context, fields)
	for field, _ in pairs(fields) do
		local current = string.split(field, "$", 2)
		if current[1] == "skins_set" then
			skins.set_player_skin(player, context.skins_list[tonumber(current[2])])
			return 'set'
		elseif current[1] == "skins_page" then
			context.skins_page = tonumber(current[2])
			return 'page'
		end
	end
	if fields.skins_selpg then
		context.skins_page = tonumber(context.dropdown_values[fields.skins_selpg])
		return 'page'
	end
end

repo_name: Desour/PlayMinetestServer

license: mit


code:
-- Unified Skins for Minetest - based modified Bags from unfied_inventory and skins from inventory_plus

-- Copyright (c) 2012 cornernote, Dean Montgomery
-- Rework 2017 by bell07
-- License: GPLv3

skins = {}
skins.modpath = minetest.get_modpath(minetest.get_current_modname())
skins.default = "character"

dofile(skins.modpath.."/skin_meta_api.lua")
dofile(skins.modpath.."/api.lua")
dofile(skins.modpath.."/skinlist.lua")
dofile(skins.modpath.."/formspecs.lua")
dofile(skins.modpath.."/chatcommands.lua")
-- Unified inventory page/integration
if minetest.get_modpath("unified_inventory") then
	dofile(skins.modpath.."/unified_inventory_page.lua")
end

if minetest.get_modpath("sfinv") then
	dofile(skins.modpath.."/sfinv_page.lua")
end

-- ie.loadfile does not exist?
skins.ie = minetest.request_insecure_environment()
skins.http = minetest.request_http_api()
dofile(skins.modpath.."/skins_updater.lua")
skins.ie = nil
skins.http = nil

-- 3d_armor compatibility
if minetest.global_exists("armor") then
	skins.armor_loaded = true
	armor.get_player_skin = function(self, name)
		local skin = skins.get_player_skin(minetest.get_player_by_name(name))
		return skin:get_texture()
	end
	armor.get_preview = function(self, name)
		local skin = skins.get_player_skin(minetest.get_player_by_name(name))
		return skin:get_preview()
	end
	armor.update_player_visuals = function(self, player)
		if not player then
			return
		end
		local skin = skins.get_player_skin(player)
		skin:apply_skin_to_player(player)
		armor:run_callbacks("on_update", player)
	end
end

if minetest.global_exists("clothing") and clothing.player_textures then
	skins.clothing_loaded = true
	clothing:register_on_update(skins.update_player_skin)
end

-- Update skin on join
skins.ui_context = {}
minetest.register_on_joinplayer(function(player)
	skins.update_player_skin(player)
end)

minetest.register_on_leaveplayer(function(player)
	skins.ui_context[player:get_player_name()] = nil
end)

player_api.register_model("skinsdb_3d_armor_character_5.b3d", {
	animation_speed = 30,
	textures = {
		"blank.png",
		"blank.png",
		"blank.png",
		"blank.png"
	},
	animations = {
		stand = {x=0, y=79},
		lay = {x=162, y=166},
		walk = {x=168, y=187},
		mine = {x=189, y=198},
		walk_mine = {x=200, y=219},
		sit = {x=81, y=160},
	},
})

-- Register default character.png if not part of this mod
local default_skin_obj = skins.get(skins.default)
if not default_skin_obj then
	default_skin_obj = skins.new(skins.default)
	default_skin_obj:set_texture("character.png")
	default_skin_obj:set_meta("format", '1.0')
	default_skin_obj:set_meta("_sort_id", 0)
	default_skin_obj:set_meta("name", "Sam")
end


code:
# textdomain: skinsdb
# Translation by Xanthin

Raw texture=Rohtextur
Name=Name
Author=Autor
Change=Wechseln
Page=Seite
License=Lizenz
Description=Beschreibung
Show, list or set player's skin=Anzeigen oder setzen der Spieler-Skins
Player not found=Spieler nicht da
unknown command=unbekannter Befehl
see /help skinsdb for supported parameters=Lese /help für erlaubte Parameter
skin set to=Skin ist jetzt
invalid skin=unbekannter Skin
unknown parameter=unbekannter Parameter
Skins=Aussehen


code:
# textdomain: skinsdb

Raw texture=Texture
Name=Nom
Author=Auteur
Change=Changer
Page=Page
License=Licence
Description=Description
Show, list or set player's skin=Afficher, lister ou définir le skin du joueur
Player not found=Joueur non trouvé
unknown command=commande inconnue
see /help skinsdb for supported parameters=voir /help skinsdb pour les paramètres supportés
skin set to=skin définie sur
invalid skin=skin peau invalide
unknown parameter=paramètre inconnu
unknown skin=skin inconnue


code:
# textdomain: skinsdb
# Malay translation by muhdnurhidayat

Raw texture=Tekstur mentah
Name=Nama
Author=Pencipta
Change=Ubah
Page=Halaman
License=Lesen
Description=Keterangan
Show, list or set player's skin=Tunjukkan, senaraikan atau tetapkan kulit pemain
Player not found=Pemain tidak dijumpai
unknown command=perintah tidak diketahui
see /help skinsdb for supported parameters=lihat /help skinsdb untuk parameter yang disokong
skin set to=kulit ditetapkan kepada
invalid skin=kulit tidak sah
unknown parameter=parameter tidak diketahui
unknown skin=kulit tidak diketahui


code:
# textdomain: skinsdb
#Translation by IFRFSX(BingFengFSX)

Raw texture=自然状态的纹理
Name=名称
Author=作者
Change=更换
Page=页面
License=许可证
Description=说明
Show, list or set player's skin=显示，列出或者设置玩家的皮肤
Player not found=玩家未找到
unknown command=未知命令
see /help skinsdb for supported parameters=有关skinsdb支持的参数，参见 /help
skin set to=皮肤设置为
invalid skin=无效皮肤
unknown parameter=未知参数
unknown skin=未知皮肤
Downloads the specified range of skins and shuts down the server=下载指定范围的皮肤并关闭服务器
Skins=皮肤


code:
# textdomain: skinsdb
#Translation by IFRFSX（BingFengFSX）
Raw texture=自然狀態的紋理
Name=名稱
Author=作者
Change=更換
Page=頁面
License=許可證
Description=說明
Show，list or set player's skin=顯示，列出或者設定玩家的皮膚
Player not found=玩家未找到
unknown command=未知命令
see /help skinsdb for supported parameters=有關skinsdb支持的參數，參見/help
skin set to=皮膚設定為
invalid skin=無效皮膚
unknown parameter=未知參數
unknown skin=未知皮膚
Downloads the specified range of skins and shuts down the server=下載指定範圍的皮膚並關閉服務器
Skins=皮膚


code:
# textdomain: skinsdb

Raw texture=
Name=
Author=
Change=
Page=
License=
Description=
Show, list or set player's skin=
Player not found=
unknown command=
see /help skinsdb for supported parameters=
skin set to=
invalid skin=
unknown parameter=
unknown skin=
Downloads the specified range of skins and shuts down the server=
Skins=


code:
Castaway
sirrobzeroone
CC0/Public Domain


code:
Castaway female
sirrobzeroone
CC0/Public Domain


code:
Farmer
sirrobzeroone
CC0/Public Domain


code:
Farmer female
sirrobzeroone
CC0/Public Domain


code:
Prince
sirrobzeroone
CC0/Public Domain


code:
Princess
sirrobzeroone
CC0/Public Domain


code:
Rogue
sirrobzeroone
CC0/Public Domain


code:
Rogue female
sirrobzeroone
CC0/Public Domain


code:
For each skin in textures folder a metadata file can be applied with "txt" suffilx. See character.txt for skin character.png for reference.
The file contains:
Skin name
Author
License


code:
name = skinsdb
description = Player skin mod, supporting unified_inventory, sfinv and smart_inventory
depends = default
optional_depends = unified_inventory,3d_armor,clothing,sfinv
release = 2936
author = bell07
title = SkinsDB


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

-- generate the current formspec
local function get_formspec(player, context)
	local skin = skins.get_player_skin(player)
	local formspec = skins.get_skin_info_formspec(skin)
	formspec = formspec..skins.get_skin_selection_formspec(player, context, 4)
	return formspec
end

sfinv.register_page("skins:overview", {
	title = S("Skins"),
	get = function(self, player, context)
		-- collect skins data
		return sfinv.make_formspec(player, context, get_formspec(player, context))
	end,
	on_player_receive_fields = function(self, player, context, fields)
		skins.on_skin_selection_receive_fields(player, context, fields)
		sfinv.set_player_inventory_formspec(player)
	end
})


code:
skins.meta = {}

local skin_class = {}
skin_class.__index = skin_class
skins.skin_class = skin_class
-----------------------
-- Class methods
-----------------------
-- constructor
function skins.new(key, object)
	assert(key, 'Unique skins key required, like "character_1"')
	local self = object or {}
	setmetatable(self, skin_class)
	self.__index = skin_class

	self._key = key
	self._sort_id = 0
	skins.meta[key] = self
	return self
end

-- getter
function skins.get(key)
	return skins.meta[key]
end

-- Skin methods
-- In this implementation it is just access to attrubutes wrapped
-- but this way allow to redefine the functionality for more complex skins provider
function skin_class:get_key()
	return self._key
end

function skin_class:set_meta(key, value)
	self[key] = value
end

function skin_class:get_meta(key)
	return self[key]
end

function skin_class:get_meta_string(key)
	return tostring(self:get_meta(key) or "")
end

function skin_class:set_texture(value)
	self._texture = value
end

function skin_class:get_texture()
	return self._texture
end

function skin_class:set_preview(value)
	self._preview = value
end

function skin_class:get_preview()
	if self._preview then
		return self._preview
	end

	local player_skin = "("..self:get_texture()..")"
	local skin = ""

	-- Consistent on both sizes:
	--Chest
	skin = skin .. "([combine:16x32:-16,-12=" .. player_skin .. "^[mask:skindb_mask_chest.png)^"
	--Head
	skin = skin .. "([combine:16x32:-4,-8=" .. player_skin .. "^[mask:skindb_mask_head.png)^"
	--Hat
	skin = skin .. "([combine:16x32:-36,-8=" .. player_skin .. "^[mask:skindb_mask_head.png)^"
	--Right Arm
	skin = skin .. "([combine:16x32:-44,-12=" .. player_skin .. "^[mask:skindb_mask_rarm.png)^"
	--Right Leg
	skin = skin .. "([combine:16x32:0,0=" .. player_skin .. "^[mask:skindb_mask_rleg.png)^"

	-- 64x skins have non-mirrored arms and legs
	local left_arm
	local left_leg

	if self:get_meta("format") == "1.8" then
		left_arm = "([combine:16x32:-24,-44=" .. player_skin .. "^[mask:(skindb_mask_rarm.png^[transformFX))^"
		left_leg = "([combine:16x32:-12,-32=" .. player_skin .. "^[mask:(skindb_mask_rleg.png^[transformFX))^"
	else
		left_arm = "([combine:16x32:-44,-12=" .. player_skin .. "^[mask:skindb_mask_rarm.png^[transformFX)^"
		left_leg = "([combine:16x32:0,0=" .. player_skin .. "^[mask:skindb_mask_rleg.png^[transformFX)^"
	end

	-- Left Arm
	skin = skin .. left_arm
	--Left Leg
	skin = skin .. left_leg

	-- Add overlays for 64x skins. these wont appear if skin is 32x because it will be cropped out
	--Chest Overlay
	skin = skin .. "([combine:16x32:-16,-28=" .. player_skin .. "^[mask:skindb_mask_chest.png)^"
	--Right Arm Overlay
	skin = skin .. "([combine:16x32:-44,-28=" .. player_skin .. "^[mask:skindb_mask_rarm.png)^"
	--Right Leg Overlay
	skin = skin .. "([combine:16x32:0,-16=" .. player_skin .. "^[mask:skindb_mask_rleg.png)^"
	--Left Arm Overlay
	skin = skin .. "([combine:16x32:-40,-44=" .. player_skin .. "^[mask:(skindb_mask_rarm.png^[transformFX))^"
	--Left Leg Overlay
	skin = skin .. "([combine:16x32:4,-32=" .. player_skin .. "^[mask:(skindb_mask_rleg.png^[transformFX))"

	-- Full Preview
	skin = "(((" .. skin .. ")^[resize:64x128)^[mask:skindb_transform.png)"

	return skin
end

function skin_class:apply_skin_to_player(player)

	local function concat_texture(base, ext)
		if base == "blank.png" then
			return ext
		elseif ext == "blank.png" then
			return base
		else
			return	base .. "^" .. ext
		end
	end

	local playername = player:get_player_name()
	local ver = self:get_meta("format") or "1.0"

	player_api.set_model(player, "skinsdb_3d_armor_character_5.b3d")

	local v10_texture = "blank.png"
	local v18_texture = "blank.png"
	local armor_texture = "blank.png"
	local wielditem_texture = "blank.png"

	if ver == "1.8" then
		v18_texture = self:get_texture()
	else
		v10_texture = self:get_texture()
	end

	-- Support for clothing
	if skins.clothing_loaded and clothing.player_textures[playername] then
		local cape = clothing.player_textures[playername].cape
		local layers = {}
		for k, v in pairs(clothing.player_textures[playername]) do
			if k ~= "skin" and k ~= "cape" then
				table.insert(layers, v)
			end
		end
		local overlay = table.concat(layers, "^")
		v10_texture = concat_texture(v10_texture, cape)
		v18_texture = concat_texture(v18_texture, overlay)
	end

	-- Support for armor
	if skins.armor_loaded then
		local armor_textures = armor.textures[playername]
		if armor_textures then
			armor_texture = concat_texture(armor_texture, armor_textures.armor)
			wielditem_texture = concat_texture(wielditem_texture, armor_textures.wielditem)
		end
	end

	player_api.set_textures(player, {
			v10_texture,
			v18_texture,
			armor_texture,
			wielditem_texture,
		})

	player:set_properties({
		visual_size = {
			x = self:get_meta("visual_size_x") or 1,
			y = self:get_meta("visual_size_y") or 1
		}
	})
end

function skin_class:set_skin(player)
	-- The set_skin is used on skins selection
	-- This means the method could be redefined to start an furmslec
	-- See character_creator for example
end

function skin_class:is_applicable_for_player(playername)
	local assigned_player = self:get_meta("playername")
	return minetest.check_player_privs(playername, {server=true}) or assigned_player == nil or assigned_player == true or
			(assigned_player:lower() == playername:lower())
end


code:
local skins_dir_list = minetest.get_dir_list(skins.modpath.."/textures")

for _, fn in pairs(skins_dir_list) do
	local name, sort_id, assignment, is_preview, playername
	local nameparts = string.gsub(fn, "[.]", "_"):split("_")

	-- check allowed prefix and file extension
	if (nameparts[1] == 'player' or nameparts[1] == 'character') and
			nameparts[#nameparts]:lower() == 'png' then

		-- cut filename extension
		table.remove(nameparts, #nameparts)

		-- check preview suffix
		if nameparts[#nameparts] == 'preview' then
			is_preview = true
			table.remove(nameparts, #nameparts)
		end

		-- Build technically skin name
		name = table.concat(nameparts, '_')

		-- Handle metadata from file name
		if not is_preview then
			-- Get player name
			if nameparts[1] == "player" then
				playername = nameparts[2]
				table.remove(nameparts, 1)
				sort_id = 0
			else
				sort_id = 5000
			end

			-- Get sort index
			if tonumber(nameparts[#nameparts]) then
				sort_id = sort_id + nameparts[#nameparts]
			end
		end

		local skin_obj = skins.get(name) or skins.new(name)
		if is_preview then
			skin_obj:set_preview(fn)
		else
			skin_obj:set_texture(fn)
			skin_obj:set_meta("_sort_id", sort_id)
			if playername then
				skin_obj:set_meta("assignment", "player:"..playername)
				skin_obj:set_meta("playername", playername)
			end
			local file = io.open(skins.modpath.."/textures/"..fn, "r")
			skin_obj:set_meta("format", skins.get_skin_format(file))
			file:close()
			file = io.open(skins.modpath.."/meta/"..name..".txt", "r")
			if file then
				local data = string.split(file:read("*all"), "\n", 3)
				file:close()
				skin_obj:set_meta("name", data[1])
				skin_obj:set_meta("author", data[2])
				skin_obj:set_meta("license", data[3])
			else
				-- remove player / character prefix if further naming given
				if nameparts[2] and not tonumber(nameparts[2]) then
					table.remove(nameparts, 1)
				end
				skin_obj:set_meta("name", table.concat(nameparts, ' '))
			end
		end
	end
end

local function skins_sort(skinslist)
	table.sort(skinslist, function(a,b)
		local a_id = a:get_meta("_sort_id") or 10000
		local b_id = b:get_meta("_sort_id") or 10000
		if a_id ~= b_id then
			return a:get_meta("_sort_id") < b:get_meta("_sort_id")
		else
			return a:get_meta("name") < b:get_meta("name")
		end
	end)
end

-- (obsolete) get skinlist. If assignment given ("mod:wardrobe" or "player:bell07") select skins matches the assignment. select_unassigned selects the skins without any assignment too
function skins.get_skinlist(assignment, select_unassigned)
	minetest.log("deprecated", "skins.get_skinlist() is deprecated. Use skins.get_skinlist_for_player() instead")
	local skinslist = {}
	for _, skin in pairs(skins.meta) do
		if not assignment or
				assignment == skin:get_meta("assignment") or
				(select_unassigned and skin:get_meta("assignment") == nil) then
			table.insert(skinslist, skin)
		end
	end
	skins_sort(skinslist)
	return skinslist
end

-- Get skinlist for player. If no player given, public skins only selected
function skins.get_skinlist_for_player(playername)
	local skinslist = {}
	for _, skin in pairs(skins.meta) do
		if skin:is_applicable_for_player(playername) and skin:get_meta("in_inventory_list") ~= false then
			table.insert(skinslist, skin)
		end
	end
	skins_sort(skinslist)
	return skinslist
end

-- Get skinlist selected by metadata
function skins.get_skinlist_with_meta(key, value)
	assert(key, "key parameter for skins.get_skinlist_with_meta() missed")
	local skinslist = {}
	for _, skin in pairs(skins.meta) do
		if skin:get_meta(key) == value then
			table.insert(skinslist, skin)
		end
	end
	skins_sort(skinslist)
	return skinslist
end


code:
-- Skins update script

local S = minetest.get_translator("skinsdb")
local _ID_ = "Lua Skins Updater"

local internal = {}
internal.errors = {}

-- Binary downloads are required
if not core.features.httpfetch_binary_data then
	internal.errors[#internal.errors + 1] =
		"Feature 'httpfetch_binary_data' is missing. Update Minetest."
end

-- Insecure environment for saving textures and meta
local ie, http = skins.ie, skins.http
if not ie or not http then
	internal.errors[#internal.errors + 1] = "Insecure environment is required. " ..
		"Please add skinsdb to `secure.trusted_mods` in minetest.conf"
end

minetest.register_chatcommand("skinsdb_download_skins", {
	params = "<skindb start page> <amount of pages>",
	description = S("Downloads the specified range of skins and shuts down the server"),
	privs = {server=true},
	func = function(name, param)
		if #internal.errors > 0 then
			return false, "Cannot run " .. _ID_ .. ":\n\t" ..
				table.concat(internal.errors, "\n\t")
		end

		local parts = string.split(param, " ")
		local start = tonumber(parts[1])
		local len = tonumber(parts[2])
		if not (start and len and len > 0) then
			return false, "Invalid page number or amount of pages"
		end

		internal.get_pages_count(internal.fetch_function, start, len)
		return true, "Started downloading..."
	end,
})

if #internal.errors > 0 then
	return -- Nonsense to load something that's not working
end

-- http://minetest.fensta.bplaced.net/api/apidoku.md
local root_url = "http://minetest.fensta.bplaced.net"
local page_url = root_url .. "/api/v2/get.json.php?getlist&page=%i&outformat=base64" -- [1] = Page#
local preview_url = root_url .. "/skins/1/%i.png" -- [1] = ID

local mod_path = skins.modpath
local meta_path = mod_path .. "/meta/"
local skins_path = mod_path .. "/textures/"

-- Fancy debug wrapper to download an URL
local function fetch_url(url, callback)
	http.fetch({
		url = url,
		user_agent = _ID_
	}, function(result)
		if result.succeeded then
			if result.code ~= 200 then
				core.log("warning", ("%s: STATUS=%i URL=%s"):format(
					_ID_, result.code, url))
			end
			return callback(result.data)
		end
		core.log("warning", ("%s: Failed to download URL=%s"):format(
			_ID_, url))
	end)
end

-- Insecure workaround since meta/ and textures/ cannot be written to
local function unsafe_file_write(path, contents)
	local f = ie.io.open(path, "w")
	f:write(contents)
	f:close()
end

-- Takes a valid skin table from the Skins Database and saves it
local function safe_single_skin(skin)
	local meta = {
		skin.name,
		skin.author,
		skin.license
	}

	local name =  "character_" .. skin.id

	-- core.safe_file_write does not work here
	unsafe_file_write(
		meta_path .. name .. ".txt",
		table.concat(meta, "\n")
	)

	unsafe_file_write(
		skins_path .. name .. ".png",
		core.decode_base64(skin.img)
	)
	fetch_url(preview_url:format(skin.id), function(preview)
		unsafe_file_write(skins_path .. name .. "_preview.png", preview)
	end)
	core.log("action", ("%s: Completed skin %s"):format(_ID_, name))
end

-- Get total pages since it'll just return the last page all over again
internal.get_pages_count = function(callback, ...)
	local vars = {...}
	fetch_url(page_url:format(1) .. "&per_page=1", function(data)
		local list = core.parse_json(data)
		-- "per_page" defaults to 20 if left away (docs say something else, though)
		callback(math.ceil(list.pages / 20), unpack(vars))
	end)
end

-- Function to fetch a range of pages
internal.fetch_function = function(pages_total, start_page, len)
	start_page = math.max(start_page, 1)
	local end_page = math.min(start_page + len - 1, pages_total)

	for page_n = start_page, end_page do
		local page_cpy = page_n
		fetch_url(page_url:format(page_n), function(data)
			core.log("action", ("%s: Page %i"):format(_ID_, page_cpy))

			local list = core.parse_json(data)
			for i, skin in pairs(list.skins) do
				assert(skin.type == "image/png")
				assert(skin.id ~= "")

				if skin.id ~= 1 then -- Skin 1 is bundled with skinsdb
					safe_single_skin(skin)
				end
			end

			if page_cpy == end_page then
				local log = _ID_ .. " finished downloading all skins. " ..
					"Shutting down server to reload media cache"
				core.log("action", log)
				core.request_shutdown(log, true, 3 --[[give some time for pending requests]])
			end
		end)
	end
end


code:
In this folder the skin files could be placed according the following file naming convention.

Public skin available for all users:
	character_[number-or-name].png

One or multiple private skins for player "nick":
	player_[nick].png or
	player_[nick]_[number-or-name].png

Preview files for public and private skins.
Optional, overrides the generated preview
	character_*_preview.png or
	player_*_*_preview.png


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

unified_inventory.register_page("skins", {
	get_formspec = function(player)
		local skin = skins.get_player_skin(player)
		local formspec = "background[0.06,0.99;7.92,7.52;ui_misc_form.png]"..skins.get_skin_info_formspec(skin)..
				"button[.75,3;6.5,.5;skins_page;"..S("Change").."]"
		return {formspec=formspec}
	end,
})

unified_inventory.register_button("skins", {
	type = "image",
	image = "skins_button.png",
	tooltip = S("Skins"),
})

local function get_formspec(player)
	local context = skins.get_formspec_context(player)
	local formspec = "background[0.06,0.99;7.92,7.52;ui_misc_form.png]"..
			skins.get_skin_selection_formspec(player, context, -0.2)
	return formspec
end

unified_inventory.register_page("skins_page", {
	get_formspec = function(player)
		return {formspec=get_formspec(player)}
	end
})

-- click button handlers
minetest.register_on_player_receive_fields(function(player, formname, fields)
	if fields.skins then
		unified_inventory.set_inventory_formspec(player, "craft")
		return
	end

	if formname ~= "" then
		return
	end

	local context = skins.get_formspec_context(player)
	local action = skins.on_skin_selection_receive_fields(player, context, fields)
	if action == 'set' then
		unified_inventory.set_inventory_formspec(player, "skins")
	elseif action == 'page' then
		unified_inventory.set_inventory_formspec(player, "skins_page")
	end
end)


code:
default
cursed_world?


code: Adds a /spawn command that telepors you to the server spawn command.


code:
spawn_command = {}
spawn_command.pos = {x=0, y=3, z=0}

if minetest.setting_get_pos("static_spawnpoint") then
    spawn_command.pos = minetest.setting_get_pos("static_spawnpoint")
end

function teleport_to_spawn(name)
    local player = minetest.get_player_by_name(name)
    if player == nil then
        -- just a check to prevent the server crashing
        return false
    end
    local pos = player:getpos()
    if pos.x>-1 and pos.x<1 and pos.z>-1 and pos.z<1 then
        minetest.chat_send_player(name, "Already close to spawn!")
    elseif _G['cursed_world'] ~= nil and    --check global table for cursed_world mod
        cursed_world.location_y and cursed_world.dimension_y and
        pos.y < (cursed_world.location_y + cursed_world.dimension_y) and    --if player is in cursed world, stay in cursed world
        pos.y > (cursed_world.location_y - cursed_world.dimension_y)
    then   --check global table for cursed_world mod
        --minetest.chat_send_player(name, "T"..(cursed_world.location_y + cursed_world.dimension_y).." "..(cursed_world.location_y - cursed_world.dimension_y))
        local spawn_pos = vector.round(spawn_command.pos);
        spawn_pos.y = spawn_pos.y + cursed_world.location_y;
        player:setpos(spawn_pos)
        minetest.chat_send_player(name, "Teleported to spawn!")
    else
        player:setpos(spawn_command.pos)
        minetest.chat_send_player(name, "Teleported to spawn!")
    end
end

minetest.register_chatcommand("spawn", {
    description = "Teleport you to spawn point.",
    func = teleport_to_spawn,
})


code: name = spawn_command


code:
Based on Minetest Game mod: stairs
==================================

Authors of source code
----------------------
Originally by Kahrl <kahrl@gmx.net> (LGPLv2.1+) and
celeron55, Perttu Ahola <celeron55@gmail.com> (LGPLv2.1+)
Various Minetest developers and contributors (LGPLv2.1+)

http://www.gnu.org/licenses/lgpl-2.1.html

License of media (textures and sounds)
--------------------------------------
Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)
http://creativecommons.org/licenses/by-sa/3.0/

Note: This mod has been amended to add new features like transparent and glowing
stairs, sloped stairs, recipes to return stairs back into blocks and also to be
used as fuel for furnaces, and alternative placement functions that use
on_rotate and sneak key.


code:
Stairs API
----------

The stairs API lets you register stairs and slabs and ensures that they are registered the same way as those delivered with Minetest Game, to keep them compatible with other mods.

`stairs.register_stair(subname, recipeitem, groups, images, description, sounds, worldaligntex)`

 * Registers a stair
 * `subname`: Basically the material name (e.g. cobble) used for the stair name. Nodename pattern: "stairs:stair_subname"
 * `recipeitem`: Item used in the craft recipe, e.g. "default:cobble", may be `nil`
 * `groups`: See [Known damage and digging time defining groups]
 * `images`: See [Tile definition]
 * `description`: Used for the description field in the stair's definition
 * `sounds`: See [#Default sounds]
 * `worldaligntex`: A bool to set all textures world-aligned. Default false. See [Tile definition]

`stairs.register_slab(subname, recipeitem, groups, images, description, sounds, worldaligntex)`

 * Registers a slab
 * `subname`: Basically the material name (e.g. cobble) used for the slab name. Nodename pattern: "stairs:slab_subname"
 * `recipeitem`: Item used in the craft recipe, e.g. "default:cobble"
 * `groups`: See [Known damage and digging time defining groups]
 * `images`: See [Tile definition]
 * `description`: Used for the description field in the slab's definition
 * `sounds`: See [#Default sounds]
 * `worldaligntex`: A bool to set all textures world-aligned. Default false. See [Tile definition]

`stairs.register_stair_inner(subname, recipeitem, groups, images, description, sounds, worldaligntex)`

 * Registers an inner corner stair
 * `subname`: Basically the material name (e.g. cobble) used for the stair name. Nodename pattern: "stairs:stair_inner_subname"
 * `recipeitem`: Item used in the craft recipe, e.g. "default:cobble", may be `nil`
 * `groups`: See [Known damage and digging time defining groups]
 * `images`: See [Tile definition]
 * `description`: Used for the description field in the stair's definition with "Inner" prepended
 * `sounds`: See [#Default sounds]
 * `worldaligntex`: A bool to set all textures world-aligned. Default false. See [Tile definition]

`stairs.register_stair_outer(subname, recipeitem, groups, images, description, sounds, worldaligntex)`

 * Registers an outer corner stair
 * `subname`: Basically the material name (e.g. cobble) used for the stair name. Nodename pattern: "stairs:stair_outer_subname"
 * `recipeitem`: Item used in the craft recipe, e.g. "default:cobble", may be `nil`
 * `groups`: See [Known damage and digging time defining groups]
 * `images`: See [Tile definition]
 * `description`: Used for the description field in the stair's definition with "Outer" prepended
 * `sounds`: See [#Default sounds]
 * `worldaligntex`: A bool to set all textures world-aligned. Default false. See [Tile definition]

`stairs.register_slope(subname, recipeitem, groups, images, description, sounds, worldaligntex)`

 * Registers a slope
 * `subname`: Basically the material name (e.g. cobble) used for the stair name. Nodename pattern: "stairs:stair_outer_subname"
 * `recipeitem`: Item used in the craft recipe, e.g. "default:cobble", may be `nil`
 * `groups`: See [Known damage and digging time defining groups]
 * `images`: See [Tile definition]
 * `description`: Used for the description field in the stair's definition with "Outer" prepended
 * `sounds`: See [#Default sounds]
 * `worldaligntex`: A bool to set all textures world-aligned. Default false. See [Tile definition]

`stairs.register_stair_and_slab(subname, recipeitem, groups, images, desc_stair, desc_slab, sounds, worldaligntex)`

 * A wrapper for stairs.register_stair, stairs.register_slab, stairs.register_stair_inner, stairs.register_stair_outer
 * Uses almost the same arguments as stairs.register_stair
 * `desc_stair`: Description for stair nodes. For corner stairs 'Inner' or 'Outer' will be prefixed
 * `desc_slab`: Description for slab node

`stairs.register_all(subname, recipeitem, groups, images, description, sounds, worldaligntex)`

 * A wrapper for stairs.register_stair, stairs.register_slab, stairs.register_stair_inner, stairs.register_stair_outer, stairs.register_slope
 * Uses almost the same arguments as stairs.register_stair
 * `description`: Description for stair nodes. 'stair' 'slab' 'stair_inner' 'stair_outer' 'Slope' will be prefixed


code: Adds stairs, slabs, inner and outer corners and slopes for blocks.


code:
-- wool sounds
function default.node_sound_wool_defaults(table)

	table = table or {}

	table.footstep = table.footstep or
			{name = "wool_coat_movement", gain = 1.0}
	table.dug = table.dug or
			{name = "wool_coat_movement", gain = 0.25}
	table.place = table.place or
			{name = "default_place_node", gain = 1.0}

	return table
end

stairs = {
	mod = "redo",
	wood = default.node_sound_wood_defaults(),
	dirt = default.node_sound_dirt_defaults(),
	stone = default.node_sound_stone_defaults(),
	glass = default.node_sound_glass_defaults(),
	leaves = default.node_sound_leaves_defaults(),
	metal = default.node_sound_metal_defaults(),
	wool = default.node_sound_wool_defaults()
}

-- cache creative
local creative = minetest.settings:get_bool("creative_mode")

function is_creative_enabled_for(name)

	if creative or minetest.check_player_privs(name, {creative = true}) then
		return true
	end

	return false
end

-- process textures
local set_textures = function(images, worldaligntex)

	local stair_images = {}

	for i, image in ipairs(images) do

		if type(image) == "string" then

			stair_images[i] = {
				name = image,
				backface_culling = true
			}

			if worldaligntex then
				stair_images[i].align_style = "world"
			end
		else
			stair_images[i] = table.copy(image)

			if stair_images[i].backface_culling == nil then
				stair_images[i].backface_culling = true
			end

			if worldaligntex and stair_images[i].align_style == nil then
				stair_images[i].align_style = "world"
			end
		end
	end

	return stair_images
end

-- placement helper
local stair_place = function(itemstack, placer, pointed_thing, stair_node)

	-- if sneak pressed then use param2 in node pointed at when placing
	if placer:is_player() and placer:get_player_control().sneak then

		local name  = placer:get_player_name()
		local pos_a = pointed_thing.above
		local node_a = minetest.get_node(pos_a)
		local def_a = minetest.registered_nodes[node_a.name]

		if not def_a.buildable_to
		or minetest.is_protected(pos_a, name) then
			return itemstack
		end

		local pos_u = pointed_thing.under
		local node_u = minetest.get_node(pos_u)

		if minetest.get_item_group(node_u.name, "stair") > 0
		or minetest.get_item_group(node_u.name, "slab") > 0 then

			minetest.set_node(pos_a, {
				name = stair_node,
				param2 = node_u.param2
			})

			if not is_creative_enabled_for(name) then
				itemstack:take_item()
			end

			return itemstack
		end
	end

	core.rotate_and_place(itemstack, placer, pointed_thing,
			is_creative_enabled_for(placer:get_player_name()),
			{invert_wall = placer:get_player_control().sneak})

	return itemstack
end

local function get_light(nodename)

	local def = minetest.registered_nodes[nodename]

	if def and def.light_source then
		return def.light_source
	end

	return nil
end

local function get_alpha(nodename)

	local def = minetest.registered_nodes[nodename]

	if def and def.use_texture_alpha then
		return def.use_texture_alpha
	end

	return nil
end

-- if recipeitem can be burned then stair can be as well
local function set_burn(recipeitem, stair_name, v)

	local burntime = minetest.get_craft_result({
		method = "fuel", width = 1, items = {recipeitem} }).time

	if burntime > 0 then

		minetest.register_craft({
			type = "fuel",
			recipe = stair_name,
			burntime = math.floor(burntime * v)
		})
	end
end

-- Node will be called stairs:stair_<subname>
function stairs.register_stair(
		subname, recipeitem, groups, images, description, snds, wat)

	local stair_images = set_textures(images, wat)
	local new_groups = table.copy(groups)

	new_groups.stair = 1

	minetest.register_node(":stairs:stair_" .. subname, {
		description = description,
		drawtype = "nodebox",
		tiles = stair_images,
		paramtype = "light",
		paramtype2 = "facedir",
		is_ground_content = false,
		use_texture_alpha = get_alpha(recipeitem),
		light_source = get_light(recipeitem),
		groups = new_groups,
		sounds = snds,
		node_box = {
			type = "fixed",
			fixed = {
				{-0.5, -0.5, -0.5, 0.5, 0, 0.5},
				{-0.5, 0, 0, 0.5, 0.5, 0.5}
			}
		},

		on_place = function(itemstack, placer, pointed_thing)

			return stair_place(itemstack, placer, pointed_thing,
					"stairs:stair_" .. subname)
		end
	})

	-- if no recipe item provided then skip craft recipes
	if not recipeitem then
		return
	end

	-- stair recipes
	minetest.register_craft({
		output = "stairs:stair_" .. subname .. " 8",
		recipe = {
			{recipeitem, "", ""},
			{recipeitem, recipeitem, ""},
			{recipeitem, recipeitem, recipeitem}
		}
	})

	minetest.register_craft({
		output = "stairs:stair_" .. subname .. " 8",
		recipe = {
			{"", "", recipeitem},
			{"", recipeitem, recipeitem},
			{recipeitem, recipeitem, recipeitem}
		}
	})

	-- stair to original material recipe
	minetest.register_craft({
		output = recipeitem .. " 3",
		recipe = {
			{"stairs:stair_" .. subname, "stairs:stair_" .. subname},
			{"stairs:stair_" .. subname, "stairs:stair_" .. subname}
		}
	})

	set_burn(recipeitem, "stairs:stair_" .. subname, 0.75)
end

-- Node will be called stairs:slab_<subname>
function stairs.register_slab(
		subname, recipeitem, groups, images, description, snds, wat)

	local slab_images = set_textures(images, wat)
	local new_groups = table.copy(groups)

	new_groups.slab = 1

	minetest.register_node(":stairs:slab_" .. subname, {
		description = description,
		drawtype = "nodebox",
		tiles = slab_images,
		paramtype = "light",
		paramtype2 = "facedir",
		is_ground_content = false,
		use_texture_alpha = get_alpha(recipeitem),
		light_source = get_light(recipeitem),
		groups = new_groups,
		sounds = snds,
		node_box = {
			type = "fixed",
			fixed = {-0.5, -0.5, -0.5, 0.5, 0, 0.5}
		},

		on_place = function(itemstack, placer, pointed_thing)

			return stair_place(itemstack, placer, pointed_thing,
					"stairs:slab_" .. subname)
		end
	})

	-- if no recipe item provided then skip craft recipes
	if not recipeitem then
		return
	end

	-- slab recipe
	minetest.register_craft({
		output = "stairs:slab_" .. subname .. " 6",
		recipe = {
			{recipeitem, recipeitem, recipeitem}
		}
	})

	-- slab to original material recipe
	minetest.register_craft({
		output = recipeitem,
		recipe = {
			{"stairs:slab_" .. subname},
			{"stairs:slab_" .. subname}
		}
	})

	set_burn(recipeitem, "stairs:slab_" .. subname, 0.5)
end

-- Node will be called stairs:stair_outer_<subname>
function stairs.register_stair_outer(
		subname, recipeitem, groups, images, description, snds, wat, fdesc)

	local stair_images = set_textures(images, wat)
	local new_groups = table.copy(groups)

	new_groups.stair = 1

	minetest.register_node(":stairs:stair_outer_" .. subname, {
		description = fdesc or "Outer " .. description,
		drawtype = "nodebox",
		tiles = stair_images,
		paramtype = "light",
		paramtype2 = "facedir",
		is_ground_content = false,
		use_texture_alpha = get_alpha(recipeitem),
		light_source = get_light(recipeitem),
		groups = new_groups,
		sounds = snds,
		node_box = {
			type = "fixed",
			fixed = {
				{-0.5, -0.5, -0.5, 0.5, 0, 0.5},
				{-0.5, 0, 0, 0, 0.5, 0.5}
			},
		},

		on_place = function(itemstack, placer, pointed_thing)

			return stair_place(itemstack, placer, pointed_thing,
					"stairs:stair_outer_" .. subname)
		end
	})

	-- add alias for old stairs redo name
	minetest.register_alias("stairs:corner_" .. subname,
			"stairs:stair_outer_" .. subname)

	-- if no recipe item provided then skip craft recipes
	if not recipeitem then
		return
	end

	-- corner stair recipe
	minetest.register_craft({
		output = "stairs:stair_outer_" .. subname .. " 6",
		recipe = {
			{"", "", ""},
			{"", recipeitem, ""},
			{recipeitem, recipeitem, recipeitem}
		},
	})

	-- corner stair to original material recipe
	minetest.register_craft({
		output = recipeitem .. " 2",
		recipe = {
			{"stairs:stair_outer_" .. subname,
					"stairs:stair_outer_" .. subname},
			{"stairs:stair_outer_" .. subname,
					"stairs:stair_outer_" .. subname}
		}
	})

	set_burn(recipeitem, "stairs:stair_outer_" .. subname, 0.625)
end

-- Node will be called stairs:stair_inner_<subname>
function stairs.register_stair_inner(
		subname, recipeitem, groups, images, description, snds, wat, fdesc)

	local stair_images = set_textures(images, wat)
	local new_groups = table.copy(groups)

	new_groups.stair = 1

	minetest.register_node(":stairs:stair_inner_" .. subname, {
		description = fdesc or "Inner " .. description,
		drawtype = "nodebox",
		tiles = stair_images,
		paramtype = "light",
		paramtype2 = "facedir",
		is_ground_content = false,
		use_texture_alpha = get_alpha(recipeitem),
		light_source = get_light(recipeitem),
		groups = new_groups,
		sounds = snds,
		node_box = {
			type = "fixed",
			fixed = {
				{-0.5, -0.5, -0.5, 0.5, 0, 0.5},
				{-0.5, 0, 0, 0.5, 0.5, 0.5},
				{-0.5, 0, -0.5, 0, 0.5, 0}
			}
		},

		on_place = function(itemstack, placer, pointed_thing)

			return stair_place(itemstack, placer, pointed_thing,
					"stairs:stair_inner_" .. subname)
		end,
	})

	-- add alias for old stairs redo name
	minetest.register_alias("stairs:invcorner_" .. subname,
			"stairs:stair_inner_" .. subname)

	-- if no recipe item provided then skip craft recipes
	if not recipeitem then
		return
	end

	-- inside corner stair recipe
	minetest.register_craft({
		output = "stairs:stair_inner_" .. subname .. " 9",
		recipe = {
			{recipeitem, recipeitem, ""},
			{recipeitem, recipeitem, recipeitem},
			{recipeitem, recipeitem, recipeitem},
		}
	})

	-- inside corner stair to original material recipe
	minetest.register_craft({
		output = recipeitem .. " 3",
		recipe = {
			{"stairs:stair_inner_" .. subname,
					"stairs:stair_inner_" .. subname},
			{"stairs:stair_inner_" .. subname,
					"stairs:stair_inner_" .. subname}
		}
	})

	set_burn(recipeitem, "stairs:stair_inner_" .. subname, 0.875)
end

-- Node will be called stairs:slope_<subname>
function stairs.register_slope(
		subname, recipeitem, groups, images, description, snds, wat)

	local stair_images = set_textures(images, wat)
	local new_groups = table.copy(groups)

	new_groups.stair = 1

	minetest.register_node(":stairs:slope_" .. subname, {
		description = description,
		drawtype = "mesh",
		mesh = "stairs_slope.obj",
		tiles = stair_images,
		paramtype = "light",
		paramtype2 = "facedir",
		is_ground_content = false,
		use_texture_alpha = get_alpha(recipeitem),
		light_source = get_light(recipeitem),
		groups = new_groups,
		sounds = snds,
		selection_box = {
			type = "fixed",
			fixed = {
				{-0.5, -0.5, -0.5, 0.5, 0, 0.5},
				{-0.5, 0, 0, 0.5, 0.5, 0.5},
			},
		},
		collision_box = {
			type = "fixed",
			fixed = {
				{-0.5, -0.5, -0.5, 0.5, 0, 0.5},
				{-0.5, 0, 0, 0.5, 0.5, 0.5}
			},
		},

		on_place = function(itemstack, placer, pointed_thing)
			return stair_place(itemstack, placer, pointed_thing,
					"stairs:slope_" .. subname)
		end
	})

	-- slope recipe
	minetest.register_craft({
		output = "stairs:slope_" .. subname .. " 6",
		recipe = {
			{recipeitem, "", ""},
			{recipeitem, recipeitem, ""}
		}
	})

	-- slope to original material recipe
	minetest.register_craft({
		output = recipeitem,
		recipe = {
			{"stairs:slope_" .. subname, "stairs:slope_" .. subname}
		}
	})

	set_burn(recipeitem, "stairs:slope_" .. subname, 0.5)
end

-- Nodes will be called stairs:{stair,slab}_<subname>
function stairs.register_stair_and_slab(
		subname, recipeitem, groups, images, desc_stair, desc_slab, sounds, wat)

	stairs.register_stair(
			subname, recipeitem, groups, images, desc_stair, sounds, wat)

	stairs.register_stair_inner(
			subname, recipeitem, groups, images, desc_stair, sounds, wat)

	stairs.register_stair_outer(
			subname, recipeitem, groups, images, desc_stair, sounds, wat)

	stairs.register_slab(
			subname, recipeitem, groups, images, desc_slab, sounds, wat)
end

-- Nodes will be called stairs:{stair,slab,corner,invcorner,slope}_<subname>
function stairs.register_all(
		subname, recipeitem, groups, images, desc, snds, wat)

	stairs.register_stair(
			subname, recipeitem, groups, images, desc .. " Stair", snds, wat)

	stairs.register_slab(
			subname, recipeitem, groups, images, desc .. " Slab", snds, wat)

	stairs.register_stair_inner(
			subname, recipeitem, groups, images, desc .. " Stair", snds, wat)

	stairs.register_stair_outer(
			subname, recipeitem, groups, images, desc .. " Stair", snds, wat)

	stairs.register_slope(
			subname, recipeitem, groups, images, desc .. " Slope", snds, wat)
end

-- compatibility function for previous stairs:invcorner_<subname>
function stairs.register_invcorner(
		subname, recipeitem, groups, images, desc, snds, wat)

	stairs.register_stair_inner(
			subname, recipeitem, groups, images, desc .. " Stair", snds, wat)
end

-- compatibility function for previous stairs:corner_<subname>
function stairs.register_corner(
		subname, recipeitem, groups, images, desc, snds, wat)

	stairs.register_stair_outer(
			subname, recipeitem, groups, images, desc .. " Stair", snds, wat)
end

-- Register stairs
dofile(minetest.get_modpath("stairs") .. "/stairs.lua")

print ("[MOD] Stairs Redo loaded")


code:
name = stairs
author = TenPlus1
description = Adds more stair types to Minetest.
release = 4067
title = Stairs Redo


code:
local grp = {} -- Helper

--= Default Minetest

-- Wood types

stairs.register_all("wood", "default:wood",
	{choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
	{"default_wood.png"},
	"Wooden",
	stairs.wood, false)

stairs.register_all("junglewood", "default:junglewood",
	{choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
	{"default_junglewood.png"},
	"Jungle Wood",
	stairs.wood, false)

stairs.register_all("pine_wood", "default:pinewood",
	{choppy = 3, oddly_breakable_by_hand = 1, flammable = 3},
	{"default_pine_wood.png"},
	"Pine Wood",
	stairs.wood, false)

-- Register aliases for new pine node names
minetest.register_alias("stairs:stair_pinewood", "stairs:stair_pine_wood")
minetest.register_alias("stairs:slab_pinewood", "stairs:slab_pine_wood")

stairs.register_all("acacia_wood", "default:acacia_wood",
	{choppy = 2, oddly_breakable_by_hand = 2, flammable = 3},
	{"default_acacia_wood.png"},
	"Acacia Wood",
	stairs.wood, false)

stairs.register_all("aspen_wood", "default:aspen_wood",
	{choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
	{"default_aspen_wood.png"},
	"Aspen Wood",
	stairs.wood, false)

-- Stone types

stairs.register_all("stone", "default:stone",
	{cracky = 3},
	{"default_stone.png"},
	"Stone",
	stairs.stone, true)

stairs.register_all("stonebrick", "default:stonebrick",
	{cracky = 2},
	{"default_stone_brick.png"},
	"Stone Brick",
	stairs.stone, false)

stairs.register_all("stone_block", "default:stone_block",
	{cracky = 2},
	{"default_stone_block.png"},
	"Stone Block",
	stairs.stone, true)

stairs.register_all("cobble", "default:cobble",
	{cracky = 3},
	{"default_cobble.png"},
	"Cobble",
	stairs.stone, true)

stairs.register_all("mossycobble", "default:mossycobble",
	{cracky = 3},
	{"default_mossycobble.png"},
	"Mossy Cobble",
	stairs.stone, true)

stairs.register_all("desert_stone", "default:desert_stone",
	{cracky = 3},
	{"default_desert_stone.png"},
	"Desert Stone",
	stairs.stone, true)

stairs.register_all("desert_stonebrick", "default:desert_stonebrick",
	{cracky = 2},
	{"default_desert_stone_brick.png"},
	"Desert Stone Brick",
	stairs.stone, false)

stairs.register_all("desert_stone_block", "default:desert_stone_block",
	{cracky = 2},
	{"default_desert_stone_block.png"},
	"Desert Stone Block",
	stairs.stone, true)

stairs.register_all("desert_cobble", "default:desert_cobble",
	{cracky = 3},
	{"default_desert_cobble.png"},
	"Desert Cobble",
	stairs.stone, true)

-- Sandstone types

stairs.register_all("sandstone", "default:sandstone",
	{crumbly = 1, cracky = 3},
	{"default_sandstone.png"},
	"Sandstone",
	stairs.stone, true)

stairs.register_all("sandstonebrick", "default:sandstonebrick",
	{cracky = 2},
	{"default_sandstone_brick.png"},
	"Sandstone Brick",
	stairs.stone, false)

stairs.register_all("sandstone_block", "default:sandstone_block",
	{cracky = 2},
	{"default_sandstone_block.png"},
	"Sandstone Block",
	stairs.stone, true)

stairs.register_all("desert_sandstone", "default:desert_sandstone",
	{crumbly = 1, cracky = 3},
	{"default_desert_sandstone.png"},
	"Desert Sandstone",
	stairs.stone, true)

stairs.register_all("desert_sandstone_brick", "default:desert_sandstone_brick",
	{cracky = 2},
	{"default_desert_sandstone_brick.png"},
	"Desert Sandstone Brick",
	stairs.stone, false)

stairs.register_all("desert_sandstone_block", "default:desert_sandstone_block",
	{cracky = 2},
	{"default_desert_sandstone_block.png"},
	"Desert Sandstone Block",
	stairs.stone, true)

stairs.register_all("silver_sandstone", "default:silver_sandstone",
	{crumbly = 1, cracky = 3},
	{"default_silver_sandstone.png"},
	"Silver Sandstone",
	stairs.stone, true)

stairs.register_all("silver_sandstone_brick", "default:silver_sandstone_brick",
	{cracky = 2},
	{"default_silver_sandstone_brick.png"},
	"Silver Sandstone Brick",
	stairs.stone, false)

stairs.register_all("silver_sandstone_block", "default:silver_sandstone_block",
	{cracky = 2},
	{"default_silver_sandstone_block.png"},
	"Silver Sandstone Block",
	stairs.stone, true)

-- Obsidian

stairs.register_all("obsidian", "default:obsidian",
	{cracky = 1, level = 2},
	{"default_obsidian.png"},
	"Obsidian",
	stairs.stone, true)

stairs.register_all("obsidianbrick", "default:obsidianbrick",
	{cracky = 1, level = 2},
	{"default_obsidian_brick.png"},
	"Obsidian Brick",
	stairs.stone, false)

stairs.register_all("obsidian_block", "default:obsidian_block",
	{cracky = 1, level = 2},
	{"default_obsidian_block.png"},
	"Obsidian block",
	stairs.stone, true)

-- Cloud (with overrides)

stairs.register_stair("cloud", "default:cloud",
	{unbreakable = 1, not_in_creative_inventory = 1},
	{"default_cloud.png"},
	"Cloud Stair",
	stairs.wool)

minetest.override_item("stairs:stair_cloud", {
	on_blast = function() end,
	on_drop = function(itemstack, dropper, pos) end,
	drop = {},
})

stairs.register_slab("cloud", "default:cloud",
	{unbreakable = 1, not_in_creative_inventory = 1},
	{"default_cloud.png"},
	"Cloud Slab",
	stairs.wool)

minetest.override_item("stairs:slab_cloud", {
	on_blast = function() end,
	on_drop = function(itemstack, dropper, pos) end,
	drop = {},
})

-- Ores

stairs.register_all("coal", "default:coalblock",
	{cracky = 3},
	{"default_coal_block.png"},
	"Coal",
	stairs.stone, true)

stairs.register_all("steelblock", "default:steelblock",
	{cracky = 1, level = 2},
	{"default_steel_block.png"},
	"Steel",
	stairs.metal, true)

stairs.register_all("copperblock", "default:copperblock",
	{cracky = 1, level = 2},
	{"default_copper_block.png"},
	"Copper",
	stairs.metal, true)

stairs.register_all("bronzeblock", "default:bronzeblock",
	{cracky = 1, level = 2},
	{"default_bronze_block.png"},
	"Bronze",
	stairs.metal, true)

stairs.register_all("tinblock", "default:tinblock",
	{cracky = 1, level = 2},
	{"default_tin_block.png"},
	"Tin",
	stairs.metal, true)

stairs.register_all("mese", "default:mese",
	{cracky = 1, level = 2},
	{"default_mese_block.png"},
	"Mese",
	stairs.stone)

stairs.register_all("goldblock", "default:goldblock",
	{cracky = 1},
	{"default_gold_block.png"},
	"Gold",
	stairs.metal)

stairs.register_all("diamondblock", "default:diamondblock",
	{cracky = 1, level = 3},
	{"default_diamond_block.png"},
	"Diamond",
	stairs.stone)

-- Glass types

stairs.register_all("glass", "default:glass",
	{cracky = 3, oddly_breakable_by_hand = 3},
	{"default_glass.png"},
	"Glass",
	stairs.glass)

stairs.register_all("obsidian_glass", "default:obsidian_glass",
	{cracky = 2},
	{"default_obsidian_glass.png"},
	"Obsidian Glass",
	stairs.glass)

-- Brick, Snow and Ice

stairs.register_all("brick", "default:brick",
	{cracky = 3},
	{"default_brick.png"},
	"Brick",
	stairs.stone, false)

stairs.register_all("snowblock", "default:snowblock",
	{crumbly = 3, cools_lava = 1, snowy = 1},
	{"default_snow.png"},
	"Snow Block",
	default.node_sound_snow_defaults(), true)

stairs.register_all("ice", "default:ice",
	{cracky = 3, cools_lava = 1},
	{"default_ice.png"},
	"Ice",
	default.node_sound_glass_defaults(), true)

--= More Ores Mod

if minetest.get_modpath("moreores") then

grp = {cracky = 1, level = 2}

stairs.register_all("silver_block", "moreores:silver_block",
	grp,
	{"moreores_silver_block.png"},
	"Silver",
	stairs.metal, true)

stairs.register_all("mithril_block", "moreores:mithril_block",
	grp,
	{"moreores_mithril_block.png"},
	"Mithril",
	stairs.metal, true)

end

--= Mobs Mod

if minetest.registered_nodes["mobs:cheeseblock"] then

grp = {crumbly = 3, flammable = 2}

stairs.register_all("cheeseblock", "mobs:cheeseblock",
	grp,
	{"mobs_cheeseblock.png"},
	"Cheese Block",
	stairs.dirt)

stairs.register_all("honey_block", "mobs:honey_block",
	grp,
	{"mobs_honey_block.png"},
	"Honey Block",
	stairs.dirt)

end

--= Lapis Mod

if minetest.get_modpath("lapis") then

grp = {cracky = 3}

stairs.register_all("lapis_block", "lapis:lapis_block",
	grp,
	{"lapis_block_side.png"},
	"Lapis",
	stairs.stone)

stairs.register_all("lapis_brick", "lapis:lapis_brick",
	grp,
	{"lapis_brick.png"},
	"Lapis Brick",
	stairs.stone)

stairs.register_all("lapis_cobble", "lapis:lapis_cobble",
	grp,
	{"lapis_cobble.png"},
	"Lapis Cobble",
	stairs.stone)

end

--= Homedecor Mod

if minetest.get_modpath("homedecor") then

local grp = {snappy = 3}

stairs.register_all("shingles_asphalt", "homedecor:shingles_asphalt",
	grp,
	{"homedecor_shingles_asphalt.png"},
	"Asphalt Shingle",
	stairs.leaves)

stairs.register_all("shingles_terracotta", "homedecor:roof_tile_terracotta",
	grp,
	{"homedecor_shingles_terracotta.png"},
	"Terracotta Shingle",
	stairs.leaves)

stairs.register_all("shingles_wood", "homedecor:shingles_wood",
	grp,
	{"homedecor_shingles_wood.png"},
	"Wooden Shingle",
	stairs.leaves)

end

--= Xanadu Mod

if minetest.get_modpath("xanadu") then

grp = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 3}

stairs.register_all("stained_wood_white", "xanadu:stained_wood_white",
	grp,
	{"stained_wood_white.png"},
	"White Wooden",
	stairs.wood, false)

stairs.register_all("stained_wood_red", "xanadu:stained_wood_red",
	grp,
	{"stained_wood_red.png"},
	"Red Wooden",
	stairs.wood, false)

-- Decorative blocks

grp = {cracky = 3}

stairs.register_all("stone1", "xanadu:stone1",
	grp,
	{"stone1.png"},
	"Decorative Stone 1",
	stairs.stone)

stairs.register_all("stone2", "xanadu:stone2",
	grp,
	{"stone2.png"},
	"Decorative Stone 2",
	stairs.stone)

stairs.register_all("stone3", "xanadu:stone3",
	grp,
	{"stone3.png"},
	"Decorative Stone 3",
	stairs.stone)

stairs.register_all("stone4", "xanadu:stone4",
	grp,
	{"stone4.png"},
	"Decorative Stone 4",
	stairs.stone)

stairs.register_all("stone5", "xanadu:stone5",
	grp,
	{"stone5.png"},
	"Decorative Stone 5",
	stairs.stone)

stairs.register_all("stone6", "xanadu:stone6",
	grp,
	{"stone6.png"},
	"Decorative Stone 6",
	stairs.stone)

stairs.register_all("sandstonebrick4", "xanadu:sandstonebrick4",
	grp,
	{"sandstonebrick4.png"},
	"Decorative Sandstone 4",
	stairs.stone)

stairs.register_slab("desert_cobble1", "xanadu:desert_cobble1",
	grp,
	{"desert_cobble1.png"},
	"Decorative desert cobble 1 slab",
	stairs.stone)

stairs.register_slab("desert_cobble5", "xanadu:desert_cobble5",
	grp,
	{"desert_cobble5.png"},
	"Decorative desert cobble 5 slab",
	stairs.stone)

stairs.register_slab("desert_stone1", "xanadu:desert_stone1",
	grp,
	{"desert_stone1.png"},
	"Decorative desert stone 1 slab",
	stairs.stone)

stairs.register_slab("desert_stone3", "xanadu:desert_stone3",
	grp,
	{"desert_stone3.png"},
	"Decorative desert stone 3 slab",
	stairs.stone)

stairs.register_slab("desert_stone4", "xanadu:desert_stone4",
	grp,
	{"desert_stone4.png"},
	"Decorative desert stone 4 slab",
	stairs.stone)

stairs.register_stair("desert_stone4", "xanadu:desert_stone4",
	grp,
	{"desert_stone4.png"},
	"Decorative desert stone 4 stair",
	stairs.stone)

stairs.register_slab("desert_stone5", "xanadu:desert_stone5",
	grp,
	{"desert_stone5.png"},
	"Decorative desert stone 5 slab",
	stairs.stone)

stairs.register_slab("red1", "xanadu:red1",
	grp,
	{"baked_clay_red1.png"},
	"Decorative baked red clay 1 slab",
	stairs.stone)

stairs.register_all("bred2", "xanadu:red2",
	grp,
	{"baked_clay_red2.png"},
	"Decorative baked red clay 2",
	stairs.stone)

end

--= Castle Mod

if minetest.get_modpath("castle") then

grp = {cracky = 2}

stairs.register_all("dungeon_stone", "castle:dungeon_stone",
	grp,
	{"castle_dungeon_stone.png"},
	"Dungeon",
	stairs.stone)

stairs.register_all("stonewall", "castle:stonewall",
	grp,
	{"castle_stonewall.png"},
	"Castle Wall",
	stairs.stone)

end

--= Wool Mod

if minetest.get_modpath("wool") then

local colours = {
	{"black",      "Black",      "#000000b0"},
	{"blue",       "Blue",       "#015dbb70"},
	{"brown",      "Brown",      "#a78c4570"},
	{"cyan",       "Cyan",       "#01ffd870"},
	{"dark_green", "Dark Green", "#005b0770"},
	{"dark_grey",  "Dark Grey",  "#303030b0"},
	{"green",      "Green",      "#61ff0170"},
	{"grey",       "Grey",       "#5b5b5bb0"},
	{"magenta",    "Magenta",    "#ff05bb70"},
	{"orange",     "Orange",     "#ff840170"},
	{"pink",       "Pink",       "#ff65b570"},
	{"red",        "Red",        "#ff000070"},
	{"violet",     "Violet",     "#2000c970"},
	{"white",      "White",      "#abababc0"},
	{"yellow",     "Yellow",     "#e3ff0070"},
}

for i = 1, #colours, 1 do

stairs.register_all("wool_" .. colours[i][1], "wool:" .. colours[i][1],
	{snappy = 2, choppy = 2, oddly_breakable_by_hand = 3, flammable = 3},
	{"wool_" .. colours[i][1] .. ".png"},
	colours[i][2] .. " Wool",
	stairs.wool)

end -- END for

end


code:
# Technic Addons
![Mk2 and Mk3 Chainsaws](chainsaws.png)
Mod was originally made for the https://playminetest.com server on the request of a player to add MK2 and MK3 chainsaws. The mod currently features...
- Chainsaw MK2: Holds 375,000 EU and uses 11EU/node
- Chainsaw MK3: Holds 1,000,000 EU and uses 10EU/node

All textures and coding was based off of Technic which it's repository is https://github.com/mt-mods/technic and therefore the unmodified portions of the coding and textures still belongs to the original Technic mod creator(s).
# How to Contribute to this mod
In order to contribute ideas or requests for changes, please create a [New Issue](https://notabug.org/PlayMinetest/TechnicAddons/issues/new) in the [issue tracker](https://notabug.org/PlayMinetest/TechnicAddons/issues). If you can make changes directly, please feel free to fork this repo, commit your improvements with comments, and submit a detailed Pull Request **that been ALREADLY been tested in a localized multiplayer server**. For the additions or changes you would like to propose to us. This is ensure we are able to push your changes as quickly as possible.


code:
-- Configuration

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

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_addons:chainsawmk2", 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_addons:chainsawmk2", {
	description = S("Chainsaw Mk2"),
	inventory_image = "technicaddons_chainsawmk2.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_addons:chainsawmk2",
	recipe = {
		{"", "technic:stainless_steel_ingot", "technic:diamond_drill_head"},
		{"technic:stainless_steel_ingot", "technic:chainsaw", "technic:stainless_steel_ingot"},
		{"technic:green_energy_crystal", "technic:stainless_steel_ingot", ""},
	},
	replacements = { {"basic_materials:gold_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:
-- Configuration

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

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_addons:chainsawmk3", 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_addons:chainsawmk3", {
	description = S("Chainsaw MK3"),
	inventory_image = "technicaddons_chainsawmk3.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_addons:chainsawmk3",
	recipe = {
		{"", "technic:diamond_drill_head", "technic:diamond_drill_head"},
		{"technic:carbon_plate", "technic_addons:chainsawmk2", "technic:diamond_drill_head"},
		{"technic:blue_energy_crystal", "technic:composite_plate", ""},
	}})

-- 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:
local modpath = minetest.get_modpath("technic_addons")
technic.modpath = modpath
dofile(modpath.."/chainsawmk2.lua")
dofile(modpath.."/chainsawmk3.lua")


code:
name = technic_addons
depends = default, pipeworks, technic_worldgen, basic_materials, technic


code:
# Auto detect text files and perform LF normalization
* text=auto

# Custom for Visual Studio
*.cs     diff=csharp
*.sln    merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union

# Standard to msysgit
*.doc	 diff=astextplain
*.DOC	 diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot  diff=astextplain
*.DOT  diff=astextplain
*.pdf  diff=astextplain
*.PDF	 diff=astextplain
*.rtf	 diff=astextplain
*.RTF	 diff=astextplain


code:
version: "1"
rules:
  - base: upstream
    upstream: minetest-mods:master
    mergeMethod: hardreset
  - base: master
    upstream: minetest-mods:master
    mergeMethod: merge


code:
name: integration-test-latest

on: [push, pull_request]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v1

    - name: test
      run: MINETEST_VERSION=latest ./integration-test.sh


code:
name: integration-test

on: [push, pull_request]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v1

    - name: test
      run: MINETEST_VERSION=5.2.0 ./integration-test.sh


code:
on: [push, pull_request]
name: luacheck
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@master
      - name: lint
        uses: Roang-zero1/factorio-mod-luacheck@master
        with:
          luacheckrc_url: ""


code:
name: luacheck

on: [push, pull_request]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v1
    - name: apt
      run: sudo apt-get install -y luarocks
    - name: luacheck install
      run: luarocks install --local luacheck
    - name: luacheck run
      run: $HOME/.luarocks/bin/luacheck ./


code:
#################
## Eclipse
#################

*.pydevproject
.project
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.classpath
.settings/
.loadpath

# External tool builders
.externalToolBuilders/

# Locally stored "Eclipse launch configurations"
*.launch

# CDT-specific
.cproject

# PDT-specific
.buildpath

#################
## Visual Studio
#################

## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.

# User-specific files
*.suo
*.user
*.sln.docstates

# Build results
[Dd]ebug/
[Rr]elease/
*_i.c
*_p.c
*.ilk
*.meta
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.vspscc
.builds
*.dotCover

## TODO: If you have NuGet Package Restore enabled, uncomment this
#packages/

# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf

# Visual Studio profiler
*.psess
*.vsp

# ReSharper is a .NET coding add-in
_ReSharper*

# Installshield output folder
[Ee]xpress

# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html

# Click-Once directory
publish

# Others
[Bb]in
[Oo]bj
sql
TestResults
*.Cache
ClientBin
stylecop.*
~$*
*.dbmdl
Generated_Code #added for RIA/Silverlight projects

# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML

############
## Windows
############

# Windows image file caches
Thumbs.db

# Folder config file
Desktop.ini

#############
## Python
#############

*.py[co]

# Packages
*.egg
*.egg-info
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg

# Installer logs
pip-log.txt

# Unit test / coverage reports
.coverage
.tox

#Translations
*.mo

#Mr Developer
.mr.developer.cfg

# Mac crap
.DS_Store

#gedit backup files
*~


code:
unused_args = false
max_line_length = 999

globals = {
    "technic", "technic_cnc", "minetest", "wrench"
}

read_globals = {
    string = {fields = {"split", "trim"}},
    table = {fields = {"copy", "getn"}},

    "intllib", "VoxelArea",
    "default", "stairsplus",

    "PseudoRandom", "ItemStack",
    "mg", "tubelib", "vector",

    "moretrees", "bucket",
    "unified_inventory", "digilines",

    "pipeworks", "screwdriver",
    "VoxelManip", "unifieddyes",

    "Settings", "mesecon",
    "digiline_remote",

    "protector", "isprotect",
    "homedecor_expect_infinite_stacks",
    "monitoring"
}

files["concrete/init.lua"].ignore = { "steel_ingot" }
files["technic/machines/MV/tool_workshop.lua"].ignore = { "pos" }
files["technic/machines/other/frames.lua"].ignore = { "item_texture", "item_type", "adj", "connected", "" }
files["technic/machines/register/battery_box.lua"].ignore = { "pos", "tube_upgrade" }
files["technic/machines/register/cables.lua"].ignore = { "name", "from_below", "p" }
files["technic/machines/register/common.lua"].ignore = { "result" }

files["technic/machines/register/generator.lua"].ignore = { "node" }
files["technic/machines/switching_station.lua"].ignore = { "pos1", "tier", "poshash" }
files["technic/radiation.lua"].ignore = { "LAVA_VISC" }
files["technic/tools/chainsaw.lua"].ignore = { "pos" }
files["technic/tools/mining_drill.lua"].ignore = { "mode" }
files["technic_chests/register.lua"].ignore = { "fs_helpers", "name", "locked_after_place" }

files["technic_cnc/cnc.lua"].ignore = { "multiplier" }
files["wrench/init.lua"].ignore = { "name", "stack" }


code:
RealBadAngel <maciej.kasatkin@o2.pl> <mk@realbadangel.pl>
Vanessa Ezekowitz <vanessaezekowitz@gmail.com>
Vanessa Ezekowitz <vanessaezekowitz@gmail.com> <vanessa@rainbird.(none)>
kaeza <kaeza@users.sf.net> Diego Martínez <kaeza@users.sf.net>


code:
Technic
-----------------

A mod for [minetest](http://www.minetest.net)

![](https://github.com/mt-mods/technic/workflows/integration-test/badge.svg)
![](https://github.com/mt-mods/technic/workflows/luacheck/badge.svg)
[![License](https://img.shields.io/badge/license-LGPLv2.0%2B-purple.svg)](https://www.gnu.org/licenses/old-licenses/lgpl-2.0.en.html)

# Overview

<img src="./technic/doc/images/Technic Screenshot.png"/>

The technic modpack extends the Minetest game with many new elements,
mainly constructable machines and tools.  It is a large modpack, and
tends to dominate gameplay when it is used.  This manual describes how
to use the technic modpack, mainly from a player's perspective.

The technic modpack depends on some other modpacks:

*   the basic Minetest game
*   mesecons, which supports the construction of logic systems based on
    signalling elements
*   pipeworks, which supports the automation of item transport
*   moreores, which provides some additional ore types
*   basic_materials, which provides some basic craft items

This manual doesn't explain how to use these other modpacks, which have
their own manuals:

*   [Minetest Game Documentation](https://wiki.minetest.net/Main_Page)
*   [Mesecons Documentation](http://mesecons.net/items.html)
*   [Pipeworks Documentation](https://gitlab.com/VanessaE/pipeworks/-/wikis/home)
*   [Moreores Forum Post](https://forum.minetest.net/viewtopic.php?t=549)
*   [Basic materials Repository](https://gitlab.com/VanessaE/basic_materials)

Recipes for constructable items in technic are generally not guessable,
and are also not specifically documented here.  You should use a
craft guide mod to look up the recipes in-game.  For the best possible
guidance, use the unified\_inventory mod, with which technic registers
its specialised recipe types.

# Documentation

Ingame:
* [Resources](./technic/doc/resources.md)
* [Substances](./technic/doc/substances.md)
* [Processes](./technic/doc/processes.md)
* [Chests](./technic/doc/chests.md)
* [Radioactivity](./technic/doc/radioactivity.md)
* [Electrical power](./technic/doc/power.md)
* [Powered machines](./technic/doc/machines.md)
* [Generators](./technic/doc/generators.md)
* [Forceload anchor](./technic/doc/anchor.md)
* [Digilines](./technic/doc/digilines.md)
* [Mesecons](./technic/doc/mesecons.md)
* [Tools](./technic/doc/tools.md)

Mod development:
* [Api](./technic/doc/api.md)

subjects missing from this manual:
* frames
* templates

## FAQ

1. My technic circuit doesn't work.  No power is distributed.
  * A: Make sure you have a switching station connected.

# Notes

This is a maintained fork of https://github.com/minetest-mods/technic with various enhancements.
Suitable for multiplayer environments.

* chainsaw re-implementation (@OgelGames)
* Switching station lag/polyfuse and globalstep execution (@BuckarooBanzay)
* No forceload hacks
* Additional HV machines (furnace, grinder, thx to @h-v-smacker)
* HV-digiline cables (@S-S-X)
* various others...

## Compatibility

This mod is meant as a **drop-in replacement** for the upstream `technic` mod.
It also provides some additional machines and items, notably:

* HV Grinder
* HV Furnace
* LV Lamp
* HV Digiline cables

# Recommended mods

Dependencies:

* https://github.com/minetest-mods/mesecons
* https://github.com/minetest-mods/moreores
* https://gitlab.com/VanessaE/pipeworks
* https://gitlab.com/VanessaE/basic_materials

Recommended optional Dependencies:

* https://github.com/minetest-mods/digilines

Recommended mods that build on the `technic mod`:

* https://github.com/mt-mods/jumpdrive
* https://github.com/OgelGames/powerbanks

# Settings

* **technic.quarry.maxdepth** max depth of the quarry (default: 100)
* **technic.switch_max_range** max cable length (default: 256)
* **technic.switch.off_delay_seconds** switching station off delay (default: 1800 seconds)
* **technic.radiation.enable_throttling** enable lag- and per-second-trottling of radiation damage

# Chat commands

* **/technic_flush_switch_cache** clears the switching station cache (stops all unloaded switches)
* **/powerctrl [on|off]** enable/disable technic power distribution globally

# Contributors

* kpoppel
* Nekogloop
* Nore/Ekdohibs
* ShadowNinja
* VanessaE
* BuckarooBanzay
* OgelGames
* int-ua
* S-S-X
* H-V-Smacker
* groxxda
* And many others...

# License

Unless otherwise stated, all components of this modpack are licensed under the
LGPL, V2 or later.  See also the individual mod folders for their
secondary/alternate licenses, if any.


code:
default
basic_materials
intllib?
moreblocks?


code:
--Minetest 0.4.7 mod: concrete
--(c) 2013 by RealBadAngel <mk@realbadangel.pl>

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

-- Boilerplate to support localized strings if intllib mod is installed.
local S = rawget(_G, "intllib") and intllib.Getter() or function(s) return s end

for i = 0, 31 do
	minetest.register_alias("technic:concrete_post"..i,
			"technic:concrete_post")
end
for i = 32, 63 do
	minetest.register_alias("technic:concrete_post"..i,
			"technic:concrete_post_with_platform")
end

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

minetest.register_craft({
	output = 'technic:concrete_post_platform 6',
	recipe = {
		{'technic:concrete','technic:concrete_post','technic:concrete'},
	}
})

minetest.register_craft({
	output = 'technic:concrete_post 12',
	recipe = {
		{'default:stone','basic_materials:steel_bar','default:stone'},
		{'default:stone','basic_materials:steel_bar','default:stone'},
		{'default:stone','basic_materials:steel_bar','default:stone'},
	}
})

minetest.register_craft({
	output = 'technic:blast_resistant_concrete 5',
	recipe = {
		{'technic:concrete','technic:composite_plate','technic:concrete'},
		{'technic:composite_plate','technic:concrete','technic:composite_plate'},
		{'technic:concrete','technic:composite_plate','technic:concrete'},
	}
})

minetest.register_node(":technic:blast_resistant_concrete", {
	description = S("Blast-resistant Concrete Block"),
	tiles = {"technic_blast_resistant_concrete_block.png",},
	groups = {cracky=1, level=3, concrete=1},
	sounds = default.node_sound_stone_defaults(),
	on_blast = function(pos, intensity)
		if intensity > 9 then
			minetest.remove_node(pos)
			return {"technic:blast_resistant_concrete"}
		end
	end,
})

if minetest.get_modpath("moreblocks") then
	stairsplus:register_all("technic","blast_resistant_concrete","technic:blast_resistant_concrete",{
		description = "Blast-resistant Concrete",
		tiles = {"technic_blast_resistant_concrete_block.png",},
		groups = {cracky=1, level=3, concrete=1},
		sounds = default.node_sound_stone_defaults(),
		on_blast = function(pos, intensity)
			if intensity > 1 then
				minetest.remove_node(pos)
				minetest.add_item(pos, "technic:blast_resistant_concrete")
			end
		end,
	})
end

local box_platform = {-0.5,  0.3,  -0.5,  0.5,  0.5, 0.5}
local box_post     = {-0.15, -0.5, -0.15, 0.15, 0.5, 0.15}
local box_front    = {-0.1,  -0.3, -0.5,  0.1,  0.3, 0}
local box_back     = {-0.1,  -0.3, 0,     0.1,  0.3, 0.5}
local box_left     = {-0.5,  -0.3, -0.1,  0,    0.3, 0.1}
local box_right    = {0,     -0.3, -0.1,  0.5,  0.3, 0.1}

minetest.register_node(":technic:concrete_post_platform", {
	description = S("Concrete Post Platform"),
	tiles = {"basic_materials_concrete_block.png",},
	groups={cracky=1, level=2},
	sounds = default.node_sound_stone_defaults(),
	paramtype = "light",
	drawtype = "nodebox",
	node_box = {
		type = "fixed",
		fixed = {box_platform}
	},
	on_place = function (itemstack, placer, pointed_thing)
		local node = minetest.get_node(pointed_thing.under)
		if node.name ~= "technic:concrete_post" then
			return minetest.item_place_node(itemstack, placer, pointed_thing)
		end
		minetest.set_node(pointed_thing.under, {name="technic:concrete_post_with_platform"})
		itemstack:take_item()
		placer:set_wielded_item(itemstack)
		return itemstack
	end,
})

for platform = 0, 1 do
	local after_dig_node = nil
	if platform == 1 then
		after_dig_node = function(pos, old_node)
			old_node.name = "technic:concrete_post"
			minetest.set_node(pos, old_node)
		end
	end

	minetest.register_node(":technic:concrete_post"..(platform == 1 and "_with_platform" or ""), {
		description = S("Concrete Post"),
		tiles = {"basic_materials_concrete_block.png"},
		groups = {cracky=1, level=2, concrete_post=1, not_in_creative_inventory=platform},
		sounds = default.node_sound_stone_defaults(),
		drop = (platform == 1 and "technic:concrete_post_platform" or
				"technic:concrete_post"),
		paramtype = "light",
		sunlight_propagates = true,
		drawtype = "nodebox",
		connects_to = {"group:concrete", "group:concrete_post"},
		node_box = {
			type = "connected",
			fixed = {box_post, (platform == 1 and box_platform or nil)},
			connect_front = box_front,
			connect_back  = box_back,
			connect_left  = box_left,
			connect_right = box_right,
		},
		after_dig_node = after_dig_node,
	})
end


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

Rebar = Bewehrungsstab
Concrete Block = Betonblock
Blast-resistant Concrete Block = Explosionsbestaendiger Betonblock
Concrete Post Platform = Betonpfostenplattform
Concrete Post = Betonpfosten


code:
# technic_concrete traducido por Carlos Barraza

Rebar = Barra de refuerzo
Concrete Block = Bloque de concreto
Blast-resistant Concrete Block = Bloque de concreto resistente a explosiones
Concrete Post Platform = Plataforma de concreto
Concrete Post = Postes de concreto


code:
# technic_concrete translation template

Rebar                           = Armature
Concrete Block                  = Bloc de béton
Blast-resistant Concrete Block  = Bloc de béton anti explosions
Concrete Post Platform          = Plateforme en béton
Concrete Post                   = Pilier en béton


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

Rebar = Pręt zbrojeniowy
Concrete Block = Blok betonu
Blast-resistant Concrete Block = Przeciwwybuchowy blok betonu
Concrete Post Platform = Betonowa platforma
Concrete Post = Betonowy słup


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

Rebar = Vergalhão
Concrete Block = Bloco de Concreto
Blast-resistant Concrete Block = Bloco de Concreto resistente-a-explosões
Concrete Post Platform = Plataforma para Poste de Concreto
Concrete Post = Poste de Concreto


code:
# technic_concrete translation template

Rebar =
Concrete Block =
Blast-resistant Concrete Block =
Concrete Post Platform =
Concrete Post =


code:
# turkish translation by mahmutelmas06

Rebar = Beton demiri
Concrete Block = Beton blok
Blast-resistant Concrete Block = Patlamaya dayanıklı beton blok
Concrete Post Platform = Beton direk platformu
Concrete Post = Beton direk


code:
name = concrete
depends = default
optional_depends = basic_materials, intllib, moreblocks


code:
default
technic_worldgen
basic_materials
concrete
unifieddyes?
intllib?
moreblocks?
steel?
streetsmod?