diff --git a/data/heart.png b/data/heart.png
new file mode 100644
index 0000000000000000000000000000000000000000..ddd273dd2f09fa7574ebb3f4ec6d802be1d259bb
Binary files /dev/null and b/data/heart.png differ
diff --git a/data/oerkki1.png b/data/oerkki1.png
new file mode 100644
index 0000000000000000000000000000000000000000..c32fb99dbc9381cba551ef0c96482624ec72792a
Binary files /dev/null and b/data/oerkki1.png differ
diff --git a/data/stick.png b/data/stick.png
index 7a4663cc3a3ea2e9e4638205d831511abd4af67f..2d31797f00678eaf60a7c84536b20b3e30d743d1 100644
Binary files a/data/stick.png and b/data/stick.png differ
diff --git a/data/tool_mesepick.png b/data/tool_mesepick.png
index 886f4b21c9cde5210568b1fdd6aaa29fe18b8320..a1f3812e0992e9b575f00944c0a2580ce78f640d 100644
Binary files a/data/tool_mesepick.png and b/data/tool_mesepick.png differ
diff --git a/data/tool_steelaxe.png b/data/tool_steelaxe.png
index 85267ae7faec869b58df019acf493340c73107f5..390dbb0870e77abfbd49e2a99eca89518cd57f4d 100644
Binary files a/data/tool_steelaxe.png and b/data/tool_steelaxe.png differ
diff --git a/data/tool_steelpick.png b/data/tool_steelpick.png
index 4bb5f8be47c38454304cdb073f8cebd9885c6559..7982dafebf650bcc5d6492e33212365f57625d7c 100644
Binary files a/data/tool_steelpick.png and b/data/tool_steelpick.png differ
diff --git a/data/tool_steelshovel.png b/data/tool_steelshovel.png
index 61d90b12aec382341207ccb67c51bccd34961920..ed8413846005dfb5d02ada350a4e2c088c566cc8 100644
Binary files a/data/tool_steelshovel.png and b/data/tool_steelshovel.png differ
diff --git a/data/tool_stoneaxe.png b/data/tool_stoneaxe.png
index bcb5896893e0dcb8c1305ed9b45241212ee10744..0c5414af570340c9d9454ff53f8afc6f2c4c5cce 100644
Binary files a/data/tool_stoneaxe.png and b/data/tool_stoneaxe.png differ
diff --git a/data/tool_stonepick.png b/data/tool_stonepick.png
index 9ca3a5e03524a653dfebbb28e6ba63d25443fc07..b34de6f327a633d5127acaf39974c4a8aac219ec 100644
Binary files a/data/tool_stonepick.png and b/data/tool_stonepick.png differ
diff --git a/data/tool_stoneshovel.png b/data/tool_stoneshovel.png
index 53eb72307e20e7ba1f03ce63add91acdbc8a56ec..ba5243101a1180a6f5f07f0fb791dae2c1e9416f 100644
Binary files a/data/tool_stoneshovel.png and b/data/tool_stoneshovel.png differ
diff --git a/data/tool_woodaxe.png b/data/tool_woodaxe.png
index cb0d95f28ffd92900cfa054b2d5b41e4b2e4ccd3..34f54eff966ad5dc9398a1b45fbec8d403022eed 100644
Binary files a/data/tool_woodaxe.png and b/data/tool_woodaxe.png differ
diff --git a/data/tool_woodpick.png b/data/tool_woodpick.png
index 3592495154f62365e3adf6b6941dec31c793772d..ea728cca3fd583d1cc76a702ea48b7f0961fd51a 100644
Binary files a/data/tool_woodpick.png and b/data/tool_woodpick.png differ
diff --git a/data/tool_woodshovel.png b/data/tool_woodshovel.png
index 2645952d00b260701eafc2fad6f125ed9afebf90..649ab4c38a79fb48376ed13949ae5e182d328cd0 100644
Binary files a/data/tool_woodshovel.png and b/data/tool_woodshovel.png differ
diff --git a/minetest.conf.example b/minetest.conf.example
index c78266e7bb0353139decd210eb95646882f703d9..02cfb7aada1b296fd68f51830f933447653a4c38 100644
--- a/minetest.conf.example
+++ b/minetest.conf.example
@@ -52,6 +52,8 @@
 # Set to true to enable creative mode (unlimited inventory)
 #creative_mode = false
 
+#enable_damage = false
+
 # Player and object positions are sent at intervals specified by this
 #objectdata_inverval = 0.2
 
diff --git a/src/activeobject.h b/src/activeobject.h
index e1fc6beaf1ee3d71d8e76e6486b56f63d1f8b7a3..382f7f7981d329a3b751da08e44362b5f3a50df2 100644
--- a/src/activeobject.h
+++ b/src/activeobject.h
@@ -40,6 +40,7 @@ struct ActiveObjectMessage
 #define ACTIVEOBJECT_TYPE_TEST 1
 #define ACTIVEOBJECT_TYPE_ITEM 2
 #define ACTIVEOBJECT_TYPE_RAT 3
+#define ACTIVEOBJECT_TYPE_OERKKI1 4
 
 /*
 	Parent class for ServerActiveObject and ClientActiveObject
diff --git a/src/client.cpp b/src/client.cpp
index e2877f5fc63434ed123b68df5bb6afd4a29f7d37..702247f66364aace840cc3bf12ba6ae417903caa 100644
--- a/src/client.cpp
+++ b/src/client.cpp
@@ -90,6 +90,7 @@ Client::Client(
 	m_connection_reinit_timer = 0.0;
 	m_avg_rtt_timer = 0.0;
 	m_playerpos_send_timer = 0.0;
+	m_ignore_damage_timer = 0.0;
 
 	//m_env_mutex.Init();
 	//m_con_mutex.Init();
@@ -154,6 +155,10 @@ void Client::step(float dtime)
 	if(dtime > 2.0)
 		dtime = 2.0;
 	
+	if(m_ignore_damage_timer > dtime)
+		m_ignore_damage_timer -= dtime;
+	else
+		m_ignore_damage_timer = 0.0;
 	
 	//dstream<<"Client steps "<<dtime<<std::endl;
 
@@ -311,6 +316,9 @@ void Client::step(float dtime)
 		Do stuff if connected
 	*/
 	
+	/*
+		Handle environment
+	*/
 	{
 		// 0ms
 		//JMutexAutoLock lock(m_env_mutex); //bulk comment-out
@@ -341,8 +349,37 @@ void Client::step(float dtime)
 			{
 			}
 		}
-	}
 
+		/*
+			Get events
+		*/
+		for(;;)
+		{
+			ClientEnvEvent event = m_env.getClientEvent();
+			if(event.type == CEE_NONE)
+			{
+				break;
+			}
+			else if(event.type == CEE_PLAYER_DAMAGE)
+			{
+				if(m_ignore_damage_timer <= 0)
+				{
+					u8 damage = event.player_damage.amount;
+					sendDamage(damage);
+
+					// Add to ClientEvent queue
+					ClientEvent event;
+					event.type = CE_PLAYER_DAMAGE;
+					event.player_damage.amount = damage;
+					m_client_event_queue.push_back(event);
+				}
+			}
+		}
+	}
+	
+	/*
+		Print some info
+	*/
 	{
 		float &counter = m_avg_rtt_timer;
 		counter += dtime;
@@ -355,6 +392,10 @@ void Client::step(float dtime)
 			dstream<<DTIME<<"Client: avg_rtt="<<peer->avg_rtt<<std::endl;
 		}
 	}
+
+	/*
+		Send player position to server
+	*/
 	{
 		float &counter = m_playerpos_send_timer;
 		counter += dtime;
@@ -388,6 +429,8 @@ void Client::step(float dtime)
 			}
 			if(r.ack_block_to_server)
 			{
+				/*dstream<<"Client: ACK block ("<<r.p.X<<","<<r.p.Y
+						<<","<<r.p.Z<<")"<<std::endl;*/
 				/*
 					Acknowledge block
 				*/
@@ -447,7 +490,7 @@ void Client::ReceiveAll()
 void Client::Receive()
 {
 	DSTACK(__FUNCTION_NAME);
-	u32 data_maxsize = 10000;
+	u32 data_maxsize = 200000;
 	Buffer<u8> data(data_maxsize);
 	u16 sender_peer_id;
 	u32 datasize;
@@ -1294,219 +1337,56 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
 			}
 		}
 	}
-	else
-	{
-		dout_client<<DTIME<<"WARNING: Client: Ignoring unknown command "
-				<<command<<std::endl;
-	}
-#if 0
-	// Default to queueing it (for slow commands)
-	else
+	else if(command == TOCLIENT_HP)
 	{
-		JMutexAutoLock lock(m_incoming_queue_mutex);
-		
-		IncomingPacket packet(data, datasize);
-		m_incoming_queue.push_back(packet);
-	}
-#endif
-}
-
-#if 0
-/*
-	Returns true if there was something in queue
-*/
-bool Client::AsyncProcessPacket()
-{
-	DSTACK(__FUNCTION_NAME);
-	
-	try //for catching con::PeerNotFoundException
-	{
-
-	con::Peer *peer;
-	{
-		//JMutexAutoLock lock(m_con_mutex); //bulk comment-out
-		// All data is coming from the server
-		peer = m_con.GetPeer(PEER_ID_SERVER);
-	}
-	
-	u8 ser_version = m_server_ser_ver;
-
-	IncomingPacket packet = getPacket();
-	u8 *data = packet.m_data;
-	u32 datasize = packet.m_datalen;
-	
-	// An empty packet means queue is empty
-	if(data == NULL){
-		return false;
+		std::string datastring((char*)&data[2], datasize-2);
+		std::istringstream is(datastring, std::ios_base::binary);
+		Player *player = m_env.getLocalPlayer();
+		assert(player != NULL);
+		u8 hp = readU8(is);
+		player->hp = hp;
 	}
-	
-	if(datasize < 2)
-		return true;
-	
-	ToClientCommand command = (ToClientCommand)readU16(&data[0]);
-
-	if(command == TOCLIENT_BLOCKDATA)
+	else if(command == TOCLIENT_MOVE_PLAYER)
 	{
-		// Ignore too small packet
-		if(datasize < 8)
-			return true;
-			
-		v3s16 p;
-		p.X = readS16(&data[2]);
-		p.Y = readS16(&data[4]);
-		p.Z = readS16(&data[6]);
-		
-		/*dout_client<<DTIME<<"Client: Thread: BLOCKDATA for ("
-				<<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
-		/*dstream<<DTIME<<"Client: Thread: BLOCKDATA for ("
-				<<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
-		
-		std::string datastring((char*)&data[8], datasize-8);
-		std::istringstream istr(datastring, std::ios_base::binary);
-		
-		MapSector *sector;
-		MapBlock *block;
-		
-		{ //envlock
-			//JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
-			
-			v2s16 p2d(p.X, p.Z);
-			sector = m_env.getMap().emergeSector(p2d);
-			
-			v2s16 sp = sector->getPos();
-			if(sp != p2d)
-			{
-				dstream<<"ERROR: Got sector with getPos()="
-						<<"("<<sp.X<<","<<sp.Y<<"), tried to get"
-						<<"("<<p2d.X<<","<<p2d.Y<<")"<<std::endl;
-			}
-
-			assert(sp == p2d);
-			//assert(sector->getPos() == p2d);
-
-			//TimeTaker timer("MapBlock deSerialize");
-			// 0ms
-			
-			try{
-				block = sector->getBlockNoCreate(p.Y);
-				/*
-					Update an existing block
-				*/
-				//dstream<<"Updating"<<std::endl;
-				block->deSerialize(istr, ser_version);
-				//block->setChangedFlag();
-			}
-			catch(InvalidPositionException &e)
-			{
-				/*
-					Create a new block
-				*/
-				//dstream<<"Creating new"<<std::endl;
-				block = new MapBlock(&m_env.getMap(), p);
-				block->deSerialize(istr, ser_version);
-				sector->insertBlock(block);
-				//block->setChangedFlag();
-
-				//DEBUG
-				/*NodeMod mod;
-				mod.type = NODEMOD_CHANGECONTENT;
-				mod.param = CONTENT_MESE;
-				block->setTempMod(v3s16(8,10,8), mod);
-				block->setTempMod(v3s16(8,9,8), mod);
-				block->setTempMod(v3s16(8,8,8), mod);
-				block->setTempMod(v3s16(8,7,8), mod);
-				block->setTempMod(v3s16(8,6,8), mod);*/
-#if 0
-				/*
-					Add some coulds
-					Well, this is a dumb way to do it, they should just
-					be drawn as separate objects. But the looks of them
-					can be tested this way.
-				*/
-				if(p.Y == 3)
-				{
-					NodeMod mod;
-					mod.type = NODEMOD_CHANGECONTENT;
-					mod.param = CONTENT_CLOUD;
-					v3s16 p2;
-					p2.Y = 8;
-					for(p2.X=3; p2.X<=13; p2.X++)
-					for(p2.Z=3; p2.Z<=13; p2.Z++)
-					{
-						block->setTempMod(p2, mod);
-					}
-				}
-#endif
-			}
-		} //envlock
-		
-		/*
-			Acknowledge block.
-		*/
-		/*
-			[0] u16 command
-			[2] u8 count
-			[3] v3s16 pos_0
-			[3+6] v3s16 pos_1
-			...
-		*/
-		u32 replysize = 2+1+6;
-		SharedBuffer<u8> reply(replysize);
-		writeU16(&reply[0], TOSERVER_GOTBLOCKS);
-		reply[2] = 1;
-		writeV3S16(&reply[3], p);
-		// Send as reliable
-		m_con.Send(PEER_ID_SERVER, 1, reply, true);
+		std::string datastring((char*)&data[2], datasize-2);
+		std::istringstream is(datastring, std::ios_base::binary);
+		Player *player = m_env.getLocalPlayer();
+		assert(player != NULL);
+		v3f pos = readV3F1000(is);
+		f32 pitch = readF1000(is);
+		f32 yaw = readF1000(is);
+		player->setPosition(pos);
+		/*player->setPitch(pitch);
+		player->setYaw(yaw);*/
+
+		dstream<<"Client got TOCLIENT_MOVE_PLAYER"
+				<<" pos=("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")"
+				<<" pitch="<<pitch
+				<<" yaw="<<yaw
+				<<std::endl;
 
 		/*
-			Update Mesh of this block and blocks at x-, y- and z-.
-			Environment should not be locked as it interlocks with the
-			main thread, from which is will want to retrieve textures.
+			Add to ClientEvent queue.
+			This has to be sent to the main program because otherwise
+			it would just force the pitch and yaw values to whatever
+			the camera points to.
 		*/
-
-		//m_env.getClientMap().updateMeshes(block->getPos(), getDayNightRatio());
-		
-		MeshMakeData data;
-		{
-			//TimeTaker timer("data fill");
-			// 0ms
-			data.fill(getDayNightRatio(), block);
-		}
-		{
-			TimeTaker timer("make mesh");
-			scene::SMesh *mesh_new = NULL;
-			mesh_new = makeMapBlockMesh(&data);
-			block->replaceMesh(mesh_new);
-		}
+		ClientEvent event;
+		event.type = CE_PLAYER_FORCE_MOVE;
+		event.player_force_move.pitch = pitch;
+		event.player_force_move.yaw = yaw;
+		m_client_event_queue.push_back(event);
+
+		// Ignore damage for a few seconds, so that the player doesn't
+		// get damage from falling on ground
+		m_ignore_damage_timer = 3.0;
 	}
 	else
 	{
 		dout_client<<DTIME<<"WARNING: Client: Ignoring unknown command "
 				<<command<<std::endl;
 	}
-
-	return true;
-
-	} //try
-	catch(con::PeerNotFoundException &e)
-	{
-		/*dout_client<<DTIME<<"Client::AsyncProcessData(): Cancelling: The server"
-				" connection doesn't exist (a timeout or not yet connected?)"<<std::endl;*/
-		return false;
-	}
-}
-
-bool Client::AsyncProcessData()
-{
-	for(;;)
-	{
-		bool r = AsyncProcessPacket();
-		if(r == false)
-			break;
-	}
-	return false;
 }
-#endif
 
 void Client::Send(u16 channelnum, SharedBuffer<u8> data, bool reliable)
 {
@@ -1514,28 +1394,6 @@ void Client::Send(u16 channelnum, SharedBuffer<u8> data, bool reliable)
 	m_con.Send(PEER_ID_SERVER, channelnum, data, reliable);
 }
 
-#if 0
-IncomingPacket Client::getPacket()
-{
-	JMutexAutoLock lock(m_incoming_queue_mutex);
-	
-	core::list<IncomingPacket>::Iterator i;
-	// Refer to first one
-	i = m_incoming_queue.begin();
-
-	// If queue is empty, return empty packet
-	if(i == m_incoming_queue.end()){
-		IncomingPacket packet;
-		return packet;
-	}
-	
-	// Pop out first packet and return it
-	IncomingPacket packet = *i;
-	m_incoming_queue.erase(i);
-	return packet;
-}
-#endif
-
 void Client::groundAction(u8 action, v3s16 nodepos_undersurface,
 		v3s16 nodepos_oversurface, u16 item)
 {
@@ -1739,6 +1597,21 @@ void Client::sendChatMessage(const std::wstring &message)
 	Send(0, data, true);
 }
 
+void Client::sendDamage(u8 damage)
+{
+	DSTACK(__FUNCTION_NAME);
+	std::ostringstream os(std::ios_base::binary);
+
+	writeU16(os, TOSERVER_DAMAGE);
+	writeU8(os, damage);
+
+	// Make data buffer
+	std::string s = os.str();
+	SharedBuffer<u8> data((u8*)s.c_str(), s.size());
+	// Send as reliable
+	Send(0, data, true);
+}
+
 void Client::sendPlayerPos()
 {
 	//JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
@@ -2061,6 +1934,13 @@ u32 Client::getDayNightRatio()
 	return m_env.getDayNightRatio();
 }
 
+u16 Client::getHP()
+{
+	Player *player = m_env.getLocalPlayer();
+	assert(player != NULL);
+	return player->hp;
+}
+
 void Client::addUpdateMeshTask(v3s16 p, bool ack_to_server)
 {
 	/*dstream<<"Client::addUpdateMeshTask(): "
@@ -2141,3 +2021,15 @@ void Client::addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server)
 	catch(InvalidPositionException &e){}
 }
 
+ClientEvent Client::getClientEvent()
+{
+	if(m_client_event_queue.size() == 0)
+	{
+		ClientEvent event;
+		event.type = CE_NONE;
+		return event;
+	}
+	return m_client_event_queue.pop_front();
+}
+
+
diff --git a/src/client.h b/src/client.h
index ef3dd435a04b5692dd49dfea576456da82e87893..ee73cc42ce83a1e6d3e61076ae84e1e5cd881700 100644
--- a/src/client.h
+++ b/src/client.h
@@ -174,55 +174,28 @@ class MeshUpdateThread : public SimpleThread
 	MutexedQueue<MeshUpdateResult> m_queue_out;
 };
 
-#if 0
-struct IncomingPacket
+enum ClientEventType
 {
-	IncomingPacket()
-	{
-		m_data = NULL;
-		m_datalen = 0;
-		m_refcount = NULL;
-	}
-	IncomingPacket(const IncomingPacket &a)
-	{
-		m_data = a.m_data;
-		m_datalen = a.m_datalen;
-		m_refcount = a.m_refcount;
-		if(m_refcount != NULL)
-			(*m_refcount)++;
-	}
-	IncomingPacket(u8 *data, u32 datalen)
-	{
-		m_data = new u8[datalen];
-		memcpy(m_data, data, datalen);
-		m_datalen = datalen;
-		m_refcount = new s32(1);
-	}
-	~IncomingPacket()
-	{
-		if(m_refcount != NULL){
-			assert(*m_refcount > 0);
-			(*m_refcount)--;
-			if(*m_refcount == 0){
-				if(m_data != NULL)
-					delete[] m_data;
-				delete m_refcount;
-			}
-		}
-	}
-	/*IncomingPacket & operator=(IncomingPacket a)
-	{
-		m_data = a.m_data;
-		m_datalen = a.m_datalen;
-		m_refcount = a.m_refcount;
-		(*m_refcount)++;
-		return *this;
-	}*/
-	u8 *m_data;
-	u32 m_datalen;
-	s32 *m_refcount;
+	CE_NONE,
+	CE_PLAYER_DAMAGE,
+	CE_PLAYER_FORCE_MOVE
+};
+
+struct ClientEvent
+{
+	ClientEventType type;
+	union{
+		struct{
+		} none;
+		struct{
+			u8 amount;
+		} player_damage;
+		struct{
+			f32 pitch;
+			f32 yaw;
+		} player_force_move;
+	};
 };
-#endif
 
 class Client : public con::PeerHandler, public InventoryManager
 {
@@ -281,6 +254,7 @@ class Client : public con::PeerHandler, public InventoryManager
 	void sendSignNodeText(v3s16 p, std::string text);
 	void sendInventoryAction(InventoryAction *a);
 	void sendChatMessage(const std::wstring &message);
+	void sendDamage(u8 damage);
 	
 	// locks envlock
 	void removeNode(v3s16 p);
@@ -330,6 +304,8 @@ class Client : public con::PeerHandler, public InventoryManager
 
 	u32 getDayNightRatio();
 
+	u16 getHP();
+
 	//void updateSomeExpiredMeshes();
 	
 	void setTempMod(v3s16 p, NodeMod mod)
@@ -394,13 +370,13 @@ class Client : public con::PeerHandler, public InventoryManager
 
 	u64 getMapSeed(){ return m_map_seed; }
 
-	/*
-		These are not thread-safe
-	*/
 	void addUpdateMeshTask(v3s16 blockpos, bool ack_to_server=false);
 	// Including blocks at appropriate edges
 	void addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server=false);
 
+	// Get event from queue. CE_NONE is returned if queue is empty.
+	ClientEvent getClientEvent();
+	
 private:
 	
 	// Virtual methods from con::PeerHandler
@@ -419,6 +395,7 @@ class Client : public con::PeerHandler, public InventoryManager
 	float m_connection_reinit_timer;
 	float m_avg_rtt_timer;
 	float m_playerpos_send_timer;
+	float m_ignore_damage_timer; // Used after server moves player
 
 	MeshUpdateThread m_mesh_update_thread;
 	
@@ -454,6 +431,8 @@ class Client : public con::PeerHandler, public InventoryManager
 	u64 m_map_seed;
 	
 	InventoryContext m_inventory_context;
+
+	Queue<ClientEvent> m_client_event_queue;
 };
 
 #endif // !SERVER
diff --git a/src/clientobject.cpp b/src/clientobject.cpp
index 5b744de6ce0d99b4c6490503e46002e68ab244fe..78258add88bce8cb1fdcc4cbf5806c16ac6054c8 100644
--- a/src/clientobject.cpp
+++ b/src/clientobject.cpp
@@ -494,7 +494,7 @@ void RatCAO::updateLight(u8 light_at_pos)
 
 v3s16 RatCAO::getLightPosition()
 {
-	return floatToInt(m_position, BS);
+	return floatToInt(m_position+v3f(0,BS*0.5,0), BS);
 }
 
 void RatCAO::updateNodePos()
@@ -552,4 +552,181 @@ void RatCAO::initialize(const std::string &data)
 	updateNodePos();
 }
 
+/*
+	Oerkki1CAO
+*/
+
+#include "inventory.h"
+
+// Prototype
+Oerkki1CAO proto_Oerkki1CAO;
+
+Oerkki1CAO::Oerkki1CAO():
+	ClientActiveObject(0),
+	m_selection_box(-BS/3.,0.0,-BS/3., BS/3.,BS*2.,BS/3.),
+	m_node(NULL),
+	m_position(v3f(0,10*BS,0)),
+	m_yaw(0)
+{
+	ClientActiveObject::registerType(getType(), create);
+}
+
+Oerkki1CAO::~Oerkki1CAO()
+{
+}
+
+ClientActiveObject* Oerkki1CAO::create()
+{
+	return new Oerkki1CAO();
+}
+
+void Oerkki1CAO::addToScene(scene::ISceneManager *smgr)
+{
+	if(m_node != NULL)
+		return;
+	
+	video::IVideoDriver* driver = smgr->getVideoDriver();
+	
+	scene::SMesh *mesh = new scene::SMesh();
+	scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+	video::SColor c(255,255,255,255);
+	video::S3DVertex vertices[4] =
+	{
+		video::S3DVertex(-BS/2,0,0, 0,0,0, c, 0,1),
+		video::S3DVertex(BS/2,0,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),
+	};
+	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().setTexture(0, NULL);
+	buf->getMaterial().setTexture
+			(0, driver->getTexture(porting::getDataPath("oerkki1.png").c_str()));
+	buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
+	buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true);
+	buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+	// Add to mesh
+	mesh->addMeshBuffer(buf);
+	buf->drop();
+	m_node = smgr->addMeshSceneNode(mesh, NULL);
+	mesh->drop();
+	// Set it to use the materials of the meshbuffers directly.
+	// This is needed for changing the texture in the future
+	m_node->setReadOnlyMaterials(true);
+	updateNodePos();
+}
+
+void Oerkki1CAO::removeFromScene()
+{
+	if(m_node == NULL)
+		return;
+
+	m_node->remove();
+	m_node = NULL;
+}
+
+void Oerkki1CAO::updateLight(u8 light_at_pos)
+{
+	if(m_node == NULL)
+		return;
+
+	u8 li = decode_light(light_at_pos);
+	video::SColor color(255,li,li,li);
+
+	scene::IMesh *mesh = m_node->getMesh();
+	if(mesh == NULL)
+		return;
+	
+	u16 mc = mesh->getMeshBufferCount();
+	for(u16 j=0; j<mc; j++)
+	{
+		scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
+		video::S3DVertex *vertices = (video::S3DVertex*)buf->getVertices();
+		u16 vc = buf->getVertexCount();
+		for(u16 i=0; i<vc; i++)
+		{
+			vertices[i].Color = color;
+		}
+	}
+}
+
+v3s16 Oerkki1CAO::getLightPosition()
+{
+	return floatToInt(m_position+v3f(0,BS*1.5,0), BS);
+}
+
+void Oerkki1CAO::updateNodePos()
+{
+	if(m_node == NULL)
+		return;
+
+	//m_node->setPosition(m_position);
+	m_node->setPosition(pos_translator.vect_show);
+
+	v3f rot = m_node->getRotation();
+	rot.Y = 180.0 - m_yaw + 90.0;
+	m_node->setRotation(rot);
+}
+
+void Oerkki1CAO::step(float dtime, ClientEnvironment *env)
+{
+	pos_translator.translate(dtime);
+	updateNodePos();
+
+	LocalPlayer *player = env->getLocalPlayer();
+	assert(player);
+	
+	v3f playerpos = player->getPosition();
+	v2f playerpos_2d(playerpos.X,playerpos.Z);
+	v2f objectpos_2d(m_position.X,m_position.Z);
+
+	if(fabs(objectpos_2d.Y - playerpos_2d.Y) < 2.0*BS &&
+			objectpos_2d.getDistanceFrom(playerpos_2d) < 1.0*BS)
+	{
+		if(m_attack_interval.step(dtime, 0.5))
+		{
+			env->damageLocalPlayer(2);
+		}
+	}
+}
+
+void Oerkki1CAO::processMessage(const std::string &data)
+{
+	//dstream<<"Oerkki1CAO: Got message"<<std::endl;
+	std::istringstream is(data, std::ios::binary);
+	// command
+	u8 cmd = readU8(is);
+	if(cmd == 0)
+	{
+		// pos
+		m_position = readV3F1000(is);
+		pos_translator.update(m_position);
+		// yaw
+		m_yaw = readF1000(is);
+		updateNodePos();
+	}
+}
+
+void Oerkki1CAO::initialize(const std::string &data)
+{
+	//dstream<<"Oerkki1CAO: Got init data"<<std::endl;
+	
+	{
+		std::istringstream is(data, std::ios::binary);
+		// version
+		u8 version = readU8(is);
+		// check version
+		if(version != 0)
+			return;
+		// pos
+		m_position = readV3F1000(is);
+		pos_translator.init(m_position);
+	}
+	
+	updateNodePos();
+}
+
 
diff --git a/src/clientobject.h b/src/clientobject.h
index 569e9eca6905857cfa69943fd34c28a8eccdff4c..8d211fef38dde5a9f8f59443e688fcf6b61c880f 100644
--- a/src/clientobject.h
+++ b/src/clientobject.h
@@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "common_irrlicht.h"
 #include "activeobject.h"
+#include "utility.h"
 
 /*
 
@@ -267,5 +268,49 @@ class RatCAO : public ClientActiveObject
 	SmoothTranslator pos_translator;
 };
 
+/*
+	Oerkki1CAO
+*/
+
+class Oerkki1CAO : public ClientActiveObject
+{
+public:
+	Oerkki1CAO();
+	virtual ~Oerkki1CAO();
+	
+	u8 getType() const
+	{
+		return ACTIVEOBJECT_TYPE_OERKKI1;
+	}
+	
+	static ClientActiveObject* create();
+
+	void addToScene(scene::ISceneManager *smgr);
+	void removeFromScene();
+	void updateLight(u8 light_at_pos);
+	v3s16 getLightPosition();
+	void updateNodePos();
+
+	void step(float dtime, ClientEnvironment *env);
+
+	void processMessage(const std::string &data);
+
+	void initialize(const std::string &data);
+	
+	core::aabbox3d<f32>* getSelectionBox()
+		{return &m_selection_box;}
+	v3f getPosition()
+		{return pos_translator.vect_show;}
+		//{return m_position;}
+
+private:
+	IntervalLimiter m_attack_interval;
+	core::aabbox3d<f32> m_selection_box;
+	scene::IMeshSceneNode *m_node;
+	v3f m_position;
+	float m_yaw;
+	SmoothTranslator pos_translator;
+};
+
 #endif
 
diff --git a/src/clientserver.h b/src/clientserver.h
index fadafed5fe4dbe53b82a0dc7794eaacd9eea476d..46ffa5eab365d25281356840a2fdb04f2cef9e5a 100644
--- a/src/clientserver.h
+++ b/src/clientserver.h
@@ -33,15 +33,18 @@ enum ToClientCommand
 
 		[0] u16 TOSERVER_INIT
 		[2] u8 deployed version
-		[3] v3s16 player's position + v3f(0,BS/2,0) floatToInt'd
-		[4] u64 map seed (new as of 2011-02-27)
+		[3] v3s16 player's position + v3f(0,BS/2,0) floatToInt'd 
+		([4] u64 map seed (new as of 2011-02-27))
+
+		NOTE: The position in here is deprecated; position is
+		      explicitly sent afterwards
 	*/
 
 	TOCLIENT_BLOCKDATA = 0x20, //TODO: Multiple blocks
 	TOCLIENT_ADDNODE = 0x21,
 	TOCLIENT_REMOVENODE = 0x22,
 	
-	TOCLIENT_PLAYERPOS = 0x23,
+	TOCLIENT_PLAYERPOS = 0x23, // Obsolete
 	/*
 		[0] u16 command
 		// Followed by an arbitary number of these:
@@ -62,9 +65,9 @@ enum ToClientCommand
 		[N] char[20] name
 	*/
 	
-	TOCLIENT_OPT_BLOCK_NOT_FOUND = 0x25, // Not used
+	TOCLIENT_OPT_BLOCK_NOT_FOUND = 0x25, // Obsolete
 
-	TOCLIENT_SECTORMETA = 0x26, // Not used
+	TOCLIENT_SECTORMETA = 0x26, // Obsolete
 	/*
 		[0] u16 command
 		[2] u8 sector count
@@ -134,6 +137,19 @@ enum ToClientCommand
 		}
 	*/
 
+	TOCLIENT_HP = 0x33,
+	/*
+		u16 command
+		u8 hp
+	*/
+
+	TOCLIENT_MOVE_PLAYER = 0x34,
+	/*
+		u16 command
+		v3f1000 player position
+		f1000 player pitch
+		f1000 player yaw
+	*/
 };
 
 enum ToServerCommand
@@ -155,9 +171,9 @@ enum ToServerCommand
 		[0] u16 TOSERVER_INIT2
 	*/
 
-	TOSERVER_GETBLOCK=0x20, // Not used
-	TOSERVER_ADDNODE = 0x21, // Not used
-	TOSERVER_REMOVENODE = 0x22, // deprecated
+	TOSERVER_GETBLOCK=0x20, // Obsolete
+	TOSERVER_ADDNODE = 0x21, // Obsolete
+	TOSERVER_REMOVENODE = 0x22, // Obsolete
 
 	TOSERVER_PLAYERPOS = 0x23,
 	/*
@@ -186,7 +202,7 @@ enum ToServerCommand
 		...
 	*/
 
-	TOSERVER_ADDNODE_FROM_INVENTORY = 0x26, // deprecated
+	TOSERVER_ADDNODE_FROM_INVENTORY = 0x26, // Obsolete
 	/*
 		[0] u16 command
 		[2] v3s16 pos
@@ -218,9 +234,9 @@ enum ToServerCommand
 		3: digging completed
 	*/
 	
-	TOSERVER_RELEASE = 0x29, // Not used
+	TOSERVER_RELEASE = 0x29, // Obsolete
 
-	TOSERVER_SIGNTEXT = 0x30,
+	TOSERVER_SIGNTEXT = 0x30, // Old signs
 	/*
 		u16 command
 		v3s16 blockpos
@@ -257,7 +273,12 @@ enum ToServerCommand
 		[3] u16 id
 		[5] u16 item
 	*/
-
+	
+	TOSERVER_DAMAGE = 0x35,
+	/*
+		u16 command
+		u8 amount
+	*/
 };
 
 inline SharedBuffer<u8> makePacket_TOCLIENT_TIME_OF_DAY(u16 time)
diff --git a/src/collision.cpp b/src/collision.cpp
index 83cefe4d1e1a5db78db65a18281b4e0c46b4caab..63186a84a5e72a2b803777f4c8795d652b5097b9 100644
--- a/src/collision.cpp
+++ b/src/collision.cpp
@@ -70,6 +70,7 @@ collisionMoveResult collisionMoveSimple(Map *map, f32 pos_max_d,
 	
 	/*
 		Go through every node around the object
+		TODO: Calculate the range of nodes that need to be checked
 	*/
 	for(s16 y = oldpos_i.Y - 1; y <= oldpos_i.Y + 2; y++)
 	for(s16 z = oldpos_i.Z - 1; z <= oldpos_i.Z + 1; z++)
diff --git a/src/collision.h b/src/collision.h
index 17243140148a8686e1a1fe50974935816979eb6c..9c913c6a9720d52116fadc58060a4eb2bb32a744 100644
--- a/src/collision.h
+++ b/src/collision.h
@@ -38,6 +38,16 @@ collisionMoveResult collisionMoveSimple(Map *map, f32 pos_max_d,
 		f32 dtime, v3f &pos_f, v3f &speed_f);
 //{return collisionMoveResult();}
 
+enum CollisionType
+{
+	COLLISION_FALL
+};
+
+struct CollisionInfo
+{
+	CollisionType t;
+	f32 speed;
+};
 
 #endif
 
diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp
index 2a548d406e3a85155bb88151d9f6b58b33c7565b..b5d86391414b22cc62fb466ec4871b51f73ca516 100644
--- a/src/defaultsettings.cpp
+++ b/src/defaultsettings.cpp
@@ -55,6 +55,7 @@ void set_default_settings()
 
 	g_settings.setDefault("enable_experimental", "false");
 	g_settings.setDefault("creative_mode", "false");
+	g_settings.setDefault("enable_damage", "false"); //TODO: Set to true
 
 	g_settings.setDefault("objectdata_interval", "0.2");
 	g_settings.setDefault("active_object_range", "2");
diff --git a/src/environment.cpp b/src/environment.cpp
index 3f95ed9f9be7f3c71d480c084f80868f4ffc9508..b3055ca6f7b88b3d83a8daec93407f830db446c7 100644
--- a/src/environment.cpp
+++ b/src/environment.cpp
@@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "environment.h"
 #include "filesys.h"
 #include "porting.h"
+#include "collision.h"
 
 Environment::Environment()
 {
@@ -377,6 +378,55 @@ void ServerEnvironment::deSerializePlayers(const std::string &savedir)
 	}
 }
 
+#if 0
+void spawnRandomObjects(MapBlock *block)
+{
+	for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
+	for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
+	{
+		bool last_node_walkable = false;
+		for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
+		{
+			v3s16 p(x0,y0,z0);
+			MapNode n = block->getNodeNoEx(p);
+			if(n.d == CONTENT_IGNORE)
+				continue;
+			if(content_features(n.d).liquid_type != LIQUID_NONE)
+				continue;
+			if(content_features(n.d).walkable)
+			{
+				last_node_walkable = true;
+				continue;
+			}
+			if(last_node_walkable)
+			{
+				// If block contains light information
+				if(content_features(n.d).param_type == CPT_LIGHT)
+				{
+					if(n.getLight(LIGHTBANK_DAY) <= 5)
+					{
+						if(myrand() % 1000 == 0)
+						{
+							v3f pos_f = intToFloat(p+block->getPosRelative(), BS);
+							pos_f.Y -= BS*0.4;
+							ServerActiveObject *obj = new Oerkki1SAO(NULL,0,pos_f);
+							std::string data = obj->getStaticData();
+							StaticObject s_obj(obj->getType(),
+									obj->getBasePosition(), data);
+							// Add one
+							block->m_static_objects.insert(0, s_obj);
+							delete obj;
+							block->setChangedFlag();
+						}
+					}
+				}
+			}
+			last_node_walkable = false;
+		}
+	}
+}
+#endif
+
 void ServerEnvironment::step(float dtime)
 {
 	DSTACK(__FUNCTION_NAME);
@@ -429,26 +479,29 @@ void ServerEnvironment::step(float dtime)
 			}
 		}
 	}
-	
+
 	/*
 		Step active objects
 	*/
-
-	bool send_recommended = false;
-	m_send_recommended_timer += dtime;
-	if(m_send_recommended_timer > 0.15)
 	{
-		m_send_recommended_timer = 0;
-		send_recommended = true;
-	}
+		//TimeTaker timer("Step active objects");
 
-	for(core::map<u16, ServerActiveObject*>::Iterator
-			i = m_active_objects.getIterator();
-			i.atEnd()==false; i++)
-	{
-		ServerActiveObject* obj = i.getNode()->getValue();
-		// Step object, putting messages directly to the queue
-		obj->step(dtime, m_active_object_messages, send_recommended);
+		bool send_recommended = false;
+		m_send_recommended_timer += dtime;
+		if(m_send_recommended_timer > 0.15)
+		{
+			m_send_recommended_timer = 0;
+			send_recommended = true;
+		}
+
+		for(core::map<u16, ServerActiveObject*>::Iterator
+				i = m_active_objects.getIterator();
+				i.atEnd()==false; i++)
+		{
+			ServerActiveObject* obj = i.getNode()->getValue();
+			// Step object, putting messages directly to the queue
+			obj->step(dtime, m_active_object_messages, send_recommended);
+		}
 	}
 
 	if(m_object_management_interval.step(dtime, 0.5))
@@ -506,7 +559,7 @@ void ServerEnvironment::step(float dtime)
 		
 
 		const s16 to_active_max_blocks = 3;
-		const f32 to_static_max_f = (to_active_max_blocks+1)*MAP_BLOCKSIZE*BS;
+		const f32 to_static_max_f = (to_active_max_blocks+2)*MAP_BLOCKSIZE*BS;
 
 		/*
 			Convert stored objects from blocks near the players to active.
@@ -719,7 +772,8 @@ void ServerEnvironment::step(float dtime)
 
 		//TestSAO *obj = new TestSAO(this, 0, pos);
 		//ServerActiveObject *obj = new ItemSAO(this, 0, pos, "CraftItem Stick 1");
-		ServerActiveObject *obj = new RatSAO(this, 0, pos);
+		//ServerActiveObject *obj = new RatSAO(this, 0, pos);
+		ServerActiveObject *obj = new Oerkki1SAO(this, 0, pos);
 		addActiveObject(obj);
 	}
 #endif
@@ -976,14 +1030,18 @@ void ClientEnvironment::step(float dtime)
 		//TimeTaker timer("Client m_map->timerUpdate()", g_device);
 		m_map->timerUpdate(dtime);
 	}
-
+	
+	// Get local player
+	LocalPlayer *lplayer = getLocalPlayer();
+	assert(lplayer);
+	// collision info queue
+	core::list<CollisionInfo> player_collisions;
+	
 	/*
 		Get the speed the player is going
 	*/
 	f32 player_speed = 0.001; // just some small value
-	LocalPlayer *lplayer = getLocalPlayer();
-	if(lplayer)
-		player_speed = lplayer->getSpeed().getLength();
+	player_speed = lplayer->getSpeed().getLength();
 	
 	/*
 		Maximum position increment
@@ -1036,20 +1094,18 @@ void ClientEnvironment::step(float dtime)
 		*/
 		
 		{
-			Player *player = getLocalPlayer();
-
-			v3f playerpos = player->getPosition();
+			v3f lplayerpos = lplayer->getPosition();
 			
 			// Apply physics
 			if(free_move == false)
 			{
 				// Gravity
-				v3f speed = player->getSpeed();
-				if(player->swimming_up == false)
+				v3f speed = lplayer->getSpeed();
+				if(lplayer->swimming_up == false)
 					speed.Y -= 9.81 * BS * dtime_part * 2;
 
 				// Water resistance
-				if(player->in_water_stable || player->in_water)
+				if(lplayer->in_water_stable || lplayer->in_water)
 				{
 					f32 max_down = 2.0*BS;
 					if(speed.Y < -max_down) speed.Y = -max_down;
@@ -1061,19 +1117,47 @@ void ClientEnvironment::step(float dtime)
 					}
 				}
 
-				player->setSpeed(speed);
+				lplayer->setSpeed(speed);
 			}
 
 			/*
-				Move the player.
+				Move the lplayer.
 				This also does collision detection.
 			*/
-			player->move(dtime_part, *m_map, position_max_increment);
+			lplayer->move(dtime_part, *m_map, position_max_increment,
+					&player_collisions);
 		}
 	}
 	while(dtime_downcount > 0.001);
 		
 	//std::cout<<"Looped "<<loopcount<<" times."<<std::endl;
+
+	for(core::list<CollisionInfo>::Iterator
+			i = player_collisions.begin();
+			i != player_collisions.end(); i++)
+	{
+		CollisionInfo &info = *i;
+		if(info.t == COLLISION_FALL)
+		{
+			//f32 tolerance = BS*10; // 2 without damage
+			f32 tolerance = BS*12; // 3 without damage
+			f32 factor = 1;
+			if(info.speed > tolerance)
+			{
+				f32 damage_f = (info.speed - tolerance)/BS*factor;
+				u16 damage = (u16)(damage_f+0.5);
+				if(lplayer->hp > damage)
+					lplayer->hp -= damage;
+				else
+					lplayer->hp = 0;
+
+				ClientEnvEvent event;
+				event.type = CEE_PLAYER_DAMAGE;
+				event.player_damage.amount = damage;
+				m_client_event_queue.push_back(event);
+			}
+		}
+	}
 	
 	/*
 		Stuff that can be done in an arbitarily large dtime
@@ -1287,6 +1371,30 @@ void ClientEnvironment::processActiveObjectMessage(u16 id,
 	obj->processMessage(data);
 }
 
+/*
+	Callbacks for activeobjects
+*/
+
+void ClientEnvironment::damageLocalPlayer(u8 damage)
+{
+	LocalPlayer *lplayer = getLocalPlayer();
+	assert(lplayer);
+
+	if(lplayer->hp > damage)
+		lplayer->hp -= damage;
+	else
+		lplayer->hp = 0;
+
+	ClientEnvEvent event;
+	event.type = CEE_PLAYER_DAMAGE;
+	event.player_damage.amount = damage;
+	m_client_event_queue.push_back(event);
+}
+
+/*
+	Client likes to call these
+*/
+	
 void ClientEnvironment::getActiveObjects(v3f origin, f32 max_d,
 		core::array<DistanceSortedActiveObject> &dest)
 {
@@ -1307,6 +1415,16 @@ void ClientEnvironment::getActiveObjects(v3f origin, f32 max_d,
 	}
 }
 
+ClientEnvEvent ClientEnvironment::getClientEvent()
+{
+	if(m_client_event_queue.size() == 0)
+	{
+		ClientEnvEvent event;
+		event.type = CEE_NONE;
+		return event;
+	}
+	return m_client_event_queue.pop_front();
+}
 
 #endif // #ifndef SERVER
 
diff --git a/src/environment.h b/src/environment.h
index e82cea6ae275ab9fe2330b543397b8bfa4431a60..00192d26278d1bb8a4b23c5900a264a4b2980df9 100644
--- a/src/environment.h
+++ b/src/environment.h
@@ -170,6 +170,24 @@ class ServerEnvironment : public Environment
 	Client uses an environment mutex.
 */
 
+enum ClientEnvEventType
+{
+	CEE_NONE,
+	CEE_PLAYER_DAMAGE
+};
+
+struct ClientEnvEvent
+{
+	ClientEnvEventType type;
+	union {
+		struct{
+		} none;
+		struct{
+			u8 amount;
+		} player_damage;
+	};
+};
+
 class ClientEnvironment : public Environment
 {
 public:
@@ -214,15 +232,29 @@ class ClientEnvironment : public Environment
 	void removeActiveObject(u16 id);
 
 	void processActiveObjectMessage(u16 id, const std::string &data);
+
+	/*
+		Callbacks for activeobjects
+	*/
+
+	void damageLocalPlayer(u8 damage);
+
+	/*
+		Client likes to call these
+	*/
 	
 	// Get all nearby objects
 	void getActiveObjects(v3f origin, f32 max_d,
 			core::array<DistanceSortedActiveObject> &dest);
 	
+	// Get event from queue. CEE_NONE is returned if queue is empty.
+	ClientEnvEvent getClientEvent();
+	
 private:
 	ClientMap *m_map;
 	scene::ISceneManager *m_smgr;
 	core::map<u16, ClientActiveObject*> m_active_objects;
+	Queue<ClientEnvEvent> m_client_event_queue;
 };
 
 #endif
diff --git a/src/inventory.h b/src/inventory.h
index d2d23542ea3c95002d16b66a66c8c7b7c3379a54..f162952d32f19320c6edb16f886066648f09f7c6 100644
--- a/src/inventory.h
+++ b/src/inventory.h
@@ -369,6 +369,12 @@ class ToolItem : public InventoryItem
 			basename = "tool_stoneaxe.png";
 		else if(m_toolname == "SteelAxe")
 			basename = "tool_steelaxe.png";
+		else if(m_toolname == "WSword")
+			basename = "tool_woodsword.png";
+		else if(m_toolname == "STSword")
+			basename = "tool_stonesword.png";
+		else if(m_toolname == "SteelSword")
+			basename = "tool_steelsword.png";
 		else
 			basename = "cloud.png";
 		
diff --git a/src/main.cpp b/src/main.cpp
index bf5f3182b36c0e0a45c8dce234cd83dccfb9daaa..436e5babc9557cfdf908adefc14a1b0c6a392ccf 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -93,6 +93,10 @@ SUGG: Meshes of blocks could be split into 6 meshes facing into
 SUGG: Calculate lighting per vertex to get a lighting effect like in
       bartwe's game
 
+SUGG: Background music based on cellular automata?
+      http://www.earslap.com/projectslab/otomata
+
+
 Gaming ideas:
 -------------
 
@@ -126,6 +130,12 @@ Game content:
 	- You can drop on top of it, and have some time to attack there
 	  before he shakes you off
 
+- Maybe the difficulty could come from monsters getting tougher in
+  far-away places, and the player starting to need something from
+  there when time goes by.
+  - The player would have some of that stuff at the beginning, and
+    would need new supplies of it when it runs out
+
 Documentation:
 --------------
 
@@ -1210,7 +1220,7 @@ void updateViewingRange(f32 frametime_in, Client *client)
 
 void draw_hotbar(video::IVideoDriver *driver, gui::IGUIFont *font,
 		v2s32 centerlowerpos, s32 imgsize, s32 itemcount,
-		Inventory *inventory)
+		Inventory *inventory, s32 halfheartcount)
 {
 	InventoryList *mainlist = inventory->getList("main");
 	if(mainlist == NULL)
@@ -1259,6 +1269,40 @@ void draw_hotbar(video::IVideoDriver *driver, gui::IGUIFont *font,
 			drawInventoryItem(driver, font, item, rect, NULL);
 		}
 	}
+	
+	/*
+		Draw hearts
+	*/
+	{
+		video::ITexture *heart_texture =
+				driver->getTexture(porting::getDataPath("heart.png").c_str());
+		v2s32 p = pos + v2s32(0, -20);
+		for(s32 i=0; i<halfheartcount/2; i++)
+		{
+			const video::SColor color(255,255,255,255);
+			const video::SColor colors[] = {color,color,color,color};
+			core::rect<s32> rect(0,0,16,16);
+			rect += p;
+			driver->draw2DImage(heart_texture, rect,
+				core::rect<s32>(core::position2d<s32>(0,0),
+				core::dimension2di(heart_texture->getOriginalSize())),
+				NULL, colors, true);
+			p += v2s32(20,0);
+		}
+		if(halfheartcount % 2 == 1)
+		{
+			const video::SColor color(255,255,255,255);
+			const video::SColor colors[] = {color,color,color,color};
+			core::rect<s32> rect(0,0,16/2,16);
+			rect += p;
+			core::dimension2di srcd(heart_texture->getOriginalSize());
+			srcd.Width /= 2;
+			driver->draw2DImage(heart_texture, rect,
+				core::rect<s32>(core::position2d<s32>(0,0), srcd),
+				NULL, colors, true);
+			p += v2s32(20,0);
+		}
+	}
 }
 
 #if 0
@@ -1519,6 +1563,215 @@ void SpeedTests()
 	}
 }
 
+void getPointedNode(v3f player_position,
+		v3f camera_direction, v3f camera_position,
+		bool &nodefound, core::line3d<f32> shootline,
+		v3s16 &nodepos, v3s16 &neighbourpos,
+		core::aabbox3d<f32> &nodehilightbox,
+		f32 d)
+{
+	assert(g_client);
+
+	f32 mindistance = BS * 1001;
+	
+	v3s16 pos_i = floatToInt(player_position, BS);
+
+	/*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
+			<<std::endl;*/
+
+	s16 a = d;
+	s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
+	s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
+	s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
+	s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
+	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 x = xstart; x <= xend; x++)
+	{
+		MapNode n;
+		try
+		{
+			n = g_client->getNode(v3s16(x,y,z));
+			if(content_pointable(n.d) == false)
+				continue;
+		}
+		catch(InvalidPositionException &e)
+		{
+			continue;
+		}
+
+		v3s16 np(x,y,z);
+		v3f npf = intToFloat(np, BS);
+		
+		f32 d = 0.01;
+		
+		v3s16 dirs[6] = {
+			v3s16(0,0,1), // back
+			v3s16(0,1,0), // top
+			v3s16(1,0,0), // right
+			v3s16(0,0,-1), // front
+			v3s16(0,-1,0), // bottom
+			v3s16(-1,0,0), // left
+		};
+		
+		/*
+			Meta-objects
+		*/
+		if(n.d == CONTENT_TORCH)
+		{
+			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();
+
+			core::aabbox3d<f32> box;
+			
+			// 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(distance < mindistance)
+			{
+				if(box.intersectsWithLine(shootline))
+				{
+					nodefound = true;
+					nodepos = np;
+					neighbourpos = np;
+					mindistance = distance;
+					nodehilightbox = box;
+				}
+			}
+		}
+		else if(n.d == CONTENT_SIGN_WALL)
+		{
+			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();
+
+			v3f vertices[4] =
+			{
+				v3f(BS*0.42,-BS*0.35,-BS*0.4),
+				v3f(BS*0.49, BS*0.35, BS*0.4),
+			};
+
+			for(s32 i=0; i<2; i++)
+			{
+				if(dir == v3s16(1,0,0))
+					vertices[i].rotateXZBy(0);
+				if(dir == v3s16(-1,0,0))
+					vertices[i].rotateXZBy(180);
+				if(dir == v3s16(0,0,1))
+					vertices[i].rotateXZBy(90);
+				if(dir == v3s16(0,0,-1))
+					vertices[i].rotateXZBy(-90);
+				if(dir == v3s16(0,-1,0))
+					vertices[i].rotateXYBy(-90);
+				if(dir == v3s16(0,1,0))
+					vertices[i].rotateXYBy(90);
+
+				vertices[i] += npf;
+			}
+
+			core::aabbox3d<f32> box;
+
+			box = core::aabbox3d<f32>(vertices[0]);
+			box.addInternalPoint(vertices[1]);
+
+			if(distance < mindistance)
+			{
+				if(box.intersectsWithLine(shootline))
+				{
+					nodefound = true;
+					nodepos = np;
+					neighbourpos = np;
+					mindistance = distance;
+					nodehilightbox = 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;
+
+						//nodehilightbox = facebox;
+
+						const float d = 0.502;
+						core::aabbox3d<f32> nodebox
+								(-BS*d, -BS*d, -BS*d, BS*d, BS*d, BS*d);
+						v3f nodepos_f = intToFloat(nodepos, BS);
+						nodebox.MinEdge += nodepos_f;
+						nodebox.MaxEdge += nodepos_f;
+						nodehilightbox = nodebox;
+					}
+				} // if distance < mindistance
+			} // for dirs
+		} // regular block
+	} // for coords
+}
+
 int main(int argc, char *argv[])
 {
 	/*
@@ -2148,30 +2401,14 @@ int main(int argc, char *argv[])
 
 	//video::SColor skycolor = video::SColor(255,90,140,200);
 	//video::SColor skycolor = video::SColor(255,166,202,244);
-	video::SColor skycolor = video::SColor(255,120,185,244);
+	//video::SColor skycolor = video::SColor(255,120,185,244);
+	video::SColor skycolor = video::SColor(255,140,186,250);
 
 	camera->setFOV(FOV_ANGLE);
 
 	// Just so big a value that everything rendered is visible
 	camera->setFarValue(100000*BS);
 	
-	/*
-		Lighting test code. Doesn't quite work this way.
-		The CPU-computed lighting is good.
-	*/
-
-	/*
-	smgr->addLightSceneNode(NULL,
-		v3f(0, BS*1000000, 0),
-		video::SColorf(0.3,0.3,0.3),
-		BS*10000000);
-
-	smgr->setAmbientLight(video::SColorf(0.0, 0.0, 0.0));
-
-	scene::ILightSceneNode *light = smgr->addLightSceneNode(camera,
-			v3f(0, 0, 0), video::SColorf(0.5,0.5,0.5), BS*4);
-	*/
-
 	f32 camera_yaw = 0; // "right/left"
 	f32 camera_pitch = 0; // "up/down"
 
@@ -2226,6 +2463,8 @@ int main(int argc, char *argv[])
 
 	core::list<float> frametime_log;
 
+	float damage_flash_timer = 0;
+
 	/*
 		Main loop
 	*/
@@ -2453,6 +2692,16 @@ int main(int argc, char *argv[])
 			);
 			client.setPlayerControl(control);
 		}
+		
+		/*
+			Run server
+		*/
+
+		if(server != NULL)
+		{
+			//TimeTaker timer("server->step(dtime)");
+			server->step(dtime);
+		}
 
 		/*
 			Process environment
@@ -2464,12 +2713,28 @@ int main(int argc, char *argv[])
 			//client.step(dtime_avg1);
 		}
 
-		if(server != NULL)
+		// Read client events
+		for(;;)
 		{
-			//TimeTaker timer("server->step(dtime)");
-			server->step(dtime);
+			ClientEvent event = client.getClientEvent();
+			if(event.type == CE_NONE)
+			{
+				break;
+			}
+			else if(event.type == CE_PLAYER_DAMAGE)
+			{
+				//u16 damage = event.player_damage.amount;
+				//dstream<<"Player damage: "<<damage<<std::endl;
+				damage_flash_timer = 0.05;
+			}
+			else if(event.type == CE_PLAYER_FORCE_MOVE)
+			{
+				camera_yaw = event.player_force_move.yaw;
+				camera_pitch = event.player_force_move.pitch;
+			}
 		}
-
+		
+		// Get player position
 		v3f player_position = client.getPlayerPosition();
 		
 		//TimeTaker //timer2("//timer2");
@@ -2637,22 +2902,6 @@ int main(int argc, char *argv[])
 			else if(g_input->getRightClicked())
 			{
 				std::cout<<DTIME<<"Right-clicked object"<<std::endl;
-#if 0
-				/*
-					Check if we want to modify the object ourselves
-				*/
-				if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
-				{
-				}
-				/*
-					Otherwise pass the event to the server as-is
-				*/
-				else
-				{
-					client.clickObject(1, selected_object->getBlock()->getPos(),
-							selected_object->getId(), g_selected_item);
-				}
-#endif
 			}
 		}
 		else // selected_object == NULL
@@ -2666,205 +2915,13 @@ int main(int argc, char *argv[])
 		v3s16 nodepos;
 		v3s16 neighbourpos;
 		core::aabbox3d<f32> nodehilightbox;
-		f32 mindistance = BS * 1001;
-		
-		v3s16 pos_i = floatToInt(player_position, BS);
-
-		/*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
-				<<std::endl;*/
-
-		s16 a = d;
-		s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
-		s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
-		s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
-		s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
-		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 x = xstart; x <= xend; x++)
-		{
-			MapNode n;
-			try
-			{
-				n = client.getNode(v3s16(x,y,z));
-				if(content_pointable(n.d) == false)
-					continue;
-			}
-			catch(InvalidPositionException &e)
-			{
-				continue;
-			}
-
-			v3s16 np(x,y,z);
-			v3f npf = intToFloat(np, BS);
-			
-			f32 d = 0.01;
-			
-			v3s16 dirs[6] = {
-				v3s16(0,0,1), // back
-				v3s16(0,1,0), // top
-				v3s16(1,0,0), // right
-				v3s16(0,0,-1), // front
-				v3s16(0,-1,0), // bottom
-				v3s16(-1,0,0), // left
-			};
-			
-			/*
-				Meta-objects
-			*/
-			if(n.d == CONTENT_TORCH)
-			{
-				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();
-
-				core::aabbox3d<f32> box;
-				
-				// 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(distance < mindistance)
-				{
-					if(box.intersectsWithLine(shootline))
-					{
-						nodefound = true;
-						nodepos = np;
-						neighbourpos = np;
-						mindistance = distance;
-						nodehilightbox = box;
-					}
-				}
-			}
-			else if(n.d == CONTENT_SIGN_WALL)
-			{
-				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();
-
-				v3f vertices[4] =
-				{
-					v3f(BS*0.42,-BS*0.35,-BS*0.4),
-					v3f(BS*0.49, BS*0.35, BS*0.4),
-				};
-
-				for(s32 i=0; i<2; i++)
-				{
-					if(dir == v3s16(1,0,0))
-						vertices[i].rotateXZBy(0);
-					if(dir == v3s16(-1,0,0))
-						vertices[i].rotateXZBy(180);
-					if(dir == v3s16(0,0,1))
-						vertices[i].rotateXZBy(90);
-					if(dir == v3s16(0,0,-1))
-						vertices[i].rotateXZBy(-90);
-					if(dir == v3s16(0,-1,0))
-						vertices[i].rotateXYBy(-90);
-					if(dir == v3s16(0,1,0))
-						vertices[i].rotateXYBy(90);
-
-					vertices[i] += npf;
-				}
-
-				core::aabbox3d<f32> box;
-
-				box = core::aabbox3d<f32>(vertices[0]);
-				box.addInternalPoint(vertices[1]);
-
-				if(distance < mindistance)
-				{
-					if(box.intersectsWithLine(shootline))
-					{
-						nodefound = true;
-						nodepos = np;
-						neighbourpos = np;
-						mindistance = distance;
-						nodehilightbox = 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;
-
-							//nodehilightbox = facebox;
-
-							const float d = 0.502;
-							core::aabbox3d<f32> nodebox
-									(-BS*d, -BS*d, -BS*d, BS*d, BS*d, BS*d);
-							v3f nodepos_f = intToFloat(nodepos, BS);
-							nodebox.MinEdge += nodepos_f;
-							nodebox.MaxEdge += nodepos_f;
-							nodehilightbox = nodebox;
-						}
-					} // if distance < mindistance
-				} // for dirs
-			} // regular block
-		} // for coords
 
+		getPointedNode(player_position,
+				camera_direction, camera_position,
+				nodefound, shootline,
+				nodepos, neighbourpos,
+				nodehilightbox, d);
+	
 		static float nodig_delay_counter = 0.0;
 
 		if(nodefound)
@@ -3430,10 +3487,26 @@ int main(int argc, char *argv[])
 		*/
 		{
 			draw_hotbar(driver, font, v2s32(displaycenter.X, screensize.Y),
-					hotbar_imagesize, hotbar_itemcount, &local_inventory);
+					hotbar_imagesize, hotbar_itemcount, &local_inventory,
+					client.getHP());
+		}
+
+		/*
+			Damage flash
+		*/
+		if(damage_flash_timer > 0.0)
+		{
+			damage_flash_timer -= dtime;
+			
+			video::SColor color(128,255,0,0);
+			driver->draw2DRectangle(color,
+					core::rect<s32>(0,0,screensize.X,screensize.Y),
+					NULL);
 		}
 		
-		// End drawing
+		/*
+			End scene
+		*/
 		{
 			TimeTaker timer("endScene");
 			driver->endScene();
diff --git a/src/map.cpp b/src/map.cpp
index 2a92f6733072d663ea336b64420c07962ef6161a..7e4fc4f47313abbe2e450c5aa37aa085eadd1e2a 100644
--- a/src/map.cpp
+++ b/src/map.cpp
@@ -37,8 +37,8 @@ Map::Map(std::ostream &dout):
 	m_dout(dout),
 	m_sector_cache(NULL)
 {
-	m_sector_mutex.Init();
-	assert(m_sector_mutex.IsInitialized());
+	/*m_sector_mutex.Init();
+	assert(m_sector_mutex.IsInitialized());*/
 }
 
 Map::~Map()
@@ -104,7 +104,7 @@ MapSector * Map::getSectorNoGenerateNoExNoLock(v2s16 p)
 
 MapSector * Map::getSectorNoGenerateNoEx(v2s16 p)
 {
-	JMutexAutoLock lock(m_sector_mutex);
+	//JMutexAutoLock lock(m_sector_mutex); // Bulk comment-out
 
 	return getSectorNoGenerateNoExNoLock(p);
 }
@@ -1347,7 +1347,7 @@ bool Map::dayNightDiffed(v3s16 blockpos)
 */
 void Map::timerUpdate(float dtime)
 {
-	JMutexAutoLock lock(m_sector_mutex);
+	//JMutexAutoLock lock(m_sector_mutex); // Bulk comment-out
 
 	core::map<v2s16, MapSector*>::Iterator si;
 
@@ -1397,7 +1397,7 @@ void Map::deleteSectors(core::list<v2s16> &list, bool only_blocks)
 u32 Map::deleteUnusedSectors(float timeout, bool only_blocks,
 		core::list<v3s16> *deleted_blocks)
 {
-	JMutexAutoLock lock(m_sector_mutex);
+	//JMutexAutoLock lock(m_sector_mutex); // Bulk comment-out
 
 	core::list<v2s16> sector_deletion_queue;
 	core::map<v2s16, MapSector*>::Iterator i = m_sectors.getIterator();
@@ -2163,6 +2163,18 @@ void addRandomObjects(MapBlock *block)
 							block->m_static_objects.insert(0, s_obj);
 							delete obj;
 						}
+						if(myrand() % 300 == 0)
+						{
+							v3f pos_f = intToFloat(p+block->getPosRelative(), BS);
+							pos_f.Y -= BS*0.4;
+							ServerActiveObject *obj = new Oerkki1SAO(NULL,0,pos_f);
+							std::string data = obj->getStaticData();
+							StaticObject s_obj(obj->getType(),
+									obj->getBasePosition(), data);
+							// Add one
+							block->m_static_objects.insert(0, s_obj);
+							delete obj;
+						}
 					}
 				}
 			}
@@ -4714,7 +4726,7 @@ s16 ServerMap::findGroundLevel(v2s16 p2d)
 	// This won't work if proper generation is disabled
 	if(m_chunksize == 0)
 		return WATER_LEVEL+2;
-	double level = base_rock_level_2d(m_seed, p2d);
+	double level = base_rock_level_2d(m_seed, p2d) + AVERAGE_MUD_AMOUNT;
 	return (s16)level;
 }
 
@@ -4794,7 +4806,7 @@ void ServerMap::save(bool only_changed)
 	u32 block_count = 0;
 	
 	{ //sectorlock
-	JMutexAutoLock lock(m_sector_mutex);
+	//JMutexAutoLock lock(m_sector_mutex); // Bulk comment-out
 	
 	core::map<v2s16, MapSector*>::Iterator i = m_sectors.getIterator();
 	for(; i.atEnd() == false; i++)
@@ -4856,7 +4868,7 @@ void ServerMap::loadAll()
 
 	dstream<<DTIME<<"There are "<<list.size()<<" sectors."<<std::endl;
 	
-	JMutexAutoLock lock(m_sector_mutex);
+	//JMutexAutoLock lock(m_sector_mutex); // Bulk comment-out
 	
 	s32 counter = 0;
 	s32 printed_counter = -100000;
@@ -5163,7 +5175,7 @@ bool ServerMap::loadSectorFull(v2s16 p2d)
 
 	MapSector *sector = NULL;
 
-	JMutexAutoLock lock(m_sector_mutex);
+	//JMutexAutoLock lock(m_sector_mutex); // Bulk comment-out
 
 	try{
 		sector = loadSectorMeta(sectorsubdir);
@@ -5410,7 +5422,7 @@ MapSector * ClientMap::emergeSector(v2s16 p2d)
 	ClientMapSector *sector = new ClientMapSector(this, p2d);
 	
 	{
-		JMutexAutoLock lock(m_sector_mutex);
+		//JMutexAutoLock lock(m_sector_mutex); // Bulk comment-out
 		m_sectors.insert(p2d, sector);
 	}
 	
@@ -5422,7 +5434,7 @@ void ClientMap::deSerializeSector(v2s16 p2d, std::istream &is)
 	DSTACK(__FUNCTION_NAME);
 	ClientMapSector *sector = NULL;
 
-	JMutexAutoLock lock(m_sector_mutex);
+	//JMutexAutoLock lock(m_sector_mutex); // Bulk comment-out
 	
 	core::map<v2s16, MapSector*>::Node *n = m_sectors.find(p2d);
 
@@ -5435,7 +5447,7 @@ void ClientMap::deSerializeSector(v2s16 p2d, std::istream &is)
 	{
 		sector = new ClientMapSector(this, p2d);
 		{
-			JMutexAutoLock lock(m_sector_mutex);
+			//JMutexAutoLock lock(m_sector_mutex); // Bulk comment-out
 			m_sectors.insert(p2d, sector);
 		}
 	}
diff --git a/src/map.h b/src/map.h
index 206dc7d7ba31ee071903b7e1e1df7c711de56236..1cd021f52b9eeaeea3fb94da1058976e6c3d5531 100644
--- a/src/map.h
+++ b/src/map.h
@@ -287,6 +287,11 @@ class Map : public NodeContainer
 	void removeNodeMetadata(v3s16 p);
 	void nodeMetadataStep(float dtime,
 			core::map<v3s16, MapBlock*> &changed_blocks);
+	
+	/*
+		Misc.
+	*/
+	core::map<v2s16, MapSector*> *getSectorsPtr(){return &m_sectors;}
 
 	/*
 		Variables
@@ -298,16 +303,13 @@ class Map : public NodeContainer
 
 	core::map<MapEventReceiver*, bool> m_event_receivers;
 	
-	// Mutex is important because on client map is accessed asynchronously
 	core::map<v2s16, MapSector*> m_sectors;
-	JMutex m_sector_mutex;
+	//JMutex m_sector_mutex;
 
 	// Be sure to set this to NULL when the cached sector is deleted 
 	MapSector *m_sector_cache;
 	v2s16 m_sector_cache_p;
 
-	//WrapperHeightmap m_hwrapper;
-	
 	// Queued transforming water nodes
 	UniqueQueue<v3s16> m_transforming_liquid;
 };
diff --git a/src/mapblock.cpp b/src/mapblock.cpp
index 38c081eec9d58ea3dc9d77ae750931eff58a0d77..5b8bc7b9f80762b0e818574fe158789e641c7e3b 100644
--- a/src/mapblock.cpp
+++ b/src/mapblock.cpp
@@ -1924,9 +1924,19 @@ void MapBlock::serialize(std::ostream &os, u8 version)
 		*/
 		if(version >= 14)
 		{
-			std::ostringstream oss(std::ios_base::binary);
-			m_node_metadata.serialize(oss);
-			os<<serializeString(oss.str());
+			if(version <= 15)
+			{
+				std::ostringstream oss(std::ios_base::binary);
+				m_node_metadata.serialize(oss);
+				os<<serializeString(oss.str());
+			}
+			else
+			{
+				std::ostringstream oss(std::ios_base::binary);
+				m_node_metadata.serialize(oss);
+				compressZlib(oss.str(), os);
+				//os<<serializeLongString(oss.str());
+			}
 		}
 	}
 }
@@ -2055,9 +2065,20 @@ void MapBlock::deSerialize(std::istream &is, u8 version)
 		{
 			// Ignore errors
 			try{
-				std::string data = deSerializeString(is);
-				std::istringstream iss(data, std::ios_base::binary);
-				m_node_metadata.deSerialize(iss);
+				if(version <= 15)
+				{
+					std::string data = deSerializeString(is);
+					std::istringstream iss(data, std::ios_base::binary);
+					m_node_metadata.deSerialize(iss);
+				}
+				else
+				{
+					//std::string data = deSerializeLongString(is);
+					std::ostringstream oss(std::ios_base::binary);
+					decompressZlib(is, oss);
+					std::istringstream iss(oss.str(), std::ios_base::binary);
+					m_node_metadata.deSerialize(iss);
+				}
 			}
 			catch(SerializationError &e)
 			{
diff --git a/src/player.cpp b/src/player.cpp
index 8f594eee638ee89d75673b7a2e07494167993c48..31415b6b279481f4b88d48faf1d0439f3d870150 100644
--- a/src/player.cpp
+++ b/src/player.cpp
@@ -33,6 +33,7 @@ Player::Player():
 	in_water_stable(false),
 	swimming_up(false),
 	craftresult_is_preview(true),
+	hp(20),
 	peer_id(PEER_ID_INEXISTENT),
 	m_pitch(0),
 	m_yaw(0),
@@ -102,6 +103,7 @@ void Player::serialize(std::ostream &os)
 	args.setFloat("yaw", m_yaw);
 	args.setV3F("position", m_position);
 	args.setBool("craftresult_is_preview", craftresult_is_preview);
+	args.setS32("hp", hp);
 
 	args.writeLines(os);
 
@@ -138,6 +140,11 @@ void Player::deSerialize(std::istream &is)
 	}catch(SettingNotFoundException &e){
 		craftresult_is_preview = true;
 	}
+	try{
+		hp = args.getS32("hp");
+	}catch(SettingNotFoundException &e){
+		hp = 20;
+	}
 
 	inventory.deSerialize(is);
 }
@@ -276,7 +283,8 @@ LocalPlayer::~LocalPlayer()
 {
 }
 
-void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d)
+void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d,
+		core::list<CollisionInfo> *collision_info)
 {
 	v3f position = getPosition();
 	v3f oldpos = position;
@@ -530,9 +538,23 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d)
 			*/
 			if(other_axes_overlap && main_axis_collides)
 			{
+				v3f old_speed = m_speed;
+
 				m_speed -= m_speed.dotProduct(dirs[i]) * dirs[i];
 				position -= position.dotProduct(dirs[i]) * dirs[i];
 				position += oldpos.dotProduct(dirs[i]) * dirs[i];
+				
+				if(collision_info)
+				{
+					// Report fall collision
+					if(old_speed.Y < m_speed.Y - 0.1)
+					{
+						CollisionInfo info;
+						info.t = COLLISION_FALL;
+						info.speed = m_speed.Y - old_speed.Y;
+						collision_info->push_back(info);
+					}
+				}
 			}
 		
 		}
@@ -617,6 +639,11 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d)
 	setPosition(position);
 }
 
+void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d)
+{
+	move(dtime, map, pos_max_d, NULL);
+}
+
 void LocalPlayer::applyControl(float dtime)
 {
 	// Clear stuff
diff --git a/src/player.h b/src/player.h
index 2eaeaae9a6cfdf6e9ce7f03371d2467676e931c7..03fba1e2c0b252870aa9300efb5fd865d724910d 100644
--- a/src/player.h
+++ b/src/player.h
@@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "common_irrlicht.h"
 #include "inventory.h"
+#include "collision.h"
 
 #define PLAYERNAME_SIZE 20
 
@@ -124,6 +125,8 @@ class Player
 
 	bool craftresult_is_preview;
 
+	u16 hp;
+
 	u16 peer_id;
 
 protected:
@@ -325,6 +328,8 @@ class LocalPlayer : public Player
 		return true;
 	}
 
+	void move(f32 dtime, Map &map, f32 pos_max_d,
+			core::list<CollisionInfo> *collision_info);
 	void move(f32 dtime, Map &map, f32 pos_max_d);
 
 	void applyControl(float dtime);
diff --git a/src/serialization.cpp b/src/serialization.cpp
index c324ca0fdb13ef38deb917ce93a595d8914305af..6a43d9190f602867508a642d7356a76f7003db2d 100644
--- a/src/serialization.cpp
+++ b/src/serialization.cpp
@@ -105,6 +105,12 @@ void compressZlib(SharedBuffer<u8> data, std::ostream &os)
 
 }
 
+void compressZlib(const std::string &data, std::ostream &os)
+{
+	SharedBuffer<u8> databuf((u8*)data.c_str(), data.size());
+	compressZlib(databuf, os);
+}
+
 void decompressZlib(std::istream &is, std::ostream &os)
 {
 	z_stream z;
diff --git a/src/serialization.h b/src/serialization.h
index c87162e69ea6e5def223c3f9f055c859f5c3b6a3..c7cafc5d19306e60a87ed86acb8417a9e420ba9c 100644
--- a/src/serialization.h
+++ b/src/serialization.h
@@ -48,17 +48,23 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 	13: (dev) Mapgen v2
 	14: (dev) NodeMetadata
 	15: (dev) StaticObjects
+	16: (dev) larger maximum size of node metadata, and compression
 */
 // This represents an uninitialized or invalid format
 #define SER_FMT_VER_INVALID 255
 // Highest supported serialization version
-#define SER_FMT_VER_HIGHEST 15
+#define SER_FMT_VER_HIGHEST 16
 // Lowest supported serialization version
 #define SER_FMT_VER_LOWEST 0
 
 #define ser_ver_supported(v) (v >= SER_FMT_VER_LOWEST && v <= SER_FMT_VER_HIGHEST)
 
+void compressZlib(SharedBuffer<u8> data, std::ostream &os);
+void compressZlib(const std::string &data, std::ostream &os);
+void decompressZlib(std::istream &is, std::ostream &os);
+
 void compress(SharedBuffer<u8> data, std::ostream &os, u8 version);
+//void compress(const std::string &data, std::ostream &os, u8 version);
 void decompress(std::istream &is, std::ostream &os, u8 version);
 
 /*class Serializable
diff --git a/src/server.cpp b/src/server.cpp
index 154603a47099b27401aef105c5e0030ae6e26b4c..ee7a035e6bdae42b0bfa1b7028fafddde5c82025 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -959,6 +959,8 @@ Server::Server(
 
 Server::~Server()
 {
+	dstream<<"Server::~Server()"<<std::endl;
+
 	/*
 		Send shutdown message
 	*/
@@ -980,13 +982,18 @@ Server::~Server()
 			if(client->serialization_version == SER_FMT_VER_INVALID)
 				continue;
 
-			SendChatMessage(client->peer_id, line);
+			try{
+				SendChatMessage(client->peer_id, line);
+			}
+			catch(con::PeerNotFoundException &e)
+			{}
 		}
 	}
 
 	/*
 		Save players
 	*/
+	dstream<<"Server: Saving players"<<std::endl;
 	m_env.serializePlayers(m_mapsavedir);
 	
 	/*
@@ -1046,11 +1053,6 @@ void Server::stop()
 	m_emergethread.stop();
 	
 	dout_server<<"Server: Threads stopped"<<std::endl;
-
-	dout_server<<"Server: Saving players"<<std::endl;
-	// Save players
-	// FIXME: Apparently this does not do anything here
-	//m_env.serializePlayers(m_mapsavedir);
 }
 
 void Server::step(float dtime)
@@ -1550,6 +1552,8 @@ void Server::AsyncRunStep()
 		Step node metadata
 	*/
 	{
+		//TimeTaker timer("Step node metadata");
+
 		JMutexAutoLock envlock(m_env_mutex);
 		JMutexAutoLock conlock(m_con_mutex);
 		
@@ -1781,20 +1785,30 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 			data[20+3-1] = 0;
 			player->updateName((const char*)&data[3]);
 		}*/
-
-		// Now answer with a TOCLIENT_INIT
 		
-		SharedBuffer<u8> reply(2+1+6+8);
-		writeU16(&reply[0], TOCLIENT_INIT);
-		writeU8(&reply[2], deployed);
-		writeV3S16(&reply[2+1], floatToInt(player->getPosition()+v3f(0,BS/2,0), BS));
-		//writeU64(&reply[2+1+6], m_env.getServerMap().getSeed());
-		
-		// Send as reliable
-		m_con.Send(peer_id, 0, reply, true);
+		/*
+			Answer with a TOCLIENT_INIT
+		*/
+		{
+			SharedBuffer<u8> reply(2+1+6+8);
+			writeU16(&reply[0], TOCLIENT_INIT);
+			writeU8(&reply[2], deployed);
+			writeV3S16(&reply[2+1], floatToInt(player->getPosition()+v3f(0,BS/2,0), BS));
+			//writeU64(&reply[2+1+6], m_env.getServerMap().getSeed());
+			writeU64(&reply[2+1+6], 0); // no seed
+			
+			// Send as reliable
+			m_con.Send(peer_id, 0, reply, true);
+		}
+
+		/*
+			Send complete position information
+		*/
+		SendMovePlayer(player);
 
 		return;
 	}
+
 	if(command == TOSERVER_INIT2)
 	{
 		derr_server<<DTIME<<"Server: Got TOSERVER_INIT2 from "
@@ -1812,7 +1826,14 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 		SendPlayerInfos();
 
 		// Send inventory to player
+		UpdateCrafting(peer->id);
 		SendInventory(peer->id);
+
+		// Send HP
+		{
+			Player *player = m_env.getPlayer(peer_id);
+			SendPlayerHP(player);
+		}
 		
 		// Send time of day
 		{
@@ -2005,6 +2026,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 				
 				// Add to inventory and send inventory
 				ilist->addItem(item);
+				UpdateCrafting(player->peer_id);
 				SendInventory(player->peer_id);
 			}
 
@@ -2026,7 +2048,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 		*/
 		u8 button = readU8(&data[2]);
 		u16 id = readS16(&data[3]);
-		//u16 item_i = readU16(&data[11]);
+		u16 item_i = readU16(&data[11]);
 	
 		ServerActiveObject *obj = m_env.getActiveObject(id);
 
@@ -2066,11 +2088,42 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 				{
 					// Add to inventory and send inventory
 					ilist->addItem(item);
+					UpdateCrafting(player->peer_id);
 					SendInventory(player->peer_id);
 
 					// Remove object from environment
 					obj->m_removed = true;
 				}
+				else
+				{
+					/*
+						Item cannot be picked up. Punch it instead.
+					*/
+
+					ToolItem *titem = NULL;
+					std::string toolname = "";
+
+					InventoryList *mlist = player->inventory.getList("main");
+					if(mlist != NULL)
+					{
+						InventoryItem *item = mlist->getItem(item_i);
+						if(item && (std::string)item->getName() == "ToolItem")
+						{
+							titem = (ToolItem*)item;
+							toolname = titem->getToolName();
+						}
+					}
+					
+					u16 wear = obj->punch(toolname);
+					
+					if(titem)
+					{
+						bool weared_out = titem->addWear(wear);
+						if(weared_out)
+							mlist->deleteItem(item_i);
+						SendInventory(player->peer_id);
+					}
+				}
 			}
 		}
 	}
@@ -2276,6 +2329,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 					player->inventory.addItem("main", item);
 
 					// Send inventory
+					UpdateCrafting(player->peer_id);
 					SendInventory(player->peer_id);
 				}
 			}
@@ -2380,6 +2434,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 					else
 						mitem->remove(1);
 					// Send inventory
+					UpdateCrafting(peer_id);
 					SendInventory(peer_id);
 				}
 				
@@ -2492,6 +2547,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 							item->remove(dropcount);
 						
 						// Send inventory
+						UpdateCrafting(peer_id);
 						SendInventory(peer_id);
 					}
 				}
@@ -2711,6 +2767,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 			else
 			{
 				// Send inventory
+				UpdateCrafting(player->peer_id);
 				SendInventory(player->peer_id);
 			}
 		}
@@ -2856,6 +2913,36 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 			}
 		}
 	}
+	else if(command == TOSERVER_DAMAGE)
+	{
+		if(g_settings.getBool("enable_damage"))
+		{
+			std::string datastring((char*)&data[2], datasize-2);
+			std::istringstream is(datastring, std::ios_base::binary);
+			u8 damage = readU8(is);
+			if(player->hp > damage)
+			{
+				player->hp -= damage;
+			}
+			else
+			{
+				player->hp = 0;
+
+				dstream<<"TODO: Server: TOSERVER_HP_DECREMENT: Player dies"
+						<<std::endl;
+				
+				v3f pos = findSpawnPos(m_env.getServerMap());
+				player->setPosition(pos);
+				player->hp = 20;
+				SendMovePlayer(player);
+				SendPlayerHP(player);
+				
+				//TODO: Throw items around
+			}
+		}
+
+		SendPlayerHP(player);
+	}
 	else
 	{
 		derr_server<<"WARNING: Server::ProcessData(): Ignoring "
@@ -2914,6 +3001,7 @@ void Server::inventoryModified(InventoryContext *c, std::string id)
 	{
 		assert(c->current_player);
 		// Send inventory
+		UpdateCrafting(c->current_player->peer_id);
 		SendInventory(c->current_player->peer_id);
 		return;
 	}
@@ -3016,6 +3104,29 @@ void Server::deletingPeer(con::Peer *peer, bool timeout)
 	m_peer_change_queue.push_back(c);
 }
 
+/*
+	Static send methods
+*/
+
+void Server::SendHP(con::Connection &con, u16 peer_id, u8 hp)
+{
+	DSTACK(__FUNCTION_NAME);
+	std::ostringstream os(std::ios_base::binary);
+
+	writeU16(os, TOCLIENT_HP);
+	writeU8(os, hp);
+
+	// Make data buffer
+	std::string s = os.str();
+	SharedBuffer<u8> data((u8*)s.c_str(), s.size());
+	// Send as reliable
+	con.Send(peer_id, 0, data, true);
+}
+
+/*
+	Non-static send methods
+*/
+
 void Server::SendObjectData(float dtime)
 {
 	DSTACK(__FUNCTION_NAME);
@@ -3082,51 +3193,351 @@ void Server::SendInventory(u16 peer_id)
 	assert(player);
 
 	/*
-		Calculate crafting stuff
+		Serialize it
 	*/
-	if(g_settings.getBool("creative_mode") == false)
+
+	std::ostringstream os;
+	//os.imbue(std::locale("C"));
+
+	player->inventory.serialize(os);
+
+	std::string s = os.str();
+	
+	SharedBuffer<u8> data(s.size()+2);
+	writeU16(&data[0], TOCLIENT_INVENTORY);
+	memcpy(&data[2], s.c_str(), s.size());
+	
+	// Send as reliable
+	m_con.Send(peer_id, 0, data, true);
+}
+
+void Server::SendChatMessage(u16 peer_id, const std::wstring &message)
+{
+	DSTACK(__FUNCTION_NAME);
+	
+	std::ostringstream os(std::ios_base::binary);
+	u8 buf[12];
+	
+	// Write command
+	writeU16(buf, TOCLIENT_CHAT_MESSAGE);
+	os.write((char*)buf, 2);
+	
+	// Write length
+	writeU16(buf, message.size());
+	os.write((char*)buf, 2);
+	
+	// Write string
+	for(u32 i=0; i<message.size(); i++)
 	{
-		InventoryList *clist = player->inventory.getList("craft");
-		InventoryList *rlist = player->inventory.getList("craftresult");
+		u16 w = message[i];
+		writeU16(buf, w);
+		os.write((char*)buf, 2);
+	}
+	
+	// Make data buffer
+	std::string s = os.str();
+	SharedBuffer<u8> data((u8*)s.c_str(), s.size());
+	// Send as reliable
+	m_con.Send(peer_id, 0, data, true);
+}
 
-		if(rlist->getUsedSlots() == 0)
-			player->craftresult_is_preview = true;
+void Server::BroadcastChatMessage(const std::wstring &message)
+{
+	for(core::map<u16, RemoteClient*>::Iterator
+		i = m_clients.getIterator();
+		i.atEnd() == false; i++)
+	{
+		// Get client and check that it is valid
+		RemoteClient *client = i.getNode()->getValue();
+		assert(client->peer_id == i.getNode()->getKey());
+		if(client->serialization_version == SER_FMT_VER_INVALID)
+			continue;
 
-		if(rlist && player->craftresult_is_preview)
-		{
-			rlist->clearItems();
-		}
-		if(clist && rlist && player->craftresult_is_preview)
-		{
-			InventoryItem *items[9];
-			for(u16 i=0; i<9; i++)
-			{
-				items[i] = clist->getItem(i);
-			}
-			
-			bool found = false;
+		SendChatMessage(client->peer_id, message);
+	}
+}
 
-			// Wood
-			if(!found)
+void Server::SendPlayerHP(Player *player)
+{
+	SendHP(m_con, player->peer_id, player->hp);
+}
+
+void Server::SendMovePlayer(Player *player)
+{
+	DSTACK(__FUNCTION_NAME);
+	std::ostringstream os(std::ios_base::binary);
+
+	writeU16(os, TOCLIENT_MOVE_PLAYER);
+	writeV3F1000(os, player->getPosition());
+	writeF1000(os, player->getPitch());
+	writeF1000(os, player->getYaw());
+	
+	{
+		v3f pos = player->getPosition();
+		f32 pitch = player->getPitch();
+		f32 yaw = player->getYaw();
+		dstream<<"Server sending TOCLIENT_MOVE_PLAYER"
+				<<" pos=("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")"
+				<<" pitch="<<pitch
+				<<" yaw="<<yaw
+				<<std::endl;
+	}
+
+	// Make data buffer
+	std::string s = os.str();
+	SharedBuffer<u8> data((u8*)s.c_str(), s.size());
+	// Send as reliable
+	m_con.Send(player->peer_id, 0, data, true);
+}
+
+void Server::sendRemoveNode(v3s16 p, u16 ignore_id,
+	core::list<u16> *far_players, float far_d_nodes)
+{
+	float maxd = far_d_nodes*BS;
+	v3f p_f = intToFloat(p, BS);
+
+	// Create packet
+	u32 replysize = 8;
+	SharedBuffer<u8> reply(replysize);
+	writeU16(&reply[0], TOCLIENT_REMOVENODE);
+	writeS16(&reply[2], p.X);
+	writeS16(&reply[4], p.Y);
+	writeS16(&reply[6], p.Z);
+
+	for(core::map<u16, RemoteClient*>::Iterator
+		i = m_clients.getIterator();
+		i.atEnd() == false; i++)
+	{
+		// Get client and check that it is valid
+		RemoteClient *client = i.getNode()->getValue();
+		assert(client->peer_id == i.getNode()->getKey());
+		if(client->serialization_version == SER_FMT_VER_INVALID)
+			continue;
+
+		// Don't send if it's the same one
+		if(client->peer_id == ignore_id)
+			continue;
+		
+		if(far_players)
+		{
+			// Get player
+			Player *player = m_env.getPlayer(client->peer_id);
+			if(player)
 			{
-				ItemSpec specs[9];
-				specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_TREE);
-				if(checkItemCombination(items, specs))
+				// If player is far away, only set modified blocks not sent
+				v3f player_pos = player->getPosition();
+				if(player_pos.getDistanceFrom(p_f) > maxd)
 				{
-					rlist->addItem(new MaterialItem(CONTENT_WOOD, 4));
-					found = true;
+					far_players->push_back(client->peer_id);
+					continue;
 				}
 			}
+		}
 
-			// Stick
-			if(!found)
+		// Send as reliable
+		m_con.Send(client->peer_id, 0, reply, true);
+	}
+}
+
+void Server::sendAddNode(v3s16 p, MapNode n, u16 ignore_id,
+		core::list<u16> *far_players, float far_d_nodes)
+{
+	float maxd = far_d_nodes*BS;
+	v3f p_f = intToFloat(p, BS);
+
+	for(core::map<u16, RemoteClient*>::Iterator
+		i = m_clients.getIterator();
+		i.atEnd() == false; i++)
+	{
+		// Get client and check that it is valid
+		RemoteClient *client = i.getNode()->getValue();
+		assert(client->peer_id == i.getNode()->getKey());
+		if(client->serialization_version == SER_FMT_VER_INVALID)
+			continue;
+
+		// Don't send if it's the same one
+		if(client->peer_id == ignore_id)
+			continue;
+
+		if(far_players)
+		{
+			// Get player
+			Player *player = m_env.getPlayer(client->peer_id);
+			if(player)
 			{
-				ItemSpec specs[9];
-				specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-				if(checkItemCombination(items, specs))
+				// If player is far away, only set modified blocks not sent
+				v3f player_pos = player->getPosition();
+				if(player_pos.getDistanceFrom(p_f) > maxd)
 				{
-					rlist->addItem(new CraftItem("Stick", 4));
-					found = true;
+					far_players->push_back(client->peer_id);
+					continue;
+				}
+			}
+		}
+
+		// Create packet
+		u32 replysize = 8 + MapNode::serializedLength(client->serialization_version);
+		SharedBuffer<u8> reply(replysize);
+		writeU16(&reply[0], TOCLIENT_ADDNODE);
+		writeS16(&reply[2], p.X);
+		writeS16(&reply[4], p.Y);
+		writeS16(&reply[6], p.Z);
+		n.serialize(&reply[8], client->serialization_version);
+
+		// Send as reliable
+		m_con.Send(client->peer_id, 0, reply, true);
+	}
+}
+
+void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver)
+{
+	DSTACK(__FUNCTION_NAME);
+	/*
+		Create a packet with the block in the right format
+	*/
+	
+	std::ostringstream os(std::ios_base::binary);
+	block->serialize(os, ver);
+	std::string s = os.str();
+	SharedBuffer<u8> blockdata((u8*)s.c_str(), s.size());
+
+	u32 replysize = 8 + blockdata.getSize();
+	SharedBuffer<u8> reply(replysize);
+	v3s16 p = block->getPos();
+	writeU16(&reply[0], TOCLIENT_BLOCKDATA);
+	writeS16(&reply[2], p.X);
+	writeS16(&reply[4], p.Y);
+	writeS16(&reply[6], p.Z);
+	memcpy(&reply[8], *blockdata, blockdata.getSize());
+
+	/*dstream<<"Server: Sending block ("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+			<<":  \tpacket size: "<<replysize<<std::endl;*/
+	
+	/*
+		Send packet
+	*/
+	m_con.Send(peer_id, 1, reply, true);
+}
+
+void Server::SendBlocks(float dtime)
+{
+	DSTACK(__FUNCTION_NAME);
+
+	JMutexAutoLock envlock(m_env_mutex);
+	JMutexAutoLock conlock(m_con_mutex);
+
+	//TimeTaker timer("Server::SendBlocks");
+
+	core::array<PrioritySortedBlockTransfer> queue;
+
+	s32 total_sending = 0;
+
+	for(core::map<u16, RemoteClient*>::Iterator
+		i = m_clients.getIterator();
+		i.atEnd() == false; i++)
+	{
+		RemoteClient *client = i.getNode()->getValue();
+		assert(client->peer_id == i.getNode()->getKey());
+
+		total_sending += client->SendingCount();
+		
+		if(client->serialization_version == SER_FMT_VER_INVALID)
+			continue;
+		
+		client->GetNextBlocks(this, dtime, queue);
+	}
+
+	// Sort.
+	// Lowest priority number comes first.
+	// Lowest is most important.
+	queue.sort();
+
+	for(u32 i=0; i<queue.size(); i++)
+	{
+		//TODO: Calculate limit dynamically
+		if(total_sending >= g_settings.getS32
+				("max_simultaneous_block_sends_server_total"))
+			break;
+		
+		PrioritySortedBlockTransfer q = queue[i];
+
+		MapBlock *block = NULL;
+		try
+		{
+			block = m_env.getMap().getBlockNoCreate(q.pos);
+		}
+		catch(InvalidPositionException &e)
+		{
+			continue;
+		}
+
+		RemoteClient *client = getClient(q.peer_id);
+
+		SendBlockNoLock(q.peer_id, block, client->serialization_version);
+
+		client->SentBlock(q.pos);
+
+		total_sending++;
+	}
+}
+
+/*
+	Something random
+*/
+
+void Server::UpdateCrafting(u16 peer_id)
+{
+	DSTACK(__FUNCTION_NAME);
+	
+	Player* player = m_env.getPlayer(peer_id);
+	assert(player);
+
+	/*
+		Calculate crafting stuff
+	*/
+	if(g_settings.getBool("creative_mode") == false)
+	{
+		InventoryList *clist = player->inventory.getList("craft");
+		InventoryList *rlist = player->inventory.getList("craftresult");
+
+		if(rlist->getUsedSlots() == 0)
+			player->craftresult_is_preview = true;
+
+		if(rlist && player->craftresult_is_preview)
+		{
+			rlist->clearItems();
+		}
+		if(clist && rlist && player->craftresult_is_preview)
+		{
+			InventoryItem *items[9];
+			for(u16 i=0; i<9; i++)
+			{
+				items[i] = clist->getItem(i);
+			}
+			
+			bool found = false;
+
+			// Wood
+			if(!found)
+			{
+				ItemSpec specs[9];
+				specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_TREE);
+				if(checkItemCombination(items, specs))
+				{
+					rlist->addItem(new MaterialItem(CONTENT_WOOD, 4));
+					found = true;
+				}
+			}
+
+			// Stick
+			if(!found)
+			{
+				ItemSpec specs[9];
+				specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
+				if(checkItemCombination(items, specs))
+				{
+					rlist->addItem(new CraftItem("Stick", 4));
+					found = true;
 				}
 			}
 
@@ -3226,7 +3637,7 @@ void Server::SendInventory(u16 peer_id)
 				}
 			}
 
-			// Wooden showel
+			// Wooden shovel
 			if(!found)
 			{
 				ItemSpec specs[9];
@@ -3240,7 +3651,7 @@ void Server::SendInventory(u16 peer_id)
 				}
 			}
 
-			// Stone showel
+			// Stone shovel
 			if(!found)
 			{
 				ItemSpec specs[9];
@@ -3254,7 +3665,7 @@ void Server::SendInventory(u16 peer_id)
 				}
 			}
 
-			// Steel showel
+			// Steel shovel
 			if(!found)
 			{
 				ItemSpec specs[9];
@@ -3316,6 +3727,48 @@ void Server::SendInventory(u16 peer_id)
 				}
 			}
 
+			// Wooden sword
+			if(!found)
+			{
+				ItemSpec specs[9];
+				specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
+				specs[4] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
+				specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
+				if(checkItemCombination(items, specs))
+				{
+					rlist->addItem(new ToolItem("WSword", 0));
+					found = true;
+				}
+			}
+
+			// Stone sword
+			if(!found)
+			{
+				ItemSpec specs[9];
+				specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
+				specs[4] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
+				specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
+				if(checkItemCombination(items, specs))
+				{
+					rlist->addItem(new ToolItem("STSword", 0));
+					found = true;
+				}
+			}
+
+			// Steel sword
+			if(!found)
+			{
+				ItemSpec specs[9];
+				specs[1] = ItemSpec(ITEM_CRAFT, "steel_ingot");
+				specs[4] = ItemSpec(ITEM_CRAFT, "steel_ingot");
+				specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
+				if(checkItemCombination(items, specs))
+				{
+					rlist->addItem(new ToolItem("SteelSword", 0));
+					found = true;
+				}
+			}
+
 			// Chest
 			if(!found)
 			{
@@ -3376,264 +3829,8 @@ void Server::SendInventory(u16 peer_id)
 		}
 	
 	} // if creative_mode == false
-
-	/*
-		Serialize it
-	*/
-
-	std::ostringstream os;
-	//os.imbue(std::locale("C"));
-
-	player->inventory.serialize(os);
-
-	std::string s = os.str();
-	
-	SharedBuffer<u8> data(s.size()+2);
-	writeU16(&data[0], TOCLIENT_INVENTORY);
-	memcpy(&data[2], s.c_str(), s.size());
-	
-	// Send as reliable
-	m_con.Send(peer_id, 0, data, true);
-}
-
-void Server::SendChatMessage(u16 peer_id, const std::wstring &message)
-{
-	DSTACK(__FUNCTION_NAME);
-	
-	std::ostringstream os(std::ios_base::binary);
-	u8 buf[12];
-	
-	// Write command
-	writeU16(buf, TOCLIENT_CHAT_MESSAGE);
-	os.write((char*)buf, 2);
-	
-	// Write length
-	writeU16(buf, message.size());
-	os.write((char*)buf, 2);
-	
-	// Write string
-	for(u32 i=0; i<message.size(); i++)
-	{
-		u16 w = message[i];
-		writeU16(buf, w);
-		os.write((char*)buf, 2);
-	}
-	
-	// Make data buffer
-	std::string s = os.str();
-	SharedBuffer<u8> data((u8*)s.c_str(), s.size());
-	// Send as reliable
-	m_con.Send(peer_id, 0, data, true);
 }
 
-void Server::BroadcastChatMessage(const std::wstring &message)
-{
-	for(core::map<u16, RemoteClient*>::Iterator
-		i = m_clients.getIterator();
-		i.atEnd() == false; i++)
-	{
-		// Get client and check that it is valid
-		RemoteClient *client = i.getNode()->getValue();
-		assert(client->peer_id == i.getNode()->getKey());
-		if(client->serialization_version == SER_FMT_VER_INVALID)
-			continue;
-
-		SendChatMessage(client->peer_id, message);
-	}
-}
-
-void Server::sendRemoveNode(v3s16 p, u16 ignore_id,
-	core::list<u16> *far_players, float far_d_nodes)
-{
-	float maxd = far_d_nodes*BS;
-	v3f p_f = intToFloat(p, BS);
-
-	// Create packet
-	u32 replysize = 8;
-	SharedBuffer<u8> reply(replysize);
-	writeU16(&reply[0], TOCLIENT_REMOVENODE);
-	writeS16(&reply[2], p.X);
-	writeS16(&reply[4], p.Y);
-	writeS16(&reply[6], p.Z);
-
-	for(core::map<u16, RemoteClient*>::Iterator
-		i = m_clients.getIterator();
-		i.atEnd() == false; i++)
-	{
-		// Get client and check that it is valid
-		RemoteClient *client = i.getNode()->getValue();
-		assert(client->peer_id == i.getNode()->getKey());
-		if(client->serialization_version == SER_FMT_VER_INVALID)
-			continue;
-
-		// Don't send if it's the same one
-		if(client->peer_id == ignore_id)
-			continue;
-		
-		if(far_players)
-		{
-			// Get player
-			Player *player = m_env.getPlayer(client->peer_id);
-			if(player)
-			{
-				// If player is far away, only set modified blocks not sent
-				v3f player_pos = player->getPosition();
-				if(player_pos.getDistanceFrom(p_f) > maxd)
-				{
-					far_players->push_back(client->peer_id);
-					continue;
-				}
-			}
-		}
-
-		// Send as reliable
-		m_con.Send(client->peer_id, 0, reply, true);
-	}
-}
-
-void Server::sendAddNode(v3s16 p, MapNode n, u16 ignore_id,
-		core::list<u16> *far_players, float far_d_nodes)
-{
-	float maxd = far_d_nodes*BS;
-	v3f p_f = intToFloat(p, BS);
-
-	for(core::map<u16, RemoteClient*>::Iterator
-		i = m_clients.getIterator();
-		i.atEnd() == false; i++)
-	{
-		// Get client and check that it is valid
-		RemoteClient *client = i.getNode()->getValue();
-		assert(client->peer_id == i.getNode()->getKey());
-		if(client->serialization_version == SER_FMT_VER_INVALID)
-			continue;
-
-		// Don't send if it's the same one
-		if(client->peer_id == ignore_id)
-			continue;
-
-		if(far_players)
-		{
-			// Get player
-			Player *player = m_env.getPlayer(client->peer_id);
-			if(player)
-			{
-				// If player is far away, only set modified blocks not sent
-				v3f player_pos = player->getPosition();
-				if(player_pos.getDistanceFrom(p_f) > maxd)
-				{
-					far_players->push_back(client->peer_id);
-					continue;
-				}
-			}
-		}
-
-		// Create packet
-		u32 replysize = 8 + MapNode::serializedLength(client->serialization_version);
-		SharedBuffer<u8> reply(replysize);
-		writeU16(&reply[0], TOCLIENT_ADDNODE);
-		writeS16(&reply[2], p.X);
-		writeS16(&reply[4], p.Y);
-		writeS16(&reply[6], p.Z);
-		n.serialize(&reply[8], client->serialization_version);
-
-		// Send as reliable
-		m_con.Send(client->peer_id, 0, reply, true);
-	}
-}
-
-void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver)
-{
-	DSTACK(__FUNCTION_NAME);
-	/*
-		Create a packet with the block in the right format
-	*/
-	
-	std::ostringstream os(std::ios_base::binary);
-	block->serialize(os, ver);
-	std::string s = os.str();
-	SharedBuffer<u8> blockdata((u8*)s.c_str(), s.size());
-
-	u32 replysize = 8 + blockdata.getSize();
-	SharedBuffer<u8> reply(replysize);
-	v3s16 p = block->getPos();
-	writeU16(&reply[0], TOCLIENT_BLOCKDATA);
-	writeS16(&reply[2], p.X);
-	writeS16(&reply[4], p.Y);
-	writeS16(&reply[6], p.Z);
-	memcpy(&reply[8], *blockdata, blockdata.getSize());
-
-	/*dstream<<"Sending block ("<<p.X<<","<<p.Y<<","<<p.Z<<")"
-			<<":  \tpacket size: "<<replysize<<std::endl;*/
-	
-	/*
-		Send packet
-	*/
-	m_con.Send(peer_id, 1, reply, true);
-}
-
-void Server::SendBlocks(float dtime)
-{
-	DSTACK(__FUNCTION_NAME);
-
-	JMutexAutoLock envlock(m_env_mutex);
-	JMutexAutoLock conlock(m_con_mutex);
-
-	//TimeTaker timer("Server::SendBlocks");
-
-	core::array<PrioritySortedBlockTransfer> queue;
-
-	s32 total_sending = 0;
-
-	for(core::map<u16, RemoteClient*>::Iterator
-		i = m_clients.getIterator();
-		i.atEnd() == false; i++)
-	{
-		RemoteClient *client = i.getNode()->getValue();
-		assert(client->peer_id == i.getNode()->getKey());
-
-		total_sending += client->SendingCount();
-		
-		if(client->serialization_version == SER_FMT_VER_INVALID)
-			continue;
-		
-		client->GetNextBlocks(this, dtime, queue);
-	}
-
-	// Sort.
-	// Lowest priority number comes first.
-	// Lowest is most important.
-	queue.sort();
-
-	for(u32 i=0; i<queue.size(); i++)
-	{
-		//TODO: Calculate limit dynamically
-		if(total_sending >= g_settings.getS32
-				("max_simultaneous_block_sends_server_total"))
-			break;
-		
-		PrioritySortedBlockTransfer q = queue[i];
-
-		MapBlock *block = NULL;
-		try
-		{
-			block = m_env.getMap().getBlockNoCreate(q.pos);
-		}
-		catch(InvalidPositionException &e)
-		{
-			continue;
-		}
-
-		RemoteClient *client = getClient(q.peer_id);
-
-		SendBlockNoLock(q.peer_id, block, client->serialization_version);
-
-		client->SentBlock(q.pos);
-
-		total_sending++;
-	}
-}
-
-
 RemoteClient* Server::getClient(u16 peer_id)
 {
 	DSTACK(__FUNCTION_NAME);
@@ -3682,14 +3879,24 @@ void setCreativeInventory(Player *player)
 {
 	player->resetInventory();
 	
-	// Give some good picks
+	// Give some good tools
 	{
-		InventoryItem *item = new ToolItem("STPick", 0);
+		InventoryItem *item = new ToolItem("MesePick", 0);
 		void* r = player->inventory.addItem("main", item);
 		assert(r == NULL);
 	}
 	{
-		InventoryItem *item = new ToolItem("MesePick", 0);
+		InventoryItem *item = new ToolItem("SteelPick", 0);
+		void* r = player->inventory.addItem("main", item);
+		assert(r == NULL);
+	}
+	{
+		InventoryItem *item = new ToolItem("SteelAxe", 0);
+		void* r = player->inventory.addItem("main", item);
+		assert(r == NULL);
+	}
+	{
+		InventoryItem *item = new ToolItem("SteelShovel", 0);
 		void* r = player->inventory.addItem("main", item);
 		assert(r == NULL);
 	}
@@ -3756,6 +3963,52 @@ void setCreativeInventory(Player *player)
 	}*/
 }
 
+v3f findSpawnPos(ServerMap &map)
+{
+	v2s16 nodepos;
+	s16 groundheight = 0;
+	
+	// Try to find a good place a few times
+	for(s32 i=0; i<1000; i++)
+	{
+		s32 range = 1 + i;
+		// We're going to try to throw the player to this position
+		nodepos = v2s16(-range + (myrand()%(range*2)),
+				-range + (myrand()%(range*2)));
+		v2s16 sectorpos = getNodeSectorPos(nodepos);
+		// Get sector (NOTE: Don't get because it's slow)
+		//m_env.getMap().emergeSector(sectorpos);
+		// Get ground height at point (fallbacks to heightmap function)
+		groundheight = map.findGroundLevel(nodepos);
+		// Don't go underwater
+		if(groundheight < WATER_LEVEL)
+		{
+			//dstream<<"-> Underwater"<<std::endl;
+			continue;
+		}
+		// Don't go to high places
+		if(groundheight > WATER_LEVEL + 4)
+		{
+			//dstream<<"-> Underwater"<<std::endl;
+			continue;
+		}
+
+		// Found a good place
+		//dstream<<"Searched through "<<i<<" places."<<std::endl;
+		break;
+	}
+	
+	// If no suitable place was not found, go above water at least.
+	if(groundheight < WATER_LEVEL)
+		groundheight = WATER_LEVEL;
+
+	return intToFloat(v3s16(
+			nodepos.X,
+			groundheight + 2,
+			nodepos.Y
+			), BS);
+}
+
 Player *Server::emergePlayer(const char *name, const char *password,
 		u16 peer_id)
 {
@@ -3811,58 +4064,9 @@ Player *Server::emergePlayer(const char *name, const char *password,
 		dstream<<"Server: Finding spawn place for player \""
 				<<player->getName()<<"\""<<std::endl;
 
-		v2s16 nodepos;
-#if 0
-		player->setPosition(intToFloat(v3s16(
-				0,
-				45, //64,
-				0
-		), BS));
-#endif
-#if 1
-		s16 groundheight = 0;
-#if 1
-		// Try to find a good place a few times
-		for(s32 i=0; i<1000; i++)
-		{
-			s32 range = 1 + i;
-			// We're going to try to throw the player to this position
-			nodepos = v2s16(-range + (myrand()%(range*2)),
-					-range + (myrand()%(range*2)));
-			v2s16 sectorpos = getNodeSectorPos(nodepos);
-			// Get sector (NOTE: Don't get because it's slow)
-			//m_env.getMap().emergeSector(sectorpos);
-			// Get ground height at point (fallbacks to heightmap function)
-			groundheight = m_env.getServerMap().findGroundLevel(nodepos);
-			// Don't go underwater
-			if(groundheight < WATER_LEVEL)
-			{
-				//dstream<<"-> Underwater"<<std::endl;
-				continue;
-			}
-			// Don't go to high places
-			if(groundheight > WATER_LEVEL + 4)
-			{
-				//dstream<<"-> Underwater"<<std::endl;
-				continue;
-			}
+		v3f pos = findSpawnPos(m_env.getServerMap());
 
-			// Found a good place
-			dstream<<"Searched through "<<i<<" places."<<std::endl;
-			break;
-		}
-#endif
-		
-		// If no suitable place was not found, go above water at least.
-		if(groundheight < WATER_LEVEL)
-			groundheight = WATER_LEVEL;
-
-		player->setPosition(intToFloat(v3s16(
-				nodepos.X,
-				groundheight + 5, // Accomodate mud
-				nodepos.Y
-		), BS));
-#endif
+		player->setPosition(pos);
 
 		/*
 			Add player to environment
diff --git a/src/server.h b/src/server.h
index 9059e91b8791dcfb1dc8ac3397b3cb2649f6ebdc..cba5fc2ce1aea7dbfc38cec1d051f8f9338857e7 100644
--- a/src/server.h
+++ b/src/server.h
@@ -33,6 +33,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "map.h"
 #include "inventory.h"
 
+/*
+	Some random functions
+*/
+v3f findSpawnPos(ServerMap &map);
+
+/*
+	A structure containing the data needed for queueing the fetching
+	of blocks.
+*/
 struct QueuedBlockEmerge
 {
 	v3s16 pos;
@@ -397,12 +406,24 @@ class Server : public con::PeerHandler, public MapEventReceiver,
 	void peerAdded(con::Peer *peer);
 	void deletingPeer(con::Peer *peer, bool timeout);
 	
+	/*
+		Static send methods
+	*/
+	
+	static void SendHP(con::Connection &con, u16 peer_id, u8 hp);
+	
+	/*
+		Non-static send methods
+	*/
+
 	// Envlock and conlock should be locked when calling these
 	void SendObjectData(float dtime);
 	void SendPlayerInfos();
 	void SendInventory(u16 peer_id);
 	void SendChatMessage(u16 peer_id, const std::wstring &message);
 	void BroadcastChatMessage(const std::wstring &message);
+	void SendPlayerHP(Player *player);
+	void SendMovePlayer(Player *player);
 	/*
 		Send a node removal/addition event to all clients except ignore_id.
 		Additionally, if far_players!=NULL, players further away than
@@ -418,6 +439,12 @@ class Server : public con::PeerHandler, public MapEventReceiver,
 	
 	// Sends blocks to clients
 	void SendBlocks(float dtime);
+
+	/*
+		Something random
+	*/
+	
+	void UpdateCrafting(u16 peer_id);
 	
 	// When called, connection mutex should be locked
 	RemoteClient* getClient(u16 peer_id);
diff --git a/src/serverobject.cpp b/src/serverobject.cpp
index 5d391dbcf359d6b64d3683f26f6bf6a3b71f6248..30234f7e98920caa5e047556ae3afe6852a102e2 100644
--- a/src/serverobject.cpp
+++ b/src/serverobject.cpp
@@ -451,4 +451,219 @@ InventoryItem* RatSAO::createPickedUpItem()
 	return item;
 }
 
+/*
+	Oerkki1SAO
+*/
+
+// Prototype
+Oerkki1SAO proto_Oerkki1SAO(NULL, 0, v3f(0,0,0));
+
+Oerkki1SAO::Oerkki1SAO(ServerEnvironment *env, u16 id, v3f pos):
+	ServerActiveObject(env, id, pos),
+	m_is_active(false),
+	m_speed_f(0,0,0)
+{
+	ServerActiveObject::registerType(getType(), create);
+
+	m_oldpos = v3f(0,0,0);
+	m_last_sent_position = v3f(0,0,0);
+	m_yaw = 0;
+	m_counter1 = 0;
+	m_counter2 = 0;
+	m_age = 0;
+	m_touching_ground = false;
+	m_hp = 20;
+}
+
+ServerActiveObject* Oerkki1SAO::create(ServerEnvironment *env, u16 id, v3f pos,
+		const std::string &data)
+{
+	std::istringstream is(data, std::ios::binary);
+	// read version
+	u8 version = readU8(is);
+	// read hp
+	u8 hp = readU8(is);
+	// check if version is supported
+	if(version != 0)
+		return NULL;
+	Oerkki1SAO *o = new Oerkki1SAO(env, id, pos);
+	o->m_hp = hp;
+	return o;
+}
+
+void Oerkki1SAO::step(float dtime, Queue<ActiveObjectMessage> &messages,
+		bool send_recommended)
+{
+	assert(m_env);
+
+	if(m_is_active == false)
+	{
+		if(m_inactive_interval.step(dtime, 0.5)==false)
+			return;
+	}
+
+	/*
+		The AI
+	*/
+
+	m_age += dtime;
+	if(m_age > 60)
+	{
+		// Die
+		m_removed = true;
+		return;
+	}
+
+	// Apply gravity
+	m_speed_f.Y -= dtime*9.81*BS;
+
+	/*
+		Move around if some player is close
+	*/
+	bool player_is_close = false;
+	v3f near_player_pos;
+	// Check connected players
+	core::list<Player*> players = m_env->getPlayers(true);
+	core::list<Player*>::Iterator i;
+	for(i = players.begin();
+			i != players.end(); i++)
+	{
+		Player *player = *i;
+		v3f playerpos = player->getPosition();
+		if(m_base_position.getDistanceFrom(playerpos) < BS*15.0)
+		{
+			player_is_close = true;
+			near_player_pos = playerpos;
+			break;
+		}
+	}
+
+	m_is_active = player_is_close;
+	
+	if(player_is_close == false)
+	{
+		m_speed_f.X = 0;
+		m_speed_f.Z = 0;
+	}
+	else
+	{
+		// Move around
+
+		v3f ndir = near_player_pos - m_base_position;
+		ndir.Y = 0;
+		ndir /= ndir.getLength();
+		f32 nyaw = 180./PI*atan2(ndir.Z,ndir.X);
+		if(nyaw < m_yaw - 180)
+			nyaw += 360;
+		else if(nyaw > m_yaw + 180)
+			nyaw -= 360;
+		m_yaw = 0.95*m_yaw + 0.05*nyaw;
+		m_yaw = wrapDegrees(m_yaw);
+
+		v3f dir(cos(m_yaw/180*PI),0,sin(m_yaw/180*PI));
+		f32 speed = 2*BS;
+		m_speed_f.X = speed * dir.X;
+		m_speed_f.Z = speed * dir.Z;
+
+		if(m_touching_ground && (m_oldpos - m_base_position).getLength()
+				< dtime*speed/2)
+		{
+			m_counter1 -= dtime;
+			if(m_counter1 < 0.0)
+			{
+				m_counter1 += 1.0;
+				// Jump
+				m_speed_f.Y = 5.0*BS;
+			}
+		}
+
+		{
+			m_counter2 -= dtime;
+			if(m_counter2 < 0.0)
+			{
+				m_counter2 += (float)(myrand()%100)/100*3.0;
+				//m_yaw += ((float)(myrand()%200)-100)/100*180;
+				m_yaw += ((float)(myrand()%200)-100)/100*90;
+				m_yaw = wrapDegrees(m_yaw);
+			}
+		}
+	}
+	
+	m_oldpos = m_base_position;
+
+	/*
+		Move it, with collision detection
+	*/
+
+	core::aabbox3d<f32> box(-BS/3.,0.0,-BS/3., BS/3.,BS*5./3.,BS/3.);
+	collisionMoveResult moveresult;
+	// Maximum movement without glitches
+	f32 pos_max_d = BS*0.25;
+	// Limit speed
+	if(m_speed_f.getLength()*dtime > pos_max_d)
+		m_speed_f *= pos_max_d / (m_speed_f.getLength()*dtime);
+	v3f pos_f = getBasePosition();
+	v3f pos_f_old = pos_f;
+	moveresult = collisionMoveSimple(&m_env->getMap(), pos_max_d,
+			box, dtime, pos_f, m_speed_f);
+	m_touching_ground = moveresult.touching_ground;
+	
+	setBasePosition(pos_f);
+
+	if(send_recommended == false)
+		return;
+
+	if(pos_f.getDistanceFrom(m_last_sent_position) > 0.05*BS)
+	{
+		m_last_sent_position = pos_f;
+
+		std::ostringstream os(std::ios::binary);
+		// command (0 = update position)
+		writeU8(os, 0);
+		// pos
+		writeV3F1000(os, m_base_position);
+		// yaw
+		writeF1000(os, m_yaw);
+		// create message and add to list
+		ActiveObjectMessage aom(getId(), false, os.str());
+		messages.push_back(aom);
+	}
+}
+
+std::string Oerkki1SAO::getClientInitializationData()
+{
+	std::ostringstream os(std::ios::binary);
+	// version
+	writeU8(os, 0);
+	// pos
+	writeV3F1000(os, m_base_position);
+	return os.str();
+}
+
+std::string Oerkki1SAO::getStaticData()
+{
+	//dstream<<__FUNCTION_NAME<<std::endl;
+	std::ostringstream os(std::ios::binary);
+	// version
+	writeU8(os, 0);
+	// hp
+	writeU8(os, m_hp);
+	return os.str();
+}
+
+u16 Oerkki1SAO::punch(const std::string &toolname)
+{
+	u16 amount = 5;
+	if(amount < m_hp)
+	{
+		m_hp -= amount;
+	}
+	else
+	{
+		// Die
+		m_removed = true;
+	}
+	return 65536/100;
+}
+
 
diff --git a/src/serverobject.h b/src/serverobject.h
index 2889d0c3940e6668fcb809070736f28382698c0a..71199475e4936ca28a2ae11a71b75cfa8a0cfd8d 100644
--- a/src/serverobject.h
+++ b/src/serverobject.h
@@ -100,6 +100,12 @@ class ServerActiveObject : public ActiveObject
 	*/
 	virtual InventoryItem* createPickedUpItem(){return NULL;}
 	
+	/*
+		If the object doesn't return an item, this will be called.
+		Return value is tool wear.
+	*/
+	virtual u16 punch(const std::string &toolname){return 0;}
+	
 	// Number of players which know about this object
 	u16 m_known_by_count;
 	/*
@@ -201,5 +207,33 @@ class RatSAO : public ServerActiveObject
 	bool m_touching_ground;
 };
 
+class Oerkki1SAO : public ServerActiveObject
+{
+public:
+	Oerkki1SAO(ServerEnvironment *env, u16 id, v3f pos);
+	u8 getType() const
+		{return ACTIVEOBJECT_TYPE_OERKKI1;}
+	static ServerActiveObject* create(ServerEnvironment *env, u16 id, v3f pos,
+			const std::string &data);
+	void step(float dtime, Queue<ActiveObjectMessage> &messages,
+			bool send_recommended);
+	std::string getClientInitializationData();
+	std::string getStaticData();
+	InventoryItem* createPickedUpItem(){return NULL;}
+	u16 punch(const std::string &toolname);
+private:
+	bool m_is_active;
+	IntervalLimiter m_inactive_interval;
+	v3f m_speed_f;
+	v3f m_oldpos;
+	v3f m_last_sent_position;
+	float m_yaw;
+	float m_counter1;
+	float m_counter2;
+	float m_age;
+	bool m_touching_ground;
+	u8 m_hp;
+};
+
 #endif
 
diff --git a/src/test.cpp b/src/test.cpp
index c7bffcdf3813a85564b2b1789eca66908e3d54f9..07ef772eff7755c2b74de152bea0091520b1d30a 100644
--- a/src/test.cpp
+++ b/src/test.cpp
@@ -951,18 +951,18 @@ struct TestConnection
 			assert(got_exception);
 		}
 		{
-			//u8 data1[1100];
-			SharedBuffer<u8> data1(1100);
-			for(u16 i=0; i<1100; i++){
+			const int datasize = 30000;
+			SharedBuffer<u8> data1(datasize);
+			for(u16 i=0; i<datasize; i++){
 				data1[i] = i/4;
 			}
 
-			dstream<<"Sending data (size="<<1100<<"):";
-			for(int i=0; i<1100 && i<20; i++){
+			dstream<<"Sending data (size="<<datasize<<"):";
+			for(int i=0; i<datasize && i<20; i++){
 				if(i%2==0) DEBUGPRINT(" ");
 				DEBUGPRINT("%.2X", ((int)((const char*)*data1)[i])&0xff);
 			}
-			if(1100>20)
+			if(datasize>20)
 				dstream<<"...";
 			dstream<<std::endl;
 			
@@ -970,10 +970,10 @@ struct TestConnection
 
 			sleep_ms(50);
 			
-			u8 recvdata[2000];
+			u8 recvdata[datasize + 1000];
 			dstream<<"** running client.Receive()"<<std::endl;
 			u16 peer_id = 132;
-			u16 size = client.Receive(peer_id, recvdata, 2000);
+			u16 size = client.Receive(peer_id, recvdata, datasize + 1000);
 			dstream<<"** Client received: peer_id="<<peer_id
 					<<", size="<<size
 					<<std::endl;
diff --git a/src/utility.h b/src/utility.h
index 0b59ce6fdae6d8a5905c8c4a09cd914727d36e7b..c6bb3f87905304266659db5d15a3545b07f0ed0a 100644
--- a/src/utility.h
+++ b/src/utility.h
@@ -215,8 +215,8 @@ inline void writeU16(std::ostream &os, u16 p)
 }
 inline u16 readU16(std::istream &is)
 {
-	char buf[12];
-	is.read(buf, 12);
+	char buf[2];
+	is.read(buf, 2);
 	return readU16((u8*)buf);
 }
 
@@ -228,8 +228,8 @@ inline void writeF1000(std::ostream &os, f32 p)
 }
 inline f32 readF1000(std::istream &is)
 {
-	char buf[12];
-	is.read(buf, 12);
+	char buf[2];
+	is.read(buf, 2);
 	return readF1000((u8*)buf);
 }