diff --git a/minetest.conf.example b/minetest.conf.example
index e6c9832a..1d2606ec 100644
--- a/minetest.conf.example
+++ b/minetest.conf.example
@@ -53,3 +53,6 @@
 #max_simultaneous_block_sends_per_client = 1
 #max_simultaneous_block_sends_server_total = 4
 
+#max_block_send_distance = 8
+#max_block_generate_distance = 5
+
diff --git a/src/main.cpp b/src/main.cpp
index 73ef3795..fd91ab35 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -179,200 +179,19 @@ SUGG: MovingObject::move and Player::move are basically the same.
 TODO: Transfer sign texts as metadata of block and not as data of
       object
 
+SUGG: Implement a "Fast check queue" (a queue with a map for checking
+      if something is already in it)
+      - TODO: Use it in active block queue in water flowing
+
+TODO: Proper looking torches.
+      - Signs could be done in the same way?
+
 Doing now:
 ======================================================================
 
-Water dynamics pseudo-code (block = MapNode):
-SUGG: Create separate flag table in VoxelManipulator to allow fast
-clearing of "modified" flags
-
-neighborCausedPressure(pos):
-	pressure = 0
-	dirs = {down, left, right, back, front, up}
-	for d in dirs:
-		pos2 = pos + d
-		p = block_at(pos2).pressure
-		if d.Y == 1 and p > min:
-			p -= 1
-		if d.Y == -1 and p < max:
-			p += 1
-		if p > pressure:
-			pressure = p
-	return pressure
-
-# This should somehow update all changed pressure values
-# in an unknown body of water
-updateWaterPressure(pos):
-	TODO
-
-FIXME: This goes in an indefinite loop when there is an underwater
-chamber like this:
-
-#111######
-#222##22##
-#33333333x<- block removed from here
-##########
-
-#111######
-#222##22##
-#3333333x1
-##########
-
-#111######
-#222##22##
-#333333x11
-##########
-
-#111######
-#222##2x##
-#333333333
-##########
-
-#111######
-#222##x2##
-#333333333
-##########
-
-Now, consider moving to the last block not allowed.
-
-Consider it a 3D case with a depth of 2. We're now at this situation.
-Note the additional blocking ## in the second depth plane.
-
-z=1         z=2
-#111######  #111######
-#222##x2##  #222##22##
-#333333333  #33333##33
-##########  ##########
-
-#111######  #111######
-#222##22##  #222##x2##
-#333333333  #33333##33
-##########  ##########
-
-#111######  #111######
-#222##22##  #222##2x##
-#333333333  #33333##33  
-##########  ##########
-
-Now there is nowhere to go, without going to an already visited block,
-but the pressure calculated in here from neighboring blocks is >= 2,
-so it is not the final ending.
-
-We will back up to a state where there is somewhere to go to.
-It is this state:
-
-#111######  #111######
-#222##22##  #222##22##
-#333333x33  #33333##33
-##########  ##########
-
-Then just go on, avoiding already visited blocks:
-
-#111######  #111######
-#222##22##  #222##22##
-#33333x333  #33333##33
-##########  ##########
-
-#111######  #111######
-#222##22##  #222##22##
-#3333x3333  #33333##33
-##########  ##########
-
-#111######  #111######
-#222##22##  #222##22##
-#333x33333  #33333##33
-##########  ##########
-
-#111######  #111######
-#222##22##  #222##22##
-#33x333333  #33333##33
-##########  ##########
-
-#111######  #111######
-#22x##22##  #222##22##
-#333333333  #33333##33
-##########  ##########
-
-#11x######  #111######
-#222##22##  #222##22##
-#333333333  #33333##33
-##########  ##########
-
-"Blob". the air bubble finally got out of the water.
-Then return recursively to a state where there is air next to water,
-clear the visit flags and feed the neighbor of the water recursively
-to the algorithm.
-
-#11 ######  #111######
-#222##22##  #222##22##
-#333333333x #33333##33
-##########  ##########
-
-#11 ######  #111######
-#222##22##  #222##22##
-#33333333x3 #33333##33
-##########  ##########
-
-...and so on.
-
-
-# removed_pos: a position that has been changed from something to air
-flowWater(removed_pos):
-	dirs = {top, left, right, back, front, bottom}
-	selected_dir = None
-	for d in dirs:
-		b2 = removed_pos + d
-
-		# Ignore positions that don't have water
-		if block_at(b2) != water:
-			continue
-
-		# Ignore positions that have already been checked
-		if block_at(b2).checked:
-			continue
-
-		# If block is at top, select it always.
-		if d.Y == 1:
-			selected_dir = d
-			break
-
-		# If block is at bottom, select it if it has enough pressure.
-		# >= 3 needed for stability (and sanity)
-		if d.Y == -1:
-			if block_at(b2).pressure >= 3:
-				selected_dir = d
-				break
-			continue
-		
-		# Else block is at some side. select it if it has enough pressure.
-		if block_at(b2).pressure >= 2:
-			selected_dir = d
-			break
-	
-	# If there is nothing to do anymore, return.
-	if selected_dir == None
-		return
-	
-	b2 = removed_pos + selected_dir
-	
-	# Move block
-	set_block(removed_pos, block_at(b2))
-	set_block(b2, air_block)
-	
-	# Update pressure
-	updateWaterPressure(removed_pos)
-	
-	# Flow water to the newly created empty position
-	flowWater(b2)
-
-	# Check empty positions around and try flowing water to them
-	for d in dirs:
-		b3 = removed_pos + d
-		# Ignore positions that are not air
-		if block_at(b3) is not air:
-			continue
-		flowWater(b3)
-
+TODO: A system for showing some nodes in some other way than cubes
+      - Needed for torches
+	  - Also for signs, stairs, etc
 
 ======================================================================
 
@@ -448,7 +267,8 @@ const char *g_material_filenames[MATERIALS_COUNT] =
 	"../data/leaves.png",
 	"../data/grass_footsteps.png",
 	"../data/mese.png",
-	"../data/mud.png"
+	"../data/mud.png",
+	"../data/water.png", // ocean
 };
 
 video::SMaterial g_materials[MATERIALS_COUNT];
@@ -499,6 +319,8 @@ void set_default_settings()
 	g_settings.set("name", "");
 	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");
 
 	// Server stuff
 	g_settings.set("creative_mode", "false");
@@ -1489,13 +1311,12 @@ int main(int argc, char *argv[])
 		g_materials[i].setFlag(video::EMF_BILINEAR_FILTER, false);
 		//g_materials[i].setFlag(video::EMF_ANISOTROPIC_FILTER, false);
 		//g_materials[i].setFlag(video::EMF_FOG_ENABLE, true);
-		if(i == MATERIAL_WATER)
-		{
-			g_materials[i].MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
-			//g_materials[i].MaterialType = video::EMT_TRANSPARENT_ADD_COLOR;
-		}
 	}
 
+	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_mesh_materials[0].setTexture(0, driver->getTexture("../data/water.png"));
 	g_mesh_materials[1].setTexture(0, driver->getTexture("../data/grass.png"));
 	g_mesh_materials[2].setTexture(0, driver->getTexture("../data/stone.png"));
diff --git a/src/map.cpp b/src/map.cpp
index 88cb0f3f..1a7cd9bb 100644
--- a/src/map.cpp
+++ b/src/map.cpp
@@ -1819,7 +1819,8 @@ MapBlock * ServerMap::emergeBlock(
 				// If under water level, it's water
 				if(real_y < WATER_LEVEL)
 				{
-					n.d = MATERIAL_WATER;
+					//n.d = MATERIAL_WATER;
+					n.d = MATERIAL_OCEAN;
 					n.setLight(diminish_light(LIGHT_SUN, WATER_LEVEL-real_y+1));
 				}
 				// else air
@@ -2731,34 +2732,6 @@ void ClientMap::renderMap(video::IVideoDriver* driver,
 	DSTACK(__FUNCTION_NAME);
 
 	bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT;
-#if 0
-	/*
-		Draw master heightmap mesh
-	*/
-	
-	{
-		JMutexAutoLock lock(mesh_mutex);
-		if(mesh != NULL)
-		{
-			u32 c = mesh->getMeshBufferCount();
-
-			for(u32 i=0; i<c; i++)
-			{
-				scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
-				const video::SMaterial& material = buf->getMaterial();
-				video::IMaterialRenderer* rnd =
-						driver->getMaterialRenderer(material.MaterialType);
-				bool transparent = (rnd && rnd->isTransparent());
-				// Render transparent on transparent pass and likewise.
-				if(transparent == is_transparent_pass)
-				{
-					driver->setMaterial(buf->getMaterial());
-					driver->drawMeshBuffer(buf);
-				}
-			}
-		}
-	}
-#endif
 
 	/*
 		Get time for measuring timeout.
@@ -3162,7 +3135,8 @@ MapVoxelManipulator::~MapVoxelManipulator()
 			<<std::endl;
 }
 
-void MapVoxelManipulator::emerge(VoxelArea a)
+#if 1
+void MapVoxelManipulator::emerge(VoxelArea a, s32 caller_id)
 {
 	TimeTaker timer1("emerge", g_device, &emerge_time);
 
@@ -3190,8 +3164,11 @@ void MapVoxelManipulator::emerge(VoxelArea a)
 		{
 			TimeTaker timer1("emerge load", g_device, &emerge_load_time);
 
-			dstream<<"Loading block ("<<p.X<<","<<p.Y<<","<<p.Z<<")"
-					<<std::endl;
+			/*dstream<<"Loading block (caller_id="<<caller_id<<")"
+					<<" ("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+					<<" wanted area: ";
+			a.print(dstream);
+			dstream<<std::endl;*/
 			
 			MapBlock *block = m_map->getBlockNoCreate(p);
 			if(block->isDummy())
@@ -3221,6 +3198,43 @@ void MapVoxelManipulator::emerge(VoxelArea a)
 
 	//dstream<<"emerge done"<<std::endl;
 }
+#endif
+
+#if 0
+void MapVoxelManipulator::emerge(VoxelArea a)
+{
+	TimeTaker timer1("emerge", g_device, &emerge_time);
+	
+	v3s16 size = a.getExtent();
+	
+	VoxelArea padded = a;
+	padded.pad(m_area.getExtent() / 4);
+	addArea(padded);
+
+	for(s16 z=0; z<size.Z; z++)
+	for(s16 y=0; y<size.Y; y++)
+	for(s16 x=0; x<size.X; x++)
+	{
+		v3s16 p(x,y,z);
+		s32 i = m_area.index(a.MinEdge + p);
+		// Don't touch nodes that have already been loaded
+		if(!(m_flags[i] & VOXELFLAG_NOT_LOADED))
+			continue;
+		try
+		{
+			TimeTaker timer1("emerge load", g_device, &emerge_load_time);
+			MapNode n = m_map->getNode(a.MinEdge + p);
+			m_data[i] = n;
+			m_flags[i] = 0;
+		}
+		catch(InvalidPositionException &e)
+		{
+			m_flags[i] = VOXELFLAG_INEXISTENT;
+		}
+	}
+}
+#endif
+
 
 /*
 	TODO: Add an option to only update eg. water and air nodes.
@@ -3230,6 +3244,9 @@ void MapVoxelManipulator::emerge(VoxelArea a)
 void MapVoxelManipulator::blitBack
 		(core::map<v3s16, MapBlock*> & modified_blocks)
 {
+	if(m_area.getExtent() == v3s16(0,0,0))
+		return;
+	
 	TimeTaker timer1("blitBack", g_device);
 	
 	/*
diff --git a/src/map.h b/src/map.h
index 62d1f8ae..6944107d 100644
--- a/src/map.h
+++ b/src/map.h
@@ -603,15 +603,18 @@ public:
 		m_loaded_blocks.clear();
 	}
 
-	virtual void emerge(VoxelArea a);
+	virtual void emerge(VoxelArea a, s32 caller_id=-1);
 
 	void blitBack(core::map<v3s16, MapBlock*> & modified_blocks);
 
 private:
 	Map *m_map;
-	// bool is dummy value
-	// SUGG: How 'bout an another VoxelManipulator for storing the
-	//       information about which block is loaded?
+	/*
+		NOTE: This might be used or not
+		bool is dummy value
+		SUGG: How 'bout an another VoxelManipulator for storing the
+		      information about which block is loaded?
+	*/
 	core::map<v3s16, bool> m_loaded_blocks;
 };
 
diff --git a/src/mapblock.cpp b/src/mapblock.cpp
index d2c32329..0f2eba85 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)
+	if(material == MATERIAL_WATER || material == MATERIAL_OCEAN)
 	{
 		alpha = 128;
 	}
@@ -173,13 +173,14 @@ u8 MapBlock::getFaceLight(v3s16 p, v3s16 face_dir)
 
 /*
 	Gets node material from any place relative to block.
-	Returns MATERIAL_AIR if doesn't exist.
+	Returns MATERIAL_IGNORE if doesn't exist or should not be drawn.
 */
 u8 MapBlock::getNodeMaterial(v3s16 p)
 {
 	try{
 		MapNode n = getNodeParent(p);
-		return n.d;
+		
+		return content_cube_material(n.d);
 	}
 	catch(InvalidPositionException &e)
 	{
@@ -470,46 +471,6 @@ void MapBlock::updateMesh()
 		
 		collector.fillMesh(mesh_new);
 
-#if 0
-		scene::IMeshBuffer *buf = NULL;
-
-		core::list<FastFace*>::Iterator i = fastfaces_new->begin();
-
-		// MATERIAL_AIR shouldn't be used by any face
-		u8 material_in_use = MATERIAL_AIR;
-
-		for(; i != fastfaces_new->end(); i++)
-		{
-			FastFace *f = *i;
-			
-			if(f->material != material_in_use || buf == NULL)
-			{
-				// Try to get a meshbuffer associated with the material
-				buf = mesh_new->getMeshBuffer(g_materials[f->material]);
-				// If not found, create one
-				if(buf == NULL)
-				{
-					// This is a "Standard MeshBuffer",
-					// it's a typedeffed CMeshBuffer<video::S3DVertex>
-					buf = new scene::SMeshBuffer();
-					// Set material
-					((scene::SMeshBuffer*)buf)->Material = g_materials[f->material];
-					// Use VBO
-					//buf->setHardwareMappingHint(scene::EHM_STATIC);
-					// Add to mesh
-					mesh_new->addMeshBuffer(buf);
-					// Mesh grabbed it
-					buf->drop();
-				}
-				material_in_use = f->material;
-			}
-			
-			u16 new_indices[] = {0,1,2,2,3,0};
-			
-			//buf->append(f->vertices, 4, indices, 6);
-		}
-#endif
-
 		// Use VBO for mesh (this just would set this for ever buffer)
 		//mesh_new->setHardwareMappingHint(scene::EHM_STATIC);
 		
@@ -517,8 +478,11 @@ void MapBlock::updateMesh()
 				<<"and uses "<<mesh_new->getMeshBufferCount()
 				<<" materials (meshbuffers)"<<std::endl;*/
 	}
+	
+	/*
+		Clear temporary FastFaces
+	*/
 
-	// TODO: Get rid of the FastFace stage
 	core::list<FastFace*>::Iterator i;
 	i = fastfaces_new->begin();
 	for(; i != fastfaces_new->end(); i++)
@@ -528,6 +492,18 @@ void MapBlock::updateMesh()
 	fastfaces_new->clear();
 	delete fastfaces_new;
 
+	/*
+		Add special graphics:
+		- torches
+	*/
+
+	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);
+	}
+
 	/*
 		Replace the mesh
 	*/
diff --git a/src/mapnode.h b/src/mapnode.h
index 7502c42d..0d65f30a 100644
--- a/src/mapnode.h
+++ b/src/mapnode.h
@@ -56,6 +56,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 	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
 */
 
 enum Material
@@ -77,6 +79,8 @@ enum Material
 	MATERIAL_MESE,
 
 	MATERIAL_MUD,
+
+	MATERIAL_OCEAN,
 	
 	// This is set to the number of the actual values in this enum
 	USEFUL_MATERIAL_COUNT
@@ -88,7 +92,7 @@ enum Material
 */
 inline bool light_propagates_material(u8 m)
 {
-	return (m == MATERIAL_AIR || m == MATERIAL_LIGHT || m == MATERIAL_WATER);
+	return (m == MATERIAL_AIR || m == MATERIAL_LIGHT || m == MATERIAL_WATER || m == MATERIAL_OCEAN);
 }
 
 /*
@@ -110,7 +114,7 @@ inline u8 material_solidness(u8 m)
 {
 	if(m == MATERIAL_AIR)
 		return 0;
-	if(m == MATERIAL_WATER)
+	if(m == MATERIAL_WATER || m == MATERIAL_OCEAN)
 		return 1;
 	return 2;
 }
@@ -118,29 +122,54 @@ inline u8 material_solidness(u8 m)
 // Objects collide with walkable materials
 inline bool material_walkable(u8 m)
 {
-	return (m != MATERIAL_AIR && m != MATERIAL_WATER);
+	return (m != MATERIAL_AIR && m != MATERIAL_WATER && m != MATERIAL_OCEAN && m != MATERIAL_LIGHT);
 }
 
 // A liquid resists fast movement
 inline bool material_liquid(u8 m)
 {
-	return (m == MATERIAL_WATER);
+	return (m == MATERIAL_WATER || m == MATERIAL_OCEAN);
 }
 
 // Pointable materials can be pointed to in the map
 inline bool material_pointable(u8 m)
 {
-	return (m != MATERIAL_AIR && m != MATERIAL_WATER);
+	return (m != MATERIAL_AIR && m != MATERIAL_WATER && m != MATERIAL_OCEAN);
 }
 
 inline bool material_diggable(u8 m)
 {
-	return (m != MATERIAL_AIR && m != MATERIAL_WATER);
+	return (m != MATERIAL_AIR && m != MATERIAL_WATER && m != MATERIAL_OCEAN);
 }
 
 inline bool material_buildable_to(u8 m)
 {
-	return (m == MATERIAL_AIR || m == MATERIAL_WATER);
+	return (m == MATERIAL_AIR || m == MATERIAL_WATER || m == MATERIAL_OCEAN);
+}
+
+/*
+	As of now, input is a "material" and the output is a "material"
+*/
+inline u8 content_cube_material(u8 c)
+{
+	if(c == MATERIAL_IGNORE || c == MATERIAL_LIGHT)
+		return MATERIAL_AIR;
+	return c;
+}
+
+/*
+	Returns true for materials that form the base ground that
+	follows the main heightmap
+*/
+inline bool is_ground_material(u8 m)
+{
+	return(
+		m == MATERIAL_STONE ||
+		m == MATERIAL_GRASS ||
+		m == MATERIAL_GRASS_FOOTSTEPS ||
+		m == MATERIAL_MESE ||
+		m == MATERIAL_MUD
+	);
 }
 
 /*
@@ -168,21 +197,6 @@ inline u8 face_materials(u8 m1, u8 m2)
 		return 2;
 }
 
-/*
-	Returns true for materials that form the base ground that
-	follows the main heightmap
-*/
-inline bool is_ground_material(u8 m)
-{
-	return(
-		m == MATERIAL_STONE ||
-		m == MATERIAL_GRASS ||
-		m == MATERIAL_GRASS_FOOTSTEPS ||
-		m == MATERIAL_MESE ||
-		m == MATERIAL_MUD
-	);
-}
-
 struct MapNode
 {
 	//TODO: block type to differ from material
@@ -214,7 +228,9 @@ struct MapNode
 
 	bool operator==(const MapNode &other)
 	{
-		return (d == other.d && param == other.param);
+		return (d == other.d
+				&& param == other.param
+				&& pressure == other.pressure);
 	}
 
 	bool light_propagates()
diff --git a/src/server.cpp b/src/server.cpp
index 8bcfe521..e343d594 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -196,6 +196,28 @@ void * EmergeThread::Thread()
 			{
 				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()
@@ -244,12 +266,6 @@ void * EmergeThread::Thread()
 				// Remove block from sent history
 				client->SetBlocksNotSent(modified_blocks);
 			}
-			
-			/*if(q->peer_ids.find(client->peer_id) != NULL)
-			{
-				// Decrement emerge queue count of client
-				client->BlockEmerged();
-			}*/
 		}
 		
 	}
@@ -348,14 +364,8 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
 
 	//bool has_incomplete_blocks = false;
 	
-	/*
-		TODO: Get this from somewhere
-	*/
-	//s16 d_max = 7;
-	s16 d_max = 8;
-
-	//TODO: Get this from somewhere (probably a bigger value)
-	s16 d_max_gen = 5;
+	s16 d_max = g_settings.getS16("max_block_send_distance");
+	s16 d_max_gen = g_settings.getS16("max_block_generate_distance");
 	
 	//dstream<<"Starting from "<<d_start<<std::endl;
 
@@ -998,7 +1008,7 @@ void Server::AsyncRunStep()
 	{
 		static float counter = 0.0;
 		counter += dtime;
-		if(counter >= 1.0)
+		if(counter >= 0.25 && m_flow_active_nodes.size() > 0)
 		{
 		
 		counter = 0.0;
@@ -1011,16 +1021,7 @@ void Server::AsyncRunStep()
 			
 			MapVoxelManipulator v(&m_env.getMap());
 			
-			/*try{
-				v.flowWater(m_flow_active_nodes, 0, false, 20);
-				//v.flowWater(p_under, 0, true, 100);
-			}
-			catch(ProcessingLimitException &e)
-			{
-				dstream<<"Processing limit reached"<<std::endl;
-			}*/
-
-			v.flowWater(m_flow_active_nodes, 0, false, 20);
+			v.flowWater(m_flow_active_nodes, 0, false, 50);
 
 			v.blitBack(modified_blocks);
 
@@ -1059,7 +1060,7 @@ void Server::AsyncRunStep()
 			}
 		}
 
-		}
+		} // interval counter
 	}
 	
 	// Periodically print some info
@@ -1547,7 +1548,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 			}
 			catch(ProcessingLimitException &e)
 			{
-				dstream<<"Processing limit reached"<<std::endl;
+				dstream<<"Processing limit reached (1)"<<std::endl;
 			}
 			
 			v.blitBack(modified_blocks);
@@ -1624,6 +1625,28 @@ 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);
+				
+				/*
+					Update water
+				*/
+				
+				// Update water pressure around modification
+				// This also adds it to m_flow_active_nodes if appropriate
+
+				MapVoxelManipulator v(&m_env.getMap());
+				
+				VoxelArea area(p_over-v3s16(1,1,1), p_over+v3s16(1,1,1));
+
+				try
+				{
+					v.updateAreaWaterPressure(area, m_flow_active_nodes);
+				}
+				catch(ProcessingLimitException &e)
+				{
+					dstream<<"Processing limit reached (1)"<<std::endl;
+				}
+				
+				v.blitBack(modified_blocks);
 			}
 			/*
 				Handle block object items
@@ -2019,6 +2042,10 @@ void Server::peerAdded(con::Peer *peer)
 			assert(USEFUL_MATERIAL_COUNT <= PLAYER_INVENTORY_SIZE);
 			for(u16 i=0; i<USEFUL_MATERIAL_COUNT; i++)
 			{
+				// Skip some materials
+				if(i == MATERIAL_OCEAN)
+					continue;
+
 				InventoryItem *item = new MaterialItem(i, 1);
 				player->inventory.addItem(item);
 			}
diff --git a/src/test.cpp b/src/test.cpp
index ebefb8e3..829aec8c 100644
--- a/src/test.cpp
+++ b/src/test.cpp
@@ -264,11 +264,13 @@ struct TestVoxelManipulator
 		s16 highest_y = -32768;
 		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);*/
 		
 		active_nodes.clear();
 		active_nodes[v3s16(9,1,0)] = 1;
 		//v.flowWater(active_nodes, 0, false);
-		v.flowWater(active_nodes, 0, true);
+		v.flowWater(active_nodes, 0, true, 1000);
 		
 		dstream<<"Final result of flowWater:"<<std::endl;
 		v.print(dstream, VOXELPRINT_WATERPRESSURE);
diff --git a/src/voxel.cpp b/src/voxel.cpp
index b85ba866..cdd41a14 100644
--- a/src/voxel.cpp
+++ b/src/voxel.cpp
@@ -335,23 +335,26 @@ int VoxelManipulator::getWaterPressure(v3s16 p, s16 &highest_y, int recur_count)
 	if(p.Y > highest_y)
 		highest_y = p.Y;
 	
-	recur_count++;
-	if(recur_count > 30)
+	/*if(recur_count > 1000)
 		throw ProcessingLimitException
-				("getWaterPressure recur_count limit reached");
+				("getWaterPressure recur_count limit reached");*/
+	
+	if(recur_count > 10000)
+		return -1;
+	
+	recur_count++;
 
 	v3s16 dirs[6] = {
 		v3s16(0,1,0), // top
-		v3s16(-1,0,0), // left
-		v3s16(1,0,0), // right
-		v3s16(0,0,-1), // front
 		v3s16(0,0,1), // back
+		v3s16(0,0,-1), // front
+		v3s16(1,0,0), // right
+		v3s16(-1,0,0), // left
 		v3s16(0,-1,0), // bottom
 	};
 
 	// Load neighboring nodes
-	// TODO: A bigger area would be better
-	emerge(VoxelArea(p - v3s16(1,1,1), p + v3s16(1,1,1)));
+	emerge(VoxelArea(p - v3s16(1,1,1), p + v3s16(1,1,1)), 1);
 
 	s32 i;
 	for(i=0; i<6; i++)
@@ -367,14 +370,14 @@ int VoxelManipulator::getWaterPressure(v3s16 p, s16 &highest_y, int recur_count)
 			continue;
 
 		int pr;
-		
-		// If at surface
-		/*if(n.pressure == 1)
+
+		// If at ocean surface
+		if(n.pressure == 1 && n.d == MATERIAL_OCEAN)
 		{
 			pr = 1;
 		}
 		// Otherwise recurse more
-		else*/
+		else
 		{
 			pr = getWaterPressure(p2, highest_y, recur_count);
 			if(pr == -1)
@@ -410,10 +413,21 @@ void VoxelManipulator::spreadWaterPressure(v3s16 p, int pr,
 		core::map<v3s16, u8> &active_nodes,
 		int recur_count)
 {
+	//if(recur_count > 10000)
+		/*throw ProcessingLimitException
+				("spreadWaterPressure recur_count limit reached");*/
+	if(recur_count > 10)
+		return;
 	recur_count++;
-	if(recur_count > 10000)
-		throw ProcessingLimitException
-				("spreadWaterPressure recur_count limit reached");
+	
+	/*dstream<<"spreadWaterPressure: p=("
+			<<p.X<<","<<p.Y<<","<<p.Z<<")"
+			<<", oldpr="<<(int)m_data[m_area.index(p)].pressure
+			<<", pr="<<pr
+			<<", recur_count="<<recur_count
+			<<", request_area=";
+	request_area.print(dstream);
+	dstream<<std::endl;*/
 
 	m_flags[m_area.index(p)] |= VOXELFLAG_CHECKED3;
 	m_data[m_area.index(p)].pressure = pr;
@@ -428,7 +442,7 @@ void VoxelManipulator::spreadWaterPressure(v3s16 p, int pr,
 	};
 
 	// Load neighboring nodes
-	emerge(VoxelArea(p - v3s16(1,1,1), p + v3s16(1,1,1)));
+	emerge(VoxelArea(p - v3s16(1,1,1), p + v3s16(1,1,1)), 2);
 
 	s32 i;
 	for(i=0; i<6; i++)
@@ -455,6 +469,7 @@ void VoxelManipulator::spreadWaterPressure(v3s16 p, int pr,
 			// If block is at top
 			if(i == 0)
 			{
+				//if(pr >= PRESERVE_WATER_VOLUME ? 3 : 2)
 				if(pr >= 3)
 					pressure_causes_flow = true;
 			}
@@ -466,6 +481,7 @@ void VoxelManipulator::spreadWaterPressure(v3s16 p, int pr,
 			// If block is at side
 			else
 			{
+				//if(pr >= PRESERVE_WATER_VOLUME ? 2 : 1)
 				if(pr >= 2)
 					pressure_causes_flow = true;
 			}
@@ -497,7 +513,10 @@ void VoxelManipulator::spreadWaterPressure(v3s16 p, int pr,
 		}
 		
 		// Ignore if correct pressure is already set and is not on
-		// request_area
+		// request_area.
+		// Thus, request_area can be used for updating as much
+		// pressure info in some area as possible to possibly
+		// make some calls to getWaterPressure unnecessary.
 		if(n.pressure == pr2 && request_area.contains(p2) == false)
 			continue;
 
@@ -512,7 +531,7 @@ void VoxelManipulator::updateAreaWaterPressure(VoxelArea a,
 	TimeTaker timer("updateAreaWaterPressure", g_device,
 			&updateareawaterpressure_time);
 
-	emerge(a);
+	emerge(a, 3);
 	
 	bool checked2_clear = false;
 	
@@ -596,20 +615,21 @@ void VoxelManipulator::updateAreaWaterPressure(VoxelArea a,
 bool VoxelManipulator::flowWater(v3s16 removed_pos,
 		core::map<v3s16, u8> &active_nodes,
 		int recursion_depth, bool debugprint,
-		int *counter, int counterlimit)
+		u32 stoptime)
 {
 	v3s16 dirs[6] = {
 		v3s16(0,1,0), // top
-		v3s16(-1,0,0), // left
-		v3s16(1,0,0), // right
 		v3s16(0,0,-1), // front
 		v3s16(0,0,1), // back
+		v3s16(-1,0,0), // left
+		v3s16(1,0,0), // right
 		v3s16(0,-1,0), // bottom
 	};
 
 	recursion_depth++;
 
 	v3s16 p;
+	bool from_ocean = false;
 	
 	// Randomize horizontal order
 	static s32 cs = 0;
@@ -625,7 +645,7 @@ bool VoxelManipulator::flowWater(v3s16 removed_pos,
 	TimeTaker timer1("flowWater pre", g_device, &flowwater_pre_time);
 	
 	// Load neighboring nodes
-	emerge(VoxelArea(removed_pos - v3s16(1,1,1), removed_pos + v3s16(1,1,1)));
+	emerge(VoxelArea(removed_pos - v3s16(1,1,1), removed_pos + v3s16(1,1,1)), 4);
 	
 	// Ignore incorrect removed_pos
 	{
@@ -660,11 +680,13 @@ bool VoxelManipulator::flowWater(v3s16 removed_pos,
 		// If block is at bottom, select it if it has enough pressure
 		if(i == 5)
 		{
+			//if(n.pressure >= PRESERVE_WATER_VOLUME ? 3 : 2)
 			if(n.pressure >= 3)
 				break;
 			continue;
 		}
 		// Else block is at some side. Select it if it has enough pressure
+		//if(n.pressure >= PRESERVE_WATER_VOLUME ? 2 : 1)
 		if(n.pressure >= 2)
 		{
 			break;
@@ -675,22 +697,47 @@ bool VoxelManipulator::flowWater(v3s16 removed_pos,
 	if(i==6)
 		return false;
 
-	// Switch nodes at p and removed_pos
+	/*
+		Move water and bubble
+	*/
+
 	u8 m = m_data[m_area.index(p)].d;
 	u8 f = m_flags[m_area.index(p)];
-	m_data[m_area.index(p)].d = m_data[m_area.index(removed_pos)].d;
-	m_flags[m_area.index(p)] = m_flags[m_area.index(removed_pos)];
+
+	if(m == MATERIAL_OCEAN)
+		from_ocean = true;
+
+	// Move air bubble if not taking water from ocean
+	if(from_ocean == false)
+	{
+		m_data[m_area.index(p)].d = m_data[m_area.index(removed_pos)].d;
+		m_flags[m_area.index(p)] = m_flags[m_area.index(removed_pos)];
+	}
+	
 	m_data[m_area.index(removed_pos)].d = m;
 	m_flags[m_area.index(removed_pos)] = f;
 
 	// Mark removed_pos checked
 	m_flags[m_area.index(removed_pos)] |= VOXELFLAG_CHECKED;
+
 	// If block was dropped from surface, increase pressure
 	if(i == 0 && m_data[m_area.index(removed_pos)].pressure == 1)
 	{
 		m_data[m_area.index(removed_pos)].pressure = 2;
 	}
 	
+	/*
+	NOTE: This does not work as-is
+	if(m == MATERIAL_OCEAN)
+	{
+		// If block was raised to surface, increase pressure of
+		// source node
+		if(i == 5 && m_data[m_area.index(p)].pressure == 1)
+		{
+			m_data[m_area.index(p)].pressure = 2;
+		}
+	}*/
+	
 	/*if(debugprint)
 	{
 		dstream<<"VoxelManipulator::flowWater(): Moved bubble:"<<std::endl;
@@ -720,12 +767,30 @@ bool VoxelManipulator::flowWater(v3s16 removed_pos,
 	}
 	
 	}//timer1
-
-	// Flow water to the newly created empty position
-	flowWater(p, active_nodes, recursion_depth,
-			debugprint, counter, counterlimit);
+	
+	//if(PRESERVE_WATER_VOLUME)
+	if(from_ocean == false)
+	{
+		// Flow water to the newly created empty position
+		/*flowWater(p, active_nodes, recursion_depth,
+				debugprint, counter, counterlimit);*/
+		flowWater(p, active_nodes, recursion_depth,
+				debugprint, stoptime);
+	}
+	
+	if(stoptime != 0 && g_device != NULL)
+	{
+		u32 timenow = g_device->getTimer()->getRealTime();
+		if(timenow >= stoptime ||
+				(stoptime < 0x80000000 && timenow > 0x80000000))
+		{
+			dstream<<"flowWater: stoptime reached"<<std::endl;
+			throw ProcessingLimitException("flowWater stoptime reached");
+		}
+	}
 	
 find_again:
+	
 	// Try flowing water to empty positions around removed_pos.
 	// They are checked in reverse order compared to the previous loop.
 	for(s32 i=5; i>=0; i--)
@@ -745,7 +810,9 @@ find_again:
 		// Flow water to node
 		bool moved =
 		flowWater(p, active_nodes, recursion_depth,
-				debugprint, counter, counterlimit);
+				debugprint, stoptime);
+		/*flowWater(p, active_nodes, recursion_depth,
+				debugprint, counter, counterlimit);*/
 		
 		if(moved)
 		{
@@ -754,27 +821,13 @@ find_again:
 		}
 	}
 
-	if(counter != NULL)
-	{
-		(*counter)++;
-		if((*counter) % 10 == 0)
-			dstream<<"flowWater(): moved "<<(*counter)<<" nodes"
-					<<std::endl;
-
-		if(counterlimit != -1 && (*counter) > counterlimit)
-		{
-			dstream<<"Counter limit reached; returning"<<std::endl;
-			throw ProcessingLimitException("flowWater counterlimit reached");
-		}
-	}
-	
 	return true;
 }
 
 void VoxelManipulator::flowWater(
 		core::map<v3s16, u8> &active_nodes,
 		int recursion_depth, bool debugprint,
-		int counterlimit)
+		u32 timelimit)
 {
 	addarea_time = 0;
 	emerge_time = 0;
@@ -783,25 +836,53 @@ void VoxelManipulator::flowWater(
 	updateareawaterpressure_time = 0;
 	flowwater_pre_time = 0;
 
+	if(active_nodes.size() == 0)
+	{
+		dstream<<"flowWater: no active nodes"<<std::endl;
+		return;
+	}
+
 	TimeTaker timer1("flowWater (active_nodes)", g_device);
 
 	dstream<<"active_nodes.size() = "<<active_nodes.size()<<std::endl;
 
-	int counter = 0;
+	//int counter = 0;
+
+	u32 stoptime = 0;
+	if(g_device != NULL)
+	{
+		stoptime = g_device->getTimer()->getRealTime() + timelimit;
+	}
+
+	// Count of handled active nodes
+	u32 handled_count = 0;
 
 	try
 	{
 
+	/*
+		Take random one at first
+
+		This is randomized only at the first time so that all
+		subsequent nodes will be taken at roughly the same position
+	*/
+	s32 k = 0;
+	if(active_nodes.size() != 0)
+		k = (s32)rand() % (s32)active_nodes.size();
+
 	// Flow water to active nodes
 	for(;;)
+	//for(s32 h=0; h<1; h++)
 	{
-		// Clear check flags
-		clearFlag(VOXELFLAG_CHECKED);
-		
 		if(active_nodes.size() == 0)
 			break;
 
-		dstream<<"Selecting a new active_node"<<std::endl;
+		handled_count++;
+		
+		// Clear check flags
+		clearFlag(VOXELFLAG_CHECKED);
+		
+		//dstream<<"Selecting a new active_node"<<std::endl;
 
 #if 0
 		// Take first one
@@ -810,9 +891,7 @@ void VoxelManipulator::flowWater(
 #endif
 
 #if 1
-		// Take random one
-		s32 k = (s32)rand() % (s32)active_nodes.size();
-		//s32 k = 0;
+		
 		core::map<v3s16, u8>::Iterator
 				i = active_nodes.getIterator().getNode();
 		for(s32 j=0; j<k; j++)
@@ -820,12 +899,17 @@ void VoxelManipulator::flowWater(
 			i++;
 		}
 		core::map<v3s16, u8>::Node *n = i.getNode();
+
+		// Decrement index if less than 0.
+		// This keeps us in existing indices always.
+		if(k > 0)
+			k--;
 #endif
 
 		v3s16 p = n->getKey();
 		active_nodes.remove(p);
 		flowWater(p, active_nodes, recursion_depth,
-				debugprint, &counter, counterlimit);
+				debugprint, stoptime);
 	}
 
 	}
@@ -836,11 +920,14 @@ void VoxelManipulator::flowWater(
 	
 	v3s16 e = m_area.getExtent();
 	s32 v = m_area.getVolume();
-	dstream<<"flowWater (active): moved "<<counter<<" nodes, "
+	//dstream<<"flowWater (active): moved "<<counter<<" nodes, "
+	dstream<<"flowWater (active): "
 			<<"area ended up as "
 			<<e.X<<"x"<<e.Y<<"x"<<e.Z<<" = "<<v
+			<<", handled a_node count: "<<handled_count
+			<<", active_nodes.size() = "<<active_nodes.size()
 			<<std::endl;
-	
+			
 	dstream<<"addarea_time: "<<addarea_time
 			<<", emerge_time: "<<emerge_time
 			<<", emerge_load_time: "<<emerge_load_time
diff --git a/src/voxel.h b/src/voxel.h
index 74c0a00e..411cf437 100644
--- a/src/voxel.h
+++ b/src/voxel.h
@@ -428,8 +428,8 @@ public:
 	bool flowWater(v3s16 removed_pos,
 			core::map<v3s16, u8> &active_nodes,
 			int recursion_depth=0,
-			bool debugprint=false, int *counter=NULL,
-			int counterlimit=-1
+			bool debugprint=false,
+			u32 stoptime=0
 	);
 
 	/*
@@ -446,7 +446,7 @@ public:
 	void flowWater(core::map<v3s16, u8> &active_nodes,
 			int recursion_depth=0,
 			bool debugprint=false,
-			int counterlimit=-1
+			u32 timelimit=50
 	);
 
 	/*
@@ -460,7 +460,7 @@ public:
 
 		If not found from source, add with VOXELFLAG_INEXISTENT
 	*/
-	virtual void emerge(VoxelArea a)
+	virtual void emerge(VoxelArea a, s32 caller_id=-1)
 	{
 		//dstream<<"emerge p=("<<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;
 		addArea(a);