code:
local load_time_start = minetest.get_us_time()

------------------------------------- Settings ---------------------------------

-- default settings
treecapitator = {
	drop_items = false,
	drop_leaf = false,
	play_sound = true,
	moretrees_support = false,
	no_hand_capitation = false,
	delay = 0,
	stem_height_min = 3,
	default_tree = {	--replaces not defined stuff (see below)
		trees = {"default:tree"},
		leaves = {"default:leaves"},
		range = 2,
		fruits = {},
		type = "default",
	},
	after_register = {},
}

-- load custom settings
for name,v in pairs(treecapitator) do
	local setting = "treecapitator." .. name
	--local typ = type(v)
	local neuv
	if type(v) == "boolean" then
		neuv = minetest.settings:get_bool(setting)
	else--if typ == "number" then
		neuv = tonumber(minetest.settings:get(setting))
	end
	if neuv ~= nil then
		treecapitator[name] = neuv
	end
end

-------------------------- Common functions ------------------------------------

local poshash = minetest.hash_node_position

local function hash2(x, y)
	return y * 0x10000 + x
end

-- don't use minetest.get_node more times for the same position (caching)
local known_nodes
local function clean_cache()
	known_nodes = {}
	setmetatable(known_nodes, {__mode = "kv"})
end
clean_cache()

local function remove_node(pos)
	known_nodes[poshash(pos)] = {name="air", param2=0}
	minetest.remove_node(pos)
	minetest.check_for_falling(pos)
end

local function get_node(pos)
	local vi = poshash(pos)
	local node = known_nodes[vi]
	if node then
		return node
	end
	node = minetest.get_node(pos)
	known_nodes[vi] = node
	return node
end

--definitions of functions for the destruction of nodes
local destroy_node, drop_leaf, remove_leaf
if treecapitator.drop_items then
	function drop_leaf(pos, item)
		minetest.add_item(pos, item)
	end

	function destroy_node(pos, node)
		local drops = minetest.get_node_drops(node.name)
		for _,item in pairs(drops) do
			minetest.add_item(pos, item)
		end
		remove_node(pos)
	end
else
	if minetest.settings:get_bool"creative_mode" then
		function drop_leaf(_, item, inv)
			if inv
			and inv:room_for_item("main", item)
			and not inv:contains_item("main", item) then
				inv:add_item("main", item)
			end
		end
	else
		function drop_leaf(pos, item, inv)
			if inv
			and inv:room_for_item("main", item) then
				inv:add_item("main", item)
			else
				minetest.add_item(pos, item)
			end
		end
	end

	destroy_node = function(pos, node, digger)
		known_nodes[poshash(pos)] = {name="air", param2=0}
		minetest.node_dig(pos, node, digger)
	end
end

if not treecapitator.drop_leaf then
	function remove_leaf(pos, node, inv)
		local leaves_drops = minetest.get_node_drops(node.name)
		for _, itemname in pairs(leaves_drops) do
			if itemname ~= node.name then
				drop_leaf(pos, itemname, inv)
			end
		end
		remove_node(pos)	--remove the leaves
	end
else
	function remove_leaf(pos, node, _, digger)
		destroy_node(pos, node, digger)
	end
end

table_contains = function(t, v)
	for i = 1,#t do
		if t[i] == v then
			return true
		end
	end
	return false
end

-- the functions for the available types
local capitate_funcs = {}

------------------------ Function for regular trees ----------------------------

-- tests if the node is a trunk which could belong to the same tree sort
local function is_trunk_of_tree(trees, node)
	return node.param2 == 0
		and trees ^ node.name
end

-- test if the trunk node there is the top trunk node of a neighbour tree
-- if so, constrain the possible leaves positions
local function get_a_tree(pos, tab, tr, xo,yo,zo)
	local p = {x=pos.x + xo, y=pos.y + yo, z=pos.z + zo}

	-- tests if a trunk is at the current pos
	local nd = get_node(p)
	if not is_trunk_of_tree(tr.trees, nd) then
		return false
	end

	-- search for a leaves or fruit node next to the trunk
	local leaf = get_node{x=p.x, y=p.y+1, z=p.z}.name
	if not tr.leaves ^ leaf
	and not tr.fruits ^ leaf then
		local leaf = get_node{x=p.x, y=p.y, z=p.z+1}.name
		if not tr.leaves ^ leaf
		and not tr.fruits ^ leaf then
			return false
		end
	end

	-- search for the requisite amount of stem trunk nodes
	for _ = 1, tr.stem_height_min-1 do
		p.y = p.y-1
		if not is_trunk_of_tree(tr.trees, get_node(p)) then
			return false
		end
	end
	p.y = p.y + tr.stem_height_min-1

	local r = tr.range
	local r_up = tr.range_up or r
	local r_down = tr.range_down or r

	-- reduce x and z avoidance range for thick stem neighbour trees
	if tr.stem_type == "2x2" then
		r = r - 1
	elseif tr.stem_type == "+" then
		r = r - 2
	end

	-- tag places which should not be removed
	local z1 = math.max(-r + zo, -r)
	local z2 = math.min(r + zo, r)
	local y1 = math.max(-r_down + yo, -r_down)
	local y2 = math.min(r_up + yo, r_up)
	local x1 = math.max(-r + xo, -r)
	local x2 = math.min(r + xo, r)
	for z = z1,z2 do
		for y = y1,y2 do
			local i = poshash{x=x1, y=y, z=z}
			for _ = x1,x2 do
				tab[i] = true
				i = i+1
			end
		end
	end
	return true
end

-- returns positions for leaves allowed to be dug
local function find_valid_head_ps(pos, head_ps, trunktop_ps, tr)
	-- exclude the stem nodes
	local before_stems = {}
	for i = 1,#trunktop_ps do
		local p = vector.subtract(trunktop_ps[i], pos)
		before_stems[hash2(p.x, p.z)] = p.y+1
	end

	local r = tr.range
	local r_up = tr.range_up or r
	local r_down = tr.range_down or r

	-- firstly, detect neighbour trees of the same sort to not hurt them
	local tab = {}
	local rx2 = 2 * r
	local rupdown = r_up + r_down
	for z = -rx2, rx2 do
		for x = -rx2, rx2 do
			local bot = before_stems[hash2(x, z)] or -rupdown
			for y = rupdown, bot, -1 do
				if get_a_tree(pos, tab, tr, x,y,z) then
					break
				end
			end
		end
	end
	-- now, get the head positions without the neighbouring trees
	local n = #head_ps
	for z = -r,r do
		for x = -r,r do
			local bot = before_stems[hash2(x, z)] or -r_down
			for y = bot,r_up do
				local p = {x=x, y=y, z=z}
				if not tab[poshash(p)] then
					n = n+1
					head_ps[n] = vector.add(pos, p)
				end
			end
		end
	end
	return n
end

-- adds the stem to the trunks
local function get_stem(trunktop_ps, trunks, tr, head_ps)
	if tr.cutting_leaves then
		treecapitator.moretrees34(trunktop_ps, trunks, tr, head_ps,
			get_node, is_trunk_of_tree)
		return
	end
	for i = 1,#trunktop_ps do
		local pos = trunktop_ps[i]
		local node = get_node(pos)
		while is_trunk_of_tree(tr.trees, node) do
			trunks[#trunks+1] = {pos, node}
			pos = {x=pos.x, y=pos.y+1, z=pos.z}
			node = get_node(pos)
		end

		-- renew trunk top position
		pos.y = pos.y-1
		trunktop_ps[i] = pos
	end
end

-- part of healthy stem searching
local function here_neat_stemps(p, tr)
	local ps = {}
	for i = 1,#tr.stem_offsets do
		local o = tr.stem_offsets[i]
		local p = {x = p.x + o[1], y = p.y, z = p.z + o[2]}
		-- air test is too simple (makeshift solution)
		if get_node(p).name ~= "air" then
			return
		end
		p.y = p.y+1
		if not is_trunk_of_tree(tr.trees, get_node(p)) then
			return
		end
		ps[#ps+1] = p
	end
	return ps
end

-- gives stem positions of a healthy tree
local function find_neat_stemps(pos, tr)
	for i = 1,#tr.stem_offsets do
		local o = tr.stem_offsets[i]
		local p = {x = pos.x - o[1], y = pos.y, z = pos.z - o[2]}
		local ps = here_neat_stemps(p, tr)
		if ps then
			return ps
		end
	end
	-- nothing found
end

-- part of incomplete stem searching
local function here_incomplete_stemps(p, tr)
	local ps = {}
	for i = 1,#tr.stem_offsets do
		local o = tr.stem_offsets[i]
		local p = {x = p.x + o[1], y = p.y+1, z = p.z + o[2]}
		if is_trunk_of_tree(tr.trees, get_node(p)) then
			p.y = p.y-1
			local node = get_node(p)
			if is_trunk_of_tree(tr.trees, node) then
				-- stem wasn't chopped enough
				return {}
			end
			-- air test is too simple (makeshift solution)
			if node.name == "air" then
				p.y = p.y+1
				ps[#ps+1] = p
			end
		end
	end
	-- #ps ∈ [3]
	return ps
end

-- gives stem positions of an eroded tree
local function find_incomplete_stemps(pos, tr)
	local ps
	local stemcount = 0
	for i = 1,#tr.stem_offsets do
		local o = tr.stem_offsets[i]
		local p = {x = pos.x - o[1], y = pos.y, z = pos.z - o[2]}
		local cps = here_incomplete_stemps(p, tr)
		local cnt = #cps
		if cnt == 0 then
			-- player needs to chop more
			return
		end
		if stemcount < cnt then
			stemcount = #cps
			ps = cps
		end
	end
	return ps
end

-- returns the lowest trunk node positions
local function get_stem_ps(pos, tr)
	if not tr.stem_type then
		-- 1x1 stem
		return {{x=pos.x, y=pos.y+1, z=pos.z}}
	end
	return find_neat_stemps(pos, tr)
		or find_incomplete_stemps(pos, tr)
end

-- gets the middle position of the tree head
local function get_head_center(trunktop_ps, stem_type)
	if stem_type == "2x2" then
		-- return the highest position
		local pos = trunktop_ps[1]
		for i = 2,#trunktop_ps do
			local p = trunktop_ps[i]
			if p.y > pos.y then
				pos = p
			end
		end
		return pos
	elseif stem_type == "+" then
		-- return the middle position
		local mid = vector.new()
		for i = 1,#trunktop_ps do
			mid = vector.add(mid, trunktop_ps[i])
		end
		return vector.round(vector.divide(mid, #trunktop_ps))
	else
		return trunktop_ps[1]
	end
end

function capitate_funcs.default(pos, tr, _, digger)
	local trees = tr.trees

	-- get the stem trunks
	local trunks = {}
	local trunktop_ps = get_stem_ps(pos, tr)
	if not trunktop_ps then
		return
	end
	local head_ps = {}
	get_stem(trunktop_ps, trunks, tr, head_ps)

	local leaves = tr.leaves
	local fruits = tr.fruits
	local hcp = get_head_center(trunktop_ps, tr.stem_type)

	-- abort if the tree lacks leaves/fruits
	local ln = get_node{x=hcp.x, y=hcp.y+1, z=hcp.z}
	if not leaves ^ ln.name
	and not fruits ^ ln.name then
		local leaf = get_node{x=hcp.x, y=hcp.y, z=hcp.z+1}.name
		if not leaves ^ leaf
		and not fruits ^ leaf then
			return
		end
	end

	-- get leaves, fruits and stem fruits
	local leaves_found = {}
	local n = find_valid_head_ps(hcp, head_ps, trunktop_ps, tr)
	local leaves_toremove = {}
	local fruits_toremove = {}
	for i = 1,n do
		local p = head_ps[i]
		local node = get_node(p)
		local nodename = node.name
		local is_trunk = trees ^ nodename
		if node.param2 ~= 0
		or not is_trunk then
			if leaves ^ nodename then
				leaves_found[nodename] = true
				leaves_toremove[#leaves_toremove+1] = {p, node}
			elseif fruits ^ nodename then
				fruits_toremove[#fruits_toremove+1] = {p, node}
			end
		elseif is_trunk
		and tr.trunk_fruit_vertical
		and fruits ^ nodename then
			trunks[#trunks+1] = {p, node}
		end
	end

	if tr.requisite_leaves then
		-- abort if specific leaves weren't found
		for i = 1,#tr.requisite_leaves do
			if not leaves_found[tr.requisite_leaves[i]] then
				return
			end
		end
	end

	-- remove fruits at first due to attachment
	-- and disable nodeupdate temporarily
	local nodeupdate = minetest.check_for_falling
	minetest.check_for_falling = function() end
	for i = 1,#fruits_toremove do
		destroy_node(fruits_toremove[i][1], fruits_toremove[i][2], digger)
	end
	minetest.check_for_falling = nodeupdate
	local inv = digger:get_inventory()
	for i = 1,#leaves_toremove do
		remove_leaf(leaves_toremove[i][1], leaves_toremove[i][2], inv, digger)
	end
	for i = 1,#trunks do
		destroy_node(trunks[i][1], trunks[i][2], digger)
	end

	-- tree was capitated, play sound
	if treecapitator.play_sound then
		minetest.sound_play("tree_falling", {pos = pos, max_hear_distance = 32})
	end
	return true
end

-- metatable for shorter code: trees ^ name ≙ name ∈ trees
local mt_default = {
	__pow = table_contains
}
treecapitator.after_register.default = function(tr)
	setmetatable(tr.trees, mt_default)
	setmetatable(tr.leaves, mt_default)
	setmetatable(tr.fruits, mt_default)
	tr.range_up = tr.range_up or tr.range
	tr.range_down = tr.range_down or tr.range
	tr.stem_height_min = tr.stem_height_min or treecapitator.stem_height_min

	if tr.stem_type == "2x2" then
		tr.stem_offsets = {
			{0,0}, {1,0},
			{0,1}, {1,1},
		}
	elseif tr.stem_type == "+" then
		tr.stem_offsets = {
			{0,0},
				{0,1},
			{-1,0},	{1,0},
				{0,-1},
		}
	end
end

--------------------- Acacia tree function -------------------------------------

function capitate_funcs.acacia(pos, tr, node_above, digger)
	local trunk = tr.trees[1]

	-- fill tab with the stem trunks
	local tab, n = {{{x=pos.x, y=pos.y+1, z=pos.z}, node_above}}, 2
	local np = {x=pos.x, y=pos.y+2, z=pos.z}
	local nd = get_node(np)
	while trunk == nd.name
	and nd.param2 < 4 do
		tab[n] = {vector.new(np), nd}
		n = n+1
		np.y = np.y+1
		nd = get_node(np)
	end
	np.y = np.y-1

	local inv = digger:get_inventory()
	for z = -1,1,2 do
		for x = -1,1,2 do
			-- add the other trunks to tab
			local p = vector.new(np)
			p.x = p.x+x
			p.z = p.z+z
			local nd = get_node(p)
			if nd.name ~= trunk then
				p.y = p.y+1
				nd = get_node(p)
				if nd.name ~= trunk then
					return
				end
			end
			tab[n] = {vector.new(p), nd}

			p.x = p.x+x
			p.z = p.z+z
			p.y = p.y+1

			if get_node(p).name ~= trunk then
				return
			end
			tab[n+1] = {vector.new(p), nd}
			n = n+2

			-- get neighbouring acacia trunks for delimiting
			local no_rms = {}
			for z = -4,4 do
				for x = -4,4 do
					if math.abs(x+z) ~= 8
					and (x ~= 0 or z ~= 0) then
						if get_node{x=p.x+x, y=p.y, z=p.z+z}.name == trunk
						and get_node{x=p.x+x, y=p.y+1, z=p.z+z}.name == tr.leaf then
							for z = math.max(-4, z-2), math.min(4, z+2) do
								for x = math.max(-4, x-2), math.min(4, x+2) do
									no_rms[(z+4)*9 + x+4] = true
								end
							end
						end
					end
				end
			end

			-- remove leaves
			p.y = p.y+1
			local i = 0
			for z = -4,4 do
				for x = -4,4 do
					if not no_rms[i] then
						local p = {x=p.x+x, y=p.y, z=p.z+z}
						local node = get_node(p)
						if node.name == tr.leaf then
							remove_leaf(p, node, inv, digger)
						end
					end
					i = i+1
				end
			end
		end
	end

	-- play the sound, then dig the stem
	if treecapitator.play_sound then
		minetest.sound_play("tree_falling", {pos = pos, max_hear_distance = 32})
	end
	for i = 1,n-1 do
		local pos,node = unpack(tab[i])
		destroy_node(pos, node, digger)
	end
	return true
end

----------------------- Palm tree function -------------------------------------

-- the 17 vectors used for walking the stem
local palm_stem_dirs = {
	{0,1,0}
}
local n = 2
for i = -1,1,2 do
	palm_stem_dirs[n] = {i,0,0}
	palm_stem_dirs[n+1] = {0,0,i}
	n = n+2
end
for i = -1,1,2 do
	palm_stem_dirs[n] = {i,0,i}
	palm_stem_dirs[n+1] = {i,0,-i}
	n = n+2
end
for i = -1,1,2 do
	palm_stem_dirs[n] = {i,1,0}
	palm_stem_dirs[n+1] = {0,1,i}
	n = n+2
end
for i = -1,1,2 do
	palm_stem_dirs[n] = {i,1,i}
	palm_stem_dirs[n+1] = {i,1,-i}
	n = n+2
end
for i = 1,17 do
	local p = palm_stem_dirs[i]
	palm_stem_dirs[i] = vector.new(unpack(p))
end

local pos_from_hash = minetest.get_position_from_hash

-- gets a list of leaves positions
local function get_palm_head(hcp, tr, max_forbi)
	local pos = {x=hcp.x, y=hcp.y+1, z=hcp.z}
	local leaves = {}
	if get_node(pos).name ~= tr.leaves then
		-- search hub position
		for xo = -1,1 do
			for zo = -1,1 do
				local p = {x=pos.x+xo, y=pos.y, z=pos.z+zo}
				if get_node(p).name == tr.leaves then
					pos = p
				end
			end
		end
	end
	-- collect leaves
	leaves[poshash(pos)] = true
	for i = -1,1 do
		for j = -1,1 do
			-- don't search around the corner except max_forbi time(s)
			local dirs = {{0,0}, {i,0}, {0,j}, {i,j},  {-i,0}, {0,-j}}
			local avoids = {}
			local todo = {pos}
			local sp = 1
			while sp > 0 do
				local p = todo[sp]
				sp = sp-1
				-- only walk the "forbidden" dir if still allowed
				local forbic = avoids[poshash(p)] or 0
				local dirc = 6
				if forbic == max_forbi then
					dirc = dirc - 2
				end
				-- walk the directions
				for i = 1,dirc do
					-- increase forbidden when needed
					local forbinc = forbic
					if i > 4 then
						forbinc = forbinc+1
					end
					local xz = dirs[i]
					for y = -1,2 do
						local p = {x=p.x+xz[1], y=p.y+y, z=p.z+xz[2]}
						local ph = poshash(p)
						local forbi = avoids[ph]
						if not forbi
						or forbi > forbinc then
							avoids[ph] = forbinc
							local dif = vector.subtract(p, pos)
							if get_node(p).name == tr.leaves
							and math.abs(dif.x) <= tr.range
							and math.abs(dif.z) <= tr.range
							and dif.y <= tr.range_up
							and dif.y >= -tr.range_down then
								sp = sp+1
								todo[sp] = p
								leaves[ph] = true
							end
						end
					end
				end
			end
		end
	end
	local ps = {}
	local n = 0
	for ph in pairs(leaves) do
		n = n+1
		ps[n] = pos_from_hash(ph)
	end
	return ps,n
end

-- returns positions for palm leaves allowed to be dug
local function palm_find_valid_head_ps(pos, head_ps, tr)
	local r = tr.range
	local r_up = tr.range_up or r
	local r_down = tr.range_down or r

	-- firstly, detect neighbour palms' leaves to not hurt them
	local tab = {}
	local rx2 = 2 * r
	local rupdown = r_up + r_down
	for z = -rx2, rx2 do
		for y = -rupdown, rupdown do
			for x = -rx2, rx2 do
				local hcp = {x=pos.x+x, y=pos.y+y, z=pos.z+z}
				if not vector.equals(hcp, pos)
				and get_node(hcp).name == tr.trunk_top then
					local leaves,n = get_palm_head(hcp, tr, 0)
					for i = 1,n do
						tab[poshash(leaves[i])] = true
					end
				end
			end
		end
	end
	-- now, get the leaves positions without the neighbouring leaves
	local leaves,lc = get_palm_head(pos, tr, tr.max_forbi)
	local n = #head_ps
	for i = 1,lc do
		local p = leaves[i]
		if not tab[poshash(p)] then
			n = n+1
			head_ps[n] = p
		end
	end
	return n
end

function capitate_funcs.palm(pos, tr, node_above, digger)
	local trunk = tr.trees[1]

	-- walk the stem up to the fruit carrier
	pos = {x=pos.x, y=pos.y+1, z=pos.z}
	local trunks = {{pos, node_above}}
	local trunk_found = true
	local nohori = false
	local hcp
	while trunk_found
	and not hcp do
		trunk_found = false
		for i = 1,17 do
			local hori = i > 1 and i < 10
			if not hori
			or not nohori then
				local p = vector.add(pos, palm_stem_dirs[i])
				local node = get_node(p)
				if node.name == trunk then
					trunk_found = true
					trunks[#trunks+1] = {p, node}
					pos = p
					nohori = hori
					break
				end
				if node.name == tr.trunk_top then
					hcp = p
					trunks[#trunks+1] = {p, node}
					break
				end
			end
		end
	end
	if not hcp then
		return
	end

	-- collect coconuts
	local fruits = {}
	for zo = -1,1 do
		for xo = -1,1 do
			local p = {x=hcp.x+xo, y=hcp.y, z=hcp.z+zo}
			local node = get_node(p)
			if node.name:sub(1, #tr.fruit) == tr.fruit then
				fruits[#fruits+1] = {p, node}
			end
		end
	end

	-- find the leaves of the palm
	local leaves_ps = {}
	local lc = palm_find_valid_head_ps(hcp, leaves_ps, tr)

	if treecapitator.play_sound then
		minetest.sound_play("tree_falling", {pos = pos, max_hear_distance = 32})
	end

	local nodeupdate = minetest.check_for_falling
	minetest.check_for_falling = function() end
	for i = 1,#fruits do
		local pos,node = unpack(fruits[i])
		destroy_node(pos, node, digger)
	end
	minetest.check_for_falling = nodeupdate

	for i = 1,#trunks do
		local pos,node = unpack(trunks[i])
		destroy_node(pos, node, digger)
	end

	local inv = digger:get_inventory()
	for i = 1,lc do
		local pos = leaves_ps[i]
		remove_leaf(pos, get_node(pos), inv, digger)
	end
	return true
end

---------------------- A moretrees capitation function -------------------------

-- table iteration instead of recursion
local function get_tab(pos, func, max)
	local todo = {pos}
	local n = 1
	local tab_avoid = {[poshash(pos)] = true}
	local tab_done,num = {pos},2
	while n ~= 0 do
		local p = todo[n]
		n = n-1
		--[[
		for i = -1,1,2 do
			for _,p2 in pairs{
				{x=p.x+i, y=p.y, z=p.z},
				{x=p.x, y=p.y+i, z=p.z},
				{x=p.x, y=p.y, z=p.z+i},
			} do]]
		for i = -1,1 do
			for j = -1,1 do
				for k = -1,1 do
					local p2 = {x=p.x+i, y=p.y+j, z=p.z+k}
					local vi = poshash(p2)
					if not tab_avoid[vi]
					and func(p2) then
						n = n+1
						todo[n] = p2

						tab_avoid[vi] = true

						tab_done[num] = p2
						num = num+1

						if max
						and num > max then
							return false
						end
					end
				end
			end
		end
	end
	return tab_done
end

function capitate_funcs.moretrees(pos, tr, _, digger)
	local trees = tr.trees
	local leaves = tr.leaves
	local fruits = tr.fruits
	local minx = pos.x-tr.range
	local maxx = pos.x+tr.range
	local minz = pos.z-tr.range
	local maxz = pos.z+tr.range
	local maxy = pos.y+tr.height
	local num_trunks = 0
	local num_leaves = 0
	local ps = get_tab({x=pos.x, y=pos.y+1, z=pos.z}, function(pos)
		if pos.x < minx
		or pos.x > maxx
		or pos.z < minz
		or pos.z > maxz
		or pos.y > maxy then
			return false
		end
		local nam = get_node(pos).name
		if table_contains(trees, nam) then
			num_trunks = num_trunks+1
		elseif table_contains(leaves, nam) then
			num_leaves = num_leaves+1
		elseif not table_contains(fruits, nam) then
			return false
		end
		return true
	end, tr.max_nodes)
	if not ps then
		print"no ps found"
		return
	end
	if num_trunks < tr.num_trunks_min
	or num_trunks > tr.num_trunks_max then
		print("wrong trunks num: "..num_trunks)
		return
	end
	if num_leaves < tr.num_leaves_min
	or num_leaves > tr.num_leaves_max then
		print("wrong leaves num: "..num_leaves)
		return
	end
	if treecapitator.play_sound then
		minetest.sound_play("tree_falling", {pos = pos, max_hear_distance = 32})
	end
	local inv = digger:get_inventory()
	for _,p in pairs(ps) do
		local node = get_node(p)
		local nodename = node.name
		if table_contains(leaves, nodename) then
			remove_leaf(p, node, inv, digger)
		else
			destroy_node(p, node, digger)
		end
	end
	return true
end

--------------------------- api interface --------------------------------------

-- the function which is used for capitating the api
local capitating = false	--necessary if minetest.node_dig is used
function treecapitator.capitate_tree(pos, digger)
	if capitating
	or not digger
	or digger:get_player_control().sneak
	or not treecapitator.capitation_allowed(pos, digger) then
		return
	end
	local t1 = minetest.get_us_time()
	capitating = true
	local node_above = get_node{x=pos.x, y=pos.y+1, z=pos.z}
	for i = 1,#treecapitator.trees do
		local tr = treecapitator.trees[i]
		if table_contains(tr.trees, node_above.name)
		and node_above.param2 < 4
		and capitate_funcs[tr.type](pos, tr, node_above, digger) then
			break
		end
	end
	clean_cache()
	capitating = false
	minetest.log("info", "[treecapitator] tree capitated at (" ..
		pos.x .. "|" .. pos.y .. "|" .. pos.z .. ") after ca. " ..
		(minetest.get_us_time() - t1) / 1000000 .. " s")
end

-- delayed capitating
local delay = treecapitator.delay
if delay > 0 then
	local oldfunc = treecapitator.capitate_tree
	function treecapitator.capitate_tree(...)
		minetest.after(delay, function(...)
			oldfunc(...)
		end, ...)
	end
end

local path = minetest.get_modpath"treecapitator" .. DIR_DELIM
dofile(path .. "api.lua")
dofile(path .. "trees.lua")
dofile(path .. "moretrees34.lua")

local time = (minetest.get_us_time() - load_time_start) / 1000000
local msg = "[treecapitator] loaded after ca. " .. time .. " seconds."
if time > 0.01 then
	print(msg)
else
	minetest.log("info", msg)
end


code:
-- This provides a workaround for the cutting leaves moretrees problem.

function treecapitator.moretrees34(trunktop_ps, trunks, tr, head_ps,
	get_node, is_trunk_of_tree
)
	local trees = tr.trees
	for i = 1,#trunktop_ps do
		-- add the usual trunks
		local pos = trunktop_ps[i]
		local node = get_node(pos)
		while is_trunk_of_tree(trees, node) do
			trunks[#trunks+1] = {pos, node}
			pos = {x=pos.x, y=pos.y+1, z=pos.z}
			node = get_node(pos)
		end

		-- meddle with the lacunarity
		local ys = pos.y
		local ye
		local detected_trunks = {}

		-- search upwards until the gap is big enough or the tree ended
		local foundleaves = 0
		while true do
			if is_trunk_of_tree(trees, node) then
				foundleaves = 0
				detected_trunks[pos.y] = node
				pos.y = pos.y+1
				node = get_node(pos)
			elseif tr.leaves ^ node.name
			or tr.fruits ^ node.name then
				foundleaves = foundleaves+1
				if foundleaves > tr.cutting_leaves then
					-- cutting leaves count exceeded
					ye = pos.y-foundleaves
					break
				end
				pos.y = pos.y+1
				node = get_node(pos)
			else
				-- above the tree
				ye = pos.y-1
				break
			end
		end

		-- search downwards until enough trunks are found above each other
		-- or no such trunks are found
		local ytop = ys-1
		local y = ye
		local last_test = ys + tr.stem_height_min
		while y >= last_test do
			if detected_trunks[y] then
				local too_short
				for ty = y - tr.stem_height_min + 1, y-1 do
					if not detected_trunks[y] then
						too_short = true
						y = ty-1
						break
					end
				end
				if not too_short then
					-- upper end found
					ytop = y
					break
				end
			end
			y = y-1
		end

		if ytop >= ys then
			-- add trunks and leaves/fruits
			for y = ys, ytop do
				local p = {x=pos.x, y=y, z=pos.z}
				if detected_trunks[y] then
					trunks[#trunks+1] = {p, detected_trunks[y]}
				else
					head_ps[#head_ps+1] = p
				end
			end
		end

		-- renew trunk top position
		pos.y = ytop
		trunktop_ps[i] = pos
	end
end

repo_name: Desour/PlayMinetestServer

license: mit


code:
#    drop items, else get them in the inventory
treecapitator.drop_items (drop items) bool false

#    if set to false, do not drop leaves, just saplings (leafdecay behaviour)
treecapitator.drop_leaf (give leaves) bool false

#    play a sound after digging a tree
treecapitator.play_sound (play sound) bool true

#    capitate moretrees' trees, experimental, use with caution
treecapitator.moretrees_support (support moretrees) bool false

#    lets trees become capitated <delay> seconds later, only works if it's > 0
treecapitator.delay (delay) float 0 0

#    If set to true, capitating only works with tools with trunk digging
#    capabilities and not with the bare hand, so when a tool is worn.
treecapitator.no_hand_capitation (Disallow bare hand capitation) bool false

#    considers trunks as neighbour trees if there're >= stem_height_min trunks
#    if set too low, vertical trunk fruits may be recognized as neighbour tree
#    if set too high, short stemmed neighbour trees are not recognited
#    stem_height_min can explicitly be overridden in tree definitions
treecapitator.stem_height_min (minimum neighbour stem height) int 3 2


code:
--[[
treecapitator.register_tree({
	trees = {<node1>, <node2>, ...},
	leaves = {<node1>, <node2>, ...},
	range = <range>,
	range_up = <range>,
	range_down = <range>,
	fruits = {<node1>, <node2>, ...},
	trunk_fruit_vertical = <some_boolean>,
	cutting_leaves = <leaves_count>,
	stem_type = <some_string>,
	stem_height_min = <height>,
	requisite_leaves = {<node1>, <node2>, ...},
	type = "default",
})

trees
the straight stem nodes with param2=0

leaves
nodes of the tree head which only drop their main item if drop_leaf is enabled

range
the size of the tree head cuboid
when using thicker stems the range needs to be bigger

range_up
range upwards beginning from the highest trunk node to head top
if omitted it's set to range

range_down
like range_up but downwards

fruits
similar to leaves but without the drop_leaf setting condition

trunk_fruit_vertical
set this to true to make a trunk node, if it's in trees
and fruits, get removed even if it isn't rotated (param2 = 0)

cutting_leaves
used as workaround for moretrees#34

stem_type
set to "2x2" for a thick tree, "+" for a even thicker one,
anything else is currently interpreted as single stem

stem_height_min
indicates how much trunk nodes a neighbour tree stem has to
consist of

requisite_leaves
if set, abort capitating if one of those nodes weren't found in the head

treecapitator.register_tree({
	trees = {<node1>, <node2>, ...},
	leaves = {<node1>, <node2>, ...},
	range = <range>,
	fruits = {<node1>, <node2>, ...},
	height = <height>,
	max_nodes = <max_nodes>,
	num_trunks_min = <some_number>,
	num_trunks_max = <some_number>,
	num_leaves_min = <some_number>,
	num_leaves_max = <some_number>,
	type = "moretrees",
})

height:	maximum tree height
max_nodes:	maximum amount of nodes the tree is allowed to consist of
num_trunks_min…
]]

-- Please try to match the tree definition close to the real tree for a more
-- fitting usage of neighbour detection and similar mechanisms.
local mgname = minetest.get_mapgen_setting"mg_name"

if mgname == "v7" then
	treecapitator.register_tree{
		trees = {"default:tree"},
		leaves = {"default:leaves"},
		range = 2,
		range_up = 4,
		range_down = 0,
		fruits = {"default:apple", "default:tree"},
		trunk_fruit_vertical = true
	}
elseif mgname == "valleys" then
       treecapitator.register_tree({
		trees = {"default:tree"},
		leaves = {"default:leaves"},
		range = 2,
		range_up = 5,
		range_down = 0,
		fruits = {"default:apple", "default:tree"},
		trunk_fruit_vertical = true,
	})
else
	treecapitator.register_tree{
		trees = {"default:tree"},
		leaves = {"default:leaves"},
		range = 2,
		fruits = {"default:apple"}
	}
end

if mgname == "valleys" then
	treecapitator.register_tree({
		trees = {"default:jungletree"},
		leaves = {"default:jungleleaves"},
		fruits = {"default:jungletree"},
		range = 4,
		range_up = 14,
		range_down = 3,
		trunk_fruit_vertical = true,
		cutting_leaves = 5,
		stem_height_min = 12,
	})

	treecapitator.register_tree({
		trees = {"default:jungletree"},
		leaves = {"default:jungleleaves"},
		fruits = {"default:jungletree"},
		range = 4,
		range_up = 14,
		range_down = 3,
		trunk_fruit_vertical = true,
		cutting_leaves = 5,
		stem_type = "2x2",
		stem_height_min = 12,
	})
else
	treecapitator.register_tree({
		trees = {"default:jungletree"},
		leaves = {"default:jungleleaves"},
		range = 3
	})
end

treecapitator.register_tree({
	trees = {"default:pine_tree"},
	leaves = {"default:pine_needles"},
	range_up = 2,
	range_down = 6,
	range = 3,
})

treecapitator.register_tree({
	trees = {"default:acacia_tree"},
	leaf = "default:acacia_leaves",
	no_param2test = true,
	--leavesrange = 4,
	type = "acacia"
})

treecapitator.register_tree({
	trees = {"default:aspen_tree"},
	leaves = {"default:aspen_leaves"},
	range = 4,
})

if minetest.get_modpath("farming_plus") then
	treecapitator.register_tree({
		trees = {"default:tree"},
		leaves = {"farming_plus:banana_leaves"},
		range = 2,
		fruits = {"farming_plus:banana"}
	})

	treecapitator.register_tree({
		trees = {"default:tree"},
		leaves = {"farming_plus:cocoa_leaves"},
		range = 2,
		fruits = {"farming_plus:cocoa"}
	})
end

if treecapitator.moretrees_support
and minetest.get_modpath("moretrees") then
	treecapitator.register_tree({
		trees = {"moretrees:acacia_trunk"},
		leaves = {"moretrees:acacia_leaves"},
		range = 10,
	})

	treecapitator.register_tree{
		trees = {"moretrees:poplar_trunk"},
		leaves = {"moretrees:poplar_leaves"},
		range_up = 5,
		range_down = 17,
		range = 2,
	}

	local dates = {"moretrees:dates_fn", "moretrees:dates_m0",
		"moretrees:dates_n"}
	for i = 0, 4 do
		dates[#dates+1] = "moretrees:dates_f" .. i
	end
	dates[#dates+1] = "moretrees:date_palm_trunk"
	treecapitator.register_tree{
		trees = {
			"moretrees:date_palm_trunk",
			"moretrees:date_palm_mfruit_trunk",
			"moretrees:date_palm_ffruit_trunk"
		},
		leaves = {"moretrees:date_palm_leaves"},
		fruits = dates,
		trunk_fruit_vertical = true,
		range = 11,
		range_up = 15,
		range_down = 0,
	}

	treecapitator.register_tree{
		trees = {"moretrees:apple_tree_trunk"},
		leaves = {"moretrees:apple_tree_leaves"},
		fruits = {"default:apple", "moretrees:apple_tree_trunk"},
		trunk_fruit_vertical = true,
		range = 9,
		range_up = 3,
		range_down = 4,
	}

	treecapitator.register_tree{
		trees = {"moretrees:beech_trunk"},
		leaves = {"moretrees:beech_leaves"},
		range = 4,
		range_down = 2,
		range_up = 3,
		fruits = {"moretrees:beech_trunk"},
		trunk_fruit_vertical = true
	}

	treecapitator.register_tree{
		trees = {"moretrees:birch_trunk"},
		leaves = {"moretrees:birch_leaves"},
		fruits = {"moretrees:birch_trunk"},
		trunk_fruit_vertical = true,
		cutting_leaves = 3,
		stem_height_min = 4,
		range = 8,
		range_down = 13,
		range_up = 10,
	}

	treecapitator.register_tree{
		trees = {"moretrees:fir_trunk"},
		leaves = {"moretrees:fir_leaves", "moretrees:fir_leaves_bright"},
		range_up = 2,
		range_down = 21,
		range = 7,
		fruits = {"moretrees:fir_cone", "moretrees:fir_trunk"},
		trunk_fruit_vertical = true
	}

	treecapitator.register_tree({
		trees = {"moretrees:jungletree_trunk"},
		leaves = {"moretrees:jungletree_leaves_green",
			"jungletree_leaves_yellow", "jungletree_leaves_red"},
		range = 8,
	})

	treecapitator.register_tree{
		trees = {"moretrees:oak_trunk"},
		leaves = {"moretrees:oak_leaves"},
		fruits = {"moretrees:acorn", "moretrees:oak_trunk"},
		trunk_fruit_vertical = true,
		stem_type = "+",
		range = 11,
		range_up = 11,
		range_down = 1,
	}

	-- needs special type
	treecapitator.register_tree({
		trees = {"moretrees:cedar_trunk"},
		leaves = {"moretrees:cedar_leaves"},
		range = 10,
		range_up = 1,
		range_down = 19,
		trunk_fruit_vertical = true,
		fruits = {"moretrees:cedar_cone", "moretrees:cedar_trunk"}
	})

	treecapitator.register_tree{
		trees = {"moretrees:rubber_tree_trunk",
			"moretrees:rubber_tree_trunk_empty"},
		leaves = {"moretrees:rubber_tree_leaves"},
		fruits = {"moretrees:rubber_tree_trunk",
			"moretrees:rubber_tree_trunk_empty"},
		trunk_fruit_vertical = true,
		stem_type = "2x2",
		range = 8,
		range_down = 1,
		range_up = 8,
	}

	treecapitator.register_tree{
		trees = {"moretrees:sequoia_trunk"},
		leaves = {"moretrees:sequoia_leaves"},
		fruits = {"moretrees:sequoia_trunk"},
		trunk_fruit_vertical = true,
		stem_type = "+",
		range = 10,
		range_up = 3,
		range_down = 33,
		cutting_leaves = 6,
		stem_height_min = 6,
	}

	treecapitator.register_tree{
		trees = {"moretrees:spruce_trunk"},
		leaves = {"moretrees:spruce_leaves"},
		fruits = {"moretrees:spruce_cone", "moretrees:spruce_trunk"},
		trunk_fruit_vertical = true,
		cutting_leaves = 1,
		stem_type = "+",
		range = 10,
		range_down = 25,
		range_up = 5,
	}

	treecapitator.register_tree{
		trees = {"moretrees:willow_trunk"},
		leaves = {"moretrees:willow_leaves"},
		fruits = {"moretrees:willow_trunk"},
		trunk_fruit_vertical = true,
		stem_type = "+",
		range = 13,
		range_up = 6,
		range_down = 6,
	}

	treecapitator.register_tree{ -- small and 2x2 jungletree at once
		trees = {"moretrees:jungletree_trunk"},
		leaves = {"default:jungleleaves", "moretrees:jungletree_leaves_red"},
		fruits = {"moretrees:jungletree_trunk"},
		requisite_leaves = {"moretrees:jungletree_leaves_red"},
		trunk_fruit_vertical = true,
		stem_height_min = 4,
		cutting_leaves = 5,
		stem_type = "2x2",
		range = 8, -- 5 small
		range_up = 2, -- 1 small
		range_down = 17, -- 6 small
	}

	treecapitator.register_tree{
		trees = {"moretrees:jungletree_trunk"},
		leaves = {"default:jungleleaves", "moretrees:jungletree_leaves_yellow",
			"moretrees:jungletree_leaves_red"},
		fruits = {"moretrees:jungletree_trunk"},
		requisite_leaves = {"moretrees:jungletree_leaves_yellow"},
		trunk_fruit_vertical = true,
		cutting_leaves = 5,
		stem_type = "+",
		range = 8,
		range_up = 4,
		range_down = 16,
	}

	treecapitator.register_tree{
		trees = {"moretrees:palm_trunk"},
		trunk_top = "moretrees:palm_fruit_trunk",
		leaves = "moretrees:palm_leaves",
		fruit = "moretrees:coconut",
		range = 10,
		range_up = 7,
		range_down = 4,
		max_forbi = 2,
		type = "palm",
	}

	--~ treecapitator.register_tree({
		--~ trees = {"moretrees:sequoia_trunk"},
		--~ leaves = {"moretrees:sequoia_leaves"},
		--~ range = 8,

		--~ height = 17,
		--~ max_nodes = 8000,
		--~ num_trunks_min = 5,
		--~ num_trunks_max = 400,
		--~ num_leaves_min = 10,
		--~ num_leaves_max = 4000,
		--~ type = "moretrees",
	--~ })

	--~ treecapitator.register_tree({
		--~ trees = {"moretrees:willow_trunk"},
		--~ leaves = {"moretrees:willow_leaves"},
		--~ range = 11,
		--~ height = 17,
		--~ max_nodes = 8000,
		--~ num_trunks_min = 5,
		--~ num_trunks_max = 400,
		--~ num_leaves_min = 10,
		--~ num_leaves_max = 4000,
		--~ type = "moretrees",
	--~ })
end

-- code from amadin and narrnika
if minetest.get_modpath("ethereal") then
	treecapitator.register_tree({--jungle [эвкалипт]
		trees = {"default:jungletree"},
		leaves = {"default:jungleleaves"},
		range = 3,
		height = 20,
		max_nodes = 145,
		num_trunks_min = 0,
		num_trunks_max = 35,
		num_leaves_min = 0,
		num_leaves_max = 110,
		type = "moretrees",
	})
	treecapitator.register_tree({--pine [кедр]
		trees = {"default:pinetree"}, -- this may need to be changed to pine_tree
		leaves = {"ethereal:pineleaves"},
		range = 6,
		type = "default",
	})
	treecapitator.register_tree({--orange [апельсиновое дерево]
		trees = {"default:tree"},
		leaves = {"default:leaves", "ethereal:orange_leaves"},
		fruits = {"default:apple", "ethereal:orange"},
		range = 2,
		type = "default",
	})
	treecapitator.register_tree({--acacia [акация]
		trees = {"ethereal:acacia_trunk"},
		leaves = {"ethereal:acacia_leaves"},
		range = 10,
		height = 10,
		max_nodes = 122,
		num_trunks_min = 0,
		num_trunks_max = 22,
		num_leaves_min = 0,
		num_leaves_max = 100,
		type = "moretrees",
	})
	treecapitator.register_tree({--banana [банановое дерево]
		trees = {"ethereal:banana_trunk"},
		leaves = {"ethereal:bananaleaves"},
		fruits = {"ethereal:banana"},
		range = 3,
		height = 7,
		max_nodes = 28,
		num_trunks_min = 0,
		num_trunks_max = 4,
		num_leaves_min = 0,
		num_leaves_max = 20,
		type = "moretrees",
	})
	treecapitator.register_tree({--coconut [кокосовое дерево]
		trees = {"ethereal:palm_trunk"},
		leaves = {"ethereal:palmleaves"},
		fruits = {"ethereal:coconut"},
		range = 3,
		height = 9,
		max_nodes = 37,
		num_trunks_min = 0,
		num_trunks_max = 8,
		num_leaves_min = 0,
		num_leaves_max = 25,
		type = "moretrees",
	})
	treecapitator.register_tree({--willow [ива]
		trees = {"ethereal:willow_trunk"},
		leaves = {"ethereal:willow_twig"},
		range = 10,
		height = 13,
		max_nodes = 540,
		num_trunks_min = 0,
		num_trunks_max = 90,
		num_leaves_min = 0,
		num_leaves_max = 450,
		type = "moretrees",
	})
	treecapitator.register_tree({--moshroom [гриб]
		trees = {"ethereal:mushroom_trunk"},
		leaves = {"ethereal:mushroom", "ethereal:mushroom_porew"},
		range = 4,
		height = 10,
		max_nodes = 100,
		num_trunks_min = 0,
		num_trunks_max = 32,
		num_leaves_min = 0,
		num_leaves_max = 80,
		type = "moretrees",
	})
end


code:
# ts_furniture

This mod adds basic furniture (Chairs, Tables, Small Tables, Tiny Tables, Benches).

It was made by Thomas-S.

It is published under the MIT license.

Code from "Get Comfortable [cozy]" (by everamzah; published under WTFPL) was used and modified.


code:
default
moretrees?
ethereal?


code: Adds basic furniture (Chairs, Tables, Small Tables, Tiny Tables, Benches).


code:
ts_furniture = {}

-- If true, you can sit on chairs and benches, when right-click them.
ts_furniture.enable_sitting = minetest.settings:get_bool("ts_furniture.enable_sitting", true)

-- Used for localization
local S = minetest.get_translator("ts_furniture")

if ts_furniture.enable_sitting then
    -- The following code is from "Get Comfortable [cozy]" (by everamzah; published under WTFPL).
    -- Thomas S. modified it, so that it can be used in this mod
    minetest.register_globalstep(function(dtime)
        local players = minetest.get_connected_players()
        for i = 1, #players do
            local name = players[i]:get_player_name()
            if default.player_attached[name] and not players[i]:get_attach() and
                    (players[i]:get_player_control().up == true or
                            players[i]:get_player_control().down == true or
                            players[i]:get_player_control().left == true or
                            players[i]:get_player_control().right == true or
                            players[i]:get_player_control().jump == true) then
                players[i]:set_eye_offset({ x = 0, y = 0, z = 0 }, { x = 0, y = 0, z = 0 })
                players[i]:set_physics_override(1, 1, 1)
                default.player_attached[name] = false
                default.player_set_animation(players[i], "stand", 30)
            end
        end
    end)
end

ts_furniture.sit = function(name, pos)
	local player = minetest.get_player_by_name(name)
	if default.player_attached[name] then
		player:set_eye_offset({ x = 0, y = 0, z = 0 }, { x = 0, y = 0, z = 0 })
		player:set_physics_override(1, 1, 1)
		default.player_attached[name] = false
		default.player_set_animation(player, "stand", 30)
	else
		player:moveto(pos)
		player:set_eye_offset({ x = 0, y = -7, z = 2 }, { x = 0, y = 0, z = 0 })
		player:set_physics_override(0, 0, 0)
		default.player_attached[name] = true
		default.player_set_animation(player, "sit", 30)
	end
end
-- end of cozy-code

local furnitures = {
	["chair"] = {
		description = "Chair",
		sitting = true,
		nodebox = {
			{ -0.3, -0.5, 0.2, -0.2, 0.5, 0.3 }, -- foot 1
			{ 0.2, -0.5, 0.2, 0.3, 0.5, 0.3 }, -- foot 2
			{ 0.2, -0.5, -0.3, 0.3, -0.1, -0.2 }, -- foot 3
			{ -0.3, -0.5, -0.3, -0.2, -0.1, -0.2 }, -- foot 4
			{ -0.3, -0.1, -0.3, 0.3, 0, 0.2 }, -- seating
			{ -0.2, 0.1, 0.25, 0.2, 0.4, 0.26 } -- conector 1-2
		},
		craft = function(recipe)
			return {
				{ "", "group:stick" },
				{ recipe, recipe },
				{ "group:stick", "group:stick" }
			}
		end
	},
	["table"] = {
		description = "Table",
		nodebox = {
			{ -0.4, -0.5, -0.4, -0.3, 0.4, -0.3 }, -- foot 1
			{ 0.3, -0.5, -0.4, 0.4, 0.4, -0.3 }, -- foot 2
			{ -0.4, -0.5, 0.3, -0.3, 0.4, 0.4 }, -- foot 3
			{ 0.3, -0.5, 0.3, 0.4, 0.4, 0.4 }, -- foot 4
			{ -0.5, 0.4, -0.5, 0.5, 0.5, 0.5 }, -- table top
		},
		craft = function(recipe)
			return {
				{ recipe, recipe, recipe },
				{ "group:stick", "", "group:stick" },
				{ "group:stick", "", "group:stick" }
			}
		end
	},
	["small_table"] = {
		description = "Small Table",
		nodebox = {
			{ -0.4, -0.5, -0.4, -0.3, 0.1, -0.3 }, -- foot 1
			{ 0.3, -0.5, -0.4, 0.4, 0.1, -0.3 }, -- foot 2
			{ -0.4, -0.5, 0.3, -0.3, 0.1, 0.4 }, -- foot 3
			{ 0.3, -0.5, 0.3, 0.4, 0.1, 0.4 }, -- foot 4
			{ -0.5, 0.1, -0.5, 0.5, 0.2, 0.5 }, -- table top
		},
		craft = function(recipe)
			return {
				{ recipe, recipe, recipe },
				{ "group:stick", "", "group:stick" }
			}
		end
	},
	["tiny_table"] = {
		description = "Tiny Table",
		nodebox = {
			{ -0.5, -0.1, -0.5, 0.5, 0, 0.5 }, -- table top
			{ -0.4, -0.5, -0.5, -0.3, -0.1, 0.5 }, -- foot 1
			{ 0.3, -0.5, -0.5, 0.4, -0.1, 0.5 }, -- foot 2
		},
		craft = function(recipe)
			local bench_name = "ts_furniture:" .. recipe:gsub(":", "_") .. "_bench"
			return {
				{ bench_name, bench_name }
			}
		end
	},
	["bench"] = {
		description = "Bench",
		sitting = true,
		nodebox = {
			{ -0.5, -0.1, 0, 0.5, 0, 0.5 }, -- seating
			{ -0.4, -0.5, 0, -0.3, -0.1, 0.5 }, -- foot 1
			{ 0.3, -0.5, 0, 0.4, -0.1, 0.5 }, -- foot 2
		},
		craft = function(recipe)
			return {
				{ recipe, recipe },
				{ "group:stick", "group:stick" }
			}
		end
	},
}

local ignore_groups = {
	["wood"] = true,
	["stone"] = true,
}

function ts_furniture.register_furniture(recipe, description, texture)
	local recipe_def = minetest.registered_items[recipe]
	if not recipe_def then
		return
	end

	local groups = {}
	for k, v in pairs(recipe_def.groups) do
		if not ignore_groups[k] then
			groups[k] = v
		end
	end

	for furniture, def in pairs(furnitures) do
		local node_name = "ts_furniture:" .. recipe:gsub(":", "_") .. "_" .. furniture

		def.on_rightclick = nil

		if def.sitting and ts_furniture.enable_sitting then
			def.on_rightclick = function(pos, node, player, itemstack, pointed_thing)
				ts_furniture.sit(player:get_player_name(), pos)
			end
		end

		minetest.register_node(":" .. node_name, {
			description = S(description .. " " .. def.description),
			drawtype = "nodebox",
			paramtype = "light",
			paramtype2 = "facedir",
			sunlight_propagates = true,
			tiles = { texture },
			groups = groups,
			node_box = {
				type = "fixed",
				fixed = def.nodebox
			},
			on_rightclick = def.on_rightclick
		})

		minetest.register_craft({
			output = node_name,
			recipe = def.craft(recipe)
		})
	end
end

ts_furniture.register_furniture("default:aspen_wood", "Aspen", "default_aspen_wood.png")
ts_furniture.register_furniture("default:pine_wood", "Pine", "default_pine_wood.png")
ts_furniture.register_furniture("default:acacia_wood", "Acacia", "default_acacia_wood.png")
ts_furniture.register_furniture("default:wood", "Wooden", "default_wood.png")
ts_furniture.register_furniture("default:junglewood", "Jungle Wood", "default_junglewood.png")

if (minetest.get_modpath("moretrees")) then
	ts_furniture.register_furniture("moretrees:apple_tree_planks", "Apple Tree", "moretrees_apple_tree_wood.png")
	ts_furniture.register_furniture("moretrees:beech_planks", "Beech", "moretrees_beech_wood.png")
	ts_furniture.register_furniture("moretrees:birch_planks", "Birch", "moretrees_birch_wood.png")
	ts_furniture.register_furniture("moretrees:fir_planks", "Fir", "moretrees_fir_wood.png")
	ts_furniture.register_furniture("moretrees:oak_planks", "Oak", "moretrees_oak_wood.png")
	ts_furniture.register_furniture("moretrees:palm_planks", "Palm", "moretrees_palm_wood.png")
	ts_furniture.register_furniture("moretrees:rubber_tree_planks", "Rubber Tree", "moretrees_rubber_tree_wood.png")
	ts_furniture.register_furniture("moretrees:sequoia_planks", "Sequoia", "moretrees_sequoia_wood.png")
	ts_furniture.register_furniture("moretrees:spruce_planks", "Spruce", "moretrees_spruce_wood.png")
	ts_furniture.register_furniture("moretrees:willow_planks", "Willow", "moretrees_willow_wood.png")
end

if minetest.get_modpath("ethereal") then
	ts_furniture.register_furniture("ethereal:banana_wood", "Banana", "banana_wood.png")
	ts_furniture.register_furniture("ethereal:birch_wood", "Birch", "moretrees_birch_wood.png")
	ts_furniture.register_furniture("ethereal:frost_wood", "Frost", "frost_wood.png")
	ts_furniture.register_furniture("ethereal:mushroom_trunk", "Mushroom", "mushroom_trunk.png")
	ts_furniture.register_furniture("ethereal:palm_wood", "Palm", "moretrees_palm_wood.png")
	ts_furniture.register_furniture("ethereal:redwood_wood", "Redwood", "redwood_wood.png")
	ts_furniture.register_furniture("ethereal:sakura_wood", "Sakura", "ethereal_sakura_wood.png")
	ts_furniture.register_furniture("ethereal:scorched_tree", "Scorched", "scorched_tree.png")
	ts_furniture.register_furniture("ethereal:willow_wood", "Willow", "willow_wood.png")
	ts_furniture.register_furniture("ethereal:yellow_wood", "Healing Tree", "yellow_wood.png")
end


code:
# textdomain:ts_furniture
Aspen Small Table=
Aspen Tiny Table=
Aspen Bench=
Aspen Table=
Aspen Chair=
Pine Small Table=
Pine Tiny Table=
Pine Bench=
Pine Table=
Pine Chair=
Acacia Small Table=
Acacia Tiny Table=
Acacia Bench=
Acacia Table=
Acacia Chair=
Wooden Small Table=
Wooden Tiny Table=
Wooden Bench=
Wooden Table=
Wooden Chair=
Jungle Wood Small Table=
Jungle Wood Tiny Table=
Jungle Wood Bench=
Jungle Wood Table=
Jungle Wood Chair=
Apple Tree Small Table=
Apple Tree Tiny Table=
Apple Tree Bench=
Apple Tree Table=
Apple Tree Chair=
Beech Small Table=
Beech Tiny Table=
Beech Bench=
Beech Table=
Beech Chair=
Birch Small Table=
Birch Tiny Table=
Birch Bench=
Birch Table=
Birch Chair=
Fir Small Table=
Fir Tiny Table=
Fir Bench=
Fir Table=
Fir Chair=
Oak Small Table=
Oak Tiny Table=
Oak Bench=
Oak Table=
Oak Chair=
Palm Small Table=
Palm Tiny Table=
Palm Bench=
Palm Table=
Palm Chair=
Rubber Tree Small Table=
Rubber Tree Tiny Table=
Rubber Tree Bench=
Rubber Tree Table=
Rubber Tree Chair=
Sequoia Small Table=
Sequoia Tiny Table=
Sequoia Bench=
Sequoia Table=
Sequoia Chair=
Spruce Small Table=
Spruce Tiny Table=
Spruce Bench=
Spruce Table=
Spruce Chair=
Willow Small Table=
Willow Tiny Table=
Willow Bench=
Willow Table=
Willow Chair=
Banana Small Table=
Banana Tiny Table=
Banana Bench=
Banana Table=
Banana Chair=
Birch Small Table=
Birch Tiny Table=
Birch Bench=
Birch Table=
Birch Chair=
Frost Small Table=
Frost Tiny Table=
Frost Bench=
Frost Table=
Frost Chair=
Mushroom Small Table=
Mushroom Tiny Table=
Mushroom Bench=
Mushroom Table=
Mushroom Chair=
Palm Small Table=
Palm Tiny Table=
Palm Bench=
Palm Table=
Palm Chair=
Redwood Small Table=
Redwood Tiny Table=
Redwood Bench=
Redwood Table=
Redwood Chair=
Sakura Small Table=
Sakura Tiny Table=
Sakura Bench=
Sakura Table=
Sakura Chair=
Scorched Small Table=
Scorched Tiny Table=
Scorched Bench=
Scorched Table=
Scorched Chair=
Willow Small Table=
Willow Tiny Table=
Willow Bench=
Willow Table=
Willow Chair=
Healing Tree Small Table=
Healing Tree Tiny Table=
Healing Tree Bench=
Healing Tree Table=
Healing Tree Chair=


code:
name = ts_furniture
depends = default
optional_depends = moretrees, ethereal
description = Adds basic furniture (Chairs, Tables, Small Tables, Tiny Tables, Benches).


code: ts_furniture.enable_sitting (Enable sitting) bool true


code:
Unified inventory
=================

Unified Inventory replaces the default survival and creative inventory.
It adds a nicer interface and a number of features, such as a crafting guide.

License
=======

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

Unified inventory code is licensed under the GNU LGPLv2+.

Licenses for textures:

VanessaE: (CC-BY-4.0)
  * `ui_group.png`

Tango Project: (Public Domain, CC-BY-4.0)
  * [`ui_reset_icon.png`](https://commons.wikimedia.org/wiki/File:Edit-clear.svg)
  * [`ui_doubleleft_icon.png`](http://commons.wikimedia.org/wiki/File:Media-seek-backward.svg)
  * [`ui_doubleright_icon.png`](http://commons.wikimedia.org/wiki/File:Media-seek-forward.svg)
  * [`ui_left_icon.png` / `ui_right_icon.png`](http://commons.wikimedia.org/wiki/File:Media-playback-start.svg)
  * [`ui_skip_backward_icon.png`](http://commons.wikimedia.org/wiki/File:Media-skip-backward.svg)
  * [`ui_skip_forward_icon.png`](http://commons.wikimedia.org/wiki/File:Media-skip-forward.svg)

From http://www.clker.com (Public Domain, CC-BY-4.0):
  * [`bags_small.png`](http://www.clker.com/clipart-moneybag-empty.html)
  * [`bags_medium.png`](http://www.clker.com/clipart-backpack-1.html)
  * [`bags_large.png` / `ui_bags_icon.png`](http://www.clker.com/clipart-backpack-green-brown.html)
  * `ui_trash_icon.png`: <http://www.clker.com/clipart-29090.html> and <http://www.clker.com/clipart-trash.html>
  * [`ui_search_icon.png`](http://www.clker.com/clipart-24887.html)
  * [`ui_off_icon.png` / `ui_on_icon.png`](http://www.clker.com/clipart-on-off-switches.html)
  * [`ui_waypoints_icon.png`](http://www.clker.com/clipart-map-pin-red.html)
  * [`ui_circular_arrows_icon.png`](http://www.clker.com/clipart-circular-arrow-pattern.html)
  * [`ui_pencil_icon.pnc`](http://www.clker.com/clipart-2256.html)
  * [`ui_waypoint_set_icon.png`](http://www.clker.com/clipart-larger-flag.html)

Everaldo Coelho (YellowIcon) (LGPL v2.1+):
  * [`ui_craftguide_icon.png` / `ui_craft_icon.png`](http://commons.wikimedia.org/wiki/File:Advancedsettings.png)

Gregory H. Revera: (CC-BY-SA 3.0)
  * [`ui_moon_icon.png`](http://commons.wikimedia.org/wiki/File:FullMoon2010.jpg)

Thomas Bresson: (CC-BY 3.0)
  * [`ui_sun_icon.png`](http://commons.wikimedia.org/wiki/File:2012-10-13_15-29-35-sun.jpg)

Fibonacci: (Public domain, CC-BY 4.0)
  * [`ui_xyz_off_icon.png`](http://commons.wikimedia.org/wiki/File:No_sign.svg)

Gregory Maxwell: (Public domain, CC-BY 4.0)
  * [`ui_ok_icon.png`](http://commons.wikimedia.org/wiki/File:Yes_check.svg)

Adrien Facélina: (LGPL v2.1+)
  * [`inventory_plus_worldedit_gui.png`](http://commons.wikimedia.org/wiki/File:Erioll_world_2.svg)

Other files from Wikimedia Commons:
  * [`ui_gohome_icon.png` / `ui_home_icon.png` / `ui_sethome_icon.png`](http://commons.wikimedia.org/wiki/File:Home_256x256.png) (GPL v2+)

RealBadAngel: (CC-BY-4.0)
  * Everything else.


code:
local S = unified_inventory.gettext
local F = unified_inventory.fgettext

-- Create detached creative inventory after loading all mods
minetest.after(0.01, function()
	local rev_aliases = {}
	for source, target in pairs(minetest.registered_aliases) do
		if not rev_aliases[target] then rev_aliases[target] = {} end
		table.insert(rev_aliases[target], source)
	end
	unified_inventory.items_list = {}
	for name, def in pairs(minetest.registered_items) do
		if (not def.groups.not_in_creative_inventory or
		   def.groups.not_in_creative_inventory == 0) and
		   def.description and def.description ~= "" then
			table.insert(unified_inventory.items_list, name)
			local all_names = rev_aliases[name] or {}
			table.insert(all_names, name)
			for _, name in ipairs(all_names) do
				local recipes = minetest.get_all_craft_recipes(name)
				if recipes then
					for _, recipe in ipairs(recipes) do

						local unknowns

						for _,chk in pairs(recipe.items) do
							local groupchk = string.find(chk, "group:")
							if (not groupchk and not minetest.registered_items[chk])
							  or (groupchk and not unified_inventory.get_group_item(string.gsub(chk, "group:", "")).item)
							  or minetest.get_item_group(chk, "not_in_craft_guide") ~= 0 then
								unknowns = true
							end
						end

						if not unknowns then
							unified_inventory.register_craft(recipe)
						end
					end
				end
			end
		end
	end
	table.sort(unified_inventory.items_list)
	unified_inventory.items_list_size = #unified_inventory.items_list
	print("Unified Inventory. inventory size: "..unified_inventory.items_list_size)
	for _, name in ipairs(unified_inventory.items_list) do
		local def = minetest.registered_items[name]
		-- Simple drops
		if type(def.drop) == "string" then
			local dstack = ItemStack(def.drop)
			if not dstack:is_empty() and dstack:get_name() ~= name then
				unified_inventory.register_craft({
					type = "digging",
					items = {name},
					output = def.drop,
					width = 0,
				})

			end
		-- Complex drops. Yes, it's really complex!
		elseif type(def.drop) == "table" then
			--[[ Extract single items from the table and save them into dedicated tables
			to register them later, in order to avoid duplicates. These tables counts
			the total number of guaranteed drops and drops by chance (“maybes”) for each item.
			For “maybes”, the final count is the theoretical maximum number of items, not
			neccessarily the actual drop count. ]]
			local drop_guaranteed = {}
			local drop_maybe = {}
			-- This is for catching an obscure corner case: If the top items table has
			-- only items with rarity = 1, but max_items is set, then only the first
			-- max_items will be part of the drop, any later entries are logically
			-- impossible, so this variable is for keeping track of this
			local max_items_left = def.drop.max_items
			-- For checking whether we still encountered only guaranteed only so far;
			-- for the first “maybe” item it will become false which will cause ALL
			-- later items to be considered “maybes”.
			-- A common idiom is:
			-- { max_items 1, { items = {
			--	{ items={"example:1"}, rarity = 5 },
			-- 	{ items={"example:2"}, rarity = 1 }, }}}
			-- example:2 must be considered a “maybe” because max_items is set and it
			-- appears after a “maybe”
			local max_start = true
			-- Let's iterate through the items madness!
			-- Handle invalid drop entries gracefully.
			local drop_items = def.drop.items or { }
			for i=1,#drop_items do
				if max_items_left ~= nil and max_items_left <= 0 then break end
				local itit = drop_items[i]
				for j=1,#itit.items do
					local dstack = ItemStack(itit.items[j])
					if not dstack:is_empty() and dstack:get_name() ~= name then
						local dname = dstack:get_name()
						local dcount = dstack:get_count()
						-- Guaranteed drops AND we are not yet in “maybe mode”
						if #itit.items == 1 and itit.rarity == 1 and max_start then
							if drop_guaranteed[dname] == nil then
								drop_guaranteed[dname] = 0
							end
							drop_guaranteed[dname] = drop_guaranteed[dname] + dcount

							if max_items_left ~= nil then
								max_items_left = max_items_left - 1
								if max_items_left <= 0 then break end
							end
						-- Drop was a “maybe”
						else
							if max_items_left ~= nil then max_start = false end
							if drop_maybe[dname] == nil then
								drop_maybe[dname] = 0
							end
							drop_maybe[dname] = drop_maybe[dname] + dcount
						end
					end
				end
			end
			for itemstring, count in pairs(drop_guaranteed) do
				unified_inventory.register_craft({
					type = "digging",
					items = {name},
					output = itemstring .. " " .. count,
					width = 0,
				})
			end
			for itemstring, count in pairs(drop_maybe) do
				unified_inventory.register_craft({
					type = "digging_chance",
					items = {name},
					output = itemstring .. " " .. count,
					width = 0,
				})
			end
		end
	end
	for _, recipes in pairs(unified_inventory.crafts_for.recipe) do
		for _, recipe in ipairs(recipes) do
			local ingredient_items = {}
			for _, spec in pairs(recipe.items) do
				local matches_spec = unified_inventory.canonical_item_spec_matcher(spec)
				for _, name in ipairs(unified_inventory.items_list) do
					if matches_spec(name) then
						ingredient_items[name] = true
					end
				end
			end
			for name, _ in pairs(ingredient_items) do
				if unified_inventory.crafts_for.usage[name] == nil then
					unified_inventory.crafts_for.usage[name] = {}
				end
				table.insert(unified_inventory.crafts_for.usage[name], recipe)
			end
		end
	end
end)

-- load_home
local function load_home()
	local input = io.open(unified_inventory.home_filename, "r")
	if not input then
		unified_inventory.home_pos = {}
		return
	end
	while true do
		local x = input:read("*n")
		if not x then break end
		local y = input:read("*n")
		local z = input:read("*n")
		local name = input:read("*l")
		unified_inventory.home_pos[name:sub(2)] = {x = x, y = y, z = z}
	end
	io.close(input)
end
load_home()

function unified_inventory.set_home(player, pos)
	local player_name = player:get_player_name()
	unified_inventory.home_pos[player_name] = vector.round(pos)
	-- save the home data from the table to the file
	local output = io.open(unified_inventory.home_filename, "w")
	for k, v in pairs(unified_inventory.home_pos) do
		output:write(v.x.." "..v.y.." "..v.z.." "..k.."\n")
	end
	io.close(output)
end

function unified_inventory.go_home(player)
	local pos = unified_inventory.home_pos[player:get_player_name()]
	if pos then
		player:setpos(pos)
	end
end

-- register_craft
function unified_inventory.register_craft(options)
	if not options.output then
		return
	end
	local itemstack = ItemStack(options.output)
	if itemstack:is_empty() then
		return
	end
	if options.type == "normal" and options.width == 0 then
		options = { type = "shapeless", items = options.items, output = options.output, width = 0 }
	end
	if not unified_inventory.crafts_for.recipe[itemstack:get_name()] then
		unified_inventory.crafts_for.recipe[itemstack:get_name()] = {}
	end
	table.insert(unified_inventory.crafts_for.recipe[itemstack:get_name()],options)
end

local craft_type_defaults = {
	width = 3,
	height = 3,
	uses_crafting_grid = false,
}

function unified_inventory.craft_type_defaults(name, options)
	if not options.description then
		options.description = name
	end
	setmetatable(options, {__index = craft_type_defaults})
	return options
end

function unified_inventory.register_craft_type(name, options)
	unified_inventory.registered_craft_types[name] =
			unified_inventory.craft_type_defaults(name, options)
end

unified_inventory.register_craft_type("normal", {
	description = F("Crafting"),
	icon = "ui_craftgrid_icon.png",
	width = 3,
	height = 3,
	get_shaped_craft_width = function (craft) return craft.width end,
	dynamic_display_size = function (craft)
		local w = craft.width
		local h = math.ceil(table.maxn(craft.items) / craft.width)
		local g = w < h and h or w
		return { width = g, height = g }
	end,
	uses_crafting_grid = true,
})

unified_inventory.register_craft_type("shapeless", {
	description = F("Mixing"),
	icon = "ui_craftgrid_icon.png",
	width = 3,
	height = 3,
	dynamic_display_size = function (craft)
		local maxn = table.maxn(craft.items)
		local g = 1
		while g*g < maxn do g = g + 1 end
		return { width = g, height = g }
	end,
	uses_crafting_grid = true,
})

unified_inventory.register_craft_type("cooking", {
	description = F("Cooking"),
	icon = "default_furnace_front.png",
	width = 1,
	height = 1,
})

unified_inventory.register_craft_type("digging", {
	description = F("Digging"),
	icon = "default_tool_steelpick.png",
	width = 1,
	height = 1,
})

unified_inventory.register_craft_type("digging_chance", {
	description = "Digging (by chance)",
	icon = "default_tool_steelpick.png^[transformFY.png",
	width = 1,
	height = 1,
})

function unified_inventory.register_page(name, def)
	unified_inventory.pages[name] = def
end

function unified_inventory.register_button(name, def)
	if not def.action then
		def.action = function(player)
			unified_inventory.set_inventory_formspec(player, name)
		end
	end
	def.name = name
	table.insert(unified_inventory.buttons, def)
end

function unified_inventory.is_creative(playername)
	return minetest.check_player_privs(playername, {creative=true})
	or minetest.setting_getbool("creative_mode")
end


code:
-- Bags for Minetest

-- Copyright (c) 2012 cornernote, Brett O'Donnell <cornernote@gmail.com>
-- License: GPLv3

local S = unified_inventory.gettext
local F = unified_inventory.fgettext

unified_inventory.register_page("bags", {
	get_formspec = function(player)
		local player_name = player:get_player_name()
		local formspec = "background[0.06,0.99;7.92,7.52;ui_bags_main_form.png]"
		formspec = formspec.."label[0,0;"..F("Bags").."]"
		formspec = formspec.."button[0,2;2,0.5;bag1;"..F("Bag 1").."]"
		formspec = formspec.."button[2,2;2,0.5;bag2;"..F("Bag 2").."]"
		formspec = formspec.."button[4,2;2,0.5;bag3;"..F("Bag 3").."]"
		formspec = formspec.."button[6,2;2,0.5;bag4;"..F("Bag 4").."]"
		formspec = formspec.."listcolors[#00000000;#00000000]"
		formspec = formspec.."list[detached:"..minetest.formspec_escape(player_name).."_bags;bag1;0.5,1;1,1;]"
		formspec = formspec.."list[detached:"..minetest.formspec_escape(player_name).."_bags;bag2;2.5,1;1,1;]"
		formspec = formspec.."list[detached:"..minetest.formspec_escape(player_name).."_bags;bag3;4.5,1;1,1;]"
		formspec = formspec.."list[detached:"..minetest.formspec_escape(player_name).."_bags;bag4;6.5,1;1,1;]"
		return {formspec=formspec}
	end,
})

unified_inventory.register_button("bags", {
	type = "image",
	image = "ui_bags_icon.png",
	tooltip = S("Bags"),
	hide_lite=true
})

for i = 1, 4 do
	local bi = i
	unified_inventory.register_page("bag"..bi, {
		get_formspec = function(player)
			local stack = player:get_inventory():get_stack("bag"..bi, 1)
			local image = stack:get_definition().inventory_image
			local formspec = ("image[7,0;1,1;"..image.."]"
					.."label[0,0;"..F("Bag @1", bi).."]"
					.."listcolors[#00000000;#00000000]"
					.."list[current_player;bag"..bi.."contents;0,1;8,3;]"
					.."listring[current_name;bag"..bi.."contents]"
					.."listring[current_player;main]")
			local slots = stack:get_definition().groups.bagslots
			if slots == 8 then
				formspec = formspec.."background[0.06,0.99;7.92,7.52;ui_bags_sm_form.png]"
			elseif slots == 16 then
				formspec = formspec.."background[0.06,0.99;7.92,7.52;ui_bags_med_form.png]"
			elseif slots == 24 then
				formspec = formspec.."background[0.06,0.99;7.92,7.52;ui_bags_lg_form.png]"
			end
			local player_name = player:get_player_name() -- For if statement.
			if unified_inventory.trash_enabled or unified_inventory.is_creative(player_name) or minetest.get_player_privs(player_name).give then
				formspec = (formspec.."background[6.06,0;0.92,0.92;ui_bags_trash.png]"
						.."list[detached:trash;main;6,0.1;1,1;]")
			end
			local inv = player:get_inventory()
			for i = 1, 4 do
				local def = inv:get_stack("bag"..i, 1):get_definition()
				local button
				if def.groups.bagslots then
					local list_name = "bag"..i.."contents"
					local size = inv:get_size(list_name)
					local used = 0
					for si = 1, size do
						local stk = inv:get_stack(list_name, si)
						if not stk:is_empty() then
							used = used + 1
						end
					end
					local img = def.inventory_image
					local label = F("Bag @1", i).."\n"..used.."/"..size
					button = "image_button["..(i+1)..",0;1,1;"..img..";bag"..i..";"..label.."]"
				else
					button = ""
				end
				formspec = formspec..button
			end
			return {formspec=formspec}
		end,
	})
end

minetest.register_on_player_receive_fields(function(player, formname, fields)
	if formname ~= "" then
		return
	end
	for i = 1, 4 do
		if fields["bag"..i] then
			local stack = player:get_inventory():get_stack("bag"..i, 1)
			if not stack:get_definition().groups.bagslots then
				return
			end
			unified_inventory.set_inventory_formspec(player, "bag"..i)
			return
		end
	end
end)

minetest.register_on_joinplayer(function(player)
	local player_inv = player:get_inventory()
	local player_name = player:get_player_name()
	local bags_inv = minetest.create_detached_inventory(player_name.."_bags",{
		on_put = function(inv, listname, index, stack, player)
			player:get_inventory():set_stack(listname, index, stack)
			player:get_inventory():set_size(listname.."contents",
					stack:get_definition().groups.bagslots)
		end,
		on_take = function(inv, listname, index, stack, player)
			player:get_inventory():set_stack(listname, index, nil)
		end,
		allow_put = function(inv, listname, index, stack, player)
			local new_slots = stack:get_definition().groups.bagslots
			if new_slots then
				local player_inv = player:get_inventory()
				local old_slots = player_inv:get_size(listname.."contents")

				if new_slots >= old_slots then
					return 1
				else
					-- using a smaller bag, make sure it fits
					local old_list = player_inv:get_list(listname.."contents")
					local new_list = {}
					local slots_used = 0
					local use_new_list = false

					for i, v in ipairs(old_list) do
						if v and not v:is_empty() then
							slots_used = slots_used + 1
							use_new_list = i > new_slots
							new_list[slots_used] = v
						end
					end
					if new_slots >= slots_used then
						if use_new_list then
							player_inv:set_list(listname.."contents", new_list)
						end
						return 1
					end
				end
			end
			return 0
		end,
		allow_take = function(inv, listname, index, stack, player)
			if player:get_inventory():is_empty(listname.."contents") then
				return stack:get_count()
			else
				return 0
			end
		end,
		allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
			return 0
		end,
	}, player_name)
	for i=1,4 do
		local bag = "bag"..i
		player_inv:set_size(bag, 1)
		bags_inv:set_size(bag, 1)
		bags_inv:set_stack(bag, 1, player_inv:get_stack(bag, 1))
	end
end)

-- register bag tools
minetest.register_tool("unified_inventory:bag_small", {
	description = S("Small Bag"),
	inventory_image = "bags_small.png",
	groups = {bagslots=8},
})

minetest.register_tool("unified_inventory:bag_medium", {
	description = S("Medium Bag"),
	inventory_image = "bags_medium.png",
	groups = {bagslots=16},
})

minetest.register_tool("unified_inventory:bag_large", {
	description = S("Large Bag"),
	inventory_image = "bags_large.png",
	groups = {bagslots=24},
})

-- register bag crafts
if minetest.get_modpath("farming") ~= nil then
	minetest.register_craft({
		output = "unified_inventory:bag_small",
		recipe = {
			{"",           "farming:cotton", ""},
			{"group:wool", "group:wool",     "group:wool"},
			{"group:wool", "group:wool",     "group:wool"},
		},
	})

	minetest.register_craft({
		output = "unified_inventory:bag_medium",
		recipe = {
			{"",               "",                            ""},
			{"farming:cotton", "unified_inventory:bag_small", "farming:cotton"},
			{"farming:cotton", "unified_inventory:bag_small", "farming:cotton"},
		},
	})

	minetest.register_craft({
		output = "unified_inventory:bag_large",
		recipe = {
			{"",               "",                             ""},
			{"farming:cotton", "unified_inventory:bag_medium", "farming:cotton"},
			{"farming:cotton", "unified_inventory:bag_medium", "farming:cotton"},
	    },
	})
end


code:
local function default_refill(stack)
	stack:set_count(stack:get_stack_max())
	local itemdef = minetest.registered_items[stack:get_name()]
	if itemdef
	and (itemdef.wear_represents or "mechanical_wear") == "mechanical_wear"
	and stack:get_wear() ~= 0 then
		stack:set_wear(0)
	end
	return stack
end

minetest.register_on_joinplayer(function(player)
	local player_name = player:get_player_name()
	unified_inventory.players[player_name] = {}
	unified_inventory.current_index[player_name] = 1
	unified_inventory.filtered_items_list[player_name] =
	unified_inventory.items_list
	unified_inventory.activefilter[player_name] = ""
	unified_inventory.active_search_direction[player_name] = "nochange"
	unified_inventory.apply_filter(player, "", "nochange")
	unified_inventory.current_searchbox[player_name] = ""
	unified_inventory.alternate[player_name] = 1
	unified_inventory.current_item[player_name] = nil
	unified_inventory.current_craft_direction[player_name] = "recipe"
	unified_inventory.set_inventory_formspec(player,
	unified_inventory.default)

	-- Refill slot
	local refill = minetest.create_detached_inventory(player_name.."refill", {
		allow_put = function(inv, listname, index, stack, player)
			local player_name = player:get_player_name()
			if unified_inventory.is_creative(player_name) then
				return stack:get_count()
			else
				return 0
			end
		end,
		on_put = function(inv, listname, index, stack, player)
			local player_name = player:get_player_name()
			local handle_refill = (minetest.registered_items[stack:get_name()] or {}).on_refill or default_refill
			stack = handle_refill(stack)
			inv:set_stack(listname, index, stack)
			minetest.sound_play("electricity",
					{to_player=player_name, gain = 1.0})
		end,
	}, player_name)
	refill:set_size("main", 1)
end)

minetest.register_on_player_receive_fields(function(player, formname, fields)
	local player_name = player:get_player_name()

	local ui_peruser,draw_lite_mode = unified_inventory.get_per_player_formspec(player_name)

	if formname ~= "" then
		return
	end

	-- always take new search text, even if not searching on it yet
	if fields.searchbox
	and fields.searchbox ~= unified_inventory.current_searchbox[player_name] then
		unified_inventory.current_searchbox[player_name] = fields.searchbox
		unified_inventory.set_inventory_formspec(player, unified_inventory.current_page[player_name])
	end

	for i, def in pairs(unified_inventory.buttons) do
		if fields[def.name] then
			def.action(player)
			minetest.sound_play("click",
					{to_player=player_name, gain = 0.1})
			return
		end
	end

	-- Inventory page controls
	local start = math.floor(
		unified_inventory.current_index[player_name] / ui_peruser.items_per_page + 1)
	local start_i = start
	local pagemax = math.floor(
		(#unified_inventory.filtered_items_list[player_name] - 1)
		/ (ui_peruser.items_per_page) + 1)

	if fields.start_list then
		start_i = 1
	end
	if fields.rewind1 then
		start_i = start_i - 1
	end
	if fields.forward1 then
		start_i = start_i + 1
	end
	if fields.rewind3 then
		start_i = start_i - 3
	end
	if fields.forward3 then
		start_i = start_i + 3
	end
	if fields.end_list then
		start_i = pagemax
	end
	if start_i < 1 then
		start_i = 1
	end
	if start_i > pagemax then
		start_i = pagemax
	end
	if start_i ~= start then
		minetest.sound_play("paperflip1",
				{to_player=player_name, gain = 1.0})
		unified_inventory.current_index[player_name] = (start_i - 1) * ui_peruser.items_per_page + 1
		unified_inventory.set_inventory_formspec(player,
				unified_inventory.current_page[player_name])
	end

	local clicked_item
	for name, value in pairs(fields) do
		if string.sub(name, 1, 12) == "item_button_" then
			local new_dir, mangled_item = string.match(name, "^item_button_([a-z]+)_(.*)$")
			clicked_item = unified_inventory.demangle_for_formspec(mangled_item)
			if string.sub(clicked_item, 1, 6) == "group:" then
				minetest.sound_play("click", {to_player=player_name, gain = 0.1})
				unified_inventory.apply_filter(player, clicked_item, new_dir)
				unified_inventory.current_searchbox[player_name] = clicked_item
				unified_inventory.set_inventory_formspec(player,
					unified_inventory.current_page[player_name])
				return
			end
			if new_dir == "recipe"
			or new_dir == "usage" then
				unified_inventory.current_craft_direction[player_name] = new_dir
			end
			break
		end
	end
	if clicked_item then
		minetest.sound_play("click",
				{to_player=player_name, gain = 0.1})
		local page = unified_inventory.current_page[player_name]
		local player_creative = unified_inventory.is_creative(player_name)
		if not player_creative then
			page = "craftguide"
		end
		if page == "craftguide" then
			unified_inventory.current_item[player_name] = clicked_item
			unified_inventory.alternate[player_name] = 1
			unified_inventory.set_inventory_formspec(player, "craftguide")
		elseif player_creative then
			local inv = player:get_inventory()
			local stack = ItemStack(clicked_item)
			stack:set_count(stack:get_stack_max())
			if inv:room_for_item("main", stack) then
				inv:add_item("main", stack)
			end
		end
	end

	if fields.searchbutton
			or fields.key_enter_field == "searchbox" then
		unified_inventory.apply_filter(player, unified_inventory.current_searchbox[player_name], "nochange")
		unified_inventory.set_inventory_formspec(player,
				unified_inventory.current_page[player_name])
		minetest.sound_play("paperflip2",
				{to_player=player_name, gain = 1.0})
	elseif fields.searchresetbutton then
		unified_inventory.apply_filter(player, "", "nochange")
		unified_inventory.current_searchbox[player_name] = ""
		unified_inventory.set_inventory_formspec(player,
				unified_inventory.current_page[player_name])
		minetest.sound_play("click",
				{to_player=player_name, gain = 0.1})
	end

	-- alternate buttons
	if not (fields.alternate or fields.alternate_prev) then
		return
	end
	minetest.sound_play("click",
			{to_player=player_name, gain = 0.1})
	local item_name = unified_inventory.current_item[player_name]
	if not item_name then
		return
	end
	local crafts = unified_inventory.crafts_for[unified_inventory.current_craft_direction[player_name]][item_name]
	if not crafts then
		return
	end
	local alternates = #crafts
	if alternates <= 1 then
		return
	end
	local alternate
	if fields.alternate then
		alternate = unified_inventory.alternate[player_name] + 1
		if alternate > alternates then
			alternate = 1
		end
	elseif fields.alternate_prev then
		alternate = unified_inventory.alternate[player_name] - 1
		if alternate < 1 then
			alternate = alternates
		end
	end
	unified_inventory.alternate[player_name] = alternate
	unified_inventory.set_inventory_formspec(player,
			unified_inventory.current_page[player_name])
end)

if minetest.delete_detached_inventory then
	minetest.register_on_leaveplayer(function(player)
		local player_name = player:get_player_name()
		minetest.delete_detached_inventory(player_name.."_bags")
		minetest.delete_detached_inventory(player_name.."craftrecipe")
		minetest.delete_detached_inventory(player_name.."refill")
	end)
end


code:
default
creative?
sfinv?
intllib?
datastorage?
farming?


code: Unified Inventory replaces the default survival and creative inventory. It adds a nicer interface and a number of features, such as a crafting guide.


code:
local S = unified_inventory.gettext

function unified_inventory.canonical_item_spec_matcher(spec)
	local specname = ItemStack(spec):get_name()
	if specname:sub(1, 6) == "group:" then
		local group_names = specname:sub(7):split(",")
		return function (itemname)
			local itemdef = minetest.registered_items[itemname]
			for _, group_name in ipairs(group_names) do
				if (itemdef.groups[group_name] or 0) == 0 then
					return false
				end
			end
			return true
		end
	else
		return function (itemname) return itemname == specname end
	end
end

function unified_inventory.item_matches_spec(item, spec)
	local itemname = ItemStack(item):get_name()
	return unified_inventory.canonical_item_spec_matcher(spec)(itemname)
end

function unified_inventory.extract_groupnames(groupname)
	local specname = ItemStack(groupname):get_name()
	if specname:sub(1, 6) == "group:" then
		local group_names = specname:sub(7):split(",")
		if #group_names == 1 then
			return group_names[1], 1
		end
		local s = ""
		for g=1,#group_names do
			if g > 1 then
				-- List connector
				s = s .. S(" and ")
			end
			s = s .. group_names[g]
		end
		return s, #group_names
	else
		return nil, 0
	end
end

unified_inventory.registered_group_items = {
	mesecon_conductor_craftable = "mesecons:wire_00000000_off",
	stone = "default:cobble",
	wood = "default:wood",
	book = "default:book",
	sand = "default:sand",
	leaves = "default:leaves",
	tree = "default:tree",
	vessel = "vessels:glass_bottle",
	wool = "wool:white",
}

function unified_inventory.register_group_item(groupname, itemname)
	unified_inventory.registered_group_items[groupname] = itemname
end

-- This is used when displaying craft recipes, where an ingredient is
-- specified by group rather than as a specific item.  A single-item group
-- is represented by that item, with the single-item status signalled
-- in the "sole" field.  If the group contains no items at all, the item
-- field will be nil.
--
-- Within a multiple-item group, we prefer to use an item that has the
-- same specific name as the group, and if there are more than one of
-- those items we prefer the one registered for the group by a mod.
-- Among equally-preferred items, we just pick the one with the
-- lexicographically earliest name.
--
-- The parameter to this function isn't just a single group name.
-- It may be a comma-separated list of group names.  This is really a
-- "group:..." ingredient specification, minus the "group:" prefix.

local function compute_group_item(group_name_list)
	local group_names = group_name_list:split(",")
	local candidate_items = {}
	for itemname, itemdef in pairs(minetest.registered_items) do
		if (itemdef.groups.not_in_creative_inventory or 0) == 0 then
			local all = true
			for _, group_name in ipairs(group_names) do
				if (itemdef.groups[group_name] or 0) == 0 then
					all = false
				end
			end
			if all then table.insert(candidate_items, itemname) end
		end
	end
	local num_candidates = #candidate_items
	if num_candidates == 0 then
		return {sole = true}
	elseif num_candidates == 1 then
		return {item = candidate_items[1], sole = true}
	end
	local is_group = {}
	local registered_rep = {}
	for _, group_name in ipairs(group_names) do
		is_group[group_name] = true
		local rep = unified_inventory.registered_group_items[group_name]
		if rep then registered_rep[rep] = true end
	end
	local bestitem = ""
	local bestpref = 0
	for _, item in ipairs(candidate_items) do
		local pref
		if registered_rep[item] then
			pref = 4
		elseif string.sub(item, 1, 8) == "default:" and is_group[string.sub(item, 9)] then
			pref = 3
		elseif is_group[item:gsub("^[^:]*:", "")] then
			pref = 2
		else
			pref = 1
		end
		if pref > bestpref or (pref == bestpref and item < bestitem) then
			bestitem = item
			bestpref = pref
		end
	end
	return {item = bestitem, sole = false}
end

local group_item_cache = {}

function unified_inventory.get_group_item(group_name)
	if not group_item_cache[group_name] then
		group_item_cache[group_name] = compute_group_item(group_name)
	end
	return group_item_cache[group_name]
end


code:
bags_small.png:
	http://www.clker.com/clipart-moneybag-empty.html

bags_medium.png:
	http://www.clker.com/clipart-backpack-1.html

bags_large.png / ui_bags_icon.png:
	http://www.clker.com/clipart-backpack-green-brown.html

ui_craftguide_icon.png / ui_craft_icon.png
	http://commons.wikimedia.org/wiki/File:Advancedsettings.png

ui_doubleleft_icon.png
	http://commons.wikimedia.org/wiki/File:Media-seek-backward.svg

ui_doubleright_icon.png
	http://commons.wikimedia.org/wiki/File:Media-seek-forward.svg

ui_left_icon.png / ui_right_icon.png
	http://commons.wikimedia.org/wiki/File:Media-playback-start.svg

ui_skip_backward_icon.png
	http://commons.wikimedia.org/wiki/File:Media-skip-backward.svg

ui_skip_forward_icon.png
	http://commons.wikimedia.org/wiki/File:Media-skip-forward.svg

ui_reset_icon.png
	https://commons.wikimedia.org/wiki/File:Edit-clear.svg

ui_gohome_icon.png / ui_home_icon.png / ui_sethome_icon.png
	http://commons.wikimedia.org/wiki/File:Home_256x256.png

ui_moon_icon.png
	http://commons.wikimedia.org/wiki/File:FullMoon2010.jpg

ui_sun_icon.png
	http://commons.wikimedia.org/wiki/File:2012-10-13_15-29-35-sun.jpg

ui_trash_icon.png
	http://www.clker.com/clipart-29090.html
	http://www.clker.com/clipart-trash.html

ui_search_icon.png
	http://www.clker.com/clipart-24887.html

ui_off_icon.png / ui_on_icon.png
	http://www.clker.com/clipart-on-off-switches.html

ui_waypoints_icon.png
	http://www.clker.com/clipart-map-pin-red.html

ui_circular_arrows_icon.png
	http://www.clker.com/clipart-circular-arrow-pattern.html

ui_pencil_icon.pnc
	http://www.clker.com/clipart-2256.html

ui_waypoint_set_icon.png
	http://www.clker.com/clipart-larger-flag.html

ui_xyz_off_icon.png
	http://commons.wikimedia.org/wiki/File:No_sign.svg

ui_ok_icon.png
	http://commons.wikimedia.org/wiki/File:Yes_check.svg

inventory_plus_worldedit_gui.png
	http://commons.wikimedia.org/wiki/File:Erioll_world_2.svg


code:
-- Unified Inventory for Minetest 0.4.8+

local modpath = minetest.get_modpath(minetest.get_current_modname())
local worldpath = minetest.get_worldpath()
local mygettext
if rawget(_G, "intllib") then
	mygettext = intllib.Getter()
else
	function mygettext(s, ...)
		local t = { ... }
		return (s:gsub("@(%d+)", function(n) return t[tonumber(n)] end))
	end
end

-- Data tables definitions
unified_inventory = {
	activefilter = {},
	active_search_direction = {},
	alternate = {},
	current_page = {},
	current_searchbox = {},
	current_index = {},
	current_item = {},
	current_craft_direction = {},
	registered_craft_types = {},
	crafts_for = {usage = {}, recipe = {} },
	players = {},
	items_list_size = 0,
	items_list = {},
	filtered_items_list_size = {},
	filtered_items_list = {},
	pages = {},
	buttons = {},

	-- Homepos stuff
	home_pos = {},
	home_filename =	worldpath.."/unified_inventory_home.home",

	-- Default inventory page
	default = "craft",

	-- intllib
	gettext = mygettext,
	fgettext = function(...) return minetest.formspec_escape(mygettext(...)) end,

	-- "Lite" mode
	lite_mode = minetest.setting_getbool("unified_inventory_lite"),

	-- Trash enabled
	trash_enabled = (minetest.setting_getbool("unified_inventory_trash") ~= false),

	pagecols = 8,
	pagerows = 10,
	page_y = 0,
	formspec_y = 1,
	main_button_x = 0,
	main_button_y = 9,
	craft_result_x = 0.3,
	craft_result_y = 0.5,
	form_header_y = 0
}

-- Disable default creative inventory
local creative = rawget(_G, "creative") or rawget(_G, "creative_inventory")
if creative then
	function creative.set_creative_formspec(player, start_i, pagenum)
		return
	end
end

-- Disable sfinv inventory
local sfinv = rawget(_G, "sfinv")
if sfinv then
	sfinv.enabled = false
end

dofile(modpath.."/group.lua")
dofile(modpath.."/api.lua")
dofile(modpath.."/internal.lua")
dofile(modpath.."/callbacks.lua")
dofile(modpath.."/register.lua")

if minetest.setting_getbool("unified_inventory_bags") ~= false then
	dofile(modpath.."/bags.lua")
end

dofile(modpath.."/item_names.lua")

if minetest.get_modpath("datastorage") then
	dofile(modpath.."/waypoints.lua")
end


code:
local S = unified_inventory.gettext
local F = unified_inventory.fgettext

-- This pair of encoding functions is used where variable text must go in
-- button names, where the text might contain formspec metacharacters.
-- We can escape button names for the formspec, to avoid screwing up
-- form structure overall, but they then don't get de-escaped, and so
-- the input we get back from the button contains the formspec escaping.
-- This is a game engine bug, and in the anticipation that it might be
-- fixed some day we don't want to rely on it.  So for safety we apply
-- an encoding that avoids all formspec metacharacters.
function unified_inventory.mangle_for_formspec(str)
	return string.gsub(str, "([^A-Za-z0-9])", function (c) return string.format("_%d_", string.byte(c)) end)
end
function unified_inventory.demangle_for_formspec(str)
	return string.gsub(str, "_([0-9]+)_", function (v) return string.char(v) end)
end

function unified_inventory.get_per_player_formspec(player_name)
	local lite = unified_inventory.lite_mode and not minetest.check_player_privs(player_name, {ui_full=true})

	local ui = {}
	ui.pagecols = unified_inventory.pagecols
	ui.pagerows = unified_inventory.pagerows
	ui.page_y = unified_inventory.page_y
	ui.formspec_y = unified_inventory.formspec_y
	ui.main_button_x = unified_inventory.main_button_x
	ui.main_button_y = unified_inventory.main_button_y
	ui.craft_result_x = unified_inventory.craft_result_x
	ui.craft_result_y = unified_inventory.craft_result_y
	ui.form_header_y = unified_inventory.form_header_y

	if lite then
		ui.pagecols = 4
		ui.pagerows = 6
		ui.page_y = 0.25
		ui.formspec_y = 0.47
		ui.main_button_x = 8.2
		ui.main_button_y = 6.5
		ui.craft_result_x = 2.8
		ui.craft_result_y = 3.4
		ui.form_header_y = -0.1
	end

	ui.items_per_page = ui.pagecols * ui.pagerows
	return ui, lite
end

function unified_inventory.get_formspec(player, page)

	if not player then
		return ""
	end

	local player_name = player:get_player_name()
	local ui_peruser,draw_lite_mode = unified_inventory.get_per_player_formspec(player_name)

	unified_inventory.current_page[player_name] = page
	local pagedef = unified_inventory.pages[page]

	local formspec = {
		"size[14,10]",
		"background[-0.19,-0.25;14.4,10.75;ui_form_bg.png]" -- Background
	}
	local n = 3

	if draw_lite_mode then
		formspec[1] = "size[11,7.7]"
		formspec[2] = "background[-0.19,-0.2;11.4,8.4;ui_form_bg.png]"
	end

	if unified_inventory.is_creative(player_name)
	and page == "craft" then
		formspec[n] = "background[0,"..(ui_peruser.formspec_y + 2)..";1,1;ui_single_slot.png]"
		n = n+1
	end

	-- Current page
	if not unified_inventory.pages[page] then
		return "" -- Invalid page name
	end

	local perplayer_formspec = unified_inventory.get_per_player_formspec(player_name)
	local fsdata = pagedef.get_formspec(player, perplayer_formspec)

	formspec[n] = fsdata.formspec
	n = n+1

	local button_row = 0
	local button_col = 0

	-- Main buttons

	local filtered_inv_buttons = {}

	for i, def in pairs(unified_inventory.buttons) do
		if not (draw_lite_mode and def.hide_lite) then
			table.insert(filtered_inv_buttons, def)
		end
	end

	for i, def in pairs(filtered_inv_buttons) do

		if draw_lite_mode and i > 4 then
			button_row = 1
			button_col = 1
		end

		if def.type == "image" then
			if (def.condition == nil or def.condition(player) == true) then
				formspec[n] = "image_button["
				formspec[n+1] = ( ui_peruser.main_button_x + 0.65 * (i - 1) - button_col * 0.65 * 4)
				formspec[n+2] = ","..(ui_peruser.main_button_y + button_row * 0.7)..";0.8,0.8;"
				formspec[n+3] = minetest.formspec_escape(def.image)..";"
				formspec[n+4] = minetest.formspec_escape(def.name)..";]"
				formspec[n+5] = "tooltip["..minetest.formspec_escape(def.name)
				formspec[n+6] = ";"..(def.tooltip or "").."]"
				n = n+7
			else
				formspec[n] = "image["
				formspec[n+1] = ( ui_peruser.main_button_x + 0.65 * (i - 1) - button_col * 0.65 * 4)
				formspec[n+2] = ","..(ui_peruser.main_button_y + button_row * 0.7)..";0.8,0.8;"
				formspec[n+3] = minetest.formspec_escape(def.image).."^[colorize:#808080:alpha]"
				n = n+4

			end
		end
	end

	if fsdata.draw_inventory ~= false then
		-- Player inventory
		formspec[n] = "listcolors[#00000000;#00000000]"
		formspec[n+1] = "list[current_player;main;0,"..(ui_peruser.formspec_y + 3.5)..";8,4;]"
		n = n+2
	end

	if fsdata.draw_item_list == false then
		return table.concat(formspec, "")
	end

	-- Controls to flip items pages
	local start_x = 9.2

	if not draw_lite_mode then
		formspec[n] =
			"image_button[" .. (start_x + 0.6 * 0)
				.. ",9;.8,.8;ui_skip_backward_icon.png;start_list;]"
			.. "tooltip[start_list;" .. F("First page") .. "]"

			.. "image_button[" .. (start_x + 0.6 * 1)
				.. ",9;.8,.8;ui_doubleleft_icon.png;rewind3;]"
			.. "tooltip[rewind3;" .. F("Back three pages") .. "]"
			.. "image_button[" .. (start_x + 0.6 * 2)
				.. ",9;.8,.8;ui_left_icon.png;rewind1;]"
			.. "tooltip[rewind1;" .. F("Back one page") .. "]"

			.. "image_button[" .. (start_x + 0.6 * 3)
				.. ",9;.8,.8;ui_right_icon.png;forward1;]"
			.. "tooltip[forward1;" .. F("Forward one page") .. "]"
			.. "image_button[" .. (start_x + 0.6 * 4)
				.. ",9;.8,.8;ui_doubleright_icon.png;forward3;]"
			.. "tooltip[forward3;" .. F("Forward three pages") .. "]"

			.. "image_button[" .. (start_x + 0.6 * 5)
				.. ",9;.8,.8;ui_skip_forward_icon.png;end_list;]"
			.. "tooltip[end_list;" .. F("Last page") .. "]"
	else
		formspec[n] =
			"image_button[" .. (8.2 + 0.65 * 0)
				.. ",5.8;.8,.8;ui_skip_backward_icon.png;start_list;]"
			.. "tooltip[start_list;" .. F("First page") .. "]"
			.. "image_button[" .. (8.2 + 0.65 * 1)
				.. ",5.8;.8,.8;ui_left_icon.png;rewind1;]"
			.. "tooltip[rewind1;" .. F("Back one page") .. "]"
			.. "image_button[" .. (8.2 + 0.65 * 2)
				.. ",5.8;.8,.8;ui_right_icon.png;forward1;]"
			.. "tooltip[forward1;" .. F("Forward one page") .. "]"
			.. "image_button[" .. (8.2 + 0.65 * 3)
				.. ",5.8;.8,.8;ui_skip_forward_icon.png;end_list;]"
			.. "tooltip[end_list;" .. F("Last page") .. "]"
	end
	n = n+1

	-- Search box
	formspec[n] = "field_close_on_enter[searchbox;false]"
	n = n+1

	if not draw_lite_mode then
		formspec[n] = "field[9.5,8.325;3,1;searchbox;;"
			.. minetest.formspec_escape(unified_inventory.current_searchbox[player_name]) .. "]"
		formspec[n+1] = "image_button[12.2,8.1;.8,.8;ui_search_icon.png;searchbutton;]"
			.. "tooltip[searchbutton;" ..F("Search") .. "]"
		formspec[n+2] = "image_button[12.9,8.1;.8,.8;ui_reset_icon.png;searchresetbutton;]"
			.. "tooltip[searchbutton;" ..F("Search") .. "]"
			.. "tooltip[searchresetbutton;" ..F("Reset search and display everything") .. "]"
	else
		formspec[n] = "field[8.5,5.225;2.2,1;searchbox;;"
			.. minetest.formspec_escape(unified_inventory.current_searchbox[player_name]) .. "]"
		formspec[n+1] = "image_button[10.3,5;.8,.8;ui_search_icon.png;searchbutton;]"
			.. "tooltip[searchbutton;" ..F("Search") .. "]"
		formspec[n+2] = "image_button[11,5;.8,.8;ui_reset_icon.png;searchresetbutton;]"
			.. "tooltip[searchbutton;" ..F("Search") .. "]"
			.. "tooltip[searchresetbutton;" ..F("Reset search and display everything") .. "]"
	end
	n = n+3

	local no_matches = "No matching items"
	if draw_lite_mode then
		no_matches = "No matches."
	end

	-- Items list
	if #unified_inventory.filtered_items_list[player_name] == 0 then
		formspec[n] = "label[8.2,"..ui_peruser.form_header_y..";" .. F(no_matches) .. "]"
	else
		local dir = unified_inventory.active_search_direction[player_name]
		local list_index = unified_inventory.current_index[player_name]
		local page = math.floor(list_index / (ui_peruser.items_per_page) + 1)
		local pagemax = math.floor(
			(#unified_inventory.filtered_items_list[player_name] - 1)
				/ (ui_peruser.items_per_page) + 1)
		local item = {}
		for y = 0, ui_peruser.pagerows - 1 do
			for x = 0, ui_peruser.pagecols - 1 do
				local name = unified_inventory.filtered_items_list[player_name][list_index]
				if minetest.registered_items[name] then
					-- Clicked on current item: Flip crafting direction
					if name == unified_inventory.current_item[player_name] then
						local cdir = unified_inventory.current_craft_direction[player_name]
						if cdir == "recipe" then
							dir = "usage"
						elseif cdir == "usage" then
							dir = "recipe"
						end
					else
					-- Default: use active search direction by default
						dir = unified_inventory.active_search_direction[player_name]
					end
					formspec[n] = "item_image_button["
						..(8.2 + x * 0.7)..","
						..(ui_peruser.formspec_y + ui_peruser.page_y + y * 0.7)..";.81,.81;"
						..name..";item_button_"..dir.."_"
						..unified_inventory.mangle_for_formspec(name)..";]"
					n = n+1
					list_index = list_index + 1
				end
			end
		end
		formspec[n] = "label[8.2,"..ui_peruser.form_header_y..";"..F("Page") .. ": "
			.. S("%s of %s"):format(page,pagemax).."]"
	end
	n= n+1

	if unified_inventory.activefilter[player_name] ~= "" then
		formspec[n] = "label[8.2,"..(ui_peruser.form_header_y + 0.4)..";" .. F("Filter") .. ":]"
		formspec[n+1] = "label[9.1,"..(ui_peruser.form_header_y + 0.4)..";"..minetest.formspec_escape(unified_inventory.activefilter[player_name]).."]"
	end
	return table.concat(formspec, "")
end

function unified_inventory.set_inventory_formspec(player, page)
	if player then
		player:set_inventory_formspec(unified_inventory.get_formspec(player, page))
	end
end

--apply filter to the inventory list (create filtered copy of full one)
function unified_inventory.apply_filter(player, filter, search_dir)
	if not player then
		return false
	end
	local player_name = player:get_player_name()
	local lfilter = string.lower(filter)
	local ffilter
	if lfilter:sub(1, 6) == "group:" then
		local groups = lfilter:sub(7):split(",")
		ffilter = function(name, def)
			for _, group in ipairs(groups) do
				if not def.groups[group]
				or def.groups[group] <= 0 then
					return false
				end
			end
			return true
		end
	else
		ffilter = function(name, def)
			local lname = string.lower(name)
			local ldesc = string.lower(def.description)
			return string.find(lname, lfilter, 1, true) or string.find(ldesc, lfilter, 1, true)
		end
	end
	local is_creative = unified_inventory.is_creative(player_name)
	unified_inventory.filtered_items_list[player_name]={}
	for name, def in pairs(minetest.registered_items) do
		if (not def.groups.not_in_creative_inventory
			or def.groups.not_in_creative_inventory == 0)
		and def.description
		and def.description ~= ""
		and ffilter(name, def)
		and (is_creative or unified_inventory.crafts_for.recipe[def.name]) then
			table.insert(unified_inventory.filtered_items_list[player_name], name)
		end
	end
	table.sort(unified_inventory.filtered_items_list[player_name])
	unified_inventory.filtered_items_list_size[player_name] = #unified_inventory.filtered_items_list[player_name]
	unified_inventory.current_index[player_name] = 1
	unified_inventory.activefilter[player_name] = filter
	unified_inventory.active_search_direction[player_name] = search_dir
	unified_inventory.set_inventory_formspec(player,
	unified_inventory.current_page[player_name])
end

function unified_inventory.items_in_group(groups)
	local items = {}
	for name, item in pairs(minetest.registered_items) do
		for _, group in pairs(groups:split(',')) do
			if item.groups[group] then
				table.insert(items, name)
			end
		end
	end
	return items
end

function unified_inventory.sort_inventory(inv)
	local inlist = inv:get_list("main")
	local typecnt = {}
	local typekeys = {}
	for _, st in ipairs(inlist) do
		if not st:is_empty() then
			local n = st:get_name()
			local w = st:get_wear()
			local m = st:get_metadata()
			local k = string.format("%s %05d %s", n, w, m)
			if not typecnt[k] then
				typecnt[k] = {
					name = n,
					wear = w,
					metadata = m,
					stack_max = st:get_stack_max(),
					count = 0,
				}
				table.insert(typekeys, k)
			end
			typecnt[k].count = typecnt[k].count + st:get_count()
		end
	end
	table.sort(typekeys)
	local outlist = {}
	for _, k in ipairs(typekeys) do
		local tc = typecnt[k]
		while tc.count > 0 do
			local c = math.min(tc.count, tc.stack_max)
			table.insert(outlist, ItemStack({
				name = tc.name,
				wear = tc.wear,
				metadata = tc.metadata,
				count = c,
			}))
			tc.count = tc.count - c
		end
	end
	if #outlist > #inlist then return end
	while #outlist < #inlist do
		table.insert(outlist, ItemStack(nil))
	end
	inv:set_list("main", outlist)
end


code:
-- Based on 4itemnames mod by 4aiman

local item_names = {} -- [player_name] = { hud, dtime, itemname }
local dlimit = 3  -- HUD element will be hidden after this many seconds
local air_hud_mod = minetest.get_modpath("4air")
local hud_mod = minetest.get_modpath("hud")
local hudbars_mod = minetest.get_modpath("hudbars")

local function set_hud(player)
	local player_name = player:get_player_name()
	local off = {x=0, y=-70}
	if air_hud_mod or hud_mod then
		off.y = off.y - 20
	elseif hudbars_mod then
		off.y = off.y + 13
	end
	item_names[player_name] = {
		hud = player:hud_add({
			hud_elem_type = "text",
			position = {x=0.5, y=1},
			offset = off,
			alignment = {x=0, y=0},
			number = 0xFFFFFF,
			text = "",
		}),
		dtime = dlimit,
		index = 1,
		itemname = ""
	}
end

minetest.register_on_joinplayer(function(player)
	minetest.after(0, set_hud, player)
end)

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

minetest.register_globalstep(function(dtime)
	for _, player in pairs(minetest.get_connected_players()) do
		local data = item_names[player:get_player_name()]
		if not data or not data.hud then
			data = {} -- Update on next step
			set_hud(player)
		end

		local index = player:get_wield_index()
		local stack = player:get_wielded_item()
		local itemname = stack:get_name()

		if data.hud and data.dtime < dlimit then
			data.dtime = data.dtime + dtime
			if data.dtime > dlimit then
				player:hud_change(data.hud, 'text', "")
			end
		end

		if data.hud and (itemname ~= data.itemname or index ~= data.index) then
			data.itemname = itemname
			data.index = index
			data.dtime = 0

			local desc = stack.get_meta
				and stack:get_meta():get_string("description")

			if not desc or desc == "" then
				-- Try to use default description when none is set in the meta
				local def = minetest.registered_items[itemname]
				desc = def and def.description or ""
			end
			player:hud_change(data.hud, 'text', desc)
		end
	end
end)