From a26c92d7dda327f2b1483fe7250cb27580a0a039 Mon Sep 17 00:00:00 2001
From: Perttu Ahola <celeron55@gmail.com>
Date: Fri, 24 Dec 2010 17:08:50 +0200
Subject: disconnect method to connection to be used instead of just timing out

---
 src/client.cpp           |   7 +-
 src/connection.cpp       |  44 ++++-
 src/connection.h         |  14 +-
 src/guiInventoryMenu.cpp |  16 +-
 src/inventory.cpp        | 209 +++++++++++++-----------
 src/inventory.h          | 196 +++++++++++++++++-----
 src/main.cpp             |  10 +-
 src/server.cpp           | 413 +++++++++++++++++++++++++++++++++++------------
 src/server.h             |  22 +++
 9 files changed, 688 insertions(+), 243 deletions(-)

(limited to 'src')

diff --git a/src/client.cpp b/src/client.cpp
index f9cccd855..b0750cae6 100644
--- a/src/client.cpp
+++ b/src/client.cpp
@@ -117,6 +117,11 @@ Client::Client(
 
 Client::~Client()
 {
+	{
+		JMutexAutoLock conlock(m_con_mutex);
+		m_con.Disconnect();
+	}
+
 	m_thread.setRun(false);
 	while(m_thread.IsRunning())
 		sleep_ms(100);
@@ -601,7 +606,7 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
 		
 		addNode(p, n);
 	}
-	if(command == TOCLIENT_PLAYERPOS)
+	else if(command == TOCLIENT_PLAYERPOS)
 	{
 		dstream<<"WARNING: Received deprecated TOCLIENT_PLAYERPOS"
 				<<std::endl;
diff --git a/src/connection.cpp b/src/connection.cpp
index e0a201773..0bc8492e6 100644
--- a/src/connection.cpp
+++ b/src/connection.cpp
@@ -542,6 +542,23 @@ void Connection::Connect(Address address)
 	//m_waiting_new_peer_id = true;
 }
 
+void Connection::Disconnect()
+{
+	// Create and send DISCO packet
+	SharedBuffer<u8> data(2);
+	writeU8(&data[0], TYPE_CONTROL);
+	writeU8(&data[1], CONTROLTYPE_DISCO);
+	
+	// Send to all
+	core::map<u16, Peer*>::Iterator j;
+	j = m_peers.getIterator();
+	for(; j.atEnd() == false; j++)
+	{
+		Peer *peer = j.getNode()->getValue();
+		SendAsPacket(peer->id, 0, data, false);
+	}
+}
+
 bool Connection::Connected()
 {
 	if(m_peers.size() != 1)
@@ -645,7 +662,22 @@ SharedBuffer<u8> Channel::ProcessPacket(
 			// the timeout counter
 			con->PrintInfo();
 			dout_con<<"PING"<<std::endl;
-			throw ProcessedSilentlyException("Got a SET_PEER_ID");
+			throw ProcessedSilentlyException("Got a PING");
+		}
+		else if(controltype == CONTROLTYPE_DISCO)
+		{
+			// Just ignore it, the incoming data already reset
+			// the timeout counter
+			con->PrintInfo();
+			dout_con<<"DISCO: Removing peer "<<(peer_id)<<std::endl;
+			
+			if(con->deletePeer(peer_id) == false)
+			{
+				con->PrintInfo(derr_con);
+				derr_con<<"DISCO: Peer not found"<<std::endl;
+			}
+
+			throw ProcessedSilentlyException("Got a DISCO");
 		}
 		else{
 			con->PrintInfo(derr_con);
@@ -1323,6 +1355,16 @@ core::list<Peer*> Connection::GetPeers()
 	return list;
 }
 
+bool Connection::deletePeer(u16 peer_id)
+{
+	if(m_peers.find(peer_id) == NULL)
+		return false;
+	m_peerhandler->deletingPeer(m_peers[peer_id], true);
+	delete m_peers[peer_id];
+	m_peers.remove(peer_id);
+	return true;
+}
+
 void Connection::PrintInfo(std::ostream &out)
 {
 	out<<m_socket.GetHandle();
diff --git a/src/connection.h b/src/connection.h
index 82cf9d38e..ad45278ba 100644
--- a/src/connection.h
+++ b/src/connection.h
@@ -226,12 +226,15 @@ controltype and data description:
 	CONTROLTYPE_SET_PEER_ID
 		[2] u16 peer_id_new
 	CONTROLTYPE_PING
-	- This can be sent in a reliable packet to get a reply
+	- There is no actual reply, but this can be sent in a reliable
+	  packet to get a reply
+	CONTROLTYPE_DISCO
 */
 #define TYPE_CONTROL 0
 #define CONTROLTYPE_ACK 0
 #define CONTROLTYPE_SET_PEER_ID 1
 #define CONTROLTYPE_PING 2
+#define CONTROLTYPE_DISCO 3
 /*
 ORIGINAL: This is a plain packet with no control and no error
 checking at all.
@@ -442,12 +445,15 @@ public:
 	void Connect(Address address);
 	bool Connected();
 
+	void Disconnect();
+
 	// Sets peer_id
 	SharedBuffer<u8> GetFromBuffers(u16 &peer_id);
 
 	// The peer_id of sender is stored in peer_id
 	// Return value: I guess this always throws an exception or
 	//               actually gets data
+	// May call PeerHandler methods
 	u32 Receive(u16 &peer_id, u8 *data, u32 datasize);
 	
 	// These will automatically package the data as an original or split
@@ -460,12 +466,18 @@ public:
 	// Sends a raw packet
 	void RawSend(const BufferedPacket &packet);
 	
+	// May call PeerHandler methods
 	void RunTimeouts(float dtime);
+
 	// Can throw a PeerNotFoundException
 	Peer* GetPeer(u16 peer_id);
 	// returns NULL if failed
 	Peer* GetPeerNoEx(u16 peer_id);
 	core::list<Peer*> GetPeers();
+	
+	// Calls PeerHandler::deletingPeer
+	// Returns false if peer was not found
+	bool deletePeer(u16 peer_id);
 
 	void SetPeerID(u16 id){ m_peer_id = id; }
 	u16 GetPeerID(){ return m_peer_id; }
diff --git a/src/guiInventoryMenu.cpp b/src/guiInventoryMenu.cpp
index bba23e719..a1f7d02d0 100644
--- a/src/guiInventoryMenu.cpp
+++ b/src/guiInventoryMenu.cpp
@@ -228,8 +228,10 @@ bool GUIInventoryMenu::OnEvent(const SEvent& event)
 	}
 	if(event.EventType==EET_MOUSE_INPUT_EVENT)
 	{
-		if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
+		if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN
+				|| event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
 		{
+			bool right = (event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN);
 			v2s32 p(event.MouseInput.X, event.MouseInput.Y);
 			//dstream<<"Mouse down at p=("<<p.X<<","<<p.Y<<")"<<std::endl;
 			ItemSpec s = getItemAtPos(p);
@@ -248,15 +250,21 @@ bool GUIInventoryMenu::OnEvent(const SEvent& event)
 						dstream<<"Queueing IACTION_MOVE"<<std::endl;
 						IMoveAction *a =
 							new IMoveAction();
-						a->count = 1;
+						a->count = right ? 1 : 0;
 						a->from_name = m_selected_item->listname;
 						a->from_i = m_selected_item->i;
 						a->to_name = s.listname;
 						a->to_i = s.i;
 						m_actions->push_back(a);
 					}
-					delete m_selected_item;
-					m_selected_item = NULL;
+					bool source_empties = false;
+					if(list_from && list_from->getItem(m_selected_item->i)->getCount()==1)
+						source_empties = true;
+					if(right == false || source_empties)
+					{
+						delete m_selected_item;
+						m_selected_item = NULL;
+					}
 				}
 				else
 				{
diff --git a/src/inventory.cpp b/src/inventory.cpp
index 332f9d999..a2aba311b 100644
--- a/src/inventory.cpp
+++ b/src/inventory.cpp
@@ -32,8 +32,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 	InventoryItem
 */
 
-InventoryItem::InventoryItem()
+InventoryItem::InventoryItem(u16 count)
 {
+	m_count = count;
 }
 
 InventoryItem::~InventoryItem()
@@ -66,6 +67,14 @@ InventoryItem* InventoryItem::deSerialize(std::istream &is)
 		std::getline(is, inventorystring, '|');
 		return new MapBlockObjectItem(inventorystring);
 	}
+	else if(name == "CraftItem")
+	{
+		std::string subname;
+		std::getline(is, subname, ' ');
+		u16 count;
+		is>>count;
+		return new CraftItem(subname, count);
+	}
 	else if(name == "ToolItem")
 	{
 		std::string toolname;
@@ -325,83 +334,93 @@ void InventoryList::deleteItem(u32 i)
 		delete item;
 }
 
-bool InventoryList::addItem(InventoryItem *newitem)
+InventoryItem * InventoryList::addItem(InventoryItem *newitem)
 {
-	// If it is a MaterialItem, try to find an already existing one
-	// and just increment the counter
-	if(std::string("MaterialItem") == newitem->getName())
+	/*
+		First try to find if it could be added to some existing items
+	*/
+	for(u32 i=0; i<m_items.size(); i++)
 	{
-		u8 material = ((MaterialItem*)newitem)->getMaterial();
-		u8 count = ((MaterialItem*)newitem)->getCount();
-		for(u32 i=0; i<m_items.size(); i++)
-		{
-			InventoryItem *item2 = m_items[i];
-			if(item2 == NULL)
-				continue;
-			if(std::string("MaterialItem") != item2->getName())
-				continue;
-			// Found one. Check if it is of the right material and has
-			// free space
-			MaterialItem *mitem2 = (MaterialItem*)item2;
-			if(mitem2->getMaterial() != material)
-				continue;
-			//TODO: Add all that can be added and add remaining part
-			// to another place
-			if(mitem2->freeSpace() < count)
-				continue;
-			// Add to the counter
-			mitem2->add(count);
-			// Dump the parameter
-			delete newitem;
-			return true;
-		}
+		// Ignore empty slots
+		if(m_items[i] == NULL)
+			continue;
+		// Try adding
+		newitem = addItem(i, newitem);
+		if(newitem == NULL)
+			return NULL; // All was eaten
 	}
-	// Else find an empty position
+
+	/*
+		Then try to add it to empty slots
+	*/
 	for(u32 i=0; i<m_items.size(); i++)
 	{
-		InventoryItem *item = m_items[i];
-		if(item != NULL)
+		// Ignore unempty slots
+		if(m_items[i] != NULL)
 			continue;
-		m_items[i] = newitem;
-		return true;
+		// Try adding
+		newitem = addItem(i, newitem);
+		if(newitem == NULL)
+			return NULL; // All was eaten
 	}
-	// Failed
-	return false;
+
+	// Return leftover
+	return newitem;
 }
 
-bool InventoryList::addItem(u32 i, InventoryItem *newitem)
+InventoryItem * InventoryList::addItem(u32 i, InventoryItem *newitem)
 {
 	// If it is an empty position, it's an easy job.
-	InventoryItem *item = m_items[i];
-	if(item == NULL)
+	InventoryItem *to_item = m_items[i];
+	if(to_item == NULL)
 	{
 		m_items[i] = newitem;
-		return true;
+		return NULL;
 	}
-
-	// If it is a material item, try to 
-	if(std::string("MaterialItem") == newitem->getName())
+	
+	// If not addable, return the item
+	if(newitem->addableTo(to_item) == false)
+		return newitem;
+	
+	// If the item fits fully in the slot, add counter and delete it
+	if(newitem->getCount() <= to_item->freeSpace())
+	{
+		to_item->add(newitem->getCount());
+		delete newitem;
+		return NULL;
+	}
+	// Else the item does not fit fully. Add all that fits and return
+	// the rest.
+	else
 	{
-		u8 material = ((MaterialItem*)newitem)->getMaterial();
-		u8 count = ((MaterialItem*)newitem)->getCount();
-		InventoryItem *item2 = m_items[i];
+		u16 freespace = to_item->freeSpace();
+		to_item->add(freespace);
+		newitem->remove(freespace);
+		return newitem;
+	}
+}
 
-		if(item2 != NULL
-			&& std::string("MaterialItem") == item2->getName())
-		{
-			// Check if it is of the right material and has free space
-			MaterialItem *mitem2 = (MaterialItem*)item2;
-			if(mitem2->getMaterial() == material
-					&& mitem2->freeSpace() >= count)
-			{
-				// Add to the counter
-				mitem2->add(count);
-				// Dump the parameter
-				delete newitem;
-				// Done
-				return true;
-			}
-		}
+InventoryItem * InventoryList::takeItem(u32 i, u32 count)
+{
+	if(count == 0)
+		return NULL;
+
+	InventoryItem *item = m_items[i];
+	// If it is an empty position, return NULL
+	if(item == NULL)
+		return NULL;
+	
+	if(count >= item->getCount())
+	{
+		// Get the item by swapping NULL to its place
+		return changeItem(i, NULL);
+	}
+	else
+	{
+		InventoryItem *item2 = item->clone();
+		item->remove(count);
+		item2->setCount(count);
+		return item2;
 	}
 	
 	return false;
@@ -411,26 +430,9 @@ void InventoryList::decrementMaterials(u16 count)
 {
 	for(u32 i=0; i<m_items.size(); i++)
 	{
-		InventoryItem *item = m_items[i];
-		if(item == NULL)
-			continue;
-		if(std::string("MaterialItem") == item->getName())
-		{
-			MaterialItem *mitem = (MaterialItem*)item;
-			if(mitem->getCount() < count)
-			{
-				dstream<<__FUNCTION_NAME<<": decrementMaterials():"
-						<<" too small material count"<<std::endl;
-			}
-			else if(mitem->getCount() == count)
-			{
-				deleteItem(i);
-			}
-			else
-			{
-				mitem->remove(1);
-			}
-		}
+		InventoryItem *item = takeItem(i, count);
+		if(item)
+			delete item;
 	}
 }
 
@@ -607,6 +609,10 @@ void IMoveAction::apply(Inventory *inventory)
 		dstream<<" list_to->getItem(to_i)="<<list_to->getItem(to_i)
 				<<std::endl;*/
 	
+	/*
+		If a list doesn't exist or the source item doesn't exist
+		or the source and the destination slots are the same
+	*/
 	if(!list_from || !list_to || list_from->getItem(from_i) == NULL
 			|| (list_from == list_to && from_i == to_i))
 	{
@@ -615,18 +621,39 @@ void IMoveAction::apply(Inventory *inventory)
 	}
 	
 	// Take item from source list
-	InventoryItem *item1 = list_from->changeItem(from_i, NULL);
+	InventoryItem *item1 = NULL;
+	if(count == 0)
+		item1 = list_from->changeItem(from_i, NULL);
+	else
+		item1 = list_from->takeItem(from_i, count);
+
 	// Try to add the item to destination list
-	if(list_to->addItem(to_i, item1))
-	{
-		// Done.
+	InventoryItem *olditem = item1;
+	item1 = list_to->addItem(to_i, item1);
+
+	// If nothing is returned, the item was fully added
+	if(item1 == NULL)
+		return;
+	
+	// If olditem is returned, nothing was added.
+	bool nothing_added = (item1 == olditem);
+	
+	// If something else is returned, part of the item was left unadded.
+	// Add the other part back to the source item
+	list_from->addItem(from_i, item1);
+
+	// If olditem is returned, nothing was added.
+	// Swap the items
+	if(nothing_added)
+	{
+		// Take item from source list
+		item1 = list_from->changeItem(from_i, NULL);
+		// Adding was not possible, swap the items.
+		InventoryItem *item2 = list_to->changeItem(to_i, item1);
+		// Put item from destination list to the source list
+		list_from->changeItem(from_i, item2);
 		return;
 	}
-	// Adding was not possible, switch it.
-	// Switch it to the destination list
-	InventoryItem *item2 = list_to->changeItem(to_i, item1);
-	// Put item from destination list to the source list
-	list_from->changeItem(from_i, item2);
 }
 	
 //END
diff --git a/src/inventory.h b/src/inventory.h
index fd2cd8778..13bd27d8b 100644
--- a/src/inventory.h
+++ b/src/inventory.h
@@ -33,10 +33,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 // For g_materials
 #include "main.h"
 
+#define QUANTITY_ITEM_MAX_COUNT 99
+
 class InventoryItem
 {
 public:
-	InventoryItem();
+	InventoryItem(u16 count);
 	virtual ~InventoryItem();
 	
 	static InventoryItem* deSerialize(std::istream &is);
@@ -53,18 +55,49 @@ public:
 	// Shall return a text to show in the GUI
 	virtual std::string getText() { return ""; }
 
-private:
-};
+	// Shall return true if the item can be add()ed to the other
+	virtual bool addableTo(InventoryItem *other)
+	{
+		return false;
+	}
+	
+	/*
+		Quantity methods
+	*/
+	u16 getCount()
+	{
+		return m_count;
+	}
+	void setCount(u16 count)
+	{
+		m_count = count;
+	}
+	virtual u16 freeSpace()
+	{
+		return 0;
+	}
+	void add(u16 count)
+	{
+		assert(m_count + count <= QUANTITY_ITEM_MAX_COUNT);
+		m_count += count;
+	}
+	void remove(u16 count)
+	{
+		assert(m_count >= count);
+		m_count -= count;
+	}
 
-#define MATERIAL_ITEM_MAX_COUNT 99
+protected:
+	u16 m_count;
+};
 
 class MaterialItem : public InventoryItem
 {
 public:
-	MaterialItem(u8 content, u16 count)
+	MaterialItem(u8 content, u16 count):
+		InventoryItem(count)
 	{
 		m_content = content;
-		m_count = count;
 	}
 	/*
 		Implementation interface
@@ -107,46 +140,38 @@ public:
 		os<<m_count;
 		return os.str();
 	}
-	/*
-		Special methods
-	*/
-	u8 getMaterial()
-	{
-		return m_content;
-	}
-	u16 getCount()
+
+	virtual bool addableTo(InventoryItem *other)
 	{
-		return m_count;
+		if(std::string(other->getName()) != "MaterialItem")
+			return false;
+		MaterialItem *m = (MaterialItem*)other;
+		if(m->getMaterial() != m_content)
+			return false;
+		return true;
 	}
 	u16 freeSpace()
 	{
-		if(m_count > MATERIAL_ITEM_MAX_COUNT)
+		if(m_count > QUANTITY_ITEM_MAX_COUNT)
 			return 0;
-		return MATERIAL_ITEM_MAX_COUNT - m_count;
-	}
-	void add(u16 count)
-	{
-		assert(m_count + count <= MATERIAL_ITEM_MAX_COUNT);
-		m_count += count;
+		return QUANTITY_ITEM_MAX_COUNT - m_count;
 	}
-	void remove(u16 count)
+	/*
+		Special methods
+	*/
+	u8 getMaterial()
 	{
-		assert(m_count >= count);
-		m_count -= count;
+		return m_content;
 	}
 private:
 	u8 m_content;
-	u16 m_count;
 };
 
 class MapBlockObjectItem : public InventoryItem
 {
 public:
-	/*MapBlockObjectItem(MapBlockObject *obj)
-	{
-		m_inventorystring = obj->getInventoryString();
-	}*/
-	MapBlockObjectItem(std::string inventorystring)
+	MapBlockObjectItem(std::string inventorystring):
+		InventoryItem(1)
 	{
 		m_inventorystring = inventorystring;
 	}
@@ -196,10 +221,90 @@ private:
 	std::string m_inventorystring;
 };
 
+/*
+	An item that is used as a mid-product when crafting.
+	Subnames:
+	- Stick
+*/
+class CraftItem : public InventoryItem
+{
+public:
+	CraftItem(std::string subname, u16 count):
+		InventoryItem(count)
+	{
+		m_subname = subname;
+	}
+	/*
+		Implementation interface
+	*/
+	virtual const char* getName() const
+	{
+		return "CraftItem";
+	}
+	virtual void serialize(std::ostream &os)
+	{
+		os<<getName();
+		os<<" ";
+		os<<m_subname;
+		os<<" ";
+		os<<m_count;
+	}
+	virtual InventoryItem* clone()
+	{
+		return new CraftItem(m_subname, m_count);
+	}
+#ifndef SERVER
+	video::ITexture * getImage()
+	{
+		std::string basename;
+		if(m_subname == "Stick")
+			basename = "../data/stick.png";
+		// Default to cloud texture
+		else
+			basename = tile_texture_path_get(TILE_CLOUD);
+		
+		// Get such a texture
+		return g_irrlicht->getTexture(basename);
+		//return g_irrlicht->getTexture(TextureSpec(finalname, basename, mod));
+	}
+#endif
+	std::string getText()
+	{
+		std::ostringstream os;
+		os<<m_count;
+		return os.str();
+	}
+	virtual bool addableTo(InventoryItem *other)
+	{
+		if(std::string(other->getName()) != "CraftItem")
+			return false;
+		CraftItem *m = (CraftItem*)other;
+		if(m->m_subname != m_subname)
+			return false;
+		return true;
+	}
+	u16 freeSpace()
+	{
+		if(m_count > QUANTITY_ITEM_MAX_COUNT)
+			return 0;
+		return QUANTITY_ITEM_MAX_COUNT - m_count;
+	}
+	/*
+		Special methods
+	*/
+	std::string getSubName()
+	{
+		return m_subname;
+	}
+private:
+	std::string m_subname;
+};
+
 class ToolItem : public InventoryItem
 {
 public:
-	ToolItem(std::string toolname, u16 wear)
+	ToolItem(std::string toolname, u16 wear):
+		InventoryItem(1)
 	{
 		m_toolname = toolname;
 		m_wear = wear;
@@ -313,11 +418,20 @@ public:
 	InventoryItem * changeItem(u32 i, InventoryItem *newitem);
 	// Delete item
 	void deleteItem(u32 i);
-	// Adds an item to a suitable place. Returns false if failed.
-	bool addItem(InventoryItem *newitem);
-	// If possible, adds item to given slot. Returns true on success.
-	// Fails when slot is populated by a different kind of item.
-	bool addItem(u32 i, InventoryItem *newitem);
+	// Adds an item to a suitable place. Returns leftover item.
+	// If all went into the list, returns NULL.
+	InventoryItem * addItem(InventoryItem *newitem);
+
+	// If possible, adds item to given slot.
+	// If cannot be added at all, returns the item back.
+	// If can be added partly, decremented item is returned back.
+	// If can be added fully, NULL is returned.
+	InventoryItem * addItem(u32 i, InventoryItem *newitem);
+
+	// Takes some items from a slot.
+	// If there are not enough, takes as many as it can.
+	// Returns NULL if couldn't take any.
+	InventoryItem * takeItem(u32 i, u32 count);
 
 	// Decrements amount of every material item
 	void decrementMaterials(u16 count);
@@ -347,12 +461,13 @@ public:
 	InventoryList * addList(const std::string &name, u32 size);
 	InventoryList * getList(const std::string &name);
 	bool deleteList(const std::string &name);
-	// A shorthand for adding items
-	bool addItem(const std::string &listname, InventoryItem *newitem)
+	// A shorthand for adding items.
+	// Returns NULL if the item was fully added, leftover otherwise.
+	InventoryItem * addItem(const std::string &listname, InventoryItem *newitem)
 	{
 		InventoryList *list = getList(listname);
 		if(list == NULL)
-			return false;
+			return newitem;
 		return list->addItem(newitem);
 	}
 	
@@ -376,6 +491,7 @@ struct InventoryAction
 
 struct IMoveAction : public InventoryAction
 {
+	// count=0 means "everything"
 	u16 count;
 	std::string from_name;
 	s16 from_i;
diff --git a/src/main.cpp b/src/main.cpp
index df335ad39..1a9379e41 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1513,6 +1513,10 @@ int main(int argc, char *argv[])
 	// Test the text input system
 	/*(new GUITextInputMenu(guienv, guiroot, -1, &g_active_menu_count,
 			NULL))->drop();*/
+	
+	// Launch pause menu
+	(new GUIPauseMenu(guienv, guiroot, -1, g_device,
+			&g_active_menu_count))->drop();
 
 	// First line of debug text
 	gui::IGUIStaticText *guitext = guienv->addStaticText(
@@ -2164,9 +2168,11 @@ int main(int argc, char *argv[])
 						}
 						// We want a slight delay to very little
 						// time consuming nodes
-						if(nodig_delay_counter < 0.15)
+						//float mindelay = 0.15;
+						float mindelay = 0.20;
+						if(nodig_delay_counter < mindelay)
 						{
-							nodig_delay_counter = 0.15;
+							nodig_delay_counter = mindelay;
 						}
 					}
 
diff --git a/src/server.cpp b/src/server.cpp
index 35f1f8a27..c0af61b98 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -849,40 +849,6 @@ void RemoteClient::SetBlocksNotSent(core::map<v3s16, MapBlock*> &blocks)
 	}
 }
 
-/*void RemoteClient::BlockEmerged()
-{
-	SharedPtr<JMutexAutoLock> lock(m_num_blocks_in_emerge_queue.getLock());
-	assert(m_num_blocks_in_emerge_queue.m_value > 0);
-	m_num_blocks_in_emerge_queue.m_value--;
-}*/
-
-/*void RemoteClient::RunSendingTimeouts(float dtime, float timeout)
-{
-	JMutexAutoLock sendinglock(m_blocks_sending_mutex);
-	
-	core::list<v3s16> remove_queue;
-	for(core::map<v3s16, float>::Iterator
-			i = m_blocks_sending.getIterator();
-			i.atEnd()==false; i++)
-	{
-		v3s16 p = i.getNode()->getKey();
-		float t = i.getNode()->getValue();
-		t += dtime;
-		i.getNode()->setValue(t);
-
-		if(t > timeout)
-		{
-			remove_queue.push_back(p);
-		}
-	}
-	for(core::list<v3s16>::Iterator
-			i = remove_queue.begin();
-			i != remove_queue.end(); i++)
-	{
-		m_blocks_sending.remove(*i);
-	}
-}*/
-
 /*
 	PlayerInfo
 */
@@ -931,7 +897,8 @@ Server::Server(
 	m_emergethread(this),
 	m_time_of_day(8000),
 	m_time_counter(0),
-	m_time_of_day_send_timer(0)
+	m_time_of_day_send_timer(0),
+	m_uptime(0)
 {
 	m_flowwater_timer = 0.0;
 	m_print_info_timer = 0.0;
@@ -1026,10 +993,20 @@ void Server::AsyncRunStep()
 	if(dtime < 0.001)
 		return;
 	
+	//dstream<<"Server steps "<<dtime<<std::endl;
+	//dstream<<"Server::AsyncRunStep(): dtime="<<dtime<<std::endl;
+	
 	{
 		JMutexAutoLock lock1(m_step_dtime_mutex);
 		m_step_dtime -= dtime;
 	}
+
+	/*
+		Update uptime
+	*/
+	{
+		m_uptime.set(m_uptime.get() + dtime);
+	}
 	
 	/*
 		Update m_time_of_day
@@ -1070,16 +1047,18 @@ void Server::AsyncRunStep()
 		}
 	}
 
-	//dstream<<"Server steps "<<dtime<<std::endl;
-	
-	//dstream<<"Server::AsyncRunStep(): dtime="<<dtime<<std::endl;
 	{
-		// Has to be locked for peerAdded/Removed
-		JMutexAutoLock lock1(m_env_mutex);
 		// Process connection's timeouts
 		JMutexAutoLock lock2(m_con_mutex);
 		m_con.RunTimeouts(dtime);
 	}
+	
+	{
+		// This has to be called so that the client list gets synced
+		// with the peer list of the connection
+		handlePeerChanges();
+	}
+
 	{
 		// Step environment
 		// This also runs Map's timers
@@ -1355,9 +1334,14 @@ void Server::Receive()
 	u32 datasize;
 	try{
 		{
-			JMutexAutoLock lock(m_con_mutex);
+			JMutexAutoLock conlock(m_con_mutex);
 			datasize = m_con.Receive(peer_id, *data, data_maxsize);
 		}
+
+		// This has to be called so that the client list gets synced
+		// with the peer list of the connection
+		handlePeerChanges();
+
 		ProcessData(*data, datasize, peer_id);
 	}
 	catch(con::InvalidIncomingDataException &e)
@@ -1499,6 +1483,36 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 					m_time_of_day.get());
 			m_con.Send(peer->id, 0, data, true);
 		}
+
+		// Send information about server to player in chat
+		{
+			std::wostringstream os(std::ios_base::binary);
+			os<<L"# Server: ";
+			// Uptime
+			os<<L"uptime="<<m_uptime.get();
+			// Information about clients
+			os<<L", clients={";
+			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;
+				// Get name of player
+				std::wstring name = L"unknown";
+				Player *player = m_env.getPlayer(client->peer_id);
+				if(player != NULL)
+					name = narrow_to_wide(player->getName());
+				// Add name to information string
+				os<<name<<L",";
+			}
+			os<<L"}";
+			// Send message
+			SendChatMessage(peer_id, os.str());
+		}
 		
 		// Send information about joining in chat
 		{
@@ -1722,52 +1736,10 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 		*/
 		if(action == 0)
 		{
-		/*
-			NOTE: This can be used in the future to check if
-			somebody is cheating, by checking the timing.
-		*/
-
-#if 0
-			u8 content;
-
-			try
-			{
-				// Get content at position
-				content = m_env.getMap().getNode(p_under).d;
-				// If it's not diggable, do nothing
-				if(content_diggable(content) == false)
-				{
-					return;
-				}
-			}
-			catch(InvalidPositionException &e)
-			{
-				derr_server<<"Server: Not starting digging: Node not found"
-						<<std::endl;
-				return;
-			}
-			
 			/*
-				Set stuff in RemoteClient
+				NOTE: This can be used in the future to check if
+				somebody is cheating, by checking the timing.
 			*/
-			RemoteClient *client = getClient(peer->id);
-			JMutexAutoLock(client->m_dig_mutex);
-			client->m_dig_tool_item = 0;
-			client->m_dig_position = p_under;
-			float dig_time = 0.5;
-			if(content == CONTENT_STONE)
-			{
-				dig_time = 1.5;
-			}
-			else if(content == CONTENT_TORCH)
-			{
-				dig_time = 0.0;
-			}
-			client->m_dig_time_remaining = dig_time;
-			
-			// Reset build time counter
-			getClient(peer->id)->m_time_from_building.set(0.0);
-#endif
 		} // action == 0
 
 		/*
@@ -2240,7 +2212,10 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 					InventoryList *clist = player->inventory.getList("craft");
 					if(clist)
 					{
-						clist->decrementMaterials(ma->count);
+						u16 count = ma->count;
+						if(count == 0)
+							count = 1;
+						clist->decrementMaterials(count);
 					}
 					// Do action
 					// Feed action to player inventory
@@ -2424,8 +2399,15 @@ void Server::peerAdded(con::Peer *peer)
 	dout_server<<"Server::peerAdded(): peer->id="
 			<<peer->id<<std::endl;
 	
-	// Connection is already locked when this is called.
-	//JMutexAutoLock lock(m_con_mutex);
+	PeerChange c;
+	c.type = PEER_ADDED;
+	c.peer_id = peer->id;
+	c.timeout = false;
+	m_peer_change_queue.push_back(c);
+
+#if 0
+	// NOTE: Connection is already locked when this is called.
+	// NOTE: Environment is already locked when this is called.
 	
 	// Error check
 	core::map<u16, RemoteClient*>::Node *n;
@@ -2440,9 +2422,6 @@ void Server::peerAdded(con::Peer *peer)
 
 	// Create player
 	{
-		// Already locked when called
-		//JMutexAutoLock envlock(m_env_mutex);
-		
 		Player *player = m_env.getPlayer(peer->id);
 		
 		// The player shouldn't already exist
@@ -2491,8 +2470,8 @@ void Server::peerAdded(con::Peer *peer)
 			// Give a good pick
 			{
 				InventoryItem *item = new ToolItem("STPick", 32000);
-				bool r = player->inventory.addItem("main", item);
-				assert(r == true);
+				void* r = player->inventory.addItem("main", item);
+				assert(r == NULL);
 			}
 			// Give all materials
 			assert(USEFUL_CONTENT_COUNT <= PLAYER_INVENTORY_SIZE);
@@ -2508,8 +2487,8 @@ void Server::peerAdded(con::Peer *peer)
 			// Sign
 			{
 				InventoryItem *item = new MapBlockObjectItem("Sign Example text");
-				bool r = player->inventory.addItem("main", item);
-				assert(r == true);
+				void* r = player->inventory.addItem("main", item);
+				assert(r == NULL);
 			}
 			/*// Rat
 			{
@@ -2520,15 +2499,20 @@ void Server::peerAdded(con::Peer *peer)
 		}
 		else
 		{
+			{
+				InventoryItem *item = new CraftItem("Stick", 4);
+				void* r = player->inventory.addItem("main", item);
+				assert(r == NULL);
+			}
 			{
 				InventoryItem *item = new ToolItem("WPick", 32000);
-				bool r = player->inventory.addItem("main", item);
-				assert(r == true);
+				void* r = player->inventory.addItem("main", item);
+				assert(r == NULL);
 			}
 			{
 				InventoryItem *item = new ToolItem("STPick", 32000);
-				bool r = player->inventory.addItem("main", item);
-				assert(r == true);
+				void* r = player->inventory.addItem("main", item);
+				assert(r == NULL);
 			}
 			/*// Give some lights
 			{
@@ -2551,6 +2535,7 @@ void Server::peerAdded(con::Peer *peer)
 			}*/
 		}
 	}
+#endif
 }
 
 void Server::deletingPeer(con::Peer *peer, bool timeout)
@@ -2559,8 +2544,18 @@ void Server::deletingPeer(con::Peer *peer, bool timeout)
 	dout_server<<"Server::deletingPeer(): peer->id="
 			<<peer->id<<", timeout="<<timeout<<std::endl;
 	
-	// Connection is already locked when this is called.
-	//JMutexAutoLock lock(m_con_mutex);
+	PeerChange c;
+	c.type = PEER_REMOVED;
+	c.peer_id = peer->id;
+	c.timeout = timeout;
+	m_peer_change_queue.push_back(c);
+
+#if 0
+	// NOTE: Connection is already locked when this is called.
+
+	// NOTE: Environment is already locked when this is called.
+	// NOTE: Locking environment cannot be moved here because connection
+	//       is already locked and env has to be locked before
 
 	// Error check
 	core::map<u16, RemoteClient*>::Node *n;
@@ -2568,10 +2563,22 @@ void Server::deletingPeer(con::Peer *peer, bool timeout)
 	// The client should exist
 	assert(n != NULL);
 	
+	// Send information about leaving in chat
+	{
+		std::wstring name = L"unknown";
+		Player *player = m_env.getPlayer(peer->id);
+		if(player != NULL)
+			name = narrow_to_wide(player->getName());
+		
+		std::wstring message;
+		message += L"*** ";
+		message += name;
+		message += L" left game";
+		BroadcastChatMessage(message);
+	}
+
 	// Delete player
 	{
-		// Already locked when called
-		//JMutexAutoLock envlock(m_env_mutex);
 		m_env.removePlayer(peer->id);
 	}
 	
@@ -2581,6 +2588,7 @@ void Server::deletingPeer(con::Peer *peer, bool timeout)
 
 	// Send player info to all clients
 	SendPlayerInfos();
+#endif
 }
 
 void Server::SendObjectData(float dtime)
@@ -2848,6 +2856,205 @@ void Server::UpdateBlockWaterPressure(MapBlock *block,
 	
 	v.blitBack(modified_blocks);
 }
+
+void Server::handlePeerChange(PeerChange &c)
+{
+	JMutexAutoLock envlock(m_env_mutex);
+	JMutexAutoLock conlock(m_con_mutex);
 	
+	if(c.type == PEER_ADDED)
+	{
+		/*
+			Add
+		*/
+
+		// Error check
+		core::map<u16, RemoteClient*>::Node *n;
+		n = m_clients.find(c.peer_id);
+		// The client shouldn't already exist
+		assert(n == NULL);
+
+		// Create client
+		RemoteClient *client = new RemoteClient();
+		client->peer_id = c.peer_id;
+		m_clients.insert(client->peer_id, client);
+
+		// Create player
+		{
+			Player *player = m_env.getPlayer(c.peer_id);
+			
+			// The player shouldn't already exist
+			assert(player == NULL);
+
+			player = new ServerRemotePlayer();
+			player->peer_id = c.peer_id;
+
+			/*
+				Set player position
+			*/
+			
+			// We're going to throw the player to this position
+			//v2s16 nodepos(29990,29990);
+			//v2s16 nodepos(9990,9990);
+			v2s16 nodepos(0,0);
+			v2s16 sectorpos = getNodeSectorPos(nodepos);
+			// Get zero sector (it could have been unloaded to disk)
+			m_env.getMap().emergeSector(sectorpos);
+			// Get ground height at origin
+			f32 groundheight = m_env.getMap().getGroundHeight(nodepos, true);
+			// The sector should have been generated -> groundheight exists
+			assert(groundheight > GROUNDHEIGHT_VALID_MINVALUE);
+			// Don't go underwater
+			if(groundheight < WATER_LEVEL)
+				groundheight = WATER_LEVEL;
+
+			player->setPosition(intToFloat(v3s16(
+					nodepos.X,
+					groundheight + 1,
+					nodepos.Y
+			)));
+
+			/*
+				Add player to environment
+			*/
+
+			m_env.addPlayer(player);
+
+			/*
+				Add stuff to inventory
+			*/
+			
+			if(g_settings.getBool("creative_mode"))
+			{
+				// Give a good pick
+				{
+					InventoryItem *item = new ToolItem("STPick", 32000);
+					void* r = player->inventory.addItem("main", item);
+					assert(r == NULL);
+				}
+				// Give all materials
+				assert(USEFUL_CONTENT_COUNT <= PLAYER_INVENTORY_SIZE);
+				for(u16 i=0; i<USEFUL_CONTENT_COUNT; i++)
+				{
+					// Skip some materials
+					if(i == CONTENT_OCEAN)
+						continue;
+
+					InventoryItem *item = new MaterialItem(i, 1);
+					player->inventory.addItem("main", item);
+				}
+				// Sign
+				{
+					InventoryItem *item = new MapBlockObjectItem("Sign Example text");
+					void* r = player->inventory.addItem("main", item);
+					assert(r == NULL);
+				}
+				/*// Rat
+				{
+					InventoryItem *item = new MapBlockObjectItem("Rat");
+					bool r = player->inventory.addItem("main", item);
+					assert(r == true);
+				}*/
+			}
+			else
+			{
+				{
+					InventoryItem *item = new CraftItem("Stick", 4);
+					void* r = player->inventory.addItem("main", item);
+					assert(r == NULL);
+				}
+				{
+					InventoryItem *item = new ToolItem("WPick", 32000);
+					void* r = player->inventory.addItem("main", item);
+					assert(r == NULL);
+				}
+				{
+					InventoryItem *item = new ToolItem("STPick", 32000);
+					void* r = player->inventory.addItem("main", item);
+					assert(r == NULL);
+				}
+				/*// Give some lights
+				{
+					InventoryItem *item = new MaterialItem(CONTENT_TORCH, 999);
+					bool r = player->inventory.addItem("main", item);
+					assert(r == true);
+				}
+				// and some signs
+				for(u16 i=0; i<4; i++)
+				{
+					InventoryItem *item = new MapBlockObjectItem("Sign Example text");
+					bool r = player->inventory.addItem("main", item);
+					assert(r == true);
+				}*/
+				/*// Give some other stuff
+				{
+					InventoryItem *item = new MaterialItem(CONTENT_TREE, 999);
+					bool r = player->inventory.addItem("main", item);
+					assert(r == true);
+				}*/
+			}
+		}
+
+	} // PEER_ADDED
+	else if(c.type == PEER_REMOVED)
+	{
+		/*
+			Delete
+		*/
+
+		// Error check
+		core::map<u16, RemoteClient*>::Node *n;
+		n = m_clients.find(c.peer_id);
+		// The client should exist
+		assert(n != NULL);
+		
+		// Collect information about leaving in chat
+		std::wstring message;
+		{
+			std::wstring name = L"unknown";
+			Player *player = m_env.getPlayer(c.peer_id);
+			if(player != NULL)
+				name = narrow_to_wide(player->getName());
+			
+			message += L"*** ";
+			message += name;
+			message += L" left game";
+		}
+
+		// Delete player
+		{
+			m_env.removePlayer(c.peer_id);
+		}
+		
+		// Delete client
+		delete m_clients[c.peer_id];
+		m_clients.remove(c.peer_id);
+
+		// Send player info to all remaining clients
+		SendPlayerInfos();
+		
+		// Send leave chat message to all remaining clients
+		BroadcastChatMessage(message);
+		
+	} // PEER_REMOVED
+	else
+	{
+		assert(0);
+	}
+}
+
+void Server::handlePeerChanges()
+{
+	while(m_peer_change_queue.size() > 0)
+	{
+		PeerChange c = m_peer_change_queue.pop_front();
+
+		dout_server<<"Server: Handling peer change: "
+				<<"id="<<c.peer_id<<", timeout="<<c.timeout
+				<<std::endl;
+
+		handlePeerChange(c);
+	}
+}
 
 
diff --git a/src/server.h b/src/server.h
index f1baaf240..cd6f78c62 100644
--- a/src/server.h
+++ b/src/server.h
@@ -434,6 +434,11 @@ private:
 	void UpdateBlockWaterPressure(MapBlock *block,
 			core::map<v3s16, MapBlock*> &modified_blocks);
 	
+	// Locks environment and connection by its own
+	struct PeerChange;
+	void handlePeerChange(PeerChange &c);
+	void handlePeerChanges();
+	
 	float m_flowwater_timer;
 	float m_print_info_timer;
 	float m_objectdata_timer;
@@ -466,6 +471,23 @@ private:
 	float m_time_counter;
 	float m_time_of_day_send_timer;
 	
+	MutexedVariable<float> m_uptime;
+
+	enum PeerChangeType
+	{
+		PEER_ADDED,
+		PEER_REMOVED
+	};
+
+	struct PeerChange
+	{
+		PeerChangeType type;
+		u16 peer_id;
+		bool timeout;
+	};
+	
+	Queue<PeerChange> m_peer_change_queue;
+
 	friend class EmergeThread;
 	friend class RemoteClient;
 };
-- 
cgit v1.2.3