#include "client.h"
#include <iostream>
+#include <algorithm>
#include "clientserver.h"
-#include "jmutexautolock.h"
+#include "jthread/jmutexautolock.h"
#include "main.h"
#include <sstream>
+#include "filesys.h"
#include "porting.h"
#include "mapsector.h"
#include "mapblock_mesh.h"
#include "itemdef.h"
#include "shader.h"
#include <IFileSystem.h>
-#include "sha1.h"
#include "base64.h"
#include "clientmap.h"
-#include "filecache.h"
+#include "clientmedia.h"
#include "sound.h"
#include "util/string.h"
-#include "hex.h"
#include "IMeshCache.h"
+#include "serialization.h"
#include "util/serialize.h"
#include "config.h"
#include "util/directiontables.h"
-
-#if USE_CURL
-#include <curl/curl.h>
-#endif
-
-static std::string getMediaCacheDir()
-{
- return porting::path_user + DIR_DELIM + "cache" + DIR_DELIM + "media";
-}
+#include "util/pointedthing.h"
+#include "version.h"
/*
QueuedMeshUpdate
MeshUpdateQueue::MeshUpdateQueue()
{
- m_mutex.Init();
}
MeshUpdateQueue::~MeshUpdateQueue()
BEGIN_DEBUG_EXCEPTION_HANDLER
- while(getRun())
+ while(!StopRequested())
{
/*// Wait for output queue to flush.
// Allow 2 in queue, this makes less frametime jitter.
return NULL;
}
-void * MediaFetchThread::Thread()
-{
- ThreadStarted();
-
- log_register_thread("MediaFetchThread");
-
- DSTACK(__FUNCTION_NAME);
-
- BEGIN_DEBUG_EXCEPTION_HANDLER
-
- #if USE_CURL
- CURL *curl;
- CURLcode res;
- for (std::list<MediaRequest>::iterator i = m_file_requests.begin();
- i != m_file_requests.end(); ++i) {
- curl = curl_easy_init();
- assert(curl);
- curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
- curl_easy_setopt(curl, CURLOPT_URL, (m_remote_url + i->name).c_str());
- curl_easy_setopt(curl, CURLOPT_FAILONERROR, true);
- std::ostringstream stream;
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_data);
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, &stream);
- res = curl_easy_perform(curl);
- if (res == CURLE_OK) {
- std::string data = stream.str();
- m_file_data.push_back(make_pair(i->name, data));
- } else {
- m_failed.push_back(*i);
- infostream << "cURL request failed for " << i->name << std::endl;
- }
- curl_easy_cleanup(curl);
- }
- #endif
-
- END_DEBUG_EXCEPTION_HANDLER(errorstream)
-
- return NULL;
-}
+/*
+ Client
+*/
Client::Client(
IrrlichtDevice *device,
IWritableItemDefManager *itemdef,
IWritableNodeDefManager *nodedef,
ISoundManager *sound,
- MtEventManager *event
+ MtEventManager *event,
+ bool ipv6
):
m_tsrc(tsrc),
m_shsrc(shsrc),
device->getSceneManager(),
tsrc, this, device
),
- m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this),
+ m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, ipv6, this),
m_device(device),
m_server_ser_ver(SER_FMT_VER_INVALID),
m_playeritem(0),
m_map_seed(0),
m_password(password),
m_access_denied(false),
- m_media_cache(getMediaCacheDir()),
- m_media_receive_started(false),
- m_media_count(0),
- m_media_received_count(0),
m_itemdef_received(false),
m_nodedef_received(false),
+ m_media_downloader(new ClientMediaDownloader()),
m_time_of_day_set(false),
m_last_time_of_day_f(-1),
m_time_of_day_update_timer(0),
m_playerpos_send_timer = 0.0;
m_ignore_damage_timer = 0.0;
- // Build main texture atlas, now that the GameDef exists (that is, us)
- if(g_settings->getBool("enable_texture_atlas"))
- m_tsrc->buildMainAtlas(this);
- else
- infostream<<"Not building texture atlas."<<std::endl;
-
/*
Add local player
*/
m_env.addPlayer(player);
}
-
- for (size_t i = 0; i < g_settings->getU16("media_fetch_threads"); ++i)
- m_media_fetch_threads.push_back(new MediaFetchThread(this));
}
Client::~Client()
m_con.Disconnect();
}
- m_mesh_update_thread.setRun(false);
- while(m_mesh_update_thread.IsRunning())
- sleep_ms(100);
+ 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_front();
+ delete r.mesh;
+ }
+
delete m_inventory_from_server;
}
}
- for (std::list<MediaFetchThread*>::iterator i = m_media_fetch_threads.begin();
- i != m_media_fetch_threads.end(); ++i)
- delete *i;
-
// cleanup 3d model meshes on client shutdown
while (m_device->getSceneManager()->getMeshCache()->getMeshCount() != 0) {
scene::IAnimatedMesh * mesh =
core::list<v3s16> deleted_blocks;
- float delete_unused_sectors_timeout =
+ float delete_unused_sectors_timeout =
g_settings->getFloat("client_delete_unused_sectors_timeout");
// Delete sector blocks
// Send TOSERVER_INIT
// [0] u16 TOSERVER_INIT
- // [2] u8 SER_FMT_VER_HIGHEST
+ // [2] u8 SER_FMT_VER_HIGHEST_READ
// [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)
SharedBuffer<u8> data(2+1+PLAYERNAME_SIZE+PASSWORD_SIZE+2+2);
writeU16(&data[0], TOSERVER_INIT);
- writeU8(&data[2], SER_FMT_VER_HIGHEST);
+ writeU8(&data[2], SER_FMT_VER_HIGHEST_READ);
memset((char*)&data[3], 0, PLAYERNAME_SIZE);
snprintf((char*)&data[3], PLAYERNAME_SIZE, "%s", myplayer->getName());
m_client_event_queue.push_back(event);
}
}
+ else if(event.type == CEE_PLAYER_BREATH)
+ {
+ u16 breath = event.player_breath.amount;
+ sendBreath(breath);
+ }
}
}
-
+
/*
Print some info
*/
// Replace with the new mesh
block->mesh = r.mesh;
+ } else {
+ delete r.mesh;
}
if(r.ack_block_to_server)
{
/*
Load fetched media
*/
- if (m_media_receive_started) {
- bool all_stopped = true;
- for (std::list<MediaFetchThread*>::iterator thread = m_media_fetch_threads.begin();
- thread != m_media_fetch_threads.end(); ++thread) {
- all_stopped &= !(*thread)->IsRunning();
- while (!(*thread)->m_file_data.empty()) {
- std::pair <std::string, std::string> out = (*thread)->m_file_data.pop_front();
- ++m_media_received_count;
-
- bool success = loadMedia(out.second, out.first);
- if(success){
- verbosestream<<"Client: Loaded received media: "
- <<"\""<<out.first<<"\". Caching."<<std::endl;
- } else{
- infostream<<"Client: Failed to load received media: "
- <<"\""<<out.first<<"\". Not caching."<<std::endl;
- continue;
- }
-
- bool did = fs::CreateAllDirs(getMediaCacheDir());
- if(!did){
- errorstream<<"Could not create media cache directory"
- <<std::endl;
- }
-
- {
- std::map<std::string, std::string>::iterator n;
- n = m_media_name_sha1_map.find(out.first);
- if(n == m_media_name_sha1_map.end())
- errorstream<<"The server sent a file that has not "
- <<"been announced."<<std::endl;
- else
- m_media_cache.update_sha1(out.second);
- }
- }
- }
- if (all_stopped) {
- std::list<MediaRequest> fetch_failed;
- for (std::list<MediaFetchThread*>::iterator thread = m_media_fetch_threads.begin();
- thread != m_media_fetch_threads.end(); ++thread) {
- for (std::list<MediaRequest>::iterator request = (*thread)->m_failed.begin();
- request != (*thread)->m_failed.end(); ++request)
- fetch_failed.push_back(*request);
- (*thread)->m_failed.clear();
- }
- if (fetch_failed.size() > 0) {
- infostream << "Failed to remote-fetch " << fetch_failed.size() << " files. "
- << "Requesting them the usual way." << std::endl;
- request_media(fetch_failed);
- }
+ if (m_media_downloader && m_media_downloader->isStarted()) {
+ m_media_downloader->step(this);
+ if (m_media_downloader->isDone()) {
+ delete m_media_downloader;
+ m_media_downloader = NULL;
}
}
string name
}
*/
-void Client::request_media(const std::list<MediaRequest> &file_requests)
+void Client::request_media(const std::list<std::string> &file_requests)
{
std::ostringstream os(std::ios_base::binary);
writeU16(os, TOSERVER_REQUEST_MEDIA);
writeU16(os, file_requests.size());
- for(std::list<MediaRequest>::const_iterator i = file_requests.begin();
+ for(std::list<std::string>::const_iterator i = file_requests.begin();
i != file_requests.end(); ++i) {
- os<<serializeString(i->name);
+ os<<serializeString(*i);
}
// Make data buffer
<<file_requests.size()<<" files)"<<std::endl;
}
+void Client::received_media()
+{
+ // notify server we received everything
+ std::ostringstream os(std::ios_base::binary);
+ writeU16(os, TOSERVER_RECEIVED_MEDIA);
+ std::string s = os.str();
+ SharedBuffer<u8> data((u8*)s.c_str(), s.size());
+ // Send as reliable
+ Send(0, data, true);
+ infostream<<"Client: Notifying server that we received all media"
+ <<std::endl;
+}
+
void Client::ReceiveAll()
{
DSTACK(__FUNCTION_NAME);
infostream<<"Client: TOCLIENT_INIT received with "
"deployed="<<((int)deployed&0xff)<<std::endl;
- if(deployed < SER_FMT_VER_LOWEST
- || deployed > SER_FMT_VER_HIGHEST)
+ if(!ser_ver_supported(deployed))
{
infostream<<"Client: TOCLIENT_INIT: Server sent "
<<"unsupported ser_fmt_ver"<<std::endl;
MapNode n;
n.deSerialize(&data[8], ser_version);
- addNode(p, n);
+ bool remove_metadata = true;
+ u32 index = 8 + MapNode::serializedLength(ser_version);
+ if ((datasize >= index+1) && data[index]){
+ remove_metadata = false;
+ }
+
+ addNode(p, n, remove_metadata);
}
else if(command == TOCLIENT_BLOCKDATA)
{
*/
//infostream<<"Updating"<<std::endl;
block->deSerialize(istr, ser_version, false);
+ block->deSerializeNetworkSpecific(istr);
}
else
{
//infostream<<"Creating new"<<std::endl;
block = new MapBlock(&m_env.getMap(), p, this);
block->deSerialize(istr, ser_version, false);
+ block->deSerializeNetworkSpecific(istr);
sector->insertBlock(block);
}
std::istringstream is(datastring, std::ios_base::binary);
//t3.stop();
- //m_env.printPlayers(infostream);
-
//TimeTaker t4("player get", m_device);
Player *player = m_env.getLocalPlayer();
assert(player != NULL);
m_client_event_queue.push_back(event);
}
}
+ else if(command == TOCLIENT_BREATH)
+ {
+ std::string datastring((char*)&data[2], datasize-2);
+ std::istringstream is(datastring, std::ios_base::binary);
+ Player *player = m_env.getLocalPlayer();
+ assert(player != NULL);
+ u16 breath = readU16(is);
+ player->setBreath(breath) ;
+ }
else if(command == TOCLIENT_MOVE_PLAYER)
{
std::string datastring((char*)&data[2], datasize-2);
std::string datastring((char*)&data[2], datasize-2);
std::istringstream is(datastring, std::ios_base::binary);
- // Mesh update thread must be stopped while
- // updating content definitions
- assert(!m_mesh_update_thread.IsRunning());
-
int num_files = readU16(is);
infostream<<"Client: Received media announcement: packet size: "
<<datasize<<std::endl;
- std::list<MediaRequest> file_requests;
+ if (m_media_downloader == NULL ||
+ m_media_downloader->isStarted()) {
+ const char *problem = m_media_downloader ?
+ "we already saw another announcement" :
+ "all media has been received already";
+ errorstream<<"Client: Received media announcement but "
+ <<problem<<"! "
+ <<" files="<<num_files
+ <<" size="<<datasize<<std::endl;
+ return;
+ }
+
+ // Mesh update thread must be stopped while
+ // updating content definitions
+ assert(!m_mesh_update_thread.IsRunning());
for(int i=0; i<num_files; i++)
{
- //read file from cache
std::string name = deSerializeString(is);
std::string sha1_base64 = deSerializeString(is);
-
- // if name contains illegal characters, ignore the file
- if(!string_allowed(name, TEXTURENAME_ALLOWED_CHARS)){
- errorstream<<"Client: ignoring illegal file name "
- <<"sent by server: \""<<name<<"\""<<std::endl;
- continue;
- }
-
std::string sha1_raw = base64_decode(sha1_base64);
- std::string sha1_hex = hex_encode(sha1_raw);
- std::ostringstream tmp_os(std::ios_base::binary);
- bool found_in_cache = m_media_cache.load_sha1(sha1_raw, tmp_os);
- m_media_name_sha1_map[name] = sha1_raw;
-
- // If found in cache, try to load it from there
- if(found_in_cache)
- {
- bool success = loadMedia(tmp_os.str(), name);
- if(success){
- verbosestream<<"Client: Loaded cached media: "
- <<sha1_hex<<" \""<<name<<"\""<<std::endl;
- continue;
- } else{
- infostream<<"Client: Failed to load cached media: "
- <<sha1_hex<<" \""<<name<<"\""<<std::endl;
- }
- }
- // Didn't load from cache; queue it to be requested
- verbosestream<<"Client: Adding file to request list: \""
- <<sha1_hex<<" \""<<name<<"\""<<std::endl;
- file_requests.push_back(MediaRequest(name));
+ m_media_downloader->addFile(name, sha1_raw);
}
- std::string remote_media = "";
+ std::vector<std::string> remote_media;
try {
- remote_media = deSerializeString(is);
+ Strfnd sf(deSerializeString(is));
+ while(!sf.atend()) {
+ std::string baseurl = trim(sf.next(","));
+ if(baseurl != "")
+ m_media_downloader->addRemoteServer(baseurl);
+ }
}
catch(SerializationError) {
// not supported by server or turned off
}
- m_media_count = file_requests.size();
- m_media_receive_started = true;
-
- if (remote_media == "" || !USE_CURL) {
- request_media(file_requests);
- } else {
- #if USE_CURL
- std::list<MediaFetchThread*>::iterator cur = m_media_fetch_threads.begin();
- for(std::list<MediaRequest>::iterator i = file_requests.begin();
- i != file_requests.end(); ++i) {
- (*cur)->m_file_requests.push_back(*i);
- cur++;
- if (cur == m_media_fetch_threads.end())
- cur = m_media_fetch_threads.begin();
- }
- for (std::list<MediaFetchThread*>::iterator i = m_media_fetch_threads.begin();
- i != m_media_fetch_threads.end(); ++i) {
- (*i)->m_remote_url = remote_media;
- (*i)->Start();
- }
- #endif
-
- // notify server we received everything
- std::ostringstream os(std::ios_base::binary);
- writeU16(os, TOSERVER_RECEIVED_MEDIA);
- std::string s = os.str();
- SharedBuffer<u8> data((u8*)s.c_str(), s.size());
- // Send as reliable
- Send(0, data, true);
+ m_media_downloader->step(this);
+ if (m_media_downloader->isDone()) {
+ // might be done already if all media is in the cache
+ delete m_media_downloader;
+ m_media_downloader = NULL;
}
- ClientEvent event;
- event.type = CE_TEXTURES_UPDATED;
- m_client_event_queue.push_back(event);
}
else if(command == TOCLIENT_MEDIA)
{
- if (m_media_count == 0)
- return;
std::string datastring((char*)&data[2], datasize-2);
std::istringstream is(datastring, std::ios_base::binary);
- // Mesh update thread must be stopped while
- // updating content definitions
- assert(!m_mesh_update_thread.IsRunning());
-
/*
u16 command
u16 total number of file bunches
*/
int num_bunches = readU16(is);
int bunch_i = readU16(is);
- int num_files = readU32(is);
+ u32 num_files = readU32(is);
infostream<<"Client: Received files: bunch "<<bunch_i<<"/"
<<num_bunches<<" files="<<num_files
<<" size="<<datasize<<std::endl;
- for(int i=0; i<num_files; i++){
- m_media_received_count++;
- std::string name = deSerializeString(is);
- std::string data = deSerializeLongString(is);
- // if name contains illegal characters, ignore the file
- if(!string_allowed(name, TEXTURENAME_ALLOWED_CHARS)){
- errorstream<<"Client: ignoring illegal file name "
- <<"sent by server: \""<<name<<"\""<<std::endl;
- continue;
- }
-
- bool success = loadMedia(data, name);
- if(success){
- verbosestream<<"Client: Loaded received media: "
- <<"\""<<name<<"\". Caching."<<std::endl;
- } else{
- infostream<<"Client: Failed to load received media: "
- <<"\""<<name<<"\". Not caching."<<std::endl;
- continue;
- }
+ if (num_files == 0)
+ return;
- bool did = fs::CreateAllDirs(getMediaCacheDir());
- if(!did){
- errorstream<<"Could not create media cache directory"
- <<std::endl;
- }
+ if (m_media_downloader == NULL ||
+ !m_media_downloader->isStarted()) {
+ const char *problem = m_media_downloader ?
+ "media has not been requested" :
+ "all media has been received already";
+ errorstream<<"Client: Received media but "
+ <<problem<<"! "
+ <<" bunch "<<bunch_i<<"/"<<num_bunches
+ <<" files="<<num_files
+ <<" size="<<datasize<<std::endl;
+ return;
+ }
- {
- std::map<std::string, std::string>::iterator n;
- n = m_media_name_sha1_map.find(name);
- if(n == m_media_name_sha1_map.end())
- errorstream<<"The server sent a file that has not "
- <<"been announced."<<std::endl;
- else
- m_media_cache.update_sha1(data);
- }
+ // Mesh update thread must be stopped while
+ // updating content definitions
+ assert(!m_mesh_update_thread.IsRunning());
+
+ for(u32 i=0; i<num_files; i++){
+ std::string name = deSerializeString(is);
+ std::string data = deSerializeLongString(is);
+ m_media_downloader->conventionalTransferDone(
+ name, data, this);
}
- ClientEvent event;
- event.type = CE_TEXTURES_UPDATED;
- m_client_event_queue.push_back(event);
+ if (m_media_downloader->isDone()) {
+ delete m_media_downloader;
+ m_media_downloader = NULL;
+ }
}
else if(command == TOCLIENT_TOOLDEF)
{
event.spawn_particle.expirationtime = expirationtime;
event.spawn_particle.size = size;
- event.add_particlespawner.collisiondetection =
+ event.spawn_particle.collisiondetection =
collisiondetection;
event.spawn_particle.texture = new std::string(texture);
m_client_event_queue.push_back(event);
}
else if(command == TOCLIENT_HUDCHANGE)
- {
+ {
std::string sdata;
v2f v2fdata;
u32 intdata = 0;
m_client_event_queue.push_back(event);
}
else if(command == TOCLIENT_HUD_SET_FLAGS)
- {
+ {
std::string datastring((char *)&data[2], datasize - 2);
std::istringstream is(datastring, std::ios_base::binary);
player->hud_flags &= ~mask;
player->hud_flags |= flags;
}
+ else if(command == TOCLIENT_HUD_SET_PARAM)
+ {
+ std::string datastring((char *)&data[2], datasize - 2);
+ std::istringstream is(datastring, std::ios_base::binary);
+
+ Player *player = m_env.getLocalPlayer();
+ assert(player != NULL);
+
+ u16 param = readU16(is);
+ std::string value = deSerializeString(is);
+
+ if(param == HUD_PARAM_HOTBAR_ITEMCOUNT && value.size() == 4){
+ s32 hotbar_itemcount = readS32((u8*) value.c_str());
+ if(hotbar_itemcount > 0 && hotbar_itemcount <= HUD_HOTBAR_ITEMCOUNT_MAX)
+ player->hud_hotbar_itemcount = hotbar_itemcount;
+ } else if (param == HUD_PARAM_HOTBAR_IMAGE) {
+ ((LocalPlayer *) player)->hotbar_image = value;
+ } else if (param == HUD_PARAM_HOTBAR_SELECTED_IMAGE) {
+ ((LocalPlayer *) player)->hotbar_selected_image = value;
+ }
+ }
else
{
infostream<<"Client: Ignoring unknown command "
Send(0, data, true);
}
-void Client::sendInventoryFields(const std::string &formname,
+void Client::sendInventoryFields(const std::string &formname,
const std::map<std::string, std::string> &fields)
{
std::ostringstream os(std::ios_base::binary);
Send(0, data, true);
}
+void Client::sendBreath(u16 breath)
+{
+ DSTACK(__FUNCTION_NAME);
+ std::ostringstream os(std::ios_base::binary);
+
+ writeU16(os, TOSERVER_BREATH);
+ writeU16(os, breath);
+ // Make data buffer
+ std::string s = os.str();
+ SharedBuffer<u8> data((u8*)s.c_str(), s.size());
+ // Send as reliable
+ Send(0, data, true);
+}
+
void Client::sendRespawn()
{
DSTACK(__FUNCTION_NAME);
writeV3S32(&data[2], position);
writeV3S32(&data[2+12], speed);
writeS32(&data[2+12+12], pitch);
- writeS32(&data[2+12+12+4], yaw);
+ writeS32(&data[2+12+12+4], yaw);
writeU32(&data[2+12+12+4+4], keyPressed);
// Send as unreliable
Send(0, data, false);
}
}
-void Client::addNode(v3s16 p, MapNode n)
+void Client::addNode(v3s16 p, MapNode n, bool remove_metadata)
{
TimeTaker timer1("Client::addNode()");
try
{
//TimeTaker timer3("Client::addNode(): addNodeAndUpdate");
- m_env.getMap().addNodeAndUpdate(p, n, modified_blocks);
+ m_env.getMap().addNodeAndUpdate(p, n, modified_blocks, remove_metadata);
}
catch(InvalidPositionException &e)
{}
return player->hp;
}
+u16 Client::getBreath()
+{
+ Player *player = m_env.getLocalPlayer();
+ assert(player != NULL);
+ return player->getBreath();
+}
+
bool Client::getChatMessage(std::wstring &message)
{
if(m_chat_queue.size() == 0)
return m_client_event_queue.pop_front();
}
+float Client::mediaReceiveProgress()
+{
+ if (m_media_downloader)
+ return m_media_downloader->getProgress();
+ else
+ return 1.0; // downloader only exists when not yet done
+}
+
void draw_load_screen(const std::wstring &text,
IrrlichtDevice* device, gui::IGUIFont* font,
float dtime=0 ,int percent=0, bool clouds=true);
infostream<<"Client::afterContentReceived() started"<<std::endl;
assert(m_itemdef_received);
assert(m_nodedef_received);
- assert(texturesReceived());
+ assert(mediaReceived());
- // remove the information about which checksum each texture
- // ought to have
- m_media_name_sha1_map.clear();
-
// Rebuild inherited images and recreate textures
infostream<<"- Rebuilding images and textures"<<std::endl;
m_tsrc->rebuildImagesAndTextures();
- // Update texture atlas
- infostream<<"- Updating texture atlas"<<std::endl;
- if(g_settings->getBool("enable_texture_atlas"))
- m_tsrc->buildMainAtlas(this);
-
// Rebuild shaders
+ infostream<<"- Rebuilding shaders"<<std::endl;
m_shsrc->rebuildShaders();
// Update node aliases