From 3b0bff2f743a3abf100368f94efafa7c2843a9b7 Mon Sep 17 00:00:00 2001
From: Perttu Ahola <celeron55@gmail.com>
Date: Tue, 21 Dec 2010 02:25:47 +0200
Subject: [PATCH] Cracking blocks while digging

---
 data/crack.png          | Bin 0 -> 1039 bytes
 src/client.cpp          |  88 ++++------------------
 src/client.h            |  25 +++++--
 src/constants.h         |   6 --
 src/defaultsettings.cpp |   2 +
 src/irrlichtwrapper.cpp |  71 ++++++++++++++----
 src/irrlichtwrapper.h   |  12 ++-
 src/main.cpp            | 161 +++++++++++-----------------------------
 src/map.cpp             |  30 +++++---
 src/map.h               |  12 ++-
 src/mapblock.cpp        |  50 ++++++-------
 src/mapblock.h          |  10 ++-
 src/server.cpp          |  12 ++-
 src/tile.cpp            |   7 ++
 src/tile.h              |   2 +
 src/utility.h           |  33 ++------
 16 files changed, 232 insertions(+), 289 deletions(-)
 create mode 100644 data/crack.png

diff --git a/data/crack.png b/data/crack.png
new file mode 100644
index 0000000000000000000000000000000000000000..e39b74da01343f285f9874e1ed00e3dd0a4314ea
GIT binary patch
literal 1039
zcmZuvO-L0$5S}tLOhxe^2qh1;2+Ocbf<<aP$tjapNoZcOKNOnu(5<v*(T`<BBZWm6
z2nJz!C>tfbgb^iy68%s^Lgg(2D^ntIoA(6q`C8cB*=1(GpV`(>#rD+X>|_8)#j@ZY
z+tY0pFHf{JvuXMUKmvryD@*O@qtiz>ZzmraswyJPOhkz2oKpY+U;qL@0B2QkSE?Wa
z%s>Q);Qp7SpaR0c1cZRjUWzM=hxa1NEJTb*&Ut1f5=6F`ND;*XM77nZdKDEGCMG0w
z=)?;vlMpgGa_pIuP|&d>PpFpR(4&jit1d!Ak%fT7JdbJq%)uI$2(sbFo9Df#vWOKT
zk!7$di6FDZq-yV5fU34SAIIK{S5_fmWHP?&l~hpJbdXn6F@5OObVsUV!OWS3cxK={
zq&0UA)u<S76h6*5h;`163#CMw>{*mpkk}!C(?wo`5nW(6km#ZcWbwp`WIbST6e~CK
zN7xG~qgdw<$aKd3287uD7)ekDA+U50qQC;g_HD%jt5E<0ijI-orOPZB#$ZWh)zJ5S
zC242#Q&vnI-_ZH2e=0w1jBY&{y^EI%A8g&-(%yG(WpK^&r1syR0}s!49IgG9H_(`p
zdH73FOW;KDk<_a>9j&1kO(V5=gQq78UlnXR6y6o=+?!o+u;K0BljDO^_meNRJ$gNM
z{pXL8uI}vI=5Trcn~QbzYbO#n*3|S~**U)I_Ky6zr0LDS(%&^?1m5?JZpq2G`LS)d
zx+nbjL+?zuJFRLayt-&Kx9m<JtGcJTJ~Yts_0N*^+V!;Y(@?^-my_qRW}Ek4IJWN8
PY(yTGRs?&u9XRt3ktgV`

literal 0
HcmV?d00001

diff --git a/src/client.cpp b/src/client.cpp
index b612c9c4..4979829d 100644
--- a/src/client.cpp
+++ b/src/client.cpp
@@ -398,45 +398,10 @@ void Client::step(float dtime)
 		}
 	}
 
-#if 0
-	/*
-		Clear old entries from fetchblock history
-	*/
-	{
-		JMutexAutoLock lock(m_fetchblock_mutex);
-		
-		core::list<v3s16> remove_queue;
-		core::map<v3s16, float>::Iterator i;
-		i = m_fetchblock_history.getIterator();
-		for(; i.atEnd() == false; i++)
-		{
-			float value = i.getNode()->getValue();
-			value += dtime;
-			i.getNode()->setValue(value);
-			if(value >= 60.0)
-				remove_queue.push_back(i.getNode()->getKey());
-		}
-		core::list<v3s16>::Iterator j;
-		j = remove_queue.begin();
-		for(; j != remove_queue.end(); j++)
-		{
-			m_fetchblock_history.remove(*j);
-		}
-	}
-#endif
-
 	/*{
 		JMutexAutoLock lock(m_step_dtime_mutex);
 		m_step_dtime += dtime;
 	}*/
-	
-	/*
-		BEGIN TEST CODE
-	*/
-
-	/*
-		END OF TEST CODE
-	*/
 }
 
 float Client::asyncStep()
@@ -975,7 +940,11 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
 			if(abs_to_delete.find(p) != NULL)
 				abs_to_delete.remove(p);
 
-			// Update objects of block
+			/*
+				Update objects of block
+				
+				NOTE: Be sure this is done in the main thread.
+			*/
 			block->updateObjects(is, m_server_ser_ver,
 					m_device->getSceneManager());
 		}
@@ -1482,11 +1451,11 @@ void Client::addNodeFromInventory(v3s16 nodepos, u16 i)
 }
 #endif
 
-void Client::pressGround(u8 button, v3s16 nodepos_undersurface,
+void Client::groundAction(u8 action, v3s16 nodepos_undersurface,
 		v3s16 nodepos_oversurface, u16 item)
 {
 	if(connectedAndInitialized() == false){
-		dout_client<<DTIME<<"Client::pressGround() "
+		dout_client<<DTIME<<"Client::groundAction() "
 				"cancelled (not connected)"
 				<<std::endl;
 		return;
@@ -1507,7 +1476,7 @@ void Client::pressGround(u8 button, v3s16 nodepos_undersurface,
 	u8 datasize = 2 + 1 + 6 + 6 + 2;
 	SharedBuffer<u8> data(datasize);
 	writeU16(&data[0], TOSERVER_GROUND_ACTION);
-	writeU8(&data[2], button);
+	writeU8(&data[2], action);
 	writeV3S16(&data[3], nodepos_undersurface);
 	writeV3S16(&data[9], nodepos_oversurface);
 	writeU16(&data[15], item);
@@ -1540,37 +1509,6 @@ void Client::clickObject(u8 button, v3s16 blockpos, s16 id, u16 item)
 	Send(0, data, true);
 }
 
-void Client::stopDigging()
-{
-	if(connectedAndInitialized() == false){
-		dout_client<<DTIME<<"Client::release() "
-				"cancelled (not connected)"
-				<<std::endl;
-		return;
-	}
-	
-	/*
-		length: 17
-		[0] u16 command
-		[2] u8 action
-		[3] v3s16 nodepos_undersurface
-		[9] v3s16 nodepos_abovesurface
-		[15] u16 item
-		actions:
-		0: start digging
-		1: place block
-		2: stop digging (all parameters ignored)
-	*/
-	u8 datasize = 2 + 1 + 6 + 6 + 2;
-	SharedBuffer<u8> data(datasize);
-	writeU16(&data[0], TOSERVER_GROUND_ACTION);
-	writeU8(&data[2], 2);
-	writeV3S16(&data[3], v3s16(0,0,0));
-	writeV3S16(&data[9], v3s16(0,0,0));
-	writeU16(&data[15], 0);
-	Send(0, data, true);
-}
-
 void Client::sendSignText(v3s16 blockpos, s16 id, std::string text)
 {
 	/*
@@ -1671,17 +1609,23 @@ MapNode Client::getNode(v3s16 p)
 	return m_env.getMap().getNode(p);
 }
 
+/*void Client::getNode(v3s16 p, MapNode n)
+{
+	JMutexAutoLock envlock(m_env_mutex);
+	m_env.getMap().setNode(p, n);
+}*/
+
 /*f32 Client::getGroundHeight(v2s16 p)
 {
 	JMutexAutoLock envlock(m_env_mutex);
 	return m_env.getMap().getGroundHeight(p);
 }*/
 
-bool Client::isNodeUnderground(v3s16 p)
+/*bool Client::isNodeUnderground(v3s16 p)
 {
 	JMutexAutoLock envlock(m_env_mutex);
 	return m_env.getMap().isNodeUnderground(p);
-}
+}*/
 
 /*Player * Client::getLocalPlayer()
 {
diff --git a/src/client.h b/src/client.h
index a1ee3f76..a3d43997 100644
--- a/src/client.h
+++ b/src/client.h
@@ -185,12 +185,9 @@ public:
 	// Pops out a packet from the packet queue
 	IncomingPacket getPacket();
 
-	/*void removeNode(v3s16 nodepos);
-	void addNodeFromInventory(v3s16 nodepos, u16 i);*/
-	void pressGround(u8 button, v3s16 nodepos_undersurface,
+	void groundAction(u8 action, v3s16 nodepos_undersurface,
 			v3s16 nodepos_oversurface, u16 item);
 	void clickObject(u8 button, v3s16 blockpos, s16 id, u16 item);
-	void stopDigging();
 
 	void sendSignText(v3s16 blockpos, s16 id, std::string text);
 	
@@ -198,10 +195,13 @@ public:
 	
 	// Returns InvalidPositionException if not found
 	MapNode getNode(v3s16 p);
+	// Returns InvalidPositionException if not found
+	//void setNode(v3s16 p, MapNode n);
+
 	// Returns InvalidPositionException if not found
 	//f32 getGroundHeight(v2s16 p);
 	// Returns InvalidPositionException if not found
-	bool isNodeUnderground(v3s16 p);
+	//bool isNodeUnderground(v3s16 p);
 
 	// Note: The players should not be exposed outside
 	// Return value is valid until client is destroyed
@@ -236,6 +236,21 @@ public:
 
 	//void updateSomeExpiredMeshes();
 	
+	void setTempMod(v3s16 p, NodeMod mod)
+	{
+		JMutexAutoLock envlock(m_env_mutex);
+		assert(m_env.getMap().mapType() == MAPTYPE_CLIENT);
+		v3s16 blockpos = ((ClientMap&)m_env.getMap()).setTempMod(p, mod);
+		m_env.getMap().updateMeshes(blockpos, m_env.getDayNightRatio());
+	}
+	void clearTempMod(v3s16 p)
+	{
+		JMutexAutoLock envlock(m_env_mutex);
+		assert(m_env.getMap().mapType() == MAPTYPE_CLIENT);
+		v3s16 blockpos = ((ClientMap&)m_env.getMap()).clearTempMod(p);
+		m_env.getMap().updateMeshes(blockpos, m_env.getDayNightRatio());
+	}
+	
 private:
 	
 	// Virtual methods from con::PeerHandler
diff --git a/src/constants.h b/src/constants.h
index c3fca432..7668c745 100644
--- a/src/constants.h
+++ b/src/constants.h
@@ -39,12 +39,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #define PI 3.14159
 
-//#define SERVERMAP_DELETE_UNUSED_SECTORS_TIMEOUT (60*10)
-#define SERVERMAP_DELETE_UNUSED_SECTORS_TIMEOUT (60)
-#define SERVER_MAP_SAVE_INTERVAL (60)
-/*#define SERVERMAP_DELETE_UNUSED_SECTORS_TIMEOUT (10)
-#define SERVER_MAP_SAVE_INTERVAL (10)*/
-
 // This is the same as in minecraft and everything else
 #define FOV_ANGLE (PI/2.5)
 
diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp
index 04cdf16b..6888fa49 100644
--- a/src/defaultsettings.cpp
+++ b/src/defaultsettings.cpp
@@ -56,5 +56,7 @@ void set_default_settings()
 	g_settings.setDefault("max_block_generate_distance", "4");
 	g_settings.setDefault("time_send_interval", "20");
 	g_settings.setDefault("time_speed", "360");
+	g_settings.setDefault("server_unload_unused_sectors_timeout", "60");
+	g_settings.setDefault("server_map_save_interval", "60");
 }
 
diff --git a/src/irrlichtwrapper.cpp b/src/irrlichtwrapper.cpp
index 69b08a6d..51511eb3 100644
--- a/src/irrlichtwrapper.cpp
+++ b/src/irrlichtwrapper.cpp
@@ -17,7 +17,8 @@ void IrrlichtWrapper::Run()
 		GetRequest<TextureSpec, video::ITexture*, u8, u8>
 				request = m_get_texture_queue.pop();
 
-		dstream<<"got request with key.name="<<request.key.name<<std::endl;
+		dstream<<"got texture request with key.name="
+				<<request.key.name<<std::endl;
 
 		GetResult<TextureSpec, video::ITexture*, u8, u8>
 				result;
@@ -37,7 +38,9 @@ video::ITexture* IrrlichtWrapper::getTexture(TextureSpec spec)
 	
 	if(get_current_thread_id() == m_main_thread)
 	{
-		dstream<<"Loading texture directly: "<<spec.name<<std::endl;
+		dstream<<"Getting texture directly: name="
+				<<spec.name<<std::endl;
+				
 		t = getTextureDirect(spec);
 	}
 	else
@@ -52,7 +55,7 @@ video::ITexture* IrrlichtWrapper::getTexture(TextureSpec spec)
 
 		// Wait result
 		GetResult<TextureSpec, video::ITexture*, u8, u8>
-				result = result_queue.pop_front(true);
+				result = result_queue.pop_front(1000);
 		
 		// Check that at least something worked OK
 		assert(result.key.name == spec.name);
@@ -67,10 +70,6 @@ video::ITexture* IrrlichtWrapper::getTexture(TextureSpec spec)
 
 video::ITexture* IrrlichtWrapper::getTexture(const std::string &path)
 {
-	/*TextureSpec spec;
-	spec.name = path;
-	spec.path = path;
-	return getTexture(spec);*/
 	return getTexture(TextureSpec(path, path, NULL));
 }
 
@@ -81,21 +80,61 @@ video::ITexture* IrrlichtWrapper::getTexture(const std::string &path)
 video::ITexture* IrrlichtWrapper::getTextureDirect(TextureSpec spec)
 {
 	video::IVideoDriver* driver = m_device->getVideoDriver();
-	//TODO
-	if(spec.mod != NULL)
+	
+	if(spec.mod == NULL)
 	{
-		dstream<<"IrrlichtWrapper::getTextureDirect: Modified textures"
-				" not supported"<<std::endl;
+		dstream<<"IrrlichtWrapper::getTextureDirect: Loading texture "
+				<<spec.path<<std::endl;
+		return driver->getTexture(spec.path.c_str());
 	}
-	return driver->getTexture(spec.path.c_str());
+
+	dstream<<"IrrlichtWrapper::getTextureDirect: Loading and modifying "
+			"texture "<<spec.path<<" to make "<<spec.name<<std::endl;
+
+	video::ITexture *base = driver->getTexture(spec.path.c_str());
+	video::ITexture *result = spec.mod->make(base, spec.name.c_str(), driver);
+
+	delete spec.mod;
+	
+	return result;
 }
 
 video::ITexture * CrackTextureMod::make(video::ITexture *original,
-		video::IVideoDriver* driver)
+		const char *newname, video::IVideoDriver* driver)
 {
-	//TODO
-	dstream<<__FUNCTION_NAME<<std::endl;
-	return NULL;
+	core::dimension2d<u32> dim(16, 16);
+	core::position2d<s32> pos_base(0, 0);
+	core::position2d<s32> pos_other(0, 16 * progression);
+
+	video::IImage *baseimage = driver->createImage(original, pos_base, dim);
+	assert(baseimage);
+	
+	video::ITexture *other = driver->getTexture("../data/crack.png");
+	// We have to get the whole texture because getting a smaller area
+	// messes the whole thing. It is probably a bug in Irrlicht.
+	video::IImage *otherimage = driver->createImage(
+			other, core::position2d<s32>(0,0), other->getSize());
+
+	assert(otherimage);
+	
+	/*core::rect<s32> clip_rect(v2s32(0,0), dim);
+	otherimage->copyToWithAlpha(baseimage, v2s32(0,0),
+			core::rect<s32>(pos_other, dim),
+			video::SColor(255,255,255,255),
+			&clip_rect);*/
+	
+	otherimage->copyToWithAlpha(baseimage, v2s32(0,0),
+			core::rect<s32>(pos_other, dim),
+			video::SColor(255,255,255,255),
+			NULL);
+	
+	otherimage->drop();
+
+	video::ITexture *newtexture = driver->addTexture(newname, baseimage);
+
+	baseimage->drop();
+
+	return newtexture;
 }
 
 #if 0
diff --git a/src/irrlichtwrapper.h b/src/irrlichtwrapper.h
index a78edfe7..981e3377 100644
--- a/src/irrlichtwrapper.h
+++ b/src/irrlichtwrapper.h
@@ -48,6 +48,9 @@ public:
 	
 	void set(std::string name, video::ITexture *texture)
 	{
+		if(texture == NULL)
+			return;
+		
 		JMutexAutoLock lock(m_mutex);
 
 		m_textures[name] = texture;
@@ -78,7 +81,7 @@ struct TextureMod
 		Shall not modify or delete the original texture.
 	*/
 	virtual video::ITexture * make(video::ITexture *original,
-			video::IVideoDriver* driver) = 0;
+			const char *newname, video::IVideoDriver* driver) = 0;
 };
 
 struct CrackTextureMod: public TextureMod
@@ -89,7 +92,7 @@ struct CrackTextureMod: public TextureMod
 	}
 	
 	virtual video::ITexture * make(video::ITexture *original,
-			video::IVideoDriver* driver);
+			const char *newname, video::IVideoDriver* driver);
 	
 	u16 progression;
 };
@@ -149,10 +152,11 @@ public:
 		These are called from other threads
 	*/
 
-	// Not exactly thread-safe but this needs to be fast
+	// Not exactly thread-safe but this needs to be fast.
+	// getTimer()->getRealTime() only reads one variable anyway.
 	u32 getTime()
 	{
-		return m_device->getTimer()->getTime();
+		return m_device->getTimer()->getRealTime();
 	}
 	
 	video::ITexture* getTexture(TextureSpec spec);
diff --git a/src/main.cpp b/src/main.cpp
index 56e72572..6b545596 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -179,12 +179,6 @@ TODO: TOSERVER_LEAVE
 Doing now:
 ======================================================================
 
-TODO: Node cracking animation when digging
-      - TODO: A way to generate new textures by combining textures
-	  - TODO: Mesh update to fetch cracked faces from the former
-
-TODO: A thread-safe wrapper for irrlicht for threads, to get rid of
-      g_device
 
 ======================================================================
 
@@ -1164,7 +1158,7 @@ int main(int argc, char *argv[])
 	<<std::endl;
 
 	std::cout<<std::endl;
-	char templine[100];
+	//char templine[100];
 	
 	// Port?
 	u16 port = 30000;
@@ -1271,70 +1265,6 @@ int main(int argc, char *argv[])
 	u16 screenW = atoi(g_settings.get("screenW").c_str());
 	u16 screenH = atoi(g_settings.get("screenH").c_str());
 
-#if 0
-	u16 screenW;
-	u16 screenH;
-	bool fullscreen = false;
-	
-	if(g_settings.get("screenW") != "" && g_settings.get("screenH") != "")
-	{
-		screenW = atoi(g_settings.get("screenW").c_str());
-		screenH = atoi(g_settings.get("screenH").c_str());
-	}
-	else
-	{
-		u16 resolutions[][3] = {
-			//W, H, fullscreen
-			{640,480, 0},
-			{800,600, 0},
-			{1024,768, 0},
-			{1280,1024, 0},
-			/*{640,480, 1},
-			{800,600, 1},
-			{1024,768, 1},
-			{1280,1024, 1},*/
-		};
-
-		u16 res_count = sizeof(resolutions)/sizeof(resolutions[0]);
-		
-		for(u16 i=0; i<res_count; i++)
-		{
-			std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"
-					<<resolutions[i][1];
-			if(resolutions[i][2])
-				std::cout<<" fullscreen"<<std::endl;
-			else
-				std::cout<<" windowed"<<std::endl;
-		}
-		std::cout<<"Select a window resolution number [empty = 2]: ";
-		std::cin.getline(templine, 100);
-
-		u16 r0;
-		if(templine[0] == 0)
-			r0 = 2;
-		else
-			r0 = atoi(templine);
-
-		if(r0 > res_count || r0 == 0)
-			r0 = 2;
-		
-		{
-			u16 i = r0-1;
-			std::cout<<"-> ";
-			std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"
-					<<resolutions[i][1];
-			if(resolutions[i][2])
-				std::cout<<" fullscreen"<<std::endl;
-			else
-				std::cout<<" windowed"<<std::endl;
-		}
-
-		screenW = resolutions[r0-1][0];
-		screenH = resolutions[r0-1][1];
-		fullscreen = resolutions[r0-1][2];
-	}
-#endif
-
 	//
 
 	MyEventReceiver receiver;
@@ -1354,10 +1284,6 @@ int main(int argc, char *argv[])
 	device = createDevice(driverType,
 			core::dimension2d<u32>(screenW, screenH),
 			16, fullscreen, false, false, &receiver);
-	// With vsync
-	/*device = createDevice(driverType,
-			core::dimension2d<u32>(screenW, screenH),
-			16, fullscreen, false, true, &receiver);*/
 
 	if (device == 0)
 		return 1; // could not create selected driver.
@@ -1380,10 +1306,10 @@ int main(int argc, char *argv[])
 	*/
 
 	video::IVideoDriver* driver = device->getVideoDriver();
-	// These make the textures not to show at all
-	//driver->setTextureCreationFlag(video::ETCF_ALWAYS_16_BIT);
-	//driver->setTextureCreationFlag(video::ETCF_OPTIMIZED_FOR_SPEED );
 
+	/*
+		This changes the minimum allowed number of vertices in a VBO
+	*/
 	//driver->setMinHardwareBufferVertexCount(1);
 
 	scene::ISceneManager* smgr = device->getSceneManager();
@@ -2147,48 +2073,69 @@ int main(int argc, char *argv[])
 			} // regular block
 		} // for coords
 
-		/*static v3s16 oldnodepos;
-		static bool oldnodefound = false;*/
-
 		if(nodefound)
 		{
-			//std::cout<<DTIME<<"nodefound == true"<<std::endl;
-			//std::cout<<DTIME<<"nodepos=("<<nodepos.X<<","<<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
-			//std::cout<<DTIME<<"neighbourpos=("<<neighbourpos.X<<","<<neighbourpos.Y<<","<<neighbourpos.Z<<")"<<std::endl;
+			static v3s16 nodepos_old(-32768,-32768,-32768);
 
-			static v3s16 nodepos_old(-1,-1,-1);
-			if(nodepos != nodepos_old){
+			static float dig_time = 0.0;
+
+			if(nodepos != nodepos_old)
+			{
 				std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
 						<<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
+
+				if(nodepos_old != v3s16(-32768,-32768,-32768))
+				{
+					client.clearTempMod(nodepos_old);
+					dig_time = 0.0;
+				}
 			}
 
 			hilightboxes.push_back(nodefacebox);
 			
-			//if(g_input->getLeftClicked())
+			if(g_input->getLeftReleased())
+			{
+				client.clearTempMod(nodepos);
+				dig_time = 0.0;
+			}
 			if(g_input->getLeftClicked() ||
 					(g_input->getLeftState() && nodepos != nodepos_old))
 			{
 				std::cout<<DTIME<<"Ground left-clicked"<<std::endl;
-				client.pressGround(0, nodepos, neighbourpos, g_selected_item);
+				client.groundAction(0, nodepos, neighbourpos, g_selected_item);
 			}
+			if(g_input->getLeftClicked())
+			{
+				client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, 0));
+			}
+			if(g_input->getLeftState())
+			{
+				dig_time += dtime;
+				
+				float dig_time_complete = 0.5;
+				MapNode n = client.getNode(nodepos);
+				if(n.d == CONTENT_STONE)
+					dig_time_complete = 1.5;
+
+				u16 dig_index = (u16)(3.99*dig_time/dig_time_complete);
+				if(dig_time > 0.2)
+				{
+					//dstream<<"dig_index="<<dig_index<<std::endl;
+					client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, dig_index));
+				}
+			}
+			
 			if(g_input->getRightClicked())
-			/*if(g_input->getRightClicked() ||
-					(g_input->getRightState() && nodepos != nodepos_old))*/
 			{
 				std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
-				client.pressGround(1, nodepos, neighbourpos, g_selected_item);
+				client.groundAction(1, nodepos, neighbourpos, g_selected_item);
 			}
 			
 			nodepos_old = nodepos;
 		}
 		else{
-			//std::cout<<DTIME<<"nodefound == false"<<std::endl;
-			//positiontextgui->setText(L"");
 		}
 
-		/*oldnodefound = nodefound;
-		oldnodepos = nodepos;*/
-
 		} // selected_object == NULL
 		
 		g_input->resetLeftClicked();
@@ -2197,7 +2144,7 @@ int main(int argc, char *argv[])
 		if(g_input->getLeftReleased())
 		{
 			std::cout<<DTIME<<"Left released"<<std::endl;
-			client.stopDigging();
+			client.groundAction(2, v3s16(0,0,0), v3s16(0,0,0), 0);
 		}
 		if(g_input->getRightReleased())
 		{
@@ -2214,26 +2161,6 @@ int main(int argc, char *argv[])
 
 		camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
 		
-		// Background color is choosen based on whether the player is
-		// much beyond the initial ground level
-		/*video::SColor bgcolor;
-		v3s16 p0 = Map::floatToInt(player_position);
-		// Does this make short random delays?
-		// NOTE: no need for this, sky doesn't show underground with
-		// enough range
-		bool is_underground = client.isNodeUnderground(p0);
-		//bool is_underground = false;
-		if(is_underground == false)
-			bgcolor = video::SColor(255,90,140,200);
-		else
-			bgcolor = video::SColor(255,0,0,0);*/
-			
-		//video::SColor bgcolor = video::SColor(255,90,140,200);
-		//video::SColor bgcolor = skycolor;
-		
-		//s32 daynight_i = client.getDayNightIndex();
-		//video::SColor bgcolor = skycolor[daynight_i];
-
 		u32 daynight_ratio = client.getDayNightRatio();
 		video::SColor bgcolor = video::SColor(
 				255,
diff --git a/src/map.cpp b/src/map.cpp
index 13db9265..2a517c54 100644
--- a/src/map.cpp
+++ b/src/map.cpp
@@ -1622,6 +1622,7 @@ MapBlock * ServerMap::emergeBlock(
 	}
 
 	//dstream<<"Not found on disk, generating."<<std::endl;
+	//TimeTaker("emergeBlock()", g_irrlicht);
 
 	/*
 		Do not generate over-limit
@@ -1668,7 +1669,7 @@ MapBlock * ServerMap::emergeBlock(
 		underground_emptiness[i] = ((rand() % 5) == 0);
 	}
 
-#if 0
+#if 1
 	/*
 		This is a messy hack to sort the emptiness a bit
 	*/
@@ -2235,20 +2236,15 @@ void ServerMap::save(bool only_changed)
 
 	}//sectorlock
 	
-	u32 deleted_count = 0;
-	deleted_count = deleteUnusedSectors(
-			SERVERMAP_DELETE_UNUSED_SECTORS_TIMEOUT);
-	
 	/*
 		Only print if something happened or saved whole map
 	*/
 	if(only_changed == false || sector_meta_count != 0
-			|| block_count != 0 || deleted_count != 0)
+			|| block_count != 0)
 	{
 		dstream<<DTIME<<"ServerMap: Written: "
 				<<sector_meta_count<<" sector metadata files, "
-				<<block_count<<" block files, "
-				<<deleted_count<<" sectors unloaded from memory."
+				<<block_count<<" block files"
 				<<std::endl;
 	}
 }
@@ -2987,9 +2983,23 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
 			<<", rendered "<<vertex_count<<" vertices."<<std::endl;*/
 }
 
-void ClientMap::updateMesh()
+v3s16 ClientMap::setTempMod(v3s16 p, NodeMod mod)
 {
-	//TODO: Remove this
+	v3s16 blockpos = getNodeBlockPos(p);
+	MapBlock * blockref = getBlockNoCreate(blockpos);
+	v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+
+	blockref->setTempMod(relpos, mod);
+	return blockpos;
+}
+v3s16 ClientMap::clearTempMod(v3s16 p)
+{
+	v3s16 blockpos = getNodeBlockPos(p);
+	MapBlock * blockref = getBlockNoCreate(blockpos);
+	v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+
+	blockref->clearTempMod(relpos);
+	return blockpos;
 }
 
 void ClientMap::PrintInfo(std::ostream &out)
diff --git a/src/map.h b/src/map.h
index 05984b1a..3b34cd89 100644
--- a/src/map.h
+++ b/src/map.h
@@ -35,7 +35,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "common_irrlicht.h"
 #include "heightmap.h"
-#include "loadstatus.h"
 #include "mapnode.h"
 #include "mapblock.h"
 #include "mapsector.h"
@@ -593,8 +592,15 @@ public:
 
 	void renderMap(video::IVideoDriver* driver, s32 pass);
 
-	// Update master heightmap mesh
-	void updateMesh();
+	/*
+		Methods for setting temporary modifications to nodes for
+		drawing.
+		Return value is position of changed block.
+	*/
+	v3s16 setTempMod(v3s16 p, NodeMod mod);
+	v3s16 clearTempMod(v3s16 p);
+	// Efficient implementation needs a cache of TempMods
+	//void clearTempMods();
 
 	// For debug printing
 	virtual void PrintInfo(std::ostream &out);
diff --git a/src/mapblock.cpp b/src/mapblock.cpp
index af08cada..e2262544 100644
--- a/src/mapblock.cpp
+++ b/src/mapblock.cpp
@@ -300,6 +300,8 @@ TileSpec MapBlock::getNodeTile(MapNode mn, v3s16 p, v3s16 face_dir)
 		}
 		if(mod.type == NODEMOD_CRACK)
 		{
+			spec.feature = TILEFEAT_CRACK;
+			spec.param.crack.progression = mod.param;
 		}
 	}
 	
@@ -646,14 +648,31 @@ void MapBlock::updateMesh(u32 daynight_ratio)
 			
 			if(f.tile.feature == TILEFEAT_NONE)
 			{
-				/*collector.append(g_tile_materials[f.tile.id], f.vertices, 4,
-						indices, 6);*/
 				collector.append(tile_material_get(f.tile.id), f.vertices, 4,
 						indices, 6);
 			}
+			else if(f.tile.feature == TILEFEAT_CRACK)
+			{
+				const char *path = tile_texture_path_get(f.tile.id);
+
+				u16 progression = f.tile.param.crack.progression;
+
+				std::string name = (std::string)path + "_cracked_"
+						+ (char)('0' + progression);
+
+				TextureMod *mod = new CrackTextureMod(progression);
+
+				video::ITexture *texture = g_irrlicht->getTexture(
+						TextureSpec(name, path, mod));
+
+				video::SMaterial material = tile_material_get(f.tile.id);
+				material.setTexture(0, texture);
+
+				collector.append(material, f.vertices, 4, indices, 6);
+			}
 			else
 			{
-				// Not implemented
+				// No such feature
 				assert(0);
 			}
 		}
@@ -668,19 +687,6 @@ void MapBlock::updateMesh(u32 daynight_ratio)
 				<<" materials (meshbuffers)"<<std::endl;*/
 	}
 
-	/*
-		Clear temporary FastFaces
-	*/
-
-	/*core::list<FastFace*>::Iterator i;
-	i = fastfaces_new->begin();
-	for(; i != fastfaces_new->end(); i++)
-	{
-		delete *i;
-	}
-	fastfaces_new->clear();
-	delete fastfaces_new;*/
-
 	/*
 		Add special graphics:
 		- torches
@@ -688,14 +694,6 @@ void MapBlock::updateMesh(u32 daynight_ratio)
 		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++)
@@ -751,20 +749,16 @@ void MapBlock::updateMesh(u32 daynight_ratio)
 			if(dir == v3s16(0,-1,0))
 				buf->getMaterial().setTexture(0,
 						g_irrlicht->getTexture("../data/torch_on_floor.png"));
-						//g_texturecache.get("torch_on_floor"));
 			else if(dir == v3s16(0,1,0))
 				buf->getMaterial().setTexture(0,
 						g_irrlicht->getTexture("../data/torch_on_ceiling.png"));
-						//g_texturecache.get("torch_on_ceiling"));
 			// For backwards compatibility
 			else if(dir == v3s16(0,0,0))
 				buf->getMaterial().setTexture(0,
 						g_irrlicht->getTexture("../data/torch_on_floor.png"));
-						//g_texturecache.get("torch_on_floor"));
 			else
 				buf->getMaterial().setTexture(0, 
 						g_irrlicht->getTexture("../data/torch.png"));
-				//buf->getMaterial().setTexture(0, g_texturecache.get("torch"));
 
 			// Add to mesh
 			mesh_new->addMeshBuffer(buf);
diff --git a/src/mapblock.h b/src/mapblock.h
index 2bdf639b..b5126b82 100644
--- a/src/mapblock.h
+++ b/src/mapblock.h
@@ -59,9 +59,10 @@ enum NodeModType
 
 struct NodeMod
 {
-	NodeMod()
+	NodeMod(enum NodeModType a_type=NODEMOD_NONE, u16 a_param=0)
 	{
-		type = NODEMOD_NONE;
+		type = a_type;
+		param = a_param;
 	}
 	enum NodeModType type;
 	u16 param;
@@ -393,6 +394,11 @@ public:
 	*/
 	void setTempMod(v3s16 p, NodeMod mod)
 	{
+		/*dstream<<"setTempMod called on block"
+				<<" ("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+				<<", mod.type="<<mod.type
+				<<", mod.param="<<mod.param
+				<<std::endl;*/
 		m_temp_mods[p] = mod;
 	}
 	void clearTempMod(v3s16 p)
diff --git a/src/server.cpp b/src/server.cpp
index 7260e21d..7582024d 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -1321,13 +1321,23 @@ void Server::AsyncRunStep()
 	{
 		float &counter = m_savemap_timer;
 		counter += dtime;
-		if(counter >= SERVER_MAP_SAVE_INTERVAL)
+		if(counter >= g_settings.getFloat("server_map_save_interval"))
 		{
 			counter = 0.0;
 
 			JMutexAutoLock lock(m_env_mutex);
+
 			// Save only changed parts
 			m_env.getMap().save(true);
+
+			// Delete unused sectors
+			u32 deleted_count = m_env.getMap().deleteUnusedSectors(
+					g_settings.getFloat("server_unload_unused_sectors_timeout"));
+			if(deleted_count > 0)
+			{
+				dout_server<<"Server: Unloaded "<<deleted_count
+						<<" sectors from memory"<<std::endl;
+			}
 		}
 	}
 }
diff --git a/src/tile.cpp b/src/tile.cpp
index 174c72fd..23b1638d 100644
--- a/src/tile.cpp
+++ b/src/tile.cpp
@@ -37,6 +37,13 @@ const char * g_tile_texture_paths[TILES_COUNT] =
 	"../data/cloud.png",
 };
 
+const char * tile_texture_path_get(u32 i)
+{
+	assert(i < TILES_COUNT);
+
+	return g_tile_texture_paths[i];
+}
+
 // A mapping from tiles to materials
 // Initialized at run-time.
 video::SMaterial g_tile_materials[TILES_COUNT];
diff --git a/src/tile.h b/src/tile.h
index d9756766..68e87a77 100644
--- a/src/tile.h
+++ b/src/tile.h
@@ -100,6 +100,8 @@ extern video::SMaterial g_tile_materials[TILES_COUNT];*/
 	Functions
 */
 
+const char * tile_texture_path_get(u32 i);
+
 // Initializes g_tile_materials
 void tile_materials_preload(IrrlichtWrapper *irrlicht);
 
diff --git a/src/utility.h b/src/utility.h
index cde55576..bfee15e6 100644
--- a/src/utility.h
+++ b/src/utility.h
@@ -1085,7 +1085,6 @@ public:
 	MutexedQueue()
 	{
 		m_mutex.Init();
-		m_is_empty_mutex.Init();
 	}
 	u32 size()
 	{
@@ -1095,12 +1094,11 @@ public:
 	{
 		JMutexAutoLock lock(m_mutex);
 		m_list.push_back(t);
-		
-		if(m_list.size() == 1)
-			m_is_empty_mutex.Unlock();
 	}
-	T pop_front(bool wait_if_empty=false)
+	T pop_front(u32 wait_time_max_ms=0)
 	{
+		u32 wait_time_ms = 0;
+
 		for(;;)
 		{
 			{
@@ -1108,24 +1106,19 @@ public:
 
 				if(m_list.size() > 0)
 				{
-					if(m_list.size() == 1)
-						m_is_empty_mutex.Lock();
-					
 					typename core::list<T>::Iterator begin = m_list.begin();
 					T t = *begin;
 					m_list.erase(begin);
 					return t;
 				}
 
-				if(wait_if_empty == false)
-					throw ItemNotFoundException("MutexedQueue: item not found");
+				if(wait_time_ms >= wait_time_max_ms)
+					throw ItemNotFoundException("MutexedQueue: queue is empty");
 			}
-			
-			// To wait for an empty list, we're gonna hang on this mutex
-			m_is_empty_mutex.Lock();
-			m_is_empty_mutex.Unlock();
 
-			// Then loop to the beginning and hopefully return something
+			// Wait a while before trying again
+			sleep_ms(10);
+			wait_time_ms += 10;
 		}
 	}
 
@@ -1134,11 +1127,6 @@ public:
 		return m_mutex;
 	}
 
-	JMutex & getIsEmptyMutex()
-	{
-		return m_is_empty_mutex;
-	}
-	
 	core::list<T> & getList()
 	{
 		return m_list;
@@ -1146,8 +1134,6 @@ public:
 
 protected:
 	JMutex m_mutex;
-	// This is locked always when the list is empty
-	JMutex m_is_empty_mutex;
 	core::list<T> m_list;
 };
 
@@ -1262,9 +1248,6 @@ public:
 		request.dest = dest;
 		
 		m_queue.getList().push_back(request);
-		
-		if(m_queue.getList().size() == 1)
-			m_queue.getIsEmptyMutex().Unlock();
 	}
 
 	GetRequest<Key, T, Caller, CallerData> pop(bool wait_if_empty=false)