code:
## Torch Bomb

If you've ever been exploring a cave and found a deep, dark hole in the ground with no clue as to what was below, this is the mod for you.

Throw a torch grenade and it will explode on impact, firing up to 12 torches 25 meters away from the detonation point to embed themselves in whatever surfaces they hit.

For surveying a larger chamber three torch grenades can be combined into a single torch bomb. Place the bomb block and ignite it by punching it with a torch. Three seconds later it'll detonate and fire up to 42 torches 50 meters away from the detonation point. Torch bombs are falling nodes when lit, much like TNT, but you need to punch them a second time once they are lit to make them fall.

Three torch bombs can be combined into one mega torch bomb. A mega torch bomb can fire up to 162 torches with a range of 150 meters from the detonation point. It produces enough of a blast to destroy nodes within 3 meters, so take care. One of these is suitable for illuminating a very large cavern with walls and ceiling too distant to reach otherwise.

If you want to scatter torches from the center of a cavern to reach floor and ceiling more evenly a torch bomb can be upgraded to a torch rocket with the addition of more TNT. Torch rockets can have a fuse set up to 14 seconds, which at their acceleration of 1 meter per second squared will cause it to rise nearly 100 meters before detonating.

TNT is an optional dependency for this mod, but torch bombs don't have a crafting recipe (and don't produce a damaging blast) without the tnt mod enabled.


code:
-- The purpose of this class is to have something that can be passed into callbacks that
-- demand a "Player" object as a parameter and hopefully prevent the mods that have
-- registered with those callbacks from crashing on a nil dereference or bad function
-- call. This is not supposed to be a remotely functional thing, it's just supposed
-- to provide dummy methods and return values of the correct data type for anything that
-- might ignore the false "is_player()" return and go ahead and try to use this thing
-- anyway.

-- I'm trying to patch holes in bad mod programming, essentially. If a mod is so badly
-- programmed that it crashes anyway there's not a lot else I can do on my end of things.

local FakePlayer = {}
FakePlayer.__index = FakePlayer

local function return_value(x)
	return (function() return x end)
end

local function return_nil()
	return nil
end

local function return_empty_string()
	return ""
end

local function return_zero()
	return 0
end

local function return_empty_table()
	return {}
end

function FakePlayer.update(self, pos, player_name)
	self.is_fake_player = player_name
	self.get_pos = return_value(pos)
end

function FakePlayer.create(pos, player_name)
	local self = {}
	setmetatable(self, FakePlayer)

	self.is_fake_player = player_name

	-- ObjectRef
	self.get_pos = return_value(pos)
	self.set_pos = return_nil
	self.move_to = return_nil
	self.punch = return_nil
	self.right_click = return_nil
	self.get_hp = return_value(10)
	self.set_hp = return_nil
	self.get_inventory = return_nil -- returns an `InvRef`
	self.get_wield_list = return_empty_string
	self.get_wield_index = return_value(1)
	self.get_wielded_item = return_value(ItemStack(nil))
	self.set_wielded_item = return_value(false)
	self.set_armor_groups = return_nil
	self.get_armor_groups = return_empty_table
	self.set_animation = return_nil
	self.get_animation = return_nil -- a set of values, maybe important?
	self.set_attach = return_nil
	self.get_attach = return_nil
	self.set_detach = return_nil
	self.set_bone_position = return_nil
	self.get_bone_position = return_nil
	self.set_properties = return_nil
	self.get_properties = return_empty_table

	self.is_player = return_value(false)

	self.get_nametag_attributes = return_empty_table
	self.set_nametag_attributes = return_nil

	--LuaEntitySAO
	self.set_velocity = return_nil
	self.get_velocity = return_value({x=0,y=0,z=0})
	self.set_acceleration = return_nil
	self.get_acceleration = return_value({x=0,y=0,z=0})
	self.set_yaw = return_nil
	self.get_yaw = return_zero
	self.set_texture_mod = return_nil
	self.get_texture_mod = return_nil -- maybe important?
	self.set_sprite = return_nil
	--self.get_entity_name` (**Deprecated**: Will be removed in a future version)
	self.get_luaentity = return_nil

	-- Player object

	self.get_player_name = return_empty_string
	self.get_player_velocity = return_nil
	self.get_look_dir = return_value({x=0,y=1,z=0})
	self.get_look_horizontal = return_zero
	self.set_look_horizontal = return_nil
	self.get_look_vertical = return_zero
	self.set_look_vertical = return_nil

	--self.get_look_pitch`: pitch in radians - Deprecated as broken. Use `get_look_vertical`
	--self.get_look_yaw`: yaw in radians - Deprecated as broken. Use `get_look_horizontal`
	--self.set_look_pitch(radians)`: sets look pitch - Deprecated. Use `set_look_vertical`.
	--self.set_look_yaw(radians)`: sets look yaw - Deprecated. Use `set_look_horizontal`.
	self.get_breath = return_value(10)
	self.set_breath = return_nil
	self.get_attribute = return_nil
	self.set_attribute = return_nil

	self.set_inventory_formspec = return_nil
	self.get_inventory_formspec = return_empty_string
	self.get_player_control = return_value({jump=false, right=false, left=false, LMB=false, RMB=false, sneak=false, aux1=false, down=false, up=false} )
    self.get_player_control_bits = return_zero

	self.set_physics_override = return_nil
	self.get_physics_override = return_value({speed = 1, jump = 1, gravity = 1, sneak = true, sneak_glitch = false, new_move = true,})

	self.hud_add = return_nil
	self.hud_remove = return_nil
	self.hud_change = return_nil
	self.hud_get = return_nil -- possibly important return value?
	self.hud_set_flags = return_nil
	self.hud_get_flags = return_value({ hotbar=true, healthbar=true, crosshair=true, wielditem=true, breathbar=true, minimap=true })
	self.hud_set_hotbar_itemcount = return_nil
	self.hud_get_hotbar_itemcount = return_zero
	self.hud_set_hotbar_image = return_nil
	self.hud_get_hotbar_image = return_empty_string
	self.hud_set_hotbar_selected_image = return_nil
	self.hud_get_hotbar_selected_image = return_empty_string
	self.set_sky = return_nil
	self.get_sky = return_empty_table -- may need members on this table

	self.set_clouds = return_nil
	self.get_clouds = return_value({density = 0, color = "#fff0f0e5", ambient = "#000000", height = 120, thickness = 16, speed = {x=0, y=-2}})

	self.override_day_night_ratio = return_nil
	self.get_day_night_ratio = return_nil

	self.set_local_animation = return_nil
	self.get_local_animation = return_empty_table

	self.set_eye_offset = return_nil
	self.get_eye_offset = return_value({x=0,y=0,z=0},{x=0,y=0,z=0})

	return self
end

return FakePlayer

repo_name: korkut/MeseCraft

license: mit


code:
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local tnt_modpath = minetest.get_modpath("tnt")
local S = minetest.get_translator(modname)

local FakePlayer = dofile(modpath .. "/" .. "class_fakeplayer.lua")
local fakeplayer = FakePlayer.create({x=0,y=0,z=0}, "torch_bomb")

-- Default to enabled when in singleplayer
local enable_tnt = minetest.settings:get_bool("enable_tnt")
if enable_tnt == nil then
	enable_tnt = minetest.is_singleplayer()
end

local grenade_range = tonumber(minetest.settings:get("torch_bomb_grenade_range")) or 25
local bomb_range = tonumber(minetest.settings:get("torch_bomb_range")) or 50
local mega_bomb_range = tonumber(minetest.settings:get("torch_bomb_mega_range")) or 150
local torch_item = minetest.settings:get("torch_bomb_torch_item") or "default:torch"

local enable_rockets = minetest.settings:get_bool("torch_bomb_enable_rockets", true)
local rocket_max_fuse = tonumber(minetest.settings:get("torch_bomb_max_fuse")) or 14 -- 14 seconds at 1 m/s^2 is 98 meters traveled
local default_fuse = rocket_max_fuse/2 -- 7 seconds at 1 m/s^2 is 24.5 meters traveled

local enable_grenade = minetest.settings:get_bool("torch_bomb_enable_grenades", true)

-- 12 torches grenade
local ico1 = {
	vector.new(0.000000,	-1.000000,	0.000000),
	vector.new(0.723600,	-0.447215,	0.525720),
	vector.new(-0.276385,	-0.447215,	0.850640),
	vector.new(-0.894425,	-0.447215,	0.000000),
	vector.new(-0.276385,	-0.447215,	-0.850640),
	vector.new(0.723600,	-0.447215,	-0.525720),
	vector.new(0.276385,	0.447215,	0.850640),
	vector.new(-0.723600,	0.447215,	0.525720),
	vector.new(-0.723600,	0.447215,	-0.525720),
	vector.new(0.276385,	0.447215,	-0.850640),
	vector.new(0.894425,	0.447215,	0.000000),
	vector.new(0.000000,	1.000000,	0.000000),
}
-- Pre-multiply the range into these unit vectors
for i, pos in ipairs(ico1) do
	ico1[i] = vector.multiply(pos, grenade_range)
end

-- 42 torches, 1*bomb_range
local ico2 = {
	vector.new(0.000000,	-1.000000,	0.000000),
	vector.new(0.723607,	-0.447220,	0.525725),
	vector.new(-0.276388,	-0.447220,	0.850649),
	vector.new(-0.894426,	-0.447216,	0.000000),
	vector.new(-0.276388,	-0.447220,	-0.850649),
	vector.new(0.723607,	-0.447220,	-0.525725),
	vector.new(0.276388,	0.447220,	0.850649),
	vector.new(-0.723607,	0.447220,	0.525725),
	vector.new(-0.723607,	0.447220,	-0.525725),
	vector.new(0.276388,	0.447220,	-0.850649),
	vector.new(0.894426,	0.447216,	0.000000),
	vector.new(0.000000,	1.000000,	0.000000),
	vector.new(-0.162456,	-0.850654,	0.499995),
	vector.new(0.425323,	-0.850654,	0.309011),
	vector.new(0.262869,	-0.525738,	0.809012),
	vector.new(0.850648,	-0.525736,	0.000000),
	vector.new(0.425323,	-0.850654,	-0.309011),
	vector.new(-0.525730,	-0.850652,	0.000000),
	vector.new(-0.688189,	-0.525736,	0.499997),
	vector.new(-0.162456,	-0.850654,	-0.499995),
	vector.new(-0.688189,	-0.525736,	-0.499997),
	vector.new(0.262869,	-0.525738,	-0.809012),
	vector.new(0.951058,	0.000000,	0.309013),
	vector.new(0.951058,	0.000000,	-0.309013),
	vector.new(0.000000,	0.000000,	1.000000),
	vector.new(0.587786,	0.000000,	0.809017),
	vector.new(-0.951058,	0.000000,	0.309013),
	vector.new(-0.587786,	0.000000,	0.809017),
	vector.new(-0.587786,	0.000000,	-0.809017),
	vector.new(-0.951058,	0.000000,	-0.309013),
	vector.new(0.587786,	0.000000,	-0.809017),
	vector.new(0.000000,	0.000000,	-1.000000),
	vector.new(0.688189,	0.525736,	0.499997),
	vector.new(-0.262869,	0.525738,	0.809012),
	vector.new(-0.850648,	0.525736,	0.000000),
	vector.new(-0.262869,	0.525738,	-0.809012),
	vector.new(0.688189,	0.525736,	-0.499997),
	vector.new(0.162456,	0.850654,	0.499995),
	vector.new(0.525730,	0.850652,	0.000000),
	vector.new(-0.425323,	0.850654,	0.309011),
	vector.new(-0.425323,	0.850654,	-0.309011),
	vector.new(0.162456,	0.850654,	-0.499995),
}
-- Pre-multiply the range into these unit vectors
for i, pos in ipairs(ico2) do
	ico2[i] = vector.multiply(pos, bomb_range)
end

-- 162 torches, 3* bomb_range
local ico3 = {
	vector.new(0.000000,	-1.000000,	0.000000),
	vector.new(0.723607,	-0.447220,	0.525725),
	vector.new(-0.276388,	-0.447220,	0.850649),
	vector.new(-0.894426,	-0.447216,	0.000000),
	vector.new(-0.276388,	-0.447220,	-0.850649),
	vector.new(0.723607,	-0.447220,	-0.525725),
	vector.new(0.276388,	0.447220,	0.850649),
	vector.new(-0.723607,	0.447220,	0.525725),
	vector.new(-0.723607,	0.447220,	-0.525725),
	vector.new(0.276388,	0.447220,	-0.850649),
	vector.new(0.894426,	0.447216,	0.000000),
	vector.new(0.000000,	1.000000,	0.000000),
	vector.new(-0.232822,	-0.657519,	0.716563),
	vector.new(-0.162456,	-0.850654,	0.499995),
	vector.new(-0.077607,	-0.967950,	0.238853),
	vector.new(0.203181,	-0.967950,	0.147618),
	vector.new(0.425323,	-0.850654,	0.309011),
	vector.new(0.609547,	-0.657519,	0.442856),
	vector.new(0.531941,	-0.502302,	0.681712),
	vector.new(0.262869,	-0.525738,	0.809012),
	vector.new(-0.029639,	-0.502302,	0.864184),
	vector.new(0.812729,	-0.502301,	-0.295238),
	vector.new(0.850648,	-0.525736,	0.000000),
	vector.new(0.812729,	-0.502301,	0.295238),
	vector.new(0.203181,	-0.967950,	-0.147618),
	vector.new(0.425323,	-0.850654,	-0.309011),
	vector.new(0.609547,	-0.657519,	-0.442856),
	vector.new(-0.753442,	-0.657515,	0.000000),
	vector.new(-0.525730,	-0.850652,	0.000000),
	vector.new(-0.251147,	-0.967949,	0.000000),
	vector.new(-0.483971,	-0.502302,	0.716565),
	vector.new(-0.688189,	-0.525736,	0.499997),
	vector.new(-0.831051,	-0.502299,	0.238853),
	vector.new(-0.232822,	-0.657519,	-0.716563),
	vector.new(-0.162456,	-0.850654,	-0.499995),
	vector.new(-0.077607,	-0.967950,	-0.238853),
	vector.new(-0.831051,	-0.502299,	-0.238853),
	vector.new(-0.688189,	-0.525736,	-0.499997),
	vector.new(-0.483971,	-0.502302,	-0.716565),
	vector.new(-0.029639,	-0.502302,	-0.864184),
	vector.new(0.262869,	-0.525738,	-0.809012),
	vector.new(0.531941,	-0.502302,	-0.681712),
	vector.new(0.956626,	0.251149,	0.147618),
	vector.new(0.951058,	-0.000000,	0.309013),
	vector.new(0.860698,	-0.251151,	0.442858),
	vector.new(0.860698,	-0.251151,	-0.442858),
	vector.new(0.951058,	0.000000,	-0.309013),
	vector.new(0.956626,	0.251149,	-0.147618),
	vector.new(0.155215,	0.251152,	0.955422),
	vector.new(0.000000,	-0.000000,	1.000000),
	vector.new(-0.155215,	-0.251152,	0.955422),
	vector.new(0.687159,	-0.251152,	0.681715),
	vector.new(0.587786,	0.000000,	0.809017),
	vector.new(0.436007,	0.251152,	0.864188),
	vector.new(-0.860698,	0.251151,	0.442858),
	vector.new(-0.951058,	-0.000000,	0.309013),
	vector.new(-0.956626,	-0.251149,	0.147618),
	vector.new(-0.436007,	-0.251152,	0.864188),
	vector.new(-0.587786,	0.000000,	0.809017),
	vector.new(-0.687159,	0.251152,	0.681715),
	vector.new(-0.687159,	0.251152,	-0.681715),
	vector.new(-0.587786,	-0.000000,	-0.809017),
	vector.new(-0.436007,	-0.251152,	-0.864188),
	vector.new(-0.956626,	-0.251149,	-0.147618),
	vector.new(-0.951058,	0.000000,	-0.309013),
	vector.new(-0.860698,	0.251151,	-0.442858),
	vector.new(0.436007,	0.251152,	-0.864188),
	vector.new(0.587786,	-0.000000,	-0.809017),
	vector.new(0.687159,	-0.251152,	-0.681715),
	vector.new(-0.155215,	-0.251152,	-0.955422),
	vector.new(0.000000,	0.000000,	-1.000000),
	vector.new(0.155215,	0.251152,	-0.955422),
	vector.new(0.831051,	0.502299,	0.238853),
	vector.new(0.688189,	0.525736,	0.499997),
	vector.new(0.483971,	0.502302,	0.716565),
	vector.new(0.029639,	0.502302,	0.864184),
	vector.new(-0.262869,	0.525738,	0.809012),
	vector.new(-0.531941,	0.502302,	0.681712),
	vector.new(-0.812729,	0.502301,	0.295238),
	vector.new(-0.850648,	0.525736,	0.000000),
	vector.new(-0.812729,	0.502301,	-0.295238),
	vector.new(-0.531941,	0.502302,	-0.681712),
	vector.new(-0.262869,	0.525738,	-0.809012),
	vector.new(0.029639,	0.502302,	-0.864184),
	vector.new(0.483971,	0.502302,	-0.716565),
	vector.new(0.688189,	0.525736,	-0.499997),
	vector.new(0.831051,	0.502299,	-0.238853),
	vector.new(0.077607,	0.967950,	0.238853),
	vector.new(0.162456,	0.850654,	0.499995),
	vector.new(0.232822,	0.657519,	0.716563),
	vector.new(0.753442,	0.657515,	0.000000),
	vector.new(0.525730,	0.850652,	0.000000),
	vector.new(0.251147,	0.967949,	0.000000),
	vector.new(-0.203181,	0.967950,	0.147618),
	vector.new(-0.425323,	0.850654,	0.309011),
	vector.new(-0.609547,	0.657519,	0.442856),
	vector.new(-0.203181,	0.967950,	-0.147618),
	vector.new(-0.425323,	0.850654,	-0.309011),
	vector.new(-0.609547,	0.657519,	-0.442856),
	vector.new(0.077607,	0.967950,	-0.238853),
	vector.new(0.162456,	0.850654,	-0.499995),
	vector.new(0.232822,	0.657519,	-0.716563),
	vector.new(0.361800,	0.894429,	-0.262863),
	vector.new(0.638194,	0.723610,	-0.262864),
	vector.new(0.447209,	0.723612,	-0.525728),
	vector.new(-0.138197,	0.894430,	-0.425319),
	vector.new(-0.052790,	0.723612,	-0.688185),
	vector.new(-0.361804,	0.723612,	-0.587778),
	vector.new(-0.447210,	0.894429,	0.000000),
	vector.new(-0.670817,	0.723611,	-0.162457),
	vector.new(-0.670817,	0.723611,	0.162457),
	vector.new(-0.138197,	0.894430,	0.425319),
	vector.new(-0.361804,	0.723612,	0.587778),
	vector.new(-0.052790,	0.723612,	0.688185),
	vector.new(0.361800,	0.894429,	0.262863),
	vector.new(0.447209,	0.723612,	0.525728),
	vector.new(0.638194,	0.723610,	0.262864),
	vector.new(0.861804,	0.276396,	-0.425322),
	vector.new(0.809019,	0.000000,	-0.587782),
	vector.new(0.670821,	0.276397,	-0.688189),
	vector.new(-0.138199,	0.276397,	-0.951055),
	vector.new(-0.309016,	-0.000000,	-0.951057),
	vector.new(-0.447215,	0.276397,	-0.850649),
	vector.new(-0.947213,	0.276396,	-0.162458),
	vector.new(-1.000000,	0.000001,	0.000000),
	vector.new(-0.947213,	0.276397,	0.162458),
	vector.new(-0.447216,	0.276397,	0.850648),
	vector.new(-0.309017,	-0.000001,	0.951056),
	vector.new(-0.138199,	0.276397,	0.951055),
	vector.new(0.670820,	0.276396,	0.688190),
	vector.new(0.809019,	-0.000002,	0.587783),
	vector.new(0.861804,	0.276394,	0.425323),
	vector.new(0.309017,	-0.000000,	-0.951056),
	vector.new(0.447216,	-0.276398,	-0.850648),
	vector.new(0.138199,	-0.276398,	-0.951055),
	vector.new(-0.809018,	-0.000000,	-0.587783),
	vector.new(-0.670819,	-0.276397,	-0.688191),
	vector.new(-0.861803,	-0.276396,	-0.425324),
	vector.new(-0.809018,	0.000000,	0.587783),
	vector.new(-0.861803,	-0.276396,	0.425324),
	vector.new(-0.670819,	-0.276397,	0.688191),
	vector.new(0.309017,	0.000000,	0.951056),
	vector.new(0.138199,	-0.276398,	0.951055),
	vector.new(0.447216,	-0.276398,	0.850648),
	vector.new(1.000000,	0.000000,	0.000000),
	vector.new(0.947213,	-0.276396,	0.162458),
	vector.new(0.947213,	-0.276396,	-0.162458),
	vector.new(0.361803,	-0.723612,	-0.587779),
	vector.new(0.138197,	-0.894429,	-0.425321),
	vector.new(0.052789,	-0.723611,	-0.688186),
	vector.new(-0.447211,	-0.723612,	-0.525727),
	vector.new(-0.361801,	-0.894429,	-0.262863),
	vector.new(-0.638195,	-0.723609,	-0.262863),
	vector.new(-0.638195,	-0.723609,	0.262864),
	vector.new(-0.361801,	-0.894428,	0.262864),
	vector.new(-0.447211,	-0.723610,	0.525729),
	vector.new(0.670817,	-0.723611,	-0.162457),
	vector.new(0.670818,	-0.723610,	0.162458),
	vector.new(0.447211,	-0.894428,	0.000001),
	vector.new(0.052790,	-0.723612,	0.688185),
	vector.new(0.138199,	-0.894429,	0.425321),
	vector.new(0.361805,	-0.723611,	0.587779),
}
-- Pre-multiply the range into these unit vectors
for i, pos in ipairs(ico3) do
	ico3[i] = vector.multiply(pos, mega_bomb_range)
end

local function find_target(raycast)
	local next_pointed = raycast:next()
	while next_pointed do
		local under_pos = next_pointed.under
		local under_node = minetest.get_node(under_pos)
		local under_def = minetest.registered_nodes[under_node.name]
		local above_pos = next_pointed.above
		local above_node = minetest.get_node(above_pos)
		local above_def = minetest.registered_nodes[above_node.name]

		if above_def.buildable_to and not under_def.buildable_to then
			return next_pointed
		end

		next_pointed  = raycast:next(next_pointed)
	end
end

local torch_def_on_place
minetest.after(0, function()
	torch_def_on_place = minetest.registered_nodes[torch_item].on_place
end)

local function kerblam(pos, placer, dirs, min_range)
	pos = vector.round(pos)
	local targets = {}
	for _, pos2 in ipairs(dirs) do
		local raycast = minetest.raycast(pos, vector.add(pos, pos2), false, true)
		local target_pointed = find_target(raycast)
		if target_pointed then
			if vector.distance(pos, target_pointed.above) > min_range then
				table.insert(targets, target_pointed)
			end
		end
	end

	if not placer then
		placer = fakeplayer
		fakeplayer:update(pos, "torch_bomb")
	end

	minetest.log("action", placer:get_player_name() .. " detonated a torch bomb at " ..
		minetest.pos_to_string(pos) .. " and placed " .. #targets .. " torches.")

	for _, target in ipairs(targets) do
		if minetest.get_item_group(minetest.get_node(target.above).name, "torch") == 0 then -- TODO remove this check after culling close-together targets
			torch_def_on_place(ItemStack(torch_item), placer, target)
			local target_pos = target.above
			local dir_back = vector.normalize(vector.subtract(pos, target_pos))
			local vel_back = vector.multiply(dir_back, 10)
			minetest.add_particlespawner({
				amount = math.random(1,6),
				time = 0.1,
				minpos = target_pos,
				maxpos = target_pos,
				minvel = vector.subtract(dir_back, 2),
				maxvel = vector.add(dir_back, 2),
				minacc = {x=0, y=-9, z=0},
				maxacc = {x=0, y=-9, z=0},
				minexptime = 1,
				maxexptime = 2,
				minsize = 1,
				maxsize = 2,
				collisiondetection = true,
				collision_removal = false,
				texture = "torch_bomb_shard.png",
			})
		end
	end
end

local player_setting_fuse_at = {}

local function rocket_formspec(fuse_length)
	return "formspec_version[2]"..
		"size[4,2]"..
		"label[0.25,0.25;" .. S("Rocket accelerates at 1 m/s^2.\nFuse duration from 1 to @1 seconds:", rocket_max_fuse) .. "]"..
		"field[0.75,1;1,0.5;seconds;;"..fuse_length.."]"..
		"button_exit[2.5,1;0.5,0.5;set;"..S("Set").."]"
end

local function rocket_effects(obj, fuse)
	minetest.add_particlespawner({
		amount = 100*fuse,
		time = fuse,
		minpos = {x=0, y=0, z=0},
		maxpos = {x=0, y=0, z=0},
		minvel = {x=-1, y=-10, z=-1},
		maxvel = {x=1, y=-12, z=1},
		minacc = {x=0, y=0, z=0},
		maxacc = {x=0, y=0, z=0},
		minexptime = 1,
		maxexptime = 1,
		minsize = 3,
		maxsize = 3,
		collisiondetection = true,
		collision_removal = true,
		attached = obj,
		texture = "smoke_puff.png",
		glow = 8
	})
end

if enable_rockets then
	minetest.register_on_player_receive_fields(function(player, formname, fields)
		if formname == "torch_bomb:torch_rocket" then
			local player_name = player:get_player_name()
			local pos = player_setting_fuse_at[player_name]
			local seconds = tonumber(fields.seconds or "")

			if not pos or not seconds then
				player_setting_fuse_at[player_name] = nil
				return
			end
			local node = minetest.get_node(pos)
			if minetest.get_item_group(node.name, "torch_bomb_rocket") == 0 then
				player_setting_fuse_at[player_name] = nil
				return
			end
			player_setting_fuse_at[player_name] = nil
			seconds = math.max(math.min(seconds, rocket_max_fuse), 1)
			local meta = minetest.get_meta(pos)
			meta:set_string("fuse", seconds)
		end
	end)
end

local function register_torch_bomb(name, desc, dirs, min_range, blast_radius, texture)

	local side_texture = "torch_bomb_side_base.png^"..texture

	minetest.register_node("torch_bomb:" .. name, {
		description = desc,
		drawtype = "normal",
		tiles = {"torch_bomb_top.png", "torch_bomb_bottom.png", side_texture},
		paramtype = "light",
		paramtype2 = "facedir",

		groups = {tnt = 1, oddly_breakable_by_hand = 1},

		on_punch = function(pos, node, puncher)
			if puncher:get_wielded_item():get_name() == "default:torch" then
				minetest.set_node(pos, {name = "torch_bomb:"..name.."_burning"})
				minetest.get_meta(pos):set_string("torch_bomb_ignitor", puncher:get_player_name())
				minetest.log("action", puncher:get_player_name() .. " ignites " .. node.name .. " at " ..
					minetest.pos_to_string(pos))
			end
		end,

		on_ignite = function(pos) -- used by TNT mod
			minetest.set_node(pos, {name = "torch_bomb:"..name.."_burning"})
		end,
	})

	minetest.register_node("torch_bomb:"..name.."_burning", {
		description = desc,
		drawtype = "normal",  -- See "Node drawtypes"
		tiles = {{
				name = "torch_bomb_top_burning_animated.png",
				animation = {
					type = "vertical_frames",
					aspect_w = 16,
					aspect_h = 16,
					length = 1,
				}
			},
			"torch_bomb_bottom.png", side_texture},
		groups = {falling_node = 1, not_in_creative_inventory = 1},
		paramtype = "light",
		paramtype2 = "facedir",
		light_source = 6,

		on_construct = function(pos)
			if tnt_modpath then
				minetest.sound_play("tnt_ignite", {pos = pos})
			end
			minetest.get_node_timer(pos):start(3)
		end,

		on_timer = function(pos, elapsed)
			local ignitor_name = minetest.get_meta(pos):get("torch_bomb_ignitor")
			local puncher
			if ignitor_name then
				puncher = minetest.get_player_by_name(ignitor_name)
			end
			minetest.set_node(pos, {name="air"})
			if tnt_modpath then
				tnt.boom(pos, {radius=blast_radius, damage_radius=blast_radius+3})
			end
			kerblam(pos, puncher, dirs, min_range)
		end,
	})

	if not enable_rockets then
		return
	end

	local rocket_bottom_texture = "torch_bomb_bottom.png^torch_bomb_rocket_bottom.png"
	local rocket_side_texture = side_texture .. "^torch_bomb_rocket_side.png"

	local function entity_detonate(player_name, target)
		--minetest.chat_send_all("entity detonate " .. (player_name or "") .. " " .. minetest.pos_to_string(target))
		local player
		if player_name then
			player = minetest.get_player_by_name(player_name)
		end
		if tnt_modpath then
			tnt.boom(target, {radius=blast_radius, damage_radius=blast_radius+3})
		end
		kerblam(target, player, dirs, min_range)
	end

	minetest.register_entity("torch_bomb:"..name.."_rocket_entity", {
		initial_properties = {
			physical = false,
			visual = "cube",
			--visual_size = {x=0.5, y=0.5},
			textures = {"torch_bomb_top.png", rocket_bottom_texture .. "^torch_bomb_rocket_bottom_lit.png",
				rocket_side_texture, rocket_side_texture, rocket_side_texture, rocket_side_texture},
			collisionbox = {0,0,0,0,0,0},
			glow = 8,
		},

		on_activate = function(self, staticdata, dtime_s)
			if staticdata == "detonated" then
				self.object:remove()
			end
		end,

		get_staticdata = function(self)
			local target = self.target
			if target then
				-- we're unloading an active rocket, skip ahead to detonation point
				local pos = self.object:get_pos()
				local raycast = minetest.raycast(pos, target, false, true)
				local target_pointed = find_target(raycast)
				if target_pointed then
					target = target_pointed.above
				end
				minetest.after(self.fuse, entity_detonate, self.player_name, target)
				return "detonated"
			end
		end,

		on_step = function(self, dtime)
			local object = self.object
			local lastpos = self.lastpos

			local pos = object:get_pos()
			local node = minetest.get_node(pos)
			local luaentity = object:get_luaentity()
			local old_fuse = luaentity.fuse or -1
			local new_fuse = old_fuse - dtime
			luaentity.fuse = new_fuse
			if math.floor(old_fuse) ~= math.floor(new_fuse) then
				-- should happen once per second
				minetest.sound_play({name="tnt_gunpowder_burning"}, {
					object = object,
					gain = 1.0,
					max_hear_distance = 32,
				})
			end

			if lastpos and (node.name ~= "air" or luaentity.fuse < 0) then
				lastpos = vector.round(lastpos)
				local player_name = luaentity.player_name
				object:remove()
				entity_detonate(player_name, lastpos)
			end
			self.lastpos={x=pos.x, y=pos.y, z=pos.z}
		end,
	})

	minetest.register_node("torch_bomb:"..name.."_rocket", {
		description = S("@1 Rocket", desc),
		drawtype = "normal",
		tiles = {"torch_bomb_top.png", rocket_bottom_texture, rocket_side_texture},
		paramtype = "light",
		paramtype2 = "facedir",

		groups = {tnt = 1, oddly_breakable_by_hand = 1, torch_bomb_rocket = 1},

		on_punch = function(pos, node, puncher)
			if puncher:get_wielded_item():get_name() == "default:torch" then
				local fuse = minetest.get_meta(pos):get("fuse")
				minetest.set_node(pos, {name = "torch_bomb:"..name.."_rocket_burning"})
				local meta = minetest.get_meta(pos)
				meta:set_string("torch_bomb_ignitor", puncher:get_player_name())
				meta:set_string("fuse", fuse)
				minetest.log("action", puncher:get_player_name() .. " ignites " .. node.name .. " at " ..
					minetest.pos_to_string(pos))
			end
		end,

		on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
			local meta = minetest.get_meta(pos)
			local fuse_length = tonumber(meta:get_string("fuse")) or default_fuse
			local player_name = clicker:get_player_name()
			player_setting_fuse_at[player_name] = pos
			minetest.show_formspec(player_name, "torch_bomb:torch_rocket", rocket_formspec(fuse_length))
		end,

		on_ignite = function(pos) -- used by TNT mod
			local fuse = minetest.get_meta(pos):get("fuse")
			minetest.set_node(pos, {name = "torch_bomb:"..name.."_rocket_burning"})
			minetest.get_meta(pos):set_string("fuse", fuse)
		end,
	})

	minetest.register_node("torch_bomb:"..name.."_rocket_burning", {
		description = S("@1 Rocket", desc),
		drawtype = "normal",
		tiles = {{
				name = "torch_bomb_top_burning_animated.png",
				animation = {
					type = "vertical_frames",
					aspect_w = 16,
					aspect_h = 16,
					length = 1,
				}
			},
			rocket_bottom_texture, rocket_side_texture},
		groups = {falling_node = 1, not_in_creative_inventory = 1},
		paramtype = "light",
		paramtype2 = "facedir",
		light_source = 6,

		on_construct = function(pos)
			if tnt_modpath then
				minetest.sound_play("tnt_ignite", {pos = pos})
			end
			minetest.get_node_timer(pos):start(3)
		end,

		on_timer = function(pos, elapsed)
			local meta = minetest.get_meta(pos)
			local ignitor_name = meta:get("torch_bomb_ignitor")
			local fuse = tonumber(meta:get_string("fuse")) or default_fuse
			minetest.set_node(pos, {name="air"})

			local obj = minetest.add_entity(pos, "torch_bomb:"..name.."_rocket_entity")
			obj:setacceleration({x=0, y=1, z=0})
			local lua_entity = obj:get_luaentity()
			lua_entity.player_name = ignitor_name
			lua_entity.fuse = fuse

			local range = 0.5 * fuse * fuse -- s = vi * t + (1/2)*a*t*t
			pos.y = pos.y + range
			lua_entity.target = pos
			rocket_effects(obj, fuse)
		end,
	})
end

register_torch_bomb("torch_bomb", S("Torch Bomb"), ico2, 5, 1, "torch_bomb_one_torch.png")
register_torch_bomb("mega_torch_bomb", S("Mega Torch Bomb"), ico3, 15, 3, "torch_bomb_three_torches.png")

-----------------------------------------------------------------------------------------------------------------
-- Throwable torch grenade

if enable_grenade then

	local throw_velocity = 20
	local gravity = {x=0, y=-9.81, z=0}

	minetest.register_craftitem("torch_bomb:torch_grenade", {
		description = S("Torch Grenade"),
		inventory_image = "torch_bomb_torch_grenade.png",
		on_use = function(itemstack, user, pointed_thing)
			local player_pos = user:get_pos()
			local obj = minetest.add_entity({x = player_pos.x, y = player_pos.y + 1.5, z = player_pos.z}, "torch_bomb:torch_grenade_entity")
			local dir = user:get_look_dir()
			obj:setvelocity(vector.multiply(dir, throw_velocity))
			obj:setacceleration(gravity)
			obj:setyaw(user:get_look_yaw()+math.pi)
			local lua_entity = obj:get_luaentity()
			lua_entity.player_name = user:get_player_name()

			minetest.sound_play({name="tnt_ignite"},
			{
				object = object,
				gain = 1.0,
				max_hear_distance = 32,
			})

			if not minetest.setting_getbool("creative_mode") and not minetest.check_player_privs(user, "creative") then
				itemstack:set_count(itemstack:get_count() - 1)
			end

			return itemstack
		end
	})

	minetest.register_entity("torch_bomb:torch_grenade_entity", {
		initial_properties = {
			physical = false,
			visual = "sprite",
			visual_size = {x=0.5, y=0.5},
			textures = {"torch_bomb_torch_grenade.png"},
			collisionbox = {0,0,0,0,0,0},
			glow = 8,
		},

		on_activate = function(self, staticdata, dtime_s)
			self.player_name = staticdata
		end,
		get_staticdata = function(self)
			return self.player_name
		end,

		on_step = function(self, dtime)
			local object = self.object
			local lastpos = self.lastpos

			local pos = object:get_pos()
			local node = minetest.get_node(pos)

			if lastpos ~= nil and node.name ~= "air" then
				lastpos = vector.round(lastpos)
				local luaentity = object:get_luaentity()
				local player_name = luaentity.player_name
				local player
				if player_name then
					player = minetest.get_player_by_name(player_name)
				end
				object:remove()
				if tnt_modpath then
					tnt.boom(lastpos, {radius=1, damage_radius=2})
				end
				kerblam(lastpos, player, ico1, 2)
			end
			self.lastpos={x=pos.x, y=pos.y, z=pos.z}
		end,
	})
end

----------------------------------------------------------------------
-- Crafting

if enable_tnt and tnt_modpath then
	minetest.register_craft({
		output = "torch_bomb:torch_grenade",
		recipe = {
			{'default:coalblock'},
			{'group:wood'},
			{'tnt:tnt_stick'},
		},
	})

	minetest.register_craft({
		output = "torch_bomb:torch_bomb",
		recipe = {
			{'default:coalblock', 'default:coalblock', 'default:coalblock'},
			{'group:wood', 'group:wood', 'group:wood'},
			{'tnt:tnt_stick', 'tnt:tnt_stick', 'tnt:tnt_stick'},
		},
	})

	minetest.register_craft({
		type = "shapeless",
		output = "torch_bomb:mega_torch_bomb",
		recipe = {"torch_bomb:torch_bomb", "torch_bomb:torch_bomb", "torch_bomb:torch_bomb"},
	})

	minetest.register_craft({
		type = "shapeless",
		output = "torch_bomb:torch_bomb 3",
		recipe = {"torch_bomb:mega_torch_bomb"},
	})

	if enable_grenade then

		minetest.register_craft({
			type = "shapeless",
			output = "torch_bomb:torch_bomb",
			recipe = {"torch_bomb:torch_grenade", "torch_bomb:torch_grenade", "torch_bomb:torch_grenade"},
		})

		minetest.register_craft({
			type = "shapeless",
			output = "torch_bomb:torch_grenade 3",
			recipe = {"torch_bomb:torch_bomb"},
		})
	end

	if enable_rockets then
		minetest.register_craft({
			type = "shapeless",
			output = "torch_bomb:torch_bomb_rocket",
			recipe = {"torch_bomb:torch_bomb", "tnt:tnt"},
		})

		minetest.register_craft({
			type = "shapeless",
			output = "torch_bomb:mega_torch_bomb_rocket",
			recipe = {"torch_bomb:mega_torch_bomb", "tnt:tnt"},
		})
	end
end


code:
@1 Rocket=
Mega Torch Bomb=
Rocket accelerates at 1 m/s^2.@nFuse duration from 1 to @1 seconds:=
Set=
Torch Bomb=
Torch Grenade=


code:
name = torch_bomb
description = Place torches throughout your entire surroundings with a torch bomb
depends = default
optional_depends = tnt


code:
torch_bomb_range (Max range of basic torch bomb) int 50
torch_bomb_mega_range (Max range of mega torch bomb) int 150
torch_bomb_torch_item (Item that torchbombs fire into walls) string default:torch

torch_bomb_enable_grenades (Enable torch grenades) bool true
torch_bomb_grenade_range (Max range of torch grenade) int 25
torch_bomb_enable_rockets (Enable rocket versions of torch bombs) bool true
# 14 seconds at 1 m/s^2 is 98 meters traveled
torch_bomb_max_fuse (Maximum fuse duration of rockets) int 14


code:
 _____             _       _____
|_   _|           | |     /  __ \
  | |_ __ __ _ ___| |__   | /  \/ __ _ _ __
  | | '__/ _` / __| '_ \  | |    / _` | '_ \
  | | | | (_| \__ \ | | | | \__/\ (_| | | | |
  \_/_|  \__,_|___/_| |_|  \____/\__,_|_| |_|

This mod adds a wooden trash can, and a dumpster to the game.  Right click it, put in your trash, and click the empty trash button.
You can also throw things in the wooden trash can by pressing "q" or throwing them out of your inventory.

Version: 0.2.2
License: MIT (see LICENSE.txt)

Dependencies:
default (found in minetest_game)

Please report bugs at the github issue tracker:
https://github.com/minetest-mods/trash_can/issues

Crafting:

Wooden trash can:
w = wood planks x = nothing

w|x|w
-----
w|x|w
-----
w|w|w

Dumpster:
i = iron ingot c = coal block g = dark green dye

c|c|c
-----
i|g|i
-----
i|i|i

Contributers:

Zeg9:
Made it so you can throw stuff in the trash can (by pressing "q")

Mossmanikin:
Made the nodeboxes for the dumpster, the textures for the wooden trash can, and the texture for the dumpster node.
(with some editing by me). (old)


code: This mod adds a wooden trash can, and a dumpster to the game.  Right click it, put in your trash, and click the empty trash button.


code:
--
-- Functions
--

local fdir_to_front = {
	{x=0, z=1},
	{x=1, z=0},
	{x=0, z=-1},
	{x=-1, z=0}
}
local function checkwall(pos)
	local fdir = minetest.get_node(pos).param2
	local second_node_x = pos.x + fdir_to_front[fdir + 1].x
	local second_node_z = pos.z + fdir_to_front[fdir + 1].z
	local second_node_pos = {x=second_node_x, y=pos.y, z=second_node_z}
	local second_node = minetest.get_node(second_node_pos)
	if not second_node or not minetest.registered_nodes[second_node.name]
	  or not minetest.registered_nodes[second_node.name].buildable_to then
		return true
	end

	return false
end

--
-- Nodeboxes
--

local trash_can_nodebox = {
	{-0.375, -0.5, 0.3125, 0.375, 0.5, 0.375},
	{0.3125, -0.5, -0.375, 0.375, 0.5, 0.375},
	{-0.375, -0.5, -0.375, 0.375, 0.5, -0.3125},
	{-0.375, -0.5, -0.375, -0.3125, 0.5, 0.375},
	{-0.3125, -0.5, -0.3125, 0.3125, -0.4375, 0.3125},
}

--
-- Node Registration
--

-- Normal Trash Can
minetest.register_node("trash_can:trash_can_wooden",{
	description = "Wooden Trash Can",
	drawtype="nodebox",
	paramtype = "light",
	tiles = {
		"trash_can_wooden_top.png",
		"trash_can_wooden_top.png",
		"trash_can_wooden.png"
	},
	node_box = {
		type = "fixed",
		fixed = trash_can_nodebox
	},
	groups = {
		snappy=1,
		choppy=2,
		oddly_breakable_by_hand=2,
		flammable=3
	},
	on_construct = function(pos)
		local meta = minetest.get_meta(pos)
		meta:set_string("formspec",
			"size[8,9]" ..
			"button[0,0;2,1;empty;Empty Trash]" ..
			"list[context;trashlist;3,1;2,3;]" ..
			"list[current_player;main;0,5;8,4;]"
		)
		meta:set_string("infotext", "Trash Can")
		local inv = meta:get_inventory()
		inv:set_size("main", 8*4)
		inv:set_size("trashlist", 2*3)
	end,
	can_dig = function(pos,player)
		local meta = minetest.get_meta(pos);
		local inv = meta:get_inventory()
				return inv:is_empty("main")
	end,
	on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
		minetest.log("action", player:get_player_name() ..
			" moves stuff in trash can at " .. minetest.pos_to_string(pos))
	end,
	on_metadata_inventory_put = function(pos, listname, index, stack, player)
		minetest.log("action", player:get_player_name() ..
			" moves stuff to trash can at " .. minetest.pos_to_string(pos))
	end,
	on_metadata_inventory_take = function(pos, listname, index, stack, player)
		minetest.log("action", player:get_player_name() ..
			" takes stuff from trash can at " .. minetest.pos_to_string(pos))
	end,
	on_receive_fields = function(pos, formname, fields, sender)
		if fields.empty then
			local meta = minetest.get_meta(pos)
			local inv = meta:get_inventory()
			inv:set_list("trashlist", {})
			minetest.sound_play("trash", {to_player=sender:get_player_name(), gain = 1.0})
			minetest.log("action", sender:get_player_name() ..
				" empties trash can at " .. minetest.pos_to_string(pos))
		end
	end,
})

--
-- Crafting
--

-- Normal Trash Can
minetest.register_craft({
	output = 'trash_can:trash_can_wooden',
	recipe = {
		{'group:wood', '', 'group:wood'},
		{'group:wood', '', 'group:wood'},
		{'group:wood', 'group:wood', 'group:wood'},
	}
})


code:
name = trash_can
depends = default
description = This mod adds a wooden trash can, and a dumpster to the game.  Right click it, put in your trash, and click the empty trash button.


code:
If you wish to register treasures to Treasurer, it is recommended to assign the treasurer to a Treasurer group.

It is not possible to assign a treasure to multiple groups. If you think a treasure fits into two groups, create
two seperate treasure definitions instead. But try to keep it as an exception.

Think of treasurer groups as categories for treasurers to put into.

This file contains some guidelines for Treasurer groups and there are even guidelines for preciousness levels.
Keep in mind these are only guidelines.

Treasurer suggests to use the following standard Treasurer groups:

crafting_component:
	Generic group for components in crafting recipes. If it is primarily a component for food, use raw_food instead.
	Preciousness is based roughly on the preciousness of items it can create.

fuel:
	Fuel for furnaces.
	Preciousness is based on burning time.

food:
	Can be eaten and restores health.
	Preciousness should equal the number of hearts restored (restored HP divided by 2), but not higher than 7.

raw_food:
	Food which is not fully processed and is not (quite) ready to be eaten.

melee_weapon:
	Primarily used to damage close foes, i.e. a sword.
	Preciousness is based on attack speed and damage.

ranged_weapon:
	Primarily used to damage far away foes,
	Preciousness is based on attack speed, damage and range.

tool:
	A tool for other uses.
	Preciousness is hard to determine; at least is should base on the number of uses.

minetool:
	A tool to destroy blocks. Includes pickaxes, axes, shovels, …
	Preciousness is based on power, number of uses and speed.

deco:
	Primarily just a decorational thing to place.
	Preciousness is based on beauty, highly subjective.

light:
	Is a light source.
	Preciousness is based on the brightness. For the maximum brightness (before sun brightness), preciousness should be 3.

building_block:
	A block for buildings. Includes stairs, slabs, fences and similar things.
	Excludes all natural blocks.
	Preciousness should be roughly based on the “cost” to craft the block.

seed:
	Seeds and saplings.
	Preciousness is based on the percieved “usefulness” of what can grow from the seed.

transport_vehicle
	A vehicle to transport players and stuff, i.e. a cart or a boat.
	Preciousness is hard to determine, maybe speed?

transport_structure
	A fixed structure which is neccessary for a transport vehicle to operate, i.e. rails.
	Preciousness is hard to dertermine …

ladder
	A ladder.

default:
	This is the group your treasure get assigned to if you don’t specify a group.

Do you miss a group? Feel free to invent your own!

Note that Treasurer groups differ from the groups as defined by the Minetest API. These groups are handled differently.


code:
= Treasurer’s README file for Treasurer version 0.2.0 =
== Overview ==
* Name: Treasurer
* Technical name: `treasurer`
* Purpose: To provide an interface for mods which want to spawn ItemStacks randomly and an interface for mods which create new items.
* Version: 0.2.0
* Dependencies: none
* License: WTFPL

== Introduction ==
Problem:
There are a bunch of mods which have cool items but they won’t appear in the world by
themselves.
There are some mods which randomly distribute treasures into the world. Sadly, these only
distribute the items they know—which are just the items of the mod “default” most of the
time. The items of the other mods are completely missed.

The reason for this is that the distributing mods can’t know what sort of items are available
unless they explicitly depend on the mods that defines these. Viewed the other way round,
the item-defining mods that also distribute these items into the world are limited in the
sense that they only know one means of distributing items.

There is a gap between defining items and distributing them. Every time a mod does both,
flexibility is limited and expansion becomes difficult.

To bridge this gap, Treasurer has been written. Treasurer makes it possible a) for mods to define
treasures without bothering _how_ these are distributed into the world and b) for mods to distribute
treasures around the world without knowledge about _what_ treasures exactly are distributed.

== Technical side of Treasurer ==
=== technical overview ===
To get a working Treasurer architecture and actually get some treasures into the world,
you need:
* Treasurer
* at least one treasure registration mod
* at least one treasure spawning mod

=== treasurer registration mod ===
Firstly, there are the treasure registration mods (TRMs). The task of TRMs is to tell
Treasurer which items does it have to offer, which relative appearance probabilities these
treasures should have, how “precious” the treasure is considered (on a scale from 0 to 10)
, optionally how big the stack of items should be and optionally how much worn out it is.
TRMs must depend on at least two mods: On treasurer and on the mod or mods
where the items are defined. Technically, a TRM consists of nothing more than a long
list of “registration” function calls. While this seems trivial, the task of balancing
out probabilties and balancing out preciousness levels of treasures is not trivial
and it may take a long time to get right.

It is strongly recommended that a TRM really does nothing
more than registering treasures (and not defining items, for example). If you want
to make your mod compatible to Treasurer, don’t change your mod, write a TRM for
it instead.

There is an example TRM, called “`trm_default_example`”. It registers some items
of the default as treasures. Unsurprisingly, it depends on `treasurer` and `default`.

=== treasurer spawning mods ===
Secondly, there are the treasure spawning mods (TSMs). The task of a TSM is to somehow
distribute the available treasures into the world. This is also called “treasure
spawning”. How exactly the TSM spawns the treasures is completely up the TSM. But a
TSM has to request Treasurer to get some random treasures to distribute. A TSM may
optionally request to filter out treasures outside certain preciousness levels
and groups, so the result is a bit controllable and not completely random.
Treasurer can not guarantee to return the requestet amount of treasures, it may
return an empty table, for two reasons:

* There is no TRM activated. There must be at least one to work.
* The filters filtered out everything, leaving Treasurer with an empty treasure pool
to choose from. This problem can be fixed by installing more TRMs or by balancing the
existing TRMs to cover as many preciousness levels as possible. It also may be that
the range specified by the TSM was too small. It is recommended to keep the
requested range at least of a size of 1. Treasurer does, however, guarantee that
the returned treasures are always in the requested bounds.

A TSM has to at least depend on Treasurer.
Unlike for TRMs, it may not be a problem to also do some other stuff than just
spawning treasures if it seems feasible. You may choose to make your TSM fully
dependant on Treasure, then it won’t work without Treasurer. You may also choose
to only add an optional dependency on Treasurer. For this to work, your mod must
now select its own treasures, which of course will only come from a rather limited
pool.

To check if the Treasurer mod is installed, you can use something like this in
your code:

```
if(minetest.get_modpath("treasurer")~=nil) then
	-- Treasurer is installed.
	-- You can call Treasurer’s functions here.
else
	-- Treasurer is not installed.
	-- You may write your replacement code here.
	-- You can not call Treasurer’s funcitons here.
end
```

There are two example TSMs. The first one is a very basic one and called “`tsm_gift_example`”.
It gives a “welcome gift” (1 random treasure) to players who just joined the server
or who respawn. The preciousness and group filters are not used. It does only depend on
Treasurer. The second one is called “`tsm_chests_example`” and pretty advanced for an example.
It places chests of the mod “default” between 20 and 200 node lenghts below the water
surface and puts 1-6 random treasures into these. The lower the chest, the higher
the preciousness. It depends on treasurer and default (for the chests, of course).

=== Recap ===
TRMs define treasures, TSMs spawn them. Treasurer manages the pool of available treasures.
TRMs and TSMs do not have to know anything from each other.
TRMs and TSMs do not neccessarily have to change any line of code of other mods to function.
Treasurer depends on nothing.

Important: It should always only be neccessary for TRMs and TSMs to depend on Treasurer.
All other mods do NOT HAVE TO and SHOULD NOT depend on Treasurer.

=== Treasure attributes ===
This section explains the various attributes a treasure can have.

==== Rarity ====
Rarity in Treasurer works in a pretty primitive way: The relative rarities of all
treasures from the treasure pool are simply all added up. The probabilitiy of one
certain treasure is then simply the rarity value divided by the sum.

==== Preciousness ====
How “precious” an item is, is highly subjective and also not always easy to categorize.
Preciousness in Treasurer’s terms should be therefore viewed as “utility” or as
“reward level” or “strength” or even “beauty” or whatever positive attributes you can
think of for items. See the text file `GROUPS_AND_PRECIOUSNESS` for a rough
guideline.
So, if you create a TRM and your treasure is something you want the player work
hard for, assign it a high preciousness. Everyday items that are already easy to
obtain in normal gameplay certainly deserve a lower precious than items that are
expensive to craft.
If your treasure consists of a powerful
item, assign it a high preciousness. When in doubt, try to value gameplay over
personal taste. Remember that TSMs can (and will!) filter out treasures based
on their preciousness.
For TSM authors, consider preciousness this way: If the trouble the player has
to get through to in order to obtain the treasure is high, better filter
out unprecious treasures. If your TSM distributes many treasures all over the world and these
are easy to obtain, filter out precious treasures.

TSMs also can just completely ignore preciousness, then the given treasures base
on sheer luck.

==== Treasurer groups ====
Every treasure can be assigned to a group. These groups are specific to Treasurer only.
The idea is that treasures which share a common property are member of the same group.
All groups have a name by which they are identified.
For example, if there are apples, plums, pears and oranges and those items can be
eaten for health, all those treasures would be members of the group “food”.

The group system can be used to further narrow down the treasure pool from which you
want Treasurer to return treasures. This makes it more interesting than just using
an one-dimensional preciousness scale.

Using the groups system is entirely optional. If your TRM does not specify any group,
your treasure will be assigned to the group “default”. It is not possible for a treasure
to not belong to any group. If your TSM does not specify a group parameter, Treasurer
will use all groups.
While not using groups as a TSM may be perfectly okay, not using groups as a TRM is
not recommended, because TSM which filter by groups may “overlook” your treasure,
even if it would actually fit, simply because you didn’t assign it to a specific group.

Note that Treasurer groups are completely distinct from Minetest’s group system.

You can basically invent your own groups on the fly, but it is strongly recommended that you
use the groups suggested in the text file `GROUPS_AND_PRECIOUSNESS` whenever possible, for
maximum portability of your TSM. The text file also has a rough guideline for finding
appropriate values for the preciousness.

==== Recap ====
Rarity determines the chance of a treasure, whereas preciousness determines
the difficulty to obtain it. Group

== Overview of examples ==
- `trm_default_example` - registers items of default mod
- `tsm_chests_example` - spawns chests (from the default mod)
- `tsm_gift_example` - gives one treasure as a “welcome gift” to joined or respawned players

== Treasurer API documentation ==
=== API documentation for treasure registration mods ===
The API consists of one function, which is called “`treasurer.register_treasure`”.

==== `treasurer.register_treasure` ====
Registers a new treasure (this means the treasure will be ready to be spawned by treasure spawning mods).

This function does some basic parameter checking to catch the most obvious
mistakes. If invalid parameters have been passed, the input is rejected and
the function returns false. However, it does not cover every possible
mistake, so some invalid treasures may slip through.

Rarity does not imply preciousness. A rare treasure may not neccessarily a
very precious one. A treasure chest with scorched stuff inside may be very
rare, but it’s certainly also very unprecious.

===== Parameters =====
* `name`: name of resulting `ItemStack`, e.g. “`mymod:item`”
* `rarity`: rarity of treasure on a scale from 0 to 1 (inclusive). lower = rarer
* `preciousness` : subjective preciousness on a scale from 0 to 10 (inclusive). higher = more precious.
* `count`: optional value which specifies the multiplicity of the item. Default is 1. See `count` syntax help in this file.
* `wear`: optional value which specifies the wear of the item. Default is 0, which disables the wear. See `wear` syntax help in this file.
* `treasurer_groups`: an optional table of group names to assign this treasure to. If omitted, the treasure is added to the default group.

===== Return value =====
`true` on success, `false` on failure.

=== data formats ===
format of count type:
==== `count` ====
A `count` can be a number or a table

* `number`: it’s always so many times
* `{min, max}`: it’s pseudorandomly between `min` and `max` times, `math.random` will be used to chose the value
* `{min, max, prob_func}`: it’s between `min` and `max` times, and the value is given by `prob_func` (see below)

==== `wear` ====
Completely analogous to `count`.

==== Format of `prob_func` function ====
There are no parameters.

It returns a random or pseudorandom number between 0 (inclusive) and 1 (exclusive).

`prob_func` is entirely optional, if it’s not used, treasurer will
default to using `math.random`. You can use `prob_func` to define your own
“randomness” function, in case you don’t wish your values to be evenly
distributed.

=== API documentation for treasure spawning mods ===
The API consists of one function, called “`treasurer.select_random_treasures`”.

==== `treasurer.select_random_treasures` ====
Request some treasures from treasurer.

===== Parameters =====
* `count`: (optional) amount of treasures. If this value is `nil`, Treasurer assumes a default of 1.
* `minimal_preciousness`: (optional) don’t consider treasures with a lower preciousness. If `nil`, there’s no lower bound.
* `maximum_preciousness`: (optional) don’t consider treasures with a higher preciousness. If `nil`, there’s no upper bound.
* `treasurer_group`: (optional): Only consider treasures which are members of at least one of the members of the provided Treasurer group table. `nil` = consider all groups

===== Return value =====
A table of `ItemStacks` (the requested treasures). It may be empty.


code:
--[==[
	Treasurer
	- A mod for Minetest
	version 0.2.0
]==]

--[=[
 	TABLE OF CONTENTS
	part 1: Initialization
	part 2: Treasure API
	part 3: Treasure spawning mod handling
	part 4: Internal functions
]=]

--[=[
	part 1: Initialization
]=]

-- This creates the main table; all functions of this mod are stored in this table
treasurer = {}

-- Table which stores all the treasures
treasurer.treasures = {}

-- This table stores the treasures again, but this time sorted by groups
treasurer.groups = {}

-- Groups defined by the Treasurer API
treasurer.groups.treasurer = {}
treasurer.groups.treasurer.default = {}

-- Groups defined by the Minetest API
treasurer.groups.minetest = {}

--[[
format of treasure table:
	treasure = {
		name,		-- treasure name, e.g. mymod:item
		rarity,		-- relative rarity on a scale from 0 to 1 (inclusive).
				-- a rare treasure must not neccessarily be a precious treasure
		count,		-- count (see below)
		preciousness,	-- preciousness or “worth” of the treasure.
				-- ranges from 0 (“scorched stuff”) to 10 (“diamond block”)
		wear,		-- wear (see below)
		metadata,	-- unused at the moment

	}
	treasures can be nodes or items

format of count type:
	count = number			-- it’s always number times
	count = {min, max}		-- it’s pseudorandomly between min and max times, math.random() will be used to chose the value
	count = {min, max, prob_func}	-- it’s between min and max times, and the value is given by prob_func (which is not neccessarily random [in the strictly mathematical sense])

format of wear type:
	completely analogous to count type

format of prob_func function:
	prob_func = function()
	--> returns a random or pseudorandom number between 0 (inclusive) and 1 (exclusive)
	prob_func is entirely optional, if it’s not used, treasurer will default to math.random.
	You can use prob_func to define your own random function, in case you don’t like an even
	distribution

format of treasurer_groups:
	This is just a table of strings, each string stands for a group name.
]]

--[=[
	part 2: Treasurer API
]=]

--[[
 treasurer.register_treasure - registers a new treasure
 (this means the treasure will be ready to be spawned by treasure spawning mods.

 name: name of resulting ItemStack, e.g. “mymod:item”
 rarity: rarity of treasure on a scale from 0 to 1 (inclusive). lower = rarer
 preciousness: preciousness of treasure on a scale from 0 (“scorched stuff”) to 10 (“diamond block”).
 count: optional value which specifies the multiplicity of the item. Default is 1. See count syntax help in this file.
 wear: optional value which specifies the wear of the item. Default is 0, which disables the wear. See wear syntax help in this file.
 treasurer_groups: (optional) a table of group names to assign this treasure to. If omitted, the treasure is added to the default group.
 This function does some basic parameter checking to catch the most obvious mistakes. If invalid parameters have been passed, the input is rejected and the function returns false. However, it does not cover every possible mistake, so some invalid treasures may slip through.

 returns: true on success, false on failure
]]
function treasurer.register_treasure(name, rarity, preciousness, count, wear, treasurer_groups )
	--[[ We don’t trust our input, so we first check if the parameters
	have the correct types and refuse to add the treasure if a
	parameter is malformed.
	What follows is a bunch of parameter checks.
	]]

	-- check wheather name is a string
	if type(name) ~= "string" then
		minetest.log("error","[treasure] I rejected a treasure because the name was of type \""..type(name).."\" instead of \"string\".")
		return false
	end
	-- first check if rarity is even a number
	if type(rarity) == "number" then
		-- then check wheather the rarity lies in the allowed range
		if rarity < 0 or rarity > 1 then
			minetest.log("error", "[treasurer] I rejected the treasure \""..tostring(name).."\" because it’s rarity value is out of bounds. (it was "..tostring(rarity)..".)")
			return false
		end
	else
		minetest.log("error","[treasurer] I rejected the treasure \""..tostring(name).."\" because it had an illegal type of rarity. Given type was \""..type(rarity).."\".")
		return false
	end

	-- check if preciousness is even a number
	if type(preciousness) == "number" then
		-- then check wheather the preciousness lies in the allowed range
		if preciousness < 0 or preciousness > 10 then
			minetest.log("error", "[treasurer] I rejected the treasure \""..tostring(name).."\" because it’s preciousness value is out of bounds. (it was "..tostring(preciousness)..".)")
			return false
		end
	else
		minetest.log("error","[treasurer] I rejected the treasure \""..tostring(name).."\" because it had an illegal type of preciousness. Given type was \""..type(preciousness).."\".")
		return false
	end

	-- first check if count is of a correct type
	if type(count) ~= "number" and type(count) ~= "nil" and type(count) ~= "table" then
		minetest.log("error", "[treasurer] I rejected the treasure \""..tostring(name).."\" because it had an illegal type of “count”. Given type was \""..type(count).."\".")
		return false
	end
	-- if count’s a table, check if it’s format is correct
	if type(count) == "table" then
		if(not (type(count[1]) == "number" and type(count[2]) == "number" and (type(count[3]) == "function" or type(count[3]) == "nil"))) then
			minetest.log("error","[treasurer] I rejected the treasure \""..tostring(name).."\" because it had a malformed table for the count parameter.")
			return false
		end
	end

	-- now do the same for wear:
	-- first check if wear is of a correct type
	if type(wear) ~= "number" and type(wear) ~= "nil" and type(wear) ~= "table" then
		minetest.log("error","[treasurer] I rejected the treasure \""..tostring(name).."\" because it had an illegal type of “wear”. Given type was \""..type(wear).."\".")
		return false
	end
	-- if wear’s a table, check if it’s format is correct
	if type(wear) == "table" then
		if(not (type(wear[1]) == "number" and type(wear[2]) == "number" and (type(wear[3]) == "function" or type(wear[3]) == "nil"))) then
			minetest.log("error","[treasurer] I rejected the treasure \""..tostring(name).."\" because it had a malformed table for the wear parameter.")
			return false
		end
	end

	-- check type of treasurer_group
	if type(treasurer_groups) ~= "table" and type(treasurer_groups) ~= "nil" and type(treasurer_groups) ~= "string" then
		minetest.log("error","[treasurer] I rejected the treasure \""..tostring(name).."\" because the treasure_group parameter is of type "..tosting(type(treasurer_groups)).." (expected: nil, string or table).")
		return false
	end

	--[[ End of checks. If we reached this point of the code, all checks have been passed
	and we finally register the treasure.]]

	-- default count is 1
	if count == nil then count = 1 end
	-- default wear is 0
	if wear == nil then wear = 0 end
	local treasure = {
		name = name,
		rarity = rarity,
		count = count,
		wear = wear,
		preciousness = preciousness,
		metadata = "",
	}
	table.insert(treasurer.treasures, treasure)

	--[[ Assign treasure to Treasurer group(s) or default if not provided ]]
	-- default Treasurer group is default
	if treasurer_groups == nil then treasurer_groups = "default" end

	if(type(treasurer_groups) == "string") then
		if(treasurer.groups.treasurer[treasurer_groups] == nil) then
			treasurer.groups.treasurer[treasurer_groups] = {}
		end
		table.insert(treasurer.groups.treasurer[treasurer_groups], treasure)
	elseif(type(treasurer_groups) == "table") then
		for i=1,#treasurer_groups do
			-- assign to Treasurer group (create table if it does not exist yet)
			if(treasurer.groups.treasurer[treasurer_groups[i]] == nil) then
				treasurer.groups.treasurer[treasurer_groups[i]] = {}
			end
			table.insert(treasurer.groups.treasurer[treasurer_groups[i]], treasure)
		end

	end

	minetest.log("info","[treasurer] Treasure successfully registered: "..name)
	return true
end

--[=[
	part 3: Treasure spawning mod (TSM) handling
]=]

--[[
  treasurer.select_random_treasures - request some treasures from treasurer
  parameters:
    count: (optional) amount of items in the treasure. If this value is nil, treasurer assumes a default of 1.
    min_preciousness: (optional) don’t consider treasures with a lower preciousness. nil = no lower limit
    max_preciousness: (optional) don’t consider treasures with a higher preciousness. nil = no lower limit
    treasurer_groups: (optional): Only consider treasures which are members of at least one of the members of the provided Treasurer group table. nil = consider all groups
  returns:
    a table of ItemStacks (the requested treasures) - may be empty
    on error, it returns false
]]
function treasurer.select_random_treasures(count, min_preciousness, max_preciousness, treasurer_groups)
	if #treasurer.treasures == 0 and count >= 1 then
		minetest.log("info","[treasurer] I was asked to return "..count.." treasure(s) but I can’t return any because no treasure was registered to me.")
		return {}
	end
	if count == nil then count = 1 end
	local sum = 0
	local cumulate = {}
	local randoms = {}

	-- copy treasures into helper table
	local p_treasures = {}
	if(treasurer_groups == nil) then
		-- if the group filter is not used (defaul behaviour), copy all treasures
		for i=1,#treasurer.treasures do
			table.insert(p_treasures, treasurer.treasures[i])
		end

	-- if the group filter IS used, copy only the treasures from the said groups
	elseif(type(treasurer_groups) == "string") then
		if(treasurer.groups.treasurer[treasurer_groups] ~= nil) then
			for i=1,#treasurer.groups.treasurer[treasurer_groups] do
				table.insert(p_treasures, treasurer.groups.treasurer[treasurer_groups][i])
			end
		else
			minetest.log("info","[treasurer] I was asked to return "..count.." treasure(s) but I can’t return any because no treasure which fits to the given Treasurer group “"..treasurer_groups.."”.")
			return {}
		end
	elseif(type(treasurer_groups) == "table") then
		for t=1,#treasurer_groups do
			if(treasurer.groups.treasurer[treasurer_groups[t]] ~= nil) then
				for i=1,#treasurer.groups.treasurer[treasurer_groups[t]] do
					table.insert(p_treasures, treasurer.groups.treasurer[treasurer_groups[t]][i])
				end
			end
		end
	else
		minetest.log("error","[treasurer] treasurer.select_random_treasures was called with a malformed treasurer_groups parameter!")
		return false
	end

	if(min_preciousness ~= nil) then
		-- filter out too unprecious treasures
		for t=#p_treasures,1,-1 do
			if((p_treasures[t].preciousness) < min_preciousness) then
				table.remove(p_treasures,t)
			end
		end
	end

	if(max_preciousness ~= nil) then
		-- filter out too precious treasures
		for t=#p_treasures,1,-1 do
			if(p_treasures[t].preciousness > max_preciousness) then
				table.remove(p_treasures,t)
			end
		end
	end

	for t=1,#p_treasures do
		sum = sum + p_treasures[t].rarity
		cumulate[t] = sum
	end
	for c=1,count do
		randoms[c] = math.random() * sum
	end

	local treasures = {}
	for c=1,count do
		for t=1,#p_treasures do
			if randoms[c] < cumulate[t] then
				table.insert(treasures, p_treasures[t])
				break
			end
		end
	end

	local itemstacks = {}
	for i=1,#treasures do
		itemstacks[i] = treasurer.treasure_to_itemstack(treasures[i])
	end
	if #itemstacks < count then
		minetest.log("info","[treasurer] I was asked to return "..count.." treasure(s) but I could only return "..(#itemstacks)..".")
	end
	return itemstacks
end

--[=[
	Part 4: internal functions
]=]

--[[ treasurer.treasure_to_itemstack - converts a treasure table to an
     ItemStack
  parameter:
    treasure: a treasure (see format in the head of this file)
  returns:
    an ItemStack
]]
function treasurer.treasure_to_itemstack(treasure)
	local itemstack = {}
	itemstack.name = treasure.name
	itemstack.count = treasurer.determine_count(treasure)
	itemstack.wear = treasurer.determine_wear(treasure)
	itemstack.metadata = treasure.metadata

	return ItemStack(itemstack)
end

--[[
  This determines the count of a treasure by taking the various different
  possible types of the count value into account
  This function assumes that the treasure table is valid.
  returns: the count
]]
function treasurer.determine_count(treasure)
	if(type(treasure.count)=="number") then
		return treasure.count
	else
		local min,max,prob = treasure.count[1], treasure.count[2], treasure.count[3]
		if(prob == nil) then
			return(math.floor(min + math.random() * (max-min)))
		else
			return(math.floor(min + prob() * (max-min)))
		end
	end
end

--[[
  This determines the wear of a treasure by taking the various different
  possible types of the wear value into account.
  This function assumes that the treasure table is valid.
  returns: the count
]]
function treasurer.determine_wear(treasure)
	if(type(treasure.wear)=="number") then
		return treasure.wear
	else
		local min,max,prob = treasure.wear[1], treasure.wear[2], treasure.wear[3]
		if(prob == nil) then
			return(math.floor(min + math.random() * (max-min)))
		else
			return(math.floor(min + prob() * (max-min)))
		end
	end
end


code:
name = treasurer
title = Treasurer
description = A framework which manages treasures which helps other mods to register and distribute treasures around the world.
author = Wuzzy
depends =
optional_depends =


code:
saras_simple_survival mod: void_chest
=====================================
Copyright 2020 freegamers.org

Description
-------------------------------------
A chest that uses the mysterious power of the void to store your items remotely. All void chests appear to have the same inventory. Void chests can also be mined and picked up, even when they appear to have stuff in them. Lastly, as everyone gets their own void chest account, the items you see in the void chest are not the same items that other players see. This chest's properties make it nice for keeping secrets, as well as essentially almost doubling your inventory space, if you choose to carry one with you!

Recipe
-------------------------------------
if the magic_materials mod is installed:

{'default:steelblock','magic_materials:void_rune','default:steelblock'},
{'magic_materials:void_rune','default:chest_locked','magic_materials:void_rune'},
{'default:steelblock','magic_materials:void_rune','default:steelblock'}

if no magic_materials mod is present then we can just use materials from default:

{'default:steelblock','default:obsidian_block','default:steelblock'},
{'default:obsidian_block','default:chest_locked','default:obsidian_block'},
{'default:steelblock','default:obsidian_block','default:steelblock'}

License of source code
-------------------------------------
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
https://www.gnu.org/licenses/agpl-3.0.en.html

License of textures
-------------------------------------
void_chest_front.png, void_chest_side.png, void_chest_top.png by PixelBox Reloaded (WTFPL)
void_chest_void_particle.png by MisterE (CC-BY 4.0)
https://creativecommons.org/licenses/by/4.0/

Credits and Ackowledgements
-------------------------------------
This mod is a fork of morechests, which itself is a fork of 0gb.us's mod called chests_0gb_us.
https://forum.minetest.net/viewtopic.php?f=11&t=4366

The textures used for void chests are from jp's "xdecor" mod, which in turn are from the PixelBOX Reloaded texture pack for Minetest.
https://github.com/minetest-mods/xdecor


code:
void_chest = {
	show_particles = (minetest.settings:get("void_chest.show_particles") ~= "false")
}

-- Register the void chest.
minetest.register_node("void_chest:void_chest", {
	description = "" ..core.colorize("#660099","Void Chest\n") ..core.colorize("#FFFFFF", "Use the power of the void to store your items."),
	tiles = {"void_chest_top.png", "void_chest_top.png", "void_chest_side.png",
		"void_chest_side.png", "void_chest_side.png", "void_chest_front.png"},
	paramtype2 = "facedir",
	groups = {snappy=2, choppy=2, oddly_breakable_by_hand=2,},
	legacy_facedir_simple = true,
	sounds = default.node_sound_wood_defaults(),
	on_construct = function(pos)
		if void_chest.show_particles then
			local timer = minetest.get_node_timer(pos)
			timer:start(.1) -- in seconds
		end
		local meta = minetest.get_meta(pos)
		meta:set_string("formspec",
				"size[8,9]"..
				default.gui_bg ..
				default.gui_bg_img ..
				default.gui_slots ..
				"list[current_player;void_chest:void_chest;0,0.3;8,4;]"..
				"list[current_player;main;0,4.85;8,1;]" ..
				"list[current_player;main;0,6.08;8,3;8]" ..
				"listring[current_player;void_chest:void_chest]" ..
				"listring[current_player;main]" ..
				default.get_hotbar_bg(0,4.85))

		meta:set_string("infotext", "Void Chest")
	end,
	on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
				minetest.log("action", player:get_player_name()..
				" moves stuff in void chest at "..minetest.pos_to_string(pos))
	end,
	on_metadata_inventory_put = function(pos, listname, index, stack, player)
				minetest.log("action", player:get_player_name()..
				" moves stuff to void chest at "..minetest.pos_to_string(pos))
	end,
	on_metadata_inventory_take = function(pos, listname, index, stack, player)
				minetest.log("action", player:get_player_name()..
				" takes stuff from void chest at "..minetest.pos_to_string(pos))
	end,
	on_timer = function(pos)
		if void_chest.show_particles then
			-- Particles for the void effect, implemented by MisterE, thanks!
			for i=1,2 do -- number of particles spawned every on_timer
				local spin_speed = math.random(80,175)/1000 --controls how fast a particular particle spins
				local vel_scalar = math.random(0,5)/10 -- multiplied by the particle's velocity vector of 1
				local accel_scalar = math.random(1,5)/10 -- multiplied by the particle's accel vector of 1
				local expir = math.random(1,5) -- number of sec particle will last, if it doesn't hit a node
				local particle_pos = {x=pos.x + ((math.random(-15,15)/10)*(math.random(6,15)/10)), y=pos.y + ((math.random(-15,15)/10)*(math.random(6,15)/10)), z=pos.z+ ((math.random(-15,15)/10)*(math.random(6,15)/10))}
				local part_vel = vector.direction(particle_pos, pos)
				part_vel = {x= vel_scalar*part_vel.x, y= vel_scalar*part_vel.y, z= vel_scalar*part_vel.z}
				local part_accel = vector.direction(particle_pos, pos)
				part_accel = {x= accel_scalar*part_accel.x, y= accel_scalar*part_accel.y, z= accel_scalar*part_accel.z}
				minetest.add_particle({
					pos = particle_pos,
					velocity = part_vel,
					acceleration = part_accel,
					expirationtime = expir,
					size = math.random(5,15)/10,
					collisiondetection = true,
					collision_removal = true,
					vertical = false,
					texture = "void_chest_void_particle.png",
						animation = {
							type = "vertical_frames",
							aspect_w = 16,
							aspect_h = 16,
							length = spin_speed,
							},
					glow = 5,
				})
			end
		end
		return true
    end,

})

-- Register crafting recipes.
-- If the "magic_materials" mod is present we use a more accurate recipe.
if minetest.get_modpath("magic_materials") then
	minetest.register_craft({
		output = 'void_chest:void_chest',
		recipe = {
			{'default:steelblock','magic_materials:void_rune','default:steelblock'},
			{'magic_materials:void_rune','default:chest_locked','magic_materials:void_rune'},
			{'default:steelblock','magic_materials:void_rune','default:steelblock'}
		}
	})
else -- Else we use a recipe using "default" to avoid a hard dependency.
	minetest.register_craft({
		output = 'void_chest:void_chest',
		recipe = {
			{'default:steelblock','default:obsidian_block','default:steelblock'},
			{'default:obsidian_block','default:chest_locked','default:obsidian_block'},
			{'default:steelblock','default:obsidian_block','default:steelblock'}
		}
	})
end
-- Create a detached void chest inventory when players connect.
minetest.register_on_joinplayer(function(player)
	local inv = player:get_inventory()
	inv:set_size("void_chest:void_chest", 8*4)
end)


code:
name = void_chest
description = Access your belongings anywhere by using the power of the void chest!
author = FreeGamers.org
depends = default
optional_depends = magic_materials


code:
# VOID CHEST SETTINGS

# VOID CHEST:
# on low performance servers the particles can cause a drop in performance.

# Default: true
void_chest.show_particles (Show Particles) bool true


code:
-- intllib
local MP = minetest.get_modpath(minetest.get_current_modname())
local S, NS = dofile(MP .. "/intllib.lua")

-- 0.4.17 or 5.0 check
local y_off = 20
if minetest.registered_nodes["default:permafrost"] then
	y_off = 10
end

-- rideable horse

mobs:register_mob("mob_horse:horse", {
	type = "animal",
	visual = "mesh",
	visual_size = {x = 1.20, y = 1.20},
	mesh = "mobs_horse.x",
	collisionbox = {-0.4, -0.01, -0.4, 0.4, 1.25, 0.4},
	animation = {
		speed_normal = 15,
		speed_run = 30,
		stand_start = 25,
		stand_end = 75,
		walk_start = 75,
		walk_end = 100,
		run_start = 75,
		run_end = 100,
	},
	textures = {
		{"mobs_horse.png"}, -- textures by Mjollna
		{"mobs_horsepeg.png"},
		{"mobs_horseara.png"}
	},
	fear_height = 3,
	runaway = true,
	fly = false,
	walk_chance = 60,
	view_range = 5,
	follow = {"farming:wheat", "default:apple"},
	passive = true,
	hp_min = 12,
	hp_max = 16,
	armor = 200,
	lava_damage = 5,
	fall_damage = 5,
	water_damage = 1,
	sounds = {
		warcry = "mobs_animal_horse_warcry",
		random = "mobs_animal_horse_random",
		damage = "mobs_animal_horse_pain",
		jump = "mobs_animal_horse_jump",
		death = "mobs_animal_horse_death",
	},
	makes_footstep_sound = true,
	drops = {
		{name = "mobs:leather", chance = 1, min = 0, max = 2}
	},

	do_custom = function(self, dtime)

		-- set needed values if not already present
		if not self.v2 then
			self.v2 = 0
			self.max_speed_forward = 6
			self.max_speed_reverse = 2
			self.accel = 6
			self.terrain_type = 3
			self.driver_attach_at = {x = 0, y = y_off, z = -2}
			self.driver_eye_offset = {x = 0, y = 3, z = 0}
		end

		-- if driver present allow control of horse
		if self.driver then

			mobs.drive(self, "walk", "stand", false, dtime)

			return false -- skip rest of mob functions
		end

		return true
	end,

	on_die = function(self, pos)

		-- drop saddle when horse is killed while riding
		-- also detach from horse properly
		if self.driver then
			minetest.add_item(pos, "mobs:saddle")
			mobs.detach(self.driver, {x = 1, y = 0, z = 1})
self.saddle = nil
		end

		-- drop any horseshoes added
		if self.shoed then
			minetest.add_item(pos, self.shoed)
		end

	end,

	on_rightclick = function(self, clicker)

		-- make sure player is clicking
		if not clicker or not clicker:is_player() then
			return
		end

		-- feed, tame or heal horse
		if mobs:feed_tame(self, clicker, 10, true, true) then
			return
		end

		-- applying protection rune
		if mobs:protect(self, clicker) then
			return
		end

		-- make sure tamed horse is being clicked by owner only
		if self.tamed and self.owner == clicker:get_player_name() then

			local inv = clicker:get_inventory()

			-- detatch player already riding horse
			if self.driver and clicker == self.driver then

				mobs.detach(clicker, {x = 1, y = 0, z = 1})

				-- add saddle back to inventory
				if inv:room_for_item("main", "mobs:saddle") then
					inv:add_item("main", "mobs:saddle")
				else
					minetest.add_item(clicker:get_pos(), "mobs:saddle")
				end

self.saddle = nil

			-- attach player to horse
			elseif (not self.driver
			and clicker:get_wielded_item():get_name() == "mobs:saddle")
			or self.saddle then

				self.object:set_properties({stepheight = 1.1})
				mobs.attach(self, clicker)

				-- take saddle from inventory
				if not self.saddle then
					inv:remove_item("main", "mobs:saddle")
				end

self.saddle = true
			end
		end

		-- used to capture horse with magic lasso
		mobs:capture_mob(self, clicker, 0, 0, 80, false, nil)
	end
})

mobs:spawn({
	name = "mob_horse:horse",
	nodes = {"default:dirt_with_grass", "ethereal:dry_dirt"},
	min_light = 14,
	interval = 60,
	chance = 16000,
	min_height = 10,
	max_height = 100,
	day_toggle = true,
})

mobs:register_egg("mob_horse:horse", S("Horse"), "wool_brown.png", 1)

-- horseshoe helper function
local apply_shoes = function(name, itemstack, obj, shoes, speed, jump, reverse)

	if obj.type ~= "object" then return end

	local mob = obj.ref
	local ent = mob:get_luaentity()

	if ent and ent.name and ent.name == "mob_horse:horse" then

		if ent.shoed then
			minetest.add_item(mob:get_pos(), ent.shoed)
		end

		ent.max_speed_forward = speed
		ent.jump_height = jump
		ent.max_speed_reverse = reverse
		ent.accel = speed
		ent.shoed = shoes

		minetest.chat_send_player(name, S("Horse shoes fitted -")
				.. S(" speed: ") .. speed
				.. S(" , jump height: ") .. jump
				.. S(" , stop speed: ") .. reverse)

		itemstack:take_item() ; return itemstack
	else
		minetest.chat_send_player(name, S("Horse shoes only work on horses!"))
	end
end

-- steel horseshoes
minetest.register_craftitem(":mobs:horseshoe_steel", {
	description = S("Steel HorseShoes (use on horse to apply)"),
	inventory_image = "mobs_horseshoe_steel.png",
	on_use = function(itemstack, user, pointed_thing)
		return apply_shoes(user:get_player_name(), itemstack, pointed_thing,
				"mobs:horseshoe_steel", 7, 4, 2)
	end,
})

minetest.register_craft({
	output = "mobs:horseshoe_steel",
	recipe = {
		{"", "default:steelblock", ""},
		{"default:steel_ingot", "", "default:steel_ingot"},
		{"default:steel_ingot", "", "default:steel_ingot"},
	}
})

-- bronze horseshoes
minetest.register_craftitem(":mobs:horseshoe_bronze", {
	description = S("Bronze HorseShoes (use on horse to apply)"),
	inventory_image = "mobs_horseshoe_bronze.png",
	on_use = function(itemstack, user, pointed_thing)
		return apply_shoes(user:get_player_name(), itemstack, pointed_thing,
				"mobs:horseshoe_bronze", 7, 4, 4)
	end,
})

minetest.register_craft({
	output = "mobs:horseshoe_bronze",
	recipe = {
		{"", "default:bronzeblock", ""},
		{"default:bronze_ingot", "", "default:bronze_ingot"},
		{"default:bronze_ingot", "", "default:bronze_ingot"},
	}
})

-- mese horseshoes
minetest.register_craftitem(":mobs:horseshoe_mese", {
	description = S("Mese HorseShoes (use on horse to apply)"),
	inventory_image = "mobs_horseshoe_mese.png",
	on_use = function(itemstack, user, pointed_thing)
		return apply_shoes(user:get_player_name(), itemstack, pointed_thing,
				"mobs:horseshoe_mese", 9, 5, 8)
	end,
})

minetest.register_craft({
	output = "mobs:horseshoe_mese",
	recipe = {
		{"", "default:mese", ""},
		{"default:mese_crystal_fragment", "", "default:mese_crystal_fragment"},
		{"default:mese_crystal_fragment", "", "default:mese_crystal_fragment"},
	}
})

-- diamond horseshoes
minetest.register_craftitem(":mobs:horseshoe_diamond", {
	description = S("Diamond HorseShoes (use on horse to apply)"),
	inventory_image = "mobs_horseshoe_diamond.png",
	on_use = function(itemstack, user, pointed_thing)
		return apply_shoes(user:get_player_name(), itemstack, pointed_thing,
				"mobs:horseshoe_diamond", 10, 6, 6)
	end,
})

minetest.register_craft({
	output = "mobs:horseshoe_diamond",
	recipe = {
		{"", "default:diamondblock", ""},
		{"default:diamond", "", "default:diamond"},
		{"default:diamond", "", "default:diamond"},
	}
})

-- lucky blocks
if minetest.get_modpath("lucky_block") then

lucky_block:add_blocks({
	{"dro", {"mobs:horseshoe_steel"}},
	{"dro", {"mobs:horseshoe_bronze"}},
	{"dro", {"mobs:horseshoe_mese"}},
	{"dro", {"mobs:horseshoe_diamond"}},
})

end


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:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-02-06 00:07+0800\n"
"PO-Revision-Date: 2018-02-06 00:14+0800\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.0.6\n"
"Last-Translator: MuhdNurHidayat (MNH48) <mnh48mail@gmail.com>\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"Language: ms\n"

#: init.lua
msgid "Horse"
msgstr "Kuda"

#: init.lua
msgid "Horse shoes fitted -"
msgstr "Ladam telah dipasang pada kuda -"

#: init.lua
msgid " speed: "
msgstr " kelajuan: "

#: init.lua
msgid " , jump height: "
msgstr " , ketinggian lompat: "

#: init.lua
msgid " , stop speed: "
msgstr " , kelajuan berhenti: "

#: init.lua
msgid "Horse shoes only work on horses!"
msgstr "Ladam hanya boleh dipakaikan pada kuda!"

#: init.lua
msgid "Steel HorseShoes (use on horse to apply)"
msgstr "Ladam Kuda Keluli (guna pada kuda untuk pakaikan ia)"

#: init.lua
msgid "Bronze HorseShoes (use on horse to apply)"
msgstr "Ladam Kuda Gangsa (guna pada kuda untuk pakaikan ia)"

#: init.lua
msgid "Mese HorseShoes (use on horse to apply)"
msgstr "Ladam Kuda Mese (guna pada kuda untuk pakaikan ia)"

#: init.lua
msgid "Diamond HorseShoes (use on horse to apply)"
msgstr "Ladam Kuda Intan (guna pada kuda untuk pakaikan ia)"


code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-02-06 00:07+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

#: init.lua
msgid "Horse"
msgstr ""

#: init.lua
msgid "Horse shoes fitted -"
msgstr ""

#: init.lua
msgid " speed: "
msgstr ""

#: init.lua
msgid " , jump height: "
msgstr ""

#: init.lua
msgid " , stop speed: "
msgstr ""

#: init.lua
msgid "Horse shoes only work on horses!"
msgstr ""

#: init.lua
msgid "Steel HorseShoes (use on horse to apply)"
msgstr ""

#: init.lua
msgid "Bronze HorseShoes (use on horse to apply)"
msgstr ""

#: init.lua
msgid "Mese HorseShoes (use on horse to apply)"
msgstr ""

#: init.lua
msgid "Diamond HorseShoes (use on horse to apply)"
msgstr ""


code:
name = mob_horse
title = Mob Horse
description = Adds horses and items.
author = TenPlus1
depends = mobs
optional_depends = lucky_block, intllib


code:
# MOB Horse

### Spawning
There are three different horse textures (white, brown, black) which will spawn on green grassy areas and dry dirt areas in Ethereal mapgen.

---
### Taming
Horses can be tamed with 10x wheat or apples which then allows the player to pick up the horse using a lasso and ride by right-clicking with a saddle.

---
### Horseshoes
Horseshoes can be crafted using steel, bronze, mese and diamond (4x ingots - 2 down either side with 1x block top middle) and placed on a horse by punching with the item.  These can make horses run faster or jump higher depending on tier.

---
### Dead Horse
When riding a horse monsters will generally attack the horse first to get to player riding it, when horse dies the player is dismounted and it will drop any shoes or saddles in use as well as some horse meat.

#### Lucky Blocks: 4


code:
TEXTURES
boomer textures...
spider texture from lottmobs
shark author: sapier, CC-BY-SA 3.0, texture modification by blerty2112.

## FIRE IMP ###
texture: "De'Krath Imp" by The Crawler - https://www.planetminecraft.com/skin/dekrath-imp/
sounds: "Tiny vicious creature" by Darsycho -  https://freesound.org/people/Darsycho/sounds/442183/

BOOGYMAN - monster 2 skin by skip7777+, sounds by
SKELETON - Mesh by Morn76 Animation by Pavel_S
SPACE ZOMBIE TEXTURE - Former Astronaut Callsign 21A by Mullraugh

MODELS
spider model from lottmobs

CODE

SOUNDS
snowdrift soundpack

Halloween candy, costumes, and textures from "halloween" by GreenXenith (https://github.com/GreenXenith/halloween), MIT LICENSE.


code:
-- TODO: ADD GLOBAL VARIABLES FOR CONTROLLING HEIGHT SPAWNS VIA MINETEST SETTING OR MINETEST.CONF

local path = minetest.get_modpath("mobs_creatures")

--staging area for new mobs that are incomplete
dofile(path .. "/mobs/facehugger.lua")

--Simplified Monsters
dofile(path .. "/mobs/bogeyman.lua")
dofile(path .. "/mobs/boomer.lua")
dofile(path .. "/mobs/demon_eye.lua")
dofile(path .. "/mobs/dirt_man.lua")
dofile(path .. "/mobs/ghost.lua")
dofile(path .. "/mobs/ghost_restless.lua")
dofile(path .. "/mobs/sand_man.lua")
dofile(path .. "/mobs/snowman.lua")
dofile(path .. "/mobs/stone_man.lua")
dofile(path .. "/mobs/skeleton_archer.lua")
dofile(path .. "/mobs/skeleton_fighter.lua")
dofile(path .. "/mobs/tree_monster.lua")
dofile(path .. "/mobs/zombie.lua")

-- Animals (surface and subterrane)
dofile(path .. "/mobs/bat.lua")
dofile(path .. "/mobs/cow.lua")
dofile(path .. "/mobs/deer.lua") -- Needs SFX.
dofile(path .. "/mobs/kangaroo.lua")
dofile(path .. "/mobs/ocelot.lua")
dofile(path .. "/mobs/mooshroom.lua")
dofile(path .. "/mobs/panda.lua") -- Needs spawn.
dofile(path .. "/mobs/parrot.lua")
dofile(path .. "/mobs/penguin.lua") -- Needs SFX, and Follow items.
dofile(path .. "/mobs/polar_bear.lua")
dofile(path .. "/mobs/rabbit.lua")
dofile(path .. "/mobs/rat.lua")
dofile(path .. "/mobs/sheep.lua") -- Needs SFX.
dofile(path .. "/mobs/spider.lua")
dofile(path .. "/mobs/wolf.lua")

-- Farm Animals
dofile(path .. "/mobs/pig.lua")
dofile(path .. "/mobs/chicken.lua")

-- Sea Animals (aquatic)
dofile(path .. "/mobs/clownfish.lua")
dofile(path .. "/mobs/cod.lua")
dofile(path .. "/mobs/crocodile.lua")
dofile(path .. "/mobs/dolphin.lua")
dofile(path .. "/mobs/salmon.lua")
dofile(path .. "/mobs/shark.lua")
dofile(path .. "/mobs/snapper.lua")
dofile(path .. "/mobs/turtle.lua")

-- Cave Creatures (anywhere)
dofile(path .. "/mobs/fire_imp.lua")
dofile(path .. "/mobs/ghost_murderous.lua") -- (-500)
dofile(path .. "/mobs/water_man.lua")

-- Cave Creatures (df_caverns, layer 1+2)
dofile(path .. "/mobs/cave_crocodile.lua")
dofile(path .. "/mobs/giant_bat.lua")
dofile(path .. "/mobs/giant_cave_spider.lua")

-- Cave Creatures (df_caverns, layers 2+3)
dofile(path .. "/mobs/cave_floater.lua")
dofile(path .. "/mobs/jabberer.lua")

-- Cave Creatures (df_caverns, layer 3)
dofile(path .. "/mobs/blood_man.lua")
dofile(path .. "/mobs/diamond_man.lua")
dofile(path .. "/mobs/gold_man.lua")
dofile(path .. "/mobs/iron_man.lua")
dofile(path .. "/mobs/magma_man.lua")

-- Cave Creatures (df_caverns, layer 3+4)
dofile(path .. "/mobs/fire_man.lua")

-- Moon Creatures (planet_moon)
dofile(path .. "/mobs/astronaut.lua")  -- trader npc
dofile(path .. "/mobs/flying_saucer.lua")
dofile(path .. "/mobs/grey_enlisted.lua")
dofile(path .. "/mobs/grey_civilian.lua")
dofile(path .. "/mobs/reptilian_elite.lua")
dofile(path .. "/mobs/zombie_space.lua")

-- Items (usually these are mob drops)
dofile(path .. "/items/blood.lua")
dofile(path .. "/items/bone.lua")
dofile(path .. "/items/butter.lua")
dofile(path .. "/items/cheese.lua")
dofile(path .. "/items/chicken_items.lua")
dofile(path .. "/items/clownfish_items.lua")
dofile(path .. "/items/cod_items.lua")
dofile(path .. "/items/death_items.lua")
dofile(path .. "/items/milk.lua")
dofile(path .. "/items/mutton.lua")
dofile(path .. "/items/poop.lua")
dofile(path .. "/items/pork.lua")
dofile(path .. "/items/rabbit_items.lua")
dofile(path .. "/items/rotten_flesh.lua")
dofile(path .. "/items/salmon_items.lua")
dofile(path .. "/items/spider_items.lua")
dofile(path .. "/items/snapper_items.lua")

-- DOOMed Creatures
dofile(path .. "/mobs/cacodemon.lua")
dofile(path .. "/mobs/cyberdemon.lua")
dofile(path .. "/mobs/hellbaron.lua")
dofile(path .. "/mobs/imp.lua")
dofile(path .. "/mobs/mancubus.lua")
dofile(path .. "/mobs/pinky.lua")
dofile(path .. "/mobs/skull.lua")


code:
--Blood Bucket and Blood Source items originally from "horror" mod by D00M3D.
minetest.register_node("mobs_creatures:blood_flowing", {
        description = "blood_source",
        inventory_image = minetest.inventorycube("mobs_creatures_items_blood_source.png^[opacity:190"),
        drawtype = "flowingliquid",
        tiles = {"mobs_creatures_items_blood_source.png^[opacity:190"},
        special_tiles = {
                {
                        image="mobs_creatures_items_blood_animate.png^[opacity:190",
                        backface_culling=false,
                        animation={type="vertical_frames", aspect_w=32, aspect_h=32, length=1}
                },
                {
                        image="mobs_creatures_items_blood_animate.png^[opacity:190",
                        backface_culling=true,
                        animation={type="vertical_frames", aspect_w=32, aspect_h=32, length=1}
                },
        },
        use_texture_alpha = "blend",
        paramtype = "light",
        paramtype2 = "flowingliquid",
        walkable = false,
        pointable = false,
        diggable = false,
        buildable_to = true,
	is_ground_content = false,
        drop = "",
        drowning = 1,
        liquidtype = "flowing",
        liquid_alternative_flowing = "mobs_creatures:blood_flowing",
        liquid_alternative_source = "mobs_creatures:blood_source",
        liquid_viscosity = 3,
	liquid_renewable = false,
        post_effect_color = {a=70, r=200, g=70, b=70},
        groups = {liquid=3, not_in_creative_inventory=1, dynamic_lava_flowing_destroys=1, dynamic_lava_source_destroys=1 },
})

minetest.register_node("mobs_creatures:blood_source", {
        description = "Blood Source",
        inventory_image = minetest.inventorycube("mobs_creatures_items_blood_source.png^[opacity:190"),
        drawtype = "liquid",
        tiles = {
                {name="mobs_creatures_items_blood_animate.png^[opacity:190", animation={type="vertical_frames", aspect_w=32, aspect_h=32, length=1}}
        },
        special_tiles = {
                -- New-style water source material (mostly unused)
                {
                        name="mobs_creatures_items_blood_animate.png^[opacity:190",
                        animation={type="vertical_frames", aspect_w=32, aspect_h=32, length=1},
                        backface_culling = false,
                }
        },
        use_texture_alpha = "blend",
        paramtype = "light",
        walkable = false,
        pointable = false,
        diggable = false,
        buildable_to = true,
	is_ground_content = false,
        drop = "",
        drowning = 1,
        liquidtype = "source",
        liquid_alternative_flowing = "mobs_creatures:blood_flowing",
        liquid_alternative_source = "mobs_creatures:blood_source",
        liquid_viscosity = 3,
	liquid_renewable = false,
        post_effect_color = {a=70, r=200, g=70, b=70},
        groups = {liquid=3, dynamic_lava_flowing_destroys=1, dynamic_lava_source_destroys=1}
})

--bucket
if minetest.get_modpath("bucket") then
bucket.register_liquid(
        "mobs_creatures:blood_source",
        "mobs_creatures:blood_flowing",
        "mobs_creatures:bucket_blood",
        "mobs_creatures_items_blood_bucket.png",
        "Blood Bucket"
)
end

-- add dynamic liquid support
if minetest.get_modpath("dynamic_liquid") then
	dynamic_liquid.liquid_abm("mobs_creatures:blood_source", "mobs_creatures:blood_flowing", 3)
end


code:
minetest.register_craftitem("mobs_creatures:bone", {
                description = "Bone",
                inventory_image = "mobs_creatures_items_bone.png"
        })
if minetest.get_modpath("bones") then
	minetest.register_craft({
		output = "mobs_creatures:bone 2",
               	recipe = {{ "mesecraft_bones:bones" }},
       	})
end


code:
-- butter
minetest.register_craftitem("mobs_creatures:butter", {
        description = "Butter",
        inventory_image = "mobs_creatures_items_butter.png",
        on_use = minetest.item_eat(1),
        groups = {food_butter = 1, flammable = 2},
})

if minetest.get_modpath("farming") and farming and farming.mod then
minetest.register_craft({
        type = "shapeless",
        output = "mobs_creatures:butter",
        recipe = {"mobs_creatures:milk_bucket", "farming:salt"},
        replacements = {{ "mobs_creatures:milk_bucket", "mesecraft_bucket:bucket_empty"}}
})
else -- some saplings are high in sodium so makes a good replacement item
minetest.register_craft({
        type = "shapeless",
        output = "mobs_creatures:butter",
        recipe = {"mobs_creatures:milk_bucket", "default:sapling"},
        replacements = {{ "mobs_creatures:milk_bucket", "mesecraft_bucket:bucket_empty"}}
})
end


code:
-- Cheese Wedge
minetest.register_craftitem("mobs_creatures:cheese", {
        description = "Cheese",
        inventory_image = "mobs_creatures_items_cheese.png",
        on_use = minetest.item_eat(4),
        groups = {food_cheese = 1, flammable = 2},
})

-- Cooking Milk Bucket to Create Cheese
minetest.register_craft({
        type = "cooking",
        output = "mobs_creatures:cheese",
        recipe = "mobs_creatures:milk_bucket",
        cooktime = 5,
        replacements = {{ "mobs_creatures:milk_bucket", "mesecraft_bucket:bucket_empty"}}
})

-- Cheese Block
minetest.register_node("mobs_creatures:cheeseblock", {
        description = "Cheese Block",
        tiles = {"mobs_creatures_items_cheeseblock.png"},
        is_ground_content = false,
        groups = {crumbly = 3},
        sounds = default.node_sound_dirt_defaults()
})

-- 9 Cheese makes a Cheese Block.
minetest.register_craft({
        output = "mobs_creatures:cheeseblock",
        recipe = {
                {'mobs_creatures:cheese', 'mobs_creatures:cheese', 'mobs_creatures:cheese'},
                {'mobs_creatures:cheese', 'mobs_creatures:cheese', 'mobs_creatures:cheese'},
                {'mobs_creatures:cheese', 'mobs_creatures:cheese', 'mobs_creatures:cheese'},
        }
})

-- A cheese block can make 9 cheese.
minetest.register_craft({
        output = "mobs_creatures:cheese 9",
        recipe = {
                {'mobs_creatures:cheeseblock'},
        }
})


code:
-- Register Chicken Egg
minetest.register_node("mobs_creatures:egg", {
        description = "Chicken Egg",
        tiles = {"mobs_creatures_items_chicken_egg.png"},
        inventory_image  = "mobs_creatures_items_chicken_egg.png",
        visual_scale = 0.7,
        drawtype = "plantlike",
        wield_image = "mobs_creatures_items_chicken_egg.png",
        paramtype = "light",
        walkable = false,
        is_ground_content = true,
        sunlight_propagates = true,
        selection_box = {
                type = "fixed",
                fixed = {-0.2, -0.5, -0.2, 0.2, 0, 0.2}
        },
        groups = {food_egg = 1, snappy = 2, dig_immediate = 3},
        after_place_node = function(pos, placer, itemstack)
                if placer:is_player() then
                        minetest.set_node(pos, {name = "mobs_creatures:egg", param2 = 1})
                end
        end,
})
-- Register "Fried Egg"
minetest.register_craftitem("mobs_creatures:chicken_egg_fried", {
        description = "Fried Egg",
        inventory_image = "mobs_creatures_items_chicken_egg_fried.png",
        on_use = minetest.item_eat(2),
        groups = {food_egg_fried = 1, flammable = 2},
})
-- Recipe for fried egg.
minetest.register_craft({
        type  =  "cooking",
        recipe  = "mobs_creatures:egg",
        output = "mobs_creatures:chicken_egg_fried",
})
-- Register raw chicken.
minetest.register_craftitem("mobs_creatures:chicken_raw", {
description = "Raw Chicken",
        inventory_image = "mobs_creatures_items_chicken_raw.png",
        on_use = minetest.item_eat(2),
        groups = {food_meat_raw = 1, food_chicken_raw = 1, flammable = 2},
})
-- Register cooked chicken.
minetest.register_craftitem("mobs_creatures:chicken_cooked", {
description = "Cooked Chicken",
        inventory_image = "mobs_creatures_items_chicken_cooked.png",
        on_use = minetest.item_eat(6),
        groups = {food_meat = 1, food_chicken = 1, flammable = 2},
})
-- Recipe for cooked chicken.
minetest.register_craft({
        type  =  "cooking",
        recipe  = "mobs_creatures:chicken_raw",
        output = "mobs_creatures:chicken_cooked",
})

-- Register feather.
minetest.register_craftitem("mobs_creatures:chicken_feather", {
        description = "Chicken Feather",
        inventory_image = "mobs_creatures_items_chicken_feather.png",
        groups = {flammable = 2},
})
-- Chicken feather make burnable.
minetest.register_craft({
        type = "fuel",
        recipe = "mobs_creatures:chicken_feather",
        burntime = 1,
})