Skip to content
Snippets Groups Projects
client.cpp 42 KiB
Newer Older
Perttu Ahola's avatar
Perttu Ahola committed
/*
Minetest
sfan5's avatar
sfan5 committed
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Perttu Ahola's avatar
Perttu Ahola committed

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
Perttu Ahola's avatar
Perttu Ahola committed
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.
Perttu Ahola's avatar
Perttu Ahola committed

You should have received a copy of the GNU Lesser General Public License along
Perttu Ahola's avatar
Perttu Ahola committed
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

Perttu Ahola's avatar
Perttu Ahola committed
#include <iostream>
#include <sstream>
#include <IFileSystem.h>
#include "jthread/jmutexautolock.h"
#include "util/directiontables.h"
#include "util/pointedthing.h"
#include "util/serialize.h"
#include "util/string.h"
#include "client.h"
#include "network/clientopcodes.h"
Perttu Ahola's avatar
Perttu Ahola committed
#include "main.h"
Perttu Ahola's avatar
Perttu Ahola committed
#include "porting.h"
Perttu Ahola's avatar
Perttu Ahola committed
#include "mapblock_mesh.h"
#include "mapblock.h"
#include "settings.h"
#include "profiler.h"
#include "gettext.h"
#include "shader.h"
#include "clientmap.h"
#include "sound.h"
#include "version.h"
#include "drawscene.h"
#include "database-sqlite3.h"
#include "serialization.h"

extern gui::IGUIEnvironment* guienv;
Perttu Ahola's avatar
Perttu Ahola committed
/*
	QueuedMeshUpdate
*/

QueuedMeshUpdate::QueuedMeshUpdate():
	p(-1337,-1337,-1337),
	data(NULL),
	ack_block_to_server(false)
{
}

QueuedMeshUpdate::~QueuedMeshUpdate()
{
	if(data)
		delete data;
}

/*
	MeshUpdateQueue
*/
Perttu Ahola's avatar
Perttu Ahola committed
MeshUpdateQueue::MeshUpdateQueue()
{
}

MeshUpdateQueue::~MeshUpdateQueue()
{
	JMutexAutoLock lock(m_mutex);

	for(std::vector<QueuedMeshUpdate*>::iterator
			i = m_queue.begin();
			i != m_queue.end(); i++)
Perttu Ahola's avatar
Perttu Ahola committed
	{
		QueuedMeshUpdate *q = *i;
		delete q;
	}
}

/*
	peer_id=0 adds with nobody to send to
*/
void MeshUpdateQueue::addBlock(v3s16 p, MeshMakeData *data, bool ack_block_to_server, bool urgent)
Perttu Ahola's avatar
Perttu Ahola committed
{
	DSTACK(__FUNCTION_NAME);

Perttu Ahola's avatar
Perttu Ahola committed

	JMutexAutoLock lock(m_mutex);

Perttu Ahola's avatar
Perttu Ahola committed
	/*
		Find if block is already in queue.
		If it is, update the data and quit.
	*/
	for(std::vector<QueuedMeshUpdate*>::iterator
			i = m_queue.begin();
			i != m_queue.end(); i++)
Perttu Ahola's avatar
Perttu Ahola committed
	{
		QueuedMeshUpdate *q = *i;
		if(q->p == p)
		{
			if(q->data)
				delete q->data;
			q->data = data;
			if(ack_block_to_server)
				q->ack_block_to_server = true;
			return;
		}
	}
Perttu Ahola's avatar
Perttu Ahola committed
	/*
		Add the block
	*/
	QueuedMeshUpdate *q = new QueuedMeshUpdate;
	q->p = p;
	q->data = data;
	q->ack_block_to_server = ack_block_to_server;
	m_queue.push_back(q);
}

// Returned pointer must be deleted
// Returns NULL if queue is empty
QueuedMeshUpdate * MeshUpdateQueue::pop()
{
	JMutexAutoLock lock(m_mutex);

	bool must_be_urgent = !m_urgents.empty();
	for(std::vector<QueuedMeshUpdate*>::iterator
			i = m_queue.begin();
			i != m_queue.end(); i++)
	{
		QueuedMeshUpdate *q = *i;
		if(must_be_urgent && m_urgents.count(q->p) == 0)
			continue;
		m_queue.erase(i);
		m_urgents.erase(q->p);
		return q;
	}
	return NULL;
Perttu Ahola's avatar
Perttu Ahola committed
}

/*
	MeshUpdateThread
*/
Perttu Ahola's avatar
Perttu Ahola committed

Perttu Ahola's avatar
Perttu Ahola committed
{
	ThreadStarted();

	log_register_thread("MeshUpdateThread");

Perttu Ahola's avatar
Perttu Ahola committed
	DSTACK(__FUNCTION_NAME);
	BEGIN_DEBUG_EXCEPTION_HANDLER
	porting::setThreadName("MeshUpdateThread");

	while(!StopRequested())
Perttu Ahola's avatar
Perttu Ahola committed
	{
		QueuedMeshUpdate *q = m_queue_in.pop();
		if(q == NULL)
		{
Perttu Ahola's avatar
Perttu Ahola committed
		ScopeProfiler sp(g_profiler, "Client: Mesh making");
		MapBlockMesh *mesh_new = new MapBlockMesh(q->data, m_camera_offset);
		if(mesh_new->getMesh()->getMeshBufferCount() == 0)
		{
			delete mesh_new;
			mesh_new = NULL;
		}

		MeshUpdateResult r;
		r.p = q->p;
		r.mesh = mesh_new;
		r.ack_block_to_server = q->ack_block_to_server;
Perttu Ahola's avatar
Perttu Ahola committed

Perttu Ahola's avatar
Perttu Ahola committed

Perttu Ahola's avatar
Perttu Ahola committed
	}
	END_DEBUG_EXCEPTION_HANDLER(errorstream)
Perttu Ahola's avatar
Perttu Ahola committed

	return NULL;
}

Client::Client(
		IrrlichtDevice *device,
		const char *playername,
Perttu Ahola's avatar
Perttu Ahola committed
		IWritableTextureSource *tsrc,
		IWritableShaderSource *shsrc,
Perttu Ahola's avatar
Perttu Ahola committed
		IWritableNodeDefManager *nodedef,
		ISoundManager *sound,
proller's avatar
proller committed
		MtEventManager *event,
		bool ipv6
Perttu Ahola's avatar
Perttu Ahola committed
):
	m_packetcounter_timer(0.0),
	m_connection_reinit_timer(0.1),
	m_avg_rtt_timer(0.0),
	m_playerpos_send_timer(0.0),
	m_ignore_damage_timer(0.0),
	m_shsrc(shsrc),
Perttu Ahola's avatar
Perttu Ahola committed
	m_nodedef(nodedef),
Perttu Ahola's avatar
Perttu Ahola committed
	m_sound(sound),
Perttu Ahola's avatar
Perttu Ahola committed
	m_mesh_update_thread(this),
Perttu Ahola's avatar
Perttu Ahola committed
			device->getSceneManager()->getRootSceneNode(),
			device->getSceneManager(), 666),
Perttu Ahola's avatar
Perttu Ahola committed
		tsrc, this, device
proller's avatar
proller committed
	m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, ipv6, this),
Perttu Ahola's avatar
Perttu Ahola committed
	m_device(device),
	m_server_ser_ver(SER_FMT_VER_INVALID),
	m_playeritem(0),
Perttu Ahola's avatar
Perttu Ahola committed
	m_inventory_updated(false),
	m_inventory_from_server(NULL),
	m_inventory_from_server_age(0.0),
	m_animation_time(0),
	m_crack_level(-1),
	m_crack_pos(0,0,0),
	m_access_denied(false),
	m_nodedef_received(false),
	m_media_downloader(new ClientMediaDownloader()),
	m_time_of_day_set(false),
	m_last_time_of_day_f(-1),
Perttu Ahola's avatar
Perttu Ahola committed
	m_time_of_day_update_timer(0),
	m_removed_sounds_check_timer(0),
	m_state(LC_Created),
	m_localdb(NULL)
Perttu Ahola's avatar
Perttu Ahola committed
{
	// Add local player
	m_env.addPlayer(new LocalPlayer(this, playername));
Perttu Ahola's avatar
Perttu Ahola committed

	m_cache_save_interval = g_settings->getU16("server_map_save_interval");
	m_cache_smooth_lighting = g_settings->getBool("smooth_lighting");
	m_cache_enable_shaders  = g_settings->getBool("enable_shaders");
Perttu Ahola's avatar
Perttu Ahola committed
}

void Client::Stop()
{
	//request all client managed threads to stop
	m_mesh_update_thread.Stop();
	// Save local server map
	if (m_localdb) {
		infostream << "Local map saving ended." << std::endl;
		m_localdb->endSave();
}

bool Client::isShutdown()
{

	if (!m_mesh_update_thread.IsRunning()) return true;

	return false;
}

Perttu Ahola's avatar
Perttu Ahola committed
Client::~Client()
{
sapier's avatar
sapier committed
	m_con.Disconnect();
	m_mesh_update_thread.Stop();
	m_mesh_update_thread.Wait();
	while(!m_mesh_update_thread.m_queue_out.empty()) {
		MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_frontNoEx();
		delete r.mesh;
	}

Perttu Ahola's avatar
Perttu Ahola committed

	// Delete detached inventories
sapier's avatar
sapier committed
	for(std::map<std::string, Inventory*>::iterator
			i = m_detached_inventories.begin();
			i != m_detached_inventories.end(); i++){
		delete i->second;
Perttu Ahola's avatar
Perttu Ahola committed
	}
sapier's avatar
sapier committed
	// cleanup 3d model meshes on client shutdown
	while (m_device->getSceneManager()->getMeshCache()->getMeshCount() != 0) {
		scene::IAnimatedMesh * mesh =
			m_device->getSceneManager()->getMeshCache()->getMeshByIndex(0);

		if (mesh != NULL)
			m_device->getSceneManager()->getMeshCache()->removeMesh(mesh);
	}
Perttu Ahola's avatar
Perttu Ahola committed
}

void Client::connect(Address address,
		const std::string &address_name,
		bool is_local_server)
Perttu Ahola's avatar
Perttu Ahola committed
{
	DSTACK(__FUNCTION_NAME);

	initLocalMapSaving(address, address_name, is_local_server);

Perttu Ahola's avatar
Perttu Ahola committed
	m_con.Connect(address);
}

void Client::step(float dtime)
{
	DSTACK(__FUNCTION_NAME);
Perttu Ahola's avatar
Perttu Ahola committed
	// Limit a bit
	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;
	m_animation_time += dtime;
	if(m_animation_time > 60.0)
		m_animation_time -= 60.0;

	m_time_of_day_update_timer += dtime;
Perttu Ahola's avatar
Perttu Ahola committed

sapier's avatar
sapier committed
	ReceiveAll();
Perttu Ahola's avatar
Perttu Ahola committed

		float &counter = m_packetcounter_timer;
		counter -= dtime;
		if(counter <= 0.0)
		{
			counter = 20.0;
			infostream << "Client packetcounter (" << m_packetcounter_timer
					<< "):"<<std::endl;
	// UGLY hack to fix 2 second startup delay caused by non existent
	// server client startup synchronization in local server or singleplayer mode
	static bool initial_step = true;
	if (initial_step) {
		initial_step = false;
	}
	else if(m_state == LC_Created) {
		float &counter = m_connection_reinit_timer;
Perttu Ahola's avatar
Perttu Ahola committed
		counter -= dtime;
Perttu Ahola's avatar
Perttu Ahola committed
			counter = 2.0;

			Player *myplayer = m_env.getLocalPlayer();		
			FATAL_ERROR_IF(myplayer == NULL, "Local player not found in environment.");

Perttu Ahola's avatar
Perttu Ahola committed
			// Send TOSERVER_INIT
			// [0] u16 TOSERVER_INIT
proller's avatar
proller committed
			// [2] u8 SER_FMT_VER_HIGHEST_READ
Perttu Ahola's avatar
Perttu Ahola committed
			// [3] u8[20] player_name
			// [23] u8[28] password (new in some version)
			// [51] u16 minimum supported network protocol version (added sometime)
			// [53] u16 maximum supported network protocol version (added later than the previous one)

			char pName[PLAYERNAME_SIZE];
			char pPassword[PASSWORD_SIZE];
			memset(pName, 0, PLAYERNAME_SIZE * sizeof(char));
			memset(pPassword, 0, PASSWORD_SIZE * sizeof(char));

			snprintf(pName, PLAYERNAME_SIZE, "%s", myplayer->getName());
			snprintf(pPassword, PASSWORD_SIZE, "%s", m_password.c_str());

			sendLegacyInit(pName, pPassword);
Perttu Ahola's avatar
Perttu Ahola committed
		}

		// Not connected, return
		return;
	}

	/*
		Do stuff if connected
	*/
	/*
		Run Map's timers and unload unused data
	*/
	const float map_timer_and_unload_dtime = 5.25;
	if(m_map_timer_and_unload_interval.step(dtime, map_timer_and_unload_dtime)) {
		ScopeProfiler sp(g_profiler, "Client: map timer and unload");
		std::vector<v3s16> deleted_blocks;
		m_env.getMap().timerUpdate(map_timer_and_unload_dtime,
				g_settings->getFloat("client_unload_unused_data_timeout"),
				&deleted_blocks);
		/*
			Send info to server
			NOTE: This loop is intentionally iterated the way it is.
		*/

		std::vector<v3s16>::iterator i = deleted_blocks.begin();
		std::vector<v3s16> sendlist;
		for(;;) {
			if(sendlist.size() == 255 || i == deleted_blocks.end()) {
					break;
				/*
					[0] u16 command
					[2] u8 count
					[3] v3s16 pos_0
					[3+6] v3s16 pos_1
					...
				*/
				sendDeletedBlocks(sendlist);

				if(i == deleted_blocks.end())
					break;

				sendlist.clear();
			}

			sendlist.push_back(*i);
	// Control local player (0ms)
	LocalPlayer *player = m_env.getLocalPlayer();
	assert(player != NULL);
	player->applyControl(dtime);
	// Step environment
	m_env.step(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;

				if(event.player_damage.send_to_server)
					sendDamage(damage);

				// Add to ClientEvent queue
				ClientEvent event;
				event.type = CE_PLAYER_DAMAGE;
				event.player_damage.amount = damage;
				m_client_event_queue.push(event);
		else if(event.type == CEE_PLAYER_BREATH) {
				u16 breath = event.player_breath.amount;
				sendBreath(breath);
		}
	float &counter = m_avg_rtt_timer;
	counter += dtime;
	if(counter >= 10) {
		counter = 0.0;
		// connectedAndInitialized() is true, peer exists.
		float avg_rtt = getRTT();
		infostream << "Client: avg_rtt=" << avg_rtt << std::endl;
Perttu Ahola's avatar
Perttu Ahola committed
	}
Perttu Ahola's avatar
Perttu Ahola committed
	{
		float &counter = m_playerpos_send_timer;
Perttu Ahola's avatar
Perttu Ahola committed
		counter += dtime;
		if((m_state == LC_Ready) && (counter >= m_recommended_send_interval))
Perttu Ahola's avatar
Perttu Ahola committed
		{
			counter = 0.0;
			sendPlayerPos();
		}
	}

Perttu Ahola's avatar
Perttu Ahola committed
	{
Perttu Ahola's avatar
Perttu Ahola committed
		int num_processed_meshes = 0;
		while(!m_mesh_update_thread.m_queue_out.empty())
Perttu Ahola's avatar
Perttu Ahola committed
			num_processed_meshes++;
			MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_frontNoEx();
			MapBlock *block = m_env.getMap().getBlockNoCreateNoEx(r.p);
				// Delete the old mesh
				if(block->mesh != NULL)
				{
					// TODO: Remove hardware buffers of meshbuffers of block->mesh
					delete block->mesh;
					block->mesh = NULL;
				}

				// Replace with the new mesh
				block->mesh = r.mesh;
			} else {
				delete r.mesh;
Perttu Ahola's avatar
Perttu Ahola committed
		if(num_processed_meshes > 0)
			g_profiler->graphAdd("num_processed_meshes", num_processed_meshes);
	if (m_media_downloader && m_media_downloader->isStarted()) {
		m_media_downloader->step(this);
		if (m_media_downloader->isDone()) {
			received_media();
			delete m_media_downloader;
			m_media_downloader = NULL;
	/*
		If the server didn't update the inventory in a while, revert
		the local inventory (so the player notices the lag problem
		and knows something is wrong).
	*/
	if(m_inventory_from_server)
	{
		float interval = 10.0;
		float count_before = floor(m_inventory_from_server_age / interval);

		m_inventory_from_server_age += dtime;

		float count_after = floor(m_inventory_from_server_age / interval);

		if(count_after != count_before)
		{
			// Do this every <interval> seconds after TOCLIENT_INVENTORY
			// Reset the locally changed inventory to the authoritative inventory
			Player *player = m_env.getLocalPlayer();
			player->inventory = *m_inventory_from_server;
			m_inventory_updated = true;
		}
	}
Perttu Ahola's avatar
Perttu Ahola committed

	/*
		Update positions of sounds attached to objects
	*/
	{
		for(std::map<int, u16>::iterator
				i = m_sounds_to_objects.begin();
				i != m_sounds_to_objects.end(); i++)
		{
			int client_id = i->first;
			u16 object_id = i->second;
			ClientActiveObject *cao = m_env.getActiveObject(object_id);
			if(!cao)
				continue;
			v3f pos = cao->getPosition();
			m_sound->updateSoundPosition(client_id, pos);
		}
	}
Perttu Ahola's avatar
Perttu Ahola committed
	/*
		Handle removed remotely initiated sounds
	*/
	m_removed_sounds_check_timer += dtime;
	if(m_removed_sounds_check_timer >= 2.32) {
Perttu Ahola's avatar
Perttu Ahola committed
		m_removed_sounds_check_timer = 0;
		// Find removed sounds and clear references to them
		std::vector<s32> removed_server_ids;
Perttu Ahola's avatar
Perttu Ahola committed
		for(std::map<s32, int>::iterator
				i = m_sounds_server_to_client.begin();
				i != m_sounds_server_to_client.end();) {
Perttu Ahola's avatar
Perttu Ahola committed
			s32 server_id = i->first;
			int client_id = i->second;
			i++;
			if(!m_sound->soundExists(client_id)) {
Perttu Ahola's avatar
Perttu Ahola committed
				m_sounds_server_to_client.erase(server_id);
				m_sounds_client_to_server.erase(client_id);
				m_sounds_to_objects.erase(client_id);
				removed_server_ids.push_back(server_id);
Perttu Ahola's avatar
Perttu Ahola committed
		// Sync to server
		if(!removed_server_ids.empty()) {
			sendRemovedSounds(removed_server_ids);

	// Write server map
	if (m_localdb && m_localdb_save_interval.step(dtime,
			m_cache_save_interval)) {
		m_localdb->endSave();
		m_localdb->beginSave();
	}
Perttu Ahola's avatar
Perttu Ahola committed
}

bool Client::loadMedia(const std::string &data, const std::string &filename)
{
	// Silly irrlicht's const-incorrectness
	Buffer<char> data_rw(data.c_str(), data.size());
	std::string name;

	const char *image_ext[] = {
		".png", ".jpg", ".bmp", ".tga",
		".pcx", ".ppm", ".psd", ".wal", ".rgb",
		NULL
	};
	name = removeStringEnd(filename, image_ext);
	if(name != "")
	{
MirceaKitsune's avatar
MirceaKitsune committed
		verbosestream<<"Client: Attempting to load image "
		<<"file \""<<filename<<"\""<<std::endl;

		io::IFileSystem *irrfs = m_device->getFileSystem();
		video::IVideoDriver *vdrv = m_device->getVideoDriver();

		// Create an irrlicht memory file
		io::IReadFile *rfile = irrfs->createMemoryReadFile(
				*data_rw, data_rw.getSize(), "_tempreadfile");

		FATAL_ERROR_IF(!rfile, "Could not create irrlicht memory file.");

		// Read image
		video::IImage *img = vdrv->createImageFromFile(rfile);
		if(!img){
			errorstream<<"Client: Cannot create image from data of "
					<<"file \""<<filename<<"\""<<std::endl;
			rfile->drop();
			return false;
		}
		else {
			m_tsrc->insertSourceImage(filename, img);
			img->drop();
			rfile->drop();
			return true;
		}
	}

	const char *sound_ext[] = {
		".0.ogg", ".1.ogg", ".2.ogg", ".3.ogg", ".4.ogg",
		".5.ogg", ".6.ogg", ".7.ogg", ".8.ogg", ".9.ogg",
		".ogg", NULL
	};
	name = removeStringEnd(filename, sound_ext);
	if(name != "")
	{
MirceaKitsune's avatar
MirceaKitsune committed
		verbosestream<<"Client: Attempting to load sound "
		<<"file \""<<filename<<"\""<<std::endl;
		m_sound->loadSoundData(name, data);
		return true;
	}

		NULL
	};
	name = removeStringEnd(filename, model_ext);
	if(name != "")
	{
		verbosestream<<"Client: Storing model into memory: "
		if(m_mesh_data.count(filename))
			errorstream<<"Multiple models with name \""<<filename.c_str()
					<<"\" found; replacing previous model"<<std::endl;
		m_mesh_data[filename] = data;
	errorstream<<"Client: Don't know how to load file \""
			<<filename<<"\""<<std::endl;
	return false;
}

Perttu Ahola's avatar
Perttu Ahola committed
// Virtual methods from con::PeerHandler
void Client::peerAdded(con::Peer *peer)
{
	infostream<<"Client::peerAdded(): peer->id="
Perttu Ahola's avatar
Perttu Ahola committed
			<<peer->id<<std::endl;
}
void Client::deletingPeer(con::Peer *peer, bool timeout)
{
	infostream<<"Client::deletingPeer(): "
Perttu Ahola's avatar
Perttu Ahola committed
			"Server Peer is getting deleted "
			<<"(timeout="<<timeout<<")"<<std::endl;
}

/*
	u16 command
	u16 number of files requested
	for each file {
		u16 length of name
		string name
	}
*/
void Client::request_media(const std::vector<std::string> &file_requests)
{
	std::ostringstream os(std::ios_base::binary);
	writeU16(os, TOSERVER_REQUEST_MEDIA);
sapier's avatar
sapier committed
	size_t file_requests_size = file_requests.size();

	FATAL_ERROR_IF(file_requests_size > 0xFFFF, "Unsupported number of file requests");
	NetworkPacket pkt(TOSERVER_REQUEST_MEDIA, 2 + 0);
	pkt << (u16) (file_requests_size & 0xFFFF);
	for(std::vector<std::string>::const_iterator i = file_requests.begin();
			i != file_requests.end(); ++i) {
	infostream << "Client: Sending media request list to server ("
			<< file_requests.size() << " files. packet size)" << std::endl;
void Client::received_media()
{
	NetworkPacket pkt(TOSERVER_RECEIVED_MEDIA, 0);
	Send(&pkt);
	infostream << "Client: Notifying server that we received all media"
			<< std::endl;
void Client::initLocalMapSaving(const Address &address,
		const std::string &hostname,
		bool is_local_server)
{
	if (!g_settings->getBool("enable_local_map_saving") || is_local_server) {

	const std::string world_path = porting::path_user
		+ DIR_DELIM + "worlds"
		+ DIR_DELIM + "server_"
		+ hostname + "_" + to_string(address.getPort());

	fs::CreateAllDirs(world_path);
	m_localdb = new Database_SQLite3(world_path);
	m_localdb->beginSave();
	actionstream << "Local map saving started, map will be saved at '" << world_path << "'" << std::endl;
}

Perttu Ahola's avatar
Perttu Ahola committed
void Client::ReceiveAll()
{
	DSTACK(__FUNCTION_NAME);
	u32 start_ms = porting::getTimeMs();
Perttu Ahola's avatar
Perttu Ahola committed
	for(;;)
	{
		// Limit time even if there would be huge amounts of data to
		// process
		if(porting::getTimeMs() > start_ms + 100)
			break;
Perttu Ahola's avatar
Perttu Ahola committed
			Receive();
Perttu Ahola's avatar
Perttu Ahola committed
			g_profiler->graphAdd("client_received_packets", 1);
Perttu Ahola's avatar
Perttu Ahola committed
		}
		catch(con::NoIncomingDataException &e) {
Perttu Ahola's avatar
Perttu Ahola committed
			break;
		}
		catch(con::InvalidIncomingDataException &e) {
			infostream<<"Client::ReceiveAll(): "
Perttu Ahola's avatar
Perttu Ahola committed
					"InvalidIncomingDataException: what()="
					<<e.what()<<std::endl;
		}
	}
}

void Client::Receive()
{
	DSTACK(__FUNCTION_NAME);
Perttu Ahola's avatar
Perttu Ahola committed
	u16 sender_peer_id;
sapier's avatar
sapier committed
	u32 datasize = m_con.Receive(sender_peer_id, data);
Perttu Ahola's avatar
Perttu Ahola committed
	ProcessData(*data, datasize, sender_peer_id);
}

inline void Client::handleCommand(NetworkPacket* pkt)
{
	const ToClientCommandHandler& opHandle = toClientCommandTable[pkt->getCommand()];
	(this->*opHandle.handler)(pkt);
}

/*
	sender_peer_id given to this shall be quaranteed to be a valid peer
*/
void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
{
	DSTACK(__FUNCTION_NAME);

	// Ignore packets that don't even fit a command
	if(datasize < 2) {
		m_packetcounter.add(60000);
		return;
BlockMen's avatar
BlockMen committed

	NetworkPacket pkt(data, datasize, sender_peer_id);
	ToClientCommand command = (ToClientCommand) pkt.getCommand();
BlockMen's avatar
BlockMen committed

	//infostream<<"Client: received command="<<command<<std::endl;
	m_packetcounter.add((u16)command);

	/*
		If this check is removed, be sure to change the queue
		system to know the ids
	*/
	if(sender_peer_id != PEER_ID_SERVER) {
		infostream << "Client::ProcessData(): Discarding data not "
			"coming from server: peer_id=" << sender_peer_id
			<< std::endl;
		return;
BlockMen's avatar
BlockMen committed
	}
	// Command must be handled into ToClientCommandHandler
	if (command >= TOCLIENT_NUM_MSG_TYPES) {
		infostream << "Client: Ignoring unknown command "
			<< command << std::endl;
	}
	/*
	 * Those packets are handled before m_server_ser_ver is set, it's normal
	 * But we must use the new ToClientConnectionState in the future,
	 * as a byte mask
	 */
	if(toClientCommandTable[command].state == TOCLIENT_STATE_NOT_CONNECTED) {
		handleCommand(&pkt);

	if(m_server_ser_ver == SER_FMT_VER_INVALID) {
		infostream << "Client: Server serialization"
				" format invalid or not initialized."
				" Skipping incoming command=" << command << std::endl;
		return;
Perttu Ahola's avatar
Perttu Ahola committed
	}

	/*
	  Handle runtime commands
	*/

	handleCommand(&pkt);
Perttu Ahola's avatar
Perttu Ahola committed
}

void Client::Send(NetworkPacket* pkt)
Perttu Ahola's avatar
Perttu Ahola committed
{
	m_con.Send(PEER_ID_SERVER,
		serverCommandFactoryTable[pkt->getCommand()].channel,
		pkt,
		serverCommandFactoryTable[pkt->getCommand()].reliable);
Perttu Ahola's avatar
Perttu Ahola committed
}

void Client::interact(u8 action, const PointedThing& pointed)
Perttu Ahola's avatar
Perttu Ahola committed
{
	if(m_state != LC_Ready) {
		errorstream << "Client::interact() "
Perttu Ahola's avatar
Perttu Ahola committed
		return;
	}
Perttu Ahola's avatar
Perttu Ahola committed
	/*
		[0] u16 command
		[2] u8 action
		[3] u16 item
		[5] u32 length of the next item
		[9] serialized PointedThing
		actions:
		0: start digging (from undersurface) or use
		1: stop digging (all parameters ignored)
		2: digging completed
		3: place block or item (to abovesurface)
		4: use item
Perttu Ahola's avatar
Perttu Ahola committed
	*/
	NetworkPacket pkt(TOSERVER_INTERACT, 1 + 2 + 0);
	pkt << action;
	pkt << (u16)getPlayerItem();
	std::ostringstream tmp_os(std::ios::binary);
	pointed.serialize(tmp_os);
Perttu Ahola's avatar
Perttu Ahola committed

	pkt.putLongString(tmp_os.str());
Perttu Ahola's avatar
Perttu Ahola committed

void Client::sendLegacyInit(const char* playerName, const char* playerPassword)
{
	NetworkPacket pkt(TOSERVER_INIT_LEGACY,
			1 + PLAYERNAME_SIZE + PASSWORD_SIZE + 2 + 2);

	pkt << (u8) SER_FMT_VER_HIGHEST_READ;
	pkt.putRawString(playerName,PLAYERNAME_SIZE);
	pkt.putRawString(playerPassword, PASSWORD_SIZE);
	pkt << (u16) CLIENT_PROTOCOL_VERSION_MIN << (u16) CLIENT_PROTOCOL_VERSION_MAX;

	Send(&pkt);
}

void Client::sendDeletedBlocks(std::vector<v3s16> &blocks)
{
	NetworkPacket pkt(TOSERVER_DELETEDBLOCKS, 1 + sizeof(v3s16) * blocks.size());

	pkt << (u8) blocks.size();

	u32 k = 0;
	for(std::vector<v3s16>::iterator
			j = blocks.begin();
			j != blocks.end(); ++j) {
		pkt << *j;
		k++;
	}

	Send(&pkt);
}

void Client::sendGotBlocks(v3s16 block)
{
	NetworkPacket pkt(TOSERVER_GOTBLOCKS, 1 + 6);
	pkt << (u8) 1 << block;
	Send(&pkt);
}

void Client::sendRemovedSounds(std::vector<s32> &soundList)
{
	size_t server_ids = soundList.size();
	assert(server_ids <= 0xFFFF);

	NetworkPacket pkt(TOSERVER_REMOVED_SOUNDS, 2 + server_ids * 4);

	pkt << (u16) (server_ids & 0xFFFF);

	for(std::vector<s32>::iterator i = soundList.begin();