Skip to content
Snippets Groups Projects
server.cpp 126 KiB
Newer Older
			0: start digging or punch object
		*/
		if(action == 0)
		{
			if(pointed.type == POINTEDTHING_NODE)
Perttu Ahola's avatar
Perttu Ahola committed
			{
Perttu Ahola's avatar
Perttu Ahola committed
				/*
					NOTE: This can be used in the future to check if
					somebody is cheating, by checking the timing.
Perttu Ahola's avatar
Perttu Ahola committed
				*/
				MapNode n(CONTENT_IGNORE);
				try
Perttu Ahola's avatar
Perttu Ahola committed
				{
					n = m_env->getMap().getNode(p_under);
				catch(InvalidPositionException &e)
					infostream<<"Server: Not punching: Node not found."
							<<" Adding block to emerge queue."
							<<std::endl;
					m_emerge_queue.addBlock(peer_id,
							getNodeBlockPos(p_above), BLOCK_EMERGE_FLAG_FROMDISK);
Perttu Ahola's avatar
Perttu Ahola committed
				}
				if(n.getContent() != CONTENT_IGNORE)
					scriptapi_node_on_punch(m_lua, p_under, n, playersao);
				// Cheat prevention
				playersao->noCheatDigStart(p_under);
			}
			else if(pointed.type == POINTEDTHING_OBJECT)
			{
				// Skip if object has been removed
				if(pointed_object->m_removed)
					return;
Perttu Ahola's avatar
Perttu Ahola committed

				actionstream<<player->getName()<<" punches object "
						<<pointed.object_id<<": "
						<<pointed_object->getDescription()<<std::endl;
				ItemStack punchitem = playersao->getWieldedItem();
				ToolCapabilities toolcap =
						punchitem.getToolCapabilities(m_itemdef);
				v3f dir = (pointed_object->getBasePosition() -
						(player->getPosition() + player->getEyeOffset())
							).normalize();
				float time_from_last_punch =
					playersao->resetTimeFromLastPunch();
				pointed_object->punch(dir, &toolcap, playersao,
						time_from_last_punch);
Perttu Ahola's avatar
Perttu Ahola committed
			}

		} // action == 0
Perttu Ahola's avatar
Perttu Ahola committed
		/*
			1: stop digging
Perttu Ahola's avatar
Perttu Ahola committed
		*/
		else if(action == 1)
Perttu Ahola's avatar
Perttu Ahola committed
		{
		} // action == 1

			2: Digging completed
		else if(action == 2)
			if(pointed.type == POINTEDTHING_NODE)
					n = m_env->getMap().getNode(p_under);
				catch(InvalidPositionException &e)
					infostream<<"Server: Not finishing digging: Node not found."
							<<" Adding block to emerge queue."
							<<std::endl;
					m_emerge_queue.addBlock(peer_id,
							getNodeBlockPos(p_above), BLOCK_EMERGE_FLAG_FROMDISK);

				/* Cheat prevention */
				bool is_valid_dig = true;
				if(!isSingleplayer() && !g_settings->getBool("disable_anticheat"))
				{
					v3s16 nocheat_p = playersao->getNoCheatDigPos();
					float nocheat_t = playersao->getNoCheatDigTime();
					playersao->noCheatDigEnd();
					// If player didn't start digging this, ignore dig
					if(nocheat_p != p_under){
						infostream<<"Server: NoCheat: "<<player->getName()
								<<" started digging "
								<<PP(nocheat_p)<<" and completed digging "
								<<PP(p_under)<<"; not digging."<<std::endl;
						is_valid_dig = false;
					}
					// Get player's wielded item
					ItemStack playeritem;
					InventoryList *mlist = playersao->getInventory()->getList("main");
					if(mlist != NULL)
						playeritem = mlist->getItem(playersao->getWieldIndex());
					ToolCapabilities playeritem_toolcap =
							playeritem.getToolCapabilities(m_itemdef);
					// Get diggability and expected digging time
					DigParams params = getDigParams(m_nodedef->get(n).groups,
							&playeritem_toolcap);
					// If can't dig, try hand
					if(!params.diggable){
						const ItemDefinition &hand = m_itemdef->get("");
						const ToolCapabilities *tp = hand.tool_capabilities;
						if(tp)
							params = getDigParams(m_nodedef->get(n).groups, tp);
					}
					// If can't dig, ignore dig
					if(!params.diggable){
						infostream<<"Server: NoCheat: "<<player->getName()
								<<" completed digging "<<PP(p_under)
								<<", which is not diggable with tool. not digging."
								<<std::endl;
						is_valid_dig = false;
					}
					// If time is considerably too short, ignore dig
					// Check time only for medium and slow timed digs
					if(params.diggable && params.time > 0.3 && nocheat_t < 0.5 * params.time){
						infostream<<"Server: NoCheat: "<<player->getName()
								<<" completed digging "
								<<PP(p_under)<<" in "<<nocheat_t<<"s; expected "
								<<params.time<<"s; not digging."<<std::endl;
						is_valid_dig = false;
					}
				}

				/* Actually dig node */

				if(is_valid_dig && n.getContent() != CONTENT_IGNORE)
					scriptapi_node_on_dig(m_lua, p_under, n, playersao);
				// Send unusual result (that is, node not being removed)
				if(m_env->getMap().getNodeNoEx(p_under).getContent() != CONTENT_AIR)
				{
					// Re-send block to revert change on client-side
					RemoteClient *client = getClient(peer_id);
					v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
					client->SetBlockNotSent(blockpos);
				}
			}
		} // action == 2
		
			3: place block or right-click object
		else if(action == 3)
			ItemStack item = playersao->getWieldedItem();
			// Reset build time counter
			if(pointed.type == POINTEDTHING_NODE &&
					item.getDefinition(m_itemdef).type == ITEM_NODE)
				getClient(peer_id)->m_time_from_building = 0.0;

			if(pointed.type == POINTEDTHING_OBJECT)
			{
				// Right click object
				// Skip if object has been removed
				if(pointed_object->m_removed)
					return;
				actionstream<<player->getName()<<" right-clicks object "
						<<pointed.object_id<<": "
						<<pointed_object->getDescription()<<std::endl;
				pointed_object->rightClick(playersao);
			}
			else if(scriptapi_item_on_place(m_lua,
					item, playersao, pointed))
				playersao->setWieldedItem(item);
			
			// If item has node placement prediction, always send the above
			// node to make sure the client knows what exactly happened
			if(item.getDefinition(m_itemdef).node_placement_prediction != ""){
				RemoteClient *client = getClient(peer_id);
				v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_above, BS));
				client->SetBlockNotSent(blockpos);
			}
		} // action == 3

			4: use
		else if(action == 4)
			ItemStack item = playersao->getWieldedItem();

			actionstream<<player->getName()<<" uses "<<item.name
					<<", pointing at "<<pointed.dump()<<std::endl;
					item, playersao, pointed))
				playersao->setWieldedItem(item);
		} // action == 4

		/*
			Catch invalid actions
		*/
		else
			infostream<<"WARNING: Server: Invalid action "
					<<action<<std::endl;
Perttu Ahola's avatar
Perttu Ahola committed
	else if(command == TOSERVER_REMOVED_SOUNDS)
	{
		std::string datastring((char*)&data[2], datasize-2);
		std::istringstream is(datastring, std::ios_base::binary);

		int num = readU16(is);
		for(int k=0; k<num; k++){
			s32 id = readS32(is);
			std::map<s32, ServerPlayingSound>::iterator i =
					m_playing_sounds.find(id);
			if(i == m_playing_sounds.end())
				continue;
			ServerPlayingSound &psound = i->second;
			psound.clients.erase(peer_id);
			if(psound.clients.size() == 0)
				m_playing_sounds.erase(i++);
		}
	}
	else if(command == TOSERVER_NODEMETA_FIELDS)
	{
		std::string datastring((char*)&data[2], datasize-2);
		std::istringstream is(datastring, std::ios_base::binary);
		
		v3s16 p = readV3S16(is);
		std::string formname = deSerializeString(is);
		int num = readU16(is);
		std::map<std::string, std::string> fields;
		for(int k=0; k<num; k++){
			std::string fieldname = deSerializeString(is);
			std::string fieldvalue = deSerializeLongString(is);
			fields[fieldname] = fieldvalue;
		}

		// If something goes wrong, this player is to blame
		RollbackScopeActor rollback_scope(m_rollback,
				std::string("player:")+player->getName());

		// Check the target node for rollback data; leave others unnoticed
		RollbackNode rn_old(&m_env->getMap(), p, this);

		scriptapi_node_on_receive_fields(m_lua, p, formname, fields,
				playersao);

		// Report rollback data
		RollbackNode rn_new(&m_env->getMap(), p, this);
		if(rollback() && rn_new != rn_old){
			RollbackAction action;
			action.setSetNode(p, rn_old, rn_new);
			rollback()->reportAction(action);
		}
	else if(command == TOSERVER_INVENTORY_FIELDS)
	{
		std::string datastring((char*)&data[2], datasize-2);
		std::istringstream is(datastring, std::ios_base::binary);
		
		std::string formname = deSerializeString(is);
		int num = readU16(is);
		std::map<std::string, std::string> fields;
		for(int k=0; k<num; k++){
			std::string fieldname = deSerializeString(is);
			std::string fieldvalue = deSerializeLongString(is);
			fields[fieldname] = fieldvalue;
		}

		scriptapi_on_player_receive_fields(m_lua, playersao, formname, fields);
	}
Perttu Ahola's avatar
Perttu Ahola committed
	else
	{
		infostream<<"Server::ProcessData(): Ignoring "
Perttu Ahola's avatar
Perttu Ahola committed
				"unknown command "<<command<<std::endl;
	}
	
	} //try
	catch(SendFailedException &e)
	{
		errorstream<<"Server::ProcessData(): SendFailedException: "
Perttu Ahola's avatar
Perttu Ahola committed
				<<"what="<<e.what()
				<<std::endl;
	}
}

void Server::onMapEditEvent(MapEditEvent *event)
Perttu Ahola's avatar
Perttu Ahola committed
{
	//infostream<<"Server::onMapEditEvent()"<<std::endl;
	if(m_ignore_map_edit_events)
		return;
	if(m_ignore_map_edit_events_area.contains(event->getArea()))
		return;
	MapEditEvent *e = event->clone();
	m_unsent_map_edit_queue.push_back(e);
Perttu Ahola's avatar
Perttu Ahola committed
}

Inventory* Server::getInventory(const InventoryLocation &loc)
{
	switch(loc.type){
	case InventoryLocation::UNDEFINED:
	{}
	break;
	case InventoryLocation::CURRENT_PLAYER:
	{}
	break;
	case InventoryLocation::PLAYER:
	{
		Player *player = m_env->getPlayer(loc.name.c_str());
		if(!player)
			return NULL;
		PlayerSAO *playersao = player->getPlayerSAO();
		if(!playersao)
			return NULL;
		return playersao->getInventory();
	}
	break;
	case InventoryLocation::NODEMETA:
	{
		NodeMetadata *meta = m_env->getMap().getNodeMetadata(loc.p);
		if(!meta)
			return NULL;
		return meta->getInventory();
	}
	break;
Perttu Ahola's avatar
Perttu Ahola committed
	case InventoryLocation::DETACHED:
	{
		if(m_detached_inventories.count(loc.name) == 0)
			return NULL;
		return m_detached_inventories[loc.name];
	}
	break;
	default:
		assert(0);
	}
	return NULL;
}
void Server::setInventoryModified(const InventoryLocation &loc)
{
	switch(loc.type){
	case InventoryLocation::UNDEFINED:
	{}
	break;
	case InventoryLocation::PLAYER:
	{
		Player *player = m_env->getPlayer(loc.name.c_str());
		if(!player)
			return;
		PlayerSAO *playersao = player->getPlayerSAO();
		if(!playersao)
		playersao->m_inventory_not_sent = true;
		playersao->m_wielded_item_not_sent = true;
	}
	break;
	case InventoryLocation::NODEMETA:
	{
		v3s16 blockpos = getNodeBlockPos(loc.p);

		MapBlock *block = m_env->getMap().getBlockNoCreateNoEx(blockpos);
		if(block)
			block->raiseModified(MOD_STATE_WRITE_NEEDED);
		
		setBlockNotSent(blockpos);
	}
	break;
Perttu Ahola's avatar
Perttu Ahola committed
	case InventoryLocation::DETACHED:
	{
		sendDetachedInventoryToAll(loc.name);
	}
	break;
Perttu Ahola's avatar
Perttu Ahola committed

Perttu Ahola's avatar
Perttu Ahola committed
core::list<PlayerInfo> Server::getPlayerInfo()
{
	DSTACK(__FUNCTION_NAME);
	JMutexAutoLock envlock(m_env_mutex);
	JMutexAutoLock conlock(m_con_mutex);
	
	core::list<PlayerInfo> list;

Perttu Ahola's avatar
Perttu Ahola committed
	core::list<Player*> players = m_env->getPlayers();
Perttu Ahola's avatar
Perttu Ahola committed
	
	core::list<Player*>::Iterator i;
	for(i = players.begin();
			i != players.end(); i++)
	{
		PlayerInfo info;

		Player *player = *i;
Perttu Ahola's avatar
Perttu Ahola committed
		try{
			// Copy info from connection to info struct
			info.id = player->peer_id;
			info.address = m_con.GetPeerAddress(player->peer_id);
			info.avg_rtt = m_con.GetPeerAvgRTT(player->peer_id);
Perttu Ahola's avatar
Perttu Ahola committed
		}
		catch(con::PeerNotFoundException &e)
		{
Perttu Ahola's avatar
Perttu Ahola committed
			info.id = 0;
			info.address = Address(0,0,0,0,0);
			info.avg_rtt = 0.0;
		}

		snprintf(info.name, PLAYERNAME_SIZE, "%s", player->getName());
		info.position = player->getPosition();

		list.push_back(info);
	}

	return list;
}

Perttu Ahola's avatar
Perttu Ahola committed
void Server::peerAdded(con::Peer *peer)
{
	DSTACK(__FUNCTION_NAME);
	verbosestream<<"Server::peerAdded(): peer->id="
Perttu Ahola's avatar
Perttu Ahola committed
			<<peer->id<<std::endl;
	
	PeerChange c;
	c.type = PEER_ADDED;
	c.peer_id = peer->id;
	c.timeout = false;
	m_peer_change_queue.push_back(c);
Perttu Ahola's avatar
Perttu Ahola committed
}

void Server::deletingPeer(con::Peer *peer, bool timeout)
{
	DSTACK(__FUNCTION_NAME);
	verbosestream<<"Server::deletingPeer(): peer->id="
Perttu Ahola's avatar
Perttu Ahola committed
			<<peer->id<<", timeout="<<timeout<<std::endl;
	
	PeerChange c;
	c.type = PEER_REMOVED;
	c.peer_id = peer->id;
	c.timeout = timeout;
	m_peer_change_queue.push_back(c);
Perttu Ahola's avatar
Perttu Ahola committed
}

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

void Server::SendAccessDenied(con::Connection &con, u16 peer_id,
		const std::wstring &reason)
{
	DSTACK(__FUNCTION_NAME);
	std::ostringstream os(std::ios_base::binary);

	writeU16(os, TOCLIENT_ACCESS_DENIED);
	os<<serializeWideString(reason);

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

void Server::SendDeathscreen(con::Connection &con, u16 peer_id,
		bool set_camera_point_target, v3f camera_point_target)
{
	DSTACK(__FUNCTION_NAME);
	std::ostringstream os(std::ios_base::binary);

	writeU16(os, TOCLIENT_DEATHSCREEN);
	writeU8(os, set_camera_point_target);
	writeV3F1000(os, camera_point_target);

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

void Server::SendItemDef(con::Connection &con, u16 peer_id,
		IItemDefManager *itemdef)
{
	DSTACK(__FUNCTION_NAME);
	std::ostringstream os(std::ios_base::binary);

	/*
		u16 command
		u32 length of the next item
		zlib-compressed serialized ItemDefManager
	std::ostringstream tmp_os(std::ios::binary);
	itemdef->serialize(tmp_os);
	std::ostringstream tmp_os2(std::ios::binary);
	compressZlib(tmp_os.str(), tmp_os2);
	os<<serializeLongString(tmp_os2.str());

	// Make data buffer
	std::string s = os.str();
	verbosestream<<"Server: Sending item definitions to id("<<peer_id
			<<"): size="<<s.size()<<std::endl;
Perttu Ahola's avatar
Perttu Ahola committed
	SharedBuffer<u8> data((u8*)s.c_str(), s.size());
	// Send as reliable
	con.Send(peer_id, 0, data, true);
}

void Server::SendNodeDef(con::Connection &con, u16 peer_id,
		INodeDefManager *nodedef, u16 protocol_version)
Perttu Ahola's avatar
Perttu Ahola committed
{
	DSTACK(__FUNCTION_NAME);
	std::ostringstream os(std::ios_base::binary);

	/*
		u16 command
		u32 length of the next item
		zlib-compressed serialized NodeDefManager
Perttu Ahola's avatar
Perttu Ahola committed
	*/
	writeU16(os, TOCLIENT_NODEDEF);
	std::ostringstream tmp_os(std::ios::binary);
	nodedef->serialize(tmp_os, protocol_version);
	std::ostringstream tmp_os2(std::ios::binary);
	compressZlib(tmp_os.str(), tmp_os2);
	os<<serializeLongString(tmp_os2.str());
Perttu Ahola's avatar
Perttu Ahola committed

	// Make data buffer
	std::string s = os.str();
	verbosestream<<"Server: Sending node definitions to id("<<peer_id
			<<"): size="<<s.size()<<std::endl;
	SharedBuffer<u8> data((u8*)s.c_str(), s.size());
	// Send as reliable
	con.Send(peer_id, 0, data, true);
}

Perttu Ahola's avatar
Perttu Ahola committed
void Server::SendInventory(u16 peer_id)
{
	DSTACK(__FUNCTION_NAME);
	
	PlayerSAO *playersao = getPlayerSAO(peer_id);
	assert(playersao);
Perttu Ahola's avatar
Perttu Ahola committed

	playersao->m_inventory_not_sent = false;
Perttu Ahola's avatar
Perttu Ahola committed
	/*
Perttu Ahola's avatar
Perttu Ahola committed
	*/
	playersao->getInventory()->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++)
Perttu Ahola's avatar
Perttu Ahola committed
	{
		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;
void Server::SendPlayerHP(u16 peer_id)
	DSTACK(__FUNCTION_NAME);
	PlayerSAO *playersao = getPlayerSAO(peer_id);
	assert(playersao);
	playersao->m_hp_not_sent = false;
	SendHP(m_con, peer_id, playersao->getHP());
void Server::SendMovePlayer(u16 peer_id)
	Player *player = m_env->getPlayer(peer_id);
	assert(player);
	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();
		verbosestream<<"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(peer_id, 0, data, true);
void Server::SendPlayerPrivileges(u16 peer_id)
{
	Player *player = m_env->getPlayer(peer_id);
	assert(player);
	if(player->peer_id == PEER_ID_INEXISTENT)
		return;

	std::set<std::string> privs;
	scriptapi_get_auth(m_lua, player->getName(), NULL, &privs);
	
	std::ostringstream os(std::ios_base::binary);
	writeU16(os, TOCLIENT_PRIVILEGES);
	writeU16(os, privs.size());
	for(std::set<std::string>::const_iterator i = privs.begin();
			i != privs.end(); i++){
		os<<serializeString(*i);
	}

	// 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::SendPlayerInventoryFormspec(u16 peer_id)
{
	Player *player = m_env->getPlayer(peer_id);
	assert(player);
	if(player->peer_id == PEER_ID_INEXISTENT)
		return;

	std::ostringstream os(std::ios_base::binary);
	writeU16(os, TOCLIENT_INVENTORY_FORMSPEC);
	os<<serializeLongString(player->inventory_formspec);

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

Perttu Ahola's avatar
Perttu Ahola committed
s32 Server::playSound(const SimpleSoundSpec &spec,
		const ServerSoundParams &params)
{
	// Find out initial position of sound
	bool pos_exists = false;
	v3f pos = params.getPos(m_env, &pos_exists);
	// If position is not found while it should be, cancel sound
	if(pos_exists != (params.type != ServerSoundParams::SSP_LOCAL))
		return -1;
	// Filter destination clients
	std::set<RemoteClient*> dst_clients;
	if(params.to_player != "")
	{
		Player *player = m_env->getPlayer(params.to_player.c_str());
		if(!player){
			infostream<<"Server::playSound: Player \""<<params.to_player
					<<"\" not found"<<std::endl;
			return -1;
		}
		if(player->peer_id == PEER_ID_INEXISTENT){
			infostream<<"Server::playSound: Player \""<<params.to_player
					<<"\" not connected"<<std::endl;
			return -1;
		}
		RemoteClient *client = getClient(player->peer_id);
		dst_clients.insert(client);
	}
	else
	{
		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);
			if(!player)
				continue;
			if(pos_exists){
				if(player->getPosition().getDistanceFrom(pos) >
						params.max_hear_distance)
					continue;
			}
			dst_clients.insert(client);
		}
	}
	if(dst_clients.size() == 0)
		return -1;
	// Create the sound
	s32 id = m_next_sound_id++;
	// The sound will exist as a reference in m_playing_sounds
	m_playing_sounds[id] = ServerPlayingSound();
	ServerPlayingSound &psound = m_playing_sounds[id];
	psound.params = params;
	for(std::set<RemoteClient*>::iterator i = dst_clients.begin();
			i != dst_clients.end(); i++)
		psound.clients.insert((*i)->peer_id);
	// Create packet
	std::ostringstream os(std::ios_base::binary);
	writeU16(os, TOCLIENT_PLAY_SOUND);
	writeS32(os, id);
	os<<serializeString(spec.name);
	writeF1000(os, spec.gain * params.gain);
	writeU8(os, params.type);
	writeV3F1000(os, pos);
	writeU16(os, params.object);
	writeU8(os, params.loop);
	// Make data buffer
	std::string s = os.str();
	SharedBuffer<u8> data((u8*)s.c_str(), s.size());
	// Send
	for(std::set<RemoteClient*>::iterator i = dst_clients.begin();
			i != dst_clients.end(); i++){
		// Send as reliable
		m_con.Send((*i)->peer_id, 0, data, true);
	}
	return id;
}
void Server::stopSound(s32 handle)
{
	// Get sound reference
	std::map<s32, ServerPlayingSound>::iterator i =
			m_playing_sounds.find(handle);
	if(i == m_playing_sounds.end())
		return;
	ServerPlayingSound &psound = i->second;
	// Create packet
	std::ostringstream os(std::ios_base::binary);
	writeU16(os, TOCLIENT_STOP_SOUND);
	writeS32(os, handle);
	// Make data buffer
	std::string s = os.str();
	SharedBuffer<u8> data((u8*)s.c_str(), s.size());
	// Send
	for(std::set<u16>::iterator i = psound.clients.begin();
			i != psound.clients.end(); i++){
		// Send as reliable
		m_con.Send(*i, 0, data, true);
	}
	// Remove sound reference
	m_playing_sounds.erase(i);
}

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
Perttu Ahola's avatar
Perttu Ahola committed
			Player *player = m_env->getPlayer(client->peer_id);
				// If player is far away, only set modified blocks not sent
				v3f player_pos = player->getPosition();
				if(player_pos.getDistanceFrom(p_f) > maxd)
Perttu Ahola's avatar
Perttu Ahola committed
				{
					far_players->push_back(client->peer_id);
					continue;
Perttu Ahola's avatar
Perttu Ahola committed
				}
		// 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
Perttu Ahola's avatar
Perttu Ahola committed
			Player *player = m_env->getPlayer(client->peer_id);
Perttu Ahola's avatar
Perttu Ahola committed
			{
				// If player is far away, only set modified blocks not sent
				v3f player_pos = player->getPosition();
				if(player_pos.getDistanceFrom(p_f) > maxd)
Perttu Ahola's avatar
Perttu Ahola committed
				{
					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::setBlockNotSent(v3s16 p)
{
	for(core::map<u16, RemoteClient*>::Iterator
		i = m_clients.getIterator();
		i.atEnd()==false; i++)
	{
		RemoteClient *client = i.getNode()->getValue();
		client->SetBlockNotSent(p);
	}
}

void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver)
{
	DSTACK(__FUNCTION_NAME);

	v3s16 p = block->getPos();
	
#if 0
	// Analyze it a bit
	bool completely_air = true;
	for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
	for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
	for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
	{
		if(block->getNodeNoEx(v3s16(x0,y0,z0)).d != CONTENT_AIR)
		{
			completely_air = false;
			x0 = y0 = z0 = MAP_BLOCKSIZE; // Break out
		}
	}

	// Print result
	infostream<<"Server: Sending block ("<<p.X<<","<<p.Y<<","<<p.Z<<"): ";
	if(completely_air)
		infostream<<"[completely air] ";
	infostream<<std::endl;
	/*
		Create a packet with the block in the right format
	*/
	
	std::ostringstream os(std::ios_base::binary);
	block->serialize(os, ver, false);
	std::string s = os.str();
	SharedBuffer<u8> blockdata((u8*)s.c_str(), s.size());

	u32 replysize = 8 + blockdata.getSize();
	SharedBuffer<u8> reply(replysize);
	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());

	/*infostream<<"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);

	ScopeProfiler sp(g_profiler, "Server: sel and send blocks to clients");

	core::array<PrioritySortedBlockTransfer> queue;