Skip to content
Snippets Groups Projects
server.cpp 126 KiB
Newer Older
		ScopeProfiler sp(g_profiler, "Server: selecting blocks for sending");
		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());

			// If definitions and textures have not been sent, don't
			// send MapBlocks either
			if(!client->definitions_sent)
				continue;

			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
		{
Perttu Ahola's avatar
Perttu Ahola committed
			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);
void Server::fillMediaCache()
	infostream<<"Server: Calculating media file checksums"<<std::endl;
	
	// Collect all media file paths
	std::list<std::string> paths;
	for(core::list<ModSpec>::Iterator i = m_mods.begin();
			i != m_mods.end(); i++){
		const ModSpec &mod = *i;
		paths.push_back(mod.path + DIR_DELIM + "textures");
		paths.push_back(mod.path + DIR_DELIM + "sounds");
		paths.push_back(mod.path + DIR_DELIM + "media");
		paths.push_back(mod.path + DIR_DELIM + "models");
	std::string path_all = "textures";
	paths.push_back(path_all + DIR_DELIM + "all");
	
	// Collect media file information from paths into cache
	for(std::list<std::string>::iterator i = paths.begin();
			i != paths.end(); i++)
	{
		std::string mediapath = *i;
		std::vector<fs::DirListNode> dirlist = fs::GetDirListing(mediapath);
		for(u32 j=0; j<dirlist.size(); j++){
			if(dirlist[j].dir) // Ignode dirs
				continue;
			std::string filename = dirlist[j].name;
			// If name contains illegal characters, ignore the file
			if(!string_allowed(filename, TEXTURENAME_ALLOWED_CHARS)){
				infostream<<"Server: ignoring illegal file name: \""
						<<filename<<"\""<<std::endl;
			// If name is not in a supported format, ignore it
			const char *supported_ext[] = {
				".png", ".jpg", ".bmp", ".tga",
				".pcx", ".ppm", ".psd", ".wal", ".rgb",
				".ogg",
				NULL
			};
			if(removeStringEnd(filename, supported_ext) == ""){
				infostream<<"Server: ignoring unsupported file extension: \""
						<<filename<<"\""<<std::endl;
				continue;
			}
			// Ok, attempt to load the file and add to cache
			std::string filepath = mediapath + DIR_DELIM + filename;
			// Read data
			std::ifstream fis(filepath.c_str(), std::ios_base::binary);
			if(fis.good() == false){
				errorstream<<"Server::fillMediaCache(): Could not open \""
						<<filename<<"\" for reading"<<std::endl;
				continue;
			}
			std::ostringstream tmp_os(std::ios_base::binary);
			bool bad = false;
			for(;;){
				char buf[1024];
				fis.read(buf, 1024);
				std::streamsize len = fis.gcount();
				tmp_os.write(buf, len);
				if(fis.eof())
					break;
				if(!fis.good()){
					bad = true;
					break;
				errorstream<<"Server::fillMediaCache(): Failed to read \""
						<<filename<<"\""<<std::endl;
				continue;
			}
			if(tmp_os.str().length() == 0){
				errorstream<<"Server::fillMediaCache(): Empty file \""
						<<filepath<<"\""<<std::endl;
			SHA1 sha1;
			sha1.addBytes(tmp_os.str().c_str(), tmp_os.str().length());
			unsigned char *digest = sha1.getDigest();
			std::string sha1_base64 = base64_encode(digest, 20);
			std::string sha1_hex = hex_encode((char*)digest, 20);
			free(digest);
			// Put in list
			this->m_media[filename] = MediaInfo(filepath, sha1_base64);
			verbosestream<<"Server: "<<sha1_hex<<" is "<<filename<<std::endl;
struct SendableMediaAnnouncement
{
	std::string name;
	std::string sha1_digest;
	SendableMediaAnnouncement(const std::string name_="",
			const std::string sha1_digest_=""):
		name(name_),
		sha1_digest(sha1_digest_)
	{}
};
void Server::sendMediaAnnouncement(u16 peer_id)
{
	verbosestream<<"Server: Announcing files to id("<<peer_id<<")"
			<<std::endl;
	core::list<SendableMediaAnnouncement> file_announcements;
	for(std::map<std::string, MediaInfo>::iterator i = m_media.begin();
			i != m_media.end(); i++){
		file_announcements.push_back(
				SendableMediaAnnouncement(i->first, i->second.sha1_digest));
	// Make packet
	std::ostringstream os(std::ios_base::binary);
		u32 number of files
		for each texture {
			u16 length of name
			string name
			u16 length of sha1_digest
	
	writeU16(os, TOCLIENT_ANNOUNCE_MEDIA);
	writeU16(os, file_announcements.size());
	for(core::list<SendableMediaAnnouncement>::Iterator
			j = file_announcements.begin();
			j != file_announcements.end(); j++){
		os<<serializeString(j->name);
		os<<serializeString(j->sha1_digest);
	}

	// 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);

}

struct SendableMedia
Perttu Ahola's avatar
Perttu Ahola committed
{
	std::string name;
	std::string path;
	std::string data;

	SendableMedia(const std::string &name_="", const std::string path_="",
Perttu Ahola's avatar
Perttu Ahola committed
			const std::string &data_=""):
		name(name_),
		path(path_),
		data(data_)
	{}
};

void Server::sendRequestedMedia(u16 peer_id,
		const core::list<MediaRequest> &tosend)
{
Perttu Ahola's avatar
Perttu Ahola committed
	DSTACK(__FUNCTION_NAME);

	verbosestream<<"Server::sendRequestedMedia(): "
			<<"Sending files to client"<<std::endl;
	/* Read files */
	// Put 5kB in one bunch (this is not accurate)
	u32 bytes_per_bunch = 5000;
	core::array< core::list<SendableMedia> > file_bunches;
	file_bunches.push_back(core::list<SendableMedia>());
	u32 file_size_bunch_total = 0;
	for(core::list<MediaRequest>::ConstIterator i = tosend.begin();
			i != tosend.end(); i++)
	{
		if(m_media.find(i->name) == m_media.end()){
			errorstream<<"Server::sendRequestedMedia(): Client asked for "
					<<"unknown file \""<<(i->name)<<"\""<<std::endl;
		std::string tpath = m_media[(*i).name].path;

		// Read data
		std::ifstream fis(tpath.c_str(), std::ios_base::binary);
		if(fis.good() == false){
			errorstream<<"Server::sendRequestedMedia(): Could not open \""
					<<tpath<<"\" for reading"<<std::endl;
			continue;
		}
		std::ostringstream tmp_os(std::ios_base::binary);
		bool bad = false;
		for(;;){
			char buf[1024];
			fis.read(buf, 1024);
			std::streamsize len = fis.gcount();
			tmp_os.write(buf, len);
			file_size_bunch_total += len;
			errorstream<<"Server::sendRequestedMedia(): Failed to read \""
		/*infostream<<"Server::sendRequestedMedia(): Loaded \""
		file_bunches[file_bunches.size()-1].push_back(
				SendableMedia((*i).name, tpath, tmp_os.str()));
		if(file_size_bunch_total >= bytes_per_bunch){
			file_bunches.push_back(core::list<SendableMedia>());
			file_size_bunch_total = 0;
	/* Create and send packets */
	u32 num_bunches = file_bunches.size();
	for(u32 i=0; i<num_bunches; i++)
	{
		std::ostringstream os(std::ios_base::binary);
		/*
			u16 command
			u16 total number of texture bunches
			u16 index of this bunch
			u32 number of files in this bunch
			for each file {
				u16 length of name
				string name
				u32 length of data
				data
		writeU16(os, TOCLIENT_MEDIA);
		writeU16(os, num_bunches);
		writeU16(os, i);
		writeU32(os, file_bunches[i].size());
		for(core::list<SendableMedia>::Iterator
				j = file_bunches[i].begin();
				j != file_bunches[i].end(); j++){
			os<<serializeString(j->name);
			os<<serializeLongString(j->data);
		}
		// Make data buffer
		std::string s = os.str();
		verbosestream<<"Server::sendRequestedMedia(): bunch "
				<<i<<"/"<<num_bunches
				<<" files="<<file_bunches[i].size()
				<<" size=" <<s.size()<<std::endl;
		SharedBuffer<u8> data((u8*)s.c_str(), s.size());
		// Send as reliable
		m_con.Send(peer_id, 0, data, true);
	}
Perttu Ahola's avatar
Perttu Ahola committed
void Server::sendDetachedInventory(const std::string &name, u16 peer_id)
{
	if(m_detached_inventories.count(name) == 0){
		errorstream<<__FUNCTION_NAME<<": \""<<name<<"\" not found"<<std::endl;
		return;
	}
	Inventory *inv = m_detached_inventories[name];

	std::ostringstream os(std::ios_base::binary);
	writeU16(os, TOCLIENT_DETACHED_INVENTORY);
	os<<serializeString(name);
	inv->serialize(os);

	// 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::sendDetachedInventoryToAll(const std::string &name)
{
	DSTACK(__FUNCTION_NAME);

	for(core::map<u16, RemoteClient*>::Iterator
			i = m_clients.getIterator();
			i.atEnd() == false; i++){
		RemoteClient *client = i.getNode()->getValue();
		sendDetachedInventory(name, client->peer_id);
	}
}

void Server::sendDetachedInventories(u16 peer_id)
{
	DSTACK(__FUNCTION_NAME);

	for(std::map<std::string, Inventory*>::iterator
			i = m_detached_inventories.begin();
			i != m_detached_inventories.end(); i++){
		const std::string &name = i->first;
		//Inventory *inv = i->second;
		sendDetachedInventory(name, peer_id);
	}
}

void Server::DiePlayer(u16 peer_id)
	DSTACK(__FUNCTION_NAME);
	
	PlayerSAO *playersao = getPlayerSAO(peer_id);
	assert(playersao);
	infostream<<"Server::DiePlayer(): Player "
			<<playersao->getPlayer()->getName()
			<<" dies"<<std::endl;

	playersao->setHP(0);

	// Trigger scripted stuff
	scriptapi_on_dieplayer(m_lua, playersao);
	SendPlayerHP(peer_id);
	SendDeathscreen(m_con, peer_id, false, v3f(0,0,0));
void Server::RespawnPlayer(u16 peer_id)
	DSTACK(__FUNCTION_NAME);

	PlayerSAO *playersao = getPlayerSAO(peer_id);
	assert(playersao);

	infostream<<"Server::RespawnPlayer(): Player "
			<<playersao->getPlayer()->getName()
			<<" respawns"<<std::endl;

	playersao->setHP(PLAYER_MAX_HP);

	bool repositioned = scriptapi_on_respawnplayer(m_lua, playersao);
	if(!repositioned){
		v3f pos = findSpawnPos(m_env->getServerMap());
		playersao->setPos(pos);
void Server::UpdateCrafting(u16 peer_id)
{
	DSTACK(__FUNCTION_NAME);
	
Perttu Ahola's avatar
Perttu Ahola committed
	Player* player = m_env->getPlayer(peer_id);
	// Get a preview for crafting
	ItemStack preview;
	getCraftingResult(&player->inventory, preview, false, this);

	// Put the new preview in
	InventoryList *plist = player->inventory.getList("craftpreview");
	assert(plist);
	assert(plist->getSize() >= 1);
	plist->changeItem(0, preview);
Perttu Ahola's avatar
Perttu Ahola committed
}

RemoteClient* Server::getClient(u16 peer_id)
{
	DSTACK(__FUNCTION_NAME);
	//JMutexAutoLock lock(m_con_mutex);
	core::map<u16, RemoteClient*>::Node *n;
	n = m_clients.find(peer_id);
	// A client should exist for all peers
	assert(n != NULL);
	return n->getValue();
}

std::wstring Server::getStatusString()
{
	std::wostringstream os(std::ios_base::binary);
	os<<L"# Server: ";
	// Version
	os<<L"version="<<narrow_to_wide(VERSION_STRING);
	os<<L", uptime="<<m_uptime.get();
	core::map<u16, RemoteClient*>::Iterator i;
	bool first;
	for(i = m_clients.getIterator(), first = true;
		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 player
Perttu Ahola's avatar
Perttu Ahola committed
		Player *player = m_env->getPlayer(client->peer_id);
		// Get name of player
		std::wstring name = L"unknown";
		if(player != NULL)
			name = narrow_to_wide(player->getName());
		// Add name to information string
		if(!first)
			os<<L",";
		else
			first = false;
		os<<name;
Perttu Ahola's avatar
Perttu Ahola committed
	if(((ServerMap*)(&m_env->getMap()))->isSavingEnabled() == false)
		os<<std::endl<<L"# Server: "<<" WARNING: Map saving is disabled.";
	if(g_settings->get("motd") != "")
		os<<std::endl<<L"# Server: "<<narrow_to_wide(g_settings->get("motd"));
std::set<std::string> Server::getPlayerEffectivePrivs(const std::string &name)
	std::set<std::string> privs;
	scriptapi_get_auth(m_lua, name, NULL, &privs);
	return privs;
bool Server::checkPriv(const std::string &name, const std::string &priv)
	std::set<std::string> privs = getPlayerEffectivePrivs(name);
	return (privs.count(priv) != 0);
void Server::reportPrivsModified(const std::string &name)
{
	if(name == ""){
		for(core::map<u16, RemoteClient*>::Iterator
				i = m_clients.getIterator();
				i.atEnd() == false; i++){
			RemoteClient *client = i.getNode()->getValue();
			Player *player = m_env->getPlayer(client->peer_id);
			reportPrivsModified(player->getName());
		}
	} else {
		Player *player = m_env->getPlayer(name.c_str());
		if(!player)
			return;
		SendPlayerPrivileges(player->peer_id);
		PlayerSAO *sao = player->getPlayerSAO();
		if(!sao)
			return;
		sao->updatePrivileges(
				getPlayerEffectivePrivs(name),
				isSingleplayer());
void Server::reportInventoryFormspecModified(const std::string &name)
{
	Player *player = m_env->getPlayer(name.c_str());
	if(!player)
		return;
	SendPlayerInventoryFormspec(player->peer_id);
}

// Saves g_settings to configpath given at initialization
void Server::saveConfig()
{
	if(m_path_config != "")
		g_settings->updateConfigFile(m_path_config.c_str());
void Server::notifyPlayer(const char *name, const std::wstring msg)
{
Perttu Ahola's avatar
Perttu Ahola committed
	Player *player = m_env->getPlayer(name);
	if(!player)
		return;
	SendChatMessage(player->peer_id, std::wstring(L"Server: -!- ")+msg);
}

Perttu Ahola's avatar
Perttu Ahola committed
void Server::notifyPlayers(const std::wstring msg)
{
	BroadcastChatMessage(msg);
}

void Server::queueBlockEmerge(v3s16 blockpos, bool allow_generate)
{
	u8 flags = 0;
	if(!allow_generate)
		flags |= BLOCK_EMERGE_FLAG_FROMDISK;
	m_emerge_queue.addBlock(PEER_ID_INEXISTENT, blockpos, flags);
}

Perttu Ahola's avatar
Perttu Ahola committed
Inventory* Server::createDetachedInventory(const std::string &name)
{
	if(m_detached_inventories.count(name) > 0){
		infostream<<"Server clearing detached inventory \""<<name<<"\""<<std::endl;
		delete m_detached_inventories[name];
	} else {
		infostream<<"Server creating detached inventory \""<<name<<"\""<<std::endl;
	}
	Inventory *inv = new Inventory(m_itemdef);
	assert(inv);
	m_detached_inventories[name] = inv;
	sendDetachedInventoryToAll(name);
	return inv;
}

class BoolScopeSet
{
public:
	BoolScopeSet(bool *dst, bool val):
		m_dst(dst)
	{
		m_orig_state = *m_dst;
		*m_dst = val;
	}
	~BoolScopeSet()
	{
		*m_dst = m_orig_state;
	}
private:
	bool *m_dst;
	bool m_orig_state;
};

// actions: time-reversed list
// Return value: success/failure
bool Server::rollbackRevertActions(const std::list<RollbackAction> &actions,
		std::list<std::string> *log)
{
	infostream<<"Server::rollbackRevertActions(len="<<actions.size()<<")"<<std::endl;
	ServerMap *map = (ServerMap*)(&m_env->getMap());
	// Disable rollback report sink while reverting
	BoolScopeSet rollback_scope_disable(&m_rollback_sink_enabled, false);
	
	// Fail if no actions to handle
	if(actions.empty()){
		log->push_back("Nothing to do.");
		return false;
	}

	int num_tried = 0;
	int num_failed = 0;
	
	for(std::list<RollbackAction>::const_iterator
			i = actions.begin();
			i != actions.end(); i++)
	{
		const RollbackAction &action = *i;
		num_tried++;
		bool success = action.applyRevert(map, this, this);
		if(!success){
			num_failed++;
			std::ostringstream os;
			os<<"Revert of step ("<<num_tried<<") "<<action.toString()<<" failed";
			infostream<<"Map::rollbackRevertActions(): "<<os.str()<<std::endl;
			if(log)
				log->push_back(os.str());
		}else{
			std::ostringstream os;
			os<<"Succesfully reverted step ("<<num_tried<<") "<<action.toString();
			infostream<<"Map::rollbackRevertActions(): "<<os.str()<<std::endl;
			if(log)
				log->push_back(os.str());
		}
	}
	
	infostream<<"Map::rollbackRevertActions(): "<<num_failed<<"/"<<num_tried
			<<" failed"<<std::endl;

	// Call it done if less than half failed
	return num_failed <= num_tried/2;
}

Perttu Ahola's avatar
Perttu Ahola committed
// IGameDef interface
// Under envlock
IItemDefManager* Server::getItemDefManager()
Perttu Ahola's avatar
Perttu Ahola committed
{
Perttu Ahola's avatar
Perttu Ahola committed
}
INodeDefManager* Server::getNodeDefManager()
{
Perttu Ahola's avatar
Perttu Ahola committed
}
ICraftDefManager* Server::getCraftDefManager()
{
	return m_craftdef;
}
Perttu Ahola's avatar
Perttu Ahola committed
ITextureSource* Server::getTextureSource()
{
	return NULL;
}
Perttu Ahola's avatar
Perttu Ahola committed
u16 Server::allocateUnknownNodeId(const std::string &name)
{
Perttu Ahola's avatar
Perttu Ahola committed
}
ISoundManager* Server::getSoundManager()
{
	return &dummySoundManager;
}
MtEventManager* Server::getEventManager()
{
	return m_event;
}
IRollbackReportSink* Server::getRollbackReportSink()
{
	if(!m_enable_rollback_recording)
		return NULL;
	if(!m_rollback_sink_enabled)
		return NULL;
	return m_rollback;
}
Perttu Ahola's avatar
Perttu Ahola committed

IWritableItemDefManager* Server::getWritableItemDefManager()
}
IWritableNodeDefManager* Server::getWritableNodeDefManager()
{
IWritableCraftDefManager* Server::getWritableCraftDefManager()
{
	return m_craftdef;
}
const ModSpec* Server::getModSpec(const std::string &modname)
{
	for(core::list<ModSpec>::Iterator i = m_mods.begin();
			i != m_mods.end(); i++){
		const ModSpec &mod = *i;
		if(mod.name == modname)
			return &mod;
	}
	return NULL;
}
void Server::getModNames(core::list<std::string> &modlist)
{
	for(core::list<ModSpec>::Iterator i = m_mods.begin(); i != m_mods.end(); i++)
	{
		modlist.push_back((*i).name);
	}
}
std::string Server::getBuiltinLuaPath()
{
	return porting::path_share + DIR_DELIM + "builtin";
}
	// 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
		v2s16 nodepos2d = v2s16(-range + (myrand()%(range*2)),
		//v2s16 sectorpos = getNodeSectorPos(nodepos2d);
		// Get ground height at point (fallbacks to heightmap function)
		s16 groundheight = map.findGroundLevel(nodepos2d);
		// Don't go underwater
		if(groundheight < WATER_LEVEL)
		{
			//infostream<<"-> Underwater"<<std::endl;
			continue;
		}
		// Don't go to high places
		if(groundheight > WATER_LEVEL + 4)
		{
			//infostream<<"-> Underwater"<<std::endl;
		
		nodepos = v3s16(nodepos2d.X, groundheight-2, nodepos2d.Y);
		bool is_good = false;
		s32 air_count = 0;
		for(s32 i=0; i<10; i++){
			v3s16 blockpos = getNodeBlockPos(nodepos);
			map.emergeBlock(blockpos, true);
			MapNode n = map.getNodeNoEx(nodepos);
			if(n.getContent() == CONTENT_AIR){
				air_count++;
				if(air_count >= 2){
					is_good = true;
					nodepos.Y -= 1;
					break;
				}
			}
			nodepos.Y++;
		}
		if(is_good){
			// Found a good place
			//infostream<<"Searched through "<<i<<" places."<<std::endl;
			break;
		}
	return intToFloat(nodepos, BS);
PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id)
	RemotePlayer *player = NULL;
	bool newplayer = false;

	player = static_cast<RemotePlayer*>(m_env->getPlayer(name));
	// If player is already connected, cancel
	if(player != NULL && player->peer_id != 0)
	{
		infostream<<"emergePlayer(): Player already connected"<<std::endl;
		return NULL;

	/*
		If player with the wanted peer_id already exists, cancel.
	*/
Perttu Ahola's avatar
Perttu Ahola committed
	if(m_env->getPlayer(peer_id) != NULL)
		infostream<<"emergePlayer(): Player with wrong name but same"
				" peer_id already exists"<<std::endl;
		return NULL;
	}
		Create a new player if it doesn't exist yet
		newplayer = true;
		player = new RemotePlayer(this);
		player->updateName(name);

		/* Set player position */
		infostream<<"Server: Finding spawn place for player \""
				<<name<<"\""<<std::endl;
Perttu Ahola's avatar
Perttu Ahola committed
		v3f pos = findSpawnPos(m_env->getServerMap());
		player->setPosition(pos);
		/* Add player to environment */
Perttu Ahola's avatar
Perttu Ahola committed
		m_env->addPlayer(player);
	/*
		Create a new player active object
	*/
	PlayerSAO *playersao = new PlayerSAO(m_env, player, peer_id,
			getPlayerEffectivePrivs(player->getName()),
			isSingleplayer());
	/* Add object to environment */
	m_env->addActiveObject(playersao);
	/* Run scripts */
	if(newplayer)
		scriptapi_on_newplayer(m_lua, playersao);

	scriptapi_on_joinplayer(m_lua, playersao);

void Server::handlePeerChange(PeerChange &c)
{
	JMutexAutoLock envlock(m_env_mutex);
	JMutexAutoLock conlock(m_con_mutex);
Perttu Ahola's avatar
Perttu Ahola committed
	
	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);

	} // 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);
		
		/*
			Mark objects to be not known by the client
		*/
		RemoteClient *client = n->getValue();
		// Handle objects
		for(core::map<u16, bool>::Iterator
				i = client->m_known_objects.getIterator();
				i.atEnd()==false; i++)
		{
			// Get object
			u16 id = i.getNode()->getKey();
Perttu Ahola's avatar
Perttu Ahola committed
			ServerActiveObject* obj = m_env->getActiveObject(id);
			
			if(obj && obj->m_known_by_count > 0)
				obj->m_known_by_count--;
		}

Perttu Ahola's avatar
Perttu Ahola committed
		/*
			Clear references to playing sounds
		*/
		for(std::map<s32, ServerPlayingSound>::iterator
				i = m_playing_sounds.begin();
				i != m_playing_sounds.end();)
		{
			ServerPlayingSound &psound = i->second;
			psound.clients.erase(c.peer_id);
			if(psound.clients.size() == 0)
				m_playing_sounds.erase(i++);
			else
				i++;
		}

		Player *player = m_env->getPlayer(c.peer_id);
Perttu Ahola's avatar
Perttu Ahola committed

		// Collect information about leaving in chat
		std::wstring message;
		{
			if(player != NULL)
			{
				std::wstring name = narrow_to_wide(player->getName());
				message += L"*** ";
				message += name;
				if(c.timeout)
					message += L" (timed out)";
			}
Perttu Ahola's avatar
Perttu Ahola committed
		
		/* Run scripts and remove from environment */
		{
			if(player != NULL)
			{
				PlayerSAO *playersao = player->getPlayerSAO();
				assert(playersao);

				scriptapi_on_leaveplayer(m_lua, playersao);

				playersao->disconnected();
			}
		}

Perttu Ahola's avatar
Perttu Ahola committed
		/*
			Print out action
		*/
			{
				std::ostringstream os(std::ios_base::binary);
				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());
					if(client->serialization_version == SER_FMT_VER_INVALID)
						continue;
					// Get player
Perttu Ahola's avatar
Perttu Ahola committed
					Player *player = m_env->getPlayer(client->peer_id);
					if(!player)
						continue;
					// Get name of player
					os<<player->getName()<<" ";
				}

				actionstream<<player->getName()<<" "
						<<(c.timeout?"times out.":"leaves game.")
						<<" List of players: "
		}
		
		// Delete client
		delete m_clients[c.peer_id];
		m_clients.remove(c.peer_id);

		// Send player info to all remaining clients
		
		// Send leave chat message to all remaining clients
		if(message.length() != 0)
			BroadcastChatMessage(message);