diff --git a/game_api.txt b/game_api.txt
index d3330e5f06beb5e89a947d46316a9c09866414fa..634b7f2993f41c608a4871ebed3dd5b92a709cad 100644
--- a/game_api.txt
+++ b/game_api.txt
@@ -39,8 +39,8 @@ Beds API
  * `beds.read_spawns() `   Returns a table containing players respawn positions
  * `beds.kick_players()`  Forces all players to leave bed
  * `beds.skip_night()`   Sets world time to morning and saves respawn position of all players currently sleeping
- 
-###Bed definition
+
+### Bed definition
 
 	{
 		description = "Simple Bed",
@@ -85,12 +85,12 @@ The doors mod allows modders to register custom doors and trapdoors.
  * `def`  See [#Fence gate definition]
 
 `doors.get(pos)`
- 
+
  * `pos` A position as a table, e.g `{x = 1, y = 1, z = 1}`
- * Returns an ObjecRef to a door, or nil if the position does not contain a door
- 
- ###Methods
- 
+ * Returns an ObjectRef to a door, or nil if the position does not contain a door
+
+    ### Methods
+
         :open(player)   -- Open the door object, returns if door was opened
         :close(player)  -- Close the door object, returns if door was closed
         :toggle(player) -- Toggle the door state, returns if state was toggled
@@ -101,7 +101,7 @@ The doors mod allows modders to register custom doors and trapdoors.
     has the permissions needed to open this door. If omitted then no
     permission checks are performed.
 
-###Door definition
+### Door definition
 
 	description = "Door description",
 	inventory_image = "mod_door_inv.png",
@@ -113,7 +113,7 @@ The doors mod allows modders to register custom doors and trapdoors.
 	sound_close = sound play for close door, -- optional
 	protected = false, -- If true, only placer can open the door (locked for others)
 
-###Trapdoor definition
+### Trapdoor definition
 
 	description = "Trapdoor description",
 	inventory_image = "mod_trapdoor_inv.png",
@@ -125,7 +125,7 @@ The doors mod allows modders to register custom doors and trapdoors.
 	sound_close = sound play for close door, -- optional
 	protected = false, -- If true, only placer can open the door (locked for others)
 
-###Fence gate definition
+### Fence gate definition
 
 	description = "Wooden Fence Gate",
 	texture = "default_wood.png",
@@ -135,6 +135,7 @@ The doors mod allows modders to register custom doors and trapdoors.
 
 Fence API
 ---------
+
 Allows creation of new fences with "fencelike" drawtype.
 
 `default.register_fence(name, item definition)`
@@ -144,7 +145,7 @@ Allows creation of new fences with "fencelike" drawtype.
  nodedef fields here except drawtype. The fence group will always be added
  for this node.
 
-###fence definition
+### fence definition
 
 	name = "default:fence_wood",
 	description = "Wooden Fence",
@@ -153,8 +154,9 @@ Allows creation of new fences with "fencelike" drawtype.
 	groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
 	sounds = default.node_sound_wood_defaults(),
 
-#Walls API
+Walls API
 ---------
+
 The walls API allows easy addition of stone auto-connecting wall nodes.
 
 walls.register(name, desc, texture, mat, sounds)
@@ -175,7 +177,7 @@ The farming API allows you to easily register plants and hoes.
 `farming.register_plant(name, Plant definition)`
  * Register a new growing plant, see [#Plant definition]
 
-###Hoe Definition
+### Hoe Definition
 
 
 	{
@@ -190,7 +192,7 @@ The farming API allows you to easily register plants and hoes.
 		}
 	}
 
-###Plant definition
+### Plant definition
 
 	{
 		description = "",                      -- Description of seed item
@@ -204,11 +206,115 @@ The farming API allows you to easily register plants and hoes.
 Fire API
 --------
 
+New node def property:
+
 `on_burn(pos)`
 
  * Called when fire attempts to remove a burning node.
  * `pos` Position of the burning node.
 
+Give Initial Stuff API
+----------------------
+
+`give_initial_stuff.give(player)`
+
+^ Give initial stuff to "player"
+
+`give_initial_stuff.add(stack)`
+
+^ Add item to the initial stuff
+^ Stack can be an ItemStack or a item name eg: "default:dirt 99"
+^ Can be called after the game has loaded
+
+`give_initial_stuff.clear()`
+
+^ Removes all items from the initial stuff
+^ Can be called after the game has loaded
+
+`give_initial_stuff.get_list()`
+
+^ returns list of item stacks
+
+`give_initial_stuff.set_list(list)`
+
+^ List of initial items with numeric indices.
+
+`give_initial_stuff.add_from_csv(str)`
+
+^ str is a comma separated list of initial stuff
+^ Adds items to the list of items to be given
+
+
+TNT API
+----------
+
+`tnt.register_tnt(definition)`
+
+^ Register a new type of tnt.
+
+ * `name` The name of the node. If no prefix is given `tnt` is used.
+ * `description` A description for your TNT.
+ * `radius` The radius within which the TNT can destroy nodes. The default is 3.
+ * `damage_radius` The radius within which the TNT can damage players and mobs. By default it is twice the `radius`.
+ * `disable_drops` Disable drops. By default it is set to false.
+ * `ignore_protection` Don't check `minetest.is_protected` before removing a node.
+ * `ignore_on_blast` Don't call `on_blast` even if a node has one.
+ * `tiles` Textures for node
+  * `side`  Side tiles. By default the name of the tnt with a suffix of `_side.png`.
+  * `top`  Top tile. By default the name of the tnt with a suffix of `_top.png`.
+  * `bottom` Bottom tile. By default the name of the tnt with a suffix of `_bottom.png`.
+  * `burning` Top tile when lit. By default the name of the tnt with a suffix of `_top_burning_animated.png".
+
+`tnt.boom(position, definition)`
+
+^ Create an explosion.
+
+* `position` The center of explosion.
+* `definition` The TNT definion as passed to `tnt.register`
+
+`tnt.burn(position)`
+
+^ Ignite TNT at position
+
+
+To make dropping items from node inventories easier, you can use the
+following helper function from 'default':
+
+default.get_inventory_drops(pos, inventory, drops)
+
+^ Return drops from node inventory "inventory" in drops.
+
+* `pos` - the node position
+* `inventory` - the name of the inventory (string)
+* `drops` - an initialized list
+
+The function returns no values. The drops are returned in the `drops`
+parameter, and drops is not reinitialized so you can call it several
+times in a row to add more inventory items to it.
+
+
+`on_blast` callbacks:
+
+Both nodedefs and entitydefs can provide an `on_blast()` callback
+
+`nodedef.on_blast(pos, intensity)`
+^ Allow drop and node removal overriding
+* `pos` - node position
+* `intensity` - TNT explosion measure. larger or equal to 1.0
+^ Should return a list of drops (e.g. {"default:stone"})
+^ Should perform node removal itself. If callback exists in the nodedef
+^ then the TNT code will not destroy this node.
+
+`entitydef.on_blast(luaobj, damage)`
+^ Allow TNT effects on entities to be overridden
+* `luaobj` - LuaEntityRef of the entity
+* `damage` - suggested HP damage value
+^ Should return a list of (bool do_damage, bool do_knockback, table drops)
+* `do_damage` - if true then TNT mod wil damage the entity
+* `do_knockback` - if true then TNT mod will knock the entity away
+* `drops` - a list of drops, e.g. {"wool:red"}
+
+
 Screwdriver API
 ---------------
 
@@ -270,7 +376,7 @@ Creates panes that automatically connect to each other
  * `subname`: used for nodename. Result: "xpanes:subname" and "xpanes:subname_{2..15}"
  * `def`: See [#Pane definition]
 
-###Pane definition
+### Pane definition
 
 	{
 		textures = {"texture_Bottom_top", "texture_left_right", "texture_front_back"}, -- More tiles aren't supported
@@ -356,7 +462,7 @@ default.player_get_animation(player)
  * Any of the fields of the returned table may be nil.
  * player: PlayerRef
 
-###Model Definition
+### Model Definition
 
 	{
 		animation_speed = 30,            -- Default animation speed, in FPS.
@@ -395,7 +501,7 @@ To make recipes that will work with any dye ever made by anybody, define
 them based on groups. You can select any group of groups, based on your need for
 amount of colors.
 
-###Color groups
+### Color groups
 
 Base color groups:
 
@@ -450,7 +556,7 @@ Example of one shapeless recipe using a color group:
 		recipe = {'<mod>:item_no_color', 'group:basecolor_yellow'},
 	})
 
-###Color lists
+### Color lists
 
  * `dye.basecolors` are an array containing the names of available base colors
 
@@ -465,7 +571,7 @@ Trees
  * `default.grow_jungle_tree(pos)`
   * Grows a mgv6 jungletree at pos
 
- * `default.grow_pine_tree(pos)` 
+ * `default.grow_pine_tree(pos)`
   * Grows a mgv6 pinetree at pos
 
  * `default.grow_new_apple_tree(pos)`
diff --git a/minetest.conf.example b/minetest.conf.example
index ac5b8f68bcb328529c4c956ae0a8ece94e5cbeb3..813f19577fad185017a58b72e3c6e18b5a44f95f 100644
--- a/minetest.conf.example
+++ b/minetest.conf.example
@@ -18,8 +18,9 @@
 # 'permanent flame' nodes will remain with either setting
 #disable_fire = false
 
-# Whether steel tools, torches and cobblestone should be given to new players
+# Whether the stuff in initial_stuff should be given to new players
 #give_initial_stuff = false
+#initial_stuff = default:pick_steel,default:axe_steel,default:shovel_steel,default:torch 99,default:cobble 99
 
 # Whether the TNT mod should be enabled
 #enable_tnt = <true in singleplayer, false in multiplayer>
diff --git a/mods/beds/spawns.lua b/mods/beds/spawns.lua
index f3980a7a0b93bee685b7bd3dcf2eefda0bc06410..48b8a669428ebf991f553f4a951f8bcb03970bb5 100644
--- a/mods/beds/spawns.lua
+++ b/mods/beds/spawns.lua
@@ -41,10 +41,12 @@ function beds.save_spawns()
 	if not beds.spawn then
 		return
 	end
+	local data = {}
 	local output = io.open(org_file, "w")
-	for i, v in pairs(beds.spawn) do
-		output:write(v.x .. " " .. v.y .. " " .. v.z .. " " .. i .. "\n")
+	for k, v in pairs(beds.spawn) do
+		table.insert(data, string.format("%.1f %.1f %.1f %s\n", v.x, v.y, v.z, k))
 	end
+	output:write(table.concat(data))
 	io.close(output)
 end
 
diff --git a/mods/boats b/mods/boats
deleted file mode 160000
index ffaf921ce4a5644c62eced2754bffe1a41950e73..0000000000000000000000000000000000000000
--- a/mods/boats
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit ffaf921ce4a5644c62eced2754bffe1a41950e73
diff --git a/mods/bones/init.lua b/mods/bones/init.lua
index 018485a61dc2ce13ecc54dbf056e69547557fb68..10b98e36649c942d0f7490bd0789e256d5be8b7c 100644
--- a/mods/bones/init.lua
+++ b/mods/bones/init.lua
@@ -123,6 +123,8 @@ minetest.register_node("bones:bones", {
 			return true
 		end
 	end,
+	on_blast = function(pos)
+	end,
 })
 
 local function may_replace(pos, player)
diff --git a/mods/default/README.txt b/mods/default/README.txt
index 975f58a117e1b353141ba5b5397e5491b9f078e4..15a7f5affe49abdff6062995c8d4eed42be476f3 100644
--- a/mods/default/README.txt
+++ b/mods/default/README.txt
@@ -144,7 +144,6 @@ BlockMen (CC BY-SA 3.0):
   default_mineral_mese.png
   default_meselamp.png
   bubble.png
-  heart.png
   gui_*.png
 
 sofar (CC BY-SA 3.0):
@@ -222,3 +221,6 @@ Mito551 (sounds) (CC BY-SA):
   default_dirt_footstep.1.ogg
   default_dirt_footstep.2.ogg
   default_glass_footstep.ogg
+
+KevDoy (CC BY-SA 3.0)
+  heart.png
diff --git a/mods/default/craftitems.lua b/mods/default/craftitems.lua
index 8b8a6d35df8b2ae0623fadac6e17bf1fecd98776..2fe59784391028f6881347719856ca52d150996f 100644
--- a/mods/default/craftitems.lua
+++ b/mods/default/craftitems.lua
@@ -11,21 +11,30 @@ minetest.register_craftitem("default:paper", {
 	inventory_image = "default_paper.png",
 })
 
+local lpp = 14 -- Lines per book's page
 local function book_on_use(itemstack, user)
 	local player_name = user:get_player_name()
 	local data = minetest.deserialize(itemstack:get_metadata())
 	local formspec, title, text, owner = "", "", "", player_name
-	local page, page_max, cpp = 1, 1, 650
+	local page, page_max, lines, string = 1, 1, {}, ""
 
 	if data then
 		title = data.title
 		text = data.text
 		owner = data.owner
 
+		for str in (text .. "\n"):gmatch("([^\n]*)[\n]") do
+			lines[#lines+1] = str
+		end
+
 		if data.page then
 			page = data.page
 			page_max = data.page_max
-			cpp = data.chars_per_page
+
+			for i = ((lpp * page) - lpp) + 1, lpp * page do
+				if not lines[i] then break end
+				string = string .. lines[i] .. "\n"
+			end
 		end
 	end
 
@@ -44,8 +53,8 @@ local function book_on_use(itemstack, user)
 			"tablecolumns[color;text]" ..
 			"tableoptions[background=#00000000;highlight=#00000000;border=false]" ..
 			"table[0.4,0;7,0.5;title;#FFFF00," .. minetest.formspec_escape(title) .. "]" ..
-			"textarea[0.5,1.5;7.5,7;;" .. minetest.formspec_escape(text:sub(
-				(cpp * page) - cpp, cpp * page)) .. ";]" ..
+			"textarea[0.5,1.5;7.5,7;;" ..
+				minetest.formspec_escape(string ~= "" and string or text) .. ";]" ..
 			"button[2.4,7.6;0.8,0.8;book_prev;<]" ..
 			"label[3.2,7.7;Page " .. page .. " of " .. page_max .. "]" ..
 			"button[4.9,7.6;0.8,0.8;book_next;>]"
@@ -76,10 +85,9 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
 		if not data then data = {} end
 		data.title = fields.title
 		data.text = fields.text
-		data.text_len = fields.text:len()
+		data.text_len = #data.text
 		data.page = 1
-		data.chars_per_page = 650
-		data.page_max = math.ceil(data.text_len / data.chars_per_page)
+		data.page_max = math.ceil((#data.text:gsub("[^\n]", "") + 1) / lpp)
 		data.owner = player:get_player_name()
 		local data_str = minetest.serialize(data)
 
@@ -94,8 +102,6 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
 			stack:set_metadata(data_str)
 		end
 
-		player:set_wielded_item(stack)
-
 	elseif fields.book_next or fields.book_prev then
 		local data = minetest.deserialize(stack:get_metadata())
 		if not data.page then return end
@@ -116,6 +122,8 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
 		stack:set_metadata(data_str)
 		book_on_use(stack, player)
 	end
+
+	player:set_wielded_item(stack)
 end)
 
 minetest.register_craftitem("default:book", {
diff --git a/mods/default/functions.lua b/mods/default/functions.lua
index 8e8a3b46375e2dac7a4afe0bffcf9cf77b61bca2..c3d03a474bc9045b303be89f53270b268b087ba1 100644
--- a/mods/default/functions.lua
+++ b/mods/default/functions.lua
@@ -49,6 +49,18 @@ function default.node_sound_sand_defaults(table)
 	return table
 end
 
+function default.node_sound_gravel_defaults(table)
+	table = table or {}
+	table.footstep = table.footstep or
+			{name = "default_gravel_footstep", gain = 0.5}
+	table.dug = table.dug or
+			{name = "default_gravel_footstep", gain = 1.0}
+	table.place = table.place or
+			{name = "default_place_node", gain = 1.0}
+	default.node_sound_defaults(table)
+	return table
+end
+
 function default.node_sound_wood_defaults(table)
 	table = table or {}
 	table.footstep = table.footstep or
@@ -110,6 +122,21 @@ minetest.register_abm({
 })
 
 
+--
+-- optimized helper to put all items in an inventory into a drops list
+--
+function default.get_inventory_drops(pos, inventory, drops)
+	local inv = minetest.get_meta(pos):get_inventory()
+	local n = #drops
+	for i = 1, inv:get_size(inventory) do
+		local stack = inv:get_stack(inventory, i)
+		if stack:get_count() > 0 then
+			drops[n+1] = stack:to_table()
+			n = n + 1
+		end
+	end
+end
+
 --
 -- Papyrus and cactus growing
 --
@@ -342,38 +369,71 @@ minetest.register_abm({
 
 
 --
--- Grass growing on well-lit dirt
+-- Convert dirt to something that fits the environment
 --
 
 minetest.register_abm({
 	nodenames = {"default:dirt"},
-	neighbors = {"air"},
+	neighbors = {
+		"default:dirt_with_grass",
+		"default:dirt_with_dry_grass",
+		"default:dirt_with_snow",
+		"group:grass",
+		"group:dry_grass",
+		"default:snow",
+	},
 	interval = 6,
 	chance = 67,
 	catch_up = false,
 	action = function(pos, node)
+		-- Most likely case, half the time it's too dark for this.
 		local above = {x = pos.x, y = pos.y + 1, z = pos.z}
-		local name = minetest.get_node(above).name
-		local nodedef = minetest.registered_nodes[name]
-		if nodedef and (nodedef.sunlight_propagates or nodedef.paramtype == "light") and
-				nodedef.liquidtype == "none" and
-				(minetest.get_node_light(above) or 0) >= 13 then
-			if name == "default:snow" or name == "default:snowblock" then
-				minetest.set_node(pos, {name = "default:dirt_with_snow"})
-			else
-				minetest.set_node(pos, {name = "default:dirt_with_grass"})
+		if (minetest.get_node_light(above) or 0) < 13 then
+			return
+		end
+
+		-- Look for likely neighbors.
+		local p2 = minetest.find_node_near(pos, 1, {"default:dirt_with_grass",
+				"default:dirt_with_dry_grass", "default:dirt_with_snow"})
+		if p2 then
+			-- But the node needs to be under air in this case.
+			local n2 = minetest.get_node(above)
+			if n2 and n2.name == "air" then
+				local n3 = minetest.get_node(p2)
+				minetest.set_node(pos, {name = n3.name})
+				return
 			end
 		end
+
+		-- Anything on top?
+		local n2 = minetest.get_node(above)
+		if not n2 then
+			return
+		end
+
+		local name = n2.name
+		-- Snow check is cheapest, so comes first.
+		if name == "default:snow" then
+			minetest.set_node(pos, {name = "default:dirt_with_snow"})
+		-- Most likely case first.
+		elseif minetest.get_item_group(name, "grass") ~= 0 then
+			minetest.set_node(pos, {name = "default:dirt_with_grass"})
+		elseif minetest.get_item_group(name, "dry_grass") ~= 0 then
+			minetest.set_node(pos, {name = "default:dirt_with_dry_grass"})
+		end
 	end
 })
 
-
 --
 -- Grass and dry grass removed in darkness
 --
 
 minetest.register_abm({
-	nodenames = {"default:dirt_with_grass", "default:dirt_with_dry_grass"},
+	nodenames = {
+		"default:dirt_with_grass",
+		"default:dirt_with_dry_grass",
+		"default:dirt_with_snow",
+	},
 	interval = 8,
 	chance = 50,
 	catch_up = false,
diff --git a/mods/default/furnace.lua b/mods/default/furnace.lua
index e4dae7ebaab551b28f026f3108cab870978150d2..3047dc41fc4fb0ba4153badb2879947b8d53d46d 100644
--- a/mods/default/furnace.lua
+++ b/mods/default/furnace.lua
@@ -90,6 +90,137 @@ local function allow_metadata_inventory_take(pos, listname, index, stack, player
 	return stack:get_count()
 end
 
+local function swap_node(pos, name)
+	local node = minetest.get_node(pos)
+	if node.name == name then
+		return
+	end
+	node.name = name
+	minetest.swap_node(pos, node)
+end
+
+local function furnace_node_timer(pos, elapsed)
+	--
+	-- Inizialize metadata
+	--
+	local meta = minetest.get_meta(pos)
+	local fuel_time = meta:get_float("fuel_time") or 0
+	local src_time = meta:get_float("src_time") or 0
+	local fuel_totaltime = meta:get_float("fuel_totaltime") or 0
+
+	local inv = meta:get_inventory()
+	local srclist = inv:get_list("src")
+	local fuellist = inv:get_list("fuel")
+	local dstlist = inv:get_list("dst")
+
+	--
+	-- Cooking
+	--
+
+	-- Check if we have cookable content
+	local cooked, aftercooked = minetest.get_craft_result({method = "cooking", width = 1, items = srclist})
+	local cookable = true
+
+	if cooked.time == 0 then
+		cookable = false
+	end
+
+	-- Check if we have enough fuel to burn
+	if fuel_time < fuel_totaltime then
+		-- The furnace is currently active and has enough fuel
+		fuel_time = fuel_time + 1
+
+		-- If there is a cookable item then check if it is ready yet
+		if cookable then
+			src_time = src_time + 1
+			if src_time >= cooked.time then
+				-- Place result in dst list if possible
+				if inv:room_for_item("dst", cooked.item) then
+					inv:add_item("dst", cooked.item)
+					inv:set_stack("src", 1, aftercooked.items[1])
+					src_time = 0
+				end
+			end
+		end
+	else
+		-- Furnace ran out of fuel
+		if cookable then
+			-- We need to get new fuel
+			local fuel, afterfuel = minetest.get_craft_result({method = "fuel", width = 1, items = fuellist})
+
+			if fuel.time == 0 then
+				-- No valid fuel in fuel list
+				fuel_totaltime = 0
+				fuel_time = 0
+				src_time = 0
+			else
+				-- Take fuel from fuel list
+				inv:set_stack("fuel", 1, afterfuel.items[1])
+
+				fuel_totaltime = fuel.time
+				fuel_time = 0
+			end
+		else
+			-- We don't need to get new fuel since there is no cookable item
+			fuel_totaltime = 0
+			fuel_time = 0
+			src_time = 0
+		end
+	end
+
+	--
+	-- Update formspec, infotext and node
+	--
+	local formspec = inactive_formspec
+	local item_state = ""
+	local item_percent = 0
+	if cookable then
+		item_percent = math.floor(src_time / cooked.time * 100)
+		item_state = item_percent .. "%"
+	else
+		if srclist[1]:is_empty() then
+			item_state = "Empty"
+		else
+			item_state = "Not cookable"
+		end
+	end
+
+	local fuel_state = "Empty"
+	local active = "inactive "
+	local result = false
+
+	if fuel_time <= fuel_totaltime and fuel_totaltime ~= 0 then
+		active = "active "
+		local fuel_percent = math.floor(fuel_time / fuel_totaltime * 100)
+		fuel_state = fuel_percent .. "%"
+		formspec = active_formspec(fuel_percent, item_percent)
+		swap_node(pos, "default:furnace_active")
+		-- make sure timer restarts automatically
+		result = true
+	else
+		if not fuellist[1]:is_empty() then
+			fuel_state = "0%"
+		end
+		swap_node(pos, "default:furnace")
+		-- stop timer on the inactive furnace
+		local timer = minetest.get_node_timer(pos)
+		timer:stop()
+	end
+
+	local infotext = "Furnace " .. active .. "(Item: " .. item_state .. "; Fuel: " .. fuel_state .. ")"
+
+	--
+	-- Set meta values
+	--
+	meta:set_float("fuel_totaltime", fuel_totaltime)
+	meta:set_float("fuel_time", fuel_time)
+	meta:set_float("src_time", src_time)
+	meta:set_string("formspec", formspec)
+	meta:set_string("infotext", infotext)
+
+	return result
+end
+
 --
 -- Node definitions
 --
@@ -106,10 +237,40 @@ minetest.register_node("default:furnace", {
 	legacy_facedir_simple = true,
 	is_ground_content = false,
 	sounds = default.node_sound_stone_defaults(),
-	
+
 	can_dig = can_dig,
 
-    allow_metadata_inventory_put = allow_metadata_inventory_put,
+	on_timer = furnace_node_timer,
+
+	on_construct = function(pos)
+		local meta = minetest.get_meta(pos)
+		meta:set_string("formspec", inactive_formspec)
+		local inv = meta:get_inventory()
+		inv:set_size('src', 1)
+		inv:set_size('fuel', 1)
+		inv:set_size('dst', 4)
+	end,
+
+	on_metadata_inventory_move = function(pos)
+		local timer = minetest.get_node_timer(pos)
+		timer:start(1.0)
+	end,
+	on_metadata_inventory_put = function(pos)
+		-- start timer function, it will sort out whether furnace can burn or not.
+		local timer = minetest.get_node_timer(pos)
+		timer:start(1.0)
+	end,
+	on_blast = function(pos)
+		local drops = {}
+		default.get_inventory_drops(pos, "src", drops)
+		default.get_inventory_drops(pos, "fuel", drops)
+		default.get_inventory_drops(pos, "dst", drops)
+		drops[#drops+1] = "default:furnace"
+		minetest.remove_node(pos)
+		return drops
+	end,
+
+	allow_metadata_inventory_put = allow_metadata_inventory_put,
 	allow_metadata_inventory_move = allow_metadata_inventory_move,
 	allow_metadata_inventory_take = allow_metadata_inventory_take,
 })
@@ -138,154 +299,12 @@ minetest.register_node("default:furnace_active", {
 	legacy_facedir_simple = true,
 	is_ground_content = false,
 	sounds = default.node_sound_stone_defaults(),
-	
+	on_timer = furnace_node_timer,
+
 	can_dig = can_dig,
-	
+
 	allow_metadata_inventory_put = allow_metadata_inventory_put,
 	allow_metadata_inventory_move = allow_metadata_inventory_move,
 	allow_metadata_inventory_take = allow_metadata_inventory_take,
 })
 
---
--- ABM
---
-
-local function swap_node(pos, name)
-	local node = minetest.get_node(pos)
-	if node.name == name then
-		return
-	end
-	node.name = name
-	minetest.swap_node(pos, node)
-end
-
-minetest.register_abm({
-	nodenames = {"default:furnace", "default:furnace_active"},
-	interval = 1.0,
-	chance = 1,
-	action = function(pos, node, active_object_count, active_object_count_wider)
-		--
-		-- Inizialize metadata
-		--
-		local meta = minetest.get_meta(pos)
-		local fuel_time = meta:get_float("fuel_time") or 0
-		local src_time = meta:get_float("src_time") or 0
-		local fuel_totaltime = meta:get_float("fuel_totaltime") or 0
-		
-		--
-		-- Inizialize inventory
-		--
-		local inv = meta:get_inventory()
-		for listname, size in pairs({
-				src = 1,
-				fuel = 1,
-				dst = 4,
-		}) do
-			if inv:get_size(listname) ~= size then
-				inv:set_size(listname, size)
-			end
-		end
-		local srclist = inv:get_list("src")
-		local fuellist = inv:get_list("fuel")
-		local dstlist = inv:get_list("dst")
-		
-		--
-		-- Cooking
-		--
-		
-		-- Check if we have cookable content
-		local cooked, aftercooked = minetest.get_craft_result({method = "cooking", width = 1, items = srclist})
-		local cookable = true
-		
-		if cooked.time == 0 then
-			cookable = false
-		end
-		
-		-- Check if we have enough fuel to burn
-		if fuel_time < fuel_totaltime then
-			-- The furnace is currently active and has enough fuel
-			fuel_time = fuel_time + 1
-			
-			-- If there is a cookable item then check if it is ready yet
-			if cookable then
-				src_time = src_time + 1
-				if src_time >= cooked.time then
-					-- Place result in dst list if possible
-					if inv:room_for_item("dst", cooked.item) then
-						inv:add_item("dst", cooked.item)
-						inv:set_stack("src", 1, aftercooked.items[1])
-						src_time = 0
-					end
-				end
-			end
-		else
-			-- Furnace ran out of fuel
-			if cookable then
-				-- We need to get new fuel
-				local fuel, afterfuel = minetest.get_craft_result({method = "fuel", width = 1, items = fuellist})
-				
-				if fuel.time == 0 then
-					-- No valid fuel in fuel list
-					fuel_totaltime = 0
-					fuel_time = 0
-					src_time = 0
-				else
-					-- Take fuel from fuel list
-					inv:set_stack("fuel", 1, afterfuel.items[1])
-					
-					fuel_totaltime = fuel.time
-					fuel_time = 0
-					
-				end
-			else
-				-- We don't need to get new fuel since there is no cookable item
-				fuel_totaltime = 0
-				fuel_time = 0
-				src_time = 0
-			end
-		end
-		
-		--
-		-- Update formspec, infotext and node
-		--
-		local formspec = inactive_formspec
-		local item_state = ""
-		local item_percent = 0
-		if cookable then
-			item_percent =  math.floor(src_time / cooked.time * 100)
-			item_state = item_percent .. "%"
-		else
-			if srclist[1]:is_empty() then
-				item_state = "Empty"
-			else
-				item_state = "Not cookable"
-			end
-		end
-		
-		local fuel_state = "Empty"
-		local active = "inactive "
-		if fuel_time <= fuel_totaltime and fuel_totaltime ~= 0 then
-			active = "active "
-			local fuel_percent = math.floor(fuel_time / fuel_totaltime * 100)
-			fuel_state = fuel_percent .. "%"
-			formspec = active_formspec(fuel_percent, item_percent)
-			swap_node(pos, "default:furnace_active")
-		else
-			if not fuellist[1]:is_empty() then
-				fuel_state = "0%"
-			end
-			swap_node(pos, "default:furnace")
-		end
-		
-		local infotext =  "Furnace " .. active .. "(Item: " .. item_state .. "; Fuel: " .. fuel_state .. ")"
-		
-		--
-		-- Set meta values
-		--
-		meta:set_float("fuel_totaltime", fuel_totaltime)
-		meta:set_float("fuel_time", fuel_time)
-		meta:set_float("src_time", src_time)
-		meta:set_string("formspec", formspec)
-		meta:set_string("infotext", infotext)
-	end,
-})
diff --git a/mods/default/models/character.b3d b/mods/default/models/character.b3d
index 0a0ca6292abb67cb2ad794f3e91c948024739c47..2699d65b46a8b9a01c060e440efc981617e64882 100644
Binary files a/mods/default/models/character.b3d and b/mods/default/models/character.b3d differ
diff --git a/mods/default/models/character.blend b/mods/default/models/character.blend
index 6493f813d70fb4dd2107ec7d5fbf584bf083c275..b5c7fc3aff1410f5db3c1a610a086906631068b0 100644
Binary files a/mods/default/models/character.blend and b/mods/default/models/character.blend differ
diff --git a/mods/default/nodes.lua b/mods/default/nodes.lua
index a47bd7cebd9ab6252ba85e32858c80a6bc8a3643..0a04ad10602d8ecda3f5c19ba9358187ef101989 100644
--- a/mods/default/nodes.lua
+++ b/mods/default/nodes.lua
@@ -353,10 +353,7 @@ minetest.register_node("default:gravel", {
 	description = "Gravel",
 	tiles = {"default_gravel.png"},
 	groups = {crumbly = 2, falling_node = 1},
-	sounds = default.node_sound_dirt_defaults({
-		footstep = {name = "default_gravel_footstep", gain = 0.5},
-		dug = {name = "default_gravel_footstep", gain = 1.0},
-	}),
+	sounds = default.node_sound_gravel_defaults(),
 	drop = {
 		max_items = 1,
 		items = {
@@ -947,7 +944,7 @@ minetest.register_node("default:junglegrass", {
 	sunlight_propagates = true,
 	walkable = false,
 	buildable_to = true,
-	groups = {snappy = 3, flora = 1, attached_node = 1},
+	groups = {snappy = 3, flora = 1, attached_node = 1, grass = 1},
 	sounds = default.node_sound_leaves_defaults(),
 	selection_box = {
 		type = "fixed",
@@ -968,7 +965,7 @@ minetest.register_node("default:grass_1", {
 	sunlight_propagates = true,
 	walkable = false,
 	buildable_to = true,
-	groups = {snappy = 3, flora = 1, attached_node = 1},
+	groups = {snappy = 3, flora = 1, attached_node = 1, grass = 1},
 	sounds = default.node_sound_leaves_defaults(),
 	selection_box = {
 		type = "fixed",
@@ -998,7 +995,7 @@ for i = 2, 5 do
 		buildable_to = true,
 		drop = "default:grass_1",
 		groups = {snappy = 3, flora = 1, attached_node = 1,
-			not_in_creative_inventory = 1},
+			not_in_creative_inventory = 1, grass = 1},
 		sounds = default.node_sound_leaves_defaults(),
 		selection_box = {
 			type = "fixed",
@@ -1019,7 +1016,8 @@ minetest.register_node("default:dry_grass_1", {
 	sunlight_propagates = true,
 	walkable = false,
 	buildable_to = true,
-	groups = {snappy = 3, flammable = 3, flora = 1, attached_node = 1},
+	groups = {snappy = 3, flammable = 3, flora = 1,
+		attached_node = 1, dry_grass = 1},
 	sounds = default.node_sound_leaves_defaults(),
 	selection_box = {
 		type = "fixed",
@@ -1047,8 +1045,8 @@ for i = 2, 5 do
 		sunlight_propagates = true,
 		walkable = false,
 		buildable_to = true,
-		groups = {snappy = 3, flammable = 3, flora = 1,
-			attached_node = 1, not_in_creative_inventory=1},
+		groups = {snappy = 3, flammable = 3, flora = 1, attached_node = 1,
+			not_in_creative_inventory=1, dry_grass = 1},
 		drop = "default:dry_grass_1",
 		sounds = default.node_sound_leaves_defaults(),
 		selection_box = {
@@ -1473,6 +1471,13 @@ minetest.register_node("default:chest", {
 			" takes " .. stack:get_name() ..
 			" from chest at " .. minetest.pos_to_string(pos))
 	end,
+	on_blast = function(pos)
+		local drops = {}
+		default.get_inventory_drops(pos, "main", drops)
+		drops[#drops+1] = "default:chest"
+		minetest.remove_node(pos)
+		return drops
+	end,
 })
 
 minetest.register_node("default:chest_locked", {
@@ -1596,6 +1601,13 @@ minetest.register_node("default:bookshelf", {
 		minetest.log("action", player:get_player_name() ..
 			" takes stuff from bookshelf at " .. minetest.pos_to_string(pos))
 	end,
+	on_blast = function(pos)
+		local drops = {}
+		default.get_inventory_drops(pos, "books", drops)
+		drops[#drops+1] = "default:bookshelf"
+		minetest.remove_node(pos)
+		return drops
+	end,
 })
 
 local function register_sign(material, desc, def)
@@ -1757,7 +1769,7 @@ minetest.register_node("default:obsidian_glass", {
 	is_ground_content = false,
 	sunlight_propagates = true,
 	sounds = default.node_sound_glass_defaults(),
-	groups = {cracky = 3, oddly_breakable_by_hand = 3},
+	groups = {cracky = 3},
 })
 
 
diff --git a/mods/default/textures/default_grass.png b/mods/default/textures/default_grass.png
index 7c5a8b82fbfb27460f92b482a1841e5dcb0a398b..0181fabda9fbaba45d8f763107bf0ad88e2f81c5 100644
Binary files a/mods/default/textures/default_grass.png and b/mods/default/textures/default_grass.png differ
diff --git a/mods/default/textures/default_grass_side.png b/mods/default/textures/default_grass_side.png
index d6c57c7a0f7fe5d0ce27bc1b55633aaf43b14299..1a853b2ee1f64bc6283beb9ba025e2555d56ac41 100644
Binary files a/mods/default/textures/default_grass_side.png and b/mods/default/textures/default_grass_side.png differ
diff --git a/mods/default/textures/heart.png b/mods/default/textures/heart.png
index fb8dcc7ed6249ca05ebbd019b8d41c9059baab43..6d4e228930dd00bcff09f16a30e2873ce1c7feb3 100644
Binary files a/mods/default/textures/heart.png and b/mods/default/textures/heart.png differ
diff --git a/mods/doors/init.lua b/mods/doors/init.lua
index 9e0999394372266ca21fd97d17bcfa73682531f5..90ddcc3d4bfe12c1bef1c76c6accd9034e4307a6 100644
--- a/mods/doors/init.lua
+++ b/mods/doors/init.lua
@@ -203,11 +203,10 @@ function doors.register(name, def)
 	end
 
 	-- replace old doors of this type automatically
-	minetest.register_abm({
+	minetest.register_lbm({
+		name = ":doors:replace_" .. name:gsub(":", "_"),
 		nodenames = {name.."_b_1", name.."_b_2"},
-		interval = 7.0,
-		chance = 1,
-		action = function(pos, node, active_object_count, active_object_count_wider)
+		action = function(pos, node)
 			local l = tonumber(node.name:sub(-1))
 			local meta = minetest.get_meta(pos)
 			local h = meta:get_int("right") + 1
diff --git a/mods/farming/api.lua b/mods/farming/api.lua
index a0c1992f2492291fcc88228b98e129be5accaa4a..68f7be752c32f7211774a731e4c2fbf7dee86832 100644
--- a/mods/farming/api.lua
+++ b/mods/farming/api.lua
@@ -220,9 +220,14 @@ farming.register_plant = function(name, def)
 			fixed = {-0.5, -0.5, -0.5, 0.5, -5/16, 0.5},
 		},
 		fertility = def.fertility,
+		sounds = default.node_sound_dirt_defaults({
+			dug = {name = "default_grass_footstep", gain = 0.2},
+			place = {name = "default_place_node", gain = 0.25},
+		}),
+
 		on_place = function(itemstack, placer, pointed_thing)
 			return farming.place_seed(itemstack, placer, pointed_thing, mname .. ":seed_" .. pname)
-		end
+		end,
 	})
 
 	-- Register harvest
diff --git a/mods/fire/init.lua b/mods/fire/init.lua
index 15f71560b05dde37aa1d624a42a88022c194e56c..457f6b5e68f0f1167f96a243dc5c2ebd3ec9d535 100644
--- a/mods/fire/init.lua
+++ b/mods/fire/init.lua
@@ -263,7 +263,7 @@ else
 				minetest.remove_node(p0)
 				return
 			end
-			if math.random(1, 4) == 1 then
+			if math.random(1, 3) == 1 then
 				-- remove flammable nodes around flame
 				local node = minetest.get_node(p)
 				local def = minetest.registered_nodes[node.name]
diff --git a/mods/flowers/init.lua b/mods/flowers/init.lua
index f5e6d2cf8ece56b49bb81fda91e3b40779fba150..2d8c93e4ab20810acd8ca98aeb7f75f253a64df0 100644
--- a/mods/flowers/init.lua
+++ b/mods/flowers/init.lua
@@ -71,50 +71,52 @@ end
 
 
 -- Flower spread
+-- Public function to enable override by mods
+
+function flowers.flower_spread(pos, node)
+	pos.y = pos.y - 1
+	local under = minetest.get_node(pos)
+	pos.y = pos.y + 1
+	if under.name == "default:desert_sand" then
+		minetest.set_node(pos, {name = "default:dry_shrub"})
+		return
+	elseif under.name ~= "default:dirt_with_grass" and
+			under.name ~= "default:dirt_with_dry_grass" then
+		return
+	end
 
-minetest.register_abm({
-	nodenames = {"group:flora"},
-	neighbors = {"default:dirt_with_grass", "default:desert_sand"},
-	interval = 13,
-	chance = 96,
-	action = function(pos, node)
-		pos.y = pos.y - 1
-		local under = minetest.get_node(pos)
-		pos.y = pos.y + 1
-		if under.name == "default:desert_sand" then
-			minetest.set_node(pos, {name = "default:dry_shrub"})
-		elseif under.name ~= "default:dirt_with_grass" then
-			return
-		end
-
-		local light = minetest.get_node_light(pos)
-		if not light or light < 13 then
-			return
-		end
+	local light = minetest.get_node_light(pos)
+	if not light or light < 13 then
+		return
+	end
 
-		local pos0 = {x = pos.x - 4, y = pos.y - 4, z = pos.z - 4}
-		local pos1 = {x = pos.x + 4, y = pos.y + 4, z = pos.z + 4}
-		if #minetest.find_nodes_in_area(pos0, pos1, "group:flora_block") > 0 then
-			return
-		end
+	local pos0 = vector.subtract(pos, 4)
+	local pos1 = vector.add(pos, 4)
+	if #minetest.find_nodes_in_area(pos0, pos1, "group:flora") > 3 then
+		return
+	end
 
-		local flowers = minetest.find_nodes_in_area(pos0, pos1, "group:flora")
-		if #flowers > 3 then
+	local seedling = minetest.find_nodes_in_area_under_air(pos0, pos1,
+		{"default:dirt_with_grass", "default:dirt_with_dry_grass"})
+	if #seedling > 0 then
+		seedling = seedling[math.random(#seedling)]
+		seedling.y = seedling.y + 1
+		light = minetest.get_node_light(seedling)
+		if not light or light < 13 then
 			return
 		end
+		minetest.set_node(seedling, {name = node.name})
+	end
+end
 
-		local seedling = minetest.find_nodes_in_area(pos0, pos1, "default:dirt_with_grass")
-		if #seedling > 0 then
-			seedling = seedling[math.random(#seedling)]
-			seedling.y = seedling.y + 1
-			light = minetest.get_node_light(seedling)
-			if not light or light < 13 then
-				return
-			end
-			if minetest.get_node(seedling).name == "air" then
-				minetest.set_node(seedling, {name = node.name})
-			end
-		end
+minetest.register_abm({
+	nodenames = {"group:flora"},
+	neighbors = {"default:dirt_with_grass", "default:dirt_with_dry_grass",
+		"default:desert_sand"},
+	interval = 13,
+	chance = 96,
+	action = function(...)
+		flowers.flower_spread(...)
 	end,
 })
 
@@ -161,7 +163,9 @@ minetest.register_node("flowers:mushroom_brown", {
 	}
 })
 
--- mushroom spread and death
+
+-- Mushroom spread and death
+
 minetest.register_abm({
 	nodenames = {"flowers:mushroom_brown", "flowers:mushroom_red"},
 	interval = 11,
@@ -169,17 +173,15 @@ minetest.register_abm({
 	action = function(pos, node)
 		if minetest.get_node_light(pos, nil) == 15 then
 			minetest.remove_node(pos)
+			return
 		end
 		local random = {
-			x = pos.x + math.random(-2,2),
-			y = pos.y + math.random(-1,1),
-			z = pos.z + math.random(-2,2)
+			x = pos.x + math.random(-2, 2),
+			y = pos.y + math.random(-1, 1),
+			z = pos.z + math.random(-2, 2)
 		}
 		local random_node = minetest.get_node_or_nil(random)
-		if not random_node then
-			return
-		end
-		if random_node.name ~= "air" then
+		if not random_node or random_node.name ~= "air" then
 			return
 		end
 		local node_under = minetest.get_node_or_nil({x = random.x,
@@ -187,15 +189,19 @@ minetest.register_abm({
 		if not node_under then
 			return
 		end
-		if minetest.get_item_group(node_under.name, "soil") ~= 0 and
-				minetest.get_node_light(pos, nil) <= 9 and
-				minetest.get_node_light(random, nil) <= 9 then
+
+		if (minetest.get_item_group(node_under.name, "soil") ~= 0 or
+				minetest.get_item_group(node_under.name, "tree") ~= 0) and
+				minetest.get_node_light(pos, 0.5) <= 3 and
+				minetest.get_node_light(random, 0.5) <= 3 then
 			minetest.set_node(random, {name = node.name})
 		end
 	end
 })
 
--- these old mushroom related nodes can be simplified now
+
+-- These old mushroom related nodes can be simplified now
+
 minetest.register_alias("flowers:mushroom_spores_brown", "flowers:mushroom_brown")
 minetest.register_alias("flowers:mushroom_spores_red", "flowers:mushroom_red")
 minetest.register_alias("flowers:mushroom_fertile_brown", "flowers:mushroom_brown")
@@ -220,6 +226,7 @@ minetest.register_node("flowers:waterlily", {
 	sunlight_propagates = true,
 	groups = {snappy = 3, flower = 1},
 	sounds = default.node_sound_leaves_defaults(),
+	node_placement_prediction = "",
 	node_box = {
 		type = "fixed",
 		fixed = {-0.5, -0.5, -0.5, 0.5, -0.46875, 0.5}
@@ -229,12 +236,22 @@ minetest.register_node("flowers:waterlily", {
 		fixed = {-0.5, -0.5, -0.5, 0.5, -0.4375, 0.5}
 	},
 
-	on_place = function(_, _, pointed_thing)
+	on_place = function(itemstack, placer, pointed_thing)
 		local pos = pointed_thing.above
 		local node = minetest.get_node(pointed_thing.under).name
 		local def = minetest.registered_nodes[node]
+		local player_name = placer:get_player_name()
+
 		if def and def.liquidtype == "source" and minetest.get_item_group(node, "water") > 0 then
-			minetest.set_node(pos, {name = "flowers:waterlily", param2 = math.random(0, 3)})
+			if not minetest.is_protected(pos, player_name) then
+				minetest.set_node(pos, {name = "flowers:waterlily", param2 = math.random(0, 3)})
+			else
+				minetest.chat_send_player(player_name, "This area is protected")
+			end
+			if not minetest.setting_getbool("creative_mode") then
+				itemstack:take_item()
+				return itemstack
+			end
 		end
 	end
 })
diff --git a/mods/tnt/init.lua b/mods/tnt/init.lua
index 003ef4400f4a0a4335fdf340c398c322816fb492..49fd70100806fe7f546f0e42f5271ef71a904189 100644
--- a/mods/tnt/init.lua
+++ b/mods/tnt/init.lua
@@ -1,15 +1,6 @@
--- TNT will need privilege!
+tnt = {}
 core.register_privilege("trusted_player", "special grantings, used for tnt for example")
 
----We want to use it on server, so this is commented
--- Default to enabled in singleplayer and disabled in multiplayer
--- local singleplayer = minetest.is_singleplayer()
--- local setting = minetest.setting_getbool("enable_tnt")
--- if (not singleplayer and setting ~= true) or
--- 		(singleplayer and setting == false) then
--- 	return
--- end
-
 -- loss probabilities array (one in X will be lost)
 local loss_prob = {}
 
@@ -52,23 +43,22 @@ local function eject_drops(drops, pos, radius)
 	local drop_pos = vector.new(pos)
 	for _, item in pairs(drops) do
 		local count = item:get_count()
-		local max = item:get_stack_max()
-		if count > max then
-			item:set_count(max)
-		end
 		while count > 0 do
-			if count < max then
-				item:set_count(count)
-			end
+			local take = math.max(1,math.min(radius * radius,
+					item:get_count(),
+					item:get_stack_max()))
 			rand_pos(pos, drop_pos, radius)
-			local obj = minetest.add_item(drop_pos, item)
+			local dropitem = ItemStack(item)
+			dropitem:set_count(take)
+			local obj = minetest.add_item(drop_pos, dropitem)
 			if obj then
 				obj:get_luaentity().collect = true
-				obj:setacceleration({x=0, y=-10, z=0})
-				obj:setvelocity({x=math.random(-3, 3), y=10,
-						z=math.random(-3, 3)})
+				obj:setacceleration({x = 0, y = -10, z = 0})
+				obj:setvelocity({x = math.random(-3, 3),
+						y = math.random(0, 10),
+						z = math.random(-3, 3)})
 			end
-			count = count - max
+			count = count - take
 		end
 	end
 end
@@ -88,27 +78,27 @@ local function add_drop(drops, item)
 	end
 end
 
-local fire_node = {name="fire:basic_flame"}
 
-local function destroy(drops, pos, cid)
-	if minetest.is_protected(pos, "") then
-		return
+local function destroy(drops, npos, cid, c_air, c_fire, on_blast_queue, ignore_protection, ignore_on_blast)
+	if not ignore_protection and minetest.is_protected(npos, "") then
+		return cid
 	end
+
 	local def = cid_data[cid]
-	if def and def.on_blast then
-		def.on_blast(vector.new(pos), 1)
-		return
-	end
-	if def and def.flammable then
-		minetest.set_node(pos, fire_node)
+
+	if not def then
+		return c_air
+	elseif not ignore_on_blast and def.on_blast then
+		on_blast_queue[#on_blast_queue + 1] = {pos = vector.new(npos), on_blast = def.on_blast}
+		return cid
+	elseif def.flammable then
+		return c_fire
 	else
-		minetest.remove_node(pos)
-		if def then
-			local node_drops = minetest.get_node_drops(def.name, "")
-			for _, item in ipairs(node_drops) do
-				add_drop(drops, item)
-			end
+		local node_drops = minetest.get_node_drops(def.name, "")
+		for _, item in ipairs(node_drops) do
+			add_drop(drops, item)
 		end
+		return c_air
 	end
 end
 
@@ -125,61 +115,179 @@ local function calc_velocity(pos1, pos2, old_vel, power)
 
 	-- Add old velocity
 	vel = vector.add(vel, old_vel)
+
+	-- randomize it a bit
+	vel = vector.add(vel, {
+		x = math.random() - 0.5,
+		y = math.random() - 0.5,
+		z = math.random() - 0.5,
+	})
+
+	-- Limit to terminal velocity
+	dist = vector.length(vel)
+	if dist > 250 then
+		vel = vector.divide(vel, dist / 250)
+	end
 	return vel
 end
 
-local function entity_physics(pos, radius)
-	-- Make the damage radius larger than the destruction radius
-	radius = radius * 2
+local function entity_physics(pos, radius, drops)
 	local objs = minetest.get_objects_inside_radius(pos, radius)
 	for _, obj in pairs(objs) do
 		local obj_pos = obj:getpos()
-		local obj_vel = obj:getvelocity()
 		local dist = math.max(1, vector.distance(pos, obj_pos))
 
-		if obj_vel ~= nil then
-			obj:setvelocity(calc_velocity(pos, obj_pos,
-					obj_vel, radius * 10))
-		end
-
 		local damage = (4 / dist) * radius
-		obj:set_hp(obj:get_hp() - damage)
+		if obj:is_player() then
+			-- currently the engine has no method to set
+			-- player velocity. See #2960
+			-- instead, we knock the player back 1.0 node, and slightly upwards
+			local dir = vector.normalize(vector.subtract(obj_pos, pos))
+			local moveoff = vector.multiply(dir, dist + 1.0)
+			local newpos = vector.add(pos, moveoff)
+			local newpos = vector.add(newpos, {x = 0, y = 0.2, z = 0})
+			obj:setpos(newpos)
+
+			obj:set_hp(obj:get_hp() - damage)
+		else
+			local do_damage = true
+			local do_knockback = true
+			local entity_drops = {}
+			local luaobj = obj:get_luaentity()
+			local objdef = minetest.registered_entities[luaobj.name]
+
+			if objdef and objdef.on_blast then
+				do_damage, do_knockback, entity_drops = objdef.on_blast(luaobj, damage)
+			end
+
+			if do_knockback then
+				local obj_vel = obj:getvelocity()
+				obj:setvelocity(calc_velocity(pos, obj_pos,
+						obj_vel, radius * 10))
+			end
+			if do_damage then
+				if not obj:get_armor_groups().immortal then
+					obj:punch(obj, 1.0, {
+						full_punch_interval = 1.0,
+						damage_groups = {fleshy = damage},
+					}, nil)
+				end
+			end
+			for _, item in ipairs(entity_drops) do
+				add_drop(drops, item)
+			end
+		end
 	end
 end
 
-local function add_effects(pos, radius)
+local function add_effects(pos, radius, drops)
+	minetest.add_particle({
+		pos = pos,
+		velocity = vector.new(),
+		acceleration = vector.new(),
+		expirationtime = 0.4,
+		size = radius * 10,
+		collisiondetection = false,
+		vertical = false,
+		texture = "tnt_boom.png",
+	})
 	minetest.add_particlespawner({
-		amount = 128,
-		time = 1,
+		amount = 64,
+		time = 0.5,
 		minpos = vector.subtract(pos, radius / 2),
 		maxpos = vector.add(pos, radius / 2),
-		minvel = {x=-20, y=-20, z=-20},
-		maxvel = {x=20,  y=20,  z=20},
+		minvel = {x = -10, y = -10, z = -10},
+		maxvel = {x = 10, y = 10, z = 10},
 		minacc = vector.new(),
 		maxacc = vector.new(),
 		minexptime = 1,
-		maxexptime = 3,
-		minsize = 8,
-		maxsize = 16,
+		maxexptime = 2.5,
+		minsize = radius * 3,
+		maxsize = radius * 5,
 		texture = "tnt_smoke.png",
 	})
+
+	-- we just dropped some items. Look at the items entities and pick
+	-- one of them to use as texture
+	local texture = "tnt_blast.png" --fallback texture
+	local most = 0
+	for name, stack in pairs(drops) do
+		local count = stack:get_count()
+		if count > most then
+			most = count
+			local def = minetest.registered_nodes[name]
+			if def and def.tiles and def.tiles[1] then
+				texture = def.tiles[1]
+			end
+		end
+	end
+
+	minetest.add_particlespawner({
+		amount = 64,
+		time = 0.1,
+		minpos = vector.subtract(pos, radius / 2),
+		maxpos = vector.add(pos, radius / 2),
+		minvel = {x = -3, y = 0, z = -3},
+		maxvel = {x = 3, y = 5,  z = 3},
+		minacc = {x = 0, y = -10, z = 0},
+		maxacc = {x = 0, y = -10, z = 0},
+		minexptime = 0.8,
+		maxexptime = 2.0,
+		minsize = radius * 0.66,
+		maxsize = radius * 2,
+		texture = texture,
+		collisiondetection = true,
+	})
 end
 
-local function burn(pos)
+function tnt.burn(pos)
 	local name = minetest.get_node(pos).name
-	if name == "tnt:tnt" then
-		minetest.sound_play("tnt_ignite", {pos=pos})
-		minetest.set_node(pos, {name="tnt:tnt_burning"})
+	local group = minetest.get_item_group(name, "tnt")
+	if group > 0 then
+		minetest.sound_play("tnt_ignite", {pos = pos})
+		minetest.set_node(pos, {name = name .. "_burning"})
 		minetest.get_node_timer(pos):start(1)
 	elseif name == "tnt:gunpowder" then
-		minetest.sound_play("tnt_gunpowder_burning", {pos=pos, gain=2})
-		minetest.set_node(pos, {name="tnt:gunpowder_burning"})
-		minetest.get_node_timer(pos):start(1)
+		minetest.set_node(pos, {name = "tnt:gunpowder_burning"})
 	end
 end
 
-local function explode(pos, radius)
+local function tnt_explode(pos, radius, ignore_protection, ignore_on_blast)
 	local pos = vector.round(pos)
+	-- scan for adjacent TNT nodes first, and enlarge the explosion
+	local vm1 = VoxelManip()
+	local p1 = vector.subtract(pos, 2)
+	local p2 = vector.add(pos, 2)
+	local minp, maxp = vm1:read_from_map(p1, p2)
+	local a = VoxelArea:new({MinEdge = minp, MaxEdge = maxp})
+	local data = vm1:get_data()
+	local count = 0
+	local c_tnt = minetest.get_content_id("tnt:tnt")
+	local c_tnt_burning = minetest.get_content_id("tnt:tnt_burning")
+	local c_tnt_boom = minetest.get_content_id("tnt:boom")
+	local c_air = minetest.get_content_id("air")
+
+	for z = pos.z - 2, pos.z + 2 do
+	for y = pos.y - 2, pos.y + 2 do
+		local vi = a:index(pos.x - 2, y, z)
+		for x = pos.x - 2, pos.x + 2 do
+			local cid = data[vi]
+			if cid == c_tnt or cid == c_tnt_boom or cid == c_tnt_burning then
+				count = count + 1
+				data[vi] = c_air
+			end
+			vi = vi + 1
+		end
+	end
+	end
+
+	vm1:set_data(data)
+	vm1:write_to_map()
+
+	-- recalculate new radius
+	radius = math.floor(radius * math.pow(count, 1/3))
+
+	-- perform the explosion
 	local vm = VoxelManip()
 	local pr = PseudoRandom(os.time())
 	local p1 = vector.subtract(pos, radius)
@@ -189,22 +297,21 @@ local function explode(pos, radius)
 	local data = vm:get_data()
 
 	local drops = {}
-	local p = {}
-
-	local c_air = minetest.get_content_id("air")
+	local on_blast_queue = {}
 
+	local c_fire = minetest.get_content_id("fire:basic_flame")
 	for z = -radius, radius do
 	for y = -radius, radius do
 	local vi = a:index(pos.x + (-radius), pos.y + y, pos.z + z)
 	for x = -radius, radius do
-		if (x * x) + (y * y) + (z * z) <=
-				(radius * radius) + pr:next(-radius, radius) then
+		local r = vector.length(vector.new(x, y, z))
+		if (radius * radius) / (r * r) >= (pr:next(80, 125) / 100) then
 			local cid = data[vi]
-			p.x = pos.x + x
-			p.y = pos.y + y
-			p.z = pos.z + z
+			local p = {x = pos.x + x, y = pos.y + y, z = pos.z + z}
 			if cid ~= c_air then
-				destroy(drops, p, cid)
+				data[vi] = destroy(drops, p, cid, c_air, c_fire,
+					on_blast_queue, ignore_protection,
+					ignore_on_blast)
 			end
 		end
 		vi = vi + 1
@@ -212,71 +319,61 @@ local function explode(pos, radius)
 	end
 	end
 
-	return drops
-end
-
-
-local function boom(pos)
-	minetest.sound_play("tnt_explode", {pos=pos, gain=1.5, max_hear_distance=2*64})
-	minetest.set_node(pos, {name="tnt:boom"})
-	minetest.get_node_timer(pos):start(0.5)
-
-	local drops = explode(pos, radius)
-	entity_physics(pos, radius)
-	eject_drops(drops, pos, radius)
-	add_effects(pos, radius)
-end
+	vm:set_data(data)
+	vm:write_to_map()
+	vm:update_map()
+	vm:update_liquids()
+
+	-- call nodeupdate for everything within 1.5x blast radius
+	for z = -radius * 1.5, radius * 1.5 do
+	for x = -radius * 1.5, radius * 1.5 do
+	for y = -radius * 1.5, radius * 1.5 do
+		local s = vector.add(pos, {x = x, y = y, z = z})
+		local r = vector.distance(pos, s)
+		if r / radius < 1.4 then
+			nodeupdate(s)
+		end
+	end
+	end
+	end
 
-minetest.register_node("tnt:tnt", {
-	description = "TNT",
-	tiles = {"tnt_top.png", "tnt_bottom.png", "tnt_side.png"},
-	is_ground_content = false,
-	groups = {dig_immediate=2},
-	sounds = default.node_sound_wood_defaults(),
-	on_punch = function(pos, node, puncher)
-		if puncher:get_wielded_item():get_name() == "default:torch" then
-			if(minetest.check_player_privs(puncher:get_player_name(), {trusted_player=true})) then
-				minetest.sound_play("tnt_ignite", {pos=pos})
-				minetest.set_node(pos, {name="tnt:tnt_burning"})
+	for _, data in ipairs(on_blast_queue) do
+		local dist = math.max(1, vector.distance(data.pos, pos))
+		local intensity = (radius * radius) / (dist * dist)
+		local node_drops = data.on_blast(data.pos, intensity)
+		if node_drops then
+			for _, item in ipairs(node_drops) do
+				add_drop(drops, item)
 			end
 		end
-	end,
-	on_blast = function(pos, intensity)
-		burn(pos)
-	end,
-	--mesecons = {effector = {action_on = boom}},
-})
+	end
 
-minetest.register_node("tnt:tnt_burning", {
-	tiles = {
-		{
-			name = "tnt_top_burning_animated.png",
-			animation = {
-				type = "vertical_frames",
-				aspect_w = 16,
-				aspect_h = 16,
-				length = 1,
-			}
-		},
-		"tnt_bottom.png", "tnt_side.png"},
-	light_source = 5,
-	drop = "",
-	sounds = default.node_sound_wood_defaults(),
-	on_construct = function(pos)
-		minetest.get_node_timer(pos):start(4)
-	end,
-	on_timer = boom,
-	-- unaffected by explosions
-	on_blast = function() end,
-})
+	return drops, radius
+end
+
+function tnt.boom(pos, def)
+	minetest.sound_play("tnt_explode", {pos = pos, gain = 1.5, max_hear_distance = 2*64})
+	minetest.set_node(pos, {name = "tnt:boom"})
+	local drops, radius = tnt_explode(pos, def.radius, def.ignore_protection,
+			def.ignore_on_blast)
+	-- append entity drops
+	local damage_radius = (radius / def.radius) * def.damage_radius
+	entity_physics(pos, damage_radius, drops)
+	if not def.disable_drops then
+		eject_drops(drops, pos, radius)
+	end
+	add_effects(pos, radius, drops)
+end
 
 minetest.register_node("tnt:boom", {
-	drawtype = "plantlike",
-	tiles = {"tnt_boom.png"},
+	drawtype = "airlike",
 	light_source = default.LIGHT_MAX,
 	walkable = false,
 	drop = "",
-	groups = {dig_immediate=3},
+	groups = {dig_immediate = 3},
+	on_construct = function(pos)
+		minetest.get_node_timer(pos):start(0.4)
+	end,
 	on_timer = function(pos, elapsed)
 		minetest.remove_node(pos)
 	end,
@@ -298,18 +395,18 @@ minetest.register_node("tnt:gunpowder", {
 		type = "fixed",
 		fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
 	},
-	groups = {dig_immediate=2,attached_node=1,connect_to_raillike=minetest.raillike_group("gunpowder")},
+	groups = {dig_immediate = 2, attached_node = 1, connect_to_raillike = minetest.raillike_group("gunpowder")},
 	sounds = default.node_sound_leaves_defaults(),
-	
+
 	on_punch = function(pos, node, puncher)
-           if puncher:get_wielded_item():get_name() == "default:torch" then
-		 if(minetest.check_player_privs(puncher:get_player_name(), {trusted_player=true})) then
-	   	burn(pos)
-		end
-	   end
+		if puncher:get_wielded_item():get_name() == "default:torch" then
+            if(minetest.check_player_privs(puncher:get_player_name(), {trusted_player=true})) then
+                tnt.burn(pos)
+            end
+        end
 	end,
 	on_blast = function(pos, intensity)
-		burn(pos)
+		tnt.burn(pos)
 	end,
 })
 
@@ -360,14 +457,14 @@ minetest.register_node("tnt:gunpowder_burning", {
 		fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
 	},
 	drop = "",
-	groups = {dig_immediate=2,attached_node=1,connect_to_raillike=minetest.raillike_group("gunpowder")},
+	groups = {dig_immediate = 2, attached_node = 1, connect_to_raillike = minetest.raillike_group("gunpowder")},
 	sounds = default.node_sound_leaves_defaults(),
 	on_timer = function(pos, elapsed)
 		for dx = -1, 1 do
 		for dz = -1, 1 do
 		for dy = -1, 1 do
 			if not (dx == 0 and dz == 0) then
-				burn({
+				tnt.burn({
 					x = pos.x + dx,
 					y = pos.y + dy,
 					z = pos.z + dz,
@@ -380,14 +477,18 @@ minetest.register_node("tnt:gunpowder_burning", {
 	end,
 	-- unaffected by explosions
 	on_blast = function() end,
+	on_construct = function(pos)
+		minetest.sound_play("tnt_gunpowder_burning", {pos = pos, gain = 2})
+		minetest.get_node_timer(pos):start(1)
+	end,
 })
 
 minetest.register_abm({
-	nodenames = {"tnt:tnt", "tnt:gunpowder"},
+	nodenames = {"group:tnt", "tnt:gunpowder"},
 	neighbors = {"fire:basic_flame", "default:lava_source", "default:lava_flowing"},
 	interval = 4,
 	chance = 1,
-	action = burn,
+	action = tnt.burn,
 })
 
 minetest.register_craft({
@@ -404,3 +505,82 @@ minetest.register_craft({
 		{"",           "group:wood",    ""}
 	}
 })
+
+function tnt.register_tnt(def)
+	local name = ""
+	if not def.name:find(':') then
+		name = "tnt:" .. def.name
+	else
+		name = def.name
+		def.name = def.name:match(":([%w_]+)")
+	end
+	if not def.tiles then def.tiles = {} end
+	local tnt_top = def.tiles.top or def.name .. "_top.png"
+	local tnt_bottom = def.tiles.bottom or def.name .. "_bottom.png"
+	local tnt_side = def.tiles.side or def.name .. "_side.png"
+	local tnt_burning = def.tiles.burning or def.name .. "_top_burning_animated.png"
+	if not def.damage_radius then def.damage_radius = def.radius * 2 end
+
+	minetest.register_node(":" .. name, {
+		description = def.description,
+		tiles = {tnt_top, tnt_bottom, tnt_side},
+		is_ground_content = false,
+		groups = {dig_immediate = 2, mesecon = 2, tnt = 1},
+		sounds = default.node_sound_wood_defaults(),
+		on_punch = function(pos, node, puncher)
+			if puncher:get_wielded_item():get_name() == "default:torch" then
+                if(minetest.check_player_privs(puncher:get_player_name(), {trusted_player=true})) then
+                    minetest.set_node(pos, {name = name .. "_burning"})
+                end
+			end
+		end,
+		on_blast = function(pos, intensity)
+			minetest.after(0.1, function()
+				tnt.boom(pos, def)
+			end)
+		end,
+		--mesecons = {effector =
+		--	{action_on =
+		--		function(pos)
+		--			tnt.boom(pos, def)
+		--		end
+		--	}
+		--},
+	})
+
+	minetest.register_node(":" .. name .. "_burning", {
+		tiles = {
+			{
+				name = tnt_burning,
+				animation = {
+					type = "vertical_frames",
+					aspect_w = 16,
+					aspect_h = 16,
+					length = 1,
+				}
+			},
+			tnt_bottom, tnt_side
+			},
+		light_source = 5,
+		drop = "",
+		sounds = default.node_sound_wood_defaults(),
+		groups = {falling_node = 1},
+		on_timer = function(pos, elapsed)
+			tnt.boom(pos, def)
+		end,
+		-- unaffected by explosions
+		on_blast = function() end,
+		on_construct = function(pos)
+			minetest.sound_play("tnt_ignite", {pos = pos})
+			minetest.get_node_timer(pos):start(4)
+			nodeupdate(pos)
+		end,
+	})
+end
+
+tnt.register_tnt({
+	name = "tnt:tnt",
+	description = "TNT",
+	radius = radius,
+})
+
diff --git a/mods/tnt/textures/tnt_blast.png b/mods/tnt/textures/tnt_blast.png
new file mode 100644
index 0000000000000000000000000000000000000000..bbb1096f5d81f68b0247f1bd386357f1c14c2579
Binary files /dev/null and b/mods/tnt/textures/tnt_blast.png differ
diff --git a/mods/vessels/init.lua b/mods/vessels/init.lua
index bbd677a341177c069a0f0ce1f2008dc294e804e9..165efbd50d938412b664c57c82dfba877699bde0 100644
--- a/mods/vessels/init.lua
+++ b/mods/vessels/init.lua
@@ -15,7 +15,7 @@ local vessels_shelf_formspec =
 
 minetest.register_node("vessels:shelf", {
 	description = "Vessels shelf",
-	tiles = {"default_wood.png", "default_wood.png", "default_wood.png^vessels_shelf.png"},
+	tiles = {"default_wood.png", "default_wood.png", "vessels_shelf.png"},
 	is_ground_content = false,
 	groups = {choppy=3,oddly_breakable_by_hand=2,flammable=3},
 	sounds = default.node_sound_wood_defaults(),
@@ -48,6 +48,13 @@ minetest.register_node("vessels:shelf", {
 		minetest.log("action", player:get_player_name() ..
 			   " takes stuff from vessels shelf at ".. minetest.pos_to_string(pos))
 	end,
+	on_blast = function(pos)
+		local drops = {}
+		default.get_inventory_drops(pos, "vessels", drops)
+		drops[#drops+1] = "vessels:shelf"
+		minetest.remove_node(pos)
+		return drops
+	end,
 })
 
 minetest.register_craft({