diff --git a/data/junglegrass.png b/data/junglegrass.png
new file mode 100644
index 0000000000000000000000000000000000000000..eea87c07c55ea290e364c45f0dbad538c19468c7
Binary files /dev/null and b/data/junglegrass.png differ
diff --git a/data/jungletree.png b/data/jungletree.png
new file mode 100644
index 0000000000000000000000000000000000000000..ccd20ac7403a35d9d288524afaf94e6e50939d26
Binary files /dev/null and b/data/jungletree.png differ
diff --git a/data/jungletree_top.png b/data/jungletree_top.png
new file mode 100644
index 0000000000000000000000000000000000000000..2a9b51373d939d19e3486f735761bf57c88614a9
Binary files /dev/null and b/data/jungletree_top.png differ
diff --git a/po/en/minetest.pot b/po/en/minetest.pot
new file mode 100644
index 0000000000000000000000000000000000000000..8fbac78d0b3c05dbff1828850f74beea1a8eb04d
--- /dev/null
+++ b/po/en/minetest.pot
@@ -0,0 +1,113 @@
+# 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: 2011-07-24 11:32+0300\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"
+
+#: src/guiMainMenu.cpp:180
+msgid "Name/Password"
+msgstr ""
+
+#: src/guiMainMenu.cpp:203
+msgid "Address/Port"
+msgstr ""
+
+#: src/guiMainMenu.cpp:223
+msgid "Leave address blank to start a local server."
+msgstr ""
+
+#: src/guiMainMenu.cpp:230
+msgid "Fancy trees"
+msgstr ""
+
+#: src/guiMainMenu.cpp:236
+msgid "Smooth Lighting"
+msgstr ""
+
+#: src/guiMainMenu.cpp:244
+msgid "Start Game / Connect"
+msgstr ""
+
+#: src/guiMainMenu.cpp:253
+msgid "Change keys"
+msgstr ""
+
+#: src/guiMainMenu.cpp:276
+msgid "Creative Mode"
+msgstr ""
+
+#: src/guiMainMenu.cpp:282
+msgid "Enable Damage"
+msgstr ""
+
+#: src/guiMainMenu.cpp:290
+msgid "Delete map"
+msgstr ""
+
+#: src/guiMessageMenu.cpp:93 src/guiTextInputMenu.cpp:111
+msgid "Proceed"
+msgstr ""
+
+#: src/guiPasswordChange.cpp:102
+msgid "Old Password"
+msgstr ""
+
+#: src/guiPasswordChange.cpp:117
+msgid "New Password"
+msgstr ""
+
+#: src/guiPasswordChange.cpp:131
+msgid "Confirm Password"
+msgstr ""
+
+#: src/guiPasswordChange.cpp:146
+msgid "Change"
+msgstr ""
+
+#: src/guiPasswordChange.cpp:155
+msgid "Passwords do not match!"
+msgstr ""
+
+#: src/guiPauseMenu.cpp:110
+msgid "Continue"
+msgstr ""
+
+#: src/guiPauseMenu.cpp:117
+msgid "Change Password"
+msgstr ""
+
+#: src/guiPauseMenu.cpp:124
+msgid "Disconnect"
+msgstr ""
+
+#: src/guiPauseMenu.cpp:131
+msgid "Exit to OS"
+msgstr ""
+
+#: src/guiPauseMenu.cpp:138
+msgid ""
+"Keys:\n"
+"- WASD: Walk\n"
+"- Mouse left: dig blocks\n"
+"- Mouse right: place blocks\n"
+"- Mouse wheel: select item\n"
+"- 0...9: select item\n"
+"- Shift: sneak\n"
+"- R: Toggle viewing all loaded chunks\n"
+"- I: Inventory menu\n"
+"- ESC: This menu\n"
+"- T: Chat\n"
+msgstr ""
diff --git a/src/collision.cpp b/src/collision.cpp
index 01d5462847134f952626b4ea315dc83910b02286..3d322cf0c4baee77342ab5026e652e639e01ecb4 100644
--- a/src/collision.cpp
+++ b/src/collision.cpp
@@ -78,7 +78,8 @@ collisionMoveResult collisionMoveSimple(Map *map, f32 pos_max_d,
 	{
 		try{
 			// Object collides into walkable nodes
-			if(content_walkable(map->getNode(v3s16(x,y,z)).d) == false)
+			MapNode n = map->getNode(v3s16(x,y,z));
+			if(content_features(n).walkable == false)
 				continue;
 		}
 		catch(InvalidPositionException &e)
diff --git a/src/content_craft.cpp b/src/content_craft.cpp
index 069e68300b9bdf412c082a84b7aaa2fdc250c19e..b5a1dc7761795deeee13cabf022ef920f15859f9 100644
--- a/src/content_craft.cpp
+++ b/src/content_craft.cpp
@@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "inventory.h"
 #include "content_mapnode.h"
 #include "player.h"
+#include "mapnode.h" // For content_t
 
 /*
 	items: actually *items[9]
@@ -446,7 +447,7 @@ void craft_set_creative_inventory(Player *player)
 	*/
 	
 	// CONTENT_IGNORE-terminated list
-	u8 material_items[] = {
+	content_t material_items[] = {
 		CONTENT_TORCH,
 		CONTENT_COBBLE,
 		CONTENT_MUD,
@@ -472,7 +473,7 @@ void craft_set_creative_inventory(Player *player)
 		CONTENT_IGNORE
 	};
 	
-	u8 *mip = material_items;
+	content_t *mip = material_items;
 	for(u16 i=0; i<PLAYER_INVENTORY_SIZE; i++)
 	{
 		if(*mip == CONTENT_IGNORE)
diff --git a/src/content_inventory.cpp b/src/content_inventory.cpp
index 1068defb5e8772a3289704fcef077aba8b5dfd03..3222506067169eb5af7be5805162fdc34dde07f8 100644
--- a/src/content_inventory.cpp
+++ b/src/content_inventory.cpp
@@ -23,7 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 //#include "serverobject.h"
 #include "content_sao.h"
 
-bool item_material_is_cookable(u8 content)
+bool item_material_is_cookable(content_t content)
 {
 	if(content == CONTENT_TREE)
 		return true;
@@ -34,7 +34,7 @@ bool item_material_is_cookable(u8 content)
 	return false;
 }
 
-InventoryItem* item_material_create_cook_result(u8 content)
+InventoryItem* item_material_create_cook_result(content_t content)
 {
 	if(content == CONTENT_TREE)
 		return new CraftItem("lump_of_coal", 1);
diff --git a/src/content_inventory.h b/src/content_inventory.h
index 54aa2151a92a5592f20369fdca2b6b6d8a64f7de..0f410128ba2aed04ddab8f0985f2b321d06ff482 100644
--- a/src/content_inventory.h
+++ b/src/content_inventory.h
@@ -22,13 +22,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "common_irrlicht.h" // For u8, s16
 #include <string>
+#include "mapnode.h" // For content_t
 
 class InventoryItem;
 class ServerActiveObject;
 class ServerEnvironment;
 
-bool           item_material_is_cookable(u8 content);
-InventoryItem* item_material_create_cook_result(u8 content);
+bool           item_material_is_cookable(content_t content);
+InventoryItem* item_material_create_cook_result(content_t content);
 
 std::string         item_craft_get_image_name(const std::string &subname);
 ServerActiveObject* item_craft_create_object(const std::string &subname,
diff --git a/src/content_mapblock.cpp b/src/content_mapblock.cpp
index d8bf71dc032ad3d77da461e9f19d763aeffe6c11..3044c8b359102c8c55293d48ecd62fb1b1509b4f 100644
--- a/src/content_mapblock.cpp
+++ b/src/content_mapblock.cpp
@@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "content_mapnode.h"
 #include "main.h" // For g_settings and g_texturesource
 #include "mineral.h"
+#include "mapblock_mesh.h" // For MapBlock_LightColor()
 
 #ifndef SERVER
 // Create a cuboid.
@@ -198,6 +199,17 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 	AtlasPointer pa_papyrus = g_texturesource->getTexture(
 			g_texturesource->getTextureId("papyrus.png"));
 	material_papyrus.setTexture(0, pa_papyrus.atlas);
+
+	// junglegrass material
+	video::SMaterial material_junglegrass;
+	material_junglegrass.setFlag(video::EMF_LIGHTING, false);
+	material_junglegrass.setFlag(video::EMF_BILINEAR_FILTER, false);
+	material_junglegrass.setFlag(video::EMF_FOG_ENABLE, true);
+	material_junglegrass.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
+	AtlasPointer pa_junglegrass = g_texturesource->getTexture(
+			g_texturesource->getTextureId("junglegrass.png"));
+	material_junglegrass.setTexture(0, pa_junglegrass.atlas);
+
 	for(s16 z=0; z<MAP_BLOCKSIZE; z++)
 	for(s16 y=0; y<MAP_BLOCKSIZE; y++)
 	for(s16 x=0; x<MAP_BLOCKSIZE; x++)
@@ -209,7 +221,7 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 		/*
 			Add torches to mesh
 		*/
-		if(n.d == CONTENT_TORCH)
+		if(n.getContent() == CONTENT_TORCH)
 		{
 			video::SColor c(255,255,255,255);
 
@@ -222,7 +234,7 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 				video::S3DVertex(-BS/2,BS/2,0, 0,0,0, c, 0,0),
 			};
 
-			v3s16 dir = unpackDir(n.dir);
+			v3s16 dir = unpackDir(n.param2);
 
 			for(s32 i=0; i<4; i++)
 			{
@@ -272,10 +284,10 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 		/*
 			Signs on walls
 		*/
-		else if(n.d == CONTENT_SIGN_WALL)
+		else if(n.getContent() == CONTENT_SIGN_WALL)
 		{
 			u8 l = decode_light(n.getLightBlend(data->m_daynight_ratio));
-			video::SColor c(255,l,l,l);
+			video::SColor c = MapBlock_LightColor(255, l);
 				
 			float d = (float)BS/16;
 			// Wall at X+ of node
@@ -287,7 +299,7 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 				video::S3DVertex(BS/2-d,BS/2,-BS/2, 0,0,0, c, 0,0),
 			};
 
-			v3s16 dir = unpackDir(n.dir);
+			v3s16 dir = unpackDir(n.param2);
 
 			for(s32 i=0; i<4; i++)
 			{
@@ -327,26 +339,26 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 		/*
 			Add flowing water to mesh
 		*/
-		else if(n.d == CONTENT_WATER)
+		else if(n.getContent() == CONTENT_WATER)
 		{
 			bool top_is_water = false;
 			MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y+1,z));
-			if(ntop.d == CONTENT_WATER || ntop.d == CONTENT_WATERSOURCE)
+			if(ntop.getContent() == CONTENT_WATER || ntop.getContent() == CONTENT_WATERSOURCE)
 				top_is_water = true;
 			
 			u8 l = 0;
 			// Use the light of the node on top if possible
-			if(content_features(ntop.d).param_type == CPT_LIGHT)
+			if(content_features(ntop).param_type == CPT_LIGHT)
 				l = decode_light(ntop.getLightBlend(data->m_daynight_ratio));
 			// Otherwise use the light of this node (the water)
 			else
 				l = decode_light(n.getLightBlend(data->m_daynight_ratio));
-			video::SColor c(WATER_ALPHA,l,l,l);
+			video::SColor c = MapBlock_LightColor(WATER_ALPHA, l);
 			
 			// Neighbor water levels (key = relative position)
 			// Includes current node
 			core::map<v3s16, f32> neighbor_levels;
-			core::map<v3s16, u8> neighbor_contents;
+			core::map<v3s16, content_t> neighbor_contents;
 			core::map<v3s16, u8> neighbor_flags;
 			const u8 neighborflag_top_is_water = 0x01;
 			v3s16 neighbor_dirs[9] = {
@@ -368,14 +380,14 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 				// Check neighbor
 				v3s16 p2 = p + neighbor_dirs[i];
 				MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
-				if(n2.d != CONTENT_IGNORE)
+				if(n2.getContent() != CONTENT_IGNORE)
 				{
-					content = n2.d;
+					content = n2.getContent();
 
-					if(n2.d == CONTENT_WATERSOURCE)
+					if(n2.getContent() == CONTENT_WATERSOURCE)
 						level = (-0.5+node_water_level) * BS;
-					else if(n2.d == CONTENT_WATER)
-						level = (-0.5 + ((float)(n2.param2 & LIQUID_LEVEL_MASK) + 0.5) / 8.0
+					else if(n2.getContent() == CONTENT_WATER)
+						level = (-0.5 + ((float)n2.param2 + 0.5) / 8.0
 								* node_water_level) * BS;
 
 					// Check node above neighbor.
@@ -383,7 +395,7 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 					//       doesn't exist
 					p2.Y += 1;
 					n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
-					if(n2.d == CONTENT_WATERSOURCE || n2.d == CONTENT_WATER)
+					if(n2.getContent() == CONTENT_WATERSOURCE || n2.getContent() == CONTENT_WATER)
 						flags |= neighborflag_top_is_water;
 				}
 				
@@ -591,14 +603,14 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 		/*
 			Add water sources to mesh if using new style
 		*/
-		else if(n.d == CONTENT_WATERSOURCE && new_style_water)
+		else if(n.getContent() == CONTENT_WATERSOURCE && new_style_water)
 		{
 			//bool top_is_water = false;
 			bool top_is_air = false;
 			MapNode n = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y+1,z));
-			/*if(n.d == CONTENT_WATER || n.d == CONTENT_WATERSOURCE)
+			/*if(n.getContent() == CONTENT_WATER || n.getContent() == CONTENT_WATERSOURCE)
 				top_is_water = true;*/
-			if(n.d == CONTENT_AIR)
+			if(n.getContent() == CONTENT_AIR)
 				top_is_air = true;
 			
 			/*if(top_is_water == true)
@@ -607,7 +619,7 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 				continue;
 
 			u8 l = decode_light(n.getLightBlend(data->m_daynight_ratio));
-			video::SColor c(WATER_ALPHA,l,l,l);
+			video::SColor c = MapBlock_LightColor(WATER_ALPHA, l);
 			
 			video::S3DVertex vertices[4] =
 			{
@@ -638,11 +650,11 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 		/*
 			Add leaves if using new style
 		*/
-		else if(n.d == CONTENT_LEAVES && new_style_leaves)
+		else if(n.getContent() == CONTENT_LEAVES && new_style_leaves)
 		{
 			/*u8 l = decode_light(n.getLightBlend(data->m_daynight_ratio));*/
 			u8 l = decode_light(undiminish_light(n.getLightBlend(data->m_daynight_ratio)));
-			video::SColor c(255,l,l,l);
+			video::SColor c = MapBlock_LightColor(255, l);
 
 			for(u32 j=0; j<6; j++)
 			{
@@ -706,10 +718,10 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 		/*
 			Add glass
 		*/
-		else if(n.d == CONTENT_GLASS)
+		else if(n.getContent() == CONTENT_GLASS)
 		{
 			u8 l = decode_light(undiminish_light(n.getLightBlend(data->m_daynight_ratio)));
-			video::SColor c(255,l,l,l);
+			video::SColor c = MapBlock_LightColor(255, l);
 
 			for(u32 j=0; j<6; j++)
 			{
@@ -769,10 +781,10 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 		/*
 			Add fence
 		*/
-		else if(n.d == CONTENT_FENCE)
+		else if(n.getContent() == CONTENT_FENCE)
 		{
 			u8 l = decode_light(undiminish_light(n.getLightBlend(data->m_daynight_ratio)));
-			video::SColor c(255,l,l,l);
+			video::SColor c = MapBlock_LightColor(255, l);
 
 			const f32 post_rad=(f32)BS/10;
 			const f32 bar_rad=(f32)BS/20;
@@ -795,7 +807,7 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 			v3s16 p2 = p;
 			p2.X++;
 			MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
-			if(n2.d == CONTENT_FENCE)
+			if(n2.getContent() == CONTENT_FENCE)
 			{
 				pos = intToFloat(p+blockpos_nodes, BS);
 				pos.X += BS/2;
@@ -821,7 +833,7 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 			p2 = p;
 			p2.Z++;
 			n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
-			if(n2.d == CONTENT_FENCE)
+			if(n2.getContent() == CONTENT_FENCE)
 			{
 				pos = intToFloat(p+blockpos_nodes, BS);
 				pos.Z += BS/2;
@@ -848,7 +860,7 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 		/*
 			Add stones with minerals if stone is invisible
 		*/
-		else if(n.d == CONTENT_STONE && invisible_stone && n.getMineral() != MINERAL_NONE)
+		else if(n.getContent() == CONTENT_STONE && invisible_stone && n.getMineral() != MINERAL_NONE)
 		{
 			for(u32 j=0; j<6; j++)
 			{
@@ -856,19 +868,20 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 				v3s16 dir = g_6dirs[j];
 				/*u8 l = 0;
 				MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + dir);
-				if(content_features(n2.d).param_type == CPT_LIGHT)
+				if(content_features(n2).param_type == CPT_LIGHT)
 					l = decode_light(n2.getLightBlend(data->m_daynight_ratio));
 				else
 					l = 255;*/
 				u8 l = 255;
-				video::SColor c(255,l,l,l);
+				video::SColor c = MapBlock_LightColor(255, l);
 				
 				// Get the right texture
 				TileSpec ts = n.getTile(dir);
 				AtlasPointer ap = ts.texture;
 				material_general.setTexture(0, ap.atlas);
+
 				video::S3DVertex vertices[4] =
- 				{
+				{
 					/*video::S3DVertex(-BS/2,-BS/2,BS/2, 0,0,0, c, 0,1),
 					video::S3DVertex(BS/2,-BS/2,BS/2, 0,0,0, c, 1,1),
 					video::S3DVertex(BS/2,BS/2,BS/2, 0,0,0, c, 1,0),
@@ -916,10 +929,10 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 			}
 		}
 #endif
-		else if(n.d == CONTENT_PAPYRUS)
+		else if(n.getContent() == CONTENT_PAPYRUS)
 		{
 			u8 l = decode_light(undiminish_light(n.getLightBlend(data->m_daynight_ratio)));
-			video::SColor c(255,l,l,l);
+			video::SColor c = MapBlock_LightColor(255, l);
 
 			for(u32 j=0; j<4; j++)
 			{
@@ -966,10 +979,61 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 				collector.append(material_papyrus, vertices, 4, indices, 6);
 			}
 		}
-		else if(n.d == CONTENT_RAIL)
+		else if(n.getContent() == CONTENT_JUNGLEGRASS)
+		{
+			u8 l = decode_light(undiminish_light(n.getLightBlend(data->m_daynight_ratio)));
+			video::SColor c = MapBlock_LightColor(255, l);
+
+			for(u32 j=0; j<4; j++)
+			{
+				video::S3DVertex vertices[4] =
+				{
+					video::S3DVertex(-BS/2,-BS/2,0, 0,0,0, c,
+						pa_papyrus.x0(), pa_papyrus.y1()),
+					video::S3DVertex(BS/2,-BS/2,0, 0,0,0, c,
+						pa_papyrus.x1(), pa_papyrus.y1()),
+					video::S3DVertex(BS/2,BS/1,0, 0,0,0, c,
+						pa_papyrus.x1(), pa_papyrus.y0()),
+					video::S3DVertex(-BS/2,BS/1,0, 0,0,0, c,
+						pa_papyrus.x0(), pa_papyrus.y0()),
+				};
+
+				if(j == 0)
+				{
+					for(u16 i=0; i<4; i++)
+						vertices[i].Pos.rotateXZBy(45);
+				}
+				else if(j == 1)
+				{
+					for(u16 i=0; i<4; i++)
+						vertices[i].Pos.rotateXZBy(-45);
+				}
+				else if(j == 2)
+				{
+					for(u16 i=0; i<4; i++)
+						vertices[i].Pos.rotateXZBy(135);
+				}
+				else if(j == 3)
+				{
+					for(u16 i=0; i<4; i++)
+						vertices[i].Pos.rotateXZBy(-135);
+				}
+
+				for(u16 i=0; i<4; i++)
+				{
+					vertices[i].Pos *= 1.3;
+					vertices[i].Pos += intToFloat(p + blockpos_nodes, BS);
+				}
+
+				u16 indices[] = {0,1,2,2,3,0};
+				// Add to mesh collector
+				collector.append(material_junglegrass, vertices, 4, indices, 6);
+			}
+		}
+		else if(n.getContent() == CONTENT_RAIL)
 		{
 			u8 l = decode_light(n.getLightBlend(data->m_daynight_ratio));
-			video::SColor c(255,l,l,l);
+			video::SColor c = MapBlock_LightColor(255, l);
 
 			bool is_rail_x [] = { false, false };  /* x-1, x+1 */
 			bool is_rail_z [] = { false, false };  /* z-1, z+1 */
@@ -979,13 +1043,13 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 			MapNode n_minus_z = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y,z-1));
 			MapNode n_plus_z = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y,z+1));
 
-			if(n_minus_x.d == CONTENT_RAIL)
+			if(n_minus_x.getContent() == CONTENT_RAIL)
 				is_rail_x[0] = true;
-			if(n_plus_x.d == CONTENT_RAIL)
+			if(n_plus_x.getContent() == CONTENT_RAIL)
 				is_rail_x[1] = true;
-			if(n_minus_z.d == CONTENT_RAIL)
+			if(n_minus_z.getContent() == CONTENT_RAIL)
 				is_rail_z[0] = true;
-			if(n_plus_z.d == CONTENT_RAIL)
+			if(n_plus_z.getContent() == CONTENT_RAIL)
 				is_rail_z[1] = true;
 
 			float d = (float)BS/16;
diff --git a/src/content_mapnode.cpp b/src/content_mapnode.cpp
index 79e10fd617abfd4dbdbffb4b61f8ce866fdc0dfd..db036ebd9a6c9a43e7462a635ee739a66bfd7141 100644
--- a/src/content_mapnode.cpp
+++ b/src/content_mapnode.cpp
@@ -31,6 +31,65 @@ void setStoneLikeDiggingProperties(DiggingPropertiesList &list, float toughness)
 void setDirtLikeDiggingProperties(DiggingPropertiesList &list, float toughness);
 void setWoodLikeDiggingProperties(DiggingPropertiesList &list, float toughness);
 
+content_t trans_table_19[][2] = {
+	{CONTENT_GRASS, 1},
+	{CONTENT_TREE, 4},
+	{CONTENT_LEAVES, 5},
+	{CONTENT_GRASS_FOOTSTEPS, 6},
+	{CONTENT_MESE, 7},
+	{CONTENT_MUD, 8},
+	{CONTENT_CLOUD, 10},
+	{CONTENT_COALSTONE, 11},
+	{CONTENT_WOOD, 12},
+	{CONTENT_SAND, 13},
+	{CONTENT_COBBLE, 18},
+	{CONTENT_STEEL, 19},
+	{CONTENT_GLASS, 20},
+	{CONTENT_MOSSYCOBBLE, 22},
+	{CONTENT_GRAVEL, 23},
+	{CONTENT_SANDSTONE, 24},
+	{CONTENT_CACTUS, 25},
+	{CONTENT_BRICK, 26},
+	{CONTENT_CLAY, 27},
+	{CONTENT_PAPYRUS, 28},
+	{CONTENT_BOOKSHELF, 29},
+};
+
+MapNode mapnode_translate_from_internal(MapNode n_from, u8 version)
+{
+	MapNode result = n_from;
+	if(version <= 19)
+	{
+		content_t c_from = n_from.getContent();
+		for(u32 i=0; i<sizeof(trans_table_19)/sizeof(trans_table_19[0]); i++)
+		{
+			if(trans_table_19[i][0] == c_from)
+			{
+				result.setContent(trans_table_19[i][1]);
+				break;
+			}
+		}
+	}
+	return result;
+}
+MapNode mapnode_translate_to_internal(MapNode n_from, u8 version)
+{
+	MapNode result = n_from;
+	if(version <= 19)
+	{
+		content_t c_from = n_from.getContent();
+		for(u32 i=0; i<sizeof(trans_table_19)/sizeof(trans_table_19[0]); i++)
+		{
+			if(trans_table_19[i][1] == c_from)
+			{
+				result.setContent(trans_table_19[i][0]);
+				break;
+			}
+		}
+	}
+	return result;
+}
+
 void content_mapnode_init()
 {
 	// Read some settings
@@ -38,7 +97,7 @@ void content_mapnode_init()
 	bool new_style_leaves = g_settings.getBool("new_style_leaves");
 	bool invisible_stone = g_settings.getBool("invisible_stone");
 
-	u8 i;
+	content_t i;
 	ContentFeatures *f = NULL;
 
 	i = CONTENT_STONE;
@@ -136,12 +195,34 @@ void content_mapnode_init()
 	f->dug_item = std::string("MaterialItem ")+itos(i)+" 1";
 	setWoodLikeDiggingProperties(f->digging_properties, 1.0);
 	
+	i = CONTENT_JUNGLETREE;
+	f = &content_features(i);
+	f->setAllTextures("jungletree.png");
+	f->setTexture(0, "jungletree_top.png");
+	f->setTexture(1, "jungletree_top.png");
+	f->param_type = CPT_MINERAL;
+	//f->is_ground_content = true;
+	f->dug_item = std::string("MaterialItem ")+itos(i)+" 1";
+	setWoodLikeDiggingProperties(f->digging_properties, 1.0);
+	
+	i = CONTENT_JUNGLEGRASS;
+	f = &content_features(i);
+	f->setInventoryTexture("junglegrass.png");
+	f->light_propagates = true;
+	f->param_type = CPT_LIGHT;
+	//f->is_ground_content = true;
+	f->air_equivalent = false; // grass grows underneath
+	f->dug_item = std::string("MaterialItem ")+itos(i)+" 1";
+	f->solidness = 0; // drawn separately, makes no faces
+	f->walkable = false;
+	setWoodLikeDiggingProperties(f->digging_properties, 0.10);
+
 	i = CONTENT_LEAVES;
 	f = &content_features(i);
 	f->light_propagates = true;
 	//f->param_type = CPT_MINERAL;
 	f->param_type = CPT_LIGHT;
-	f->is_ground_content = true;
+	//f->is_ground_content = true;
 	if(new_style_leaves)
 	{
 		f->solidness = 0; // drawn separately, makes no faces
@@ -191,6 +272,7 @@ void content_mapnode_init()
 	i = CONTENT_GLASS;
 	f = &content_features(i);
 	f->light_propagates = true;
+	f->sunlight_propagates = true;
 	f->param_type = CPT_LIGHT;
 	f->is_ground_content = true;
 	f->dug_item = std::string("MaterialItem ")+itos(i)+" 1";
diff --git a/src/content_mapnode.h b/src/content_mapnode.h
index e53624c21597d5cb19d6e62d1299d387254d253a..5fdbf45f371e58dcc2ba42939b9c711505b23306 100644
--- a/src/content_mapnode.h
+++ b/src/content_mapnode.h
@@ -20,43 +20,57 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #ifndef CONTENT_MAPNODE_HEADER
 #define CONTENT_MAPNODE_HEADER
 
+#include "mapnode.h"
+
 void content_mapnode_init();
 
+MapNode mapnode_translate_from_internal(MapNode n_from, u8 version);
+MapNode mapnode_translate_to_internal(MapNode n_from, u8 version);
+
 /*
 	Node content type IDs
+	Ranges:
 */
+
+// 0x000...0x07f (0...127): param2 is fully usable
+// 126 and 127 are reserved.
+// Use these sparingly, only when the extra space in param2 might be needed.
 #define CONTENT_STONE 0
-#define CONTENT_GRASS 1
 #define CONTENT_WATER 2
 #define CONTENT_TORCH 3
-#define CONTENT_TREE 4
-#define CONTENT_LEAVES 5
-#define CONTENT_GRASS_FOOTSTEPS 6
-#define CONTENT_MESE 7
-#define CONTENT_MUD 8
 #define CONTENT_WATERSOURCE 9
-// Pretty much useless, clouds won't be drawn this way
-#define CONTENT_CLOUD 10
-#define CONTENT_COALSTONE 11
-#define CONTENT_WOOD 12
-#define CONTENT_SAND 13
 #define CONTENT_SIGN_WALL 14
 #define CONTENT_CHEST 15
 #define CONTENT_FURNACE 16
-//#define CONTENT_WORKBENCH 17
-#define CONTENT_COBBLE 18
-#define CONTENT_STEEL 19
-#define CONTENT_GLASS 20
 #define CONTENT_FENCE 21
-#define CONTENT_MOSSYCOBBLE 22
-#define CONTENT_GRAVEL 23
-#define CONTENT_SANDSTONE 24
-#define CONTENT_CACTUS 25
-#define CONTENT_BRICK 26
-#define CONTENT_CLAY 27
-#define CONTENT_PAPYRUS 28
-#define CONTENT_BOOKSHELF 29
 #define CONTENT_RAIL 30
 
+// 0x800...0xfff (2048...4095): higher 4 bytes of param2 are not usable
+#define CONTENT_GRASS 0x800 //1
+#define CONTENT_TREE 0x801 //4
+#define CONTENT_LEAVES 0x802 //5
+#define CONTENT_GRASS_FOOTSTEPS 0x803 //6
+#define CONTENT_MESE 0x804 //7
+#define CONTENT_MUD 0x805 //8
+// Pretty much useless, clouds won't be drawn this way
+#define CONTENT_CLOUD 0x806 //10
+#define CONTENT_COALSTONE 0x807 //11
+#define CONTENT_WOOD 0x808 //12
+#define CONTENT_SAND 0x809 //13
+#define CONTENT_COBBLE 0x80a //18
+#define CONTENT_STEEL 0x80b //19
+#define CONTENT_GLASS 0x80c //20
+#define CONTENT_MOSSYCOBBLE 0x80d //22
+#define CONTENT_GRAVEL 0x80e //23
+#define CONTENT_SANDSTONE 0x80f //24
+#define CONTENT_CACTUS 0x810 //25
+#define CONTENT_BRICK 0x811 //26
+#define CONTENT_CLAY 0x812 //27
+#define CONTENT_PAPYRUS 0x813 //28
+#define CONTENT_BOOKSHELF 0x814 //29
+#define CONTENT_JUNGLETREE 0x815
+#define CONTENT_JUNGLEGRASS 0x816
+
+
 #endif
 
diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp
index cbc78ad3f51a70773a17658618fa0e32d5728812..8326a0f0d8ea25453da1cb7c649ae183d78d41bd 100644
--- a/src/defaultsettings.cpp
+++ b/src/defaultsettings.cpp
@@ -51,7 +51,7 @@ void set_default_settings()
 	g_settings.setDefault("wanted_fps", "30");
 	g_settings.setDefault("fps_max", "60");
 	g_settings.setDefault("viewing_range_nodes_max", "300");
-	g_settings.setDefault("viewing_range_nodes_min", "25");
+	g_settings.setDefault("viewing_range_nodes_min", "15");
 	g_settings.setDefault("screenW", "800");
 	g_settings.setDefault("screenH", "600");
 	g_settings.setDefault("address", "");
diff --git a/src/environment.cpp b/src/environment.cpp
index df41dc63f24fc40feb39300b772853bd5799ce49..d723696209c32a1cdee54e564ba1018898b416e5 100644
--- a/src/environment.cpp
+++ b/src/environment.cpp
@@ -543,11 +543,11 @@ void spawnRandomObjects(MapBlock *block)
 		{
 			v3s16 p(x0,y0,z0);
 			MapNode n = block->getNodeNoEx(p);
-			if(n.d == CONTENT_IGNORE)
+			if(n.getContent() == CONTENT_IGNORE)
 				continue;
-			if(content_features(n.d).liquid_type != LIQUID_NONE)
+			if(content_features(n).liquid_type != LIQUID_NONE)
 				continue;
-			if(content_features(n.d).walkable)
+			if(content_features(n).walkable)
 			{
 				last_node_walkable = true;
 				continue;
@@ -555,7 +555,7 @@ void spawnRandomObjects(MapBlock *block)
 			if(last_node_walkable)
 			{
 				// If block contains light information
-				if(content_features(n.d).param_type == CPT_LIGHT)
+				if(content_features(n).param_type == CPT_LIGHT)
 				{
 					if(n.getLight(LIGHTBANK_DAY) <= 5)
 					{
@@ -624,15 +624,15 @@ void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime)
 #if 1
 		// Test something:
 		// Convert all mud under proper day lighting to grass
-		if(n.d == CONTENT_MUD)
+		if(n.getContent() == CONTENT_MUD)
 		{
 			if(dtime_s > 300)
 			{
 				MapNode n_top = block->getNodeNoEx(p0+v3s16(0,1,0));
-				if(content_features(n_top.d).air_equivalent &&
+				if(content_features(n_top).air_equivalent &&
 						n_top.getLight(LIGHTBANK_DAY) >= 13)
 				{
-					n.d = CONTENT_GRASS;
+					n.setContent(CONTENT_GRASS);
 					m_map->addNodeWithEvent(p, n);
 				}
 			}
@@ -686,9 +686,9 @@ void ServerEnvironment::step(float dtime)
 			v3s16 bottompos = floatToInt(playerpos + v3f(0,-BS/4,0), BS);
 			try{
 				MapNode n = m_map->getNode(bottompos);
-				if(n.d == CONTENT_GRASS)
+				if(n.getContent() == CONTENT_GRASS)
 				{
-					n.d = CONTENT_GRASS_FOOTSTEPS;
+					n.setContent(CONTENT_GRASS_FOOTSTEPS);
 					m_map->setNode(bottompos, n);
 				}
 			}
@@ -859,15 +859,15 @@ void ServerEnvironment::step(float dtime)
 					Test something:
 					Convert mud under proper lighting to grass
 				*/
-				if(n.d == CONTENT_MUD)
+				if(n.getContent() == CONTENT_MUD)
 				{
 					if(myrand()%20 == 0)
 					{
-						MapNode n_top = block->getNodeNoEx(p0+v3s16(0,1,0));
-						if(content_features(n_top.d).air_equivalent &&
+						MapNode n_top = m_map->getNodeNoEx(p+v3s16(0,1,0));
+						if(content_features(n_top).air_equivalent &&
 								n_top.getLightBlend(getDayNightRatio()) >= 13)
 						{
-							n.d = CONTENT_GRASS;
+							n.setContent(CONTENT_GRASS);
 							m_map->addNodeWithEvent(p, n);
 						}
 					}
@@ -875,15 +875,14 @@ void ServerEnvironment::step(float dtime)
 				/*
 					Convert grass into mud if under something else than air
 				*/
-				else if(n.d == CONTENT_GRASS)
+				else if(n.getContent() == CONTENT_GRASS)
 				{
 					//if(myrand()%20 == 0)
 					{
-						MapNode n_top = block->getNodeNoEx(p0+v3s16(0,1,0));
-						if(n_top.d != CONTENT_AIR
-								&& n_top.d != CONTENT_IGNORE)
+						MapNode n_top = m_map->getNodeNoEx(p+v3s16(0,1,0));
+                        if(content_features(n_top).air_equivalent == false)
 						{
-							n.d = CONTENT_MUD;
+							n.setContent(CONTENT_MUD);
 							m_map->addNodeWithEvent(p, n);
 						}
 					}
@@ -1633,9 +1632,9 @@ void ClientEnvironment::step(float dtime)
 			v3s16 bottompos = floatToInt(playerpos + v3f(0,-BS/4,0), BS);
 			try{
 				MapNode n = m_map->getNode(bottompos);
-				if(n.d == CONTENT_GRASS)
+				if(n.getContent() == CONTENT_GRASS)
 				{
-					n.d = CONTENT_GRASS_FOOTSTEPS;
+					n.setContent(CONTENT_GRASS_FOOTSTEPS);
 					m_map->setNode(bottompos, n);
 					// Update mesh on client
 					if(m_map->mapType() == MAPTYPE_CLIENT)
@@ -1874,7 +1873,7 @@ void ClientEnvironment::drawPostFx(video::IVideoDriver* driver, v3f camera_pos)
 	v3f pos_f = camera_pos;
 	v3s16 p_nodes = floatToInt(pos_f, BS);
 	MapNode n = m_map->getNodeNoEx(p_nodes);
-	if(n.d == CONTENT_WATER || n.d == CONTENT_WATERSOURCE)
+	if(n.getContent() == CONTENT_WATER || n.getContent() == CONTENT_WATERSOURCE)
 	{
 		v2u32 ss = driver->getScreenSize();
 		core::rect<s32> rect(0,0, ss.X, ss.Y);
diff --git a/src/game.cpp b/src/game.cpp
index 0f858e879694cc3404a896e207ec0223d91e8afe..b3069d6f98002e617d2c6fc86f6a29b1c8e35054 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -417,7 +417,7 @@ void getPointedNode(Client *client, v3f player_position,
 		try
 		{
 			n = client->getNode(v3s16(x,y,z));
-			if(content_pointable(n.d) == false)
+			if(content_pointable(n.getContent()) == false)
 				continue;
 		}
 		catch(InvalidPositionException &e)
@@ -442,9 +442,9 @@ void getPointedNode(Client *client, v3f player_position,
 		/*
 			Meta-objects
 		*/
-		if(n.d == CONTENT_TORCH)
+		if(n.getContent() == CONTENT_TORCH)
 		{
-			v3s16 dir = unpackDir(n.dir);
+			v3s16 dir = unpackDir(n.param2);
 			v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
 			dir_f *= BS/2 - BS/6 - BS/20;
 			v3f cpf = npf + dir_f;
@@ -489,9 +489,9 @@ void getPointedNode(Client *client, v3f player_position,
 				}
 			}
 		}
-		else if(n.d == CONTENT_SIGN_WALL)
+		else if(n.getContent() == CONTENT_SIGN_WALL)
 		{
-			v3s16 dir = unpackDir(n.dir);
+			v3s16 dir = unpackDir(n.param2);
 			v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
 			dir_f *= BS/2 - BS/6 - BS/20;
 			v3f cpf = npf + dir_f;
@@ -538,9 +538,9 @@ void getPointedNode(Client *client, v3f player_position,
 				}
 			}
 		}
-		else if(n.d == CONTENT_RAIL)
+		else if(n.getContent() == CONTENT_RAIL)
 		{
-			v3s16 dir = unpackDir(n.dir);
+			v3s16 dir = unpackDir(n.param0);
 			v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
 			dir_f *= BS/2 - BS/6 - BS/20;
 			v3f cpf = npf + dir_f;
@@ -1038,9 +1038,9 @@ void the_game(
 		//bool screensize_changed = screensize != last_screensize;
 
 		// Resize hotbar
-		if(screensize.Y <= 600)
+		if(screensize.Y <= 800)
 			hotbar_imagesize = 32;
-		else if(screensize.Y <= 1024)
+		else if(screensize.Y <= 1280)
 			hotbar_imagesize = 48;
 		else
 			hotbar_imagesize = 64;
@@ -1759,7 +1759,7 @@ void the_game(
 					}
 
 					// Get digging properties for material and tool
-					u8 material = n.d;
+					content_t material = n.getContent();
 					DiggingProperties prop =
 							getDiggingProperties(material, toolname);
 					
diff --git a/src/inventory.cpp b/src/inventory.cpp
index fec51a759e581617db89f43eef9bb494ef82f949..7ef7f0138ab89a88b53929cb98e05d81b5d0838e 100644
--- a/src/inventory.cpp
+++ b/src/inventory.cpp
@@ -61,7 +61,7 @@ InventoryItem* InventoryItem::deSerialize(std::istream &is)
 		is>>material;
 		u16 count;
 		is>>count;
-		if(material > 255)
+		if(material > MAX_CONTENT)
 			throw SerializationError("Too large material number");
 		return new MaterialItem(material, count);
 	}
diff --git a/src/inventory.h b/src/inventory.h
index 07d81a3f7eecb77c8575616bd87a763d447357f4..5c64f89bb20e9bb65fe084da289493fb7309f08a 100644
--- a/src/inventory.h
+++ b/src/inventory.h
@@ -30,8 +30,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "common_irrlicht.h"
 #include "debug.h"
 #include "mapblockobject.h"
-// For g_materials
-#include "main.h"
+#include "main.h" // For g_materials
+#include "mapnode.h" // For content_t
 
 #define QUANTITY_ITEM_MAX_COUNT 99
 
@@ -113,7 +113,7 @@ class InventoryItem
 class MaterialItem : public InventoryItem
 {
 public:
-	MaterialItem(u8 content, u16 count):
+	MaterialItem(content_t content, u16 count):
 		InventoryItem(count)
 	{
 		m_content = content;
@@ -175,12 +175,12 @@ class MaterialItem : public InventoryItem
 	/*
 		Special methods
 	*/
-	u8 getMaterial()
+	content_t getMaterial()
 	{
 		return m_content;
 	}
 private:
-	u8 m_content;
+	content_t m_content;
 };
 
 //TODO: Remove
diff --git a/src/main.cpp b/src/main.cpp
index bcca60d95d58ba16e343f035bef17e7fd80e86f1..da17b84bc593b44cb217d927a2c39142635c6cd6 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,1652 +1,1682 @@
-/*
-Minetest-c55
-Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-/*
-=============================== NOTES ==============================
-NOTE: Things starting with TODO are sometimes only suggestions.
-
-NOTE: iostream.imbue(std::locale("C")) is very slow
-NOTE: Global locale is now set at initialization
-
-NOTE: If VBO (EHM_STATIC) is used, remember to explicitly free the
-      hardware buffer (it is not freed automatically)
-
-NOTE: A random to-do list saved here as documentation:
-A list of "active blocks" in which stuff happens. (+=done)
-	+ Add a never-resetted game timer to the server
-	+ Add a timestamp value to blocks
-	+ The simple rule: All blocks near some player are "active"
-	- Do stuff in real time in active blocks
-		+ Handle objects
-		- Grow grass, delete leaves without a tree
-		- Spawn some mobs based on some rules
-		- Transform cobble to mossy cobble near water
-		- Run a custom script
-		- ...And all kinds of other dynamic stuff
-	+ Keep track of when a block becomes active and becomes inactive
-	+ When a block goes inactive:
-		+ Store objects statically to block
-		+ Store timer value as the timestamp
-	+ When a block goes active:
-		+ Create active objects out of static objects
-		- Simulate the results of what would have happened if it would have
-		  been active for all the time
-		  	- Grow a lot of grass and so on
-	+ Initially it is fine to send information about every active object
-	  to every player. Eventually it should be modified to only send info
-	  about the nearest ones.
-	  	+ This was left to be done by the old system and it sends only the
-		  nearest ones.
-
-Old, wild and random suggestions that probably won't be done:
--------------------------------------------------------------
-
-SUGG: If player is on ground, mainly fetch ground-level blocks
-
-SUGG: Expose Connection's seqnums and ACKs to server and client.
-      - This enables saving many packets and making a faster connection
-	  - This also enables server to check if client has received the
-	    most recent block sent, for example.
-SUGG: Add a sane bandwidth throttling system to Connection
-
-SUGG: More fine-grained control of client's dumping of blocks from
-      memory
-	  - ...What does this mean in the first place?
-
-SUGG: A map editing mode (similar to dedicated server mode)
-
-SUGG: Transfer more blocks in a single packet
-SUGG: A blockdata combiner class, to which blocks are added and at
-      destruction it sends all the stuff in as few packets as possible.
-SUGG: Make a PACKET_COMBINED which contains many subpackets. Utilize
-      it by sending more stuff in a single packet.
-	  - Add a packet queue to RemoteClient, from which packets will be
-	    combined with object data packets
-		- This is not exactly trivial: the object data packets are
-		  sometimes very big by themselves
-	  - This might not give much network performance gain though.
-
-SUGG: Precalculate lighting translation table at runtime (at startup)
-      - This is not doable because it is currently hand-made and not
-	    based on some mathematical function.
-		- Note: This has been changing lately
-
-SUGG: A version number to blocks, which increments when the block is
-      modified (node add/remove, water update, lighting update)
-	  - This can then be used to make sure the most recent version of
-	    a block has been sent to client, for example
-
-SUGG: Make the amount of blocks sending to client and the total
-	  amount of blocks dynamically limited. Transferring blocks is the
-	  main network eater of this system, so it is the one that has
-	  to be throttled so that RTTs stay low.
-
-SUGG: Meshes of blocks could be split into 6 meshes facing into
-      different directions and then only those drawn that need to be
-
-SUGG: Background music based on cellular automata?
-      http://www.earslap.com/projectslab/otomata
-
-SUGG: Simple light color information to air
-
-SUGG: Server-side objects could be moved based on nodes to enable very
-      lightweight operation and simple AI
-	- Not practical; client would still need to show smooth movement.
-
-SUGG: Make a system for pregenerating quick information for mapblocks, so
-	  that the client can show them as cubes before they are actually sent
-	  or even generated.
-
-SUGG: Erosion simulation at map generation time
-    - This might be plausible if larger areas of map were pregenerated
-	  without lighting (which is slow)
-	- Simulate water flows, which would carve out dirt fast and
-	  then turn stone into gravel and sand and relocate it.
-	- How about relocating minerals, too? Coal and gold in
-	  downstream sand and gravel would be kind of cool
-	  - This would need a better way of handling minerals, mainly
-		to have mineral content as a separate field. the first
-		parameter field is free for this.
-	- Simulate rock falling from cliffs when water has removed
-	  enough solid rock from the bottom
-
-SUGG: For non-mapgen FarMesh: Add a per-sector database to store surface
-      stuff as simple flags/values
-      - Light?
-	  - A building?
-	  And at some point make the server send this data to the client too,
-	  instead of referring to the noise functions
-	  - Ground height
-	  - Surface ground type
-	  - Trees?
-
-Gaming ideas:
--------------
-
-- Aim for something like controlling a single dwarf in Dwarf Fortress
-- The player could go faster by a crafting a boat, or riding an animal
-- Random NPC traders. what else?
-
-Game content:
--------------
-
-- When furnace is destroyed, move items to player's inventory
-- Add lots of stuff
-- Glass blocks
-- Growing grass, decaying leaves
-	- This can be done in the active blocks I guess.
-	- Lots of stuff can be done in the active blocks.
-	- Uh, is there an active block list somewhere? I think not. Add it.
-- Breaking weak structures
-	- This can probably be accomplished in the same way as grass
-- Player health points
-	- When player dies, throw items on map (needs better item-on-map
-	  implementation)
-- Cobble to get mossy if near water
-- More slots in furnace source list, so that multiple ingredients
-  are possible.
-- Keys to chests?
-
-- The Treasure Guard; a big monster with a hammer
-	- The hammer does great damage, shakes the ground and removes a block
-	- You can drop on top of it, and have some time to attack there
-	  before he shakes you off
-
-- Maybe the difficulty could come from monsters getting tougher in
-  far-away places, and the player starting to need something from
-  there when time goes by.
-  - The player would have some of that stuff at the beginning, and
-    would need new supplies of it when it runs out
-
-- A bomb
-- A spread-items-on-map routine for the bomb, and for dying players
-
-- Fighting:
-  - Proper sword swing simulation
-  - Player should get damage from colliding to a wall at high speed
-
-Documentation:
---------------
-
-Build system / running:
------------------------
-
-Networking and serialization:
------------------------------
-
-SUGG: Fix address to be ipv6 compatible
-
-User Interface:
----------------
-
-Graphics:
----------
-
-SUGG: Combine MapBlock's face caches to so big pieces that VBO
-      can be used
-      - That is >500 vertices
-	  - This is not easy; all the MapBlocks close to the player would
-	    still need to be drawn separately and combining the blocks
-		would have to happen in a background thread
-
-SUGG: Make fetching sector's blocks more efficient when rendering
-      sectors that have very large amounts of blocks (on client)
-	  - Is this necessary at all?
-
-SUGG: Draw cubes in inventory directly with 3D drawing commands, so that
-      animating them is easier.
-
-SUGG: Option for enabling proper alpha channel for textures
-
-TODO: Flowing water animation
-
-TODO: A setting for enabling bilinear filtering for textures
-
-TODO: Better control of draw_control.wanted_max_blocks
-
-TODO: Further investigate the use of GPU lighting in addition to the
-      current one
-
-TODO: Artificial (night) light could be more yellow colored than sunlight.
-      - This is technically doable.
-	  - Also the actual colors of the textures could be made less colorful
-	    in the dark but it's a bit more difficult.
-
-SUGG: Somehow make the night less colorful
-
-TODO: Occlusion culling
-      - At the same time, move some of the renderMap() block choosing code
-        to the same place as where the new culling happens.
-      - Shoot some rays per frame and when ready, make a new list of
-	    blocks for usage of renderMap and give it a new pointer to it.
-
-Configuration:
---------------
-
-Client:
--------
-
-TODO: Untie client network operations from framerate
-      - Needs some input queues or something
-	  - This won't give much performance boost because calculating block
-	    meshes takes so long
-
-SUGG: Make morning and evening transition more smooth and maybe shorter
-
-TODO: Don't update all meshes always on single node changes, but
-      check which ones should be updated
-	  - implement Map::updateNodeMeshes() and the usage of it
-	  - It will give almost always a 4x boost in mesh update performance.
-
-- A weapon engine
-
-- Tool/weapon visualization
-
-FIXME: When disconnected to the menu, memory is not freed properly
-
-TODO: Investigate how much the mesh generator thread gets used when
-      transferring map data
-
-Server:
--------
-
-SUGG: Make an option to the server to disable building and digging near
-      the starting position
-
-FIXME: Server sometimes goes into some infinite PeerNotFoundException loop
-
-* Fix the problem with the server constantly saving one or a few
-  blocks? List the first saved block, maybe it explains.
-  - It is probably caused by oscillating water
-  - TODO: Investigate if this still happens (this is a very old one)
-* Make a small history check to transformLiquids to detect and log
-  continuous oscillations, in such detail that they can be fixed.
-
-FIXME: The new optimized map sending doesn't sometimes send enough blocks
-       from big caves and such
-FIXME: Block send distance configuration does not take effect for some reason
-
-Environment:
-------------
-
-TODO: Add proper hooks to when adding and removing active blocks
-
-TODO: Finish the ActiveBlockModifier stuff and use it for something
-
-Objects:
---------
-
-TODO: Get rid of MapBlockObjects and use only ActiveObjects
-	- Skipping the MapBlockObject data is nasty - there is no "total
-	  length" stored; have to make a SkipMBOs function which contains
-	  enough of the current code to skip them properly.
-
-SUGG: MovingObject::move and Player::move are basically the same.
-      combine them.
-	- NOTE: This is a bit tricky because player has the sneaking ability
-	- NOTE: Player::move is more up-to-date.
-	- NOTE: There is a simple move implementation now in collision.{h,cpp}
-	- NOTE: MovingObject will be deleted (MapBlockObject)
-
-TODO: Add a long step function to objects that is called with the time
-      difference when block activates
-
-Map:
-----
-
-TODO: Mineral and ground material properties
-      - This way mineral ground toughness can be calculated with just
-	    some formula, as well as tool strengths
-	  - There are TODOs in appropriate files: material.h, content_mapnode.h
-
-TODO: Flowing water to actually contain flow direction information
-      - There is a space for this - it just has to be implemented.
-
-TODO: Consider smoothening cave floors after generating them
-
-Misc. stuff:
-------------
-TODO: Make sure server handles removing grass when a block is placed (etc)
-      - The client should not do it by itself
-	  - NOTE: I think nobody does it currently...
-TODO: Block cube placement around player's head
-TODO: Protocol version field
-TODO: Think about using same bits for material for fences and doors, for
-	  example
-TODO: Move mineral to param2, increment map serialization version, add
-      conversion
-
-TODO: Restart irrlicht completely when coming back to main menu from game.
-	- This gets rid of everything that is stored in irrlicht's caches.
-
-TODO: Merge bahamada's audio stuff (clean patch available)
-
-TODO: Merge key configuration menu (no clean patch available)
-
-Making it more portable:
-------------------------
- 
-Stuff to do before release:
----------------------------
-
-Fixes to the current release:
------------------------------
-
-Stuff to do after release:
----------------------------
-
-Doing currently:
-----------------
-
-======================================================================
-
-*/
-
-#ifdef NDEBUG
-	#ifdef _WIN32
-		#pragma message ("Disabling unit tests")
-	#else
-		#warning "Disabling unit tests"
-	#endif
-	// Disable unit tests
-	#define ENABLE_TESTS 0
-#else
-	// Enable unit tests
-	#define ENABLE_TESTS 1
-#endif
-
-#ifdef _MSC_VER
-	#pragma comment(lib, "Irrlicht.lib")
-	//#pragma comment(lib, "jthread.lib")
-	#pragma comment(lib, "zlibwapi.lib")
-	#pragma comment(lib, "Shell32.lib")
-	// This would get rid of the console window
-	//#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
-#endif
-
-#include <iostream>
-#include <fstream>
-#include <locale.h>
-#include "main.h"
-#include "common_irrlicht.h"
-#include "debug.h"
-#include "test.h"
-#include "server.h"
-#include "constants.h"
-#include "porting.h"
-#include "gettime.h"
-#include "guiMessageMenu.h"
-#include "filesys.h"
-#include "config.h"
-#include "guiMainMenu.h"
-#include "mineral.h"
-#include "materials.h"
-#include "game.h"
-#include "keycode.h"
-#include "tile.h"
-
-#include "gettext.h"
-
-// This makes textures
-ITextureSource *g_texturesource = NULL;
-
-/*
-	Settings.
-	These are loaded from the config file.
-*/
-
-Settings g_settings;
-// This is located in defaultsettings.cpp
-extern void set_default_settings();
-
-// Global profiler
-Profiler g_profiler;
-
-/*
-	Random stuff
-*/
-
-/*
-	GUI Stuff
-*/
-
-gui::IGUIEnvironment* guienv = NULL;
-gui::IGUIStaticText *guiroot = NULL;
-
-MainMenuManager g_menumgr;
-
-bool noMenuActive()
-{
-	return (g_menumgr.menuCount() == 0);
-}
-
-// Passed to menus to allow disconnecting and exiting
-
-MainGameCallback *g_gamecallback = NULL;
-
-/*
-	Debug streams
-*/
-
-// Connection
-std::ostream *dout_con_ptr = &dummyout;
-std::ostream *derr_con_ptr = &dstream_no_stderr;
-//std::ostream *dout_con_ptr = &dstream_no_stderr;
-//std::ostream *derr_con_ptr = &dstream_no_stderr;
-//std::ostream *dout_con_ptr = &dstream;
-//std::ostream *derr_con_ptr = &dstream;
-
-// Server
-std::ostream *dout_server_ptr = &dstream;
-std::ostream *derr_server_ptr = &dstream;
-
-// Client
-std::ostream *dout_client_ptr = &dstream;
-std::ostream *derr_client_ptr = &dstream;
-
-/*
-	gettime.h implementation
-*/
-
-// A small helper class
-class TimeGetter
-{
-public:
-	virtual u32 getTime() = 0;
-};
-
-// A precise irrlicht one
-class IrrlichtTimeGetter: public TimeGetter
-{
-public:
-	IrrlichtTimeGetter(IrrlichtDevice *device):
-		m_device(device)
-	{}
-	u32 getTime()
-	{
-		if(m_device == NULL)
-			return 0;
-		return m_device->getTimer()->getRealTime();
-	}
-private:
-	IrrlichtDevice *m_device;
-};
-// Not so precise one which works without irrlicht
-class SimpleTimeGetter: public TimeGetter
-{
-public:
-	u32 getTime()
-	{
-		return porting::getTimeMs();
-	}
-};
-
-// A pointer to a global instance of the time getter
-// TODO: why?
-TimeGetter *g_timegetter = NULL;
-
-u32 getTimeMs()
-{
-	if(g_timegetter == NULL)
-		return 0;
-	return g_timegetter->getTime();
-}
-
-/*
-	Event handler for Irrlicht
-
-	NOTE: Everything possible should be moved out from here,
-	      probably to InputHandler and the_game
-*/
-
-class MyEventReceiver : public IEventReceiver
-{
-public:
-	// This is the one method that we have to implement
-	virtual bool OnEvent(const SEvent& event)
-	{
-		/*
-			React to nothing here if a menu is active
-		*/
-		if(noMenuActive() == false)
-		{
-			return false;
-		}
-
-		// Remember whether each key is down or up
-		if(event.EventType == irr::EET_KEY_INPUT_EVENT)
-		{
-			keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
-
-			if(event.KeyInput.PressedDown)
-				keyWasDown[event.KeyInput.Key] = true;
-		}
-
-		if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
-		{
-			if(noMenuActive() == false)
-			{
-				left_active = false;
-				middle_active = false;
-				right_active = false;
-			}
-			else
-			{
-				//dstream<<"MyEventReceiver: mouse input"<<std::endl;
-				left_active = event.MouseInput.isLeftPressed();
-				middle_active = event.MouseInput.isMiddlePressed();
-				right_active = event.MouseInput.isRightPressed();
-
-				if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
-				{
-					leftclicked = true;
-				}
-				if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
-				{
-					rightclicked = true;
-				}
-				if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
-				{
-					leftreleased = true;
-				}
-				if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
-				{
-					rightreleased = true;
-				}
-				if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
-				{
-					mouse_wheel += event.MouseInput.Wheel;
-				}
-			}
-		}
-
-		return false;
-	}
-
-	bool IsKeyDown(EKEY_CODE keyCode) const
-	{
-		return keyIsDown[keyCode];
-	}
-	
-	// Checks whether a key was down and resets the state
-	bool WasKeyDown(EKEY_CODE keyCode)
-	{
-		bool b = keyWasDown[keyCode];
-		keyWasDown[keyCode] = false;
-		return b;
-	}
-
-	s32 getMouseWheel()
-	{
-		s32 a = mouse_wheel;
-		mouse_wheel = 0;
-		return a;
-	}
-
-	void clearInput()
-	{
-		for(u32 i=0; i<KEY_KEY_CODES_COUNT; i++)
-		{
-			keyIsDown[i] = false;
-			keyWasDown[i] = false;
-		}
-		
-		leftclicked = false;
-		rightclicked = false;
-		leftreleased = false;
-		rightreleased = false;
-
-		left_active = false;
-		middle_active = false;
-		right_active = false;
-
-		mouse_wheel = 0;
-	}
-
-	MyEventReceiver()
-	{
-		clearInput();
-	}
-
-	bool leftclicked;
-	bool rightclicked;
-	bool leftreleased;
-	bool rightreleased;
-
-	bool left_active;
-	bool middle_active;
-	bool right_active;
-
-	s32 mouse_wheel;
-
-private:
-	IrrlichtDevice *m_device;
-	
-	// The current state of keys
-	bool keyIsDown[KEY_KEY_CODES_COUNT];
-	// Whether a key has been pressed or not
-	bool keyWasDown[KEY_KEY_CODES_COUNT];
-};
-
-/*
-	Separated input handler
-*/
-
-class RealInputHandler : public InputHandler
-{
-public:
-	RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
-		m_device(device),
-		m_receiver(receiver)
-	{
-	}
-	virtual bool isKeyDown(EKEY_CODE keyCode)
-	{
-		return m_receiver->IsKeyDown(keyCode);
-	}
-	virtual bool wasKeyDown(EKEY_CODE keyCode)
-	{
-		return m_receiver->WasKeyDown(keyCode);
-	}
-	virtual v2s32 getMousePos()
-	{
-		return m_device->getCursorControl()->getPosition();
-	}
-	virtual void setMousePos(s32 x, s32 y)
-	{
-		m_device->getCursorControl()->setPosition(x, y);
-	}
-
-	virtual bool getLeftState()
-	{
-		return m_receiver->left_active;
-	}
-	virtual bool getRightState()
-	{
-		return m_receiver->right_active;
-	}
-	
-	virtual bool getLeftClicked()
-	{
-		return m_receiver->leftclicked;
-	}
-	virtual bool getRightClicked()
-	{
-		return m_receiver->rightclicked;
-	}
-	virtual void resetLeftClicked()
-	{
-		m_receiver->leftclicked = false;
-	}
-	virtual void resetRightClicked()
-	{
-		m_receiver->rightclicked = false;
-	}
-
-	virtual bool getLeftReleased()
-	{
-		return m_receiver->leftreleased;
-	}
-	virtual bool getRightReleased()
-	{
-		return m_receiver->rightreleased;
-	}
-	virtual void resetLeftReleased()
-	{
-		m_receiver->leftreleased = false;
-	}
-	virtual void resetRightReleased()
-	{
-		m_receiver->rightreleased = false;
-	}
-
-	virtual s32 getMouseWheel()
-	{
-		return m_receiver->getMouseWheel();
-	}
-
-	void clear()
-	{
-		m_receiver->clearInput();
-	}
-private:
-	IrrlichtDevice *m_device;
-	MyEventReceiver *m_receiver;
-};
-
-class RandomInputHandler : public InputHandler
-{
-public:
-	RandomInputHandler()
-	{
-		leftdown = false;
-		rightdown = false;
-		leftclicked = false;
-		rightclicked = false;
-		leftreleased = false;
-		rightreleased = false;
-		for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
-			keydown[i] = false;
-	}
-	virtual bool isKeyDown(EKEY_CODE keyCode)
-	{
-		return keydown[keyCode];
-	}
-	virtual bool wasKeyDown(EKEY_CODE keyCode)
-	{
-		return false;
-	}
-	virtual v2s32 getMousePos()
-	{
-		return mousepos;
-	}
-	virtual void setMousePos(s32 x, s32 y)
-	{
-		mousepos = v2s32(x,y);
-	}
-
-	virtual bool getLeftState()
-	{
-		return leftdown;
-	}
-	virtual bool getRightState()
-	{
-		return rightdown;
-	}
-
-	virtual bool getLeftClicked()
-	{
-		return leftclicked;
-	}
-	virtual bool getRightClicked()
-	{
-		return rightclicked;
-	}
-	virtual void resetLeftClicked()
-	{
-		leftclicked = false;
-	}
-	virtual void resetRightClicked()
-	{
-		rightclicked = false;
-	}
-
-	virtual bool getLeftReleased()
-	{
-		return leftreleased;
-	}
-	virtual bool getRightReleased()
-	{
-		return rightreleased;
-	}
-	virtual void resetLeftReleased()
-	{
-		leftreleased = false;
-	}
-	virtual void resetRightReleased()
-	{
-		rightreleased = false;
-	}
-
-	virtual s32 getMouseWheel()
-	{
-		return 0;
-	}
-
-	virtual void step(float dtime)
-	{
-		{
-			static float counter1 = 0;
-			counter1 -= dtime;
-			if(counter1 < 0.0)
-			{
-				counter1 = 0.1*Rand(1, 40);
-				keydown[getKeySetting("keymap_jump")] =
-						!keydown[getKeySetting("keymap_jump")];
-			}
-		}
-		{
-			static float counter1 = 0;
-			counter1 -= dtime;
-			if(counter1 < 0.0)
-			{
-				counter1 = 0.1*Rand(1, 40);
-				keydown[getKeySetting("keymap_special1")] =
-						!keydown[getKeySetting("keymap_special1")];
-			}
-		}
-		{
-			static float counter1 = 0;
-			counter1 -= dtime;
-			if(counter1 < 0.0)
-			{
-				counter1 = 0.1*Rand(1, 40);
-				keydown[getKeySetting("keymap_forward")] =
-						!keydown[getKeySetting("keymap_forward")];
-			}
-		}
-		{
-			static float counter1 = 0;
-			counter1 -= dtime;
-			if(counter1 < 0.0)
-			{
-				counter1 = 0.1*Rand(1, 40);
-				keydown[getKeySetting("keymap_left")] =
-						!keydown[getKeySetting("keymap_left")];
-			}
-		}
-		{
-			static float counter1 = 0;
-			counter1 -= dtime;
-			if(counter1 < 0.0)
-			{
-				counter1 = 0.1*Rand(1, 20);
-				mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
-			}
-		}
-		{
-			static float counter1 = 0;
-			counter1 -= dtime;
-			if(counter1 < 0.0)
-			{
-				counter1 = 0.1*Rand(1, 30);
-				leftdown = !leftdown;
-				if(leftdown)
-					leftclicked = true;
-				if(!leftdown)
-					leftreleased = true;
-			}
-		}
-		{
-			static float counter1 = 0;
-			counter1 -= dtime;
-			if(counter1 < 0.0)
-			{
-				counter1 = 0.1*Rand(1, 15);
-				rightdown = !rightdown;
-				if(rightdown)
-					rightclicked = true;
-				if(!rightdown)
-					rightreleased = true;
-			}
-		}
-		mousepos += mousespeed;
-	}
-
-	s32 Rand(s32 min, s32 max)
-	{
-		return (myrand()%(max-min+1))+min;
-	}
-private:
-	bool keydown[KEY_KEY_CODES_COUNT];
-	v2s32 mousepos;
-	v2s32 mousespeed;
-	bool leftdown;
-	bool rightdown;
-	bool leftclicked;
-	bool rightclicked;
-	bool leftreleased;
-	bool rightreleased;
-};
-
-// These are defined global so that they're not optimized too much.
-// Can't change them to volatile.
-s16 temp16;
-f32 tempf;
-v3f tempv3f1;
-v3f tempv3f2;
-std::string tempstring;
-std::string tempstring2;
-
-void SpeedTests()
-{
-	{
-		dstream<<"The following test should take around 20ms."<<std::endl;
-		TimeTaker timer("Testing std::string speed");
-		const u32 jj = 10000;
-		for(u32 j=0; j<jj; j++)
-		{
-			tempstring = "";
-			tempstring2 = "";
-			const u32 ii = 10;
-			for(u32 i=0; i<ii; i++){
-				tempstring2 += "asd";
-			}
-			for(u32 i=0; i<ii+1; i++){
-				tempstring += "asd";
-				if(tempstring == tempstring2)
-					break;
-			}
-		}
-	}
-	
-	dstream<<"All of the following tests should take around 100ms each."
-			<<std::endl;
-
-	{
-		TimeTaker timer("Testing floating-point conversion speed");
-		tempf = 0.001;
-		for(u32 i=0; i<4000000; i++){
-			temp16 += tempf;
-			tempf += 0.001;
-		}
-	}
-	
-	{
-		TimeTaker timer("Testing floating-point vector speed");
-
-		tempv3f1 = v3f(1,2,3);
-		tempv3f2 = v3f(4,5,6);
-		for(u32 i=0; i<10000000; i++){
-			tempf += tempv3f1.dotProduct(tempv3f2);
-			tempv3f2 += v3f(7,8,9);
-		}
-	}
-
-	{
-		TimeTaker timer("Testing core::map speed");
-		
-		core::map<v2s16, f32> map1;
-		tempf = -324;
-		const s16 ii=300;
-		for(s16 y=0; y<ii; y++){
-			for(s16 x=0; x<ii; x++){
-				map1.insert(v2s16(x,y), tempf);
-				tempf += 1;
-			}
-		}
-		for(s16 y=ii-1; y>=0; y--){
-			for(s16 x=0; x<ii; x++){
-				tempf = map1[v2s16(x,y)];
-			}
-		}
-	}
-
-	{
-		dstream<<"Around 5000/ms should do well here."<<std::endl;
-		TimeTaker timer("Testing mutex speed");
-		
-		JMutex m;
-		m.Init();
-		u32 n = 0;
-		u32 i = 0;
-		do{
-			n += 10000;
-			for(; i<n; i++){
-				m.Lock();
-				m.Unlock();
-			}
-		}
-		// Do at least 10ms
-		while(timer.getTime() < 10);
-
-		u32 dtime = timer.stop();
-		u32 per_ms = n / dtime;
-		std::cout<<"Done. "<<dtime<<"ms, "
-				<<per_ms<<"/ms"<<std::endl;
-	}
-}
-
-void drawMenuBackground(video::IVideoDriver* driver)
-{
-	core::dimension2d<u32> screensize = driver->getScreenSize();
-		
-	video::ITexture *bgtexture =
-			driver->getTexture(getTexturePath("mud.png").c_str());
-	if(bgtexture)
-	{
-		s32 texturesize = 128;
-		s32 tiled_y = screensize.Height / texturesize + 1;
-		s32 tiled_x = screensize.Width / texturesize + 1;
-		
-		for(s32 y=0; y<tiled_y; y++)
-		for(s32 x=0; x<tiled_x; x++)
-		{
-			core::rect<s32> rect(0,0,texturesize,texturesize);
-			rect += v2s32(x*texturesize, y*texturesize);
-			driver->draw2DImage(bgtexture, rect,
-				core::rect<s32>(core::position2d<s32>(0,0),
-				core::dimension2di(bgtexture->getSize())),
-				NULL, NULL, true);
-		}
-	}
-	
-	video::ITexture *logotexture =
-			driver->getTexture(getTexturePath("menulogo.png").c_str());
-	if(logotexture)
-	{
-		v2s32 logosize(logotexture->getOriginalSize().Width,
-				logotexture->getOriginalSize().Height);
-		logosize *= 4;
-
-		video::SColor bgcolor(255,50,50,50);
-		core::rect<s32> bgrect(0, screensize.Height-logosize.Y-20,
-				screensize.Width, screensize.Height);
-		driver->draw2DRectangle(bgcolor, bgrect, NULL);
-
-		core::rect<s32> rect(0,0,logosize.X,logosize.Y);
-		rect += v2s32(screensize.Width/2,screensize.Height-10-logosize.Y);
-		rect -= v2s32(logosize.X/2, 0);
-		driver->draw2DImage(logotexture, rect,
-			core::rect<s32>(core::position2d<s32>(0,0),
-			core::dimension2di(logotexture->getSize())),
-			NULL, NULL, true);
-	}
-}
-
-int main(int argc, char *argv[])
-{
-	/*
-		Initialization
-	*/
-
-	// Set locale. This is for forcing '.' as the decimal point.
-	std::locale::global(std::locale("C"));
-	// This enables printing all characters in bitmap font
-	setlocale(LC_CTYPE, "en_US");
-	/*
-		Parse command line
-	*/
-	
-	// List all allowed options
-	core::map<std::string, ValueSpec> allowed_options;
-	allowed_options.insert("help", ValueSpec(VALUETYPE_FLAG));
-	allowed_options.insert("server", ValueSpec(VALUETYPE_FLAG,
-			"Run server directly"));
-	allowed_options.insert("config", ValueSpec(VALUETYPE_STRING,
-			"Load configuration from specified file"));
-	allowed_options.insert("port", ValueSpec(VALUETYPE_STRING));
-	allowed_options.insert("address", ValueSpec(VALUETYPE_STRING));
-	allowed_options.insert("random-input", ValueSpec(VALUETYPE_FLAG));
-	allowed_options.insert("disable-unittests", ValueSpec(VALUETYPE_FLAG));
-	allowed_options.insert("enable-unittests", ValueSpec(VALUETYPE_FLAG));
-	allowed_options.insert("map-dir", ValueSpec(VALUETYPE_STRING));
-#ifdef _WIN32
-	allowed_options.insert("dstream-on-stderr", ValueSpec(VALUETYPE_FLAG));
-#endif
-	allowed_options.insert("speedtests", ValueSpec(VALUETYPE_FLAG));
-
-	Settings cmd_args;
-	
-	bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options);
-
-	if(ret == false || cmd_args.getFlag("help"))
-	{
-		dstream<<"Allowed options:"<<std::endl;
-		for(core::map<std::string, ValueSpec>::Iterator
-				i = allowed_options.getIterator();
-				i.atEnd() == false; i++)
-		{
-			dstream<<"  --"<<i.getNode()->getKey();
-			if(i.getNode()->getValue().type == VALUETYPE_FLAG)
-			{
-			}
-			else
-			{
-				dstream<<" <value>";
-			}
-			dstream<<std::endl;
-
-			if(i.getNode()->getValue().help != NULL)
-			{
-				dstream<<"      "<<i.getNode()->getValue().help
-						<<std::endl;
-			}
-		}
-
-		return cmd_args.getFlag("help") ? 0 : 1;
-	}
-	
-	/*
-		Low-level initialization
-	*/
-
-	bool disable_stderr = false;
-#ifdef _WIN32
-	if(cmd_args.getFlag("dstream-on-stderr") == false)
-		disable_stderr = true;
-#endif
-
-	porting::signal_handler_init();
-	bool &kill = *porting::signal_handler_killstatus();
-	
-	// Initialize porting::path_data and porting::path_userdata
-	porting::initializePaths();
-
-	// Create user data directory
-	fs::CreateDir(porting::path_userdata);
-
-	init_gettext((porting::path_data+"/../locale").c_str());
-
-	// Initialize debug streams
-#ifdef RUN_IN_PLACE
-	std::string debugfile = DEBUGFILE;
-#else
-	std::string debugfile = porting::path_userdata+"/"+DEBUGFILE;
-#endif
-	debugstreams_init(disable_stderr, debugfile.c_str());
-	// Initialize debug stacks
-	debug_stacks_init();
-
-	DSTACK(__FUNCTION_NAME);
-
-	// Init material properties table
-	//initializeMaterialProperties();
-
-	// Debug handler
-	BEGIN_DEBUG_EXCEPTION_HANDLER
-
-	// Print startup message
-	dstream<<DTIME<<PROJECT_NAME <<
-			" with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
-			<<", "<<BUILD_INFO
-			<<std::endl;
-	
-	/*
-		Basic initialization
-	*/
-
-	// Initialize default settings
-	set_default_settings();
-	
-	// Initialize sockets
-	sockets_init();
-	atexit(sockets_cleanup);
-	
-	/*
-		Read config file
-	*/
-	
-	// Path of configuration file in use
-	std::string configpath = "";
-	
-	if(cmd_args.exists("config"))
-	{
-		bool r = g_settings.readConfigFile(cmd_args.get("config").c_str());
-		if(r == false)
-		{
-			dstream<<"Could not read configuration from \""
-					<<cmd_args.get("config")<<"\""<<std::endl;
-			return 1;
-		}
-		configpath = cmd_args.get("config");
-	}
-	else
-	{
-		core::array<std::string> filenames;
-		filenames.push_back(porting::path_userdata + "/minetest.conf");
-#ifdef RUN_IN_PLACE
-		filenames.push_back(porting::path_userdata + "/../minetest.conf");
-#endif
-
-		for(u32 i=0; i<filenames.size(); i++)
-		{
-			bool r = g_settings.readConfigFile(filenames[i].c_str());
-			if(r)
-			{
-				configpath = filenames[i];
-				break;
-			}
-		}
-		
-		// If no path found, use the first one (menu creates the file)
-		if(configpath == "")
-			configpath = filenames[0];
-	}
-
-	// Initialize random seed
-	srand(time(0));
-	mysrand(time(0));
-
-	/*
-		Pre-initialize some stuff with a dummy irrlicht wrapper.
-
-		These are needed for unit tests at least.
-	*/
-	
-	// Initial call with g_texturesource not set.
-	init_mapnode();
-
-	/*
-		Run unit tests
-	*/
-
-	if((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false)
-			|| cmd_args.getFlag("enable-unittests") == true)
-	{
-		run_tests();
-	}
-	
-	/*for(s16 y=-100; y<100; y++)
-	for(s16 x=-100; x<100; x++)
-	{
-		std::cout<<noise2d_gradient((double)x/10,(double)y/10, 32415)<<std::endl;
-	}
-	return 0;*/
-	
-	/*
-		Game parameters
-	*/
-
-	// Port
-	u16 port = 30000;
-	if(cmd_args.exists("port"))
-		port = cmd_args.getU16("port");
-	else if(g_settings.exists("port"))
-		port = g_settings.getU16("port");
-	if(port == 0)
-		port = 30000;
-	
-	// Map directory
-	std::string map_dir = porting::path_userdata+"/world";
-	if(cmd_args.exists("map-dir"))
-		map_dir = cmd_args.get("map-dir");
-	else if(g_settings.exists("map-dir"))
-		map_dir = g_settings.get("map-dir");
-	
-	// Run dedicated server if asked to
-	if(cmd_args.getFlag("server"))
-	{
-		DSTACK("Dedicated server branch");
-
-		// Create time getter
-		g_timegetter = new SimpleTimeGetter();
-		
-		// Create server
-		Server server(map_dir.c_str());
-		server.start(port);
-		
-		// Run server
-		dedicated_server_loop(server, kill);
-
-		return 0;
-	}
-
-
-	/*
-		More parameters
-	*/
-	
-	// Address to connect to
-	std::string address = "";
-	
-	if(cmd_args.exists("address"))
-	{
-		address = cmd_args.get("address");
-	}
-	else
-	{
-		address = g_settings.get("address");
-	}
-	
-	std::string playername = g_settings.get("name");
-
-	/*
-		Device initialization
-	*/
-
-	// Resolution selection
-	
-	bool fullscreen = false;
-	u16 screenW = g_settings.getU16("screenW");
-	u16 screenH = g_settings.getU16("screenH");
-
-	// Determine driver
-
-	video::E_DRIVER_TYPE driverType;
-	
-	std::string driverstring = g_settings.get("video_driver");
-
-	if(driverstring == "null")
-		driverType = video::EDT_NULL;
-	else if(driverstring == "software")
-		driverType = video::EDT_SOFTWARE;
-	else if(driverstring == "burningsvideo")
-		driverType = video::EDT_BURNINGSVIDEO;
-	else if(driverstring == "direct3d8")
-		driverType = video::EDT_DIRECT3D8;
-	else if(driverstring == "direct3d9")
-		driverType = video::EDT_DIRECT3D9;
-	else if(driverstring == "opengl")
-		driverType = video::EDT_OPENGL;
-	else
-	{
-		dstream<<"WARNING: Invalid video_driver specified; defaulting "
-				"to opengl"<<std::endl;
-		driverType = video::EDT_OPENGL;
-	}
-
-	/*
-		Create device and exit if creation failed
-	*/
-
-	MyEventReceiver receiver;
-
-	IrrlichtDevice *device;
-	device = createDevice(driverType,
-			core::dimension2d<u32>(screenW, screenH),
-			16, fullscreen, false, false, &receiver);
-
-	if (device == 0)
-		return 1; // could not create selected driver.
-	
-	// Set device in game parameters
-	device = device;
+/*
+Minetest-c55
+Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+/*
+=============================== NOTES ==============================
+NOTE: Things starting with TODO are sometimes only suggestions.
+
+NOTE: iostream.imbue(std::locale("C")) is very slow
+NOTE: Global locale is now set at initialization
+
+NOTE: If VBO (EHM_STATIC) is used, remember to explicitly free the
+      hardware buffer (it is not freed automatically)
+
+NOTE: A random to-do list saved here as documentation:
+A list of "active blocks" in which stuff happens. (+=done)
+	+ Add a never-resetted game timer to the server
+	+ Add a timestamp value to blocks
+	+ The simple rule: All blocks near some player are "active"
+	- Do stuff in real time in active blocks
+		+ Handle objects
+		- Grow grass, delete leaves without a tree
+		- Spawn some mobs based on some rules
+		- Transform cobble to mossy cobble near water
+		- Run a custom script
+		- ...And all kinds of other dynamic stuff
+	+ Keep track of when a block becomes active and becomes inactive
+	+ When a block goes inactive:
+		+ Store objects statically to block
+		+ Store timer value as the timestamp
+	+ When a block goes active:
+		+ Create active objects out of static objects
+		- Simulate the results of what would have happened if it would have
+		  been active for all the time
+		  	- Grow a lot of grass and so on
+	+ Initially it is fine to send information about every active object
+	  to every player. Eventually it should be modified to only send info
+	  about the nearest ones.
+	  	+ This was left to be done by the old system and it sends only the
+		  nearest ones.
+
+Vim conversion regexpes for moving to extended content type storage:
+%s/\(\.\|->\)d \([!=]=\)/\1getContent() \2/g
+%s/content_features(\([^.]*\)\.d)/content_features(\1)/g
+%s/\(\.\|->\)d = \([^;]*\);/\1setContent(\2);/g
+%s/\(getNodeNoExNoEmerge([^)]*)\)\.d/\1.getContent()/g
+%s/\(getNodeNoExNoEmerge(.*)\)\.d/\1.getContent()/g
+%s/\.d;/.getContent();/g
+%s/\(content_liquid\|content_flowing_liquid\|make_liquid_flowing\|content_pointable\)(\([^.]*\).d)/\1(\2.getContent())/g
+Other things to note:
+- node.d = node.param0 (only in raw serialization; use getContent() otherwise)
+- node.param = node.param1
+- node.dir = node.param2
+- content_walkable(node.d) etc should be changed to
+  content_features(node).walkable etc
+- Also check for lines that store the result of getContent to a 8-bit
+  variable and fix them (result of getContent() must be stored in
+  content_t, which is 16-bit)
+
+NOTE: Seeds in 1260:6c77e7dbfd29:
+5721858502589302589:
+	Spawns you on a small sand island with a surface dungeon
+2983455799928051958:
+	Enormous jungle + a surface dungeon at ~(250,0,0)
+
+Old, wild and random suggestions that probably won't be done:
+-------------------------------------------------------------
+
+SUGG: If player is on ground, mainly fetch ground-level blocks
+
+SUGG: Expose Connection's seqnums and ACKs to server and client.
+      - This enables saving many packets and making a faster connection
+	  - This also enables server to check if client has received the
+	    most recent block sent, for example.
+SUGG: Add a sane bandwidth throttling system to Connection
+
+SUGG: More fine-grained control of client's dumping of blocks from
+      memory
+	  - ...What does this mean in the first place?
+
+SUGG: A map editing mode (similar to dedicated server mode)
+
+SUGG: Transfer more blocks in a single packet
+SUGG: A blockdata combiner class, to which blocks are added and at
+      destruction it sends all the stuff in as few packets as possible.
+SUGG: Make a PACKET_COMBINED which contains many subpackets. Utilize
+      it by sending more stuff in a single packet.
+	  - Add a packet queue to RemoteClient, from which packets will be
+	    combined with object data packets
+		- This is not exactly trivial: the object data packets are
+		  sometimes very big by themselves
+	  - This might not give much network performance gain though.
+
+SUGG: Precalculate lighting translation table at runtime (at startup)
+      - This is not doable because it is currently hand-made and not
+	    based on some mathematical function.
+		- Note: This has been changing lately
+
+SUGG: A version number to blocks, which increments when the block is
+      modified (node add/remove, water update, lighting update)
+	  - This can then be used to make sure the most recent version of
+	    a block has been sent to client, for example
+
+SUGG: Make the amount of blocks sending to client and the total
+	  amount of blocks dynamically limited. Transferring blocks is the
+	  main network eater of this system, so it is the one that has
+	  to be throttled so that RTTs stay low.
+
+SUGG: Meshes of blocks could be split into 6 meshes facing into
+      different directions and then only those drawn that need to be
+
+SUGG: Background music based on cellular automata?
+      http://www.earslap.com/projectslab/otomata
+
+SUGG: Simple light color information to air
+
+SUGG: Server-side objects could be moved based on nodes to enable very
+      lightweight operation and simple AI
+	- Not practical; client would still need to show smooth movement.
+
+SUGG: Make a system for pregenerating quick information for mapblocks, so
+	  that the client can show them as cubes before they are actually sent
+	  or even generated.
+
+SUGG: Erosion simulation at map generation time
+    - This might be plausible if larger areas of map were pregenerated
+	  without lighting (which is slow)
+	- Simulate water flows, which would carve out dirt fast and
+	  then turn stone into gravel and sand and relocate it.
+	- How about relocating minerals, too? Coal and gold in
+	  downstream sand and gravel would be kind of cool
+	  - This would need a better way of handling minerals, mainly
+		to have mineral content as a separate field. the first
+		parameter field is free for this.
+	- Simulate rock falling from cliffs when water has removed
+	  enough solid rock from the bottom
+
+SUGG: For non-mapgen FarMesh: Add a per-sector database to store surface
+      stuff as simple flags/values
+      - Light?
+	  - A building?
+	  And at some point make the server send this data to the client too,
+	  instead of referring to the noise functions
+	  - Ground height
+	  - Surface ground type
+	  - Trees?
+
+Gaming ideas:
+-------------
+
+- Aim for something like controlling a single dwarf in Dwarf Fortress
+- The player could go faster by a crafting a boat, or riding an animal
+- Random NPC traders. what else?
+
+Game content:
+-------------
+
+- When furnace is destroyed, move items to player's inventory
+- Add lots of stuff
+- Glass blocks
+- Growing grass, decaying leaves
+	- This can be done in the active blocks I guess.
+	- Lots of stuff can be done in the active blocks.
+	- Uh, is there an active block list somewhere? I think not. Add it.
+- Breaking weak structures
+	- This can probably be accomplished in the same way as grass
+- Player health points
+	- When player dies, throw items on map (needs better item-on-map
+	  implementation)
+- Cobble to get mossy if near water
+- More slots in furnace source list, so that multiple ingredients
+  are possible.
+- Keys to chests?
+
+- The Treasure Guard; a big monster with a hammer
+	- The hammer does great damage, shakes the ground and removes a block
+	- You can drop on top of it, and have some time to attack there
+	  before he shakes you off
+
+- Maybe the difficulty could come from monsters getting tougher in
+  far-away places, and the player starting to need something from
+  there when time goes by.
+  - The player would have some of that stuff at the beginning, and
+    would need new supplies of it when it runs out
+
+- A bomb
+- A spread-items-on-map routine for the bomb, and for dying players
+
+- Fighting:
+  - Proper sword swing simulation
+  - Player should get damage from colliding to a wall at high speed
+
+Documentation:
+--------------
+
+Build system / running:
+-----------------------
+
+Networking and serialization:
+-----------------------------
+
+SUGG: Fix address to be ipv6 compatible
+
+User Interface:
+---------------
+
+Graphics:
+---------
+
+SUGG: Combine MapBlock's face caches to so big pieces that VBO
+      can be used
+      - That is >500 vertices
+	  - This is not easy; all the MapBlocks close to the player would
+	    still need to be drawn separately and combining the blocks
+		would have to happen in a background thread
+
+SUGG: Make fetching sector's blocks more efficient when rendering
+      sectors that have very large amounts of blocks (on client)
+	  - Is this necessary at all?
+
+SUGG: Draw cubes in inventory directly with 3D drawing commands, so that
+      animating them is easier.
+
+SUGG: Option for enabling proper alpha channel for textures
+
+TODO: Flowing water animation
+
+TODO: A setting for enabling bilinear filtering for textures
+
+TODO: Better control of draw_control.wanted_max_blocks
+
+TODO: Further investigate the use of GPU lighting in addition to the
+      current one
+
+TODO: Artificial (night) light could be more yellow colored than sunlight.
+      - This is technically doable.
+	  - Also the actual colors of the textures could be made less colorful
+	    in the dark but it's a bit more difficult.
+
+SUGG: Somehow make the night less colorful
+
+TODO: Occlusion culling
+      - At the same time, move some of the renderMap() block choosing code
+        to the same place as where the new culling happens.
+      - Shoot some rays per frame and when ready, make a new list of
+	    blocks for usage of renderMap and give it a new pointer to it.
+
+Configuration:
+--------------
+
+Client:
+-------
+
+TODO: Untie client network operations from framerate
+      - Needs some input queues or something
+	  - This won't give much performance boost because calculating block
+	    meshes takes so long
+
+SUGG: Make morning and evening transition more smooth and maybe shorter
+
+TODO: Don't update all meshes always on single node changes, but
+      check which ones should be updated
+	  - implement Map::updateNodeMeshes() and the usage of it
+	  - It will give almost always a 4x boost in mesh update performance.
+
+- A weapon engine
+
+- Tool/weapon visualization
+
+FIXME: When disconnected to the menu, memory is not freed properly
+
+TODO: Investigate how much the mesh generator thread gets used when
+      transferring map data
+
+Server:
+-------
+
+SUGG: Make an option to the server to disable building and digging near
+      the starting position
+
+FIXME: Server sometimes goes into some infinite PeerNotFoundException loop
+
+* Fix the problem with the server constantly saving one or a few
+  blocks? List the first saved block, maybe it explains.
+  - It is probably caused by oscillating water
+  - TODO: Investigate if this still happens (this is a very old one)
+* Make a small history check to transformLiquids to detect and log
+  continuous oscillations, in such detail that they can be fixed.
+
+FIXME: The new optimized map sending doesn't sometimes send enough blocks
+       from big caves and such
+FIXME: Block send distance configuration does not take effect for some reason
+
+Environment:
+------------
+
+TODO: Add proper hooks to when adding and removing active blocks
+
+TODO: Finish the ActiveBlockModifier stuff and use it for something
+
+Objects:
+--------
+
+TODO: Get rid of MapBlockObjects and use only ActiveObjects
+	- Skipping the MapBlockObject data is nasty - there is no "total
+	  length" stored; have to make a SkipMBOs function which contains
+	  enough of the current code to skip them properly.
+
+SUGG: MovingObject::move and Player::move are basically the same.
+      combine them.
+	- NOTE: This is a bit tricky because player has the sneaking ability
+	- NOTE: Player::move is more up-to-date.
+	- NOTE: There is a simple move implementation now in collision.{h,cpp}
+	- NOTE: MovingObject will be deleted (MapBlockObject)
+
+TODO: Add a long step function to objects that is called with the time
+      difference when block activates
+
+Map:
+----
+
+TODO: Mineral and ground material properties
+      - This way mineral ground toughness can be calculated with just
+	    some formula, as well as tool strengths. Sounds too.
+	  - There are TODOs in appropriate files: material.h, content_mapnode.h
+
+TODO: Flowing water to actually contain flow direction information
+      - There is a space for this - it just has to be implemented.
+
+TODO: Consider smoothening cave floors after generating them
+
+TODO: Fix make_tree, make_* to use seed-position-consistent pseudorandom
+	  - delta also
+
+Misc. stuff:
+------------
+TODO: Make sure server handles removing grass when a block is placed (etc)
+      - The client should not do it by itself
+	  - NOTE: I think nobody does it currently...
+TODO: Block cube placement around player's head
+TODO: Protocol version field
+TODO: Think about using same bits for material for fences and doors, for
+	  example
+TODO: Move mineral to param2, increment map serialization version, add
+      conversion
+
+SUGG: Restart irrlicht completely when coming back to main menu from game.
+	- This gets rid of everything that is stored in irrlicht's caches.
+	- This might be needed for texture pack selection in menu
+
+TODO: Merge bahamada's audio stuff (clean patch available)
+
+TODO: Move content_features to mapnode_content_features.{h,cpp} or so
+
+Making it more portable:
+------------------------
+ 
+Stuff to do before release:
+---------------------------
+
+Fixes to the current release:
+-----------------------------
+
+Stuff to do after release:
+---------------------------
+
+Doing currently:
+----------------
+
+======================================================================
+
+*/
+
+#ifdef NDEBUG
+	#ifdef _WIN32
+		#pragma message ("Disabling unit tests")
+	#else
+		#warning "Disabling unit tests"
+	#endif
+	// Disable unit tests
+	#define ENABLE_TESTS 0
+#else
+	// Enable unit tests
+	#define ENABLE_TESTS 1
+#endif
+
+#ifdef _MSC_VER
+	#pragma comment(lib, "Irrlicht.lib")
+	//#pragma comment(lib, "jthread.lib")
+	#pragma comment(lib, "zlibwapi.lib")
+	#pragma comment(lib, "Shell32.lib")
+	// This would get rid of the console window
+	//#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
+#endif
+
+#include <iostream>
+#include <fstream>
+#include <locale.h>
+#include "main.h"
+#include "common_irrlicht.h"
+#include "debug.h"
+#include "test.h"
+#include "server.h"
+#include "constants.h"
+#include "porting.h"
+#include "gettime.h"
+#include "guiMessageMenu.h"
+#include "filesys.h"
+#include "config.h"
+#include "guiMainMenu.h"
+#include "mineral.h"
+#include "materials.h"
+#include "game.h"
+#include "keycode.h"
+#include "tile.h"
+
+#include "gettext.h"
+
+// This makes textures
+ITextureSource *g_texturesource = NULL;
+
+/*
+	Settings.
+	These are loaded from the config file.
+*/
+
+Settings g_settings;
+// This is located in defaultsettings.cpp
+extern void set_default_settings();
+
+// Global profiler
+Profiler g_profiler;
+
+/*
+	Random stuff
+*/
+
+/*
+	GUI Stuff
+*/
+
+gui::IGUIEnvironment* guienv = NULL;
+gui::IGUIStaticText *guiroot = NULL;
+
+MainMenuManager g_menumgr;
+
+bool noMenuActive()
+{
+	return (g_menumgr.menuCount() == 0);
+}
+
+// Passed to menus to allow disconnecting and exiting
+
+MainGameCallback *g_gamecallback = NULL;
+
+/*
+	Debug streams
+*/
+
+// Connection
+std::ostream *dout_con_ptr = &dummyout;
+std::ostream *derr_con_ptr = &dstream_no_stderr;
+//std::ostream *dout_con_ptr = &dstream_no_stderr;
+//std::ostream *derr_con_ptr = &dstream_no_stderr;
+//std::ostream *dout_con_ptr = &dstream;
+//std::ostream *derr_con_ptr = &dstream;
+
+// Server
+std::ostream *dout_server_ptr = &dstream;
+std::ostream *derr_server_ptr = &dstream;
+
+// Client
+std::ostream *dout_client_ptr = &dstream;
+std::ostream *derr_client_ptr = &dstream;
+
+/*
+	gettime.h implementation
+*/
+
+// A small helper class
+class TimeGetter
+{
+public:
+	virtual u32 getTime() = 0;
+};
+
+// A precise irrlicht one
+class IrrlichtTimeGetter: public TimeGetter
+{
+public:
+	IrrlichtTimeGetter(IrrlichtDevice *device):
+		m_device(device)
+	{}
+	u32 getTime()
+	{
+		if(m_device == NULL)
+			return 0;
+		return m_device->getTimer()->getRealTime();
+	}
+private:
+	IrrlichtDevice *m_device;
+};
+// Not so precise one which works without irrlicht
+class SimpleTimeGetter: public TimeGetter
+{
+public:
+	u32 getTime()
+	{
+		return porting::getTimeMs();
+	}
+};
+
+// A pointer to a global instance of the time getter
+// TODO: why?
+TimeGetter *g_timegetter = NULL;
+
+u32 getTimeMs()
+{
+	if(g_timegetter == NULL)
+		return 0;
+	return g_timegetter->getTime();
+}
+
+/*
+	Event handler for Irrlicht
+
+	NOTE: Everything possible should be moved out from here,
+	      probably to InputHandler and the_game
+*/
+
+class MyEventReceiver : public IEventReceiver
+{
+public:
+	// This is the one method that we have to implement
+	virtual bool OnEvent(const SEvent& event)
+	{
+		/*
+			React to nothing here if a menu is active
+		*/
+		if(noMenuActive() == false)
+		{
+			return false;
+		}
+
+		// Remember whether each key is down or up
+		if(event.EventType == irr::EET_KEY_INPUT_EVENT)
+		{
+			keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
+
+			if(event.KeyInput.PressedDown)
+				keyWasDown[event.KeyInput.Key] = true;
+		}
+
+		if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
+		{
+			if(noMenuActive() == false)
+			{
+				left_active = false;
+				middle_active = false;
+				right_active = false;
+			}
+			else
+			{
+				//dstream<<"MyEventReceiver: mouse input"<<std::endl;
+				left_active = event.MouseInput.isLeftPressed();
+				middle_active = event.MouseInput.isMiddlePressed();
+				right_active = event.MouseInput.isRightPressed();
+
+				if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
+				{
+					leftclicked = true;
+				}
+				if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
+				{
+					rightclicked = true;
+				}
+				if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
+				{
+					leftreleased = true;
+				}
+				if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
+				{
+					rightreleased = true;
+				}
+				if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
+				{
+					mouse_wheel += event.MouseInput.Wheel;
+				}
+			}
+		}
+
+		return false;
+	}
+
+	bool IsKeyDown(EKEY_CODE keyCode) const
+	{
+		return keyIsDown[keyCode];
+	}
+	
+	// Checks whether a key was down and resets the state
+	bool WasKeyDown(EKEY_CODE keyCode)
+	{
+		bool b = keyWasDown[keyCode];
+		keyWasDown[keyCode] = false;
+		return b;
+	}
+
+	s32 getMouseWheel()
+	{
+		s32 a = mouse_wheel;
+		mouse_wheel = 0;
+		return a;
+	}
+
+	void clearInput()
+	{
+		for(u32 i=0; i<KEY_KEY_CODES_COUNT; i++)
+		{
+			keyIsDown[i] = false;
+			keyWasDown[i] = false;
+		}
+		
+		leftclicked = false;
+		rightclicked = false;
+		leftreleased = false;
+		rightreleased = false;
+
+		left_active = false;
+		middle_active = false;
+		right_active = false;
+
+		mouse_wheel = 0;
+	}
+
+	MyEventReceiver()
+	{
+		clearInput();
+	}
+
+	bool leftclicked;
+	bool rightclicked;
+	bool leftreleased;
+	bool rightreleased;
+
+	bool left_active;
+	bool middle_active;
+	bool right_active;
+
+	s32 mouse_wheel;
+
+private:
+	IrrlichtDevice *m_device;
+	
+	// The current state of keys
+	bool keyIsDown[KEY_KEY_CODES_COUNT];
+	// Whether a key has been pressed or not
+	bool keyWasDown[KEY_KEY_CODES_COUNT];
+};
+
+/*
+	Separated input handler
+*/
+
+class RealInputHandler : public InputHandler
+{
+public:
+	RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
+		m_device(device),
+		m_receiver(receiver)
+	{
+	}
+	virtual bool isKeyDown(EKEY_CODE keyCode)
+	{
+		return m_receiver->IsKeyDown(keyCode);
+	}
+	virtual bool wasKeyDown(EKEY_CODE keyCode)
+	{
+		return m_receiver->WasKeyDown(keyCode);
+	}
+	virtual v2s32 getMousePos()
+	{
+		return m_device->getCursorControl()->getPosition();
+	}
+	virtual void setMousePos(s32 x, s32 y)
+	{
+		m_device->getCursorControl()->setPosition(x, y);
+	}
+
+	virtual bool getLeftState()
+	{
+		return m_receiver->left_active;
+	}
+	virtual bool getRightState()
+	{
+		return m_receiver->right_active;
+	}
+	
+	virtual bool getLeftClicked()
+	{
+		return m_receiver->leftclicked;
+	}
+	virtual bool getRightClicked()
+	{
+		return m_receiver->rightclicked;
+	}
+	virtual void resetLeftClicked()
+	{
+		m_receiver->leftclicked = false;
+	}
+	virtual void resetRightClicked()
+	{
+		m_receiver->rightclicked = false;
+	}
+
+	virtual bool getLeftReleased()
+	{
+		return m_receiver->leftreleased;
+	}
+	virtual bool getRightReleased()
+	{
+		return m_receiver->rightreleased;
+	}
+	virtual void resetLeftReleased()
+	{
+		m_receiver->leftreleased = false;
+	}
+	virtual void resetRightReleased()
+	{
+		m_receiver->rightreleased = false;
+	}
+
+	virtual s32 getMouseWheel()
+	{
+		return m_receiver->getMouseWheel();
+	}
+
+	void clear()
+	{
+		m_receiver->clearInput();
+	}
+private:
+	IrrlichtDevice *m_device;
+	MyEventReceiver *m_receiver;
+};
+
+class RandomInputHandler : public InputHandler
+{
+public:
+	RandomInputHandler()
+	{
+		leftdown = false;
+		rightdown = false;
+		leftclicked = false;
+		rightclicked = false;
+		leftreleased = false;
+		rightreleased = false;
+		for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
+			keydown[i] = false;
+	}
+	virtual bool isKeyDown(EKEY_CODE keyCode)
+	{
+		return keydown[keyCode];
+	}
+	virtual bool wasKeyDown(EKEY_CODE keyCode)
+	{
+		return false;
+	}
+	virtual v2s32 getMousePos()
+	{
+		return mousepos;
+	}
+	virtual void setMousePos(s32 x, s32 y)
+	{
+		mousepos = v2s32(x,y);
+	}
+
+	virtual bool getLeftState()
+	{
+		return leftdown;
+	}
+	virtual bool getRightState()
+	{
+		return rightdown;
+	}
+
+	virtual bool getLeftClicked()
+	{
+		return leftclicked;
+	}
+	virtual bool getRightClicked()
+	{
+		return rightclicked;
+	}
+	virtual void resetLeftClicked()
+	{
+		leftclicked = false;
+	}
+	virtual void resetRightClicked()
+	{
+		rightclicked = false;
+	}
+
+	virtual bool getLeftReleased()
+	{
+		return leftreleased;
+	}
+	virtual bool getRightReleased()
+	{
+		return rightreleased;
+	}
+	virtual void resetLeftReleased()
+	{
+		leftreleased = false;
+	}
+	virtual void resetRightReleased()
+	{
+		rightreleased = false;
+	}
+
+	virtual s32 getMouseWheel()
+	{
+		return 0;
+	}
+
+	virtual void step(float dtime)
+	{
+		{
+			static float counter1 = 0;
+			counter1 -= dtime;
+			if(counter1 < 0.0)
+			{
+				counter1 = 0.1*Rand(1, 40);
+				keydown[getKeySetting("keymap_jump")] =
+						!keydown[getKeySetting("keymap_jump")];
+			}
+		}
+		{
+			static float counter1 = 0;
+			counter1 -= dtime;
+			if(counter1 < 0.0)
+			{
+				counter1 = 0.1*Rand(1, 40);
+				keydown[getKeySetting("keymap_special1")] =
+						!keydown[getKeySetting("keymap_special1")];
+			}
+		}
+		{
+			static float counter1 = 0;
+			counter1 -= dtime;
+			if(counter1 < 0.0)
+			{
+				counter1 = 0.1*Rand(1, 40);
+				keydown[getKeySetting("keymap_forward")] =
+						!keydown[getKeySetting("keymap_forward")];
+			}
+		}
+		{
+			static float counter1 = 0;
+			counter1 -= dtime;
+			if(counter1 < 0.0)
+			{
+				counter1 = 0.1*Rand(1, 40);
+				keydown[getKeySetting("keymap_left")] =
+						!keydown[getKeySetting("keymap_left")];
+			}
+		}
+		{
+			static float counter1 = 0;
+			counter1 -= dtime;
+			if(counter1 < 0.0)
+			{
+				counter1 = 0.1*Rand(1, 20);
+				mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
+			}
+		}
+		{
+			static float counter1 = 0;
+			counter1 -= dtime;
+			if(counter1 < 0.0)
+			{
+				counter1 = 0.1*Rand(1, 30);
+				leftdown = !leftdown;
+				if(leftdown)
+					leftclicked = true;
+				if(!leftdown)
+					leftreleased = true;
+			}
+		}
+		{
+			static float counter1 = 0;
+			counter1 -= dtime;
+			if(counter1 < 0.0)
+			{
+				counter1 = 0.1*Rand(1, 15);
+				rightdown = !rightdown;
+				if(rightdown)
+					rightclicked = true;
+				if(!rightdown)
+					rightreleased = true;
+			}
+		}
+		mousepos += mousespeed;
+	}
+
+	s32 Rand(s32 min, s32 max)
+	{
+		return (myrand()%(max-min+1))+min;
+	}
+private:
+	bool keydown[KEY_KEY_CODES_COUNT];
+	v2s32 mousepos;
+	v2s32 mousespeed;
+	bool leftdown;
+	bool rightdown;
+	bool leftclicked;
+	bool rightclicked;
+	bool leftreleased;
+	bool rightreleased;
+};
+
+// These are defined global so that they're not optimized too much.
+// Can't change them to volatile.
+s16 temp16;
+f32 tempf;
+v3f tempv3f1;
+v3f tempv3f2;
+std::string tempstring;
+std::string tempstring2;
+
+void SpeedTests()
+{
+	{
+		dstream<<"The following test should take around 20ms."<<std::endl;
+		TimeTaker timer("Testing std::string speed");
+		const u32 jj = 10000;
+		for(u32 j=0; j<jj; j++)
+		{
+			tempstring = "";
+			tempstring2 = "";
+			const u32 ii = 10;
+			for(u32 i=0; i<ii; i++){
+				tempstring2 += "asd";
+			}
+			for(u32 i=0; i<ii+1; i++){
+				tempstring += "asd";
+				if(tempstring == tempstring2)
+					break;
+			}
+		}
+	}
+	
+	dstream<<"All of the following tests should take around 100ms each."
+			<<std::endl;
+
+	{
+		TimeTaker timer("Testing floating-point conversion speed");
+		tempf = 0.001;
+		for(u32 i=0; i<4000000; i++){
+			temp16 += tempf;
+			tempf += 0.001;
+		}
+	}
+	
+	{
+		TimeTaker timer("Testing floating-point vector speed");
+
+		tempv3f1 = v3f(1,2,3);
+		tempv3f2 = v3f(4,5,6);
+		for(u32 i=0; i<10000000; i++){
+			tempf += tempv3f1.dotProduct(tempv3f2);
+			tempv3f2 += v3f(7,8,9);
+		}
+	}
+
+	{
+		TimeTaker timer("Testing core::map speed");
+		
+		core::map<v2s16, f32> map1;
+		tempf = -324;
+		const s16 ii=300;
+		for(s16 y=0; y<ii; y++){
+			for(s16 x=0; x<ii; x++){
+				map1.insert(v2s16(x,y), tempf);
+				tempf += 1;
+			}
+		}
+		for(s16 y=ii-1; y>=0; y--){
+			for(s16 x=0; x<ii; x++){
+				tempf = map1[v2s16(x,y)];
+			}
+		}
+	}
+
+	{
+		dstream<<"Around 5000/ms should do well here."<<std::endl;
+		TimeTaker timer("Testing mutex speed");
+		
+		JMutex m;
+		m.Init();
+		u32 n = 0;
+		u32 i = 0;
+		do{
+			n += 10000;
+			for(; i<n; i++){
+				m.Lock();
+				m.Unlock();
+			}
+		}
+		// Do at least 10ms
+		while(timer.getTime() < 10);
+
+		u32 dtime = timer.stop();
+		u32 per_ms = n / dtime;
+		std::cout<<"Done. "<<dtime<<"ms, "
+				<<per_ms<<"/ms"<<std::endl;
+	}
+}
+
+void drawMenuBackground(video::IVideoDriver* driver)
+{
+	core::dimension2d<u32> screensize = driver->getScreenSize();
+		
+	video::ITexture *bgtexture =
+			driver->getTexture(getTexturePath("mud.png").c_str());
+	if(bgtexture)
+	{
+		s32 texturesize = 128;
+		s32 tiled_y = screensize.Height / texturesize + 1;
+		s32 tiled_x = screensize.Width / texturesize + 1;
+		
+		for(s32 y=0; y<tiled_y; y++)
+		for(s32 x=0; x<tiled_x; x++)
+		{
+			core::rect<s32> rect(0,0,texturesize,texturesize);
+			rect += v2s32(x*texturesize, y*texturesize);
+			driver->draw2DImage(bgtexture, rect,
+				core::rect<s32>(core::position2d<s32>(0,0),
+				core::dimension2di(bgtexture->getSize())),
+				NULL, NULL, true);
+		}
+	}
+	
+	video::ITexture *logotexture =
+			driver->getTexture(getTexturePath("menulogo.png").c_str());
+	if(logotexture)
+	{
+		v2s32 logosize(logotexture->getOriginalSize().Width,
+				logotexture->getOriginalSize().Height);
+		logosize *= 4;
+
+		video::SColor bgcolor(255,50,50,50);
+		core::rect<s32> bgrect(0, screensize.Height-logosize.Y-20,
+				screensize.Width, screensize.Height);
+		driver->draw2DRectangle(bgcolor, bgrect, NULL);
+
+		core::rect<s32> rect(0,0,logosize.X,logosize.Y);
+		rect += v2s32(screensize.Width/2,screensize.Height-10-logosize.Y);
+		rect -= v2s32(logosize.X/2, 0);
+		driver->draw2DImage(logotexture, rect,
+			core::rect<s32>(core::position2d<s32>(0,0),
+			core::dimension2di(logotexture->getSize())),
+			NULL, NULL, true);
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	/*
+		Initialization
+	*/
+
+	// Set locale. This is for forcing '.' as the decimal point.
+	std::locale::global(std::locale("C"));
+	// This enables printing all characters in bitmap font
+	setlocale(LC_CTYPE, "en_US");
+
+	/*
+		Parse command line
+	*/
+	
+	// List all allowed options
+	core::map<std::string, ValueSpec> allowed_options;
+	allowed_options.insert("help", ValueSpec(VALUETYPE_FLAG));
+	allowed_options.insert("server", ValueSpec(VALUETYPE_FLAG,
+			"Run server directly"));
+	allowed_options.insert("config", ValueSpec(VALUETYPE_STRING,
+			"Load configuration from specified file"));
+	allowed_options.insert("port", ValueSpec(VALUETYPE_STRING));
+	allowed_options.insert("address", ValueSpec(VALUETYPE_STRING));
+	allowed_options.insert("random-input", ValueSpec(VALUETYPE_FLAG));
+	allowed_options.insert("disable-unittests", ValueSpec(VALUETYPE_FLAG));
+	allowed_options.insert("enable-unittests", ValueSpec(VALUETYPE_FLAG));
+	allowed_options.insert("map-dir", ValueSpec(VALUETYPE_STRING));
+#ifdef _WIN32
+	allowed_options.insert("dstream-on-stderr", ValueSpec(VALUETYPE_FLAG));
+#endif
+	allowed_options.insert("speedtests", ValueSpec(VALUETYPE_FLAG));
+
+	Settings cmd_args;
+	
+	bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options);
+
+	if(ret == false || cmd_args.getFlag("help"))
+	{
+		dstream<<"Allowed options:"<<std::endl;
+		for(core::map<std::string, ValueSpec>::Iterator
+				i = allowed_options.getIterator();
+				i.atEnd() == false; i++)
+		{
+			dstream<<"  --"<<i.getNode()->getKey();
+			if(i.getNode()->getValue().type == VALUETYPE_FLAG)
+			{
+			}
+			else
+			{
+				dstream<<" <value>";
+			}
+			dstream<<std::endl;
+
+			if(i.getNode()->getValue().help != NULL)
+			{
+				dstream<<"      "<<i.getNode()->getValue().help
+						<<std::endl;
+			}
+		}
+
+		return cmd_args.getFlag("help") ? 0 : 1;
+	}
+	
+	/*
+		Low-level initialization
+	*/
+
+	bool disable_stderr = false;
+#ifdef _WIN32
+	if(cmd_args.getFlag("dstream-on-stderr") == false)
+		disable_stderr = true;
+#endif
+
+	porting::signal_handler_init();
+	bool &kill = *porting::signal_handler_killstatus();
+	
+	// Initialize porting::path_data and porting::path_userdata
+	porting::initializePaths();
+
+	// Create user data directory
+	fs::CreateDir(porting::path_userdata);
+
+	init_gettext((porting::path_data+"/../locale").c_str());
+	
+	// Initialize debug streams
+#ifdef RUN_IN_PLACE
+	std::string debugfile = DEBUGFILE;
+#else
+	std::string debugfile = porting::path_userdata+"/"+DEBUGFILE;
+#endif
+	debugstreams_init(disable_stderr, debugfile.c_str());
+	// Initialize debug stacks
+	debug_stacks_init();
+
+	DSTACK(__FUNCTION_NAME);
+
+	// Init material properties table
+	//initializeMaterialProperties();
+
+	// Debug handler
+	BEGIN_DEBUG_EXCEPTION_HANDLER
+
+	// Print startup message
+	dstream<<DTIME<<PROJECT_NAME
+			" with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
+			<<", "<<BUILD_INFO
+			<<std::endl;
+	
+	/*
+		Basic initialization
+	*/
+
+	// Initialize default settings
+	set_default_settings();
+	
+	// Initialize sockets
+	sockets_init();
+	atexit(sockets_cleanup);
+	
+	/*
+		Read config file
+	*/
+	
+	// Path of configuration file in use
+	std::string configpath = "";
+	
+	if(cmd_args.exists("config"))
+	{
+		bool r = g_settings.readConfigFile(cmd_args.get("config").c_str());
+		if(r == false)
+		{
+			dstream<<"Could not read configuration from \""
+					<<cmd_args.get("config")<<"\""<<std::endl;
+			return 1;
+		}
+		configpath = cmd_args.get("config");
+	}
+	else
+	{
+		core::array<std::string> filenames;
+		filenames.push_back(porting::path_userdata + "/minetest.conf");
+#ifdef RUN_IN_PLACE
+		filenames.push_back(porting::path_userdata + "/../minetest.conf");
+#endif
+
+		for(u32 i=0; i<filenames.size(); i++)
+		{
+			bool r = g_settings.readConfigFile(filenames[i].c_str());
+			if(r)
+			{
+				configpath = filenames[i];
+				break;
+			}
+		}
+		
+		// If no path found, use the first one (menu creates the file)
+		if(configpath == "")
+			configpath = filenames[0];
+	}
+
+	// Initialize random seed
+	srand(time(0));
+	mysrand(time(0));
+
+	/*
+		Pre-initialize some stuff with a dummy irrlicht wrapper.
+
+		These are needed for unit tests at least.
+	*/
+	
+	// Initial call with g_texturesource not set.
+	init_mapnode();
+
+	/*
+		Run unit tests
+	*/
+
+	if((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false)
+			|| cmd_args.getFlag("enable-unittests") == true)
+	{
+		run_tests();
+	}
+	
+	/*for(s16 y=-100; y<100; y++)
+	for(s16 x=-100; x<100; x++)
+	{
+		std::cout<<noise2d_gradient((double)x/10,(double)y/10, 32415)<<std::endl;
+	}
+	return 0;*/
+	
+	/*
+		Game parameters
+	*/
+
+	// Port
+	u16 port = 30000;
+	if(cmd_args.exists("port"))
+		port = cmd_args.getU16("port");
+	else if(g_settings.exists("port"))
+		port = g_settings.getU16("port");
+	if(port == 0)
+		port = 30000;
+	
+	// Map directory
+	std::string map_dir = porting::path_userdata+"/world";
+	if(cmd_args.exists("map-dir"))
+		map_dir = cmd_args.get("map-dir");
+	else if(g_settings.exists("map-dir"))
+		map_dir = g_settings.get("map-dir");
+	
+	// Run dedicated server if asked to
+	if(cmd_args.getFlag("server"))
+	{
+		DSTACK("Dedicated server branch");
+
+		// Create time getter
+		g_timegetter = new SimpleTimeGetter();
+		
+		// Create server
+		Server server(map_dir.c_str());
+		server.start(port);
+		
+		// Run server
+		dedicated_server_loop(server, kill);
+
+		return 0;
+	}
+
+
+	/*
+		More parameters
+	*/
+	
+	// Address to connect to
+	std::string address = "";
+	
+	if(cmd_args.exists("address"))
+	{
+		address = cmd_args.get("address");
+	}
+	else
+	{
+		address = g_settings.get("address");
+	}
+	
+	std::string playername = g_settings.get("name");
+
+	/*
+		Device initialization
+	*/
+
+	// Resolution selection
+	
+	bool fullscreen = false;
+	u16 screenW = g_settings.getU16("screenW");
+	u16 screenH = g_settings.getU16("screenH");
+
+	// Determine driver
+
+	video::E_DRIVER_TYPE driverType;
+	
+	std::string driverstring = g_settings.get("video_driver");
+
+	if(driverstring == "null")
+		driverType = video::EDT_NULL;
+	else if(driverstring == "software")
+		driverType = video::EDT_SOFTWARE;
+	else if(driverstring == "burningsvideo")
+		driverType = video::EDT_BURNINGSVIDEO;
+	else if(driverstring == "direct3d8")
+		driverType = video::EDT_DIRECT3D8;
+	else if(driverstring == "direct3d9")
+		driverType = video::EDT_DIRECT3D9;
+	else if(driverstring == "opengl")
+		driverType = video::EDT_OPENGL;
+	else
+	{
+		dstream<<"WARNING: Invalid video_driver specified; defaulting "
+				"to opengl"<<std::endl;
+		driverType = video::EDT_OPENGL;
+	}
+
+	/*
+		Create device and exit if creation failed
+	*/
+
+	MyEventReceiver receiver;
+
+	IrrlichtDevice *device;
+	device = createDevice(driverType,
+			core::dimension2d<u32>(screenW, screenH),
+			16, fullscreen, false, false, &receiver);
+
+	if (device == 0)
+		return 1; // could not create selected driver.
+	
+	// Set device in game parameters
+	device = device;
 
 	// Set the window caption
 	device->setWindowCaption(L"Minetest [Main Menu]");
-	
-	// Create time getter
-	g_timegetter = new IrrlichtTimeGetter(device);
-	
-	// Create game callback for menus
-	g_gamecallback = new MainGameCallback(device);
-	
-	// Create texture source
-	g_texturesource = new TextureSource(device);
-
-	/*
-		Speed tests (done after irrlicht is loaded to get timer)
-	*/
-	if(cmd_args.getFlag("speedtests"))
-	{
-		dstream<<"Running speed tests"<<std::endl;
-		SpeedTests();
-		return 0;
-	}
-	
-	device->setResizable(true);
-
-	bool random_input = g_settings.getBool("random_input")
-			|| cmd_args.getFlag("random-input");
-	InputHandler *input = NULL;
-	if(random_input)
-		input = new RandomInputHandler();
-	else
-		input = new RealInputHandler(device, &receiver);
-	
-	/*
-		Continue initialization
-	*/
-
-	//video::IVideoDriver* driver = device->getVideoDriver();
-
-	/*
-		This changes the minimum allowed number of vertices in a VBO.
-		Default is 500.
-	*/
-	//driver->setMinHardwareBufferVertexCount(50);
-
-	scene::ISceneManager* smgr = device->getSceneManager();
-
-	guienv = device->getGUIEnvironment();
-	gui::IGUISkin* skin = guienv->getSkin();
-	gui::IGUIFont* font = guienv->getFont(getTexturePath("fontlucida.png").c_str());
-	if(font)
-		skin->setFont(font);
-	else
-		dstream<<"WARNING: Font file was not found."
-				" Using default font."<<std::endl;
-	// If font was not found, this will get us one
-	font = skin->getFont();
-	assert(font);
-	
-	u32 text_height = font->getDimension(L"Hello, world!").Height;
-	dstream<<"text_height="<<text_height<<std::endl;
-
-	//skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
-	skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
-	//skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
-	//skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
-	skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
-	skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
-	
-	/*
-		Preload some textures and stuff
-	*/
-
-	init_mapnode(); // Second call with g_texturesource set
-	init_mineral();
-
-	/*
-		GUI stuff
-	*/
-
-	/*
-		If an error occurs, this is set to something and the
-		menu-game loop is restarted. It is then displayed before
-		the menu.
-	*/
-	std::wstring error_message = L"";
-
-	// The password entered during the menu screen,
-	std::string password;
-
-	/*
-		Menu-game loop
-	*/
-	while(device->run() && kill == false)
-	{
-
-		// This is used for catching disconnects
-		try
-		{
-
-			/*
-				Clear everything from the GUIEnvironment
-			*/
-			guienv->clear();
-			
-			/*
-				We need some kind of a root node to be able to add
-				custom gui elements directly on the screen.
-				Otherwise they won't be automatically drawn.
-			*/
-			guiroot = guienv->addStaticText(L"",
-					core::rect<s32>(0, 0, 10000, 10000));
-			
-			/*
-				Out-of-game menu loop.
-
-				Loop quits when menu returns proper parameters.
-			*/
-			while(kill == false)
-			{
-				// Cursor can be non-visible when coming from the game
-				device->getCursorControl()->setVisible(true);
-				// Some stuff are left to scene manager when coming from the game
-				// (map at least?)
-				smgr->clear();
-				// Reset or hide the debug gui texts
-				/*guitext->setText(L"Minetest-c55");
-				guitext2->setVisible(false);
-				guitext_info->setVisible(false);
-				guitext_chat->setVisible(false);*/
-				
-				// Initialize menu data
-				MainMenuData menudata;
-				menudata.address = narrow_to_wide(address);
-				menudata.name = narrow_to_wide(playername);
-				menudata.port = narrow_to_wide(itos(port));
-				menudata.fancy_trees = g_settings.getBool("new_style_leaves");
-				menudata.smooth_lighting = g_settings.getBool("smooth_lighting");
-				menudata.creative_mode = g_settings.getBool("creative_mode");
-				menudata.enable_damage = g_settings.getBool("enable_damage");
-
-				GUIMainMenu *menu =
-						new GUIMainMenu(guienv, guiroot, -1, 
-							&g_menumgr, &menudata, g_gamecallback);
-				menu->allowFocusRemoval(true);
-
-				if(error_message != L"")
-				{
-					dstream<<"WARNING: error_message = "
-							<<wide_to_narrow(error_message)<<std::endl;
-
-					GUIMessageMenu *menu2 =
-							new GUIMessageMenu(guienv, guiroot, -1, 
-								&g_menumgr, error_message.c_str());
-					menu2->drop();
-					error_message = L"";
-				}
-
-				video::IVideoDriver* driver = device->getVideoDriver();
-				
-				dstream<<"Created main menu"<<std::endl;
-
-				while(device->run() && kill == false)
-				{
-					if(menu->getStatus() == true)
-						break;
-
-					//driver->beginScene(true, true, video::SColor(255,0,0,0));
-					driver->beginScene(true, true, video::SColor(255,128,128,128));
-
-					drawMenuBackground(driver);
-
-					guienv->drawAll();
-					
-					driver->endScene();
-					
-					// On some computers framerate doesn't seem to be
-					// automatically limited
-					sleep_ms(25);
-				}
-				
-				// Break out of menu-game loop to shut down cleanly
-				if(device->run() == false || kill == true)
-					break;
-				
-				dstream<<"Dropping main menu"<<std::endl;
-
-				menu->drop();
-				
-				// Delete map if requested
-				if(menudata.delete_map)
-				{
-					bool r = fs::RecursiveDeleteContent(map_dir);
-					if(r == false)
-						error_message = L"Delete failed";
-					continue;
-				}
-
-				playername = wide_to_narrow(menudata.name);
-
-				password = translatePassword(playername, menudata.password);
-
-				address = wide_to_narrow(menudata.address);
-				int newport = stoi(wide_to_narrow(menudata.port));
-				if(newport != 0)
-					port = newport;
-				g_settings.set("new_style_leaves", itos(menudata.fancy_trees));
-				g_settings.set("smooth_lighting", itos(menudata.smooth_lighting));
-				g_settings.set("creative_mode", itos(menudata.creative_mode));
-				g_settings.set("enable_damage", itos(menudata.enable_damage));
-				
-				// NOTE: These are now checked server side; no need to do it
-				//       here, so let's not do it here.
-				/*// Check for valid parameters, restart menu if invalid.
-				if(playername == "")
-				{
-					error_message = L"Name required.";
-					continue;
-				}
-				// Check that name has only valid chars
-				if(string_allowed(playername, PLAYERNAME_ALLOWED_CHARS)==false)
-				{
-					error_message = L"Characters allowed: "
-							+narrow_to_wide(PLAYERNAME_ALLOWED_CHARS);
-					continue;
-				}*/
-
-				// Save settings
-				g_settings.set("name", playername);
-				g_settings.set("address", address);
-				g_settings.set("port", itos(port));
-				// Update configuration file
-				if(configpath != "")
-					g_settings.updateConfigFile(configpath.c_str());
-			
-				// Continue to game
-				break;
-			}
-			
-			// Break out of menu-game loop to shut down cleanly
-			if(device->run() == false)
-				break;
-			
-			// Initialize mapnode again to enable changed graphics settings
-			init_mapnode();
-
-			/*
-				Run game
-			*/
-			the_game(
-				kill,
-				random_input,
-				input,
-				device,
-				font,
-				map_dir,
-				playername,
-				password,
-				address,
-				port,
-				error_message
-			);
-
-		} //try
-		catch(con::PeerNotFoundException &e)
-		{
-			dstream<<DTIME<<"Connection error (timed out?)"<<std::endl;
-			error_message = L"Connection error (timed out?)";
-		}
-		catch(SocketException &e)
-		{
-			dstream<<DTIME<<"Socket error (port already in use?)"<<std::endl;
-			error_message = L"Socket error (port already in use?)";
-		}
-#ifdef NDEBUG
-		catch(std::exception &e)
-		{
-			std::string narrow_message = "Some exception, what()=\"";
-			narrow_message += e.what();
-			narrow_message += "\"";
-			dstream<<DTIME<<narrow_message<<std::endl;
-			error_message = narrow_to_wide(narrow_message);
-		}
-#endif
-
-	} // Menu-game loop
-	
-	delete input;
-
-	/*
-		In the end, delete the Irrlicht device.
-	*/
-	device->drop();
-	
-	END_DEBUG_EXCEPTION_HANDLER
-	
-	debugstreams_deinit();
-	
-	return 0;
-}
-
-//END
+	
+	// Create time getter
+	g_timegetter = new IrrlichtTimeGetter(device);
+	
+	// Create game callback for menus
+	g_gamecallback = new MainGameCallback(device);
+	
+	// Create texture source
+	g_texturesource = new TextureSource(device);
+
+	/*
+		Speed tests (done after irrlicht is loaded to get timer)
+	*/
+	if(cmd_args.getFlag("speedtests"))
+	{
+		dstream<<"Running speed tests"<<std::endl;
+		SpeedTests();
+		return 0;
+	}
+	
+	device->setResizable(true);
+
+	bool random_input = g_settings.getBool("random_input")
+			|| cmd_args.getFlag("random-input");
+	InputHandler *input = NULL;
+	if(random_input)
+		input = new RandomInputHandler();
+	else
+		input = new RealInputHandler(device, &receiver);
+	
+	/*
+		Continue initialization
+	*/
+
+	//video::IVideoDriver* driver = device->getVideoDriver();
+
+	/*
+		This changes the minimum allowed number of vertices in a VBO.
+		Default is 500.
+	*/
+	//driver->setMinHardwareBufferVertexCount(50);
+
+	scene::ISceneManager* smgr = device->getSceneManager();
+
+	guienv = device->getGUIEnvironment();
+	gui::IGUISkin* skin = guienv->getSkin();
+	gui::IGUIFont* font = guienv->getFont(getTexturePath("fontlucida.png").c_str());
+	if(font)
+		skin->setFont(font);
+	else
+		dstream<<"WARNING: Font file was not found."
+				" Using default font."<<std::endl;
+	// If font was not found, this will get us one
+	font = skin->getFont();
+	assert(font);
+	
+	u32 text_height = font->getDimension(L"Hello, world!").Height;
+	dstream<<"text_height="<<text_height<<std::endl;
+
+	//skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
+	skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
+	//skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
+	//skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
+	skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
+	skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
+	
+	/*
+		Preload some textures and stuff
+	*/
+
+	init_mapnode(); // Second call with g_texturesource set
+	init_mineral();
+
+	/*
+		GUI stuff
+	*/
+
+	/*
+		If an error occurs, this is set to something and the
+		menu-game loop is restarted. It is then displayed before
+		the menu.
+	*/
+	std::wstring error_message = L"";
+
+	// The password entered during the menu screen,
+	std::string password;
+
+	/*
+		Menu-game loop
+	*/
+	while(device->run() && kill == false)
+	{
+
+		// This is used for catching disconnects
+		try
+		{
+
+			/*
+				Clear everything from the GUIEnvironment
+			*/
+			guienv->clear();
+			
+			/*
+				We need some kind of a root node to be able to add
+				custom gui elements directly on the screen.
+				Otherwise they won't be automatically drawn.
+			*/
+			guiroot = guienv->addStaticText(L"",
+					core::rect<s32>(0, 0, 10000, 10000));
+			
+			/*
+				Out-of-game menu loop.
+
+				Loop quits when menu returns proper parameters.
+			*/
+			while(kill == false)
+			{
+				// Cursor can be non-visible when coming from the game
+				device->getCursorControl()->setVisible(true);
+				// Some stuff are left to scene manager when coming from the game
+				// (map at least?)
+				smgr->clear();
+				// Reset or hide the debug gui texts
+				/*guitext->setText(L"Minetest-c55");
+				guitext2->setVisible(false);
+				guitext_info->setVisible(false);
+				guitext_chat->setVisible(false);*/
+				
+				// Initialize menu data
+				MainMenuData menudata;
+				menudata.address = narrow_to_wide(address);
+				menudata.name = narrow_to_wide(playername);
+				menudata.port = narrow_to_wide(itos(port));
+				menudata.fancy_trees = g_settings.getBool("new_style_leaves");
+				menudata.smooth_lighting = g_settings.getBool("smooth_lighting");
+				menudata.creative_mode = g_settings.getBool("creative_mode");
+				menudata.enable_damage = g_settings.getBool("enable_damage");
+
+				GUIMainMenu *menu =
+						new GUIMainMenu(guienv, guiroot, -1, 
+							&g_menumgr, &menudata, g_gamecallback);
+				menu->allowFocusRemoval(true);
+
+				if(error_message != L"")
+				{
+					dstream<<"WARNING: error_message = "
+							<<wide_to_narrow(error_message)<<std::endl;
+
+					GUIMessageMenu *menu2 =
+							new GUIMessageMenu(guienv, guiroot, -1, 
+								&g_menumgr, error_message.c_str());
+					menu2->drop();
+					error_message = L"";
+				}
+
+				video::IVideoDriver* driver = device->getVideoDriver();
+				
+				dstream<<"Created main menu"<<std::endl;
+
+				while(device->run() && kill == false)
+				{
+					if(menu->getStatus() == true)
+						break;
+
+					//driver->beginScene(true, true, video::SColor(255,0,0,0));
+					driver->beginScene(true, true, video::SColor(255,128,128,128));
+
+					drawMenuBackground(driver);
+
+					guienv->drawAll();
+					
+					driver->endScene();
+					
+					// On some computers framerate doesn't seem to be
+					// automatically limited
+					sleep_ms(25);
+				}
+				
+				// Break out of menu-game loop to shut down cleanly
+				if(device->run() == false || kill == true)
+					break;
+				
+				dstream<<"Dropping main menu"<<std::endl;
+
+				menu->drop();
+				
+				// Delete map if requested
+				if(menudata.delete_map)
+				{
+					bool r = fs::RecursiveDeleteContent(map_dir);
+					if(r == false)
+						error_message = L"Delete failed";
+					continue;
+				}
+
+				playername = wide_to_narrow(menudata.name);
+
+				password = translatePassword(playername, menudata.password);
+
+				address = wide_to_narrow(menudata.address);
+				int newport = stoi(wide_to_narrow(menudata.port));
+				if(newport != 0)
+					port = newport;
+				g_settings.set("new_style_leaves", itos(menudata.fancy_trees));
+				g_settings.set("smooth_lighting", itos(menudata.smooth_lighting));
+				g_settings.set("creative_mode", itos(menudata.creative_mode));
+				g_settings.set("enable_damage", itos(menudata.enable_damage));
+				
+				// NOTE: These are now checked server side; no need to do it
+				//       here, so let's not do it here.
+				/*// Check for valid parameters, restart menu if invalid.
+				if(playername == "")
+				{
+					error_message = L"Name required.";
+					continue;
+				}
+				// Check that name has only valid chars
+				if(string_allowed(playername, PLAYERNAME_ALLOWED_CHARS)==false)
+				{
+					error_message = L"Characters allowed: "
+							+narrow_to_wide(PLAYERNAME_ALLOWED_CHARS);
+					continue;
+				}*/
+
+				// Save settings
+				g_settings.set("name", playername);
+				g_settings.set("address", address);
+				g_settings.set("port", itos(port));
+				// Update configuration file
+				if(configpath != "")
+					g_settings.updateConfigFile(configpath.c_str());
+			
+				// Continue to game
+				break;
+			}
+			
+			// Break out of menu-game loop to shut down cleanly
+			if(device->run() == false)
+				break;
+			
+			// Initialize mapnode again to enable changed graphics settings
+			init_mapnode();
+
+			/*
+				Run game
+			*/
+			the_game(
+				kill,
+				random_input,
+				input,
+				device,
+				font,
+				map_dir,
+				playername,
+				password,
+				address,
+				port,
+				error_message
+			);
+
+		} //try
+		catch(con::PeerNotFoundException &e)
+		{
+			dstream<<DTIME<<"Connection error (timed out?)"<<std::endl;
+			error_message = L"Connection error (timed out?)";
+		}
+		catch(SocketException &e)
+		{
+			dstream<<DTIME<<"Socket error (port already in use?)"<<std::endl;
+			error_message = L"Socket error (port already in use?)";
+		}
+#ifdef NDEBUG
+		catch(std::exception &e)
+		{
+			std::string narrow_message = "Some exception, what()=\"";
+			narrow_message += e.what();
+			narrow_message += "\"";
+			dstream<<DTIME<<narrow_message<<std::endl;
+			error_message = narrow_to_wide(narrow_message);
+		}
+#endif
+
+	} // Menu-game loop
+	
+	delete input;
+
+	/*
+		In the end, delete the Irrlicht device.
+	*/
+	device->drop();
+	
+	END_DEBUG_EXCEPTION_HANDLER
+	
+	debugstreams_deinit();
+	
+	return 0;
+}
+
+//END
+
diff --git a/src/map.cpp b/src/map.cpp
index e1769b8eff897718ace3b053b00cdf9c20a3bd08..092ce97fdcf030cca3b60c8af40a9b09a0b6a0fe 100644
--- a/src/map.cpp
+++ b/src/map.cpp
@@ -630,9 +630,9 @@ s16 Map::propagateSunlight(v3s16 start,
 		else
 		{
 			/*// Turn mud into grass
-			if(n.d == CONTENT_MUD)
+			if(n.getContent() == CONTENT_MUD)
 			{
-				n.d = CONTENT_GRASS;
+				n.setContent(CONTENT_GRASS);
 				block->setNode(relpos, n);
 				modified_blocks.insert(blockpos, block);
 			}*/
@@ -920,15 +920,15 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
 	/*
 		If the new node is solid and there is grass below, change it to mud
 	*/
-	if(content_features(n.d).walkable == true)
+	if(content_features(n).walkable == true)
 	{
 		try{
 			MapNode bottomnode = getNode(bottompos);
 
-			if(bottomnode.d == CONTENT_GRASS
-					|| bottomnode.d == CONTENT_GRASS_FOOTSTEPS)
+			if(bottomnode.getContent() == CONTENT_GRASS
+					|| bottomnode.getContent() == CONTENT_GRASS_FOOTSTEPS)
 			{
-				bottomnode.d = CONTENT_MUD;
+				bottomnode.setContent(CONTENT_MUD);
 				setNode(bottompos, bottomnode);
 			}
 		}
@@ -943,9 +943,9 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
 		If the new node is mud and it is under sunlight, change it
 		to grass
 	*/
-	if(n.d == CONTENT_MUD && node_under_sunlight)
+	if(n.getContent() == CONTENT_MUD && node_under_sunlight)
 	{
-		n.d = CONTENT_GRASS;
+		n.setContent(CONTENT_GRASS);
 	}
 #endif
 
@@ -986,7 +986,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
 		If node lets sunlight through and is under sunlight, it has
 		sunlight too.
 	*/
-	if(node_under_sunlight && content_features(n.d).sunlight_propagates)
+	if(node_under_sunlight && content_features(n).sunlight_propagates)
 	{
 		n.setLight(LIGHTBANK_DAY, LIGHT_SUN);
 	}
@@ -1001,7 +1001,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
 		Add intial metadata
 	*/
 
-	NodeMetadata *meta_proto = content_features(n.d).initial_metadata;
+	NodeMetadata *meta_proto = content_features(n).initial_metadata;
 	if(meta_proto)
 	{
 		NodeMetadata *meta = meta_proto->clone();
@@ -1015,7 +1015,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
 		TODO: This could be optimized by mass-unlighting instead
 			  of looping
 	*/
-	if(node_under_sunlight && !content_features(n.d).sunlight_propagates)
+	if(node_under_sunlight && !content_features(n).sunlight_propagates)
 	{
 		s16 y = p.Y - 1;
 		for(;; y--){
@@ -1086,7 +1086,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
 		v3s16 p2 = p + dirs[i];
 
 		MapNode n2 = getNode(p2);
-		if(content_liquid(n2.d) || n2.d == CONTENT_AIR)
+		if(content_liquid(n2.getContent()))
 		{
 			m_transforming_liquid.push_back(p2);
 		}
@@ -1111,7 +1111,7 @@ void Map::removeNodeAndUpdate(v3s16 p,
 	v3s16 toppos = p + v3s16(0,1,0);
 
 	// Node will be replaced with this
-	u8 replace_material = CONTENT_AIR;
+	content_t replace_material = CONTENT_AIR;
 
 	/*
 		If there is a node at top and it doesn't have sunlight,
@@ -1158,7 +1158,7 @@ void Map::removeNodeAndUpdate(v3s16 p,
 	*/
 
 	MapNode n;
-	n.d = replace_material;
+	n.setContent(replace_material);
 	setNode(p, n);
 
 	for(s32 i=0; i<2; i++)
@@ -1260,7 +1260,7 @@ void Map::removeNodeAndUpdate(v3s16 p,
 		v3s16 p2 = p + dirs[i];
 
 		MapNode n2 = getNode(p2);
-		if(content_liquid(n2.d) || n2.d == CONTENT_AIR)
+		if(content_liquid(n2.getContent()))
 		{
 			m_transforming_liquid.push_back(p2);
 		}
@@ -1576,20 +1576,20 @@ void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
 		 */
 		s8 liquid_level = -1;
 		u8 liquid_kind = CONTENT_IGNORE;
-		LiquidType liquid_type = content_features(n0.d).liquid_type;
+		LiquidType liquid_type = content_features(n0.getContent()).liquid_type;
 		switch (liquid_type) {
 			case LIQUID_SOURCE:
 				liquid_level = 8;
-				liquid_kind = content_features(n0.d).liquid_alternative_flowing;
+				liquid_kind = content_features(n0.getContent()).liquid_alternative_flowing;
 				break;
 			case LIQUID_FLOWING:
 				liquid_level = (n0.param2 & LIQUID_LEVEL_MASK);
-				liquid_kind = n0.d;
+				liquid_kind = n0.getContent();
 				break;
 			case LIQUID_NONE:
 				// if this is an air node, it *could* be transformed into a liquid. otherwise,
 				// continue with the next node.
-				if (n0.d != CONTENT_AIR)
+				if (n0.getContent() != CONTENT_AIR)
 					continue;
 				liquid_kind = CONTENT_AIR;
 				break;
@@ -1627,9 +1627,9 @@ void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
 			}
 			v3s16 npos = p0 + dirs[i];
 			NodeNeighbor nb = {getNodeNoEx(npos), nt, npos};
-			switch (content_features(nb.n.d).liquid_type) {
+			switch (content_features(nb.n.getContent()).liquid_type) {
 				case LIQUID_NONE:
-					if (nb.n.d == CONTENT_AIR) {
+					if (nb.n.getContent() == CONTENT_AIR) {
 						airs[num_airs++] = nb;
 						// if the current node happens to be a flowing node, it will start to flow down here.
 						if (nb.t == NEIGHBOR_LOWER)
@@ -1641,8 +1641,8 @@ void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
 				case LIQUID_SOURCE:
 					// if this node is not (yet) of a liquid type, choose the first liquid type we encounter 
 					if (liquid_kind == CONTENT_AIR)
-						liquid_kind = content_features(nb.n.d).liquid_alternative_flowing;
-					if (content_features(nb.n.d).liquid_alternative_flowing !=liquid_kind) {
+						liquid_kind = content_features(nb.n.getContent()).liquid_alternative_flowing;
+					if (content_features(nb.n.getContent()).liquid_alternative_flowing !=liquid_kind) {
 						neutrals[num_neutrals++] = nb;
 					} else {
 						sources[num_sources++] = nb;
@@ -1651,8 +1651,8 @@ void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
 				case LIQUID_FLOWING:
 					// if this node is not (yet) of a liquid type, choose the first liquid type we encounter
 					if (liquid_kind == CONTENT_AIR)
-						liquid_kind = content_features(nb.n.d).liquid_alternative_flowing;
-					if (content_features(nb.n.d).liquid_alternative_flowing != liquid_kind) {
+						liquid_kind = content_features(nb.n.getContent()).liquid_alternative_flowing;
+					if (content_features(nb.n.getContent()).liquid_alternative_flowing != liquid_kind) {
 						neutrals[num_neutrals++] = nb;
 					} else {
 						flows[num_flows++] = nb;
@@ -1722,7 +1722,7 @@ void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
 		/*
 			check if anything has changed. if not, just continue with the next node.
 		 */
-		if (new_node_content == n0.d && (content_features(n0.d).liquid_type != LIQUID_FLOWING ||
+		if (new_node_content == n0.getContent() && (content_features(n0.getContent()).liquid_type != LIQUID_FLOWING ||
 										 ((n0.param2 & LIQUID_LEVEL_MASK) == (u8)new_node_level &&
 										 ((n0.param2 & LIQUID_FLOW_DOWN_MASK) == LIQUID_FLOW_DOWN_MASK)
 										 == flowing_down)))
@@ -1733,8 +1733,8 @@ void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
 			update the current node
 		 */
 		bool flow_down_enabled = (flowing_down && ((n0.param2 & LIQUID_FLOW_DOWN_MASK) != LIQUID_FLOW_DOWN_MASK));
-		n0.d = new_node_content;
-		if (content_features(n0.d).liquid_type == LIQUID_FLOWING) {
+		n0.setContent(new_node_content);
+		if (content_features(n0.getContent()).liquid_type == LIQUID_FLOWING) {
 			// set level to last 3 bits, flowing down bit to 4th bit
 			n0.param2 = (flowing_down ? LIQUID_FLOW_DOWN_MASK : 0x00) | (new_node_level & LIQUID_LEVEL_MASK);
 		} else {
@@ -1749,7 +1749,7 @@ void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
 		/*
 			enqueue neighbors for update if neccessary
 		 */
-		switch (content_features(n0.d).liquid_type) {
+		switch (content_features(n0.getContent()).liquid_type) {
 			case LIQUID_SOURCE:
 				// make sure source flows into all neighboring nodes
 				for (u16 i = 0; i < num_flows; i++)
@@ -2003,8 +2003,10 @@ ServerMap::~ServerMap()
 
 void ServerMap::initBlockMake(mapgen::BlockMakeData *data, v3s16 blockpos)
 {
-	/*dstream<<"initBlockMake(): ("<<blockpos.X<<","<<blockpos.Y<<","
-			<<blockpos.Z<<")"<<std::endl;*/
+	bool enable_mapgen_debug_info = g_settings.getBool("enable_mapgen_debug_info");
+	if(enable_mapgen_debug_info)
+		dstream<<"initBlockMake(): ("<<blockpos.X<<","<<blockpos.Y<<","
+				<<blockpos.Z<<")"<<std::endl;
 	
 	// Do nothing if not inside limits (+-1 because of neighbors)
 	if(blockpos_over_limit(blockpos - v3s16(1,1,1)) ||
@@ -2034,25 +2036,28 @@ void ServerMap::initBlockMake(mapgen::BlockMakeData *data, v3s16 blockpos)
 
 			for(s16 y=-1; y<=1; y++)
 			{
-				//MapBlock *block = createBlock(blockpos);
+				v3s16 p(blockpos.X+x, blockpos.Y+y, blockpos.Z+z);
+				//MapBlock *block = createBlock(p);
 				// 1) get from memory, 2) load from disk
-				MapBlock *block = emergeBlock(blockpos, false);
+				MapBlock *block = emergeBlock(p, false);
 				// 3) create a blank one
 				if(block == NULL)
-					block = createBlock(blockpos);
+				{
+					block = createBlock(p);
+
+					/*
+						Block gets sunlight if this is true.
+
+						Refer to the map generator heuristics.
+					*/
+					bool ug = mapgen::block_is_underground(data->seed, p);
+					block->setIsUnderground(ug);
+				}
 
 				// Lighting will not be valid after make_chunk is called
 				block->setLightingExpired(true);
 				// Lighting will be calculated
 				//block->setLightingExpired(false);
-
-				/*
-					Block gets sunlight if this is true.
-
-					This should be set to true when the top side of a block
-					is completely exposed to the sky.
-				*/
-				block->setIsUnderground(false);
 			}
 		}
 	}
@@ -2128,10 +2133,14 @@ MapBlock* ServerMap::finishBlockMake(mapgen::BlockMakeData *data,
 	assert(block);
 
 	/*
-		Set is_underground flag for lighting with sunlight
+		Set is_underground flag for lighting with sunlight.
+
+		Refer to map generator heuristics.
+
+		NOTE: This is done in initChunkMake
 	*/
+	//block->setIsUnderground(mapgen::block_is_underground(data->seed, blockpos));
 
-	block->setIsUnderground(mapgen::block_is_underground(data->seed, blockpos));
 
 	/*
 		Add sunlight to central block.
@@ -2162,6 +2171,13 @@ MapBlock* ServerMap::finishBlockMake(mapgen::BlockMakeData *data,
 #if 1
 		// Center block
 		lighting_update_blocks.insert(block->getPos(), block);
+
+		/*{
+			s16 x = 0;
+			s16 z = 0;
+			v3s16 p = block->getPos()+v3s16(x,1,z);
+			lighting_update_blocks[p] = getBlockNoCreateNoEx(p);
+		}*/
 #endif
 #if 0
 		// All modified blocks
@@ -2178,8 +2194,28 @@ MapBlock* ServerMap::finishBlockMake(mapgen::BlockMakeData *data,
 			lighting_update_blocks.insert(i.getNode()->getKey(),
 					i.getNode()->getValue());
 		}
+		/*// Also force-add all the upmost blocks for proper sunlight
+		for(s16 x=-1; x<=1; x++)
+		for(s16 z=-1; z<=1; z++)
+		{
+			v3s16 p = block->getPos()+v3s16(x,1,z);
+			lighting_update_blocks[p] = getBlockNoCreateNoEx(p);
+		}*/
 #endif
 		updateLighting(lighting_update_blocks, changed_blocks);
+		
+		/*
+			Set lighting to non-expired state in all of them.
+			This is cheating, but it is not fast enough if all of them
+			would actually be updated.
+		*/
+		for(s16 x=-1; x<=1; x++)
+		for(s16 y=-1; y<=1; y++)
+		for(s16 z=-1; z<=1; z++)
+		{
+			v3s16 p = block->getPos()+v3s16(x,y,z);
+			getBlockNoCreateNoEx(p)->setLightingExpired(false);
+		}
 
 		if(enable_mapgen_debug_info == false)
 			t.stop(true); // Hide output
@@ -2221,7 +2257,26 @@ MapBlock* ServerMap::finishBlockMake(mapgen::BlockMakeData *data,
 
 	/*dstream<<"finishBlockMake() done for ("<<blockpos.X<<","<<blockpos.Y<<","
 			<<blockpos.Z<<")"<<std::endl;*/
-	
+#if 0
+	if(enable_mapgen_debug_info)
+	{
+		/*
+			Analyze resulting blocks
+		*/
+		for(s16 x=-1; x<=1; x++)
+		for(s16 y=-1; y<=1; y++)
+		for(s16 z=-1; z<=1; z++)
+		{
+			v3s16 p = block->getPos()+v3s16(x,y,z);
+			MapBlock *block = getBlockNoCreateNoEx(p);
+			char spos[20];
+			snprintf(spos, 20, "(%2d,%2d,%2d)", x, y, z);
+			dstream<<"Generated "<<spos<<": "
+					<<analyze_block(block)<<std::endl;
+		}
+	}
+#endif
+
 	return block;
 }
 
@@ -2355,7 +2410,7 @@ MapBlock * ServerMap::generateBlock(
 		{
 			v3s16 p(x0,y0,z0);
 			MapNode n = block->getNode(p);
-			if(n.d == CONTENT_IGNORE)
+			if(n.getContent() == CONTENT_IGNORE)
 			{
 				dstream<<"CONTENT_IGNORE at "
 						<<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
@@ -2384,9 +2439,9 @@ MapBlock * ServerMap::generateBlock(
 			{
 				MapNode n;
 				if(y0%2==0)
-					n.d = CONTENT_AIR;
+					n.setContent(CONTENT_AIR);
 				else
-					n.d = CONTENT_STONE;
+					n.setContent(CONTENT_STONE);
 				block->setNode(v3s16(x0,y0,z0), n);
 			}
 		}
@@ -2470,7 +2525,7 @@ MapBlock * ServerMap::emergeBlock(v3s16 p, bool allow_generate)
 	
 	{
 		MapBlock *block = getBlockNoCreateNoEx(p);
-		if(block)
+		if(block && block->isDummy() == false)
 			return block;
 	}
 
@@ -2667,19 +2722,19 @@ s16 ServerMap::findGroundLevel(v2s16 p2d)
 	for(; p.Y>min; p.Y--)
 	{
 		MapNode n = getNodeNoEx(p);
-		if(n.d != CONTENT_IGNORE)
+		if(n.getContent() != CONTENT_IGNORE)
 			break;
 	}
 	if(p.Y == min)
 		goto plan_b;
 	// If this node is not air, go to plan b
-	if(getNodeNoEx(p).d != CONTENT_AIR)
+	if(getNodeNoEx(p).getContent() != CONTENT_AIR)
 		goto plan_b;
 	// Search existing walkable and return it
 	for(; p.Y>min; p.Y--)
 	{
 		MapNode n = getNodeNoEx(p);
-		if(content_walkable(n.d) && n.d != CONTENT_IGNORE)
+		if(content_walkable(n.d) && n.getContent() != CONTENT_IGNORE)
 			return p.Y;
 	}
 
@@ -4072,10 +4127,16 @@ void ManualMapVoxelManipulator::blitBackAll(
 			i = m_loaded_blocks.getIterator();
 			i.atEnd() == false; i++)
 	{
+		v3s16 p = i.getNode()->getKey();
 		bool existed = i.getNode()->getValue();
 		if(existed == false)
+		{
+			// The Great Bug was found using this
+			/*dstream<<"ManualMapVoxelManipulator::blitBackAll: "
+					<<"Inexistent ("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+					<<std::endl;*/
 			continue;
-		v3s16 p = i.getNode()->getKey();
+		}
 		MapBlock *block = m_map->getBlockNoCreateNoEx(p);
 		if(block == NULL)
 		{
diff --git a/src/mapblock.cpp b/src/mapblock.cpp
index 647a177564c2a1b088eb2820aebe84cf4b98afdf..44ca75ff0b794ab66393c8329ab70ffd9b153de3 100644
--- a/src/mapblock.cpp
+++ b/src/mapblock.cpp
@@ -242,7 +242,12 @@ bool MapBlock::propagateSunlight(core::map<v3s16, bool> & light_sources,
 			// Check if node above block has sunlight
 			try{
 				MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
-				if(n.d == CONTENT_IGNORE || n.getLight(LIGHTBANK_DAY) != LIGHT_SUN)
+				if(n.getContent() == CONTENT_IGNORE)
+				{
+					// Trust heuristics
+					no_sunlight = is_underground;
+				}
+				else if(n.getLight(LIGHTBANK_DAY) != LIGHT_SUN)
 				{
 					no_sunlight = true;
 				}
@@ -260,8 +265,8 @@ bool MapBlock::propagateSunlight(core::map<v3s16, bool> & light_sources,
 				else
 				{
 					MapNode n = getNode(v3s16(x, MAP_BLOCKSIZE-1, z));
-					//if(n.d == CONTENT_WATER || n.d == CONTENT_WATERSOURCE)
-					if(content_features(n.d).sunlight_propagates == false)
+					//if(n.getContent() == CONTENT_WATER || n.getContent() == CONTENT_WATERSOURCE)
+					if(content_features(n).sunlight_propagates == false)
 					{
 						no_sunlight = true;
 					}
@@ -322,14 +327,14 @@ bool MapBlock::propagateSunlight(core::map<v3s16, bool> & light_sources,
 						bool upper_is_air = false;
 						try
 						{
-							if(getNodeParent(pos+v3s16(0,1,0)).d == CONTENT_AIR)
+							if(getNodeParent(pos+v3s16(0,1,0)).getContent() == CONTENT_AIR)
 								upper_is_air = true;
 						}
 						catch(InvalidPositionException &e)
 						{
 						}
 						// Turn mud into grass
-						if(upper_is_air && n.d == CONTENT_MUD
+						if(upper_is_air && n.getContent() == CONTENT_MUD
 								&& current_light == LIGHT_SUN)
 						{
 							n.d = CONTENT_GRASS;
@@ -473,7 +478,7 @@ void MapBlock::updateDayNightDiff()
 		for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
 		{
 			MapNode &n = data[i];
-			if(n.d != CONTENT_AIR)
+			if(n.getContent() != CONTENT_AIR)
 			{
 				only_air = false;
 				break;
@@ -496,8 +501,8 @@ s16 MapBlock::getGroundLevel(v2s16 p2d)
 		s16 y = MAP_BLOCKSIZE-1;
 		for(; y>=0; y--)
 		{
-			//if(is_ground_content(getNodeRef(p2d.X, y, p2d.Y).d))
-			if(content_features(getNodeRef(p2d.X, y, p2d.Y).d).walkable)
+			MapNode n = getNodeRef(p2d.X, y, p2d.Y);
+			if(content_features(n).walkable)
 			{
 				if(y == MAP_BLOCKSIZE-1)
 					return -2;
@@ -560,7 +565,7 @@ void MapBlock::serialize(std::ostream &os, u8 version)
 		SharedBuffer<u8> materialdata(nodecount);
 		for(u32 i=0; i<nodecount; i++)
 		{
-			materialdata[i] = data[i].d;
+			materialdata[i] = data[i].param0;
 		}
 		compress(materialdata, os, version);
 
@@ -568,7 +573,7 @@ void MapBlock::serialize(std::ostream &os, u8 version)
 		SharedBuffer<u8> lightdata(nodecount);
 		for(u32 i=0; i<nodecount; i++)
 		{
-			lightdata[i] = data[i].param;
+			lightdata[i] = data[i].param1;
 		}
 		compress(lightdata, os, version);
 		
@@ -715,7 +720,7 @@ void MapBlock::deSerialize(std::istream &is, u8 version)
 						("MapBlock::deSerialize: invalid format");
 			for(u32 i=0; i<s.size(); i++)
 			{
-				data[i].d = s[i];
+				data[i].param0 = s[i];
 			}
 		}
 		{
@@ -728,7 +733,7 @@ void MapBlock::deSerialize(std::istream &is, u8 version)
 						("MapBlock::deSerialize: invalid format");
 			for(u32 i=0; i<s.size(); i++)
 			{
-				data[i].param = s[i];
+				data[i].param1 = s[i];
 			}
 		}
 	
@@ -862,5 +867,130 @@ void MapBlock::deSerializeDiskExtra(std::istream &is, u8 version)
 	}
 }
 
+/*
+	Get a quick string to describe what a block actually contains
+*/
+std::string analyze_block(MapBlock *block)
+{
+	if(block == NULL)
+	{
+		return "NULL";
+	}
+
+	std::ostringstream desc;
+	
+	v3s16 p = block->getPos();
+	char spos[20];
+	snprintf(spos, 20, "(%2d,%2d,%2d), ", p.X, p.Y, p.Z);
+	desc<<spos;
+	
+	switch(block->getModified())
+	{
+	case MOD_STATE_CLEAN:
+		desc<<"CLEAN,           ";
+		break;
+	case MOD_STATE_WRITE_AT_UNLOAD:
+		desc<<"WRITE_AT_UNLOAD, ";
+		break;
+	case MOD_STATE_WRITE_NEEDED:
+		desc<<"WRITE_NEEDED,    ";
+		break;
+	default:
+		desc<<"unknown getModified()="+itos(block->getModified())+", ";
+	}
+
+	if(block->isGenerated())
+		desc<<"is_gen [X], ";
+	else
+		desc<<"is_gen [ ], ";
+
+	if(block->getIsUnderground())
+		desc<<"is_ug [X], ";
+	else
+		desc<<"is_ug [ ], ";
+
+#ifndef SERVER
+	if(block->getMeshExpired())
+		desc<<"mesh_exp [X], ";
+	else
+		desc<<"mesh_exp [ ], ";
+#endif
+
+	if(block->getLightingExpired())
+		desc<<"lighting_exp [X], ";
+	else
+		desc<<"lighting_exp [ ], ";
+
+	if(block->isDummy())
+	{
+		desc<<"Dummy, ";
+	}
+	else
+	{
+		// We'll just define the numbers here, don't want to include
+		// content_mapnode.h
+		const content_t content_water = 2;
+		const content_t content_watersource = 9;
+		const content_t content_tree = 0x801;
+		const content_t content_leaves = 0x802;
+		const content_t content_jungletree = 0x815;
+
+		bool full_ignore = true;
+		bool some_ignore = false;
+		bool full_air = true;
+		bool some_air = false;
+		bool trees = false;
+		bool water = false;
+		for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
+		for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
+		for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
+		{
+			v3s16 p(x0,y0,z0);
+			MapNode n = block->getNode(p);
+			content_t c = n.getContent();
+			if(c == CONTENT_IGNORE)
+				some_ignore = true;
+			else
+				full_ignore = false;
+			if(c == CONTENT_AIR)
+				some_air = true;
+			else
+				full_air = false;
+			if(c == content_tree || c == content_jungletree
+					|| c == content_leaves)
+				trees = true;
+			if(c == content_water
+					|| c == content_watersource)
+				water = true;
+		}
+		
+		desc<<"content {";
+		
+		std::ostringstream ss;
+		
+		if(full_ignore)
+			ss<<"IGNORE (full), ";
+		else if(some_ignore)
+			ss<<"IGNORE, ";
+		
+		if(full_air)
+			ss<<"AIR (full), ";
+		else if(some_air)
+			ss<<"AIR, ";
+
+		if(trees)
+			ss<<"trees, ";
+		if(water)
+			ss<<"water, ";
+		
+		if(ss.str().size()>=2)
+			desc<<ss.str().substr(0, ss.str().size()-2);
+
+		desc<<"}, ";
+	}
+
+	return desc.str().substr(0, desc.str().size()-2);
+}
+
 
 //END
diff --git a/src/mapblock.h b/src/mapblock.h
index 8f3b8464a83bedbaa714555211045b18f56235c3..741c306eb9968c58c0a3052ce9b0e3a8122f8774 100644
--- a/src/mapblock.h
+++ b/src/mapblock.h
@@ -743,5 +743,10 @@ inline s16 getNodeBlockY(s16 y)
 	return getContainerPos(y, MAP_BLOCKSIZE);
 }
 
+/*
+	Get a quick string to describe what a block actually contains
+*/
+std::string analyze_block(MapBlock *block);
+
 #endif
 
diff --git a/src/mapblock_mesh.cpp b/src/mapblock_mesh.cpp
index 447716d0059d44e53f6681af8c3dda1b8314b6aa..c625983b059c906558ba97010ee61e63a6c68073 100644
--- a/src/mapblock_mesh.cpp
+++ b/src/mapblock_mesh.cpp
@@ -140,9 +140,24 @@ void getNodeVertexDirs(v3s16 dir, v3s16 *vertex_dirs)
 	}
 }
 
-inline video::SColor lightColor(u8 alpha, u8 light)
+video::SColor MapBlock_LightColor(u8 alpha, u8 light)
 {
+#if 0
 	return video::SColor(alpha,light,light,light);
+#endif
+	//return video::SColor(alpha,light,light,MYMAX(0, (s16)light-25)+25);
+	/*return video::SColor(alpha,light,light,MYMAX(0,
+			pow((float)light/255.0, 0.8)*255.0));*/
+#if 1
+	// Emphase blue a bit in darker places
+	float lim = 80;
+	float power = 0.8;
+	if(light > lim)
+		return video::SColor(alpha,light,light,light);
+	else
+		return video::SColor(alpha,light,light,MYMAX(0,
+				pow((float)light/lim, power)*lim));
+#endif
 }
 
 struct FastFace
@@ -198,7 +213,7 @@ void makeFastFace(TileSpec tile, u8 li0, u8 li1, u8 li2, u8 li3, v3f p,
 	float w = tile.texture.size.X;
 	float h = tile.texture.size.Y;
 
-	/*video::SColor c = lightColor(alpha, li);
+	/*video::SColor c = MapBlock_LightColor(alpha, li);
 
 	face.vertices[0] = video::S3DVertex(vertex_pos[0], v3f(0,1,0), c,
 			core::vector2d<f32>(x0+w*abs_scale, y0+h));
@@ -210,16 +225,16 @@ void makeFastFace(TileSpec tile, u8 li0, u8 li1, u8 li2, u8 li3, v3f p,
 			core::vector2d<f32>(x0+w*abs_scale, y0));*/
 
 	face.vertices[0] = video::S3DVertex(vertex_pos[0], v3f(0,1,0),
-			lightColor(alpha, li0),
+			MapBlock_LightColor(alpha, li0),
 			core::vector2d<f32>(x0+w*abs_scale, y0+h));
 	face.vertices[1] = video::S3DVertex(vertex_pos[1], v3f(0,1,0),
-			lightColor(alpha, li1),
+			MapBlock_LightColor(alpha, li1),
 			core::vector2d<f32>(x0, y0+h));
 	face.vertices[2] = video::S3DVertex(vertex_pos[2], v3f(0,1,0),
-			lightColor(alpha, li2),
+			MapBlock_LightColor(alpha, li2),
 			core::vector2d<f32>(x0, y0));
 	face.vertices[3] = video::S3DVertex(vertex_pos[3], v3f(0,1,0),
-			lightColor(alpha, li3),
+			MapBlock_LightColor(alpha, li3),
 			core::vector2d<f32>(x0+w*abs_scale, y0));
 
 	face.tile = tile;
@@ -285,7 +300,7 @@ TileSpec getNodeTile(MapNode mn, v3s16 p, v3s16 face_dir,
 	return spec;
 }
 
-u8 getNodeContent(v3s16 p, MapNode mn, NodeModMap &temp_mods)
+content_t getNodeContent(v3s16 p, MapNode mn, NodeModMap &temp_mods)
 {
 	/*
 		Check temporary modifications on this node
@@ -320,7 +335,7 @@ u8 getNodeContent(v3s16 p, MapNode mn, NodeModMap &temp_mods)
 		}
 	}
 
-	return mn.d;
+	return mn.getContent();
 }
 
 v3s16 dirs8[8] = {
@@ -343,16 +358,16 @@ u8 getSmoothLight(v3s16 p, VoxelManipulator &vmanip, u32 daynight_ratio)
 	for(u32 i=0; i<8; i++)
 	{
 		MapNode n = vmanip.getNodeNoEx(p - dirs8[i]);
-		if(content_features(n.d).param_type == CPT_LIGHT
+		if(content_features(n).param_type == CPT_LIGHT
 				// Fast-style leaves look better this way
-				&& content_features(n.d).solidness != 2)
+				&& content_features(n).solidness != 2)
 		{
 			light += decode_light(n.getLightBlend(daynight_ratio));
 			light_count++;
 		}
 		else
 		{
-			if(n.d != CONTENT_IGNORE)
+			if(n.getContent() != CONTENT_IGNORE)
 				ambient_occlusion++;
 		}
 	}
@@ -408,8 +423,8 @@ void getTileInfo(
 	TileSpec tile1 = getNodeTile(n1, p + face_dir, -face_dir, temp_mods);
 	
 	// This is hackish
-	u8 content0 = getNodeContent(p, n0, temp_mods);
-	u8 content1 = getNodeContent(p + face_dir, n1, temp_mods);
+	content_t content0 = getNodeContent(p, n0, temp_mods);
+	content_t content1 = getNodeContent(p + face_dir, n1, temp_mods);
 	u8 mf = face_contents(content0, content1);
 
 	if(mf == 0)
diff --git a/src/mapblock_mesh.h b/src/mapblock_mesh.h
index 591172bc90e0ea8eac8ed46a78957a9a8629d939..d43c19a253f53cc020d122714ca093ab77293829 100644
--- a/src/mapblock_mesh.h
+++ b/src/mapblock_mesh.h
@@ -121,6 +121,9 @@ class MeshCollector
 	core::array<PreMeshBuffer> m_prebuffers;
 };
 
+// Helper functions
+video::SColor MapBlock_LightColor(u8 alpha, u8 light);
+
 class MapBlock;
 
 struct MeshMakeData
@@ -137,6 +140,7 @@ struct MeshMakeData
 	void fill(u32 daynight_ratio, MapBlock *block);
 };
 
+// This is the highest-level function in here
 scene::SMesh* makeMapBlockMesh(MeshMakeData *data);
 
 #endif
diff --git a/src/mapblockobject.cpp b/src/mapblockobject.cpp
index ab1c20267dfd39687446f991fa797c26ef6cb150..071a14b0cdb3b497d586f057fb6a1503377b0f20 100644
--- a/src/mapblockobject.cpp
+++ b/src/mapblockobject.cpp
@@ -161,8 +161,8 @@ void MovingObject::move(float dtime, v3f acceleration)
 		for(s16 x = oldpos_i.X - 1; x <= oldpos_i.X + 1; x++)
 		{
 			try{
-				if(content_walkable(m_block->getNodeParent(v3s16(x,y,z)).d)
-						== false)
+				MapNode n = m_block->getNodeParent(v3s16(x,y,z));
+				if(content_features(n).walkable == false)
 					continue;
 			}
 			catch(InvalidPositionException &e)
diff --git a/src/mapgen.cpp b/src/mapgen.cpp
index 8dda93c960e30a74cd4cd2d55e30b11ba741e882..0ba7f91abadb9d71178f0ed4bab29f84b04e553b 100644
--- a/src/mapgen.cpp
+++ b/src/mapgen.cpp
@@ -67,8 +67,8 @@ static s16 find_ground_level_clever(VoxelManipulator &vmanip, v2s16 p2d)
 	{
 		MapNode &n = vmanip.m_data[i];
 		if(content_walkable(n.d)
-				&& n.d != CONTENT_TREE
-				&& n.d != CONTENT_LEAVES)
+				&& n.getContent() != CONTENT_TREE
+				&& n.getContent() != CONTENT_LEAVES)
 			break;
 
 		vmanip.m_area.add_y(em, i, -1);
@@ -143,8 +143,94 @@ static void make_tree(VoxelManipulator &vmanip, v3s16 p0)
 		if(vmanip.m_area.contains(p) == false)
 			continue;
 		u32 vi = vmanip.m_area.index(p);
-		if(vmanip.m_data[vi].d != CONTENT_AIR
-				&& vmanip.m_data[vi].d != CONTENT_IGNORE)
+		if(vmanip.m_data[vi].getContent() != CONTENT_AIR
+				&& vmanip.m_data[vi].getContent() != CONTENT_IGNORE)
+			continue;
+		u32 i = leaves_a.index(x,y,z);
+		if(leaves_d[i] == 1)
+			vmanip.m_data[vi] = leavesnode;
+	}
+}
+
+static void make_jungletree(VoxelManipulator &vmanip, v3s16 p0)
+{
+	MapNode treenode(CONTENT_JUNGLETREE);
+	MapNode leavesnode(CONTENT_LEAVES);
+
+	for(s16 x=-1; x<=1; x++)
+	for(s16 z=-1; z<=1; z++)
+	{
+		if(myrand_range(0, 2) == 0)
+			continue;
+		v3s16 p1 = p0 + v3s16(x,0,z);
+		v3s16 p2 = p0 + v3s16(x,-1,z);
+		if(vmanip.m_area.contains(p2)
+				&& vmanip.m_data[vmanip.m_area.index(p2)] == CONTENT_AIR)
+			vmanip.m_data[vmanip.m_area.index(p2)] = treenode;
+		else if(vmanip.m_area.contains(p1))
+			vmanip.m_data[vmanip.m_area.index(p1)] = treenode;
+	}
+
+	s16 trunk_h = myrand_range(8, 12);
+	v3s16 p1 = p0;
+	for(s16 ii=0; ii<trunk_h; ii++)
+	{
+		if(vmanip.m_area.contains(p1))
+			vmanip.m_data[vmanip.m_area.index(p1)] = treenode;
+		p1.Y++;
+	}
+
+	// p1 is now the last piece of the trunk
+	p1.Y -= 1;
+
+	VoxelArea leaves_a(v3s16(-3,-2,-3), v3s16(3,2,3));
+	//SharedPtr<u8> leaves_d(new u8[leaves_a.getVolume()]);
+	Buffer<u8> leaves_d(leaves_a.getVolume());
+	for(s32 i=0; i<leaves_a.getVolume(); i++)
+		leaves_d[i] = 0;
+
+	// Force leaves at near the end of the trunk
+	{
+		s16 d = 1;
+		for(s16 z=-d; z<=d; z++)
+		for(s16 y=-d; y<=d; y++)
+		for(s16 x=-d; x<=d; x++)
+		{
+			leaves_d[leaves_a.index(v3s16(x,y,z))] = 1;
+		}
+	}
+
+	// Add leaves randomly
+	for(u32 iii=0; iii<30; iii++)
+	{
+		s16 d = 1;
+
+		v3s16 p(
+			myrand_range(leaves_a.MinEdge.X, leaves_a.MaxEdge.X-d),
+			myrand_range(leaves_a.MinEdge.Y, leaves_a.MaxEdge.Y-d),
+			myrand_range(leaves_a.MinEdge.Z, leaves_a.MaxEdge.Z-d)
+		);
+
+		for(s16 z=0; z<=d; z++)
+		for(s16 y=0; y<=d; y++)
+		for(s16 x=0; x<=d; x++)
+		{
+			leaves_d[leaves_a.index(p+v3s16(x,y,z))] = 1;
+		}
+	}
+
+	// Blit leaves to vmanip
+	for(s16 z=leaves_a.MinEdge.Z; z<=leaves_a.MaxEdge.Z; z++)
+	for(s16 y=leaves_a.MinEdge.Y; y<=leaves_a.MaxEdge.Y; y++)
+	for(s16 x=leaves_a.MinEdge.X; x<=leaves_a.MaxEdge.X; x++)
+	{
+		v3s16 p(x,y,z);
+		p += p1;
+		if(vmanip.m_area.contains(p) == false)
+			continue;
+		u32 vi = vmanip.m_area.index(p);
+		if(vmanip.m_data[vi].getContent() != CONTENT_AIR
+				&& vmanip.m_data[vi].getContent() != CONTENT_IGNORE)
 			continue;
 		u32 i = leaves_a.index(x,y,z);
 		if(leaves_d[i] == 1)
@@ -251,8 +337,8 @@ static void make_randomstone(VoxelManipulator &vmanip, v3s16 p0)
 		if(vmanip.m_area.contains(p) == false)
 			continue;
 		u32 vi = vmanip.m_area.index(p);
-		if(vmanip.m_data[vi].d != CONTENT_AIR
-				&& vmanip.m_data[vi].d != CONTENT_IGNORE)
+		if(vmanip.m_data[vi].getContent() != CONTENT_AIR
+				&& vmanip.m_data[vi].getContent() != CONTENT_IGNORE)
 			continue;
 		u32 i = stone_a.index(x,y,z);
 		if(stone_d[i] == 1)
@@ -335,8 +421,8 @@ static void make_largestone(VoxelManipulator &vmanip, v3s16 p0)
 		if(vmanip.m_area.contains(p) == false)
 			continue;
 		u32 vi = vmanip.m_area.index(p);
-		/*if(vmanip.m_data[vi].d != CONTENT_AIR
-				&& vmanip.m_data[vi].d != CONTENT_IGNORE)
+		/*if(vmanip.m_data[vi].getContent() != CONTENT_AIR
+				&& vmanip.m_data[vi].getContent() != CONTENT_IGNORE)
 			continue;*/
 		u32 i = stone_a.index(x,y,z);
 		if(stone_d[i] == 1)
@@ -544,9 +630,9 @@ static void make_corridor(VoxelManipulator &vmanip, v3s16 doorplace,
 			p.Y += make_stairs;
 
 		/*// If already empty
-		if(vmanip.getNodeNoExNoEmerge(p).d
+		if(vmanip.getNodeNoExNoEmerge(p).getContent()
 				== CONTENT_AIR
-		&& vmanip.getNodeNoExNoEmerge(p+v3s16(0,1,0)).d
+		&& vmanip.getNodeNoExNoEmerge(p+v3s16(0,1,0)).getContent()
 				== CONTENT_AIR)
 		{
 		}*/
@@ -642,9 +728,9 @@ class RoomWalker
 				randomizeDir();
 				continue;
 			}
-			if(vmanip.getNodeNoExNoEmerge(p).d
+			if(vmanip.getNodeNoExNoEmerge(p).getContent()
 					== CONTENT_COBBLE
-			&& vmanip.getNodeNoExNoEmerge(p1).d
+			&& vmanip.getNodeNoExNoEmerge(p1).getContent()
 					== CONTENT_COBBLE)
 			{
 				// Found wall, this is a good place!
@@ -658,25 +744,25 @@ class RoomWalker
 				Determine where to move next
 			*/
 			// Jump one up if the actual space is there
-			if(vmanip.getNodeNoExNoEmerge(p+v3s16(0,0,0)).d
+			if(vmanip.getNodeNoExNoEmerge(p+v3s16(0,0,0)).getContent()
 					== CONTENT_COBBLE
-			&& vmanip.getNodeNoExNoEmerge(p+v3s16(0,1,0)).d
+			&& vmanip.getNodeNoExNoEmerge(p+v3s16(0,1,0)).getContent()
 					== CONTENT_AIR
-			&& vmanip.getNodeNoExNoEmerge(p+v3s16(0,2,0)).d
+			&& vmanip.getNodeNoExNoEmerge(p+v3s16(0,2,0)).getContent()
 					== CONTENT_AIR)
 				p += v3s16(0,1,0);
 			// Jump one down if the actual space is there
-			if(vmanip.getNodeNoExNoEmerge(p+v3s16(0,1,0)).d
+			if(vmanip.getNodeNoExNoEmerge(p+v3s16(0,1,0)).getContent()
 					== CONTENT_COBBLE
-			&& vmanip.getNodeNoExNoEmerge(p+v3s16(0,0,0)).d
+			&& vmanip.getNodeNoExNoEmerge(p+v3s16(0,0,0)).getContent()
 					== CONTENT_AIR
-			&& vmanip.getNodeNoExNoEmerge(p+v3s16(0,-1,0)).d
+			&& vmanip.getNodeNoExNoEmerge(p+v3s16(0,-1,0)).getContent()
 					== CONTENT_AIR)
 				p += v3s16(0,-1,0);
 			// Check if walking is now possible
-			if(vmanip.getNodeNoExNoEmerge(p).d
+			if(vmanip.getNodeNoExNoEmerge(p).getContent()
 					!= CONTENT_AIR
-			|| vmanip.getNodeNoExNoEmerge(p+v3s16(0,1,0)).d
+			|| vmanip.getNodeNoExNoEmerge(p+v3s16(0,1,0)).getContent()
 					!= CONTENT_AIR)
 			{
 				// Cannot continue walking here
@@ -798,7 +884,7 @@ static void make_dungeon1(VoxelManipulator &vmanip, PseudoRandom &random)
 				fits = false;
 				break;
 			}
-			if(vmanip.m_data[vi].d == CONTENT_IGNORE)
+			if(vmanip.m_data[vi].getContent() == CONTENT_IGNORE)
 			{
 				fits = false;
 				break;
@@ -1004,13 +1090,26 @@ double tree_amount_2d(u64 seed, v2s16 p)
 	double noise = noise2d_perlin(
 			0.5+(float)p.X/125, 0.5+(float)p.Y/125,
 			seed+2, 4, 0.66);
-	double zeroval = -0.35;
+	double zeroval = -0.39;
 	if(noise < zeroval)
 		return 0;
 	else
 		return 0.04 * (noise-zeroval) / (1.0-zeroval);
 }
 
+double surface_humidity_2d(u64 seed, v2s16 p)
+{
+	double noise = noise2d_perlin(
+			0.5+(float)p.X/500, 0.5+(float)p.Y/500,
+			seed+72384, 4, 0.66);
+	noise = (noise + 1.0)/2.0;
+	if(noise < 0.0)
+		noise = 0.0;
+	if(noise > 1.0)
+		noise = 1.0;
+	return noise;
+}
+
 #if 0
 double randomstone_amount_2d(u64 seed, v2s16 p)
 {
@@ -1273,11 +1372,11 @@ void add_random_objects(MapBlock *block)
 		{
 			v3s16 p(x0,y0,z0);
 			MapNode n = block->getNodeNoEx(p);
-			if(n.d == CONTENT_IGNORE)
+			if(n.getContent() == CONTENT_IGNORE)
 				continue;
-			if(content_features(n.d).liquid_type != LIQUID_NONE)
+			if(content_features(n).liquid_type != LIQUID_NONE)
 				continue;
-			if(content_features(n.d).walkable)
+			if(content_features(n).walkable)
 			{
 				last_node_walkable = true;
 				continue;
@@ -1285,7 +1384,7 @@ void add_random_objects(MapBlock *block)
 			if(last_node_walkable)
 			{
 				// If block contains light information
-				if(content_features(n.d).param_type == CPT_LIGHT)
+				if(content_features(n).param_type == CPT_LIGHT)
 				{
 					if(n.getLight(LIGHTBANK_DAY) <= 3)
 					{
@@ -1392,7 +1491,7 @@ void make_block(BlockMakeData *data)
 				for(s16 y=node_min.Y; y<=node_max.Y; y++)
 				{
 					// Only modify places that have no content
-					if(vmanip.m_data[i].d == CONTENT_IGNORE)
+					if(vmanip.m_data[i].getContent() == CONTENT_IGNORE)
 					{
 						if(y <= WATER_LEVEL)
 							vmanip.m_data[i] = MapNode(CONTENT_WATERSOURCE);
@@ -1498,7 +1597,7 @@ void make_block(BlockMakeData *data)
 			for(s16 y=node_min.Y; y<=node_max.Y; y++)
 			{
 				// Only modify places that have no content
-				if(vmanip.m_data[i].d == CONTENT_IGNORE)
+				if(vmanip.m_data[i].getContent() == CONTENT_IGNORE)
 				{
 					// First priority: make air and water.
 					// This avoids caves inside water.
@@ -1543,7 +1642,7 @@ void make_block(BlockMakeData *data)
 				{
 					v3s16 p = v3s16(x,y,z) + g_27dirs[i];
 					u32 vi = vmanip.m_area.index(p);
-					if(vmanip.m_data[vi].d == CONTENT_STONE)
+					if(vmanip.m_data[vi].getContent() == CONTENT_STONE)
 						if(mineralrandom.next()%8 == 0)
 							vmanip.m_data[vi] = MapNode(CONTENT_MESE);
 				}
@@ -1584,13 +1683,13 @@ void make_block(BlockMakeData *data)
 				{
 				}*/
 
-				if(new_content.d != CONTENT_IGNORE)
+				if(new_content.getContent() != CONTENT_IGNORE)
 				{
 					for(u16 i=0; i<27; i++)
 					{
 						v3s16 p = v3s16(x,y,z) + g_27dirs[i];
 						u32 vi = vmanip.m_area.index(p);
-						if(vmanip.m_data[vi].d == base_content)
+						if(vmanip.m_data[vi].getContent() == base_content)
 						{
 							if(mineralrandom.next()%sparseness == 0)
 								vmanip.m_data[vi] = new_content;
@@ -1621,7 +1720,7 @@ void make_block(BlockMakeData *data)
 				{
 					v3s16 p = v3s16(x,y,z) + g_27dirs[i];
 					u32 vi = vmanip.m_area.index(p);
-					if(vmanip.m_data[vi].d == CONTENT_STONE)
+					if(vmanip.m_data[vi].getContent() == CONTENT_STONE)
 						if(mineralrandom.next()%8 == 0)
 							vmanip.m_data[vi] = MapNode(CONTENT_STONE, MINERAL_COAL);
 				}
@@ -1647,7 +1746,7 @@ void make_block(BlockMakeData *data)
 				{
 					v3s16 p = v3s16(x,y,z) + g_27dirs[i];
 					u32 vi = vmanip.m_area.index(p);
-					if(vmanip.m_data[vi].d == CONTENT_STONE)
+					if(vmanip.m_data[vi].getContent() == CONTENT_STONE)
 						if(mineralrandom.next()%8 == 0)
 							vmanip.m_data[vi] = MapNode(CONTENT_STONE, MINERAL_IRON);
 				}
@@ -1670,7 +1769,7 @@ void make_block(BlockMakeData *data)
 			u32 i = vmanip.m_area.index(v3s16(p2d.X, node_max.Y, p2d.Y));
 			for(s16 y=node_max.Y; y>=node_min.Y; y--)
 			{
-				if(vmanip.m_data[i].d == CONTENT_STONE)
+				if(vmanip.m_data[i].getContent() == CONTENT_STONE)
 				{
 					if(noisebuf_ground_crumbleness.get(x,y,z) > 1.3)
 					{
@@ -1722,9 +1821,9 @@ void make_block(BlockMakeData *data)
 				u32 i = vmanip.m_area.index(v3s16(p2d.X, full_node_max.Y, p2d.Y));
 				for(s16 y=full_node_max.Y; y>=full_node_min.Y; y--)
 				{
-					if(vmanip.m_data[i].d == CONTENT_AIR)
+					if(vmanip.m_data[i].getContent() == CONTENT_AIR)
 						vmanip.m_flags[i] |= VMANIP_FLAG_DUNGEON_PRESERVE;
-					else if(vmanip.m_data[i].d == CONTENT_WATERSOURCE)
+					else if(vmanip.m_data[i].getContent() == CONTENT_WATERSOURCE)
 						vmanip.m_flags[i] |= VMANIP_FLAG_DUNGEON_PRESERVE;
 					data->vmanip->m_area.add_y(em, i, -1);
 				}
@@ -1755,17 +1854,17 @@ void make_block(BlockMakeData *data)
 					double d = noise3d_perlin((float)x/2.5,
 							(float)y/2.5,(float)z/2.5,
 							blockseed, 2, 1.4);
-					if(vmanip.m_data[i].d == CONTENT_COBBLE)
+					if(vmanip.m_data[i].getContent() == CONTENT_COBBLE)
 					{
 						if(d < wetness/3.0)
 						{
-							vmanip.m_data[i].d = CONTENT_MOSSYCOBBLE;
+							vmanip.m_data[i].setContent(CONTENT_MOSSYCOBBLE);
 						}
 					}
 					/*else if(vmanip.m_flags[i] & VMANIP_FLAG_DUNGEON_INSIDE)
 					{
 						if(wetness > 1.2)
-							vmanip.m_data[i].d = CONTENT_MUD;
+							vmanip.m_data[i].setContent(CONTENT_MUD);
 					}*/
 					data->vmanip->m_area.add_y(em, i, -1);
 				}
@@ -1791,7 +1890,7 @@ void make_block(BlockMakeData *data)
 			{
 				if(water_found == false)
 				{
-					if(vmanip.m_data[i].d == CONTENT_WATERSOURCE)
+					if(vmanip.m_data[i].getContent() == CONTENT_WATERSOURCE)
 					{
 						v3s16 p = v3s16(p2d.X, y, p2d.Y);
 						data->transforming_liquid.push_back(p);
@@ -1803,7 +1902,7 @@ void make_block(BlockMakeData *data)
 					// This can be done because water_found can only
 					// turn to true and end up here after going through
 					// a single block.
-					if(vmanip.m_data[i+1].d != CONTENT_WATERSOURCE)
+					if(vmanip.m_data[i+1].getContent() != CONTENT_WATERSOURCE)
 					{
 						v3s16 p = v3s16(p2d.X, y+1, p2d.Y);
 						data->transforming_liquid.push_back(p);
@@ -1846,16 +1945,16 @@ void make_block(BlockMakeData *data)
 				u32 i = vmanip.m_area.index(v3s16(p2d.X, start_y, p2d.Y));
 				for(s16 y=start_y; y>=node_min.Y-3; y--)
 				{
-					if(vmanip.m_data[i].d == CONTENT_WATERSOURCE)
+					if(vmanip.m_data[i].getContent() == CONTENT_WATERSOURCE)
 						water_detected = true;
-					if(vmanip.m_data[i].d == CONTENT_AIR)
+					if(vmanip.m_data[i].getContent() == CONTENT_AIR)
 						air_detected = true;
 
-					if((vmanip.m_data[i].d == CONTENT_STONE
-							|| vmanip.m_data[i].d == CONTENT_GRASS
-							|| vmanip.m_data[i].d == CONTENT_MUD
-							|| vmanip.m_data[i].d == CONTENT_SAND
-							|| vmanip.m_data[i].d == CONTENT_GRAVEL
+					if((vmanip.m_data[i].getContent() == CONTENT_STONE
+							|| vmanip.m_data[i].getContent() == CONTENT_GRASS
+							|| vmanip.m_data[i].getContent() == CONTENT_MUD
+							|| vmanip.m_data[i].getContent() == CONTENT_SAND
+							|| vmanip.m_data[i].getContent() == CONTENT_GRAVEL
 							) && (air_detected || water_detected))
 					{
 						if(current_depth == 0 && y <= WATER_LEVEL+2
@@ -1890,8 +1989,8 @@ void make_block(BlockMakeData *data)
 						}
 						else
 						{
-							if(vmanip.m_data[i].d == CONTENT_MUD
-								|| vmanip.m_data[i].d == CONTENT_GRASS)
+							if(vmanip.m_data[i].getContent() == CONTENT_MUD
+								|| vmanip.m_data[i].getContent() == CONTENT_GRASS)
 								vmanip.m_data[i] = MapNode(CONTENT_STONE);
 						}
 
@@ -1909,11 +2008,19 @@ void make_block(BlockMakeData *data)
 		}
 
 		/*
-			Add trees
+			Calculate some stuff
 		*/
 		
+		float surface_humidity = surface_humidity_2d(data->seed, p2d_center);
+		bool is_jungle = surface_humidity > 0.75;
 		// Amount of trees
 		u32 tree_count = block_area_nodes * tree_amount_2d(data->seed, p2d_center);
+		if(is_jungle)
+			tree_count *= 5;
+
+		/*
+			Add trees
+		*/
 		PseudoRandom treerandom(blockseed);
 		// Put trees in random places on part of division
 		for(u32 i=0; i<tree_count; i++)
@@ -1938,7 +2045,7 @@ void make_block(BlockMakeData *data)
 			{
 				u32 i = data->vmanip->m_area.index(p);
 				MapNode *n = &data->vmanip->m_data[i];
-				if(n->d != CONTENT_AIR && n->d != CONTENT_WATERSOURCE && n->d != CONTENT_IGNORE)
+				if(n->getContent() != CONTENT_AIR && n->getContent() != CONTENT_WATERSOURCE && n->getContent() != CONTENT_IGNORE)
 				{
 					found = true;
 					break;
@@ -1952,23 +2059,27 @@ void make_block(BlockMakeData *data)
 				u32 i = data->vmanip->m_area.index(p);
 				MapNode *n = &data->vmanip->m_data[i];
 
-				if(n->d != CONTENT_MUD && n->d != CONTENT_GRASS && n->d != CONTENT_SAND)
+				if(n->getContent() != CONTENT_MUD && n->getContent() != CONTENT_GRASS && n->getContent() != CONTENT_SAND)
 						continue;
 
 				// Papyrus grows only on mud and in water
-				if(n->d == CONTENT_MUD && y <= WATER_LEVEL)
+				if(n->getContent() == CONTENT_MUD && y <= WATER_LEVEL)
 				{
 					p.Y++;
 					make_papyrus(vmanip, p);
 				}
 				// Trees grow only on mud and grass, on land
-				else if((n->d == CONTENT_MUD || n->d == CONTENT_GRASS) && y > WATER_LEVEL + 2)
+				else if((n->getContent() == CONTENT_MUD || n->getContent() == CONTENT_GRASS) && y > WATER_LEVEL + 2)
 				{
 					p.Y++;
-					make_tree(vmanip, p);
+					//if(surface_humidity_2d(data->seed, v2s16(x, y)) < 0.5)
+					if(is_jungle == false)
+						make_tree(vmanip, p);
+					else
+						make_jungletree(vmanip, p);
 				}
 				// Cactii grow only on sand, on land
-				else if(n->d == CONTENT_SAND && y > WATER_LEVEL + 2)
+				else if(n->getContent() == CONTENT_SAND && y > WATER_LEVEL + 2)
 				{
 					p.Y++;
 					make_cactus(vmanip, p);
@@ -1976,6 +2087,54 @@ void make_block(BlockMakeData *data)
 			}
 		}
 
+		/*
+			Add jungle grass
+		*/
+		if(is_jungle)
+		{
+			PseudoRandom grassrandom(blockseed);
+			for(u32 i=0; i<surface_humidity*5*tree_count; i++)
+			{
+				s16 x = grassrandom.range(node_min.X, node_max.X);
+				s16 z = grassrandom.range(node_min.Z, node_max.Z);
+				s16 y = find_ground_level_from_noise(data->seed, v2s16(x,z), 4);
+				if(y < WATER_LEVEL)
+					continue;
+				if(y < node_min.Y || y > node_max.Y)
+					continue;
+				/*
+					Find exact ground level
+				*/
+				v3s16 p(x,y+6,z);
+				bool found = false;
+				for(; p.Y >= y-6; p.Y--)
+				{
+					u32 i = data->vmanip->m_area.index(p);
+					MapNode *n = &data->vmanip->m_data[i];
+					if(content_features(*n).is_ground_content
+							|| n->getContent() == CONTENT_JUNGLETREE)
+					{
+						found = true;
+						break;
+					}
+				}
+				// If not found, handle next one
+				if(found == false)
+					continue;
+				p.Y++;
+				if(vmanip.m_area.contains(p) == false)
+					continue;
+				if(vmanip.m_data[vmanip.m_area.index(p)].getContent() != CONTENT_AIR)
+					continue;
+				/*p.Y--;
+				if(vmanip.m_area.contains(p))
+					vmanip.m_data[vmanip.m_area.index(p)] = CONTENT_MUD;
+				p.Y++;*/
+				if(vmanip.m_area.contains(p))
+					vmanip.m_data[vmanip.m_area.index(p)] = CONTENT_JUNGLEGRASS;
+			}
+		}
+
 #if 0
 		/*
 			Add some kind of random stones
@@ -2000,7 +2159,7 @@ void make_block(BlockMakeData *data)
 			/*{
 				u32 i = data->vmanip->m_area.index(v3s16(p));
 				MapNode *n = &data->vmanip->m_data[i];
-				if(n->d != CONTENT_MUD && n->d != CONTENT_GRASS)
+				if(n->getContent() != CONTENT_MUD && n->getContent() != CONTENT_GRASS)
 					continue;
 			}*/
 			// Will be placed one higher
@@ -2035,7 +2194,7 @@ void make_block(BlockMakeData *data)
 			/*{
 				u32 i = data->vmanip->m_area.index(v3s16(p));
 				MapNode *n = &data->vmanip->m_data[i];
-				if(n->d != CONTENT_MUD && n->d != CONTENT_GRASS)
+				if(n->getContent() != CONTENT_MUD && n->getContent() != CONTENT_GRASS)
 					continue;
 			}*/
 			// Will be placed one lower
diff --git a/src/mapnode.cpp b/src/mapnode.cpp
index 1e9b64989767f440e45e87061076fa3131cf8c7d..c9f85c303ddd35698f795c7e365972b3dd60a85d 100644
--- a/src/mapnode.cpp
+++ b/src/mapnode.cpp
@@ -30,8 +30,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 ContentFeatures::~ContentFeatures()
 {
-	/*if(translate_to)
-		delete translate_to;*/
 	if(initial_metadata)
 		delete initial_metadata;
 }
@@ -83,12 +81,16 @@ void ContentFeatures::setInventoryTextureCube(std::string top,
 	inventory_texture = g_texturesource->getTextureRaw(imgname_full);
 }
 
-struct ContentFeatures g_content_features[256];
+struct ContentFeatures g_content_features[MAX_CONTENT+1];
 
-ContentFeatures & content_features(u8 i)
+ContentFeatures & content_features(content_t i)
 {
 	return g_content_features[i];
 }
+ContentFeatures & content_features(MapNode &n)
+{
+	return content_features(n.getContent());
+}
 
 /*
 	See mapnode.h for description.
@@ -128,7 +130,7 @@ void init_mapnode()
 		initial_material_type = MATERIAL_ALPHA_SIMPLE;
 	else
 		initial_material_type = MATERIAL_ALPHA_NONE;*/
-	for(u16 i=0; i<256; i++)
+	for(u16 i=0; i<MAX_CONTENT+1; i++)
 	{
 		ContentFeatures *f = &g_content_features[i];
 		// Re-initialize
@@ -142,7 +144,7 @@ void init_mapnode()
 		Initially set every block to be shown as an unknown block.
 		Don't touch CONTENT_IGNORE or CONTENT_AIR.
 	*/
-	for(u16 i=0; i<256; i++)
+	for(u16 i=0; i<MAX_CONTENT+1; i++)
 	{
 		if(i == CONTENT_IGNORE || i == CONTENT_AIR)
 			continue;
@@ -183,7 +185,7 @@ v3s16 facedir_rotate(u8 facedir, v3s16 dir)
 
 TileSpec MapNode::getTile(v3s16 dir)
 {
-	if(content_features(d).param_type == CPT_FACEDIR_SIMPLE)
+	if(content_features(*this).param_type == CPT_FACEDIR_SIMPLE)
 		dir = facedir_rotate(param1, dir);
 	
 	TileSpec spec;
@@ -207,16 +209,16 @@ TileSpec MapNode::getTile(v3s16 dir)
 	
 	if(dir_i == -1)
 		// Non-directional
-		spec = content_features(d).tiles[0];
+		spec = content_features(*this).tiles[0];
 	else 
-		spec = content_features(d).tiles[dir_i];
+		spec = content_features(*this).tiles[dir_i];
 	
 	/*
 		If it contains some mineral, change texture id
 	*/
-	if(content_features(d).param_type == CPT_MINERAL && g_texturesource)
+	if(content_features(*this).param_type == CPT_MINERAL && g_texturesource)
 	{
-		u8 mineral = param & 0x1f;
+		u8 mineral = getMineral();
 		std::string mineral_texture_name = mineral_block_texture(mineral);
 		if(mineral_texture_name != "")
 		{
@@ -235,9 +237,9 @@ TileSpec MapNode::getTile(v3s16 dir)
 
 u8 MapNode::getMineral()
 {
-	if(content_features(d).param_type == CPT_MINERAL)
+	if(content_features(*this).param_type == CPT_MINERAL)
 	{
-		return param & 0x1f;
+		return param1 & 0x0f;
 	}
 
 	return MINERAL_NONE;
@@ -260,33 +262,36 @@ void MapNode::serialize(u8 *dest, u8 version)
 	if(!ser_ver_supported(version))
 		throw VersionMismatchException("ERROR: MapNode format not supported");
 		
-	u8 actual_d = d;
+	// Translate to wanted version
+	MapNode n_foreign = mapnode_translate_from_internal(*this, version);
 
-	// Convert from new version to old
+	u8 actual_param0 = n_foreign.param0;
+
+	// Convert special values from new version to old
 	if(version <= 18)
 	{
 		// In these versions, CONTENT_IGNORE and CONTENT_AIR
 		// are 255 and 254
-		if(actual_d == CONTENT_IGNORE)
-			actual_d = 255;
-		else if(actual_d == CONTENT_AIR)
-			actual_d = 254;
+		if(actual_param0 == CONTENT_IGNORE)
+			actual_param0 = 255;
+		else if(actual_param0 == CONTENT_AIR)
+			actual_param0 = 254;
 	}
 
 	if(version == 0)
 	{
-		dest[0] = actual_d;
+		dest[0] = actual_param0;
 	}
 	else if(version <= 9)
 	{
-		dest[0] = actual_d;
-		dest[1] = param;
+		dest[0] = actual_param0;
+		dest[1] = n_foreign.param1;
 	}
 	else
 	{
-		dest[0] = actual_d;
-		dest[1] = param;
-		dest[2] = param2;
+		dest[0] = actual_param0;
+		dest[1] = n_foreign.param1;
+		dest[2] = n_foreign.param2;
 	}
 }
 void MapNode::deSerialize(u8 *source, u8 version)
@@ -296,47 +301,50 @@ void MapNode::deSerialize(u8 *source, u8 version)
 		
 	if(version == 0)
 	{
-		d = source[0];
+		param0 = source[0];
 	}
 	else if(version == 1)
 	{
-		d = source[0];
+		param0 = source[0];
 		// This version doesn't support saved lighting
 		if(light_propagates() || light_source() > 0)
-			param = 0;
+			param1 = 0;
 		else
-			param = source[1];
+			param1 = source[1];
 	}
 	else if(version <= 9)
 	{
-		d = source[0];
-		param = source[1];
+		param0 = source[0];
+		param1 = source[1];
 	}
 	else
 	{
-		d = source[0];
-		param = source[1];
+		param0 = source[0];
+		param1 = source[1];
 		param2 = source[2];
 	}
 	
-	// Convert from old version to new
+	// Convert special values from old version to new
 	if(version <= 18)
 	{
 		// In these versions, CONTENT_IGNORE and CONTENT_AIR
 		// are 255 and 254
-		if(d == 255)
-			d = CONTENT_IGNORE;
-		else if(d == 254)
-			d = CONTENT_AIR;
+		if(param0 == 255)
+			param0 = CONTENT_IGNORE;
+		else if(param0 == 254)
+			param0 = CONTENT_AIR;
 	}
 	// version 19 is fucked up with sometimes the old values and sometimes not
 	if(version == 19)
 	{
-		if(d == 255)
-			d = CONTENT_IGNORE;
-		else if(d == 254)
-			d = CONTENT_AIR;
+		if(param0 == 255)
+			param0 = CONTENT_IGNORE;
+		else if(param0 == 254)
+			param0 = CONTENT_AIR;
 	}
+
+	// Translate to our known version
+	*this = mapnode_translate_to_internal(*this, version);
 }
 
 /*
diff --git a/src/mapnode.h b/src/mapnode.h
index 33128049a2599692bf35212d671991e7b6401e2d..3cca985bc40e80133607764600301dd28c477172 100644
--- a/src/mapnode.h
+++ b/src/mapnode.h
@@ -32,16 +32,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 /*
 	Naming scheme:
 	- Material = irrlicht's Material class
-	- Content = (u8) content of a node
+	- Content = (content_t) content of a node
 	- Tile = TileSpec at some side of a node of some content type
-*/
 
-/*
-	Ranges:
+	Content ranges:
 		0x000...0x07f: param2 is fully usable
 		0x800...0xfff: param2 lower 4 bytes are free
 */
 typedef u16 content_t;
+#define MAX_CONTENT 0xfff
 
 /*
 	Initializes all kind of stuff in here.
@@ -102,10 +101,7 @@ class NodeMetadata;
 
 struct ContentFeatures
 {
-	// If non-NULL, content is translated to this when deserialized
-	//MapNode *translate_to;
-
-	// Type of MapNode::param
+	// Type of MapNode::param1
 	ContentParamType param_type;
 
 	/*
@@ -119,7 +115,8 @@ struct ContentFeatures
 	TileSpec tiles[6];
 	
 	video::ITexture *inventory_texture;
-
+	
+	// True for all ground-like things like stone and mud, false for eg. trees
 	bool is_ground_content;
 	bool light_propagates;
 	bool sunlight_propagates;
@@ -150,10 +147,10 @@ struct ContentFeatures
 	NodeMetadata *initial_metadata;
 	
 	// If the content is liquid, this is the flowing version of the liquid.
-	// If content is flowing liquid, this is the same content.
-	u8 liquid_alternative_flowing;
+	// If content is liquid, this is the same content.
+	content_t liquid_alternative_flowing;
 	// If the content is liquid, this is the source version of the liquid.
-	u8 liquid_alternative_source;
+	content_t liquid_alternative_source;
 	
 	// Amount of light the node emits
 	u8 light_source;
@@ -165,7 +162,6 @@ struct ContentFeatures
 
 	void reset()
 	{
-		//translate_to = NULL;
 		param_type = CPT_NONE;
 		inventory_texture = NULL;
 		is_ground_content = false;
@@ -230,8 +226,8 @@ struct ContentFeatures
 /*
 	Call this to access the ContentFeature list
 */
-ContentFeatures & content_features(u8 i);
-
+ContentFeatures & content_features(content_t i);
+ContentFeatures & content_features(MapNode &n);
 
 /*
 	Here is a bunch of DEPRECATED functions.
@@ -242,7 +238,7 @@ ContentFeatures & content_features(u8 i);
 	in param.
 	NOTE: Don't use, use "content_features(m).whatever" instead
 */
-inline bool light_propagates_content(u8 m)
+inline bool light_propagates_content(content_t m)
 {
 	return content_features(m).light_propagates;
 }
@@ -251,7 +247,7 @@ inline bool light_propagates_content(u8 m)
 	NOTE: It doesn't seem to go through torches regardlessly of this
 	NOTE: Don't use, use "content_features(m).whatever" instead
 */
-inline bool sunlight_propagates_content(u8 m)
+inline bool sunlight_propagates_content(content_t m)
 {
 	return content_features(m).sunlight_propagates;
 }
@@ -263,35 +259,35 @@ inline bool sunlight_propagates_content(u8 m)
 	2: Opaque
 	NOTE: Don't use, use "content_features(m).whatever" instead
 */
-inline u8 content_solidness(u8 m)
+inline u8 content_solidness(content_t m)
 {
 	return content_features(m).solidness;
 }
 // Objects collide with walkable contents
 // NOTE: Don't use, use "content_features(m).whatever" instead
-inline bool content_walkable(u8 m)
+inline bool content_walkable(content_t m)
 {
 	return content_features(m).walkable;
 }
 // NOTE: Don't use, use "content_features(m).whatever" instead
-inline bool content_liquid(u8 m)
+inline bool content_liquid(content_t m)
 {
 	return content_features(m).liquid_type != LIQUID_NONE;
 }
 // NOTE: Don't use, use "content_features(m).whatever" instead
-inline bool content_flowing_liquid(u8 m)
+inline bool content_flowing_liquid(content_t m)
 {
 	return content_features(m).liquid_type == LIQUID_FLOWING;
 }
 // NOTE: Don't use, use "content_features(m).whatever" instead
-inline bool content_liquid_source(u8 m)
+inline bool content_liquid_source(content_t m)
 {
 	return content_features(m).liquid_type == LIQUID_SOURCE;
 }
 // CONTENT_WATER || CONTENT_WATERSOURCE -> CONTENT_WATER
 // CONTENT_LAVA || CONTENT_LAVASOURCE -> CONTENT_LAVA
 // NOTE: Don't use, use "content_features(m).whatever" instead
-inline u8 make_liquid_flowing(u8 m)
+inline content_t make_liquid_flowing(content_t m)
 {
 	u8 c = content_features(m).liquid_alternative_flowing;
 	assert(c != CONTENT_IGNORE);
@@ -299,17 +295,17 @@ inline u8 make_liquid_flowing(u8 m)
 }
 // Pointable contents can be pointed to in the map
 // NOTE: Don't use, use "content_features(m).whatever" instead
-inline bool content_pointable(u8 m)
+inline bool content_pointable(content_t m)
 {
 	return content_features(m).pointable;
 }
 // NOTE: Don't use, use "content_features(m).whatever" instead
-inline bool content_diggable(u8 m)
+inline bool content_diggable(content_t m)
 {
 	return content_features(m).diggable;
 }
 // NOTE: Don't use, use "content_features(m).whatever" instead
-inline bool content_buildable_to(u8 m)
+inline bool content_buildable_to(content_t m)
 {
 	return content_features(m).buildable_to;
 }
@@ -321,7 +317,7 @@ inline bool content_buildable_to(u8 m)
 		1: Face uses m1's content
 		2: Face uses m2's content
 */
-inline u8 face_contents(u8 m1, u8 m2)
+inline u8 face_contents(content_t m1, content_t m2)
 {
 	if(m1 == CONTENT_IGNORE || m2 == CONTENT_IGNORE)
 		return 0;
@@ -425,7 +421,7 @@ struct MapNode
 	union
 	{
 		u8 param0;
-		u8 d;
+		//u8 d;
 	};
 
 	/*
@@ -440,17 +436,18 @@ struct MapNode
 	union
 	{
 		u8 param1;
-		s8 param;
+		//s8 param;
 	};
 	
 	/*
 		The second parameter. Initialized to 0.
 		E.g. direction for torches and flowing water.
+		If param0 >= 0x80, bits 0xf0 of this is extended content type data
 	*/
 	union
 	{
 		u8 param2;
-		u8 dir;
+		//u8 dir;
 	};
 
 	MapNode(const MapNode & n)
@@ -458,28 +455,44 @@ struct MapNode
 		*this = n;
 	}
 	
-	MapNode(u8 data=CONTENT_AIR, u8 a_param=0, u8 a_param2=0)
+	MapNode(content_t content=CONTENT_AIR, u8 a_param1=0, u8 a_param2=0)
 	{
-		d = data;
-		param = a_param;
+		//param0 = a_param0;
+		param1 = a_param1;
 		param2 = a_param2;
+		// Set after other params because this needs to override part of param2
+		setContent(content);
 	}
 
 	bool operator==(const MapNode &other)
 	{
-		return (d == other.d
-				&& param == other.param
+		return (param0 == other.param0
+				&& param1 == other.param1
 				&& param2 == other.param2);
 	}
 	
 	// To be used everywhere
 	content_t getContent()
 	{
-		return d;
+		if(param0 < 0x80)
+			return param0;
+		else
+			return (param0<<4) + (param2>>4);
 	}
 	void setContent(content_t c)
 	{
-		d = c;
+		if(c < 0x80)
+		{
+			if(param0 >= 0x80)
+				param2 &= ~(0xf0);
+			param0 = c;
+		}
+		else
+		{
+			param0 = c>>4;
+			param2 &= ~(0xf0);
+			param2 |= (c&0x0f)<<4;
+		}
 	}
 	
 	/*
@@ -487,19 +500,19 @@ struct MapNode
 	*/
 	bool light_propagates()
 	{
-		return light_propagates_content(d);
+		return light_propagates_content(getContent());
 	}
 	bool sunlight_propagates()
 	{
-		return sunlight_propagates_content(d);
+		return sunlight_propagates_content(getContent());
 	}
 	u8 solidness()
 	{
-		return content_solidness(d);
+		return content_solidness(getContent());
 	}
 	u8 light_source()
 	{
-		return content_features(d).light_source;
+		return content_features(*this).light_source;
 	}
 
 	u8 getLightBanksWithSource()
@@ -507,10 +520,10 @@ struct MapNode
 		// Select the brightest of [light source, propagated light]
 		u8 lightday = 0;
 		u8 lightnight = 0;
-		if(content_features(d).param_type == CPT_LIGHT)
+		if(content_features(*this).param_type == CPT_LIGHT)
 		{
-			lightday = param & 0x0f;
-			lightnight = (param>>4)&0x0f;
+			lightday = param1 & 0x0f;
+			lightnight = (param1>>4)&0x0f;
 		}
 		if(light_source() > lightday)
 			lightday = light_source();
@@ -523,12 +536,12 @@ struct MapNode
 	{
 		// Select the brightest of [light source, propagated light]
 		u8 light = 0;
-		if(content_features(d).param_type == CPT_LIGHT)
+		if(content_features(*this).param_type == CPT_LIGHT)
 		{
 			if(bank == LIGHTBANK_DAY)
-				light = param & 0x0f;
+				light = param1 & 0x0f;
 			else if(bank == LIGHTBANK_NIGHT)
-				light = (param>>4)&0x0f;
+				light = (param1>>4)&0x0f;
 			else
 				assert(0);
 		}
@@ -566,17 +579,17 @@ struct MapNode
 	void setLight(enum LightBank bank, u8 a_light)
 	{
 		// If node doesn't contain light data, ignore this
-		if(content_features(d).param_type != CPT_LIGHT)
+		if(content_features(*this).param_type != CPT_LIGHT)
 			return;
 		if(bank == LIGHTBANK_DAY)
 		{
-			param &= 0xf0;
-			param |= a_light & 0x0f;
+			param1 &= 0xf0;
+			param1 |= a_light & 0x0f;
 		}
 		else if(bank == LIGHTBANK_NIGHT)
 		{
-			param &= 0x0f;
-			param |= (a_light & 0x0f)<<4;
+			param1 &= 0x0f;
+			param1 |= (a_light & 0x0f)<<4;
 		}
 		else
 			assert(0);
diff --git a/src/materials.cpp b/src/materials.cpp
index e3a24b9e34f8d16d58b5da7396e70ceabf3472cc..24f30072435915a6f701d4404b1b703b40415371 100644
--- a/src/materials.cpp
+++ b/src/materials.cpp
@@ -3,12 +3,12 @@
 
 // NOTE: DEPRECATED
 
-DiggingPropertiesList * getDiggingPropertiesList(u8 content)
+DiggingPropertiesList * getDiggingPropertiesList(u16 content)
 {
 	return &content_features(content).digging_properties;
 }
 
-DiggingProperties getDiggingProperties(u8 content, const std::string &tool)
+DiggingProperties getDiggingProperties(u16 content, const std::string &tool)
 {
 	DiggingPropertiesList *mprop = getDiggingPropertiesList(content);
 	if(mprop == NULL)
diff --git a/src/materials.h b/src/materials.h
index f061ecbfa5eb7075c5fe0e4463fad10ff6279301..1439df194c27ea892e20d9e15ee2170fd47e2bd6 100644
--- a/src/materials.h
+++ b/src/materials.h
@@ -97,7 +97,7 @@ class DiggingPropertiesList
 };
 
 // For getting the default properties, set tool=""
-DiggingProperties getDiggingProperties(u8 material, const std::string &tool);
+DiggingProperties getDiggingProperties(u16 material, const std::string &tool);
 
 #endif
 
diff --git a/src/player.cpp b/src/player.cpp
index 6bacb088d79578cfc493b1d569cafa51793e8738..d52d6b88f032937505e4de950e7576739f2c4b2e 100644
--- a/src/player.cpp
+++ b/src/player.cpp
@@ -342,13 +342,13 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d,
 		if(in_water)
 		{
 			v3s16 pp = floatToInt(position + v3f(0,BS*0.1,0), BS);
-			in_water = content_liquid(map.getNode(pp).d);
+			in_water = content_liquid(map.getNode(pp).getContent());
 		}
 		// If not in water, the threshold of going in is at lower y
 		else
 		{
 			v3s16 pp = floatToInt(position + v3f(0,BS*0.5,0), BS);
-			in_water = content_liquid(map.getNode(pp).d);
+			in_water = content_liquid(map.getNode(pp).getContent());
 		}
 	}
 	catch(InvalidPositionException &e)
@@ -361,7 +361,7 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d,
 	*/
 	try{
 		v3s16 pp = floatToInt(position + v3f(0,0,0), BS);
-		in_water_stable = content_liquid(map.getNode(pp).d);
+		in_water_stable = content_liquid(map.getNode(pp).getContent());
 	}
 	catch(InvalidPositionException &e)
 	{
@@ -470,7 +470,7 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d,
 	{
 		try{
 			// Player collides into walkable nodes
-			if(content_walkable(map.getNode(v3s16(x,y,z)).d) == false)
+			if(content_walkable(map.getNode(v3s16(x,y,z)).getContent()) == false)
 				continue;
 		}
 		catch(InvalidPositionException &e)
@@ -633,10 +633,10 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d,
 
 			try{
 				// The node to be sneaked on has to be walkable
-				if(content_walkable(map.getNode(p).d) == false)
+				if(content_walkable(map.getNode(p).getContent()) == false)
 					continue;
 				// And the node above it has to be nonwalkable
-				if(content_walkable(map.getNode(p+v3s16(0,1,0)).d) == true)
+				if(content_walkable(map.getNode(p+v3s16(0,1,0)).getContent()) == true)
 					continue;
 			}
 			catch(InvalidPositionException &e)
diff --git a/src/serialization.h b/src/serialization.h
index 974ae95d8aa8392dabf4d31668ee16bf5256c996..d93e6189299e5dd147e36756c89ef4d7ea0a9370 100644
--- a/src/serialization.h
+++ b/src/serialization.h
@@ -55,11 +55,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 	17: MapBlocks contain timestamp
 	18: new generator (not really necessary, but it's there)
 	19: new content type handling
+	20: many existing content types translated to extended ones
 */
 // This represents an uninitialized or invalid format
 #define SER_FMT_VER_INVALID 255
 // Highest supported serialization version
-#define SER_FMT_VER_HIGHEST 19
+#define SER_FMT_VER_HIGHEST 20
 // Lowest supported serialization version
 #define SER_FMT_VER_LOWEST 0
 
diff --git a/src/server.cpp b/src/server.cpp
index c2433e1af033d9fdf6525f3abf60620c2f9170bd..e2e6ce46b04ceb37dd5a8350b4a58e8ad7473988 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -105,10 +105,10 @@ void * EmergeThread::Thread()
 
 	DSTACK(__FUNCTION_NAME);
 
-	//bool debug=false;
-	
 	BEGIN_DEBUG_EXCEPTION_HANDLER
 
+	bool enable_mapgen_debug_info = g_settings.getBool("enable_mapgen_debug_info");
+	
 	/*
 		Get block info from queue, emerge them and send them
 		to clients.
@@ -155,7 +155,7 @@ void * EmergeThread::Thread()
 			Also decrement the emerge queue count in clients.
 		*/
 
-		bool optional = true;
+		bool only_from_disk = true;
 
 		{
 			core::map<u16, u8>::Iterator i;
@@ -166,14 +166,15 @@ void * EmergeThread::Thread()
 				// Check flags
 				u8 flags = i.getNode()->getValue();
 				if((flags & BLOCK_EMERGE_FLAG_FROMDISK) == false)
-					optional = false;
+					only_from_disk = false;
 				
 			}
 		}
-
-		/*dstream<<"EmergeThread: p="
-				<<"("<<p.X<<","<<p.Y<<","<<p.Z<<") "
-				<<"optional="<<optional<<std::endl;*/
+		
+		if(enable_mapgen_debug_info)
+			dstream<<"EmergeThread: p="
+					<<"("<<p.X<<","<<p.Y<<","<<p.Z<<") "
+					<<"only_from_disk="<<only_from_disk<<std::endl;
 		
 		ServerMap &map = ((ServerMap&)m_server->m_env.getMap());
 			
@@ -184,11 +185,6 @@ void * EmergeThread::Thread()
 		bool got_block = true;
 		core::map<v3s16, MapBlock*> modified_blocks;
 		
-		bool only_from_disk = false;
-		
-		if(optional)
-			only_from_disk = true;
-
 		/*
 			Fetch block from map or generate a single block
 		*/
@@ -203,6 +199,9 @@ void * EmergeThread::Thread()
 			block = map.getBlockNoCreateNoEx(p);
 			if(!block || block->isDummy() || !block->isGenerated())
 			{
+				if(enable_mapgen_debug_info)
+					dstream<<"EmergeThread: not in memory, loading"<<std::endl;
+
 				// Get, load or create sector
 				/*ServerMapSector *sector =
 						(ServerMapSector*)map.createSector(p2d);*/
@@ -213,12 +212,20 @@ void * EmergeThread::Thread()
 						lighting_invalidated_blocks);*/
 
 				block = map.loadBlock(p);
+				
+				if(only_from_disk == false)
+				{
+					if(block == NULL || block->isGenerated() == false)
+					{
+						if(enable_mapgen_debug_info)
+							dstream<<"EmergeThread: generating"<<std::endl;
+						block = map.generateBlock(p, modified_blocks);
+					}
+				}
 
-				if(block == NULL && only_from_disk == false)
-					block = map.generateBlock(p, modified_blocks);
-					//block = map.generateBlock(p, changed_blocks);
-					/*block = map.generateBlock(p, block, sector, changed_blocks,
-							lighting_invalidated_blocks);*/
+				if(enable_mapgen_debug_info)
+					dstream<<"EmergeThread: ended up with: "
+							<<analyze_block(block)<<std::endl;
 
 				if(block == NULL)
 				{
@@ -2494,7 +2501,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 			// Mandatory parameter; actually used for nothing
 			core::map<v3s16, MapBlock*> modified_blocks;
 
-			u8 material = CONTENT_IGNORE;
+			content_t material = CONTENT_IGNORE;
 			u8 mineral = MINERAL_NONE;
 
 			bool cannot_remove_node = false;
@@ -2505,7 +2512,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 				// Get mineral
 				mineral = n.getMineral();
 				// Get material at position
-				material = n.d;
+				material = n.getContent();
 				// If not yet cancelled
 				if(cannot_remove_node == false)
 				{
@@ -2705,7 +2712,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 							<<" because privileges are "<<getPlayerPrivs(player)
 							<<std::endl;
 
-					if(content_buildable_to(n2.d) == false
+					if(content_features(n2).buildable_to == false
 						|| no_enough_privs)
 					{
 						// Client probably has wrong data.
@@ -2736,14 +2743,14 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 				// Create node data
 				MaterialItem *mitem = (MaterialItem*)item;
 				MapNode n;
-				n.d = mitem->getMaterial();
+				n.setContent(mitem->getMaterial());
 
 				// Calculate direction for wall mounted stuff
-				if(content_features(n.d).wall_mounted)
-					n.dir = packDir(p_under - p_over);
+				if(content_features(n).wall_mounted)
+					n.param2 = packDir(p_under - p_over);
 
 				// Calculate the direction for furnaces and chests and stuff
-				if(content_features(n.d).param_type == CPT_FACEDIR_SIMPLE)
+				if(content_features(n).param_type == CPT_FACEDIR_SIMPLE)
 				{
 					v3f playerpos = player->getPosition();
 					v3f blockpos = intToFloat(p_over, BS) - playerpos;
diff --git a/src/test.cpp b/src/test.cpp
index 0487f1436a3aeea49d52872ac71e54c6f1e76aff..3ff4dc8071d6ae41baf6b45a2a93a2e366458f07 100644
--- a/src/test.cpp
+++ b/src/test.cpp
@@ -219,14 +219,14 @@ struct TestMapNode
 		MapNode n;
 
 		// Default values
-		assert(n.d == CONTENT_AIR);
+		assert(n.getContent() == CONTENT_AIR);
 		assert(n.getLight(LIGHTBANK_DAY) == 0);
 		assert(n.getLight(LIGHTBANK_NIGHT) == 0);
 		
 		// Transparency
-		n.d = CONTENT_AIR;
+		n.setContent(CONTENT_AIR);
 		assert(n.light_propagates() == true);
-		n.d = CONTENT_STONE;
+		n.setContent(CONTENT_STONE);
 		assert(n.light_propagates() == false);
 	}
 };
@@ -284,7 +284,7 @@ struct TestVoxelManipulator
 
 		v.print(dstream);
 
- 		assert(v.getNode(v3s16(-1,0,-1)).d == 2);
+ 		assert(v.getNode(v3s16(-1,0,-1)).getContent() == 2);
 
 		dstream<<"*** Reading from inexistent (0,0,-1) ***"<<std::endl;
 
@@ -298,7 +298,7 @@ struct TestVoxelManipulator
 		
 		v.print(dstream);
 
-		assert(v.getNode(v3s16(-1,0,-1)).d == 2);
+		assert(v.getNode(v3s16(-1,0,-1)).getContent() == 2);
 		EXCEPTION_CHECK(InvalidPositionException, v.getNode(v3s16(0,1,1)));
 
 #if 0
@@ -331,11 +331,11 @@ struct TestVoxelManipulator
 			MapNode n;
 			//n.pressure = size.Y - y;
 			if(*p == '#')
-				n.d = CONTENT_STONE;
+				n.setContent(CONTENT_STONE);
 			else if(*p == '.')
-				n.d = CONTENT_WATER;
+				n.setContent(CONTENT_WATER);
 			else if(*p == ' ')
-				n.d = CONTENT_AIR;
+				n.setContent(CONTENT_AIR);
 			else
 				assert(0);
 			v.setNode(v3s16(x,y,z), n);
@@ -469,8 +469,8 @@ struct TestMapBlock
 		for(u16 y=0; y<MAP_BLOCKSIZE; y++)
 		for(u16 x=0; x<MAP_BLOCKSIZE; x++)
 		{
-			//assert(b.getNode(v3s16(x,y,z)).d == CONTENT_AIR);
-			assert(b.getNode(v3s16(x,y,z)).d == CONTENT_IGNORE);
+			//assert(b.getNode(v3s16(x,y,z)).getContent() == CONTENT_AIR);
+			assert(b.getNode(v3s16(x,y,z)).getContent() == CONTENT_IGNORE);
 			assert(b.getNode(v3s16(x,y,z)).getLight(LIGHTBANK_DAY) == 0);
 			assert(b.getNode(v3s16(x,y,z)).getLight(LIGHTBANK_NIGHT) == 0);
 		}
@@ -489,7 +489,7 @@ struct TestMapBlock
 			Parent fetch functions
 		*/
 		parent.position_valid = false;
-		parent.node.d = 5;
+		parent.node.setContent(5);
 
 		MapNode n;
 		
@@ -497,7 +497,7 @@ struct TestMapBlock
 		assert(b.isValidPositionParent(v3s16(0,0,0)) == true);
 		assert(b.isValidPositionParent(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1)) == true);
 		n = b.getNodeParent(v3s16(0,MAP_BLOCKSIZE-1,0));
-		assert(n.d == CONTENT_AIR);
+		assert(n.getContent() == CONTENT_AIR);
 
 		// ...but outside the block they should be invalid
 		assert(b.isValidPositionParent(v3s16(-121,2341,0)) == false);
@@ -523,15 +523,15 @@ struct TestMapBlock
 		assert(b.isValidPositionParent(v3s16(-1,0,0)) == true);
 		assert(b.isValidPositionParent(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE)) == true);
 		n = b.getNodeParent(v3s16(0,0,MAP_BLOCKSIZE));
-		assert(n.d == 5);
+		assert(n.getContent() == 5);
 
 		/*
 			Set a node
 		*/
 		v3s16 p(1,2,0);
-		n.d = 4;
+		n.setContent(4);
 		b.setNode(p, n);
-		assert(b.getNode(p).d == 4);
+		assert(b.getNode(p).getContent() == 4);
 		//TODO: Update to new system
 		/*assert(b.getNodeTile(p) == 4);
 		assert(b.getNodeTile(v3s16(-1,-1,0)) == 5);*/
@@ -556,7 +556,7 @@ struct TestMapBlock
 			*/
 			parent.position_valid = true;
 			b.setIsUnderground(false);
-			parent.node.d = CONTENT_AIR;
+			parent.node.setContent(CONTENT_AIR);
 			parent.node.setLight(LIGHTBANK_DAY, LIGHT_SUN);
 			parent.node.setLight(LIGHTBANK_NIGHT, 0);
 			core::map<v3s16, bool> light_sources;
@@ -611,7 +611,7 @@ struct TestMapBlock
 				for(u16 y=0; y<MAP_BLOCKSIZE; y++){
 					for(u16 x=0; x<MAP_BLOCKSIZE; x++){
 						MapNode n;
-						n.d = CONTENT_AIR;
+						n.setContent(CONTENT_AIR);
 						n.setLight(LIGHTBANK_DAY, 0);
 						b.setNode(v3s16(x,y,z), n);
 					}
diff --git a/src/tile.cpp b/src/tile.cpp
index 23fa1129da78bca05411aadacdacca0f2f99816e..9f6a6eb72f57e68b194408ec846c9233e8b9f1c3 100644
--- a/src/tile.cpp
+++ b/src/tile.cpp
@@ -179,7 +179,7 @@ void TextureSource::processQueue()
 
 		dstream<<"INFO: TextureSource::processQueue(): "
 				<<"got texture request with "
-				<<"name="<<request.key
+				<<"name=\""<<request.key<<"\""
 				<<std::endl;
 
 		GetResult<std::string, u32, u8, u8>
@@ -194,7 +194,7 @@ void TextureSource::processQueue()
 
 u32 TextureSource::getTextureId(const std::string &name)
 {
-	//dstream<<"INFO: getTextureId(): name="<<name<<std::endl;
+	//dstream<<"INFO: getTextureId(): \""<<name<<"\""<<std::endl;
 
 	{
 		/*
@@ -218,7 +218,7 @@ u32 TextureSource::getTextureId(const std::string &name)
 	}
 	else
 	{
-		dstream<<"INFO: getTextureId(): Queued: name="<<name<<std::endl;
+		dstream<<"INFO: getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
 
 		// We're gonna ask the result to be put into here
 		ResultQueue<std::string, u32, u8, u8> result_queue;
@@ -226,8 +226,8 @@ u32 TextureSource::getTextureId(const std::string &name)
 		// Throw a request in
 		m_get_texture_queue.add(name, 0, 0, &result_queue);
 		
-		dstream<<"INFO: Waiting for texture from main thread, name="
-				<<name<<std::endl;
+		dstream<<"INFO: Waiting for texture from main thread, name=\""
+				<<name<<"\""<<std::endl;
 		
 		try
 		{
@@ -276,7 +276,7 @@ video::IImage* generate_image_from_scratch(std::string name,
 */
 u32 TextureSource::getTextureIdDirect(const std::string &name)
 {
-	dstream<<"INFO: getTextureIdDirect(): name="<<name<<std::endl;
+	//dstream<<"INFO: getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
 
 	// Empty name means texture 0
 	if(name == "")
@@ -305,14 +305,14 @@ u32 TextureSource::getTextureIdDirect(const std::string &name)
 		n = m_name_to_id.find(name);
 		if(n != NULL)
 		{
-			dstream<<"INFO: getTextureIdDirect(): name="<<name
-					<<" found in cache"<<std::endl;
+			dstream<<"INFO: getTextureIdDirect(): \""<<name
+					<<"\" found in cache"<<std::endl;
 			return n->getValue();
 		}
 	}
 
-	dstream<<"INFO: getTextureIdDirect(): name="<<name
-			<<" NOT found in cache. Creating it."<<std::endl;
+	dstream<<"INFO: getTextureIdDirect(): \""<<name
+			<<"\" NOT found in cache. Creating it."<<std::endl;
 	
 	/*
 		Get the base image
@@ -346,12 +346,13 @@ u32 TextureSource::getTextureIdDirect(const std::string &name)
 	{
 		// Construct base name
 		base_image_name = name.substr(0, last_separator_position);
-		dstream<<"INFO: getTextureIdDirect(): Calling itself recursively"
-				" to get base image, name="<<base_image_name<<std::endl;
+		/*dstream<<"INFO: getTextureIdDirect(): Calling itself recursively"
+				" to get base image of \""<<name<<"\" = \""
+                <<base_image_name<<"\""<<std::endl;*/
 		base_image_id = getTextureIdDirect(base_image_name);
 	}
 	
-	dstream<<"base_image_id="<<base_image_id<<std::endl;
+	//dstream<<"base_image_id="<<base_image_id<<std::endl;
 	
 	video::IVideoDriver* driver = m_device->getVideoDriver();
 	assert(driver);
@@ -393,9 +394,9 @@ u32 TextureSource::getTextureIdDirect(const std::string &name)
 					core::rect<s32>(pos_from, dim) // from
 			);
 
-			dstream<<"INFO: getTextureIdDirect(): Loaded \""
+			/*dstream<<"INFO: getTextureIdDirect(): Loaded \""
 					<<base_image_name<<"\" from image cache"
-					<<std::endl;
+					<<std::endl;*/
 		}
 	}
 	
@@ -405,7 +406,7 @@ u32 TextureSource::getTextureIdDirect(const std::string &name)
 	*/
 
 	std::string last_part_of_name = name.substr(last_separator_position+1);
-	dstream<<"last_part_of_name="<<last_part_of_name<<std::endl;
+	//dstream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
 
 	// Generate image according to part of name
 	if(generate_image(last_part_of_name, baseimg, m_device) == false)
@@ -447,8 +448,8 @@ u32 TextureSource::getTextureIdDirect(const std::string &name)
 	m_atlaspointer_cache.push_back(nap);
 	m_name_to_id.insert(name, id);
 
-	dstream<<"INFO: getTextureIdDirect(): name="<<name
-			<<": succesfully returning id="<<id<<std::endl;
+	/*dstream<<"INFO: getTextureIdDirect(): "
+			<<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
 	
 	return id;
 }
@@ -517,13 +518,11 @@ void TextureSource::buildMainAtlas()
 	sourcelist.push_back("cobble.png");
 	sourcelist.push_back("mossycobble.png");
 	sourcelist.push_back("gravel.png");
+	sourcelist.push_back("cactus.png");
+	sourcelist.push_back("jungletree.png");
 	
 	sourcelist.push_back("stone.png^mineral_coal.png");
 	sourcelist.push_back("stone.png^mineral_iron.png");
-	sourcelist.push_back("mud.png^mineral_coal.png");
-	sourcelist.push_back("mud.png^mineral_iron.png");
-	sourcelist.push_back("sand.png^mineral_coal.png");
-	sourcelist.push_back("sand.png^mineral_iron.png");
 	
 	// Padding to disallow texture bleeding
 	s32 padding = 16;
@@ -580,6 +579,9 @@ void TextureSource::buildMainAtlas()
 			break;
 		}
 		
+        dstream<<"INFO: TextureSource::buildMainAtlas(): Adding \""<<name
+                <<"\" to texture atlas"<<std::endl;
+
 		// Tile it a few times in the X direction
 		u16 xwise_tiling = 16;
 		for(u32 j=0; j<xwise_tiling; j++)
@@ -670,8 +672,8 @@ void TextureSource::buildMainAtlas()
 video::IImage* generate_image_from_scratch(std::string name,
 		IrrlichtDevice *device)
 {
-	dstream<<"INFO: generate_image_from_scratch(): "
-			"name="<<name<<std::endl;
+	/*dstream<<"INFO: generate_image_from_scratch(): "
+			"\""<<name<<"\""<<std::endl;*/
 	
 	video::IVideoDriver* driver = device->getVideoDriver();
 	assert(driver);
@@ -708,8 +710,9 @@ video::IImage* generate_image_from_scratch(std::string name,
 	{
 		// Construct base name
 		base_image_name = name.substr(0, last_separator_position);
-		dstream<<"INFO: generate_image_from_scratch(): Calling itself recursively"
-				" to get base image, name="<<base_image_name<<std::endl;
+		/*dstream<<"INFO: generate_image_from_scratch(): Calling itself recursively"
+				" to get base image of \""<<name<<"\" = \""
+                <<base_image_name<<"\""<<std::endl;*/
 		baseimg = generate_image_from_scratch(base_image_name, device);
 	}
 	
@@ -719,7 +722,7 @@ video::IImage* generate_image_from_scratch(std::string name,
 	*/
 
 	std::string last_part_of_name = name.substr(last_separator_position+1);
-	dstream<<"last_part_of_name="<<last_part_of_name<<std::endl;
+	//dstream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
 	
 	// Generate image according to part of name
 	if(generate_image(last_part_of_name, baseimg, device) == false)
@@ -744,21 +747,21 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
 	{
 		// A normal texture; load it from a file
 		std::string path = getTexturePath(part_of_name.c_str());
-		dstream<<"INFO: getTextureIdDirect(): Loading path \""<<path
-				<<"\""<<std::endl;
+		/*dstream<<"INFO: generate_image(): Loading path \""<<path
+				<<"\""<<std::endl;*/
 		
 		video::IImage *image = driver->createImageFromFile(path.c_str());
 
 		if(image == NULL)
 		{
-			dstream<<"WARNING: Could not load image \""<<part_of_name
-					<<"\" from path \""<<path<<"\""
+			dstream<<"WARNING: generate_image(): Could not load image \""
+                    <<part_of_name<<"\" from path \""<<path<<"\""
 					<<" while building texture"<<std::endl;
 
 			//return false;
 
-			dstream<<"WARNING: Creating a dummy"<<" image for \""
-					<<part_of_name<<"\""<<std::endl;
+			dstream<<"WARNING: generate_image(): Creating a dummy"
+                    <<" image for \""<<part_of_name<<"\""<<std::endl;
 
 			// Just create a dummy image
 			//core::dimension2d<u32> dim(2,2);
@@ -782,7 +785,7 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
 		// If base image is NULL, load as base.
 		if(baseimg == NULL)
 		{
-			dstream<<"INFO: Setting "<<part_of_name<<" as base"<<std::endl;
+			//dstream<<"INFO: Setting "<<part_of_name<<" as base"<<std::endl;
 			/*
 				Copy it this way to get an alpha channel.
 				Otherwise images with alpha cannot be blitted on 
@@ -796,7 +799,7 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
 		// Else blit on base.
 		else
 		{
-			dstream<<"INFO: Blitting "<<part_of_name<<" on base"<<std::endl;
+			//dstream<<"INFO: Blitting "<<part_of_name<<" on base"<<std::endl;
 			// Size of the copied area
 			core::dimension2d<u32> dim = image->getDimension();
 			//core::dimension2d<u32> dim(16,16);
@@ -817,7 +820,7 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
 	{
 		// A special texture modification
 
-		dstream<<"INFO: getTextureIdDirect(): generating special "
+		dstream<<"INFO: generate_image(): generating special "
 				<<"modification \""<<part_of_name<<"\""
 				<<std::endl;
 		
@@ -840,9 +843,9 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
 		{
 			if(baseimg == NULL)
 			{
-				dstream<<"WARNING: getTextureIdDirect(): baseimg==NULL "
-						<<"for part_of_name="<<part_of_name
-						<<", cancelling."<<std::endl;
+				dstream<<"WARNING: generate_image(): baseimg==NULL "
+						<<"for part_of_name=\""<<part_of_name
+						<<"\", cancelling."<<std::endl;
 				return false;
 			}
 			
@@ -977,9 +980,9 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
 		{
 			if(baseimg == NULL)
 			{
-				dstream<<"WARNING: getTextureIdDirect(): baseimg==NULL "
-						<<"for part_of_name="<<part_of_name
-						<<", cancelling."<<std::endl;
+				dstream<<"WARNING: generate_image(): baseimg==NULL "
+						<<"for part_of_name=\""<<part_of_name
+						<<"\", cancelling."<<std::endl;
 				return false;
 			}
 
@@ -997,9 +1000,9 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
 		{
 			if(baseimg != NULL)
 			{
-				dstream<<"WARNING: getTextureIdDirect(): baseimg!=NULL "
-						<<"for part_of_name="<<part_of_name
-						<<", cancelling."<<std::endl;
+				dstream<<"WARNING: generate_image(): baseimg!=NULL "
+						<<"for part_of_name=\""<<part_of_name
+						<<"\", cancelling."<<std::endl;
 				return false;
 			}
 
@@ -1007,14 +1010,14 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
 
 			std::string path = getTexturePath(filename.c_str());
 
-			dstream<<"INFO: getTextureIdDirect(): Loading path \""<<path
+			dstream<<"INFO: generate_image(): Loading path \""<<path
 					<<"\""<<std::endl;
 			
 			video::IImage *image = driver->createImageFromFile(path.c_str());
 			
 			if(image == NULL)
 			{
-				dstream<<"WARNING: getTextureIdDirect(): Loading path \""
+				dstream<<"WARNING: generate_image(): Loading path \""
 						<<path<<"\" failed"<<std::endl;
 			}
 			else
@@ -1048,9 +1051,9 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
 		{
 			if(baseimg != NULL)
 			{
-				dstream<<"WARNING: getTextureIdDirect(): baseimg!=NULL "
-						<<"for part_of_name="<<part_of_name
-						<<", cancelling."<<std::endl;
+				dstream<<"WARNING: generate_image(): baseimg!=NULL "
+						<<"for part_of_name=\""<<part_of_name
+						<<"\", cancelling."<<std::endl;
 				return false;
 			}
 
@@ -1066,7 +1069,7 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
 
 			if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
 			{
-				dstream<<"WARNING: getTextureIdDirect(): EVDF_RENDER_TO_TARGET"
+				dstream<<"WARNING: generate_image(): EVDF_RENDER_TO_TARGET"
 						" not supported. Creating fallback image"<<std::endl;
 				baseimg = generate_image_from_scratch(
 						imagename_top, device);
@@ -1075,7 +1078,7 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
 			
 			u32 w0 = 64;
 			u32 h0 = 64;
-			dstream<<"INFO: inventorycube w="<<w0<<" h="<<h0<<std::endl;
+			//dstream<<"INFO: inventorycube w="<<w0<<" h="<<h0<<std::endl;
 			core::dimension2d<u32> dim(w0,h0);
 			
 			// Generate images for the faces of the cube
@@ -1177,7 +1180,7 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
 		}
 		else
 		{
-			dstream<<"WARNING: getTextureIdDirect(): Invalid "
+			dstream<<"WARNING: generate_image(): Invalid "
 					" modification: \""<<part_of_name<<"\""<<std::endl;
 		}
 	}
diff --git a/src/voxel.cpp b/src/voxel.cpp
index 5938f9016d7e202d161cc9c5d722c2870e9cf4ad..616a197e3abfdd79afc77ac2417201d36f596da8 100644
--- a/src/voxel.cpp
+++ b/src/voxel.cpp
@@ -93,7 +93,7 @@ void VoxelManipulator::print(std::ostream &o, VoxelPrintMode mode)
 				else
 				{
 					c = 'X';
-					u8 m = m_data[m_area.index(x,y,z)].d;
+					content_t m = m_data[m_area.index(x,y,z)].getContent();
 					u8 pr = m_data[m_area.index(x,y,z)].param2;
 					if(mode == VOXELPRINT_MATERIAL)
 					{
diff --git a/util/minetestmapper.py b/util/minetestmapper.py
index 5f3aab456cdcdc2b2fffc9b855cc4b156ca4b3c3..162ceb204476f1b6247a70fbc05e3bdd70489498 100755
--- a/util/minetestmapper.py
+++ b/util/minetestmapper.py
@@ -170,6 +170,9 @@ stuff = {}
 
 starttime = time.time()
 
+def data_is_air(d):
+	return (d == 254 or d == 126)
+
 # Go through all sectors.
 for n in range(len(xlist)):
 	#if n > 500:
@@ -283,7 +286,7 @@ for n in range(len(xlist)):
 			for (x, z) in reversed(pixellist):
 				for y in reversed(range(16)):
 					datapos = x + y * 16 + z * 256
-					if(ord(mapdata[datapos]) != 254 and ord(mapdata[datapos]) in colors):
+					if(not data_is_air(ord(mapdata[datapos])) and ord(mapdata[datapos]) in colors):
 						if(ord(mapdata[datapos]) == 2 or ord(mapdata[datapos]) == 9):
 							water[(x, z)] += 1
 							# Add dummy stuff for drawing sea without seabed
@@ -293,7 +296,7 @@ for n in range(len(xlist)):
 							# Memorize information on the type and height of the block and for drawing the picture.
 							stuff[(chunkxpos + x, chunkzpos + z)] = (chunkypos + y, ord(mapdata[datapos]), water[(x, z)])
 							break
-					elif(ord(mapdata[datapos]) != 254 and ord(mapdata[datapos]) not in colors):
+					elif(not data_is_air(ord(mapdata[datapos])) and ord(mapdata[datapos]) not in colors):
 						print "strange block: " + xhex + "/" + zhex + "/" + yhex + " x: " + str(x) + " y: " + str(y) + " z: " + str(z) + " palikka: " + str(ord(mapdata[datapos]))
 		
 		# After finding all the pixels in the sector, we can move on to the next sector without having to continue the Y axis.
@@ -324,7 +327,7 @@ for n in range(len(xlist)):
 				for (x, z) in reversed(pixellist):
 					for y in reversed(range(16)):
 						datapos = x + y * 16 + z * 256
-						if(ord(mapdata[datapos]) != 254 and ord(mapdata[datapos]) in colors):
+						if(not data_is_air(ord(mapdata[datapos])) and ord(mapdata[datapos]) in colors):
 							if(ord(mapdata[datapos]) == 2 or ord(mapdata[datapos]) == 9):
 								water[(x, z)] += 1
 								# Add dummy stuff for drawing sea without seabed
@@ -334,7 +337,7 @@ for n in range(len(xlist)):
 								# Memorize information on the type and height of the block and for drawing the picture.
 								stuff[(chunkxpos + x, chunkzpos + z)] = (chunkypos + y, ord(mapdata[datapos]), water[(x, z)])
 								break
-						elif(ord(mapdata[datapos]) != 254 and ord(mapdata[datapos]) not in colors):
+						elif(not data_is_air(ord(mapdata[datapos])) and ord(mapdata[datapos]) not in colors):
 							print "outo palikka: " + xhex + "/" + zhex + "/" + yhex + " x: " + str(x) + " y: " + str(y) + " z: " + str(z) + " palikka: " + str(ord(mapdata[datapos]))
 			
 			# After finding all the pixels in the sector, we can move on to the next sector without having to continue the Y axis.