From 571fb14f9480a0fd70d7cfeb0484c9513153c33a Mon Sep 17 00:00:00 2001
From: Perttu Ahola <celeron55@gmail.com>
Date: Mon, 13 Dec 2010 03:19:12 +0200
Subject: [PATCH] working nicely

---
 makepackage_binary.sh  |   6 +
 minetest.conf.example  |   5 +-
 src/client.cpp         |   1 +
 src/environment.cpp    |   4 +-
 src/light.cpp          |  43 +++++++
 src/main.cpp           | 211 +++++++++++++++++++++++---------
 src/main.h             |   4 +-
 src/map.cpp            | 182 ++++++++++++++++++++++------
 src/mapblock.cpp       | 157 +++++++++++++++++++-----
 src/mapblock.h         |   9 +-
 src/mapblockobject.cpp |   2 +-
 src/mapblockobject.h   |  64 ++++++++--
 src/mapnode.h          | 207 ++++++++++++++++++++-----------
 src/player.cpp         |   6 +-
 src/server.cpp         | 268 +++++++++++++++++++----------------------
 src/server.h           |  12 +-
 src/test.cpp           |  32 ++---
 src/utility.h          |  42 +++++++
 src/voxel.cpp          |  73 ++++++++---
 src/voxel.h            |   6 +
 20 files changed, 938 insertions(+), 396 deletions(-)

diff --git a/makepackage_binary.sh b/makepackage_binary.sh
index 38a301294..587ba3a63 100755
--- a/makepackage_binary.sh
+++ b/makepackage_binary.sh
@@ -33,6 +33,12 @@ cp -r data/sign.png $PACKAGEPATH/data/
 cp -r data/sign_back.png $PACKAGEPATH/data/
 cp -r data/rat.png $PACKAGEPATH/data/
 cp -r data/mud.png $PACKAGEPATH/data/
+cp -r data/torch.png $PACKAGEPATH/data/
+cp -r data/torch_floor.png $PACKAGEPATH/data/
+cp -r data/torch_ceiling.png $PACKAGEPATH/data/
+cp -r data/skybox1.png $PACKAGEPATH/data/
+cp -r data/skybox2.png $PACKAGEPATH/data/
+cp -r data/skybox3.png $PACKAGEPATH/data/
 
 cp -r doc/README.txt $PACKAGEPATH/doc/README.txt
 
diff --git a/minetest.conf.example b/minetest.conf.example
index 1d2606ec2..1ba9a5ba9 100644
--- a/minetest.conf.example
+++ b/minetest.conf.example
@@ -54,5 +54,8 @@
 #max_simultaneous_block_sends_server_total = 4
 
 #max_block_send_distance = 8
-#max_block_generate_distance = 5
+#max_block_generate_distance = 6
+
+#disable_water_climb = true
+#endless_water = true
 
diff --git a/src/client.cpp b/src/client.cpp
index 8a9c688dd..2471558f8 100644
--- a/src/client.cpp
+++ b/src/client.cpp
@@ -1651,6 +1651,7 @@ MapBlockObject * Client::getSelectedObject(
 		v3f from_pos_f_on_block = from_pos_f_on_map - block_pos_f_on_map;
 
 		block->getObjects(from_pos_f_on_block, max_d, objects);
+		//block->getPseudoObjects(from_pos_f_on_block, max_d, objects);
 	}
 
 	//dstream<<"Collected "<<objects.size()<<" nearby objects"<<std::endl;
diff --git a/src/environment.cpp b/src/environment.cpp
index 2d7590f1a..1f4223b23 100644
--- a/src/environment.cpp
+++ b/src/environment.cpp
@@ -142,9 +142,9 @@ void Environment::step(float dtime)
 			v3s16 bottompos = floatToInt(playerpos + v3f(0,-BS/4,0));
 			try{
 				MapNode n = m_map->getNode(bottompos);
-				if(n.d == MATERIAL_GRASS)
+				if(n.d == CONTENT_GRASS)
 				{
-					n.d = MATERIAL_GRASS_FOOTSTEPS;
+					n.d = CONTENT_GRASS_FOOTSTEPS;
 					m_map->setNode(bottompos, n);
 
 					// Update mesh on client
diff --git a/src/light.cpp b/src/light.cpp
index 5fd0ce54f..a9fe023ef 100644
--- a/src/light.cpp
+++ b/src/light.cpp
@@ -101,4 +101,47 @@ u8 light_decode_table[LIGHT_MAX+1] =
 255,
 };
 
+/*
+#!/usr/bin/python
+
+from math import *
+from sys import stdout
+
+# We want 0 at light=0 and 255 at light=LIGHT_MAX
+LIGHT_MAX = 14
+#FACTOR = 0.69
+FACTOR = 0.75
+
+maxlight = 255
+minlight = 8
+
+L = []
+for i in range(1,LIGHT_MAX+1):
+    L.append(minlight+int(round((maxlight-minlight) * FACTOR ** (i-1))))
+    #L.append(int(round(255.0 * FACTOR ** (i-1))))
+L.append(minlight)
+
+L.reverse()
+for i in L:
+    stdout.write(str(i)+",\n")
+*/
+/*u8 light_decode_table[LIGHT_MAX+1] = 
+{
+8,
+14,
+16,
+18,
+22,
+27,
+33,
+41,
+52,
+67,
+86,
+112,
+147,
+193,
+255,
+};*/
+
 
diff --git a/src/main.cpp b/src/main.cpp
index fd91ab35c..6aa95d879 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -186,13 +186,11 @@ SUGG: Implement a "Fast check queue" (a queue with a map for checking
 TODO: Proper looking torches.
       - Signs could be done in the same way?
 
+TODO: A mapper to map contents to tile names (for each side)
+
 Doing now:
 ======================================================================
 
-TODO: A system for showing some nodes in some other way than cubes
-      - Needed for torches
-	  - Also for signs, stairs, etc
-
 ======================================================================
 
 */
@@ -257,22 +255,26 @@ TODO: A system for showing some nodes in some other way than cubes
 
 IrrlichtDevice *g_device = NULL;
 
-const char *g_material_filenames[MATERIALS_COUNT] =
+const char *g_content_filenames[MATERIALS_COUNT] =
 {
 	"../data/stone.png",
 	"../data/grass.png",
 	"../data/water.png",
-	"../data/light.png",
+	"../data/torch_on_floor.png",
 	"../data/tree.png",
 	"../data/leaves.png",
 	"../data/grass_footsteps.png",
 	"../data/mese.png",
 	"../data/mud.png",
-	"../data/water.png", // ocean
+	"../data/water.png", // CONTENT_OCEAN
 };
 
+// Material cache
 video::SMaterial g_materials[MATERIALS_COUNT];
-//video::SMaterial g_mesh_materials[3];
+
+// Texture cache
+TextureCache g_texturecache;
+
 
 // All range-related stuff below is locked behind this
 JMutex g_range_mutex;
@@ -320,20 +322,22 @@ void set_default_settings()
 	g_settings.set("random_input", "false");
 	g_settings.set("client_delete_unused_sectors_timeout", "1200");
 	g_settings.set("max_block_send_distance", "8");
-	g_settings.set("max_block_generate_distance", "5");
+	g_settings.set("max_block_generate_distance", "6");
 
 	// Server stuff
 	g_settings.set("creative_mode", "false");
-	g_settings.set("heightmap_blocksize", "128");
-	g_settings.set("height_randmax", "constant 70.0");
+	g_settings.set("heightmap_blocksize", "32");
+	g_settings.set("height_randmax", "constant 50.0");
 	g_settings.set("height_randfactor", "constant 0.6");
-	g_settings.set("height_base", "linear 0 35 0");
+	g_settings.set("height_base", "linear 0 0 0");
 	g_settings.set("plants_amount", "1.0");
 	g_settings.set("ravines_amount", "1.0");
 	g_settings.set("objectdata_interval", "0.2");
 	g_settings.set("active_object_range", "2");
 	g_settings.set("max_simultaneous_block_sends_per_client", "1");
 	g_settings.set("max_simultaneous_block_sends_server_total", "4");
+	g_settings.set("disable_water_climb", "true");
+	g_settings.set("endless_water", "true");
 }
 
 /*
@@ -673,7 +677,7 @@ class RandomInputHandler : public InputHandler
 			if(counter1 < 0.0)
 			{
 				counter1 = 0.1*Rand(1,10);
-				/*if(g_selected_material < USEFUL_MATERIAL_COUNT-1)
+				/*if(g_selected_material < USEFUL_CONTENT_COUNT-1)
 					g_selected_material++;
 				else
 					g_selected_material = 0;*/
@@ -1297,7 +1301,7 @@ int main(int argc, char *argv[])
 		g_materials[i].Lighting = false;
 		g_materials[i].BackfaceCulling = false;
 
-		const char *filename = g_material_filenames[i];
+		const char *filename = g_content_filenames[i];
 		if(filename != NULL){
 			video::ITexture *t = driver->getTexture(filename);
 			if(t == NULL){
@@ -1313,9 +1317,9 @@ int main(int argc, char *argv[])
 		//g_materials[i].setFlag(video::EMF_FOG_ENABLE, true);
 	}
 
-	g_materials[MATERIAL_WATER].MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
-	//g_materials[MATERIAL_WATER].MaterialType = video::EMT_TRANSPARENT_ADD_COLOR;
-	g_materials[MATERIAL_OCEAN].MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
+	g_materials[CONTENT_WATER].MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
+	//g_materials[CONTENT_WATER].MaterialType = video::EMT_TRANSPARENT_ADD_COLOR;
+	g_materials[CONTENT_OCEAN].MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
 
 	/*g_mesh_materials[0].setTexture(0, driver->getTexture("../data/water.png"));
 	g_mesh_materials[1].setTexture(0, driver->getTexture("../data/grass.png"));
@@ -1328,8 +1332,18 @@ int main(int argc, char *argv[])
 		g_mesh_materials[i].setFlag(video::EMF_FOG_ENABLE, true);
 	}*/
 
-	// Make a scope here for the client so that it gets removed
-	// before the irrlicht device
+	/*
+		Preload some random textures that are used in threads
+	*/
+	
+	g_texturecache.set("torch", driver->getTexture("../data/torch.png"));
+	g_texturecache.set("torch_on_floor", driver->getTexture("../data/torch_on_floor.png"));
+	g_texturecache.set("torch_on_ceiling", driver->getTexture("../data/torch_on_ceiling.png"));
+
+	/*
+		Make a scope here for the client so that it gets removed
+		before the irrlicht device
+	*/
 	{
 
 	std::cout<<DTIME<<"Creating server and client"<<std::endl;
@@ -1380,6 +1394,23 @@ int main(int argc, char *argv[])
 		std::cout<<DTIME<<"Timed out."<<std::endl;
 		return 0;
 	}
+
+	/*
+		Create skybox
+	*/
+	scene::ISceneNode* skybox = smgr->addSkyBoxSceneNode(
+		driver->getTexture("../data/skybox2.png"),
+		driver->getTexture("../data/skybox3.png"),
+		driver->getTexture("../data/skybox1.png"),
+		driver->getTexture("../data/skybox1.png"),
+		driver->getTexture("../data/skybox1.png"),
+		driver->getTexture("../data/skybox1.png"));
+	/*	driver->getTexture("../data/irrlicht2_up.jpg"),
+		driver->getTexture("../data/irrlicht2_dn.jpg"),
+		driver->getTexture("../data/irrlicht2_lf.jpg"),
+		driver->getTexture("../data/irrlicht2_rt.jpg"),
+		driver->getTexture("../data/irrlicht2_ft.jpg"),
+		driver->getTexture("../data/irrlicht2_bk.jpg"));*/
 	
 	/*
 		Create the camera node
@@ -1862,14 +1893,19 @@ int main(int argc, char *argv[])
 		s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
 		s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
 		
-		for(s16 y = ystart; y <= yend; y++){
-		for(s16 z = zstart; z <= zend; z++){
+		for(s16 y = ystart; y <= yend; y++)
+		for(s16 z = zstart; z <= zend; z++)
 		for(s16 x = xstart; x <= xend; x++)
 		{
-			try{
-				if(material_pointable(client.getNode(v3s16(x,y,z)).d) == false)
+			MapNode n;
+			try
+			{
+				n = client.getNode(v3s16(x,y,z));
+				if(content_pointable(n.d) == false)
 					continue;
-			}catch(InvalidPositionException &e){
+			}
+			catch(InvalidPositionException &e)
+			{
 				continue;
 			}
 
@@ -1878,55 +1914,110 @@ int main(int argc, char *argv[])
 			
 			f32 d = 0.01;
 			
-			v3s16 directions[6] = {
+			v3s16 dirs[6] = {
 				v3s16(0,0,1), // back
 				v3s16(0,1,0), // top
 				v3s16(1,0,0), // right
-				v3s16(0,0,-1),
-				v3s16(0,-1,0),
-				v3s16(-1,0,0),
+				v3s16(0,0,-1), // front
+				v3s16(0,-1,0), // bottom
+				v3s16(-1,0,0), // left
 			};
+			
+			/*
+				Meta-objects
+			*/
+			if(n.d == CONTENT_LIGHT)
+			{
+				v3s16 dir = unpackDir(n.dir);
+				v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
+				dir_f *= BS/2 - BS/6 - BS/20;
+				v3f cpf = npf + dir_f;
+				f32 distance = (cpf - camera_position).getLength();
 
-			for(u16 i=0; i<6; i++){
-			//{u16 i=3;
-				v3f dir_f = v3f(directions[i].X,
-						directions[i].Y, directions[i].Z);
-				v3f centerpoint = npf + dir_f * BS/2;
-				f32 distance =
-						(centerpoint - camera_position).getLength();
+				core::aabbox3d<f32> box;
 				
-				if(distance < mindistance){
-					//std::cout<<DTIME<<"for centerpoint=("<<centerpoint.X<<","<<centerpoint.Y<<","<<centerpoint.Z<<"): distance < mindistance"<<std::endl;
-					//std::cout<<DTIME<<"npf=("<<npf.X<<","<<npf.Y<<","<<npf.Z<<")"<<std::endl;
-					core::CMatrix4<f32> m;
-					m.buildRotateFromTo(v3f(0,0,1), dir_f);
-
-					// This is the back face
-					v3f corners[2] = {
-						v3f(BS/2, BS/2, BS/2),
-						v3f(-BS/2, -BS/2, BS/2+d)
-					};
-					
-					for(u16 j=0; j<2; j++){
-						m.rotateVect(corners[j]);
-						corners[j] += npf;
-						//std::cout<<DTIME<<"box corners["<<j<<"]: ("<<corners[j].X<<","<<corners[j].Y<<","<<corners[j].Z<<")"<<std::endl;
-					}
-
-					//core::aabbox3d<f32> facebox(corners[0],corners[1]);
-					core::aabbox3d<f32> facebox(corners[0]);
-					facebox.addInternalPoint(corners[1]);
+				// bottom
+				if(dir == v3s16(0,-1,0))
+				{
+					box = core::aabbox3d<f32>(
+						npf - v3f(BS/6, BS/2, BS/6),
+						npf + v3f(BS/6, -BS/2+BS/3*2, BS/6)
+					);
+				}
+				// top
+				else if(dir == v3s16(0,1,0))
+				{
+					box = core::aabbox3d<f32>(
+						npf - v3f(BS/6, -BS/2+BS/3*2, BS/6),
+						npf + v3f(BS/6, BS/2, BS/6)
+					);
+				}
+				// side
+				else
+				{
+					box = core::aabbox3d<f32>(
+						cpf - v3f(BS/6, BS/3, BS/6),
+						cpf + v3f(BS/6, BS/3, BS/6)
+					);
+				}
 
-					if(facebox.intersectsWithLine(shootline)){
+				if(distance < mindistance)
+				{
+					if(box.intersectsWithLine(shootline))
+					{
 						nodefound = true;
 						nodepos = np;
-						neighbourpos = np + directions[i];
+						neighbourpos = np;
 						mindistance = distance;
-						nodefacebox = facebox;
+						nodefacebox = box;
 					}
 				}
 			}
-		}}}
+			/*
+				Regular blocks
+			*/
+			else
+			{
+				for(u16 i=0; i<6; i++)
+				{
+					v3f dir_f = v3f(dirs[i].X,
+							dirs[i].Y, dirs[i].Z);
+					v3f centerpoint = npf + dir_f * BS/2;
+					f32 distance =
+							(centerpoint - camera_position).getLength();
+					
+					if(distance < mindistance)
+					{
+						core::CMatrix4<f32> m;
+						m.buildRotateFromTo(v3f(0,0,1), dir_f);
+
+						// This is the back face
+						v3f corners[2] = {
+							v3f(BS/2, BS/2, BS/2),
+							v3f(-BS/2, -BS/2, BS/2+d)
+						};
+						
+						for(u16 j=0; j<2; j++)
+						{
+							m.rotateVect(corners[j]);
+							corners[j] += npf;
+						}
+
+						core::aabbox3d<f32> facebox(corners[0]);
+						facebox.addInternalPoint(corners[1]);
+
+						if(facebox.intersectsWithLine(shootline))
+						{
+							nodefound = true;
+							nodepos = np;
+							neighbourpos = np + dirs[i];
+							mindistance = distance;
+							nodefacebox = facebox;
+						}
+					} // if distance < mindistance
+				} // for dirs
+			} // regular block
+		} // for coords
 
 		if(nodefound)
 		{
diff --git a/src/main.h b/src/main.h
index a08cff3f0..3d676bd43 100644
--- a/src/main.h
+++ b/src/main.h
@@ -55,7 +55,9 @@ extern std::ostream *derr_server_ptr;
 // This header is only for MATERIALS_COUNT
 #include "mapnode.h"
 extern video::SMaterial g_materials[MATERIALS_COUNT];
-//extern video::SMaterial g_mesh_materials[3];
+
+#include "utility.h"
+extern TextureCache g_texturecache;
 
 extern IrrlichtDevice *g_device;
 
diff --git a/src/map.cpp b/src/map.cpp
index 1a7cd9bb9..ff57e8d90 100644
--- a/src/map.cpp
+++ b/src/map.cpp
@@ -510,6 +510,10 @@ void Map::unspreadLight(core::map<v3s16, u8> & from_nodes,
 							light_sources.remove(n2pos);
 						}*/
 					}
+					
+					/*// DEBUG
+					if(light_sources.find(n2pos) != NULL)
+						light_sources.remove(n2pos);*/
 				}
 				else{
 					light_sources.insert(n2pos, true);
@@ -850,8 +854,8 @@ void Map::updateLighting(core::map<v3s16, MapBlock*> & a_blocks,
 					
 					// Collect borders for unlighting
 					if(x==0 || x == MAP_BLOCKSIZE-1
-							|| y==0 || y == MAP_BLOCKSIZE-1
-							|| z==0 || z == MAP_BLOCKSIZE-1)
+					|| y==0 || y == MAP_BLOCKSIZE-1
+					|| z==0 || z == MAP_BLOCKSIZE-1)
 					{
 						v3s16 p_map = p + v3s16(
 								MAP_BLOCKSIZE*pos.X,
@@ -912,6 +916,8 @@ void Map::updateLighting(core::map<v3s16, MapBlock*> & a_blocks,
 	// Yes, add it to light_sources... somehow.
 	// It has to be added at somewhere above, in the loop.
 	// TODO
+	// NOTE: This actually works quite fine without it
+	//       - Find out why it works
 
 	{
 		//TimeTaker timer("spreadLight", g_device);
@@ -1048,7 +1054,7 @@ void Map::removeNodeAndUpdate(v3s16 p,
 	v3s16 toppos = p + v3s16(0,1,0);
 
 	// Node will be replaced with this
-	u8 replace_material = MATERIAL_AIR;
+	u8 replace_material = CONTENT_AIR;
 	
 	// NOTE: Water is now managed elsewhere
 #if 0
@@ -1099,7 +1105,7 @@ void Map::removeNodeAndUpdate(v3s16 p,
 			if(
 					c > highest_ranking ||
 					// Prefer something else than air
-					(c >= highest_ranking && m != MATERIAL_AIR)
+					(c >= highest_ranking && m != CONTENT_AIR)
 
 			)
 			{
@@ -1722,17 +1728,91 @@ MapBlock * ServerMap::emergeBlock(
 		low_block_is_empty = true;*/
 	
 	const s32 ued = 4;
+	//const s32 ued = 8;
 	bool underground_emptiness[ued*ued*ued];
 	for(s32 i=0; i<ued*ued*ued; i++)
 	{
-		underground_emptiness[i] = ((rand() % 4) == 0);
+		underground_emptiness[i] = ((rand() % 5) == 0);
 	}
+
+#if 0
+	/*
+		This is a messy hack to sort the emptiness a bit
+	*/
+	for(s32 j=0; j<2; j++)
+	for(s32 y0=0; y0<ued; y0++)
+	for(s32 z0=0; z0<ued; z0++)
+	for(s32 x0=0; x0<ued; x0++)
+	{
+		v3s16 p0(x0,y0,z0);
+		bool &e0 = underground_emptiness[
+				ued*ued*(z0*ued/MAP_BLOCKSIZE)
+				+ued*(y0*ued/MAP_BLOCKSIZE)
+				+(x0*ued/MAP_BLOCKSIZE)];
+				
+		v3s16 dirs[6] = {
+			v3s16(0,0,1), // back
+			v3s16(1,0,0), // right
+			v3s16(0,0,-1), // front
+			v3s16(-1,0,0), // left
+			/*v3s16(0,1,0), // top
+			v3s16(0,-1,0), // bottom*/
+		};
+		for(s32 i=0; i<4; i++)
+		{
+			v3s16 p1 = p0 + dirs[i];
+			if(isInArea(p1, ued) == false)
+				continue;
+			bool &e1 = underground_emptiness[
+					ued*ued*(p1.Z*ued/MAP_BLOCKSIZE)
+					+ued*(p1.Y*ued/MAP_BLOCKSIZE)
+					+(p1.X*ued/MAP_BLOCKSIZE)];
+			if(e0 == e1)
+				continue;
+				
+			v3s16 dirs[6] = {
+				v3s16(0,1,0), // top
+				v3s16(0,-1,0), // bottom
+				/*v3s16(0,0,1), // back
+				v3s16(1,0,0), // right
+				v3s16(0,0,-1), // front
+				v3s16(-1,0,0), // left*/
+			};
+			for(s32 i=0; i<2; i++)
+			{
+				v3s16 p2 = p1 + dirs[i];
+				if(p2 == p0)
+					continue;
+				if(isInArea(p2, ued) == false)
+					continue;
+				bool &e2 = underground_emptiness[
+						ued*ued*(p2.Z*ued/MAP_BLOCKSIZE)
+						+ued*(p2.Y*ued/MAP_BLOCKSIZE)
+						+(p2.X*ued/MAP_BLOCKSIZE)];
+				if(e2 != e0)
+					continue;
+				
+				bool t = e1;
+				e1 = e2;
+				e2 = t;
+
+				break;
+			}
+			//break;
+		}
+	}
+#endif
 	
 	// This is the basic material of what the visible flat ground
 	// will consist of
-	u8 material = MATERIAL_GRASS;
+	u8 material = CONTENT_GRASS;
+
+	u8 water_material = CONTENT_WATER;
+	if(g_settings.getBool("endless_water"))
+		water_material = CONTENT_OCEAN;
 	
 	s32 lowest_ground_y = 32767;
+	s32 highest_ground_y = -32768;
 	
 	// DEBUG
 	//sector->printHeightmaps();
@@ -1755,14 +1835,19 @@ MapBlock * ServerMap::emergeBlock(
 		//avg_ground_y += surface_y;
 		if(surface_y < lowest_ground_y)
 			lowest_ground_y = surface_y;
+		if(surface_y > highest_ground_y)
+			highest_ground_y = surface_y;
 
 		s32 surface_depth = 0;
 		
 		float slope = sector->getSlope(v2s16(x0,z0)).getLength();
 		
-		float min_slope = 0.45;
-		float max_slope = 0.85;
-		float min_slope_depth = 5.0;
+		//float min_slope = 0.45;
+		//float max_slope = 0.85;
+		float min_slope = 0.70;
+		float max_slope = 1.20;
+		float min_slope_depth = 4.0;
+		//float min_slope_depth = 5.0;
 		float max_slope_depth = 0;
 		if(slope < min_slope)
 			surface_depth = min_slope_depth;
@@ -1783,33 +1868,55 @@ MapBlock * ServerMap::emergeBlock(
 			*/
 			if(real_y > surface_y)
 				n.setLight(LIGHT_SUN);
+
 			/*
 				Calculate material
 			*/
+
 			// If node is very low
-			if(real_y <= surface_y - 7){
+			/*if(real_y <= surface_y - 7)
+			{
 				// Create dungeons
 				if(underground_emptiness[
 						ued*ued*(z0*ued/MAP_BLOCKSIZE)
 						+ued*(y0*ued/MAP_BLOCKSIZE)
 						+(x0*ued/MAP_BLOCKSIZE)])
 				{
-					n.d = MATERIAL_AIR;
+					n.d = CONTENT_AIR;
 				}
 				else
 				{
-					n.d = MATERIAL_STONE;
+					n.d = CONTENT_STONE;
 				}
 			}
 			// If node is under surface level
 			else if(real_y <= surface_y - surface_depth)
-				n.d = MATERIAL_STONE;
+				n.d = CONTENT_STONE;
+			*/
+			if(real_y <= surface_y - surface_depth)
+			{
+				// Create dungeons
+				if(underground_emptiness[
+						ued*ued*(z0*ued/MAP_BLOCKSIZE)
+						+ued*(y0*ued/MAP_BLOCKSIZE)
+						+(x0*ued/MAP_BLOCKSIZE)])
+				{
+					n.d = CONTENT_AIR;
+				}
+				else
+				{
+					n.d = CONTENT_STONE;
+				}
+			}
 			// If node is at or under heightmap y
 			else if(real_y <= surface_y)
 			{
 				// If under water level, it's mud
 				if(real_y < WATER_LEVEL)
-					n.d = MATERIAL_MUD;
+					n.d = CONTENT_MUD;
+				// Only the topmost node is grass
+				else if(real_y <= surface_y - 1)
+					n.d = CONTENT_MUD;
 				// Else it's the main material
 				else
 					n.d = material;
@@ -1819,13 +1926,12 @@ MapBlock * ServerMap::emergeBlock(
 				// If under water level, it's water
 				if(real_y < WATER_LEVEL)
 				{
-					//n.d = MATERIAL_WATER;
-					n.d = MATERIAL_OCEAN;
+					n.d = water_material;
 					n.setLight(diminish_light(LIGHT_SUN, WATER_LEVEL-real_y+1));
 				}
 				// else air
 				else
-					n.d = MATERIAL_AIR;
+					n.d = CONTENT_AIR;
 			}
 			block->setNode(v3s16(x0,y0,z0), n);
 		}
@@ -1836,15 +1942,17 @@ MapBlock * ServerMap::emergeBlock(
 	*/
 	// Probably underground if the highest part of block is under lowest
 	// ground height
-	bool is_underground = (block_y+1) * MAP_BLOCKSIZE < lowest_ground_y;
+	bool is_underground = (block_y+1) * MAP_BLOCKSIZE <= lowest_ground_y;
 	block->setIsUnderground(is_underground);
 
 	/*
-		Force lighting update if underground.
-		This is needed because of ravines.
+		Force lighting update if some part of block is underground
+		This is needed because of caves.
 	*/
-
-	if(is_underground)
+	
+	bool some_part_underground = (block_y+0) * MAP_BLOCKSIZE < highest_ground_y;
+	if(some_part_underground)
+	//if(is_underground)
 	{
 		lighting_invalidated_blocks[block->getPos()] = block;
 	}
@@ -1867,15 +1975,15 @@ MapBlock * ServerMap::emergeBlock(
 				);
 
 				MapNode n;
-				n.d = MATERIAL_MESE;
+				n.d = CONTENT_MESE;
 				
-				if(is_ground_material(block->getNode(cp).d))
+				if(is_ground_content(block->getNode(cp).d))
 					if(rand()%8 == 0)
 						block->setNode(cp, n);
 
 				for(u16 i=0; i<26; i++)
 				{
-					if(is_ground_material(block->getNode(cp+g_26dirs[i]).d))
+					if(is_ground_content(block->getNode(cp+g_26dirs[i]).d))
 						if(rand()%8 == 0)
 							block->setNode(cp+g_26dirs[i], n);
 				}
@@ -1897,7 +2005,7 @@ MapBlock * ServerMap::emergeBlock(
 			);
 
 			// Check that the place is empty
-			//if(!is_ground_material(block->getNode(cp).d))
+			//if(!is_ground_content(block->getNode(cp).d))
 			if(1)
 			{
 				RatObject *obj = new RatObject(NULL, -1, intToFloat(cp));
@@ -1978,7 +2086,7 @@ MapBlock * ServerMap::emergeBlock(
 					p + v3s16(0,0,0), &changed_blocks_sector))
 			{
 				MapNode n;
-				n.d = MATERIAL_LIGHT;
+				n.d = CONTENT_LIGHT;
 				sector->setNode(p, n);
 				objects_to_remove.push_back(p);
 			}
@@ -1991,13 +2099,13 @@ MapBlock * ServerMap::emergeBlock(
 					&changed_blocks_sector))
 			{
 				MapNode n;
-				n.d = MATERIAL_TREE;
+				n.d = CONTENT_TREE;
 				sector->setNode(p+v3s16(0,0,0), n);
 				sector->setNode(p+v3s16(0,1,0), n);
 				sector->setNode(p+v3s16(0,2,0), n);
 				sector->setNode(p+v3s16(0,3,0), n);
 
-				n.d = MATERIAL_LEAVES;
+				n.d = CONTENT_LEAVES;
 
 				sector->setNode(p+v3s16(0,4,0), n);
 				
@@ -2032,7 +2140,7 @@ MapBlock * ServerMap::emergeBlock(
 					p + v3s16(0,0,0), &changed_blocks_sector))
 			{
 				MapNode n;
-				n.d = MATERIAL_LEAVES;
+				n.d = CONTENT_LEAVES;
 				sector->setNode(p+v3s16(0,0,0), n);
 				
 				objects_to_remove.push_back(p);
@@ -2047,9 +2155,9 @@ MapBlock * ServerMap::emergeBlock(
 					&changed_blocks_sector))
 			{
 				MapNode n;
-				n.d = MATERIAL_STONE;
+				n.d = CONTENT_STONE;
 				MapNode n2;
-				n2.d = MATERIAL_AIR;
+				n2.d = CONTENT_AIR;
 				s16 depth = maxdepth + (rand()%10);
 				s16 z = 0;
 				s16 minz = -6 - (-2);
@@ -2067,22 +2175,22 @@ MapBlock * ServerMap::emergeBlock(
 								<<std::endl;*/
 						{
 							v3s16 p2 = p + v3s16(x,y,z-2);
-							if(is_ground_material(sector->getNode(p2).d))
+							if(is_ground_content(sector->getNode(p2).d))
 								sector->setNode(p2, n);
 						}
 						{
 							v3s16 p2 = p + v3s16(x,y,z-1);
-							if(is_ground_material(sector->getNode(p2).d))
+							if(is_ground_content(sector->getNode(p2).d))
 								sector->setNode(p2, n2);
 						}
 						{
 							v3s16 p2 = p + v3s16(x,y,z+0);
-							if(is_ground_material(sector->getNode(p2).d))
+							if(is_ground_content(sector->getNode(p2).d))
 								sector->setNode(p2, n2);
 						}
 						{
 							v3s16 p2 = p + v3s16(x,y,z+1);
-							if(is_ground_material(sector->getNode(p2).d))
+							if(is_ground_content(sector->getNode(p2).d))
 								sector->setNode(p2, n);
 						}
 
@@ -3100,7 +3208,7 @@ void ClientMap::updateMesh()
 		/*dstream<<"mesh_old refcount="<<mesh_old->getReferenceCount()
 				<<std::endl;
 		scene::IMeshBuffer *buf = mesh_new->getMeshBuffer
-				(g_materials[MATERIAL_GRASS]);
+				(g_materials[CONTENT_GRASS]);
 		if(buf != NULL)
 			dstream<<"grass buf refcount="<<buf->getReferenceCount()
 					<<std::endl;*/
diff --git a/src/mapblock.cpp b/src/mapblock.cpp
index 0f2eba856..033c69cdb 100644
--- a/src/mapblock.cpp
+++ b/src/mapblock.cpp
@@ -111,7 +111,7 @@ FastFace * MapBlock::makeFastFace(u8 material, u8 light, v3f p,
 
 	u8 alpha = 255;
 
-	if(material == MATERIAL_WATER || material == MATERIAL_OCEAN)
+	if(material == CONTENT_WATER || material == CONTENT_OCEAN)
 	{
 		alpha = 128;
 	}
@@ -152,7 +152,11 @@ u8 MapBlock::getFaceLight(v3s16 p, v3s16 face_dir)
 		MapNode n = getNodeParent(p);
 		MapNode n2 = getNodeParent(p + face_dir);
 		u8 light;
-		if(n.solidness() < n2.solidness())
+		/*if(n.solidness() < n2.solidness())
+			light = n.getLight();
+		else
+			light = n2.getLight();*/
+		if(n.getLight() > n2.getLight())
 			light = n.getLight();
 		else
 			light = n2.getLight();
@@ -173,18 +177,18 @@ u8 MapBlock::getFaceLight(v3s16 p, v3s16 face_dir)
 
 /*
 	Gets node material from any place relative to block.
-	Returns MATERIAL_IGNORE if doesn't exist or should not be drawn.
+	Returns CONTENT_IGNORE if doesn't exist or should not be drawn.
 */
-u8 MapBlock::getNodeMaterial(v3s16 p)
+u8 MapBlock::getNodeTile(v3s16 p)
 {
 	try{
 		MapNode n = getNodeParent(p);
 		
-		return content_cube_material(n.d);
+		return content_tile(n.d);
 	}
 	catch(InvalidPositionException &e)
 	{
-		return MATERIAL_IGNORE;
+		return CONTENT_IGNORE;
 	}
 }
 
@@ -216,82 +220,82 @@ void MapBlock::updateFastFaceRow(v3s16 startpos,
 	*/
 	u8 light = getFaceLight(p, face_dir);
 	
-	u16 continuous_materials_count = 0;
+	u16 continuous_tiles_count = 0;
 	
-	u8 material0 = getNodeMaterial(p);
-	u8 material1 = getNodeMaterial(p + face_dir);
+	u8 tile0 = getNodeTile(p);
+	u8 tile1 = getNodeTile(p + face_dir);
 		
 	for(u16 j=0; j<length; j++)
 	{
 		bool next_is_different = true;
 		
 		v3s16 p_next;
-		u8 material0_next = 0;
-		u8 material1_next = 0;
+		u8 tile0_next = 0;
+		u8 tile1_next = 0;
 		u8 light_next = 0;
 
 		if(j != length - 1){
 			p_next = p + translate_dir;
-			material0_next = getNodeMaterial(p_next);
-			material1_next = getNodeMaterial(p_next + face_dir);
+			tile0_next = getNodeTile(p_next);
+			tile1_next = getNodeTile(p_next + face_dir);
 			light_next = getFaceLight(p_next, face_dir);
 
-			if(material0_next == material0
-					&& material1_next == material1
+			if(tile0_next == tile0
+					&& tile1_next == tile1
 					&& light_next == light)
 			{
 				next_is_different = false;
 			}
 		}
 
-		continuous_materials_count++;
+		continuous_tiles_count++;
 		
 		if(next_is_different)
 		{
 			/*
 				Create a face if there should be one
 			*/
-			u8 mf = face_materials(material0, material1);
+			u8 mf = face_contents(tile0, tile1);
 			
 			if(mf != 0)
 			{
 				// Floating point conversion of the position vector
 				v3f pf(p.X, p.Y, p.Z);
 				// Center point of face (kind of)
-				v3f sp = pf - ((f32)continuous_materials_count / 2. - 0.5) * translate_dir_f;
+				v3f sp = pf - ((f32)continuous_tiles_count / 2. - 0.5) * translate_dir_f;
 				v3f scale(1,1,1);
 				if(translate_dir.X != 0){
-					scale.X = continuous_materials_count;
+					scale.X = continuous_tiles_count;
 				}
 				if(translate_dir.Y != 0){
-					scale.Y = continuous_materials_count;
+					scale.Y = continuous_tiles_count;
 				}
 				if(translate_dir.Z != 0){
-					scale.Z = continuous_materials_count;
+					scale.Z = continuous_tiles_count;
 				}
 				
 				FastFace *f;
 
-				// If node at sp (material0) is more solid
+				// If node at sp (tile0) is more solid
 				if(mf == 1)
 				{
-					f = makeFastFace(material0, light,
+					f = makeFastFace(tile0, light,
 							sp, face_dir_f, scale,
 							posRelative_f);
 				}
 				// If node at sp is less solid (mf == 2)
 				else
 				{
-					f = makeFastFace(material1, light,
+					f = makeFastFace(tile1, light,
 							sp+face_dir_f, -1*face_dir_f, scale,
 							posRelative_f);
 				}
 				dest.push_back(f);
 			}
 
-			continuous_materials_count = 0;
-			material0 = material0_next;
-			material1 = material1_next;
+			continuous_tiles_count = 0;
+			tile0 = tile0_next;
+			tile1 = tile1_next;
 			light = light_next;
 		}
 		
@@ -451,6 +455,8 @@ void MapBlock::updateMesh()
 
 	scene::SMesh *mesh_new = NULL;
 	
+	mesh_new = new scene::SMesh();
+	
 	if(fastfaces_new->getSize() > 0)
 	{
 		MeshCollector collector;
@@ -467,8 +473,6 @@ void MapBlock::updateMesh()
 					indices, 6);
 		}
 
-		mesh_new = new scene::SMesh();
-		
 		collector.fillMesh(mesh_new);
 
 		// Use VBO for mesh (this just would set this for ever buffer)
@@ -495,13 +499,103 @@ void MapBlock::updateMesh()
 	/*
 		Add special graphics:
 		- torches
+		
+		TODO: Optimize by using same meshbuffer for same textures
 	*/
 
+	/*scene::ISceneManager *smgr = NULL;
+	video::IVideoDriver* driver = NULL;
+	if(g_device)
+	{
+		smgr = g_device->getSceneManager();
+		driver = smgr->getVideoDriver();
+	}*/
+			
 	for(s16 z=0; z<MAP_BLOCKSIZE; z++)
 	for(s16 y=0; y<MAP_BLOCKSIZE; y++)
 	for(s16 x=0; x<MAP_BLOCKSIZE; x++)
 	{
 		v3s16 p(x,y,z);
+
+		MapNode &n = getNodeRef(x,y,z);
+		
+		if(n.d == CONTENT_LIGHT)
+		{
+			//scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+			scene::SMeshBuffer *buf = new scene::SMeshBuffer();
+			video::SColor c(255,255,255,255);
+
+			video::S3DVertex vertices[4] =
+			{
+				video::S3DVertex(-BS/2,-BS/2,0, 0,0,0, c, 0,1),
+				video::S3DVertex(BS/2,-BS/2,0, 0,0,0, c, 1,1),
+				video::S3DVertex(BS/2,BS/2,0, 0,0,0, c, 1,0),
+				video::S3DVertex(-BS/2,BS/2,0, 0,0,0, c, 0,0),
+			};
+
+			v3s16 dir = unpackDir(n.dir);
+
+			for(s32 i=0; i<4; i++)
+			{
+				if(dir == v3s16(1,0,0))
+					vertices[i].Pos.rotateXZBy(0);
+				if(dir == v3s16(-1,0,0))
+					vertices[i].Pos.rotateXZBy(180);
+				if(dir == v3s16(0,0,1))
+					vertices[i].Pos.rotateXZBy(90);
+				if(dir == v3s16(0,0,-1))
+					vertices[i].Pos.rotateXZBy(-90);
+				if(dir == v3s16(0,-1,0))
+					vertices[i].Pos.rotateXZBy(45);
+				if(dir == v3s16(0,1,0))
+					vertices[i].Pos.rotateXZBy(-45);
+
+				vertices[i].Pos += intToFloat(p + getPosRelative());
+			}
+
+			u16 indices[] = {0,1,2,2,3,0};
+			buf->append(vertices, 4, indices, 6);
+
+			// Set material
+			buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
+			buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false);
+			buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
+			//buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+			buf->getMaterial().MaterialType
+					= video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
+			if(dir == v3s16(0,-1,0))
+				buf->getMaterial().setTexture(0,
+						g_texturecache.get("torch_on_floor"));
+			else if(dir == v3s16(0,1,0))
+				buf->getMaterial().setTexture(0,
+						g_texturecache.get("torch_on_ceiling"));
+			// For backwards compatibility
+			else if(dir == v3s16(0,0,0))
+				buf->getMaterial().setTexture(0,
+						g_texturecache.get("torch_on_floor"));
+			else
+				buf->getMaterial().setTexture(0, g_texturecache.get("torch"));
+
+			// Add to mesh
+			mesh_new->addMeshBuffer(buf);
+			buf->drop();
+		}
+	}
+
+	/*
+		Do some stuff to the mesh
+	*/
+
+	mesh_new->recalculateBoundingBox();
+
+	/*
+		Delete new mesh if it is empty
+	*/
+
+	if(mesh_new->getMeshBufferCount() == 0)
+	{
+		mesh_new->drop();
+		mesh_new = NULL;
 	}
 
 	/*
@@ -681,6 +775,11 @@ void MapBlock::copyTo(VoxelManipulator &dst)
 			getPosRelative(), data_size);
 }
 
+/*void getPseudoObjects(v3f origin, f32 max_d,
+		core::array<DistanceSortedObject> &dest)
+{
+}*/
+
 /*
 	Serialization
 */
diff --git a/src/mapblock.h b/src/mapblock.h
index c18bbb2b4..76465903b 100644
--- a/src/mapblock.h
+++ b/src/mapblock.h
@@ -288,11 +288,7 @@ class MapBlock : public NodeContainer
 	
 	u8 getFaceLight(v3s16 p, v3s16 face_dir);
 	
-	/*
-		Gets node material from any place relative to block.
-		Returns MATERIAL_AIR if doesn't exist.
-	*/
-	u8 getNodeMaterial(v3s16 p);
+	u8 getNodeTile(v3s16 p);
 
 	/*
 		startpos:
@@ -376,6 +372,9 @@ class MapBlock : public NodeContainer
 		m_objects.getObjects(origin, max_d, dest);
 	}
 
+	/*void getPseudoObjects(v3f origin, f32 max_d,
+			core::array<DistanceSortedObject> &dest);*/
+
 	s32 getObjectCount()
 	{
 		return m_objects.getCount();
diff --git a/src/mapblockobject.cpp b/src/mapblockobject.cpp
index 3a28e2250..cd12feb0c 100644
--- a/src/mapblockobject.cpp
+++ b/src/mapblockobject.cpp
@@ -145,7 +145,7 @@ void MovingObject::move(float dtime, v3f acceleration)
 		for(s16 x = oldpos_i.X - 1; x <= oldpos_i.X + 1; x++)
 		{
 			try{
-				if(material_walkable(m_block->getNodeParent(v3s16(x,y,z)).d)
+				if(content_walkable(m_block->getNodeParent(v3s16(x,y,z)).d)
 						== false)
 					continue;
 			}
diff --git a/src/mapblockobject.h b/src/mapblockobject.h
index 2a5f9c933..61bd6610d 100644
--- a/src/mapblockobject.h
+++ b/src/mapblockobject.h
@@ -28,13 +28,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "constants.h"
 #include "debug.h"
 
-enum
-{
-	MAPBLOCKOBJECT_TYPE_TEST=0,
-	MAPBLOCKOBJECT_TYPE_TEST2=1,
-	MAPBLOCKOBJECT_TYPE_SIGN=2,
-	MAPBLOCKOBJECT_TYPE_RAT=3,
-};
+#define MAPBLOCKOBJECT_TYPE_TEST 0
+#define MAPBLOCKOBJECT_TYPE_TEST2 1
+#define MAPBLOCKOBJECT_TYPE_SIGN 2
+#define MAPBLOCKOBJECT_TYPE_RAT 3
+// Used for handling selecting special stuff
+//#define MAPBLOCKOBJECT_TYPE_PSEUDO 4
 
 class MapBlock;
 
@@ -170,6 +169,57 @@ class MapBlockObject
 	friend class MapBlockObjectList;
 };
 
+#if 0
+/*
+	Used for handling selections of special stuff
+*/
+class PseudoMBObject : public MapBlockObject
+{
+public:
+	// The constructor of every MapBlockObject should be like this
+	PseudoMBObject(MapBlock *block, s16 id, v3f pos):
+		MapBlockObject(block, id, pos)
+	{
+	}
+	virtual ~PseudoMBObject()
+	{
+		if(m_selection_box)
+			delete m_selection_box;
+	}
+	
+	/*
+		Implementation interface
+	*/
+	virtual u16 getTypeId() const
+	{
+		return MAPBLOCKOBJECT_TYPE_PSEUDO;
+	}
+	virtual void serialize(std::ostream &os, u8 version)
+	{
+		assert(0);
+	}
+	virtual void update(std::istream &is, u8 version)
+	{
+		assert(0);
+	}
+	virtual bool serverStep(float dtime)
+	{
+		assert(0);
+	}
+
+	/*
+		Special methods
+	*/
+	
+	void setSelectionBox(core::aabbox3d<f32> box)
+	{
+		m_selection_box = new core::aabbox3d<f32>(box);
+	}
+	
+protected:
+};
+#endif
+
 class TestObject : public MapBlockObject
 {
 public:
diff --git a/src/mapnode.h b/src/mapnode.h
index 0d65f30a4..9d9aba899 100644
--- a/src/mapnode.h
+++ b/src/mapnode.h
@@ -41,66 +41,68 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 	Doesn't create faces with anything and is considered being
 	out-of-map in the game map.
 */
-#define MATERIAL_IGNORE 255
-#define MATERIAL_IGNORE_DEFAULT_PARAM 0
+#define CONTENT_IGNORE 255
+#define CONTENT_IGNORE_DEFAULT_PARAM 0
 
 /*
 	The common material through which the player can walk and which
 	is transparent to light
 */
-#define MATERIAL_AIR 254
+#define CONTENT_AIR 254
 
 /*
-	Materials-todo:
-
+	Suggested materials:
 	GRAVEL
 	  - Dynamics of gravel: if there is a drop of more than two
 	    blocks on any side, it will drop in there. Is this doable?
 	
-	TODO: These should be named to "content" or something like that
+	New naming scheme:
+	- Material = irrlicht's Material class
+	- Content = (u8) content of a node
+	- Tile = (u16) Material ID at some side of a node
 */
 
-enum Material
+enum Content
 {
-	MATERIAL_STONE=0,
+	CONTENT_STONE=0,
 
-	MATERIAL_GRASS,
+	CONTENT_GRASS,
 
-	MATERIAL_WATER,
+	CONTENT_WATER,
 
-	MATERIAL_LIGHT,
+	CONTENT_LIGHT,
 
-	MATERIAL_TREE,
+	CONTENT_TREE,
 	
-	MATERIAL_LEAVES,
+	CONTENT_LEAVES,
 
-	MATERIAL_GRASS_FOOTSTEPS,
+	CONTENT_GRASS_FOOTSTEPS,
 	
-	MATERIAL_MESE,
+	CONTENT_MESE,
 
-	MATERIAL_MUD,
+	CONTENT_MUD,
 
-	MATERIAL_OCEAN,
+	CONTENT_OCEAN,
 	
 	// This is set to the number of the actual values in this enum
-	USEFUL_MATERIAL_COUNT
+	USEFUL_CONTENT_COUNT
 };
 
 /*
 	If true, the material allows light propagation and brightness is stored
 	in param.
 */
-inline bool light_propagates_material(u8 m)
+inline bool light_propagates_content(u8 m)
 {
-	return (m == MATERIAL_AIR || m == MATERIAL_LIGHT || m == MATERIAL_WATER || m == MATERIAL_OCEAN);
+	return (m == CONTENT_AIR || m == CONTENT_LIGHT || m == CONTENT_WATER || m == CONTENT_OCEAN);
 }
 
 /*
 	If true, the material allows lossless sunlight propagation.
 */
-inline bool sunlight_propagates_material(u8 m)
+inline bool sunlight_propagates_content(u8 m)
 {
-	return (m == MATERIAL_AIR);
+	return (m == CONTENT_AIR || m == CONTENT_LIGHT);
 }
 
 /*
@@ -110,98 +112,157 @@ inline bool sunlight_propagates_material(u8 m)
 	1: Transparent
 	2: Opaque
 */
-inline u8 material_solidness(u8 m)
+inline u8 content_solidness(u8 m)
 {
-	if(m == MATERIAL_AIR)
+	if(m == CONTENT_AIR)
 		return 0;
-	if(m == MATERIAL_WATER || m == MATERIAL_OCEAN)
+	if(m == CONTENT_WATER || m == CONTENT_OCEAN)
 		return 1;
 	return 2;
 }
 
-// Objects collide with walkable materials
-inline bool material_walkable(u8 m)
+// Objects collide with walkable contents
+inline bool content_walkable(u8 m)
 {
-	return (m != MATERIAL_AIR && m != MATERIAL_WATER && m != MATERIAL_OCEAN && m != MATERIAL_LIGHT);
+	return (m != CONTENT_AIR && m != CONTENT_WATER && m != CONTENT_OCEAN && m != CONTENT_LIGHT);
 }
 
 // A liquid resists fast movement
-inline bool material_liquid(u8 m)
+inline bool content_liquid(u8 m)
 {
-	return (m == MATERIAL_WATER || m == MATERIAL_OCEAN);
+	return (m == CONTENT_WATER || m == CONTENT_OCEAN);
 }
 
-// Pointable materials can be pointed to in the map
-inline bool material_pointable(u8 m)
+// Pointable contents can be pointed to in the map
+inline bool content_pointable(u8 m)
 {
-	return (m != MATERIAL_AIR && m != MATERIAL_WATER && m != MATERIAL_OCEAN);
+	return (m != CONTENT_AIR && m != CONTENT_WATER && m != CONTENT_OCEAN);
 }
 
-inline bool material_diggable(u8 m)
+inline bool content_diggable(u8 m)
 {
-	return (m != MATERIAL_AIR && m != MATERIAL_WATER && m != MATERIAL_OCEAN);
+	return (m != CONTENT_AIR && m != CONTENT_WATER && m != CONTENT_OCEAN);
 }
 
-inline bool material_buildable_to(u8 m)
+inline bool content_buildable_to(u8 m)
 {
-	return (m == MATERIAL_AIR || m == MATERIAL_WATER || m == MATERIAL_OCEAN);
+	return (m == CONTENT_AIR || m == CONTENT_WATER || m == CONTENT_OCEAN);
 }
 
 /*
-	As of now, input is a "material" and the output is a "material"
+	TODO: Make a mapper class for mapping every side of a content
+	      to some tile.
+	This dumbily maps all sides of content to the tile of the same id.
 */
-inline u8 content_cube_material(u8 c)
+inline u8 content_tile(u8 c)
 {
-	if(c == MATERIAL_IGNORE || c == MATERIAL_LIGHT)
-		return MATERIAL_AIR;
+	if(c == CONTENT_IGNORE || c == CONTENT_LIGHT)
+		return CONTENT_AIR;
 	return c;
 }
 
 /*
-	Returns true for materials that form the base ground that
+	Returns true for contents that form the base ground that
 	follows the main heightmap
 */
-inline bool is_ground_material(u8 m)
+inline bool is_ground_content(u8 m)
 {
 	return(
-		m == MATERIAL_STONE ||
-		m == MATERIAL_GRASS ||
-		m == MATERIAL_GRASS_FOOTSTEPS ||
-		m == MATERIAL_MESE ||
-		m == MATERIAL_MUD
+		m == CONTENT_STONE ||
+		m == CONTENT_GRASS ||
+		m == CONTENT_GRASS_FOOTSTEPS ||
+		m == CONTENT_MESE ||
+		m == CONTENT_MUD
 	);
 }
 
 /*
-	Nodes make a face if materials differ and solidness differs.
+	Nodes make a face if contents differ and solidness differs.
 	Return value:
 		0: No face
-		1: Face uses m1's material
-		2: Face uses m2's material
+		1: Face uses m1's content
+		2: Face uses m2's content
 */
-inline u8 face_materials(u8 m1, u8 m2)
+inline u8 face_contents(u8 m1, u8 m2)
 {
-	if(m1 == MATERIAL_IGNORE || m2 == MATERIAL_IGNORE)
+	if(m1 == CONTENT_IGNORE || m2 == CONTENT_IGNORE)
 		return 0;
 	
-	bool materials_differ = (m1 != m2);
-	bool solidness_differs = (material_solidness(m1) != material_solidness(m2));
-	bool makes_face = materials_differ && solidness_differs;
+	bool contents_differ = (m1 != m2);
+	bool solidness_differs = (content_solidness(m1) != content_solidness(m2));
+	bool makes_face = contents_differ && solidness_differs;
 
 	if(makes_face == false)
 		return 0;
 
-	if(material_solidness(m1) > material_solidness(m2))
+	if(content_solidness(m1) > content_solidness(m2))
 		return 1;
 	else
 		return 2;
 }
 
+inline bool liquid_replaces_content(u8 c)
+{
+	return (c == CONTENT_AIR || c == CONTENT_LIGHT);
+}
+
+/*
+	When placing a node, drection info is added to it if this is true
+*/
+inline bool content_directional(u8 c)
+{
+	return (c == CONTENT_LIGHT);
+}
+
+/*
+	Packs directions like (1,0,0), (1,-1,0)
+*/
+inline u8 packDir(v3s16 dir)
+{
+	u8 b = 0;
+
+	if(dir.X > 0)
+		b |= (1<<0);
+	else if(dir.X < 0)
+		b |= (1<<1);
+
+	if(dir.Y > 0)
+		b |= (1<<2);
+	else if(dir.Y < 0)
+		b |= (1<<3);
+
+	if(dir.Z > 0)
+		b |= (1<<4);
+	else if(dir.Z < 0)
+		b |= (1<<5);
+	
+	return b;
+}
+inline v3s16 unpackDir(u8 b)
+{
+	v3s16 d(0,0,0);
+
+	if(b & (1<<0))
+		d.X = 1;
+	else if(b & (1<<1))
+		d.X = -1;
+
+	if(b & (1<<2))
+		d.Y = 1;
+	else if(b & (1<<3))
+		d.Y = -1;
+
+	if(b & (1<<4))
+		d.Z = 1;
+	else if(b & (1<<5))
+		d.Z = -1;
+	
+	return d;
+}
+
 struct MapNode
 {
-	//TODO: block type to differ from material
-	//      (e.g. grass edges or something)
-	// block type
+	// Content
 	u8 d;
 
 	/*
@@ -211,15 +272,27 @@ struct MapNode
 		  Sunlight is LIGHT_SUN, which is LIGHT_MAX+1.
 	*/
 	s8 param;
+	
+	union
+	{
+		/*
+			Pressure for liquids
+		*/
+		u8 pressure;
 
-	u8 pressure;
+		/*
+			Direction for torches and other stuff.
+			If possible, packed with packDir.
+		*/
+		u8 dir;
+	};
 
 	MapNode(const MapNode & n)
 	{
 		*this = n;
 	}
 	
-	MapNode(u8 data=MATERIAL_AIR, u8 a_param=0, u8 a_pressure=0)
+	MapNode(u8 data=CONTENT_AIR, u8 a_param=0, u8 a_pressure=0)
 	{
 		d = data;
 		param = a_param;
@@ -235,17 +308,17 @@ struct MapNode
 
 	bool light_propagates()
 	{
-		return light_propagates_material(d);
+		return light_propagates_content(d);
 	}
 	
 	bool sunlight_propagates()
 	{
-		return sunlight_propagates_material(d);
+		return sunlight_propagates_content(d);
 	}
 	
 	u8 solidness()
 	{
-		return material_solidness(d);
+		return content_solidness(d);
 	}
 
 	u8 light_source()
@@ -253,7 +326,7 @@ struct MapNode
 		/*
 			Note that a block that isn't light_propagates() can be a light source.
 		*/
-		if(d == MATERIAL_LIGHT)
+		if(d == CONTENT_LIGHT)
 			return LIGHT_MAX;
 		
 		return 0;
@@ -261,7 +334,7 @@ struct MapNode
 
 	u8 getLight()
 	{
-		// Select the brightest of [light_source, transparent_light]
+		// Select the brightest of [light source, propagated light]
 		u8 light = 0;
 		if(light_propagates())
 			light = param & 0x0f;
diff --git a/src/player.cpp b/src/player.cpp
index 5e838bf7a..ef2a3bdfb 100644
--- a/src/player.cpp
+++ b/src/player.cpp
@@ -72,12 +72,12 @@ void Player::move(f32 dtime, Map &map)
 		if(in_water)
 		{
 			v3s16 pp = floatToInt(position + v3f(0,0,0));
-			in_water = material_liquid(map.getNode(pp).d);
+			in_water = content_liquid(map.getNode(pp).d);
 		}
 		else
 		{
 			v3s16 pp = floatToInt(position + v3f(0,BS/2,0));
-			in_water = material_liquid(map.getNode(pp).d);
+			in_water = content_liquid(map.getNode(pp).d);
 		}
 	}
 	catch(InvalidPositionException &e)
@@ -122,7 +122,7 @@ void Player::move(f32 dtime, Map &map)
 		for(s16 z = oldpos_i.Z - 1; z <= oldpos_i.Z + 1; z++){
 			for(s16 x = oldpos_i.X - 1; x <= oldpos_i.X + 1; x++){
 				try{
-					if(material_walkable(map.getNode(v3s16(x,y,z)).d) == false){
+					if(content_walkable(map.getNode(v3s16(x,y,z)).d) == false){
 						continue;
 					}
 				}
diff --git a/src/server.cpp b/src/server.cpp
index e343d5947..c7b589e7a 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -94,6 +94,8 @@ void * EmergeThread::Thread()
 		v3s16 &p = q->pos;
 		
 		//derr_server<<"EmergeThread::Thread(): running"<<std::endl;
+
+		//TimeTaker timer("block emerge", g_device);
 		
 		/*
 			Try to emerge it from somewhere.
@@ -185,39 +187,32 @@ void * EmergeThread::Thread()
 				dout_server<<std::endl;
 			}
 
+			/*
+				Update water pressure
+			*/
+
+			m_server->UpdateBlockWaterPressure(block, modified_blocks);
+
+			for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
+					i.atEnd() == false; i++)
+			{
+				MapBlock *block = i.getNode()->getValue();
+				m_server->UpdateBlockWaterPressure(block, modified_blocks);
+				//v3s16 p = i.getNode()->getKey();
+				//m_server->UpdateBlockWaterPressure(p, modified_blocks);
+			}
+
 			/*
 				Collect a list of blocks that have been modified in
 				addition to the fetched one.
 			*/
 
-			// Add all the "changed blocks"
+			// Add all the "changed blocks" to modified_blocks
 			for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
 					i.atEnd() == false; i++)
 			{
 				MapBlock *block = i.getNode()->getValue();
 				modified_blocks.insert(block->getPos(), block);
-
-				/*
-					Update water pressure.
-					This also adds suitable nodes to active_nodes.
-				*/
-
-				MapVoxelManipulator v(&map);
-				
-				VoxelArea area(block->getPosRelative(),
-						block->getPosRelative() + v3s16(1,1,1)*(MAP_BLOCKSIZE-1));
-
-				try
-				{
-					v.updateAreaWaterPressure(area, m_server->m_flow_active_nodes);
-				}
-				catch(ProcessingLimitException &e)
-				{
-					dstream<<"Processing limit reached (1)"<<std::endl;
-				}
-				
-				v.blitBack(modified_blocks);
-
 			}
 			
 			/*dstream<<"lighting "<<lighting_invalidated_blocks.size()
@@ -1017,9 +1012,11 @@ void Server::AsyncRunStep()
 
 		{
 
-			JMutexAutoLock lock(m_env_mutex);
+			JMutexAutoLock envlock(m_env_mutex);
 			
 			MapVoxelManipulator v(&m_env.getMap());
+			v.m_disable_water_climb =
+					g_settings.getBool("disable_water_climb");
 			
 			v.flowWater(m_flow_active_nodes, 0, false, 50);
 
@@ -1039,7 +1036,7 @@ void Server::AsyncRunStep()
 				MapBlock *block = i.getNode()->getValue();
 				modified_blocks.insert(block->getPos(), block);
 			}
-		}
+		} // envlock
 
 		/*
 			Set the modified blocks unsent for all the clients
@@ -1492,7 +1489,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 				// Get material at position
 				material = m_env.getMap().getNode(p_under).d;
 				// If it's not diggable, do nothing
-				if(material_diggable(material) == false)
+				if(content_diggable(material) == false)
 				{
 					return;
 				}
@@ -1539,6 +1536,8 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 			// This also adds it to m_flow_active_nodes if appropriate
 
 			MapVoxelManipulator v(&m_env.getMap());
+			v.m_disable_water_climb =
+					g_settings.getBool("disable_water_climb");
 			
 			VoxelArea area(p_under-v3s16(1,1,1), p_under+v3s16(1,1,1));
 
@@ -1575,15 +1574,10 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 			*/
 			if(std::string("MaterialItem") == item->getName())
 			{
-				MaterialItem *mitem = (MaterialItem*)item;
-				
-				MapNode n;
-				n.d = mitem->getMaterial();
-
 				try{
 					// Don't add a node if this is not a free space
 					MapNode n2 = m_env.getMap().getNode(p_over);
-					if(material_buildable_to(n2.d) == false)
+					if(content_buildable_to(n2.d) == false)
 						return;
 				}
 				catch(InvalidPositionException &e)
@@ -1596,17 +1590,14 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 				// Reset build time counter
 				getClient(peer->id)->m_time_from_building.set(0.0);
 				
-				if(g_settings.getBool("creative_mode") == false)
-				{
-					// Remove from inventory and send inventory
-					if(mitem->getCount() == 1)
-						player->inventory.deleteItem(item_i);
-					else
-						mitem->remove(1);
-					// Send inventory
-					SendInventory(peer_id);
-				}
-				
+				// Create node data
+				MaterialItem *mitem = (MaterialItem*)item;
+				MapNode n;
+				n.d = mitem->getMaterial();
+				if(content_directional(n.d))
+					n.dir = packDir(p_under - p_over);
+
+#if 1
 				// Create packet
 				u32 replysize = 8 + MapNode::serializedLength(peer_ser_ver);
 				SharedBuffer<u8> reply(replysize);
@@ -1618,6 +1609,20 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 				// Send as reliable
 				m_con.SendToAll(0, reply, true);
 				
+				/*
+					Handle inventory
+				*/
+				if(g_settings.getBool("creative_mode") == false)
+				{
+					// Remove from inventory and send inventory
+					if(mitem->getCount() == 1)
+						player->inventory.deleteItem(item_i);
+					else
+						mitem->remove(1);
+					// Send inventory
+					SendInventory(peer_id);
+				}
+				
 				/*
 					Add node.
 
@@ -1625,6 +1630,49 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 				*/
 				core::map<v3s16, MapBlock*> modified_blocks;
 				m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks);
+#endif
+#if 0
+				/*
+					Handle inventory
+				*/
+				if(g_settings.getBool("creative_mode") == false)
+				{
+					// Remove from inventory and send inventory
+					if(mitem->getCount() == 1)
+						player->inventory.deleteItem(item_i);
+					else
+						mitem->remove(1);
+					// Send inventory
+					SendInventory(peer_id);
+				}
+
+				/*
+					Add node.
+
+					This takes some time so it is done after the quick stuff
+				*/
+				core::map<v3s16, MapBlock*> modified_blocks;
+				m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks);
+
+				/*
+					Set the modified blocks unsent for all the clients
+				*/
+				
+				//JMutexAutoLock lock2(m_con_mutex);
+
+				for(core::map<u16, RemoteClient*>::Iterator
+						i = m_clients.getIterator();
+						i.atEnd() == false; i++)
+				{
+					RemoteClient *client = i.getNode()->getValue();
+					
+					if(modified_blocks.size() > 0)
+					{
+						// Remove block from sent history
+						client->SetBlocksNotSent(modified_blocks);
+					}
+				}
+#endif
 				
 				/*
 					Update water
@@ -1634,6 +1682,8 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 				// This also adds it to m_flow_active_nodes if appropriate
 
 				MapVoxelManipulator v(&m_env.getMap());
+				v.m_disable_water_climb =
+						g_settings.getBool("disable_water_climb");
 				
 				VoxelArea area(p_over-v3s16(1,1,1), p_over+v3s16(1,1,1));
 
@@ -1825,6 +1875,8 @@ void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver)
 	writeS16(&reply[4], p.Y);
 	writeS16(&reply[6], p.Z);
 	memcpy(&reply[8], *blockdata, blockdata.getSize());
+
+	//dstream<<"Sending block: packet size: "<<replysize<<std::endl;
 	
 	/*
 		Send packet
@@ -1832,107 +1884,6 @@ void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver)
 	m_con.Send(peer_id, 1, reply, true);
 }
 
-/*void Server::SendBlock(u16 peer_id, MapBlock *block, u8 ver)
-{
-	JMutexAutoLock conlock(m_con_mutex);
-	
-	SendBlockNoLock(peer_id, block, ver);
-}*/
-
-#if 0
-void Server::SendSectorMeta(u16 peer_id, core::list<v2s16> ps, u8 ver)
-{
-	DSTACK(__FUNCTION_NAME);
-	dstream<<"Server sending sector meta of "
-			<<ps.getSize()<<" sectors"<<std::endl;
-
-	core::list<v2s16>::Iterator i = ps.begin();
-	core::list<v2s16> sendlist;
-	for(;;)
-	{
-		if(sendlist.size() == 255 || i == ps.end())
-		{
-			if(sendlist.size() == 0)
-				break;
-			/*
-				[0] u16 command
-				[2] u8 sector count
-				[3...] v2s16 pos + sector metadata
-			*/
-			std::ostringstream os(std::ios_base::binary);
-			u8 buf[4];
-
-			writeU16(buf, TOCLIENT_SECTORMETA);
-			os.write((char*)buf, 2);
-
-			writeU8(buf, sendlist.size());
-			os.write((char*)buf, 1);
-
-			for(core::list<v2s16>::Iterator
-					j = sendlist.begin();
-					j != sendlist.end(); j++)
-			{
-				// Write position
-				writeV2S16(buf, *j);
-				os.write((char*)buf, 4);
-				
-				/*
-					Write ClientMapSector metadata
-				*/
-
-				/*
-					[0] u8 serialization version
-					[1] s16 corners[0]
-					[3] s16 corners[1]
-					[5] s16 corners[2]
-					[7] s16 corners[3]
-					size = 9
-					
-					In which corners are in these positions
-					v2s16(0,0),
-					v2s16(1,0),
-					v2s16(1,1),
-					v2s16(0,1),
-				*/
-
-				// Write version
-				writeU8(buf, ver);
-				os.write((char*)buf, 1);
-
-				// Write corners
-				// TODO: Get real values
-				s16 corners[4];
-				((ServerMap&)m_env.getMap()).getSectorCorners(*j, corners);
-
-				writeS16(buf, corners[0]);
-				os.write((char*)buf, 2);
-				writeS16(buf, corners[1]);
-				os.write((char*)buf, 2);
-				writeS16(buf, corners[2]);
-				os.write((char*)buf, 2);
-				writeS16(buf, corners[3]);
-				os.write((char*)buf, 2);
-			}
-
-			SharedBuffer<u8> data((u8*)os.str().c_str(), os.str().size());
-
-			/*dstream<<"Server::SendSectorMeta(): sending packet"
-					" with "<<sendlist.size()<<" sectors"<<std::endl;*/
-
-			m_con.Send(peer_id, 1, data, true);
-
-			if(i == ps.end())
-				break;
-
-			sendlist.clear();
-		}
-
-		sendlist.push_back(*i);
-		i++;
-	}
-}
-#endif
-
 core::list<PlayerInfo> Server::getPlayerInfo()
 {
 	DSTACK(__FUNCTION_NAME);
@@ -2039,11 +1990,11 @@ void Server::peerAdded(con::Peer *peer)
 		if(g_settings.getBool("creative_mode"))
 		{
 			// Give all materials
-			assert(USEFUL_MATERIAL_COUNT <= PLAYER_INVENTORY_SIZE);
-			for(u16 i=0; i<USEFUL_MATERIAL_COUNT; i++)
+			assert(USEFUL_CONTENT_COUNT <= PLAYER_INVENTORY_SIZE);
+			for(u16 i=0; i<USEFUL_CONTENT_COUNT; i++)
 			{
 				// Skip some materials
-				if(i == MATERIAL_OCEAN)
+				if(i == CONTENT_OCEAN)
 					continue;
 
 				InventoryItem *item = new MaterialItem(i, 1);
@@ -2272,4 +2223,27 @@ RemoteClient* Server::getClient(u16 peer_id)
 	return n->getValue();
 }
 
+void Server::UpdateBlockWaterPressure(MapBlock *block,
+			core::map<v3s16, MapBlock*> &modified_blocks)
+{
+	MapVoxelManipulator v(&m_env.getMap());
+	v.m_disable_water_climb =
+			g_settings.getBool("disable_water_climb");
+	
+	VoxelArea area(block->getPosRelative(),
+			block->getPosRelative() + v3s16(1,1,1)*(MAP_BLOCKSIZE-1));
+
+	try
+	{
+		v.updateAreaWaterPressure(area, m_flow_active_nodes);
+	}
+	catch(ProcessingLimitException &e)
+	{
+		dstream<<"Processing limit reached (1)"<<std::endl;
+	}
+	
+	v.blitBack(modified_blocks);
+}
+	
+
 
diff --git a/src/server.h b/src/server.h
index 82e9136b5..273a7d5ef 100644
--- a/src/server.h
+++ b/src/server.h
@@ -275,7 +275,7 @@ class RemoteClient
 	u8 pending_serialization_version;
 
 	RemoteClient():
-		m_time_from_building(0.0)
+		m_time_from_building(9999)
 		//m_num_blocks_in_emerge_queue(0)
 	{
 		peer_id = 0;
@@ -450,10 +450,18 @@ class Server : public con::PeerHandler
 	
 	// When called, connection mutex should be locked
 	RemoteClient* getClient(u16 peer_id);
+
+	/*
+		Update water pressure.
+		This also adds suitable nodes to active_nodes.
+
+		environment has to be locked when calling.
+	*/
+	void UpdateBlockWaterPressure(MapBlock *block,
+			core::map<v3s16, MapBlock*> &modified_blocks);
 	
 	// NOTE: If connection and environment are both to be locked,
 	// environment shall be locked first.
-
 	JMutex m_env_mutex;
 	Environment m_env;
 
diff --git a/src/test.cpp b/src/test.cpp
index 829aec8c1..726930ce6 100644
--- a/src/test.cpp
+++ b/src/test.cpp
@@ -133,11 +133,11 @@ struct TestMapNode
 		MapNode n;
 
 		// Default values
-		assert(n.d == MATERIAL_AIR);
+		assert(n.d == CONTENT_AIR);
 		assert(n.getLight() == 0);
 		
 		// Transparency
-		n.d = MATERIAL_AIR;
+		n.d = CONTENT_AIR;
 		assert(n.light_propagates() == true);
 		n.d = 0;
 		assert(n.light_propagates() == false);
@@ -243,11 +243,11 @@ struct TestVoxelManipulator
 			MapNode n;
 			//n.pressure = size.Y - y;
 			if(*p == '#')
-				n.d = MATERIAL_STONE;
+				n.d = CONTENT_STONE;
 			else if(*p == '.')
-				n.d = MATERIAL_WATER;
+				n.d = CONTENT_WATER;
 			else if(*p == ' ')
-				n.d = MATERIAL_AIR;
+				n.d = CONTENT_AIR;
 			else
 				assert(0);
 			v.setNode(v3s16(x,y,z), n);
@@ -262,8 +262,12 @@ struct TestVoxelManipulator
 		v.print(dstream, VOXELPRINT_WATERPRESSURE);
 		
 		s16 highest_y = -32768;
-		assert(v.getWaterPressure(v3s16(7, 1, 1), highest_y, 0) == -1);
-		assert(highest_y == 3);
+		/*
+			NOTE: These are commented out because this behaviour is changed
+			      all the time
+		*/
+		//assert(v.getWaterPressure(v3s16(7, 1, 1), highest_y, 0) == -1);
+		//assert(highest_y == 3);
 		/*assert(v.getWaterPressure(v3s16(7, 1, 1), highest_y, 0) == 3);
 		//assert(highest_y == 3);*/
 		
@@ -365,11 +369,11 @@ struct TestMapBlock
 		assert(b.getChangedFlag() == false);
 
 		// All nodes should have been set to
-		// .d=MATERIAL_AIR and .getLight() = 0
+		// .d=CONTENT_AIR and .getLight() = 0
 		for(u16 z=0; z<MAP_BLOCKSIZE; z++)
 			for(u16 y=0; y<MAP_BLOCKSIZE; y++)
 				for(u16 x=0; x<MAP_BLOCKSIZE; x++){
-					assert(b.getNode(v3s16(x,y,z)).d == MATERIAL_AIR);
+					assert(b.getNode(v3s16(x,y,z)).d == CONTENT_AIR);
 					assert(b.getNode(v3s16(x,y,z)).getLight() == 0);
 				}
 		
@@ -385,7 +389,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 == MATERIAL_AIR);
+		assert(n.d == CONTENT_AIR);
 
 		// ...but outside the block they should be invalid
 		assert(b.isValidPositionParent(v3s16(-121,2341,0)) == false);
@@ -420,8 +424,8 @@ struct TestMapBlock
 		n.d = 4;
 		b.setNode(p, n);
 		assert(b.getNode(p).d == 4);
-		assert(b.getNodeMaterial(p) == 4);
-		assert(b.getNodeMaterial(v3s16(-1,-1,0)) == 5);
+		assert(b.getNodeTile(p) == 4);
+		assert(b.getNodeTile(v3s16(-1,-1,0)) == 5);
 		
 		/*
 			propagateSunlight()
@@ -442,7 +446,7 @@ struct TestMapBlock
 			*/
 			parent.position_valid = true;
 			b.setIsUnderground(false);
-			parent.node.d = MATERIAL_AIR;
+			parent.node.d = CONTENT_AIR;
 			parent.node.setLight(LIGHT_SUN);
 			core::map<v3s16, bool> light_sources;
 			// The bottom block is invalid, because we have a shadowing node
@@ -493,7 +497,7 @@ struct TestMapBlock
 				for(u16 y=0; y<MAP_BLOCKSIZE; y++){
 					for(u16 x=0; x<MAP_BLOCKSIZE; x++){
 						MapNode n;
-						n.d = MATERIAL_AIR;
+						n.d = CONTENT_AIR;
 						n.setLight(0);
 						b.setNode(v3s16(x,y,z), n);
 					}
diff --git a/src/utility.h b/src/utility.h
index c9b13546c..d22e1d651 100644
--- a/src/utility.h
+++ b/src/utility.h
@@ -32,6 +32,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include <fstream>
 #include <string>
 #include <sstream>
+#include <jmutex.h>
+#include <jmutexautolock.h>
 
 extern const v3s16 g_26dirs[26];
 
@@ -808,5 +810,45 @@ class Settings
 	core::map<std::string, std::string> m_settings;
 };
 
+/*
+	A thread-safe texture cache.
+
+	This is used so that irrlicht doesn't get called from many threads
+*/
+
+class TextureCache
+{
+public:
+	TextureCache()
+	{
+		m_mutex.Init();
+		assert(m_mutex.IsInitialized());
+	}
+	
+	void set(std::string name, video::ITexture *texture)
+	{
+		JMutexAutoLock lock(m_mutex);
+
+		m_textures[name] = texture;
+	}
+	
+	video::ITexture* get(std::string name)
+	{
+		JMutexAutoLock lock(m_mutex);
+
+		core::map<std::string, video::ITexture*>::Node *n;
+		n = m_textures.find(name);
+
+		if(n != NULL)
+			return n->getValue();
+
+		return NULL;
+	}
+
+private:
+	core::map<std::string, video::ITexture*> m_textures;
+	JMutex m_mutex;
+};
+
 #endif
 
diff --git a/src/voxel.cpp b/src/voxel.cpp
index cdd41a14f..3df29dc68 100644
--- a/src/voxel.cpp
+++ b/src/voxel.cpp
@@ -41,6 +41,7 @@ VoxelManipulator::VoxelManipulator():
 	m_data(NULL),
 	m_flags(NULL)
 {
+	m_disable_water_climb = false;
 }
 
 VoxelManipulator::~VoxelManipulator()
@@ -103,13 +104,13 @@ void VoxelManipulator::print(std::ostream &o, VoxelPrintMode mode)
 					}
 					else if(mode == VOXELPRINT_WATERPRESSURE)
 					{
-						if(m == MATERIAL_WATER)
+						if(m == CONTENT_WATER)
 						{
 							c = 'w';
 							if(pr <= 9)
 								c = pr + '0';
 						}
-						else if(m == MATERIAL_AIR)
+						else if(liquid_replaces_content(m))
 						{
 							c = ' ';
 						}
@@ -249,7 +250,7 @@ void VoxelManipulator::interpolate(VoxelArea area)
 		
 		s16 total = 0;
 		s16 airness = 0;
-		u8 m = MATERIAL_IGNORE;
+		u8 m = CONTENT_IGNORE;
 
 		for(s16 i=0; i<8; i++)
 		//for(s16 i=0; i<26; i++)
@@ -263,17 +264,17 @@ void VoxelManipulator::interpolate(VoxelArea area)
 
 			MapNode &n = m_data[m_area.index(p2)];
 
-			airness += (n.d == MATERIAL_AIR) ? 1 : -1;
+			airness += (n.d == CONTENT_AIR) ? 1 : -1;
 			total++;
 
-			if(m == MATERIAL_IGNORE && n.d != MATERIAL_AIR)
+			if(m == CONTENT_IGNORE && n.d != CONTENT_AIR)
 				m = n.d;
 		}
 
 		// 1 if air, 0 if not
-		buf[area.index(p)] = airness > -total/2 ? MATERIAL_AIR : m;
-		//buf[area.index(p)] = airness > -total ? MATERIAL_AIR : m;
-		//buf[area.index(p)] = airness >= -7 ? MATERIAL_AIR : m;
+		buf[area.index(p)] = airness > -total/2 ? CONTENT_AIR : m;
+		//buf[area.index(p)] = airness > -total ? CONTENT_AIR : m;
+		//buf[area.index(p)] = airness >= -7 ? CONTENT_AIR : m;
 	}
 
 	for(s32 z=area.MinEdge.Z; z<=area.MaxEdge.Z; z++)
@@ -366,13 +367,14 @@ int VoxelManipulator::getWaterPressure(v3s16 p, s16 &highest_y, int recur_count)
 			continue;
 		MapNode &n = m_data[m_area.index(p2)];
 		// Ignore non-liquid nodes
-		if(material_liquid(n.d) == false)
+		if(content_liquid(n.d) == false)
 			continue;
 
 		int pr;
 
 		// If at ocean surface
-		if(n.pressure == 1 && n.d == MATERIAL_OCEAN)
+		if(n.pressure == 1 && n.d == CONTENT_OCEAN)
+		//if(n.pressure == 1) // Causes glitches but is fast
 		{
 			pr = 1;
 		}
@@ -463,12 +465,15 @@ void VoxelManipulator::spreadWaterPressure(v3s16 p, int pr,
 			NOTE: Do not remove anything from there. We cannot know
 			      here if some other neighbor of it causes flow.
 		*/
-		if(n.d == MATERIAL_AIR)
+		if(liquid_replaces_content(n.d))
 		{
 			bool pressure_causes_flow = false;
-			// If block is at top
+			// If empty block is at top
 			if(i == 0)
 			{
+				if(m_disable_water_climb)
+					continue;
+				
 				//if(pr >= PRESERVE_WATER_VOLUME ? 3 : 2)
 				if(pr >= 3)
 					pressure_causes_flow = true;
@@ -495,7 +500,7 @@ void VoxelManipulator::spreadWaterPressure(v3s16 p, int pr,
 		}
 
 		// Ignore non-liquid nodes
-		if(material_liquid(n.d) == false)
+		if(content_liquid(n.d) == false)
 			continue;
 
 		int pr2 = pr;
@@ -511,6 +516,12 @@ void VoxelManipulator::spreadWaterPressure(v3s16 p, int pr,
 			if(pr2 < 255)
 				pr2++;
 		}
+
+		/*if(m_disable_water_climb)
+		{
+			if(pr2 > 3)
+				pr2 = 3;
+		}*/
 		
 		// Ignore if correct pressure is already set and is not on
 		// request_area.
@@ -556,7 +567,7 @@ void VoxelManipulator::updateAreaWaterPressure(VoxelArea a,
 			continue;
 		MapNode &n = m_data[m_area.index(p)];
 		// Ignore non-liquid nodes
-		if(material_liquid(n.d) == false)
+		if(content_liquid(n.d) == false)
 			continue;
 		
 		if(checked2_clear == false)
@@ -654,14 +665,18 @@ bool VoxelManipulator::flowWater(v3s16 removed_pos,
 		if(f & (VOXELFLAG_INEXISTENT | VOXELFLAG_CHECKED))
 			return false;
 		MapNode &n = m_data[m_area.index(removed_pos)];
-		// Water can move only to air
-		if(n.d != MATERIAL_AIR)
+		// Ignore nodes to which the water can't go
+		if(liquid_replaces_content(n.d) == false)
 			return false;
 	}
 	
 	s32 i;
 	for(i=0; i<6; i++)
 	{
+		// Don't raise water from bottom
+		if(m_disable_water_climb && i == 5)
+			continue;
+
 		p = removed_pos + v3s16(s1*dirs[i].X, dirs[i].Y, s2*dirs[i].Z);
 
 		u8 f = m_flags[m_area.index(p)];
@@ -670,7 +685,7 @@ bool VoxelManipulator::flowWater(v3s16 removed_pos,
 			continue;
 		MapNode &n = m_data[m_area.index(p)];
 		// Only liquid nodes can move
-		if(material_liquid(n.d) == false)
+		if(content_liquid(n.d) == false)
 			continue;
 		// If block is at top, select it always
 		if(i == 0)
@@ -704,7 +719,7 @@ bool VoxelManipulator::flowWater(v3s16 removed_pos,
 	u8 m = m_data[m_area.index(p)].d;
 	u8 f = m_flags[m_area.index(p)];
 
-	if(m == MATERIAL_OCEAN)
+	if(m == CONTENT_OCEAN)
 		from_ocean = true;
 
 	// Move air bubble if not taking water from ocean
@@ -714,9 +729,23 @@ bool VoxelManipulator::flowWater(v3s16 removed_pos,
 		m_flags[m_area.index(p)] = m_flags[m_area.index(removed_pos)];
 	}
 	
+	/*
+		This has to be done to copy the brightness of a light source
+		correctly. Otherwise unspreadLight will fuck up when water
+		has replaced a light source.
+	*/
+	u8 light = m_data[m_area.index(removed_pos)].getLight();
+
 	m_data[m_area.index(removed_pos)].d = m;
 	m_flags[m_area.index(removed_pos)] = f;
 
+	m_data[m_area.index(removed_pos)].setLight(light);
+	
+	/*// NOTE: HACK: This has to be set to LIGHT_MAX so that
+	// unspreadLight will clear all light that came from this node.
+	// Otherwise there will be weird bugs
+	m_data[m_area.index(removed_pos)].setLight(LIGHT_MAX);*/
+
 	// Mark removed_pos checked
 	m_flags[m_area.index(removed_pos)] |= VOXELFLAG_CHECKED;
 
@@ -728,7 +757,7 @@ bool VoxelManipulator::flowWater(v3s16 removed_pos,
 	
 	/*
 	NOTE: This does not work as-is
-	if(m == MATERIAL_OCEAN)
+	if(m == CONTENT_OCEAN)
 	{
 		// If block was raised to surface, increase pressure of
 		// source node
@@ -795,6 +824,10 @@ bool VoxelManipulator::flowWater(v3s16 removed_pos,
 	// They are checked in reverse order compared to the previous loop.
 	for(s32 i=5; i>=0; i--)
 	{
+		// Don't try to flow to top
+		if(m_disable_water_climb && i == 0)
+			continue;
+
 		//v3s16 p = removed_pos + dirs[i];
 		p = removed_pos + v3s16(s1*dirs[i].X, dirs[i].Y, s2*dirs[i].Z);
 
@@ -804,7 +837,7 @@ bool VoxelManipulator::flowWater(v3s16 removed_pos,
 			continue;
 		MapNode &n = m_data[m_area.index(p)];
 		// Water can only move to air
-		if(n.d != MATERIAL_AIR)
+		if(liquid_replaces_content(n.d) == false)
 			continue;
 			
 		// Flow water to node
diff --git a/src/voxel.h b/src/voxel.h
index 411cf4376..472f0740d 100644
--- a/src/voxel.h
+++ b/src/voxel.h
@@ -502,6 +502,12 @@ class VoxelManipulator /*: public NodeContainer*/
 	//TODO: Would these make any speed improvement?
 	//bool m_pressure_route_valid;
 	//v3s16 m_pressure_route_surface;
+
+	/*
+		Some settings
+	*/
+	bool m_disable_water_climb;
+
 private:
 };
 
-- 
GitLab