code:
[mod] visible wielded items [wieldview]
=======================================

Depends on: 3d_armor

Makes hand wielded items visible to other players.

default settings: [minetest.conf]

# Set number of seconds between visible wielded item updates.
wieldview_update_time = 2

# Show nodes as tiles, disabled by default
wieldview_node_tiles = false

Info for modders
################

Wield image transformation: To apply a simple transformation to the item in
hand, add the group “wieldview_transform” to the item definition. The group
rating equals one of the numbers used for the [transform texture modifier
of the Lua API.


code: Makes hand wielded items visible to other players.

repo_name: Desour/PlayMinetestServer

license: mit


code:
local time = 0
local update_time = tonumber(minetest.settings:get("wieldview_update_time"))
if not update_time then
	update_time = 2
	minetest.settings:set("wieldview_update_time", tostring(update_time))
end
local node_tiles = minetest.settings:get_bool("wieldview_node_tiles")
if not node_tiles then
	node_tiles = false
	minetest.settings:set("wieldview_node_tiles", "false")
end

wieldview = {
	wielded_item = {},
	transform = {},
}

dofile(minetest.get_modpath(minetest.get_current_modname()).."/transform.lua")

wieldview.get_item_texture = function(self, item)
	local texture = "3d_armor_trans.png"
	if item ~= "" then
		if minetest.registered_items[item] then
			if minetest.registered_items[item].inventory_image ~= "" then
				texture = minetest.registered_items[item].inventory_image
			elseif node_tiles == true and minetest.registered_items[item].tiles
					and type(minetest.registered_items[item].tiles[1]) == "string"
					and minetest.registered_items[item].tiles[1] ~= "" then
				texture = minetest.inventorycube(minetest.registered_items[item].tiles[1])
			end
		end
		-- Get item image transformation, first from group, then from transform.lua
		local transform = minetest.get_item_group(item, "wieldview_transform")
		if transform == 0 then
			transform = wieldview.transform[item]
		end
		if transform then
			-- This actually works with groups ratings because transform1, transform2, etc.
			-- have meaning and transform0 is used for identidy, so it can be ignored
			texture = texture.."^[transform"..tostring(transform)
		end
	end
	return texture
end

wieldview.update_wielded_item = function(self, player)
	if not player then
		return
	end
	local name = player:get_player_name()
	local stack = player:get_wielded_item()
	local item = stack:get_name()
	if not item then
		return
	end
	if self.wielded_item[name] then
		if self.wielded_item[name] == item then
			return
		end
		armor.textures[name].wielditem = self:get_item_texture(item)
		armor:update_player_visuals(player)
	end
	self.wielded_item[name] = item
end

minetest.register_on_joinplayer(function(player)
	local name = player:get_player_name()
	wieldview.wielded_item[name] = ""
	minetest.after(0, function()
		local pplayer = minetest.get_player_by_name(name)
		if player then
			wieldview:update_wielded_item(pplayer)
		end
	end)
end)

minetest.register_globalstep(function(dtime)
	time = time + dtime
	if time > update_time then
		for _,player in ipairs(minetest.get_connected_players()) do
			wieldview:update_wielded_item(player)
		end
		time = 0
	end
end)


code:
name = wieldview
depends = 3d_armor
description = Makes hand wielded items visible to other players.


code:
-- Wielded Item Transformations - http://dev.minetest.net/texture

wieldview.transform = {
	["default:torch"]="R270",
	["default:sapling"]="R270",
	["flowers:dandelion_white"]="R270",
	["flowers:dandelion_yellow"]="R270",
	["flowers:geranium"]="R270",
	["flowers:rose"]="R270",
	["flowers:tulip"]="R270",
	["flowers:viola"]="R270",
	["bucket:bucket_empty"]="R270",
	["bucket:bucket_water"]="R270",
	["bucket:bucket_lava"]="R270",
	["screwdriver:screwdriver"]="R270",
	["screwdriver:screwdriver1"]="R270",
	["screwdriver:screwdriver2"]="R270",
	["screwdriver:screwdriver3"]="R270",
	["screwdriver:screwdriver4"]="R270",
	["vessels:glass_bottle"]="R270",
	["vessels:drinking_glass"]="R270",
	["vessels:steel_bottle"]="R270",
}


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


code:
MultiCraft Game mod: itemframes
===============================

License of source code
----------------------
Copyright (C) Zeg9
Copyright (C) VanessaE
Copyright (C) 2019-2020 MultiCraft Development Team

https://gitlab.com/VanessaE/homedecor_modpack/tree/master/itemframes

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 3.0 of the License.

The complete text of the GNU Lesser General Public 3.0 License can be found at:
http://www.gnu.org/licenses/lgpl-3.0.html

License of textures
-------------------
Copyright (C) Vanessa Ezekowitz [VanessaE] (CC-BY-SA 4.0)


code:
minetest.register_entity("itemframes:item", {
	visual = "wielditem",
	visual_size = {x = 0.33, y = 0.33},
	collisionbox = {0},
	physical = false,

	on_activate = function(self, staticdata)
		local ent = self.object

		-- remove entity for missing frames
		local node = minetest.get_node_or_nil(ent:get_pos())
		if not node or node.name ~= "itemframes:frame" then
			ent:remove()
			return
		end

		local data = staticdata:split(";")

		ent:set_properties({textures = {data[2]}})
		self.texture = data[2]
	end,

	get_staticdata = function(self)
		local texture = self.texture or "air"
		return " ;" .. texture
	end
})

local pi = math.pi
local postab = {
	[2] = {{x =  0.41, z =  0}, pi * 1.5},
	[3] = {{x = -0.41, z =  0}, pi * 0.5},
	[4] = {{x =  0,    z =  0.41}, 0},
	[5] = {{x =  0,    z = -0.41}, pi}
}

local update_item = function(pos, node)
	local meta = minetest.get_meta(pos)
	local item = meta:get_string("item")
	local item_name = ItemStack(item):get_name()
	if item_name == "" then return end

	local param2 = node.param2 or 0
	if param2 < 2 or param2 > 5 then return end
	local posad = postab[param2][1]
	pos.x = pos.x + posad.x
	pos.z = pos.z + posad.z

	-- Strange to stay compatible with the previous implementation
	local staticdata = " ;" .. item_name

	local entity = minetest.add_entity(pos, "itemframes:item", staticdata)
	if param2 ~= 4 then
		entity:set_yaw(postab[param2][2])
	end
end

local drop_item = function(pos)
	local meta = minetest.get_meta(pos)
	local item = meta:get_string("item")

	if item ~= "" then
		minetest.add_item(pos, item)
		meta:set_string("item", "")
	end

	for _, obj in pairs(minetest.get_objects_inside_radius(pos, 0.5)) do
		local ent = obj:get_luaentity()
		if ent and ent.name == "itemframes:item" then
			obj:remove()
		end
	end
end

local function check_item(pos, node)
	local meta = minetest.get_meta(pos)
	local item = meta:get_string("item")
	if item == "" then return end

	for _, obj in pairs(minetest.get_objects_inside_radius(pos, 0.5)) do
		local ent = obj:get_luaentity()
		if ent and ent.name == "itemframes:item" then
			return
		end
	end

	update_item(pos, node)
end

local function after_dig_node(pos)
	local meta = minetest.get_meta(pos)
	local item = meta:get_string("item")
	if item == "" then return end
	drop_item(pos)
end

minetest.register_node("itemframes:frame",{
	description = "Item frame",
	drawtype = "nodebox",
	node_box = {
		type = "wallmounted",
		wall_side = {-0.5, -15/32, -15/32, -7/16, 15/32, 15/32}
	},
	tiles = {"itemframe.png"},
	inventory_image = "itemframe.png",
	wield_image = "itemframe.png",
	paramtype = "light",
	paramtype2 = "wallmounted",
	node_placement_prediction = "",
	sunlight_propagates = true,
	groups = {choppy = 2, dig_immediate = 2, attached_node = 1},
	sounds = default.node_sound_wood_defaults(),

	on_place = function(itemstack, placer, pointed_thing)
		if pointed_thing.type == "node" then
			local undery = pointed_thing.under.y
			local posy = pointed_thing.above.y
			if undery == posy then -- allowed wall-mounted only
				itemstack = minetest.item_place(itemstack, placer, pointed_thing)
				minetest.sound_play({name = "default_place_node_hard", gain = 1},
						{pos = pointed_thing.above})
			end
		end
		return itemstack
	end,

	after_place_node = function(pos, placer)
		local meta = minetest.get_meta(pos)
		local pn = placer:get_player_name()
		meta:set_string("owner", pn)
		meta:set_string("infotext", "Item frame" .. "\n" .. "Owned by " .. pn)
	end,

	on_rightclick = function(pos, node, clicker, itemstack)
		if not clicker or
		minetest.is_protected(pos, clicker:get_player_name()) then return end

		drop_item(pos)

		if not itemstack or itemstack:get_name() == "" then return end
		local item = clicker:get_wielded_item():get_name() -- get real item name
		local meta = minetest.get_meta(pos)

		if minetest.registered_tools[item] then
			meta:set_string("item", itemstack:take_item():to_string())
		else
			meta:set_string("item", item)
			itemstack:take_item() -- take 1 item
		end

		update_item(pos, node)
		return itemstack
	end,

	can_dig = function(pos, player)
		if minetest.is_protected(pos, player and player:get_player_name()) then
			return false
		end
		return true
	end,

	on_punch = check_item,
	after_dig_node = after_dig_node,
	on_destruct = after_dig_node
})

minetest.register_lbm({
	label = "Check Itemframe item",
	name = "itemframes:item",
	nodenames = {"itemframes:frame"},
	run_at_every_load = true,
	action = check_item
})

-- Craft
minetest.register_craft({
	output = "itemframes:frame",
	recipe = {
		{"default:stick", "default:stick", "default:stick"},
		{"default:stick", "default:paper", "default:stick"},
		{"default:stick", "default:stick", "default:stick"}
	}
})

minetest.register_craft({
	type = "fuel",
	recipe = "itemframes:frame",
	burntime = 10
})


code:
name = itemframes
depends = default
description = A decorative frame in which you can place any item or node.


code:
Advanced Ban [advancedban]
===========

This mod will ban player based on its username, not its IP address.

# License
MIT License (see LICENSE file)

# Usage
## Parameter
- `FILE_NAME`: name of the file that contains list of banned player
- `BAN_MESSAGE`: a message that will be sent to the banned player each time it connects

## Chat commands
### Check ban for a player
```
/abancheck <player name>
```
### Simple ban/unban
(requires `ban` privilege)
```
/aban <player name>
/unaban <player name>
```

### Ban with kick
(requires `ban` and `kick` privileges)
```
/abankick <player name>
```

### Ban/unban with its IP
(requires `ban` privilege)
```
/aban+ <player name>
/unban+ <player name>
```

## API
```lua
advancedban.is_banned(player_name)
-- true if the player is banned, false if not

advancedban.ban(player_name)
-- true if OK, false if the player has been banned before

advancedban.unban(player_name)
-- true if OK, false if the player hasn't been banned before
```


code:
Advanced Ban [advancedban]
===========

This mod will ban player based on its username, not its IP address.

# License
MIT License (see LICENSE file)

# Usage
## Parameter
- FILE_NAME: name of the file that contains list of banned player
- BAN_MESSAGE: a message that will be sent to the banned player each time it connects

## Chat commands
### Check ban for a player

/abancheck <player name>

### Simple ban/unban
(requires "ban" privilege)

/aban <player name>
/unaban <player name>

### Ban with kick
(requires "ban" and "kick" privileges)

/abankick <player name>

### Ban/unban with its IP
(requires "ban" privilege)

/aban+ <player name>
/unban+ <player name>

## API

advancedban.is_banned(player_name)
-- true if the player is banned, false if not

advancedban.ban(player_name)
-- true if OK, false if the player has been banned before

advancedban.unban(player_name)
-- true if OK, false if the player hasn't been banned before


code:
-- Advanced Ban [advancedban] by srifqi
advancedban = {}

-- parameter
local FILE_NAME = "bannedplayerlist.txt"
local BAN_MESSAGE = "Your username is banned."

function advancedban.is_banned(name)
	if not file_exists(minetest.get_worldpath() .. DIR_DELIM .. FILE_NAME) then
		return false
	end
	local list = io.open(minetest.get_worldpath() .. DIR_DELIM .. FILE_NAME, "r")
	for username in list:lines() do
		if name == username then
			list:close()
			return true
		end
	end
	list:close()
	return false
end

function advancedban.ban(name)
	if advancedban.is_banned(name) then
		-- minetest.log("info", name .. " is already in advancedban list.") -- print debug
		return false
	else
		local list = io.open(minetest.get_worldpath() .. DIR_DELIM .. FILE_NAME, "a")
		list:write(name .. "\n")
		list:close()
		minetest.log("action", name .. " has been added to advancedban list.") -- print debug
		return true
	end
end

function advancedban.unban(name)
	local found = false
	if file_exists(minetest.get_worldpath() .. DIR_DELIM .. FILE_NAME) then
		local list = io.open(minetest.get_worldpath() .. DIR_DELIM .. FILE_NAME, "r")
		local text = ""
		for username in list:lines() do
			if name == username then
				found = true
			else
				text = text .. username .. "\n"
			end
		end
		list:close()
		local list = io.open(minetest.get_worldpath() .. DIR_DELIM .. FILE_NAME, "w")
		list:write(text)
		list:close()
	end
	if found then
		minetest.log("action", name .. " has been removed from advancedban list.") -- print debug
	end
	return found
end

minetest.register_chatcommand("abancheck", {
	params = "<player name>",
	description = "Check ban for a player with given name",
	privs = {},
	func = function(name, param)
		if advancedban.is_banned(param) then
			minetest.chat_send_player(name, param .. " is in advancedban list.")
		else
			minetest.chat_send_player(name, param .. " is not in advancedban list.")
		end
	end
})

minetest.register_chatcommand("aban", {
	params = "<player name>",
	description = "Ban a player with given name",
	privs = {ban = true},
	func = function(name, param)
		if advancedban.ban(param) then
			minetest.chat_send_player(name, param .. " has been added to advancedban list.")
		else
			minetest.chat_send_player(name, param .. " is already in advancedban list.")
		end
	end
})

minetest.register_chatcommand("abankick", {
	params = "<player name>",
	description = "Ban and kick a player with given name",
	privs = {ban = true, kick = true},
	func = function(name, param)
		local kick = ", but failed to kick the player."
		if minetest.kick_player(param) then
			kick = " and has been kicked."
		end
		if advancedban.ban(param) then
			minetest.chat_send_player(name, param .. " has been added to advancedban list" .. kick)
		else
			minetest.chat_send_player(name, param .. " is already in advancedban list" .. kick)
		end
	end
})

minetest.register_chatcommand("aban+", {
	params = "<player name>",
	description = "Ban a player and its IP with given name",
	privs = {ban = true},
	func = function(name, param)
		local IP = ", but failed to ban IP of the player."
		if minetest.ban_player(param) then
			IP = " and IP of the player is banned."
		end
		if advancedban.ban(param) then
			minetest.chat_send_player(name, param .. " has been added to advancedban list" .. IP)
		else
			minetest.chat_send_player(name, param .. " is already in advancedban list" .. IP)
		end
	end
})

minetest.register_chatcommand("unaban", {
	params = "<player name>",
	description = "Remove player ban with given name",
	privs = {ban = true},
	func = function(name, param)
		if advancedban.unban(param) then
			minetest.chat_send_player(name, param .. " has been removed from advancedban list.")
		else
			minetest.chat_send_player(name, param .. " is not found in advancedban list.")
		end
	end
})

minetest.register_chatcommand("unaban+", {
	params = "<player name>",
	description = "Remove player ban and its IP with given name",
	privs = {ban = true},
	func = function(name, param)
		local IP = ", but failed to unban IP of the player"
		if minetest.unban_player_or_ip(param) then
			IP = " and IP of the player is unbanned."
		end
		if advancedban.unban(param) then
			minetest.chat_send_player(name, param .. " has been removed from advancedban list" .. IP)
		else
			minetest.chat_send_player(name, param .. " is not found in advancedban list" .. IP)
		end
	end
})

-- prevent advancedbanned player to join
minetest.register_on_prejoinplayer(function(name)
	if advancedban.is_banned(name) then
		return BAN_MESSAGE
	end
end)

-- Minetest library - misc_helpers.lua
function file_exists(filename)local f=io.open(filename,"r");if f==nil then return false else f:close() return true end end


code:
name = advancedban
description = Advanced Ban: Ban a player based on its username
release = 1739
title = Advanced Ban
author = srifqi


code:
Areas mod for Minetest 0.4.8+
=============================

Configuration
-------------

If you wish to specify configuration options, such as whether players are
allowed to protect their own areas with the `protect` command (disabled by
default), you should check settings.lua and set the appropriate settings in your
server's configuration file (probably `minetest.conf`).

Tutorial
--------

To protect an area you must first set the corner positions of the area.
In order to set the corner positions you can run:
  * `/area_pos set` and punch the two corner nodes to set them.
  * `/area_pos set1/set2` and punch only the first or second corner node to
	set them one at a time.
  * `/area_pos1/2` to set one of the positions to your current position.
  * `/area_pos1/2 X Y Z` to set one of the positions to the specified
	coordinates.

Once you have set the border positions you can protect the area by running one
of the following commands:
  * `/set_owner <OwnerName> <AreaName>` -- If you have the `areas` privilege.
  * `/protect <AreaName>` -- If you have the `areas` privilege or the server
	administrator has enabled area self-protection.

The area name is used only for informational purposes (so that you know what
an area is for).  It is not used for any other purpose.
For example: `/set_owner SomePlayer Mese city`

Now that you own an area you may want to add sub-owners to it. You can do this
with the `add_owner` command.  Anyone with an area can use the `add_owner`
command on their areas.  Before using the `add_owner` command you have to
select the corners of the sub-area as you did for `set_owner`. If your markers
are still around your original area and you want to grant access to your
entire area you will not have to re-set them. You can also use `select_area` to
place the markers at the corners of an existing area if you've reset your
markers and want to grant access to a full area.
The `add_owner` command expects three arguments:
  1. The ID number of the parent area (the area that you want to add a
	sub-area to).
  2. The name of the player that will own the sub-area.
  3. The name of the sub-area. (can contain spaces)

For example: `/add_owner 123 BobTheBuilder Diamond lighthouse`

Commands
--------

  * `/protect <AreaName>` -- Protects an area for yourself. (if
	self-protection is enabled)

  * `/set_owner <OwnerName> <AreaName>` -- Protects an area for a specified
	player. (requires the `areas` privilege)

  * `/add_owner <ParentID> <OwnerName> <ChildName>` -- Grants another player
	control over part (or all) of an area.

  * `/rename_area <ID> <NewName>` -- Renames an existing area.

  * `/list_areas` -- Lists all of the areas that you own, or all areas if you
	have the `areas` privilege.

  * `/find_areas <Regex>` -- Finds areas using a Lua regular expresion.
	For example, to find castles:

		/find_areas [Cc]astle

  * `/remove_area <ID>` -- Removes an area that you own. Any sub-areas of that
	area are made sub-areas of the removed area's parent, if it exists.
	If the removed area has no parent it's sub-areas will have no parent.

  * `/recursive_remove_areas <ID>` -- Removes an area and all sub-areas of it.

  * `/change_owner <ID> <NewOwner>` -- Change the owner of an area.

  * `/area_info` -- Returns information about area configuration and usage.

  * `/select_area <ID>` -- Sets the area positions to those of an existing
	area.

  * `/area_pos {set,set1,set2,get}` -- Sets the area positions by punching
	nodes or shows the current area positions.

  * `/area_pos1 [X,Y,Z|X Y Z]` -- Sets area position one to your position or
	the one supplied.

  * `/area_pos2 [X,Y,Z|X Y Z]` -- Sets area position two to your position or
	the one supplied.

License
-------

Copyright (C) 2013 ShadowNinja

Licensed under the GNU LGPL version 2.1 or later.
See LICENSE.txt and http://www.gnu.org/licenses/lgpl-2.1.txt


code:
local hudHandlers = {}

--- Adds a function as a HUD handler, it will be able to add items to the Areas HUD element.
function areas:registerHudHandler(handler)
	table.insert(hudHandlers, handler)
end

function areas:getExternalHudEntries(pos)
	local areas = {}
	for _, func in pairs(hudHandlers) do
		func(pos, areas)
	end
	return areas
end

--- Returns a list of areas that include the provided position.
function areas:getAreasAtPos(pos)
	local res = {}

	if self.store then
		local a = self.store:get_areas_for_pos(pos, false, true)
		for store_id, store_area in pairs(a) do
			local id = tonumber(store_area.data)
			res[id] = self.areas[id]
		end
	else
		local px, py, pz = pos.x, pos.y, pos.z
		for id, area in pairs(self.areas) do
			local ap1, ap2 = area.pos1, area.pos2
			if
					(px >= ap1.x and px <= ap2.x) and
					(py >= ap1.y and py <= ap2.y) and
					(pz >= ap1.z and pz <= ap2.z) then
				res[id] = area
			end
		end
	end
	return res
end

--- Returns areas that intersect with the passed area.
function areas:getAreasIntersectingArea(pos1, pos2)
	local res = {}
	if self.store then
		local a = self.store:get_areas_in_area(pos1, pos2,
				true, false, true)
		for store_id, store_area in pairs(a) do
			local id = tonumber(store_area.data)
			res[id] = self.areas[id]
		end
	else
		self:sortPos(pos1, pos2)
		local p1x, p1y, p1z = pos1.x, pos1.y, pos1.z
		local p2x, p2y, p2z = pos2.x, pos2.y, pos2.z
		for id, area in pairs(self.areas) do
			local ap1, ap2 = area.pos1, area.pos2
			if
					(ap1.x <= p2x and ap2.x >= p1x) and
					(ap1.y <= p2y and ap2.y >= p1y) and
					(ap1.z <= p2z and ap2.z >= p1z) then
				-- Found an intersecting area.
				res[id] = area
			end
		end
	end
	return res
end

-- Checks if the area is unprotected or owned by you
function areas:canInteract(pos, name)
	if minetest.check_player_privs(name, self.adminPrivs) then
		return true
	end
	local owned = false
	for _, area in pairs(self:getAreasAtPos(pos)) do
		if area.owner == name or area.open then
			return true
		else
			owned = true
		end
	end
	return not owned
end

-- Returns a table (list) of all players that own an area
function areas:getNodeOwners(pos)
	local owners = {}
	for _, area in pairs(self:getAreasAtPos(pos)) do
		table.insert(owners, area.owner)
	end
	return owners
end

--- Checks if the area intersects with an area that the player can't interact in.
-- Note that this fails and returns false when the specified area is fully
-- owned by the player, but with multiple protection zones, none of which
-- cover the entire checked area.
-- @param name (optional) Player name.  If not specified checks for any intersecting areas.
-- @param allow_open Whether open areas should be counted as if they didn't exist.
-- @return Boolean indicating whether the player can interact in that area.
-- @return Un-owned intersecting area ID, if found.
function areas:canInteractInArea(pos1, pos2, name, allow_open)
	if name and minetest.check_player_privs(name, self.adminPrivs) then
		return true
	end
	self:sortPos(pos1, pos2)

	-- Intersecting non-owned area ID, if found.
	local blocking_area = nil

	local areas = self:getAreasIntersectingArea(pos1, pos2)
	for id, area in pairs(areas) do
		-- First check for a fully enclosing owned area.
		-- A little optimization: isAreaOwner isn't necessary
		-- here since we're iterating over all relevant areas.
		if area.owner == name and
				self:isSubarea(pos1, pos2, id) then
			return true
		end

		-- Then check for intersecting non-owned (blocking) areas.
		-- We don't bother with this check if we've already found a
		-- blocking area, as the check is somewhat expensive.
		-- The area blocks if the area is closed or open areas aren't
		-- acceptable to the caller, and the area isn't owned.
		-- Note: We can't return directly here, because there might be
		-- an exclosing owned area that we haven't gotten to yet.
		if not blocking_area and
				(not allow_open or not area.open) and
				(not name or not self:isAreaOwner(id, name)) then
			blocking_area = id
		end
	end

	if blocking_area then
		return false, blocking_area
	end

	-- There are no intersecting areas or they are only partially
	-- intersecting areas and they are all owned by the player.
	return true
end


code:
Areas mod API
===

API list
---

 * `areas.registerHudHandler(handler)` - Registers a handler to add items to the Areas HUD.  See [HUD](#hud).

HUD
---

If you are making a protection mod or a similar mod that adds invisible regions
to the world, and you would like then to show up in the areas HUD element, you
can register a callback to show your areas.

HUD handler specification:

 * `handler(pos, list)`
   * `pos` - The position to check.
   * `list` - The list of area HUD elements, this should be modified in-place.

The area list item is a table containing a list of tables with the following fields:

 * `id` - An identifier for the area. This should be a unique string in the format `mod:id`.
 * `name` - The name of the area.
 * `owner` - The player name of the region owner, if any.

All of the fields are optional but at least one of them must be set.

### Example

	local function areas_hud_handler(pos, areas)
		local val = find_my_protection(pos)

		if val then
			table.insert(areas, {
				id = "mod:"..val.id,
				name = val.name,
				owner = val.owner,
			})
		end
	end

	areas:registerHudHandler(areas_hud_handler)


code:
minetest.register_chatcommand("protect", {
	params = "<AreaName>",
	description = "Protect your own area",
	privs = {[areas.config.self_protection_privilege]=true},
	func = function(name, param)
		if param == "" then
			return false, "Invalid usage, see /help protect."
		end
		local pos1, pos2 = areas:getPos(name)
		if not (pos1 and pos2) then
			return false, "You need to select an area first."
		end

		minetest.log("action", "/protect invoked, owner="..name..
				" AreaName="..param..
				" StartPos="..minetest.pos_to_string(pos1)..
				" EndPos="  ..minetest.pos_to_string(pos2))

		local canAdd, errMsg = areas:canPlayerAddArea(pos1, pos2, name)
		if not canAdd then
			return false, "You can't protect that area: "..errMsg
		end

		local id = areas:add(name, param, pos1, pos2, nil)
		areas:save()

		return true, "Area protected. ID: "..id
	end
})

minetest.register_chatcommand("set_owner", {
	params = "<PlayerName> <AreaName>",
	description = "Protect an area beetween two positions and give"
		.." a player access to it without setting the parent of the"
		.." area to any existing area",
	privs = areas.adminPrivs,
	func = function(name, param)
		local ownerName, areaName = param:match('^(%S+)%s(.+)$')

		if not ownerName then
			return false, "Incorrect usage, see /help set_owner."
		end

		local pos1, pos2 = areas:getPos(name)
		if not (pos1 and pos2) then
			return false, "You need to select an area first."
		end

		if not areas:player_exists(ownerName) then
			return false, "The player \""
					..ownerName.."\" does not exist."
		end

		minetest.log("action", name.." runs /set_owner. Owner = "..ownerName..
				" AreaName = "..areaName..
				" StartPos = "..minetest.pos_to_string(pos1)..
				" EndPos = "  ..minetest.pos_to_string(pos2))

		local id = areas:add(ownerName, areaName, pos1, pos2, nil)
		areas:save()

		minetest.chat_send_player(ownerName,
				"You have been granted control over area #"..
				id..". Type /list_areas to show your areas.")
		return true, "Area protected. ID: "..id
	end
})

minetest.register_chatcommand("add_owner", {
	params = "<ParentID> <Player> <AreaName>",
	description = "Give a player access to a sub-area beetween two"
		.." positions that have already been protected,"
		.." Use set_owner if you don't want the parent to be set.",
	func = function(name, param)
		local pid, ownerName, areaName
				= param:match('^(%d+) ([^ ]+) (.+)$')

		if not pid then
			minetest.chat_send_player(name, "Incorrect usage, see /help add_owner")
			return
		end

		local pos1, pos2 = areas:getPos(name)
		if not (pos1 and pos2) then
			return false, "You need to select an area first."
		end

		if not areas:player_exists(ownerName) then
			return false, "The player \""..ownerName.."\" does not exist."
		end

		minetest.log("action", name.." runs /add_owner. Owner = "..ownerName..
				" AreaName = "..areaName.." ParentID = "..pid..
				" StartPos = "..pos1.x..","..pos1.y..","..pos1.z..
				" EndPos = "  ..pos2.x..","..pos2.y..","..pos2.z)

		-- Check if this new area is inside an area owned by the player
		pid = tonumber(pid)
		if (not areas:isAreaOwner(pid, name)) or
		   (not areas:isSubarea(pos1, pos2, pid)) then
			return false, "You can't protect that area."
		end

		local id = areas:add(ownerName, areaName, pos1, pos2, pid)
		areas:save()

		minetest.chat_send_player(ownerName,
				"You have been granted control over area #"..
				id..". Type /list_areas to show your areas.")
		return true, "Area protected. ID: "..id
	end
})

minetest.register_chatcommand("rename_area", {
	params = "<ID> <newName>",
	description = "Rename a area that you own",
	func = function(name, param)
		local id, newName = param:match("^(%d+)%s(.+)$")
		if not id then
			return false, "Invalid usage, see /help rename_area."
		end

		id = tonumber(id)
		if not id then
			return false, "That area doesn't exist."
		end

		if not areas:isAreaOwner(id, name) then
			return true, "You don't own that area."
		end

		areas.areas[id].name = newName
		areas:save()
		return true, "Area renamed."
	end
})

minetest.register_chatcommand("find_areas", {
	params = "<regexp>",
	description = "Find areas using a Lua regular expression",
	privs = areas.adminPrivs,
	func = function(name, param)
		if param == "" then
			return false, "A regular expression is required."
		end

		-- Check expression for validity
		local function testRegExp()
			("Test [1]: Player (0,0,0) (0,0,0)"):find(param)
		end
		if not pcall(testRegExp) then
			return false, "Invalid regular expression."
		end

		local matches = {}
		for id, area in pairs(areas.areas) do
			local str = areas:toString(id)
			if str:find(param) then
				table.insert(matches, str)
			end
		end
		if #matches > 0 then
			return true, table.concat(matches, "\n")
		else
			return true, "No matches found."
		end
	end
})

minetest.register_chatcommand("list_areas", {
	description = "List your areas, or all areas if you are an admin.",
	func = function(name, param)
		local admin = minetest.check_player_privs(name, areas.adminPrivs)
		local areaStrings = {}
		for id, area in pairs(areas.areas) do
			if admin or areas:isAreaOwner(id, name) then
				table.insert(areaStrings, areas:toString(id))
			end
		end
		if #areaStrings == 0 then
			return true, "No visible areas."
		end
		return true, table.concat(areaStrings, "\n")
	end
})

minetest.register_chatcommand("recursive_remove_areas", {
	params = "<id>",
	description = "Recursively remove areas using an id",
	func = function(name, param)
		local id = tonumber(param)
		if not id then
			return false, "Invalid usage, see"
					.." /help recursive_remove_areas"
		end

		if not areas:isAreaOwner(id, name) then
			return false, "Area "..id.." does not exist or is"
					.." not owned by you."
		end

		areas:remove(id, true)
		areas:save()
		return true, "Removed area "..id.." and it's sub areas."
	end
})

minetest.register_chatcommand("remove_area", {
	params = "<id>",
	description = "Remove an area using an id",
	func = function(name, param)
		local id = tonumber(param)
		if not id then
			return false, "Invalid usage, see /help remove_area"
		end

		if not areas:isAreaOwner(id, name) then
			return false, "Area "..id.." does not exist or"
					.." is not owned by you."
		end

		areas:remove(id)
		areas:save()
		return true, "Removed area "..id
	end
})

minetest.register_chatcommand("change_owner", {
	params = "<ID> <NewOwner>",
	description = "Change the owner of an area using it's ID",
	func = function(name, param)
		local id, newOwner = param:match("^(%d+)%s(%S+)$")
		if not id then
			return false, "Invalid usage, see"
					.." /help change_owner."
		end

		if not areas:player_exists(newOwner) then
			return false, "The player \""..newOwner
					.."\" does not exist."
		end

		id = tonumber(id)
		if not areas:isAreaOwner(id, name) then
			return false, "Area "..id.." does not exist"
					.." or is not owned by you."
		end
		areas.areas[id].owner = newOwner
		areas:save()
		minetest.chat_send_player(newOwner,
			("%s has given you control over the area %q (ID %d).")
				:format(name, areas.areas[id].name, id))
		return true, "Owner changed."
	end
})

minetest.register_chatcommand("area_open", {
	params = "<ID>",
	description = "Toggle an area open (anyone can interact) or closed",
	func = function(name, param)
		local id = tonumber(param)
		if not id then
			return false, "Invalid usage, see /help area_open."
		end

		if not areas:isAreaOwner(id, name) then
			return false, "Area "..id.." does not exist"
					.." or is not owned by you."
		end
		local open = not areas.areas[id].open
		-- Save false as nil to avoid inflating the DB.
		areas.areas[id].open = open or nil
		areas:save()
		return true, ("Area %s."):format(open and "opened" or "closed")
	end
})

minetest.register_chatcommand("move_area", {
	params = "<ID>",
	description = "Move (or resize) an area to the current positions.",
	privs = areas.adminPrivs,
	func = function(name, param)
		local id = tonumber(param)
		if not id then
			return false, "Invalid usage, see /help move_area."
		end

		local area = areas.areas[id]
		if not area then
			return false, "Area does not exist."
		end

		local pos1, pos2 = areas:getPos(name)
		if not pos1 then
			return false, "You need to select an area first."
		end

		areas:move(id, area, pos1, pos2)
		areas:save()

		return true, "Area successfully moved."
	end,
})

minetest.register_chatcommand("area_info", {
	description = "Get information about area configuration and usage.",
	func = function(name, param)
		local lines = {}
		local privs = minetest.get_player_privs(name)

		-- Short (and fast to access) names
		local cfg = areas.config
		local self_prot  = cfg.self_protection
		local prot_priv  = cfg.self_protection_privilege
		local limit      = cfg.self_protection_max_areas
		local limit_high = cfg.self_protection_max_areas_high
		local size_limit = cfg.self_protection_max_size
		local size_limit_high = cfg.self_protection_max_size_high

		local has_high_limit = privs.areas_high_limit
		local has_prot_priv = not prot_priv or privs[prot_priv]
		local can_prot = privs.areas or (self_prot and has_prot_priv)
		local max_count = can_prot and
			(has_high_limit and limit_high or limit) or 0
		local max_size = has_high_limit and
			size_limit_high or size_limit

		-- Privilege information
		local self_prot_line = ("Self protection is %sabled"):format(
				self_prot and "en" or "dis")
		if self_prot and prot_priv then
			self_prot_line = self_prot_line..
				(" %s have the neccessary privilege (%q).")
				:format(
					has_prot_priv and "and you" or
						"but you don't",
					prot_priv)
		else
			self_prot_line = self_prot_line.."."
		end
		table.insert(lines, self_prot_line)
		if privs.areas then
			table.insert(lines, "You are an area"..
				" administrator (\"areas\" privilege).")
		elseif has_high_limit then
			table.insert(lines,
				"You have extended area protection"..
				" limits (\"areas_high_limit\" privilege).")
		end

		-- Area count
		local area_num = 0
		for id, area in pairs(areas.areas) do
			if area.owner == name then
				area_num = area_num + 1
			end
		end
		local count_line = ("You have %d area%s"):format(
			area_num, area_num == 1 and "" or "s")
		if privs.areas then
			count_line = count_line..
				" and have no area protection limits."
		elseif can_prot then
			count_line = count_line..(", out of a maximum of %d.")
				:format(max_count)
		end
		table.insert(lines, count_line)

		-- Area size limits
		local function size_info(str, size)
			table.insert(lines, ("%s spanning up to %dx%dx%d.")
				:format(str, size.x, size.y, size.z))
		end
		local function priv_limit_info(priv, max_count, max_size)
			size_info(("Players with the %q privilege"..
				" can protect up to %d areas"):format(
					priv, max_count), max_size)
		end
		if self_prot then
			if privs.areas then
				priv_limit_info(prot_priv,
					limit, size_limit)
				priv_limit_info("areas_high_limit",
					limit_high, size_limit_high)
			elseif has_prot_priv then
				size_info("You can protect areas", max_size)
			end
		end

		return true, table.concat(lines, "\n")
	end,
})


code:
-- This is inspired by the landrush mod by Bremaweb

areas.hud = {}

minetest.register_globalstep(function(dtime)
	for _, player in pairs(minetest.get_connected_players()) do
		local name = player:get_player_name()
		local pos = vector.round(player:getpos())
		local areaStrings = {}

		for id, area in pairs(areas:getAreasAtPos(pos)) do
			table.insert(areaStrings, ("%s [%u] (%s%s)")
					:format(area.name, id, area.owner,
					area.open and ":open" or ""))
		end

		for i, area in pairs(areas:getExternalHudEntries(pos)) do
			local str = ""
			if area.name then str = area.name .. " " end
			if area.id then str = str.."["..area.id.."] " end
			if area.owner then str = str.."("..area.owner..")" end
			table.insert(areaStrings, str)
		end

		local areaString = "Areas:"
		if #areaStrings > 0 then
			areaString = areaString.."\n"..
				table.concat(areaStrings, "\n")
		end
		local hud = areas.hud[name]
		if not hud then
			hud = {}
			areas.hud[name] = hud
			hud.areasId = player:hud_add({
				hud_elem_type = "text",
				name = "Areas",
				number = 0xFFFFFF,
				position = {x=0, y=1},
				offset = {x=8, y=-8},
				text = areaString,
				scale = {x=200, y=60},
				alignment = {x=1, y=-1},
			})
			hud.oldAreas = areaString
			return
		elseif hud.oldAreas ~= areaString then
			player:hud_change(hud.areasId, "text", areaString)
			hud.oldAreas = areaString
		end
	end
end)

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


code:
-- Areas mod by ShadowNinja
-- Based on node_ownership
-- License: LGPLv2+

areas = {}

areas.adminPrivs = {areas=true}
areas.startTime = os.clock()

areas.modpath = minetest.get_modpath("areas")
dofile(areas.modpath.."/settings.lua")
dofile(areas.modpath.."/api.lua")
dofile(areas.modpath.."/internal.lua")
dofile(areas.modpath.."/chatcommands.lua")
dofile(areas.modpath.."/pos.lua")
dofile(areas.modpath.."/interact.lua")
dofile(areas.modpath.."/legacy.lua")
dofile(areas.modpath.."/hud.lua")

areas:load()

minetest.register_privilege("areas", {
	description = "Can administer areas."
})
minetest.register_privilege("areas_high_limit", {
	description = "Can can more, bigger areas."
})

if not minetest.registered_privileges[areas.config.self_protection_privilege] then
	minetest.register_privilege(areas.config.self_protection_privilege, {
		description = "Can protect areas.",
	})
end

if minetest.settings:get_bool("log_mod") then
	local diffTime = os.clock() - areas.startTime
	minetest.log("action", "areas loaded in "..diffTime.."s.")
end


code:
local old_is_protected = minetest.is_protected
function minetest.is_protected(pos, name)
	if not areas:canInteract(pos, name) then
		return true
	end
	return old_is_protected(pos, name)
end

minetest.register_on_protection_violation(function(pos, name)
	if not areas:canInteract(pos, name) then
		local owners = areas:getNodeOwners(pos)
		minetest.chat_send_player(name,
			("%s is protected by %s."):format(
				minetest.pos_to_string(pos),
				table.concat(owners, ", ")))
	end
end)


code:
function areas:player_exists(name)
	return minetest.get_auth_handler().get_auth(name) ~= nil
end

-- Save the areas table to a file
function areas:save()
	local datastr = minetest.serialize(self.areas)
	if not datastr then
		minetest.log("error", "[areas] Failed to serialize area data!")
		return
	end
	local file, err = io.open(self.config.filename, "w")
	if err then
		return err
	end
	file:write(datastr)
	file:close()
end

-- Load the areas table from the save file
function areas:load()
	local file, err = io.open(self.config.filename, "r")
	if err then
		self.areas = self.areas or {}
		return err
	end
	self.areas = minetest.deserialize(file:read("*a"))
	if type(self.areas) ~= "table" then
		self.areas = {}
	end
	file:close()
	self:populateStore()
end

--- Checks an AreaStore ID.
-- Deletes the AreaStore (falling back to the iterative method)
-- and prints an error message if the ID is invalid.
-- @return Whether the ID was valid.
function areas:checkAreaStoreId(sid)
	if not sid then
		minetest.log("error", "AreaStore failed to find an ID for an "
			.."area!  Falling back to iterative area checking.")
		self.store = nil
		self.store_ids = nil
	end
	return sid and true or false
end

-- Populates the AreaStore after loading, if needed.
function areas:populateStore()
	if not rawget(_G, "AreaStore") then
		return
	end
	local store = AreaStore()
	local store_ids = {}
	for id, area in pairs(areas.areas) do
		local sid = store:insert_area(area.pos1,
			area.pos2, tostring(id))
		if not self:checkAreaStoreId(sid) then
			return
		end
		store_ids[id] = sid
	end
	self.store = store
	self.store_ids = store_ids
end

-- Finds the first usable index in a table
-- Eg: {[1]=false,[4]=true} -> 2
local function findFirstUnusedIndex(t)
	local i = 0
	repeat i = i + 1
	until t[i] == nil
	return i
end

--- Add a area.
-- @return The new area's ID.
function areas:add(owner, name, pos1, pos2, parent)
	local id = findFirstUnusedIndex(self.areas)
	self.areas[id] = {
		name = name,
		pos1 = pos1,
		pos2 = pos2,
		owner = owner,
		parent = parent
	}
	-- Add to AreaStore
	if self.store then
		local sid = self.store:insert_area(pos1, pos2, tostring(id))
		if self:checkAreaStoreId(sid) then
			self.store_ids[id] = sid
		end
	end
	return id
end

--- Remove a area, and optionally it's children recursively.
-- If a area is deleted non-recursively the children will
-- have the removed area's parent as their new parent.
function areas:remove(id, recurse)
	if recurse then
		-- Recursively find child entries and remove them
		local cids = self:getChildren(id)
		for _, cid in pairs(cids) do
			self:remove(cid, true)
		end
	else
		-- Update parents
		local parent = self.areas[id].parent
		local children = self:getChildren(id)
		for _, cid in pairs(children) do
			-- The subarea parent will be niled out if the
			-- removed area does not have a parent
			self.areas[cid].parent = parent

		end
	end

	-- Remove main entry
	self.areas[id] = nil

	-- Remove from AreaStore
	if self.store then
		self.store:remove_area(self.store_ids[id])
		self.store_ids[id] = nil
	end
end

--- Move an area.
function areas:move(id, area, pos1, pos2)
	area.pos1 = pos1
	area.pos2 = pos2

	if self.store then
		self.store:remove_area(areas.store_ids[id])
		local sid = self.store:insert_area(pos1, pos2, tostring(id))
		if self:checkAreaStoreId(sid) then
			self.store_ids[id] = sid
		end
	end
end

-- Checks if a area between two points is entirely contained by another area.
-- Positions must be sorted.
function areas:isSubarea(pos1, pos2, id)
	local area = self.areas[id]
	if not area then
		return false
	end
	local ap1, ap2 = area.pos1, area.pos2
	local ap1x, ap1y, ap1z = ap1.x, ap1.y, ap1.z
	local ap2x, ap2y, ap2z = ap2.x, ap2.y, ap2.z
	local p1x, p1y, p1z = pos1.x, pos1.y, pos1.z
	local p2x, p2y, p2z = pos2.x, pos2.y, pos2.z
	if
			(p1x >= ap1x and p1x <= ap2x) and
			(p2x >= ap1x and p2x <= ap2x) and
			(p1y >= ap1y and p1y <= ap2y) and
			(p2y >= ap1y and p2y <= ap2y) and
			(p1z >= ap1z and p1z <= ap2z) and
			(p2z >= ap1z and p2z <= ap2z) then
		return true
	end
end

-- Returns a table (list) of children of an area given it's identifier
function areas:getChildren(id)
	local children = {}
	for cid, area in pairs(self.areas) do
		if area.parent and area.parent == id then
			table.insert(children, cid)
		end
	end
	return children
end

-- Checks if the user has sufficient privileges.
-- If the player is not a administrator it also checks
-- if the area intersects other areas that they do not own.
-- Also checks the size of the area and if the user already
-- has more than max_areas.
function areas:canPlayerAddArea(pos1, pos2, name)
	local privs = minetest.get_player_privs(name)
	if privs.areas then
		return true
	end

	-- Check self protection privilege, if it is enabled,
	-- and if the area is too big.
	if not self.config.self_protection or
			not privs[areas.config.self_protection_privilege] then
		return false, "Self protection is disabled or you do not have"
				.." the necessary privilege."
	end

	local max_size = privs.areas_high_limit and
			self.config.self_protection_max_size_high or
			self.config.self_protection_max_size
	if
			(pos2.x - pos1.x) > max_size.x or
			(pos2.y - pos1.y) > max_size.y or
			(pos2.z - pos1.z) > max_size.z then
		return false, "Area is too big."
	end

	-- Check number of areas the user has and make sure it not above the max
	local count = 0
	for _, area in pairs(self.areas) do
		if area.owner == name then
			count = count + 1
		end
	end
	local max_areas = privs.areas_high_limit and
			self.config.self_protection_max_areas_high or
			self.config.self_protection_max_areas
	if count >= max_areas then
		return false, "You have reached the maximum amount of"
				.." areas that you are allowed to  protect."
	end

	-- Check intersecting areas
	local can, id = self:canInteractInArea(pos1, pos2, name)
	if not can then
		local area = self.areas[id]
		return false, ("The area intersects with %s [%u] (%s).")
				:format(area.name, id, area.owner)
	end

	return true
end

-- Given a id returns a string in the format:
-- "name [id]: owner (x1, y1, z1) (x2, y2, z2) -> children"
function areas:toString(id)
	local area = self.areas[id]
	local message = ("%s [%d]: %s %s %s"):format(
		area.name, id, area.owner,
		minetest.pos_to_string(area.pos1),
		minetest.pos_to_string(area.pos2))

	local children = areas:getChildren(id)
	if #children > 0 then
		message = message.." -> "..table.concat(children, ", ")
	end
	return message
end

-- Re-order areas in table by their identifiers
function areas:sort()
	local sa = {}
	for k, area in pairs(self.areas) do
		if not area.parent then
			table.insert(sa, area)
			local newid = #sa
			for _, subarea in pairs(self.areas) do
				if subarea.parent == k then
					subarea.parent = newid
					table.insert(sa, subarea)
				end
			end
		end
	end
	self.areas = sa
end

-- Checks if a player owns an area or a parent of it
function areas:isAreaOwner(id, name)
	local cur = self.areas[id]
	if cur and minetest.check_player_privs(name, self.adminPrivs) then
		return true
	end
	while cur do
		if cur.owner == name then
			return true
		elseif cur.parent then
			cur = self.areas[cur.parent]
		else
			return false
		end
	end
	return false
end


code:
-- This file contains functions to convert from
-- the old areas format and other compatability code.

minetest.register_chatcommand("legacy_load_areas", {
	params = "<version>",
	description = "Loads, converts, and saves the areas from"
		.." a legacy save file.",
	privs = {areas=true, server=true},
	func = function(name, param)
		minetest.chat_send_player(name, "Converting areas...")
		local version = tonumber(param)
		if version == 0 then
			err = areas:node_ownership_load()
			if err then
				minetest.chat_send_player(name, "Error loading legacy file: "..err)
				return
			end
		else
			minetest.chat_send_player(name, "Invalid version number. (0 allowed)")
			return
		end
		minetest.chat_send_player(name, "Legacy file loaded.")

		for k, area in pairs(areas.areas) do
			-- New position format
			area.pos1 = {x=area.x1, y=area.y1, z=area.z1}
			area.pos2 = {x=area.x2, y=area.y2, z=area.z2}

			area.x1, area.y1, area.z1,
			area.x2, area.y2, area.z2 =
				nil, nil, nil, nil, nil, nil

			-- Area positions sorting
			areas:sortPos(area.pos1, area.pos2)

			-- Add name
			area.name = "unnamed"

			-- Remove ID
			area.id = nil
		end
		minetest.chat_send_player(name, "Table format updated.")

		areas:save()
		minetest.chat_send_player(name, "Converted areas saved. Done.")
	end
})

function areas:node_ownership_load()
	local filename = minetest.get_worldpath().."/owners.tbl"
	tables, err = loadfile(filename)
	if err then
		return err
	end

	tables = tables()
	for idx = 1, #tables do
		local tolinkv, tolinki = {}, {}
		for i, v in pairs(tables[idx]) do
			if type(v) == "table" and tables[v[1]] then
				table.insert(tolinkv, {i, tables[v[1]]})
			end
			if type(i) == "table" and tables[i[1]] then
				table.insert(tolinki, {i, tables[i[1]]})
			end
		end
		-- link values, first due to possible changes of indices
		for _, v in ipairs(tolinkv) do
			tables[idx][v[1]] = v[2]
		end
		-- link indices
		for _, v in ipairs(tolinki) do
			tables[idx][v[2]], tables[idx][v[1]] =  tables[idx][v[1]], nil
		end
	end
	self.areas = tables[1]
end

-- Returns the name of the first player that owns an area
function areas.getNodeOwnerName(pos)
	for id, area in pairs(areas:getAreasAtPos(pos)) do
		return area.owner
	end
	return false
end

-- Checks if a node is owned by you
function areas.isNodeOwner(pos, name)
	if minetest.check_player_privs(name, areas.adminPrivs) then
		return true
	end
	for id, area in pairs(areas:getAreasAtPos(pos)) do
		if name == area.owner then
			return true
		end
	end
	return false
end

-- Checks if an area has an owner
function areas.hasOwner(pos)
	for id, area in pairs(areas:getAreasAtPos(pos)) do
		return true
	end
	return false
end

IsPlayerNodeOwner = areas.isNodeOwner
GetNodeOwnerName  = areas.getNodeOwnerName
HasOwner          = areas.hasOwner

-- This is entirely untested and may break in strange and new ways.
if areas.config.legacy_table then
	owner_defs = setmetatable({}, {
		__index = function(table, key)
			local a = rawget(areas.areas, key)
			if not a then return a end
			local b = {}
			for k, v in pairs(a) do b[k] = v end
			b.x1, b.y1, b.z1 = b.pos1.x, b.pos1.y, b.pos1.z
			b.x2, b.y1, b.z2 = b.pos2.x, b.pos2.y, b.pos2.z
			b.pos1, b.pos2 = nil, nil
			b.id = key
			return b
		end,
		__newindex = function(table, key, value)
			local a = value
			a.pos1, a.pos2 = {x=a.x1, y=a.y1, z=a.z1},
					{x=a.x2, y=a.y2, z=a.z2}
			a.x1, a.y1, a.z1, a.x2, a.y2, a.z2 =
				nil, nil, nil, nil, nil, nil
			a.name = a.name or "unnamed"
			a.id = nil
			return rawset(areas.areas, key, a)
		end
	})
end


code: name = areas


code:
-- I could depend on WorldEdit for this, but you need to have the 'worldedit'
-- permission to use those commands and you don't have
-- /area_pos{1,2} [X Y Z|X,Y,Z].
-- Since this is mostly copied from WorldEdit it is mostly
-- licensed under the AGPL. (select_area is a exception)

areas.marker1 = {}
areas.marker2 = {}
areas.set_pos = {}
areas.pos1 = {}
areas.pos2 = {}

minetest.register_chatcommand("select_area", {
	params = "<ID>",
	description = "Select a area by id.",
	func = function(name, param)
		local id = tonumber(param)
		if not id then
			return false, "Invalid usage, see /help select_area."
		end
		if not areas.areas[id] then
			return false, "The area "..id.." does not exist."
		end

		areas:setPos1(name, areas.areas[id].pos1)
		areas:setPos2(name, areas.areas[id].pos2)
		return true, "Area "..id.." selected."
	end,
})

minetest.register_chatcommand("area_pos1", {
	params = "[X Y Z|X,Y,Z]",
	description = "Set area protection region position 1 to your"
		.." location or the one specified",
	privs = {},
	func = function(name, param)
		local pos = nil
		local found, _, x, y, z = param:find(
				"^(-?%d+)[, ](-?%d+)[, ](-?%d+)$")
		if found then
			pos = {x=tonumber(x), y=tonumber(y), z=tonumber(z)}
		elseif param == "" then
			local player = minetest.get_player_by_name(name)
			if player then
				pos = player:getpos()
			else
				return false, "Unable to get position."
			end
		else
			return false, "Invalid usage, see /help area_pos1."
		end
		pos = vector.round(pos)
		areas:setPos1(name, pos)
		return true, "Area position 1 set to "
				..minetest.pos_to_string(pos)
	end,
})

minetest.register_chatcommand("area_pos2", {
	params = "[X Y Z|X,Y,Z]",
	description = "Set area protection region position 2 to your"
		.." location or the one specified",
	func = function(name, param)
		local pos = nil
		local found, _, x, y, z = param:find(
				"^(-?%d+)[, ](-?%d+)[, ](-?%d+)$")
		if found then
			pos = {x=tonumber(x), y=tonumber(y), z=tonumber(z)}
		elseif param == "" then
			local player = minetest.get_player_by_name(name)
			if player then
				pos = player:getpos()
			else
				return false, "Unable to get position."
			end
		else
			return false, "Invalid usage, see /help area_pos2."
		end
		pos = vector.round(pos)
		areas:setPos2(name, pos)
		return true, "Area position 2 set to "
				..minetest.pos_to_string(pos)
	end,
})

minetest.register_chatcommand("area_pos", {
	params = "set/set1/set2/get",
	description = "Set area protection region, position 1, or position 2"
		.." by punching nodes, or display the region",
	func = function(name, param)
		if param == "set" then -- Set both area positions
			areas.set_pos[name] = "pos1"
			return true, "Select positions by punching two nodes."
		elseif param == "set1" then -- Set area position 1
			areas.set_pos[name] = "pos1only"
			return true, "Select position 1 by punching a node."
		elseif param == "set2" then -- Set area position 2
			areas.set_pos[name] = "pos2"
			return true, "Select position 2 by punching a node."
		elseif param == "get" then -- Display current area positions
			local pos1str, pos2str = "Position 1: ", "Position 2: "
			if areas.pos1[name] then
				pos1str = pos1str..minetest.pos_to_string(areas.pos1[name])
			else
				pos1str = pos1str.."<not set>"
			end
			if areas.pos2[name] then
				pos2str = pos2str..minetest.pos_to_string(areas.pos2[name])
			else
				pos2str = pos2str.."<not set>"
			end
			return true, pos1str.."\n"..pos2str
		else
			return false, "Unknown subcommand: "..param
		end
	end,
})

function areas:getPos(playerName)
	local pos1, pos2 = areas.pos1[playerName], areas.pos2[playerName]
	if not (pos1 and pos2) then
		return nil
	end
	-- Copy positions so that the area table doesn't contain multiple
	-- references to the same position.
	pos1, pos2 = vector.new(pos1), vector.new(pos2)
	return areas:sortPos(pos1, pos2)
end

function areas:setPos1(playerName, pos)
	areas.pos1[playerName] = pos
	areas.markPos1(playerName)
end

function areas:setPos2(playerName, pos)
	areas.pos2[playerName] = pos
	areas.markPos2(playerName)
end

minetest.register_on_punchnode(function(pos, node, puncher)
	local name = puncher:get_player_name()
	-- Currently setting position
	if name ~= "" and areas.set_pos[name] then
		if areas.set_pos[name] == "pos1" then
			areas.pos1[name] = pos
			areas.markPos1(name)
			areas.set_pos[name] = "pos2"
			minetest.chat_send_player(name,
					"Position 1 set to "
					..minetest.pos_to_string(pos))
		elseif areas.set_pos[name] == "pos1only" then
			areas.pos1[name] = pos
			areas.markPos1(name)
			areas.set_pos[name] = nil
			minetest.chat_send_player(name,
					"Position 1 set to "
					..minetest.pos_to_string(pos))
		elseif areas.set_pos[name] == "pos2" then
			areas.pos2[name] = pos
			areas.markPos2(name)
			areas.set_pos[name] = nil
			minetest.chat_send_player(name,
					"Position 2 set to "
					..minetest.pos_to_string(pos))
		end
	end
end)

-- Modifies positions `pos1` and `pos2` so that each component of `pos1`
-- is less than or equal to its corresponding component of `pos2`,
-- returning the two positions.
function areas:sortPos(pos1, pos2)
	if pos1.x > pos2.x then
		pos2.x, pos1.x = pos1.x, pos2.x
	end
	if pos1.y > pos2.y then
		pos2.y, pos1.y = pos1.y, pos2.y
	end
	if pos1.z > pos2.z then
		pos2.z, pos1.z = pos1.z, pos2.z
	end
	return pos1, pos2
end

-- Marks area position 1
areas.markPos1 = function(name)
	local pos = areas.pos1[name]
	if areas.marker1[name] ~= nil then -- Marker already exists
		areas.marker1[name]:remove() -- Remove marker
		areas.marker1[name] = nil
	end
	if pos ~= nil then -- Add marker
		areas.marker1[name] = minetest.add_entity(pos, "areas:pos1")
		areas.marker1[name]:get_luaentity().active = true
	end
end

-- Marks area position 2
areas.markPos2 = function(name)
	local pos = areas.pos2[name]
	if areas.marker2[name] ~= nil then -- Marker already exists
		areas.marker2[name]:remove() -- Remove marker
		areas.marker2[name] = nil
	end
	if pos ~= nil then -- Add marker
		areas.marker2[name] = minetest.add_entity(pos, "areas:pos2")
		areas.marker2[name]:get_luaentity().active = true
	end
end

minetest.register_entity("areas:pos1", {
	initial_properties = {
		visual = "cube",
		visual_size = {x=1.1, y=1.1},
		textures = {"areas_pos1.png", "areas_pos1.png",
		            "areas_pos1.png", "areas_pos1.png",
		            "areas_pos1.png", "areas_pos1.png"},
		collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55},
	},
	on_step = function(self, dtime)
		if self.active == nil then
			self.object:remove()
		end
	end,
	on_punch = function(self, hitter)
		self.object:remove()
		local name = hitter:get_player_name()
		areas.marker1[name] = nil
	end,
})

minetest.register_entity("areas:pos2", {
	initial_properties = {
		visual = "cube",
		visual_size = {x=1.1, y=1.1},
		textures = {"areas_pos2.png", "areas_pos2.png",
		            "areas_pos2.png", "areas_pos2.png",
		            "areas_pos2.png", "areas_pos2.png"},
		collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55},
	},
	on_step = function(self, dtime)
		if self.active == nil then
			self.object:remove()
		end
	end,
	on_punch = function(self, hitter)
		self.object:remove()
		local name = hitter:get_player_name()
		areas.marker2[name] = nil
	end,
})


code:
local world_path = minetest.get_worldpath()

areas.config = {}

local function setting(tp, name, default)
	local full_name = "areas."..name
	local value
	if tp == "boolean" then
		value = minetest.settings:get_bool(full_name)
	elseif tp == "string" then
		value = minetest.settings:get(full_name)
	elseif tp == "position" then
		value = minetest.setting_get_pos(full_name)
	elseif tp == "number" then
		value = tonumber(minetest.settings:get(full_name))
	else
		error("Invalid setting type!")
	end
	if value == nil then
		value = default
	end
	areas.config[name] = value
end

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

setting("string", "filename", world_path.."/areas.dat")

-- Allow players with a privilege create their own areas
-- within the maximum size and number.
setting("boolean",  "self_protection", true)
setting("string",   "self_protection_privilege", "interact")
setting("position", "self_protection_max_size",      {x=64,  y=128, z=64})
setting("number",   "self_protection_max_areas",      4)
-- For players with the areas_high_limit privilege.
setting("position", "self_protection_max_size_high", {x=512, y=512, z=512})
setting("number",   "self_protection_max_areas_high", 32)

-- legacy_table (owner_defs) compatibility.  Untested and has known issues.
setting("boolean", "legacy_table", false)


code:
-- Translation support
local S = minetest.get_translator("basic_materials")

-- items

minetest.register_craftitem("basic_materials:silicon", {
	description = S("Silicon lump"),
	inventory_image = "basic_materials_silicon.png",
})

minetest.register_craftitem("basic_materials:ic", {
	description = S("Simple Integrated Circuit"),
	inventory_image = "basic_materials_ic.png",
})

minetest.register_craftitem("basic_materials:motor", {
	description = S("Simple Motor"),
	inventory_image = "basic_materials_motor.png",
})

minetest.register_craftitem("basic_materials:heating_element", {
	description = S("Heating element"),
	inventory_image = "basic_materials_heating_element.png",
})

minetest.register_craftitem("basic_materials:energy_crystal_simple", {
	description = S("Simple energy crystal"),
	inventory_image = "basic_materials_energy_crystal.png",
})

-- crafts

minetest.register_craft( {
	output = "mesecons_materials:silicon 4",
	recipe = {
		{ "default:sand", "default:sand" },
		{ "default:sand", "default:steel_ingot" },
	},
})

minetest.register_craft( {
	output = "basic_materials:ic 4",
	recipe = {
		{ "mesecons_materials:silicon", "mesecons_materials:silicon" },
		{ "mesecons_materials:silicon", "default:copper_ingot" },
	},
})

minetest.register_craft( {
    output = "basic_materials:motor 2",
    recipe = {
		{ "default:mese_crystal_fragment", "basic_materials:copper_wire", "basic_materials:plastic_sheet" },
		{ "default:copper_ingot",          "default:steel_ingot",         "default:steel_ingot" },
		{ "default:mese_crystal_fragment", "basic_materials:copper_wire", "basic_materials:plastic_sheet" }
    },
	replacements = {
		{ "basic_materials:copper_wire", "basic_materials:empty_spool" },
		{ "basic_materials:copper_wire", "basic_materials:empty_spool" },
	}
})

minetest.register_craft( {
    output = "basic_materials:heating_element 2",
    recipe = {
		{ "default:copper_ingot", "default:mese_crystal_fragment", "default:copper_ingot" }
    },
})

minetest.register_craft({
	--type = "shapeless",
	output = "basic_materials:energy_crystal_simple 2",
	recipe = {
		{ "default:mese_crystal_fragment", "default:torch", "default:mese_crystal_fragment" },
		{ "default:diamond", "default:gold_ingot", "default:diamond" }
	},
})

-- aliases

minetest.register_alias("homedecor:ic",                     "basic_materials:ic")
minetest.register_alias("homedecor:motor",                  "basic_materials:motor")
minetest.register_alias("technic:motor",                    "basic_materials:motor")
minetest.register_alias("homedecor:heating_element",        "basic_materials:heating_element")
minetest.register_alias("homedecor:power_crystal",          "basic_materials:energy_crystal_simple")

minetest.register_alias_force("mesecons_materials:silicon", "basic_materials:silicon")


code:
-- Basic materials mod
-- by Vanessa Dannenberg

-- This mod supplies all those little random craft items that everyone always
-- seems to need, such as metal bars (ala rebar), plastic, wire, and so on.

local modpath = minetest.get_modpath("basic_materials")

basic_materials = {}

dofile(modpath.."/metals.lua")
dofile(modpath.."/plastics.lua")
dofile(modpath.."/electrical-electronic.lua")
dofile(modpath.."/misc.lua")


code:
# textdomain: basic_materials
Silicon lump=Morceau de silicium
Simple Integrated Circuit=Circuit intégré simple
Simple Motor=Moteur simple
Heating element=Élément chauffant
Simple energy crystal=Cristal d’énergie simple

Spool of steel wire=Bobine de fil d’acier
Spool of copper wire=Bobine de fil de cuivre
Spool of silver wire=Bobine de fil d’argent
Spool of gold wire=Bobine de fil d’or
Steel Strip=Bande de acier
Copper Strip=Bande de cuivre
Steel Bar=Barre d’acier
Chainlinks (brass)=Maillon en laiton
Chainlinks (steel)=Maillon en acier
Brass Ingot=Lingot de laiton
Steel gear=Rouage en acier
Padlock=Cadenas
Chain (steel, hanging)=Chaine en acier
Chain (brass, hanging)=Chaine en laiton
Brass Block=Bloc de laiton

Oil extract=Extrait d’huile
Unprocessed paraffin=Paraffine non transformée
Uncooked Terracotta Base=Argile crue
Wet Cement=Ciment humide
Cement=Ciment
Concrete Block=Bloc de béton

Plastic sheet=Morceau de plastique
Plastic strips=Bande de plastique
Empty wire spool=Bobine de fil vide


code:
-- Translation support
local S = minetest.get_translator("basic_materials")

-- items

minetest.register_craftitem("basic_materials:steel_wire", {
	description = S("Spool of steel wire"),
	inventory_image = "basic_materials_steel_wire.png"
})

minetest.register_craftitem("basic_materials:copper_wire", {
	description = S("Spool of copper wire"),
	inventory_image = "basic_materials_copper_wire.png"
})

minetest.register_craftitem("basic_materials:silver_wire", {
	description = S("Spool of silver wire"),
	inventory_image = "basic_materials_silver_wire.png"
})

minetest.register_craftitem("basic_materials:gold_wire", {
	description = S("Spool of gold wire"),
	inventory_image = "basic_materials_gold_wire.png"
})

minetest.register_craftitem("basic_materials:steel_strip", {
	description = S("Steel Strip"),
	inventory_image = "basic_materials_steel_strip.png"
})

minetest.register_craftitem("basic_materials:copper_strip", {
	description = S("Copper Strip"),
	inventory_image = "basic_materials_copper_strip.png"
})

minetest.register_craftitem("basic_materials:steel_bar", {
	description = S("Steel Bar"),
	inventory_image = "basic_materials_steel_bar.png",
})

minetest.register_craftitem("basic_materials:chainlink_brass", {
	description = S("Chainlinks (brass)"),
	inventory_image = "basic_materials_chainlink_brass.png"
})

minetest.register_craftitem("basic_materials:chainlink_steel", {
	description = S("Chainlinks (steel)"),
	inventory_image = "basic_materials_chainlink_steel.png"
})

minetest.register_craftitem("basic_materials:brass_ingot", {
	description = S("Brass Ingot"),
	inventory_image = "basic_materials_brass_ingot.png",
})

minetest.register_craftitem("basic_materials:gear_steel", {
	description = S("Steel gear"),
	inventory_image = "basic_materials_gear_steel.png"
})

minetest.register_craftitem("basic_materials:padlock", {
	description = S("Padlock"),
	inventory_image = "basic_materials_padlock.png"
})

-- nodes

local chains_sbox = {
	type = "fixed",
	fixed = { -0.1, -0.5, -0.1, 0.1, 0.5, 0.1 }
}

local topchains_sbox = {
	type = "fixed",
	fixed = {
		{ -0.25, 0.35, -0.25, 0.25, 0.5, 0.25 },
		{ -0.1, -0.5, -0.1, 0.1, 0.4, 0.1 }
	}
}

minetest.register_node("basic_materials:chain_steel", {
	description = S("Chain (steel, hanging)"),
	drawtype = "mesh",
	mesh = "basic_materials_chains.obj",
	tiles = {"basic_materials_chain_steel.png"},
	walkable = false,
	climbable = true,
	sunlight_propagates = true,
	paramtype = "light",
	inventory_image = "basic_materials_chain_steel_inv.png",
	groups = {cracky=3},
	selection_box = chains_sbox,
})

minetest.register_node("basic_materials:chain_brass", {
	description = S("Chain (brass, hanging)"),
	drawtype = "mesh",
	mesh = "basic_materials_chains.obj",
	tiles = {"basic_materials_chain_brass.png"},
	walkable = false,
	climbable = true,
	sunlight_propagates = true,
	paramtype = "light",
	inventory_image = "basic_materials_chain_brass_inv.png",
	groups = {cracky=3},
	selection_box = chains_sbox,
})

minetest.register_node("basic_materials:brass_block", {
	description = S("Brass Block"),
	tiles = { "basic_materials_brass_block.png" },
	is_ground_content = false,
	groups = {cracky=1, level=2},
	sounds = default.node_sound_metal_defaults()
})

-- crafts

minetest.register_craft( {
	output = "basic_materials:copper_wire 2",
	type = "shapeless",
	recipe = {
		"default:copper_ingot",
		"basic_materials:empty_spool",
		"basic_materials:empty_spool",
	},
})

minetest.register_craft( {
	output = "basic_materials:silver_wire 2",
	type = "shapeless",
	recipe = {
		"moreores:silver_ingot",
		"basic_materials:empty_spool",
		"basic_materials:empty_spool",
	},
})

minetest.register_craft( {
	output = "basic_materials:gold_wire 2",
	type = "shapeless",
	recipe = {
		"default:gold_ingot",
		"basic_materials:empty_spool",
		"basic_materials:empty_spool",
	},
})

minetest.register_craft( {
	output = "basic_materials:steel_wire 2",
	type = "shapeless",
	recipe = {
		"default:steel_ingot",
		"basic_materials:empty_spool",
		"basic_materials:empty_spool",
	},
})

minetest.register_craft( {
	output = "basic_materials:steel_strip 12",
	recipe = {
		{ "", "default:steel_ingot", "" },
		{ "default:steel_ingot", "", "" },
	},
})

minetest.register_craft( {
	output = "basic_materials:copper_strip 12",
	recipe = {
		{ "", "default:copper_ingot", "" },
		{ "default:copper_ingot", "", "" },
	},
})

minetest.register_craft( {
	output = "basic_materials:steel_bar 6",
	recipe = {
		{ "", "", "default:steel_ingot" },
		{ "", "default:steel_ingot", "" },
		{ "default:steel_ingot", "", "" },
	},
})

minetest.register_craft( {
	output = "basic_materials:padlock 2",
	recipe = {
		{ "basic_materials:steel_bar" },
		{ "default:steel_ingot" },
		{ "default:steel_ingot" },
	},
})

minetest.register_craft({
	output = "basic_materials:chainlink_steel 12",
	recipe = {
		{"", "default:steel_ingot", "default:steel_ingot"},
		{ "default:steel_ingot", "", "default:steel_ingot" },
		{ "default:steel_ingot", "default:steel_ingot", "" },
	},
})

minetest.register_craft({
	output = "basic_materials:chainlink_brass 12",
	recipe = {
		{"", "basic_materials:brass_ingot", "basic_materials:brass_ingot"},
		{ "basic_materials:brass_ingot", "", "basic_materials:brass_ingot" },
		{ "basic_materials:brass_ingot", "basic_materials:brass_ingot", "" },
	},
})

minetest.register_craft({
	output = 'basic_materials:chain_steel 2',
	recipe = {
		{"basic_materials:chainlink_steel"},
		{"basic_materials:chainlink_steel"},
		{"basic_materials:chainlink_steel"}
	}
})

minetest.register_craft({
	output = 'basic_materials:chain_brass 2',
	recipe = {
		{"basic_materials:chainlink_brass"},
		{"basic_materials:chainlink_brass"},
		{"basic_materials:chainlink_brass"}
	}
})

minetest.register_craft( {
	output = "basic_materials:gear_steel 6",
	recipe = {
		{ "", "default:steel_ingot", "" },
		{ "default:steel_ingot","basic_materials:chainlink_steel", "default:steel_ingot" },
		{ "", "default:steel_ingot", "" }
	},
})

minetest.register_craft( {
	type = "shapeless",
	output = "basic_materials:brass_ingot 3",
	recipe = {
		"default:copper_ingot",
		"default:copper_ingot",
		"moreores:silver_ingot",
	},
})

minetest.register_craft( {
	type = "shapeless",
	output = "basic_materials:brass_ingot 9",
	recipe = { "basic_materials:brass_block" },
})

minetest.register_craft( {
	output = "basic_materials:brass_block",
	recipe = {
		{ "basic_materials:brass_ingot", "basic_materials:brass_ingot", "basic_materials:brass_ingot" },
		{ "basic_materials:brass_ingot", "basic_materials:brass_ingot", "basic_materials:brass_ingot" },
		{ "basic_materials:brass_ingot", "basic_materials:brass_ingot", "basic_materials:brass_ingot" },
	},
})

-- aliases

minetest.register_alias("homedecor:copper_wire",           "basic_materials:copper_wire")
minetest.register_alias("technic:fine_copper_wire",        "basic_materials:copper_wire")
minetest.register_alias("technic:fine_silver_wire",        "basic_materials:silver_wire")
minetest.register_alias("technic:fine_gold_wire",          "basic_materials:gold_wire")

minetest.register_alias("homedecor:steel_wire",            "basic_materials:steel_wire")

minetest.register_alias("homedecor:brass_ingot",           "basic_materials:brass_ingot")
minetest.register_alias("technic:brass_ingot",             "basic_materials:brass_ingot")
minetest.register_alias("technic:brass_block",             "basic_materials:brass_block")

minetest.register_alias("homedecor:copper_strip",          "basic_materials:copper_strip")
minetest.register_alias("homedecor:steel_strip",           "basic_materials:steel_strip")

minetest.register_alias_force("glooptest:chainlink",       "basic_materials:chainlink_steel")
minetest.register_alias_force("homedecor:chainlink_steel", "basic_materials:chainlink_steel")
minetest.register_alias("homedecor:chainlink_brass",       "basic_materials:chainlink_brass")
minetest.register_alias("chains:chain",                    "basic_materials:chain_steel")
minetest.register_alias("chains:chain_brass",              "basic_materials:chain_brass")

minetest.register_alias("pipeworks:gear",                  "basic_materials:gear_steel")

minetest.register_alias("technic:rebar",                  "basic_materials:steel_bar")


code:
-- Translation support
local S = minetest.get_translator("basic_materials")

-- items

minetest.register_craftitem("basic_materials:oil_extract", {
	description = S("Oil extract"),
	inventory_image = "basic_materials_oil_extract.png",
})

minetest.register_craftitem("basic_materials:paraffin", {
	description = S("Unprocessed paraffin"),
	inventory_image = "basic_materials_paraffin.png",
})

minetest.register_craftitem("basic_materials:terracotta_base", {
	description = S("Uncooked Terracotta Base"),
	inventory_image = "basic_materials_terracotta_base.png",
})

minetest.register_craftitem("basic_materials:wet_cement", {
	description = S("Wet Cement"),
	inventory_image = "basic_materials_wet_cement.png",
})

-- nodes

minetest.register_node("basic_materials:cement_block", {
	description = S("Cement"),
	tiles = {"basic_materials_cement_block.png"},
	is_ground_content = true,
	groups = {cracky=2},
	sounds = default.node_sound_stone_defaults(),
})

minetest.register_node("basic_materials:concrete_block", {
	description = S("Concrete Block"),
	tiles = {"basic_materials_concrete_block.png",},
	groups = {cracky=1, level=2, concrete=1},
	sounds = default.node_sound_stone_defaults(),
})

-- crafts

minetest.register_craft({
	type = "shapeless",
	output = "basic_materials:oil_extract 2",
	recipe = {
		"group:leaves",
		"group:leaves",
		"group:leaves",
		"group:leaves",
		"group:leaves",
		"group:leaves"
	}
})

minetest.register_craft({
	type = "cooking",
	output = "basic_materials:paraffin",
	recipe = "basic_materials:oil_extract",
})

minetest.register_craft({
	type = "fuel",
	recipe = "basic_materials:oil_extract",
	burntime = 30,
})

minetest.register_craft({
	type = "fuel",
	recipe = "basic_materials:paraffin",
	burntime = 30,
})

minetest.register_craft( {
	type = "shapeless",
	output = "basic_materials:terracotta_base 8",
	recipe = {
		"bucket:bucket_water",
		"default:clay_lump",
		"default:gravel",
	},
	replacements = { {"bucket:bucket_water", "bucket:bucket_empty"}, },
})

minetest.register_craft({
	type = "shapeless",
	output = "basic_materials:wet_cement 3",
	recipe = {
		"default:dirt",
		"dye:dark_grey",
		"dye:dark_grey",
		"dye:dark_grey",
		"bucket:bucket_water"
	},
	replacements = {{'bucket:bucket_water', 'bucket:bucket_empty'},},
})

minetest.register_craft({
	type = "cooking",
	output = "basic_materials:cement_block",
	recipe = "basic_materials:wet_cement",
	cooktime = 8
})

minetest.register_craft({
	output = 'basic_materials:concrete_block 6',
	recipe = {
		{'group:sand',                'basic_materials:wet_cement', 'default:gravel'},
		{'basic_materials:steel_bar', 'basic_materials:wet_cement', 'basic_materials:steel_bar'},
		{'default:gravel',            'basic_materials:wet_cement', 'group:sand'},
	}
})

-- aliases

minetest.register_alias("homedecor:oil_extract",      "basic_materials:oil_extract")
minetest.register_alias("homedecor:paraffin",         "basic_materials:paraffin")
minetest.register_alias("homedecor:plastic_base",     "basic_materials:paraffin")
minetest.register_alias("homedecor:terracotta_base",  "basic_materials:terracotta_base")

minetest.register_alias("gloopblocks:wet_cement",     "basic_materials:wet_cement")
minetest.register_alias("gloopblocks:cement",         "basic_materials:cement_block")

minetest.register_alias("technic:concrete",           "basic_materials:concrete_block")


code:
name = basic_materials
depends = default
optional_depends = moreores
release = 2977
title = Basic Materials and items
description = Provides a small selection of "basic" materials and items that other mods should use when possible -- things like steel bars and chains, wire, plastic strips and sheets, and more.
author = VanessaE


code:
-- Translation support
local S = minetest.get_translator("basic_materials")

-- items

minetest.register_craftitem("basic_materials:plastic_sheet", {
	description = S("Plastic sheet"),
	inventory_image = "basic_materials_plastic_sheet.png",
})

minetest.register_craftitem("basic_materials:plastic_strip", {
	description = S("Plastic strips"),
	inventory_image = "basic_materials_plastic_strip.png",
})

minetest.register_craftitem("basic_materials:empty_spool", {
	description = S("Empty wire spool"),
	inventory_image = "basic_materials_empty_spool.png"
})

-- crafts

minetest.register_craft({
	type = "cooking",
	output = "basic_materials:plastic_sheet",
	recipe = "basic_materials:paraffin",
})

minetest.register_craft({
	type = "fuel",
	recipe = "basic_materials:plastic_sheet",
	burntime = 30,
})

minetest.register_craft( {
    output = "basic_materials:plastic_strip 9",
    recipe = {
		{ "basic_materials:plastic_sheet", "basic_materials:plastic_sheet", "basic_materials:plastic_sheet" }
    },
})

minetest.register_craft( {
	output = "basic_materials:empty_spool 3",
	recipe = {
		{ "basic_materials:plastic_sheet", "basic_materials:plastic_sheet", "basic_materials:plastic_sheet" },
		{ "", "basic_materials:plastic_sheet", "" },
		{ "basic_materials:plastic_sheet", "basic_materials:plastic_sheet", "basic_materials:plastic_sheet" }
	},
})

-- aliases

minetest.register_alias("homedecor:plastic_sheeting", "basic_materials:plastic_sheet")
minetest.register_alias("homedecor:plastic_strips",   "basic_materials:plastic_strip")
minetest.register_alias("homedecor:empty_spool",      "basic_materials:empty_spool")


code:
Bonemeal mod [bonemeal]

This mod adds four new items into the game, bones which can be dug from normal
dirt which can be made into bonemeal, mulch which is is crafted using a tree and
8x leaves, and fertiliser which is a mixture of them both.

Each item can be used on saplings and crops for a chance to grow them quicker as
well as dirt which will generate random grass, flowers or whichever decoration
is registered.

Mulch has a strength of 1, Bonemeal 2 and Fertiliser 3 which means the stronger
the item, the more chance of growing saplings in low light, making crops sprout
quicker or simply decorate a larger area with grass and flowers.

The api.txt document shows how to add your own saplings, crops and grasses to
the list by using one of the 3 commands included and the mod.lua file gives you
many examples by using some of the popular mods available.

https://forum.minetest.net/viewtopic.php?f=9&t=16446

Changelog:

- 0.1 - Initial release
- 0.2 - Added global on_use function for bonemeal growth
- 0.3 - Added strength to on_use global for new items (mulch and fertiliser)
- 0.4 - Added Intllib support and fr.txt file
- 0.5 - Added support for default bush and acacia bush saplings
- 0.6 - Using newer functions, Minetest 0.4.16 and above needed to run
- 0.7 - Can be used on papyrus and cactus now, added coral recipe, api addition
- 0.8 - Added support for farming redo's new garlic, pepper and onion crops
- 0.9 - Added support for farming redo's pea and beetroot crops, checks for place_param
- 1.0 - add_deco() now adds to existing item list while set_deco() replaces item list (thanks h-v-smacker)
- 1.1 - Added {can_bonemeal=1} group for special nodes
- 1.2 - Added support for minetest 5.0 cactus seedling, blueberry bush sapling and emergent jungle tree saplings, additional flowers and pine bush sapling.
- 1.3 - Ability to craft dye from mulch, bonemeal and fertiliser (thanks orbea)

Lucky Blocks: 6


code:
Bonemeal API
============

This guide will show you how to add saplings, crops and dirt types for the
bonemeal mod to use from withhin your own mods.  Please make sure that bonemeal
appears in the depends.txt file of your mod so everything work properly.

Function Usage
==============

Adding Crops
------------

bonemeal:add_crop({
	{ nodename_start, growing_steps, seed_name }
})

This command is used to add new crops for bonemeal to work on.

e.g.

bonemeal:add_crop({
	{"farming:cotton_", 8, "farming:seed_cotton"},
	{"farming:wheat_", 8, "farming:seed_wheat"},
})

Adding Saplings
---------------

bonemeal:add_sapling({
	{ sapling_node, function, soil_type[sand, dirt, nodename] }
})

This command will add new saplings for bonemeal to grow on sand, soil or a
specified node type.

bonemeal:add_sapling({
	{"ethereal:palm_sapling", ethereal.grow_palm_tree, "soil"},
	{"ethereal:palm_sapling", ethereal.grow_palm_tree, "sand"},
})

Adding Dirt Decoration
----------------------

bonemeal:add_deco({
	{ dirt_node, {grass_node_list}, {decor_node_list} }
})

This command will add grass and decoration to specific dirt types, use "" to
add an empty node. If some decorations have been already defined for this dirt type, new
will be added to the respective list. All empty ("") entries will be added regardless,
which allows to decrease the frequency of decoration emergence, if needed.

e.g.

bonemeal:add_deco({
	{"default:dirt_with_dry_grass", {"default:dry_grass_1", ""},
		{"flowers:rose", "flowers:viola"} }
})

Thus, add_deco() always adds (to) a definition, and never overrides. To discard an existing
definiton in favor of the new one, use

bonemeal:set_deco({
	{ dirt_node, {grass_node_list}, {decor_node_list} }
})

This command will set decoration for a given dirt type, fully replacing any existing definition.

Global ON_USE Function
----------------------

bonemeal:on_use(pos, strength, node)

This function can be called from other mods to grow plants using alternative
bonemeal items and have the same effect.

  {pos} is the location to apply growing
  {strength} is how strong to grow [low of 1 to high of 4]
  {node} is the node at pos, but can be left nil to get_node itself

Note: Higher strength items require lower light levels, and a strength of 4
needs no light at all.

Final Words
===========

I hope this guide helps you add your own plants so you can grow them quickly
with the items included.  Please check the mods.lua for more examples.


code:
default
intllib?
farming?
ethereal?
moretrees?
technic_worldgen?
lucky_block?
flowers?
dye?


code: Adds bone and bonemeal giving the ability to quickly grow plants and saplings.


code:
bonemeal = {}

local path = minetest.get_modpath("bonemeal")
local min, max, random = math.min, math.max, math.random

-- Load support for intllib.
local S = minetest.get_translator and minetest.get_translator("bonemeal") or
		dofile(path .. "/intllib.lua")

-- creative check
local creative_mode_cache = minetest.settings:get_bool("creative_mode")
function is_creative(name)
	return creative_mode_cache or minetest.check_player_privs(name, {creative = true})
end

-- default crops
local crops = {
	{"farming:cotton_", 8, "farming:seed_cotton"},
	{"farming:wheat_", 8, "farming:seed_wheat"}
}

-- special pine check for nearby snow
local function pine_grow(pos)

	if minetest.find_node_near(pos, 1,
		{"default:snow", "default:snowblock", "default:dirt_with_snow"}) then

		default.grow_new_snowy_pine_tree(pos)
	else
		default.grow_new_pine_tree(pos)
	end
end

-- default saplings
local saplings = {
	{"default:sapling", default.grow_new_apple_tree, "soil"},
	{"default:junglesapling", default.grow_new_jungle_tree, "soil"},
	{"default:emergent_jungle_sapling", default.grow_new_emergent_jungle_tree, "soil"},
	{"default:acacia_sapling", default.grow_new_acacia_tree, "soil"},
	{"default:aspen_sapling", default.grow_new_aspen_tree, "soil"},
	{"default:pine_sapling", pine_grow, "soil"},
	{"default:bush_sapling", default.grow_bush, "soil"},
	{"default:acacia_bush_sapling", default.grow_acacia_bush, "soil"},
	{"default:large_cactus_seedling", default.grow_large_cactus, "sand"},
	{"default:blueberry_bush_sapling", default.grow_blueberry_bush, "soil"},
	{"default:pine_bush_sapling", default.grow_pine_bush, "soil"}
}

-- helper tables ( "" denotes a blank item )
local green_grass = {
	"default:grass_2", "default:grass_3", "default:grass_4",
	"default:grass_5", "", ""
}

local dry_grass = {
	"default:dry_grass_2", "default:dry_grass_3", "default:dry_grass_4",
	"default:dry_grass_5", "", ""
}

-- add all in-game flowers except waterlily
local flowers = {}

for node, def in pairs(minetest.registered_nodes) do

	if def.groups.flower and not node:find("waterlily") then
		flowers[#flowers + 1] = node
	end
end

-- add additional bakedclay flowers if enabled
if minetest.get_modpath("bakedclay") then
	flowers[#flowers + 1] = "bakedclay:delphinium"
	flowers[#flowers + 1] = "bakedclay:thistle"
	flowers[#flowers + 1] = "bakedclay:lazarus"
	flowers[#flowers + 1] = "bakedclay:mannagrass"
	flowers[#flowers + 1] = ""
end

-- default biomes deco
local deco = {
	{"default:dirt_with_dry_grass", dry_grass, flowers},
	{"default:sand", {}, {"default:dry_shrub", "", "", ""} },
	{"default:desert_sand", {}, {"default:dry_shrub", "", "", ""} },
	{"default:silver_sand", {}, {"default:dry_shrub", "", "", ""} },
}

--
-- local functions
--

-- particles
local function particle_effect(pos)

	minetest.add_particlespawner({
		amount = 4,
		time = 0.15,
		minpos = pos,
		maxpos = pos,
		minvel = {x = -1, y = 2, z = -1},
		maxvel = {x = 1, y = 4, z = 1},
		minacc = {x = -1, y = -1, z = -1},
		maxacc = {x = 1, y = 1, z = 1},
		minexptime = 1,
		maxexptime = 1,
		minsize = 1,
		maxsize = 3,
		texture = "bonemeal_particle.png"
	})
end

-- tree type check
local function grow_tree(pos, object)

	if type(object) == "table" and object.axiom then
		-- grow L-system tree
		minetest.remove_node(pos)
		minetest.spawn_tree(pos, object)

	elseif type(object) == "string" and minetest.registered_nodes[object] then
		-- place node
		minetest.set_node(pos, {name = object})

	elseif type(object) == "function" then
		-- function
		object(pos)
	end
end

-- sapling check
local function check_sapling(pos, nodename)

	-- what is sapling placed on?
	local under =  minetest.get_node({
		x = pos.x,
		y = pos.y - 1,
		z = pos.z
	})

	local can_grow, grow_on

	-- check list for sapling and function
	for n = 1, #saplings do

		if saplings[n][1] == nodename then

			grow_on = saplings[n][3]

			-- sapling grows on top of specific node
			if grow_on
			and grow_on ~= "soil"
			and grow_on ~= "sand"
			and grow_on == under.name then
				can_grow = true
			end

			-- sapling grows on top of soil (default)
			if can_grow == nil
			and (grow_on == nil or grow_on == "soil")
			and minetest.get_item_group(under.name, "soil") > 0 then
				can_grow = true
			end

			-- sapling grows on top of sand
			if can_grow == nil
			and grow_on == "sand"
			and minetest.get_item_group(under.name, "sand") > 0 then
				can_grow = true
			end

			-- check if we can grow sapling
			if can_grow then
				particle_effect(pos)
				grow_tree(pos, saplings[n][2])
				return
			end
		end
	end
end

-- crops check
local function check_crops(pos, nodename, strength)

	local mod, crop, stage, nod, def

	-- grow registered crops
	for n = 1, #crops do

		if nodename:find(crops[n][1])
		or nodename == crops[n][3] then

			-- separate mod and node name
			mod = nodename:split(":")[1] .. ":"
			crop = nodename:split(":")[2]

			-- get stage number or set to 0 for seed
			stage = tonumber( crop:split("_")[2] ) or 0
			stage = min(stage + strength, crops[n][2])

			-- check for place_param setting
			nod = crops[n][1] .. stage
			def = minetest.registered_nodes[nod]
			def = def and def.place_param2 or 0

			minetest.set_node(pos, {name = nod, param2 = def})

			particle_effect(pos)

			return
		end
	end
end

-- check soil for specific decoration placement
local function check_soil(pos, nodename, strength)

	-- set radius according to strength
	local side = strength - 1
	local tall = max(strength - 2, 0)
	local floor
	local groups = minetest.registered_items[nodename]
		and minetest.registered_items[nodename].groups or {}

	-- only place decoration on one type of surface
	if groups.soil then
		floor = {"group:soil"}
	elseif groups.sand then
		floor = {"group:sand"}
	else
		floor = {nodename}
	end

	-- get area of land with free space above
	local dirt = minetest.find_nodes_in_area_under_air(
		{x = pos.x - side, y = pos.y - tall, z = pos.z - side},
		{x = pos.x + side, y = pos.y + tall, z = pos.z + side}, floor)

	-- set default grass and decoration
	local grass = green_grass
	local decor = flowers

	-- choose grass and decoration to use on dirt patch
	for n = 1, #deco do

		-- do we have a grass match?
		if nodename == deco[n][1] then
			grass = deco[n][2] or {}
			decor = deco[n][3] or {}
		end
	end

	local pos2, nod, def

	-- loop through soil
	for _, n in pairs(dirt) do

		if random(5) == 5 then
			if decor and #decor > 0 then
				-- place random decoration (rare)
				local dnum = #decor or 1
				nod = decor[random(dnum)] or ""
			end
		else
			if grass and #grass > 0 then
				-- place random grass (common)
				local dgra = #grass or 1
				nod = #grass > 0 and grass[random(dgra)] or ""
			end
		end

		pos2 = n

		pos2.y = pos2.y + 1

		if nod and nod ~= "" then

			-- get crop param2 value
			def = minetest.registered_nodes[nod]
			def = def and def.place_param2

			-- if param2 not preset then get from existing node
			if not def then
				local node = minetest.get_node_or_nil(pos2)
				def = node and node.param2 or 0
			end

			minetest.set_node(pos2, {name = nod, param2 = def})
		end

		particle_effect(pos2)
	end
end

-- global functions

-- add to sapling list
-- {sapling node, schematic or function name, "soil"|"sand"|specific_node}
--e.g. {"default:sapling", default.grow_new_apple_tree, "soil"}

function bonemeal:add_sapling(list)

	for n = 1, #list do
		saplings[#saplings + 1] = list[n]
	end
end

-- add to crop list to force grow
-- {crop name start_, growth steps, seed node (if required)}
-- e.g. {"farming:wheat_", 8, "farming:seed_wheat"}
function bonemeal:add_crop(list)

	for n = 1, #list do
		crops[#crops + 1] = list[n]
	end
end

-- add grass and flower/plant decoration for specific dirt types
--  {dirt_node, {grass_nodes}, {flower_nodes}
-- e.g. {"default:dirt_with_dry_grass", dry_grass, flowers}
-- if an entry already exists for a given dirt type, it will add new entries and all empty
-- entries, allowing to both add decorations and decrease their frequency.
function bonemeal:add_deco(list)

	for l = 1, #list do

		for n = 1, #deco do

			-- update existing entry
			if list[l][1] == deco[n][1] then

				-- adding grass types
				for _, extra in pairs(list[l][2]) do

					if extra ~= "" then

						for _, entry in pairs(deco[n][2]) do

							if extra == entry then
								extra = false
								break
							end
						end
					end

					if extra then
						deco[n][2][#deco[n][2] + 1] = extra
					end
				end

				-- adding decoration types
				for _, extra in ipairs(list[l][3]) do

					if extra ~= "" then

						for __, entry in pairs(deco[n][3]) do

							if extra == entry then
								extra = false
								break
							end
						end
					end

					if extra then
						deco[n][3][#deco[n][3] + 1] = extra
					end
				end

				list[l] = false
				break
			end
		end

		if list[l] then
			deco[#deco + 1] = list[l]
		end
	end
end

-- definitively set a decration scheme
-- this function will either add a new entry as is, or replace the existing one
function bonemeal:set_deco(list)

	for l = 1, #list do

		for n = 1, #deco do

			-- replace existing entry
			if list[l][1] == deco[n][1] then
				deco[n][2] = list[l][2]
				deco[n][3] = list[l][3]
				list[l] = false
				break
			end
		end

		if list[l] then
			deco[#deco + 1] = list[l]
		end
	end
end

-- global on_use function for bonemeal
function bonemeal:on_use(pos, strength, node)

	-- get node pointed at
	local node = node or minetest.get_node(pos)

	-- return if nothing there
	if node.name == "ignore" then
		return
	end

	-- make sure strength is between 1 and 4
	strength = strength or 1
	strength = max(strength, 1)
	strength = min(strength, 4)

	-- papyrus and cactus
	if node.name == "default:papyrus" then

		default.grow_papyrus(pos, node)
		particle_effect(pos)
		return

	elseif node.name == "default:cactus" then

		default.grow_cactus(pos, node)
		particle_effect(pos)
		return
	end

	-- grow grass and flowers
	if minetest.get_item_group(node.name, "soil") > 0
	or minetest.get_item_group(node.name, "sand") > 0
	or minetest.get_item_group(node.name, "can_bonemeal") > 0 then
		check_soil(pos, node.name, strength)
		return
	end

	-- light check depending on strength (strength of 4 = no light needed)
	if (minetest.get_node_light(pos) or 0) < (12 - (strength * 3)) then
		return
	end

	-- check for tree growth if pointing at sapling
	if minetest.get_item_group(node.name, "sapling") > 0
	and random(5 - strength) == 1 then
		check_sapling(pos, node.name)
		return
	end

	-- check for crop growth
	check_crops(pos, node.name, strength)
end

--
-- items
--

-- mulch (strength 1)
minetest.register_craftitem("bonemeal:mulch", {
	description = S("Mulch"),
	inventory_image = "bonemeal_mulch.png",

	on_use = function(itemstack, user, pointed_thing)

		-- did we point at a node?
		if pointed_thing.type ~= "node" then
			return
		end

		-- is area protected?
		if minetest.is_protected(pointed_thing.under, user:get_player_name()) then
			return
		end

		-- take item if not in creative
		if not is_creative(user:get_player_name()) then
			itemstack:take_item()
		end

		-- call global on_use function with strength of 1
		bonemeal:on_use(pointed_thing.under, 1)

		return itemstack
	end
})

-- bonemeal (strength 2)
minetest.register_craftitem("bonemeal:bonemeal", {
	description = S("Bone Meal"),
	inventory_image = "bonemeal_item.png",

	on_use = function(itemstack, user, pointed_thing)

		-- did we point at a node?
		if pointed_thing.type ~= "node" then
			return
		end

		-- is area protected?
		if minetest.is_protected(pointed_thing.under, user:get_player_name()) then
			return
		end

		-- take item if not in creative
		if not is_creative(user:get_player_name()) then
			itemstack:take_item()
		end

		-- call global on_use function with strength of 2
		bonemeal:on_use(pointed_thing.under, 2)

		return itemstack
	end
})

-- fertiliser (strength 3)
minetest.register_craftitem("bonemeal:fertiliser", {
	description = S("Fertiliser"),
	inventory_image = "bonemeal_fertiliser.png",

	on_use = function(itemstack, user, pointed_thing)

		-- did we point at a node?
		if pointed_thing.type ~= "node" then
			return
		end

		-- is area protected?
		if minetest.is_protected(pointed_thing.under, user:get_player_name()) then
			return
		end

		-- take item if not in creative
		if not is_creative(user:get_player_name()) then
			itemstack:take_item()
		end

		-- call global on_use function with strength of 3
		bonemeal:on_use(pointed_thing.under, 3)

		return itemstack
	end
})

-- bone
minetest.register_craftitem("bonemeal:bone", {
	description = S("Bone"),
	inventory_image = "bonemeal_bone.png",
	groups = {bone = 1}
})

-- gelatin powder
minetest.register_craftitem("bonemeal:gelatin_powder", {
	description = S("Gelatin Powder"),
	inventory_image = "bonemeal_gelatin_powder.png",
	groups = {food_gelatin = 1, flammable = 2}
})

--
-- crafting recipes
--

-- gelatin powder
minetest.register_craft({
	output = "bonemeal:gelatin_powder 4",
	recipe = {
		{"group:bone", "group:bone", "group:bone"},
		{"bucket:bucket_water", "bucket:bucket_water", "bucket:bucket_water"},
		{"bucket:bucket_water", "default:torch", "bucket:bucket_water"},
	},
	replacements = {
		{"bucket:bucket_water", "bucket:bucket_empty 5"},
	}
})

-- bonemeal (from bone)
minetest.register_craft({
	type = "shapeless",
	output = "bonemeal:bonemeal 2",
	recipe = {"group:bone"}
})

-- bonemeal (from player bones)
minetest.register_craft({
	type = "shapeless",
	output = "bonemeal:bonemeal 4",
	recipe = {"bones:bones"}
})

-- bonemeal (from coral skeleton)
minetest.register_craft({
	type = "shapeless",
	output = "bonemeal:bonemeal 2",
	recipe = {"default:coral_skeleton"}
})

-- mulch
minetest.register_craft({
	type = "shapeless",
	output = "bonemeal:mulch 4",
	recipe = {
		"group:tree", "group:leaves", "group:leaves",
		"group:leaves", "group:leaves", "group:leaves",
		"group:leaves", "group:leaves", "group:leaves"
	}
})

-- fertiliser
minetest.register_craft({
	type = "shapeless",
	output = "bonemeal:fertiliser 2",
	recipe = {"bonemeal:bonemeal", "bonemeal:mulch"}
})

-- add bones to dirt
minetest.override_item("default:dirt", {
	drop = {
		max_items = 1,
		items = {
			{
				items = {"bonemeal:bone"},
				rarity = 40
			},
			{
				items = {"default:dirt"}
			}
		}
	}
})

-- add support for other mods
dofile(path .. "/mods.lua")
dofile(path .. "/lucky_block.lua")

print (S("[MOD] bonemeal loaded"))


code:
-- Fallback functions for when `intllib` is not installed.
-- Code released under Unlicense <http://unlicense.org>.

-- Get the latest version of this file at:
--   https://raw.githubusercontent.com/minetest-mods/intllib/master/lib/intllib.lua

local function format(str, ...)
	local args = { ... }
	local function repl(escape, open, num, close)
		if escape == "" then
			local replacement = tostring(args[tonumber(num)])
			if open == "" then
				replacement = replacement..close
			end
			return replacement
		else
			return "@"..open..num..close
		end
	end
	return (str:gsub("(@?)@(%(?)(%d+)(%)?)", repl))
end

local gettext, ngettext
if minetest.get_modpath("intllib") then
	if intllib.make_gettext_pair then
		-- New method using gettext.
		gettext, ngettext = intllib.make_gettext_pair()
	else
		-- Old method using text files.
		gettext = intllib.Getter()
	end
end

-- Fill in missing functions.

gettext = gettext or function(msgid, ...)
	return format(msgid, ...)
end

ngettext = ngettext or function(msgid, msgid_plural, n, ...)
	return format(n==1 and msgid or msgid_plural, ...)
end

return gettext, ngettext


code:
# textdomain: bonemeal
Mulch=Mantillo
Bone Meal=Comida de hueso
Fertiliser=Fertilizante
Bone=Hueso
[MOD] bonemeal loaded=[MOD] bonemeal cargado


code:
# textdomain: beds
Mulch=Paillis
Bone Meal=Poudre d'os
Fertiliser=Engrais
Bone=Os
[MOD] bonemeal loaded=[MOD] bonemeal chargé


code:
# textdomain: bonemeal
Mulch=Pacciame
Bone Meal=Pasto osseo
Fertiliser=Fertilizzante
Bone=Ossa
[MOD] bonemeal loaded=[MOD] bonemeal caricata


code:
# textdomain: bonemeal
Mulch=Мульча
Bone Meal=Костная Мука
Fertiliser=Удобрение
Bone=Кость
[MOD] bonemeal loaded=[MOD] костная мука загружена


code:
# init.lua

Mulch = Mantillo
Bone Meal = Comida de hueso
Fertiliser = Fertilizante
Bone = Hueso
[MOD] bonemeal loaded = [MOD] bonemeal cargado


code:
# init.lua

Mulch = Paillis
Bone Meal = Poudre d'os
Fertiliser = Engrais
Bone = Os
[MOD] bonemeal loaded = [MOD] bonemeal chargé


code:
# init.lua

Mulch = Pacciame
Bone Meal = Pasto osseo
Fertiliser = Fertilizzante
Bone = Ossa
[MOD] bonemeal loaded = [MOD] bonemeal caricata


code:
# init.lua

Mulch = Мульча
Bone Meal = Костная Мука
Fertiliser = Удобрение
Bone = Кость
[MOD] bonemeal loaded = [MOD] костная мука загружена


code:
# textdomain: bonemeal
Mulch =
Bone Meal =
Fertiliser =
Bone =
[MOD] bonemeal loaded =


code:
# init.lua

Mulch =
Bone Meal =
Fertiliser =
Bone =
[MOD] bonemeal loaded =


code:
-- add lucky blocks

local function growy(pos, player)

	local dpos = minetest.find_node_near(pos, 1, "group:soil")

	if dpos then
		bonemeal:on_use(dpos, 5)
	end
end

if minetest.get_modpath("lucky_block") then

	lucky_block:add_blocks({
		{"lig"},
		{"dro", {"bonemeal:mulch"}, 10},
		{"dro", {"bonemeal:bonemeal"}, 10},
		{"dro", {"bonemeal:fertiliser"}, 10},
		{"cus", growy},
		{"nod", "default:chest", 0, {
			{name = "bonemeal:mulch", max = 20},
			{name = "bonemeal:bonemeal", max = 15},
			{name = "bonemeal:fertiliser", max = 10},
		}},
	})

end


code: name = bonemeal