X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fclient.cpp;h=7b962cd94acb9d3ffc347f5d3a0a8e64351426bf;hb=04cc9de8f2fbcb11f133c88f02fc11504b3ea6f3;hp=68815e8e67ecc284baddaacfffa55fc77c6b4bef;hpb=009149a073ac02dd412af3c203979157976c0dd8;p=minetest.git diff --git a/src/client.cpp b/src/client.cpp index 68815e8e6..7b962cd94 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -20,192 +20,36 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include +#include #include -#include "jthread/jmutexautolock.h" +#include "threading/mutex_auto_lock.h" +#include "util/auth.h" #include "util/directiontables.h" #include "util/pointedthing.h" #include "util/serialize.h" #include "util/string.h" +#include "util/srp.h" #include "client.h" #include "network/clientopcodes.h" -#include "main.h" #include "filesys.h" -#include "porting.h" #include "mapblock_mesh.h" #include "mapblock.h" -#include "settings.h" +#include "minimap.h" +#include "mods.h" #include "profiler.h" #include "gettext.h" -#include "log.h" -#include "nodemetadata.h" -#include "itemdef.h" -#include "shader.h" #include "clientmap.h" #include "clientmedia.h" -#include "sound.h" -#include "IMeshCache.h" -#include "config.h" #include "version.h" #include "drawscene.h" -#include "subgame.h" -#include "server.h" -#include "database.h" #include "database-sqlite3.h" +#include "serialization.h" +#include "guiscalingfilter.h" +#include "script/clientscripting.h" +#include "game.h" extern gui::IGUIEnvironment* guienv; -/* - QueuedMeshUpdate -*/ - -QueuedMeshUpdate::QueuedMeshUpdate(): - p(-1337,-1337,-1337), - data(NULL), - ack_block_to_server(false) -{ -} - -QueuedMeshUpdate::~QueuedMeshUpdate() -{ - if(data) - delete data; -} - -/* - MeshUpdateQueue -*/ - -MeshUpdateQueue::MeshUpdateQueue() -{ -} - -MeshUpdateQueue::~MeshUpdateQueue() -{ - JMutexAutoLock lock(m_mutex); - - for(std::vector::iterator - i = m_queue.begin(); - i != m_queue.end(); i++) - { - 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) -{ - DSTACK(__FUNCTION_NAME); - - assert(data); - - JMutexAutoLock lock(m_mutex); - - if(urgent) - m_urgents.insert(p); - - /* - Find if block is already in queue. - If it is, update the data and quit. - */ - for(std::vector::iterator - i = m_queue.begin(); - i != m_queue.end(); i++) - { - 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; - } - } - - /* - 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::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; -} - -/* - MeshUpdateThread -*/ - -void * MeshUpdateThread::Thread() -{ - ThreadStarted(); - - log_register_thread("MeshUpdateThread"); - - DSTACK(__FUNCTION_NAME); - - BEGIN_DEBUG_EXCEPTION_HANDLER - - porting::setThreadName("MeshUpdateThread"); - - while(!StopRequested()) - { - QueuedMeshUpdate *q = m_queue_in.pop(); - if(q == NULL) - { - sleep_ms(3); - continue; - } - - 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; - - m_queue_out.push_back(r); - - delete q; - } - - END_DEBUG_EXCEPTION_HANDLER(errorstream) - - return NULL; -} - /* Client */ @@ -221,7 +65,8 @@ Client::Client( IWritableNodeDefManager *nodedef, ISoundManager *sound, MtEventManager *event, - bool ipv6 + bool ipv6, + GameUIFlags *game_ui_flags ): m_packetcounter_timer(0.0), m_connection_reinit_timer(0.1), @@ -236,7 +81,7 @@ Client::Client( m_event(event), m_mesh_update_thread(this), m_env( - new ClientMap(this, this, control, + new ClientMap(this, control, device->getSceneManager()->getRootSceneNode(), device->getSceneManager(), 666), device->getSceneManager(), @@ -245,19 +90,23 @@ Client::Client( m_particle_manager(&m_env), m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, ipv6, this), m_device(device), + m_camera(NULL), + m_minimap_disabled_by_server(false), m_server_ser_ver(SER_FMT_VER_INVALID), + m_proto_ver(0), m_playeritem(0), m_inventory_updated(false), m_inventory_from_server(NULL), m_inventory_from_server_age(0.0), - m_show_highlighted(false), m_animation_time(0), m_crack_level(-1), m_crack_pos(0,0,0), - m_highlighted_pos(0,0,0), m_map_seed(0), m_password(password), + m_chosen_auth_mech(AUTH_MECHANISM_NONE), + m_auth_data(NULL), m_access_denied(false), + m_access_denied_reconnect(false), m_itemdef_received(false), m_nodedef_received(false), m_media_downloader(new ClientMediaDownloader()), @@ -266,47 +115,118 @@ Client::Client( m_time_of_day_update_timer(0), m_recommended_send_interval(0.1), m_removed_sounds_check_timer(0), - m_state(LC_Created) + m_state(LC_Created), + m_localdb(NULL), + m_script(NULL), + m_mod_storage_save_timer(10.0f), + m_game_ui_flags(game_ui_flags), + m_shutdown(false) { - /* - Add local player - */ - { - Player *player = new LocalPlayer(this, playername); + // Add local player + m_env.setLocalPlayer(new LocalPlayer(this, playername)); + + m_minimap = new Minimap(device, this); + m_cache_save_interval = g_settings->getU16("server_map_save_interval"); + + m_modding_enabled = g_settings->getBool("enable_client_modding"); + m_script = new ClientScripting(this); + m_env.setScript(m_script); + m_script->setEnv(&m_env); +} + +void Client::initMods() +{ + m_script->loadMod(getBuiltinLuaPath() + DIR_DELIM "init.lua", BUILTIN_MOD_NAME); + + // If modding is not enabled, don't load mods, just builtin + if (!m_modding_enabled) { + return; + } + + ClientModConfiguration modconf(getClientModsLuaPath()); + std::vector mods = modconf.getMods(); + std::vector unsatisfied_mods = modconf.getUnsatisfiedMods(); + // complain about mods with unsatisfied dependencies + if (!modconf.isConsistent()) { + modconf.printUnsatisfiedModsError(); + } - m_env.addPlayer(player); + // Print mods + infostream << "Client Loading mods: "; + for (std::vector::const_iterator i = mods.begin(); + i != mods.end(); ++i) { + infostream << (*i).name << " "; } - m_cache_smooth_lighting = g_settings->getBool("smooth_lighting"); - m_cache_enable_shaders = g_settings->getBool("enable_shaders"); + infostream << std::endl; + // Load and run "mod" scripts + for (std::vector::const_iterator it = mods.begin(); + it != mods.end(); ++it) { + const ModSpec &mod = *it; + if (!string_allowed(mod.name, MODNAME_ALLOWED_CHARS)) { + throw ModError("Error loading mod \"" + mod.name + + "\": Mod name does not follow naming conventions: " + "Only chararacters [a-z0-9_] are allowed."); + } + std::string script_path = mod.path + DIR_DELIM + "init.lua"; + infostream << " [" << padStringRight(mod.name, 12) << "] [\"" + << script_path << "\"]" << std::endl; + m_script->loadMod(script_path, mod.name); + } +} + +const std::string &Client::getBuiltinLuaPath() +{ + static const std::string builtin_dir = porting::path_share + DIR_DELIM + "builtin"; + return builtin_dir; +} + +const std::string &Client::getClientModsLuaPath() +{ + static const std::string clientmods_dir = porting::path_share + DIR_DELIM + "clientmods"; + return clientmods_dir; +} + +const std::vector& Client::getMods() const +{ + static std::vector client_modspec_temp; + return client_modspec_temp; +} + +const ModSpec* Client::getModSpec(const std::string &modname) const +{ + return NULL; } void Client::Stop() { + m_shutdown = true; + // Don't disable this part when modding is disabled, it's used in builtin + m_script->on_shutdown(); //request all client managed threads to stop - m_mesh_update_thread.Stop(); - if (localdb != NULL) { - actionstream << "Local map saving ended" << std::endl; - localdb->endSave(); - delete localserver; + m_mesh_update_thread.stop(); + // Save local server map + if (m_localdb) { + infostream << "Local map saving ended." << std::endl; + m_localdb->endSave(); } + + delete m_script; } bool Client::isShutdown() { - - if (!m_mesh_update_thread.IsRunning()) return true; - - return false; + return m_shutdown || !m_mesh_update_thread.isRunning(); } Client::~Client() { + m_shutdown = true; m_con.Disconnect(); - m_mesh_update_thread.Stop(); - m_mesh_update_thread.Wait(); - while(!m_mesh_update_thread.m_queue_out.empty()) { + 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; } @@ -315,27 +235,29 @@ Client::~Client() delete m_inventory_from_server; // Delete detached inventories - for(std::map::iterator + for (UNORDERED_MAP::iterator i = m_detached_inventories.begin(); - i != m_detached_inventories.end(); i++){ + i != m_detached_inventories.end(); ++i) { delete i->second; } // cleanup 3d model meshes on client shutdown while (m_device->getSceneManager()->getMeshCache()->getMeshCount() != 0) { - scene::IAnimatedMesh * mesh = + scene::IAnimatedMesh *mesh = m_device->getSceneManager()->getMeshCache()->getMeshByIndex(0); if (mesh != NULL) m_device->getSceneManager()->getMeshCache()->removeMesh(mesh); } + + delete m_minimap; } void Client::connect(Address address, const std::string &address_name, bool is_local_server) { - DSTACK(__FUNCTION_NAME); + DSTACK(FUNCTION_NAME); initLocalMapSaving(address, address_name, is_local_server); @@ -345,7 +267,7 @@ void Client::connect(Address address, void Client::step(float dtime) { - DSTACK(__FUNCTION_NAME); + DSTACK(FUNCTION_NAME); // Limit a bit if(dtime > 2.0) @@ -393,31 +315,34 @@ void Client::step(float dtime) if(counter <= 0.0) { counter = 2.0; - Player *myplayer = m_env.getLocalPlayer(); - assert(myplayer != NULL); - // Send TOSERVER_INIT - // [0] u16 TOSERVER_INIT - // [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) + LocalPlayer *myplayer = m_env.getLocalPlayer(); + FATAL_ERROR_IF(myplayer == NULL, "Local player not found in environment."); - char pName[PLAYERNAME_SIZE]; - char pPassword[PASSWORD_SIZE]; + u16 proto_version_min = g_settings->getFlag("send_pre_v25_init") ? + CLIENT_PROTOCOL_VERSION_MIN_LEGACY : CLIENT_PROTOCOL_VERSION_MIN; - snprintf(pName, PLAYERNAME_SIZE, "%s", myplayer->getName()); - snprintf(pPassword, PASSWORD_SIZE, "%s", m_password.c_str()); + if (proto_version_min < 25) { + // Send TOSERVER_INIT_LEGACY + // [0] u16 TOSERVER_INIT_LEGACY + // [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) - NetworkPacket* pkt = new NetworkPacket(TOSERVER_INIT, - 1 + PLAYERNAME_SIZE + PASSWORD_SIZE + 2 + 2); + char pName[PLAYERNAME_SIZE]; + char pPassword[PASSWORD_SIZE]; + memset(pName, 0, PLAYERNAME_SIZE * sizeof(char)); + memset(pPassword, 0, PASSWORD_SIZE * sizeof(char)); - *pkt << (u8) SER_FMT_VER_HIGHEST_READ; - pkt->putRawString(pName,PLAYERNAME_SIZE); - pkt->putRawString(pPassword, PASSWORD_SIZE); - *pkt << (u16) CLIENT_PROTOCOL_VERSION_MIN << (u16) CLIENT_PROTOCOL_VERSION_MAX; + std::string hashed_password = translate_password(myplayer->getName(), m_password); + snprintf(pName, PLAYERNAME_SIZE, "%s", myplayer->getName()); + snprintf(pPassword, PASSWORD_SIZE, "%s", hashed_password.c_str()); - Send(pkt); + sendLegacyInit(pName, pPassword); + } + if (CLIENT_PROTOCOL_VERSION_MAX >= 25) + sendInit(myplayer->getName()); } // Not connected, return @@ -436,8 +361,9 @@ void Client::step(float dtime) ScopeProfiler sp(g_profiler, "Client: map timer and unload"); std::vector deleted_blocks; m_env.getMap().timerUpdate(map_timer_and_unload_dtime, - g_settings->getFloat("client_unload_unused_data_timeout"), - &deleted_blocks); + g_settings->getFloat("client_unload_unused_data_timeout"), + g_settings->getS32("client_mapblock_limit"), + &deleted_blocks); /* Send info to server @@ -457,19 +383,8 @@ void Client::step(float dtime) [3+6] v3s16 pos_1 ... */ - NetworkPacket* pkt = new NetworkPacket(TOSERVER_DELETEDBLOCKS, 1 + sizeof(v3s16) * sendlist.size()); - - *pkt << (u8) sendlist.size(); - u32 k = 0; - for(std::vector::iterator - j = sendlist.begin(); - j != sendlist.end(); ++j) { - *pkt << *j; - k++; - } - - Send(pkt); + sendDeletedBlocks(sendlist); if(i == deleted_blocks.end()) break; @@ -512,12 +427,13 @@ void Client::step(float dtime) ClientEvent event; event.type = CE_PLAYER_DAMAGE; event.player_damage.amount = damage; - m_client_event_queue.push_back(event); + m_client_event_queue.push(event); } } - else if(event.type == CEE_PLAYER_BREATH) { - u16 breath = event.player_breath.amount; - sendBreath(breath); + // Protocol v29 or greater obsoleted this event + else if (event.type == CEE_PLAYER_BREATH && m_proto_ver < 29) { + u16 breath = event.player_breath.amount; + sendBreath(breath); } } @@ -551,39 +467,52 @@ void Client::step(float dtime) */ { int num_processed_meshes = 0; - while(!m_mesh_update_thread.m_queue_out.empty()) + while (!m_mesh_update_thread.m_queue_out.empty()) { num_processed_meshes++; + + MinimapMapblock *minimap_mapblock = NULL; + bool do_mapper_update = true; + MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_frontNoEx(); MapBlock *block = m_env.getMap().getBlockNoCreateNoEx(r.p); - if(block) { + if (block) { // Delete the old mesh - if(block->mesh != NULL) - { - // TODO: Remove hardware buffers of meshbuffers of block->mesh + if (block->mesh != NULL) { delete block->mesh; block->mesh = NULL; } - // Replace with the new mesh - block->mesh = r.mesh; + if (r.mesh) { + minimap_mapblock = r.mesh->moveMinimapMapblock(); + if (minimap_mapblock == NULL) + do_mapper_update = false; + } + + if (r.mesh && r.mesh->getMesh()->getMeshBufferCount() == 0) { + delete r.mesh; + } else { + // Replace with the new mesh + block->mesh = r.mesh; + } } else { delete r.mesh; } - if(r.ack_block_to_server) { + if (do_mapper_update) + m_minimap->addBlock(r.p, minimap_mapblock); + + if (r.ack_block_to_server) { /* Acknowledge block [0] u8 count [1] v3s16 pos_0 */ - NetworkPacket* pkt = new NetworkPacket(TOSERVER_GOTBLOCKS, 1 + 6); - *pkt << (u8) 1 << r.p; - Send(pkt); + sendGotBlocks(r.p); } } - if(num_processed_meshes > 0) + if (num_processed_meshes > 0) g_profiler->graphAdd("num_processed_meshes", num_processed_meshes); } @@ -617,7 +546,7 @@ void Client::step(float dtime) { // Do this every seconds after TOCLIENT_INVENTORY // Reset the locally changed inventory to the authoritative inventory - Player *player = m_env.getLocalPlayer(); + LocalPlayer *player = m_env.getLocalPlayer(); player->inventory = *m_inventory_from_server; m_inventory_updated = true; } @@ -627,10 +556,8 @@ void Client::step(float dtime) Update positions of sounds attached to objects */ { - for(std::map::iterator - i = m_sounds_to_objects.begin(); - i != m_sounds_to_objects.end(); i++) - { + for(UNORDERED_MAP::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); @@ -648,37 +575,44 @@ void Client::step(float dtime) if(m_removed_sounds_check_timer >= 2.32) { m_removed_sounds_check_timer = 0; // Find removed sounds and clear references to them - std::set removed_server_ids; - for(std::map::iterator - i = m_sounds_server_to_client.begin(); + std::vector removed_server_ids; + for(UNORDERED_MAP::iterator i = m_sounds_server_to_client.begin(); i != m_sounds_server_to_client.end();) { s32 server_id = i->first; int client_id = i->second; - i++; + ++i; if(!m_sound->soundExists(client_id)) { 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.insert(server_id); + removed_server_ids.push_back(server_id); } } // Sync to server if(!removed_server_ids.empty()) { - size_t server_ids = removed_server_ids.size(); - assert(server_ids <= 0xFFFF); - - NetworkPacket* pkt = new NetworkPacket(TOSERVER_REMOVED_SOUNDS, 2 + server_ids * 4); - - *pkt << (u16) (server_ids & 0xFFFF); - - for(std::set::iterator i = removed_server_ids.begin(); - i != removed_server_ids.end(); i++) - *pkt << *i; + sendRemovedSounds(removed_server_ids); + } + } - Send(pkt); + m_mod_storage_save_timer -= dtime; + if (m_mod_storage_save_timer <= 0.0f) { + verbosestream << "Saving registered mod storages." << std::endl; + m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval"); + for (UNORDERED_MAP::const_iterator + it = m_mod_storages.begin(); it != m_mod_storages.end(); ++it) { + if (it->second->isModified()) { + it->second->save(getModStoragePath()); + } } } + + // Write server map + if (m_localdb && m_localdb_save_interval.step(dtime, + m_cache_save_interval)) { + m_localdb->endSave(); + m_localdb->beginSave(); + } } bool Client::loadMedia(const std::string &data, const std::string &filename) @@ -705,7 +639,9 @@ bool Client::loadMedia(const std::string &data, const std::string &filename) // Create an irrlicht memory file io::IReadFile *rfile = irrfs->createMemoryReadFile( *data_rw, data_rw.getSize(), "_tempreadfile"); - assert(rfile); + + FATAL_ERROR_IF(!rfile, "Could not create irrlicht memory file."); + // Read image video::IImage *img = vdrv->createImageFromFile(rfile); if(!img){ @@ -760,14 +696,19 @@ bool Client::loadMedia(const std::string &data, const std::string &filename) // Virtual methods from con::PeerHandler void Client::peerAdded(con::Peer *peer) { - infostream<<"Client::peerAdded(): peer->id=" - <id<id=" + << peer->id << std::endl; } void Client::deletingPeer(con::Peer *peer, bool timeout) { - infostream<<"Client::deletingPeer(): " + infostream << "Client::deletingPeer(): " "Server Peer is getting deleted " - <<"(timeout="< &file_requests) +void Client::request_media(const std::vector &file_requests) { std::ostringstream os(std::ios_base::binary); writeU16(os, TOSERVER_REQUEST_MEDIA); size_t file_requests_size = file_requests.size(); - assert(file_requests_size <= 0xFFFF); + + FATAL_ERROR_IF(file_requests_size > 0xFFFF, "Unsupported number of file requests"); // Packet dynamicly resized - NetworkPacket* pkt = new NetworkPacket(TOSERVER_REQUEST_MEDIA, 2 + 0); + NetworkPacket pkt(TOSERVER_REQUEST_MEDIA, 2 + 0); - *pkt << (u16) (file_requests_size & 0xFFFF); + pkt << (u16) (file_requests_size & 0xFFFF); - for(std::list::const_iterator i = file_requests.begin(); + for(std::vector::const_iterator i = file_requests.begin(); i != file_requests.end(); ++i) { - *pkt << (*i); + pkt << (*i); } - Send(pkt); + Send(&pkt); - infostream<<"Client: Sending media request list to server (" - <getBool("enable_local_map_saving") || is_local_server) + if (!g_settings->getBool("enable_local_map_saving") || is_local_server) { return; + } const std::string world_path = porting::path_user + DIR_DELIM + "worlds" + DIR_DELIM + "server_" - + hostname + "_" + to_string(address.getPort()); + + hostname + "_" + std::to_string(address.getPort()); - SubgameSpec gamespec; + fs::CreateAllDirs(world_path); - if (!getWorldExists(world_path)) { - gamespec = findSubgame(g_settings->get("default_game")); - if (!gamespec.isValid()) - gamespec = findSubgame("minimal"); - } else { - gamespec = findWorldSubgame(world_path); - } - - if (!gamespec.isValid()) { - errorstream << "Couldn't find subgame for local map saving." << std::endl; - return; - } - - localserver = new Server(world_path, gamespec, false, false); - localdb = new Database_SQLite3(&(ServerMap&)localserver->getMap(), world_path); - localdb->beginSave(); + m_localdb = new Database_SQLite3(world_path); + m_localdb->beginSave(); actionstream << "Local map saving started, map will be saved at '" << world_path << "'" << std::endl; } void Client::ReceiveAll() { - DSTACK(__FUNCTION_NAME); + DSTACK(FUNCTION_NAME); u32 start_ms = porting::getTimeMs(); for(;;) { @@ -872,11 +799,10 @@ void Client::ReceiveAll() void Client::Receive() { - DSTACK(__FUNCTION_NAME); - SharedBuffer data; - u16 sender_peer_id; - u32 datasize = m_con.Receive(sender_peer_id, data); - ProcessData(*data, datasize, sender_peer_id); + DSTACK(FUNCTION_NAME); + NetworkPacket pkt; + m_con.Receive(&pkt); + ProcessData(&pkt); } inline void Client::handleCommand(NetworkPacket* pkt) @@ -888,19 +814,12 @@ inline void Client::handleCommand(NetworkPacket* 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) +void Client::ProcessData(NetworkPacket *pkt) { - DSTACK(__FUNCTION_NAME); - - // Ignore packets that don't even fit a command - if(datasize < 2) { - m_packetcounter.add(60000); - return; - } - - NetworkPacket* pkt = new NetworkPacket(data, datasize, sender_peer_id); + DSTACK(FUNCTION_NAME); ToClientCommand command = (ToClientCommand) pkt->getCommand(); + u32 sender_peer_id = pkt->getPeerId(); //infostream<<"Client: received command="<putLongString(tmp_os.str()); + pkt.putLongString(tmp_os.str()); - Send(pkt); + writePlayerPos(myplayer, &m_env.getClientMap(), &pkt); + + Send(&pkt); +} + +void Client::deleteAuthData() +{ + if (!m_auth_data) + return; + + switch (m_chosen_auth_mech) { + case AUTH_MECHANISM_FIRST_SRP: + break; + case AUTH_MECHANISM_SRP: + case AUTH_MECHANISM_LEGACY_PASSWORD: + srp_user_delete((SRPUser *) m_auth_data); + m_auth_data = NULL; + break; + case AUTH_MECHANISM_NONE: + break; + } + m_chosen_auth_mech = AUTH_MECHANISM_NONE; +} + + +AuthMechanism Client::choseAuthMech(const u32 mechs) +{ + if (mechs & AUTH_MECHANISM_SRP) + return AUTH_MECHANISM_SRP; + + if (mechs & AUTH_MECHANISM_FIRST_SRP) + return AUTH_MECHANISM_FIRST_SRP; + + if (mechs & AUTH_MECHANISM_LEGACY_PASSWORD) + return AUTH_MECHANISM_LEGACY_PASSWORD; + + return AUTH_MECHANISM_NONE; +} + +void Client::sendLegacyInit(const char* playerName, const char* playerPassword) +{ + NetworkPacket pkt(TOSERVER_INIT_LEGACY, + 1 + PLAYERNAME_SIZE + PASSWORD_SIZE + 2 + 2); + + u16 proto_version_min = g_settings->getFlag("send_pre_v25_init") ? + CLIENT_PROTOCOL_VERSION_MIN_LEGACY : CLIENT_PROTOCOL_VERSION_MIN; + + pkt << (u8) SER_FMT_VER_HIGHEST_READ; + pkt.putRawString(playerName,PLAYERNAME_SIZE); + pkt.putRawString(playerPassword, PASSWORD_SIZE); + pkt << (u16) proto_version_min << (u16) CLIENT_PROTOCOL_VERSION_MAX; + + Send(&pkt); +} + +void Client::sendInit(const std::string &playerName) +{ + NetworkPacket pkt(TOSERVER_INIT, 1 + 2 + 2 + (1 + playerName.size())); + + // we don't support network compression yet + u16 supp_comp_modes = NETPROTO_COMPRESSION_NONE; + + u16 proto_version_min = g_settings->getFlag("send_pre_v25_init") ? + CLIENT_PROTOCOL_VERSION_MIN_LEGACY : CLIENT_PROTOCOL_VERSION_MIN; + + pkt << (u8) SER_FMT_VER_HIGHEST_READ << (u16) supp_comp_modes; + pkt << (u16) proto_version_min << (u16) CLIENT_PROTOCOL_VERSION_MAX; + pkt << playerName; + + Send(&pkt); +} + +void Client::startAuth(AuthMechanism chosen_auth_mechanism) +{ + m_chosen_auth_mech = chosen_auth_mechanism; + + switch (chosen_auth_mechanism) { + case AUTH_MECHANISM_FIRST_SRP: { + // send srp verifier to server + std::string verifier; + std::string salt; + generate_srp_verifier_and_salt(getPlayerName(), m_password, + &verifier, &salt); + + NetworkPacket resp_pkt(TOSERVER_FIRST_SRP, 0); + resp_pkt << salt << verifier << (u8)((m_password == "") ? 1 : 0); + + Send(&resp_pkt); + break; + } + case AUTH_MECHANISM_SRP: + case AUTH_MECHANISM_LEGACY_PASSWORD: { + u8 based_on = 1; + + if (chosen_auth_mechanism == AUTH_MECHANISM_LEGACY_PASSWORD) { + m_password = translate_password(getPlayerName(), m_password); + based_on = 0; + } + + std::string playername_u = lowercase(getPlayerName()); + m_auth_data = srp_user_new(SRP_SHA256, SRP_NG_2048, + getPlayerName().c_str(), playername_u.c_str(), + (const unsigned char *) m_password.c_str(), + m_password.length(), NULL, NULL); + char *bytes_A = 0; + size_t len_A = 0; + SRP_Result res = srp_user_start_authentication( + (struct SRPUser *) m_auth_data, NULL, NULL, 0, + (unsigned char **) &bytes_A, &len_A); + FATAL_ERROR_IF(res != SRP_OK, "Creating local SRP user failed."); + + NetworkPacket resp_pkt(TOSERVER_SRP_BYTES_A, 0); + resp_pkt << std::string(bytes_A, len_A) << based_on; + Send(&resp_pkt); + break; + } + case AUTH_MECHANISM_NONE: + break; // not handled in this method + } +} + +void Client::sendDeletedBlocks(std::vector &blocks) +{ + NetworkPacket pkt(TOSERVER_DELETEDBLOCKS, 1 + sizeof(v3s16) * blocks.size()); + + pkt << (u8) blocks.size(); + + u32 k = 0; + for(std::vector::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 &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::iterator i = soundList.begin(); + i != soundList.end(); ++i) + pkt << *i; + + Send(&pkt); } void Client::sendNodemetaFields(v3s16 p, const std::string &formname, - const std::map &fields) + const StringMap &fields) { size_t fields_size = fields.size(); - assert(fields_size <= 0xFFFF); - NetworkPacket* pkt = new NetworkPacket(TOSERVER_NODEMETA_FIELDS, 0); + FATAL_ERROR_IF(fields_size > 0xFFFF, "Unsupported number of nodemeta fields"); + + NetworkPacket pkt(TOSERVER_NODEMETA_FIELDS, 0); - *pkt << p << formname << (u16) (fields_size & 0xFFFF); + pkt << p << formname << (u16) (fields_size & 0xFFFF); - for(std::map::const_iterator - i = fields.begin(); i != fields.end(); i++) { - const std::string &name = i->first; - const std::string &value = i->second; - *pkt << name; - pkt->putLongString(value); + StringMap::const_iterator it; + for (it = fields.begin(); it != fields.end(); ++it) { + const std::string &name = it->first; + const std::string &value = it->second; + pkt << name; + pkt.putLongString(value); } - Send(pkt); + Send(&pkt); } void Client::sendInventoryFields(const std::string &formname, - const std::map &fields) + const StringMap &fields) { size_t fields_size = fields.size(); - assert(fields_size <= 0xFFFF); + FATAL_ERROR_IF(fields_size > 0xFFFF, "Unsupported number of inventory fields"); - NetworkPacket* pkt = new NetworkPacket(TOSERVER_INVENTORY_FIELDS, 0); - *pkt << formname << (u16) (fields_size & 0xFFFF); + NetworkPacket pkt(TOSERVER_INVENTORY_FIELDS, 0); + pkt << formname << (u16) (fields_size & 0xFFFF); - for(std::map::const_iterator - i = fields.begin(); i != fields.end(); i++) { - const std::string &name = i->first; - const std::string &value = i->second; - *pkt << name; - pkt->putLongString(value); + StringMap::const_iterator it; + for (it = fields.begin(); it != fields.end(); ++it) { + const std::string &name = it->first; + const std::string &value = it->second; + pkt << name; + pkt.putLongString(value); } - Send(pkt); + Send(&pkt); } void Client::sendInventoryAction(InventoryAction *a) @@ -1045,84 +1155,94 @@ void Client::sendInventoryAction(InventoryAction *a) // Make data buffer std::string s = os.str(); - NetworkPacket* pkt = new NetworkPacket(TOSERVER_INVENTORY_ACTION, s.size()); - pkt->putRawString(s.c_str(),s.size()); + NetworkPacket pkt(TOSERVER_INVENTORY_ACTION, s.size()); + pkt.putRawString(s.c_str(),s.size()); - Send(pkt); + Send(&pkt); } void Client::sendChatMessage(const std::wstring &message) { - NetworkPacket* pkt = new NetworkPacket(TOSERVER_CHAT_MESSAGE, 2 + message.size() * sizeof(u16)); + NetworkPacket pkt(TOSERVER_CHAT_MESSAGE, 2 + message.size() * sizeof(u16)); - *pkt << message; + pkt << message; - Send(pkt); + Send(&pkt); } -void Client::sendChangePassword(const std::wstring &oldpassword, - const std::wstring &newpassword) +void Client::sendChangePassword(const std::string &oldpassword, + const std::string &newpassword) { - Player *player = m_env.getLocalPlayer(); - if(player == NULL) + LocalPlayer *player = m_env.getLocalPlayer(); + if (player == NULL) return; std::string playername = player->getName(); - std::string oldpwd = translatePassword(playername, oldpassword); - std::string newpwd = translatePassword(playername, newpassword); + if (m_proto_ver >= 25) { + // get into sudo mode and then send new password to server + m_password = oldpassword; + m_new_password = newpassword; + startAuth(choseAuthMech(m_sudo_auth_methods)); + } else { + std::string oldpwd = translate_password(playername, oldpassword); + std::string newpwd = translate_password(playername, newpassword); - NetworkPacket* pkt = new NetworkPacket(TOSERVER_PASSWORD, 2 * PASSWORD_SIZE); + NetworkPacket pkt(TOSERVER_PASSWORD_LEGACY, 2 * PASSWORD_SIZE); - for(u8 i = 0; i < PASSWORD_SIZE; i++) { - *pkt << (u8) (i < oldpwd.length() ? oldpwd[i] : 0); - } + for (u8 i = 0; i < PASSWORD_SIZE; i++) { + pkt << (u8) (i < oldpwd.length() ? oldpwd[i] : 0); + } - for(u8 i = 0; i < PASSWORD_SIZE; i++) { - *pkt << (u8) (i < newpwd.length() ? newpwd[i] : 0); + for (u8 i = 0; i < PASSWORD_SIZE; i++) { + pkt << (u8) (i < newpwd.length() ? newpwd[i] : 0); + } + Send(&pkt); } - - Send(pkt); } void Client::sendDamage(u8 damage) { - DSTACK(__FUNCTION_NAME); + DSTACK(FUNCTION_NAME); - NetworkPacket* pkt = new NetworkPacket(TOSERVER_DAMAGE, sizeof(u8)); - *pkt << damage; - Send(pkt); + NetworkPacket pkt(TOSERVER_DAMAGE, sizeof(u8)); + pkt << damage; + Send(&pkt); } void Client::sendBreath(u16 breath) { - DSTACK(__FUNCTION_NAME); + DSTACK(FUNCTION_NAME); - NetworkPacket* pkt = new NetworkPacket(TOSERVER_BREATH, sizeof(u16)); - *pkt << breath; - Send(pkt); + // Protocol v29 make this obsolete + if (m_proto_ver >= 29) + return; + + NetworkPacket pkt(TOSERVER_BREATH, sizeof(u16)); + pkt << breath; + Send(&pkt); } void Client::sendRespawn() { - DSTACK(__FUNCTION_NAME); + DSTACK(FUNCTION_NAME); - NetworkPacket* pkt = new NetworkPacket(TOSERVER_RESPAWN, 0); - Send(pkt); + NetworkPacket pkt(TOSERVER_RESPAWN, 0); + Send(&pkt); } void Client::sendReady() { - DSTACK(__FUNCTION_NAME); + DSTACK(FUNCTION_NAME); - NetworkPacket* pkt = new NetworkPacket(TOSERVER_CLIENT_READY, - 1 + 1 + 1 + 1 + 2 + sizeof(char) * strlen(minetest_version_hash)); + NetworkPacket pkt(TOSERVER_CLIENT_READY, + 1 + 1 + 1 + 1 + 2 + sizeof(char) * strlen(g_version_hash)); - *pkt << (u8) VERSION_MAJOR << (u8) VERSION_MINOR << (u8) VERSION_PATCH_ORIG - << (u8) 0 << (u16) strlen(minetest_version_hash); + pkt << (u8) VERSION_MAJOR << (u8) VERSION_MINOR << (u8) VERSION_PATCH + << (u8) 0 << (u16) strlen(g_version_hash); - pkt->putRawString(minetest_version_hash, (u16) strlen(minetest_version_hash)); - Send(pkt); + pkt.putRawString(g_version_hash, (u16) strlen(g_version_hash)); + Send(&pkt); } void Client::sendPlayerPos() @@ -1131,59 +1251,53 @@ void Client::sendPlayerPos() if(myplayer == NULL) return; + ClientMap &map = m_env.getClientMap(); + + u8 camera_fov = map.getCameraFov(); + u8 wanted_range = map.getControl().wanted_range; + // Save bandwidth by only updating position when something changed if(myplayer->last_position == myplayer->getPosition() && - myplayer->last_speed == myplayer->getSpeed() && - myplayer->last_pitch == myplayer->getPitch() && - myplayer->last_yaw == myplayer->getYaw() && - myplayer->last_keyPressed == myplayer->keyPressed) + myplayer->last_speed == myplayer->getSpeed() && + myplayer->last_pitch == myplayer->getPitch() && + myplayer->last_yaw == myplayer->getYaw() && + myplayer->last_keyPressed == myplayer->keyPressed && + myplayer->last_camera_fov == camera_fov && + myplayer->last_wanted_range == wanted_range) return; - myplayer->last_position = myplayer->getPosition(); - myplayer->last_speed = myplayer->getSpeed(); - myplayer->last_pitch = myplayer->getPitch(); - myplayer->last_yaw = myplayer->getYaw(); - myplayer->last_keyPressed = myplayer->keyPressed; + myplayer->last_position = myplayer->getPosition(); + myplayer->last_speed = myplayer->getSpeed(); + myplayer->last_pitch = myplayer->getPitch(); + myplayer->last_yaw = myplayer->getYaw(); + myplayer->last_keyPressed = myplayer->keyPressed; + myplayer->last_camera_fov = camera_fov; + myplayer->last_wanted_range = wanted_range; + + //infostream << "Sending Player Position information" << std::endl; u16 our_peer_id; { - //JMutexAutoLock lock(m_con_mutex); //bulk comment-out + //MutexAutoLock lock(m_con_mutex); //bulk comment-out our_peer_id = m_con.GetPeerID(); } // Set peer id if not set already if(myplayer->peer_id == PEER_ID_INEXISTENT) myplayer->peer_id = our_peer_id; - // Check that an existing peer_id is the same as the connection's - assert(myplayer->peer_id == our_peer_id); - v3f pf = myplayer->getPosition(); - v3f sf = myplayer->getSpeed(); - s32 pitch = myplayer->getPitch() * 100; - s32 yaw = myplayer->getYaw() * 100; - u32 keyPressed = myplayer->keyPressed; - - v3s32 position(pf.X*100, pf.Y*100, pf.Z*100); - v3s32 speed(sf.X*100, sf.Y*100, sf.Z*100); - /* - Format: - [0] v3s32 position*100 - [12] v3s32 speed*100 - [12+12] s32 pitch*100 - [12+12+4] s32 yaw*100 - [12+12+4+4] u32 keyPressed - */ + assert(myplayer->peer_id == our_peer_id); - NetworkPacket* pkt = new NetworkPacket(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4); + NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4 + 1 + 1); - *pkt << position << speed << pitch << yaw << keyPressed; + writePlayerPos(myplayer, &map, &pkt); - Send(pkt); + Send(&pkt); } void Client::sendPlayerItem(u16 item) { - Player *myplayer = m_env.getLocalPlayer(); + LocalPlayer *myplayer = m_env.getLocalPlayer(); if(myplayer == NULL) return; @@ -1192,15 +1306,13 @@ void Client::sendPlayerItem(u16 item) // Set peer id if not set already if(myplayer->peer_id == PEER_ID_INEXISTENT) myplayer->peer_id = our_peer_id; - - // Check that an existing peer_id is the same as the connection's assert(myplayer->peer_id == our_peer_id); - NetworkPacket* pkt = new NetworkPacket(TOSERVER_PLAYERITEM, 2); + NetworkPacket pkt(TOSERVER_PLAYERITEM, 2); - *pkt << item; + pkt << item; - Send(pkt); + Send(&pkt); } void Client::removeNode(v3s16 p) @@ -1220,6 +1332,11 @@ void Client::removeNode(v3s16 p) } } +MapNode Client::getNode(v3s16 p, bool *is_valid_position) +{ + return m_env.getMap().getNodeNoEx(p, is_valid_position); +} + void Client::addNode(v3s16 p, MapNode n, bool remove_metadata) { //TimeTaker timer1("Client::addNode()"); @@ -1266,7 +1383,7 @@ bool Client::getLocalInventoryUpdated() // Copies the inventory of the local player to parameter void Client::getLocalInventory(Inventory &dst) { - Player *player = m_env.getLocalPlayer(); + LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); dst = player->inventory; } @@ -1279,15 +1396,16 @@ Inventory* Client::getInventory(const InventoryLocation &loc) break; case InventoryLocation::CURRENT_PLAYER: { - Player *player = m_env.getLocalPlayer(); + LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); return &player->inventory; } break; case InventoryLocation::PLAYER: { - Player *player = m_env.getPlayer(loc.name.c_str()); - if(!player) + // Check if we are working with local player inventory + LocalPlayer *player = m_env.getLocalPlayer(); + if (!player || strcmp(player->getName(), loc.name.c_str()) != 0) return NULL; return &player->inventory; } @@ -1302,13 +1420,14 @@ Inventory* Client::getInventory(const InventoryLocation &loc) break; case InventoryLocation::DETACHED: { - if(m_detached_inventories.count(loc.name) == 0) + if (m_detached_inventories.count(loc.name) == 0) return NULL; return m_detached_inventories[loc.name]; } break; default: - assert(0); + FATAL_ERROR("Invalid inventory location type."); + break; } return NULL; } @@ -1329,49 +1448,6 @@ void Client::inventoryAction(InventoryAction *a) delete a; } -ClientActiveObject * Client::getSelectedActiveObject( - f32 max_d, - v3f from_pos_f_on_map, - core::line3d shootline_on_map - ) -{ - std::vector objects; - - m_env.getActiveObjects(from_pos_f_on_map, max_d, objects); - - // Sort them. - // After this, the closest object is the first in the array. - std::sort(objects.begin(), objects.end()); - - for(unsigned int i=0; i *selection_box = obj->getSelectionBox(); - if(selection_box == NULL) - continue; - - v3f pos = obj->getPosition(); - - core::aabbox3d offsetted_box( - selection_box->MinEdge + pos, - selection_box->MaxEdge + pos - ); - - if(offsetted_box.intersectsWithLine(shootline_on_map)) - { - return obj; - } - } - - return NULL; -} - -std::list Client::getConnectedPlayerNames() -{ - return m_env.getPlayerNames(); -} - float Client::getAnimationTime() { return m_animation_time; @@ -1382,13 +1458,9 @@ int Client::getCrackLevel() return m_crack_level; } -void Client::setHighlighted(v3s16 pos, bool show_highlighted) +v3s16 Client::getCrackPos() { - m_show_highlighted = show_highlighted; - v3s16 old_highlighted_pos = m_highlighted_pos; - m_highlighted_pos = pos; - addUpdateMeshTaskForNode(old_highlighted_pos, false, true); - addUpdateMeshTaskForNode(m_highlighted_pos, false, true); + return m_crack_pos; } void Client::setCrack(int level, v3s16 pos) @@ -1413,23 +1485,17 @@ void Client::setCrack(int level, v3s16 pos) u16 Client::getHP() { - Player *player = m_env.getLocalPlayer(); + LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); 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 false; - message = m_chat_queue.pop_front(); + message = m_chat_queue.front(); + m_chat_queue.pop(); return true; } @@ -1439,47 +1505,37 @@ void Client::typeChatMessage(const std::wstring &message) if(message == L"") return; + // If message was ate by script API, don't send it to server + if (m_script->on_sending_message(wide_to_utf8(message))) { + return; + } + // Send to others sendChatMessage(message); // Show locally - if (message[0] == L'/') - { - m_chat_queue.push_back((std::wstring)L"issued command: " + message); - } - else + if (message[0] != L'/') { - LocalPlayer *player = m_env.getLocalPlayer(); - assert(player != NULL); - std::wstring name = narrow_to_wide(player->getName()); - m_chat_queue.push_back((std::wstring)L"<" + name + L"> " + message); + // compatibility code + if (m_proto_ver < 29) { + LocalPlayer *player = m_env.getLocalPlayer(); + assert(player != NULL); + std::wstring name = narrow_to_wide(player->getName()); + pushToChatQueue((std::wstring)L"<" + name + L"> " + message); + } } } void Client::addUpdateMeshTask(v3s16 p, bool ack_to_server, bool urgent) { + // Check if the block exists to begin with. In the case when a non-existing + // neighbor is automatically added, it may not. In that case we don't want + // to tell the mesh update thread about it. MapBlock *b = m_env.getMap().getBlockNoCreateNoEx(p); - if(b == NULL) + if (b == NULL) return; - /* - Create a task to update the mesh of the block - */ - - MeshMakeData *data = new MeshMakeData(this, m_cache_enable_shaders); - - { - //TimeTaker timer("data fill"); - // Release: ~0ms - // Debug: 1-6ms, avg=2ms - data->fill(b); - data->setCrack(m_crack_level, m_crack_pos); - data->setHighlighted(m_highlighted_pos, m_show_highlighted); - data->setSmoothLighting(m_cache_smooth_lighting); - } - - // Add task to queue - m_mesh_update_thread.m_queue_in.addBlock(p, data, ack_to_server, urgent); + m_mesh_update_thread.updateBlock(&m_env.getMap(), p, ack_to_server, urgent); } void Client::addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server, bool urgent) @@ -1545,13 +1601,15 @@ void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool ur ClientEvent Client::getClientEvent() { - if(m_client_event_queue.size() == 0) - { - ClientEvent event; + ClientEvent event; + if (m_client_event_queue.empty()) { event.type = CE_NONE; - return event; } - return m_client_event_queue.pop_front(); + else { + event = m_client_event_queue.front(); + m_client_event_queue.pop(); + } + return event; } float Client::mediaReceiveProgress() @@ -1562,119 +1620,211 @@ float Client::mediaReceiveProgress() return 1.0; // downloader only exists when not yet done } -void Client::afterContentReceived(IrrlichtDevice *device, gui::IGUIFont* font) +typedef struct TextureUpdateArgs { + IrrlichtDevice *device; + gui::IGUIEnvironment *guienv; + u32 last_time_ms; + u16 last_percent; + const wchar_t* text_base; + ITextureSource *tsrc; +} TextureUpdateArgs; + +void texture_update_progress(void *args, u32 progress, u32 max_progress) +{ + TextureUpdateArgs* targs = (TextureUpdateArgs*) args; + u16 cur_percent = ceil(progress / (double) max_progress * 100.); + + // update the loading menu -- if neccessary + bool do_draw = false; + u32 time_ms = targs->last_time_ms; + if (cur_percent != targs->last_percent) { + targs->last_percent = cur_percent; + time_ms = getTimeMs(); + // only draw when the user will notice something: + do_draw = (time_ms - targs->last_time_ms > 100); + } + + if (do_draw) { + targs->last_time_ms = time_ms; + std::basic_stringstream strm; + strm << targs->text_base << " " << targs->last_percent << "%..."; + draw_load_screen(strm.str(), targs->device, targs->guienv, targs->tsrc, 0, + 72 + (u16) ((18. / 100.) * (double) targs->last_percent), true); + } +} + +void Client::afterContentReceived(IrrlichtDevice *device) { infostream<<"Client::afterContentReceived() started"<getVideoDriver()); + // Rebuild inherited images and recreate textures infostream<<"- Rebuilding images and textures"<rebuildImagesAndTextures(); delete[] text; // Rebuild shaders infostream<<"- Rebuilding shaders"<rebuildShaders(); delete[] text; // Update node aliases infostream<<"- Updating node aliases"<updateAliases(m_itemdef); + std::string texture_path = g_settings->get("texture_path"); + if (texture_path != "" && fs::IsDir(texture_path)) + m_nodedef->applyTextureOverrides(texture_path + DIR_DELIM + "override.txt"); m_nodedef->setNodeRegistrationStatus(true); - m_nodedef->runNodeResolverCallbacks(); + m_nodedef->runNodeResolveCallbacks(); delete[] text; // Update node textures and assign shaders to each tile infostream<<"- Updating node textures"<updateTextures(this); - - // Preload item textures and meshes if configured to - if(g_settings->getBool("preload_item_visuals")) - { - verbosestream<<"Updating item textures and meshes"< names = m_itemdef->getAll(); - size_t size = names.size(); - size_t count = 0; - int percent = 0; - for(std::set::const_iterator - i = names.begin(); i != names.end(); ++i) - { - // Asking for these caches the result - m_itemdef->getInventoryTexture(*i, this); - m_itemdef->getWieldMesh(*i, this); - count++; - percent = (count * 100 / size * 0.2) + 80; - draw_load_screen(text, device, guienv, 0, percent); - } - delete[] text; - } + TextureUpdateArgs tu_args; + tu_args.device = device; + tu_args.guienv = guienv; + tu_args.last_time_ms = getTimeMs(); + tu_args.last_percent = 0; + tu_args.text_base = wgettext("Initializing nodes"); + tu_args.tsrc = m_tsrc; + m_nodedef->updateTextures(this, texture_update_progress, &tu_args); + delete[] tu_args.text_base; // Start mesh update thread after setting up content definitions infostream<<"- Starting mesh update thread"<getBool("enable_client_modding")) { + m_script->on_client_ready(m_env.getLocalPlayer()); + m_script->on_connect(); + } + text = wgettext("Done!"); - draw_load_screen(text, device, guienv, 0, 100); + draw_load_screen(text, device, guienv, m_tsrc, 0, 100); infostream<<"Client::afterContentReceived() done"<getVideoDriver(); irr::video::IImage* const raw_image = driver->createScreenShot(); - if (raw_image) { - irr::video::IImage* const image = driver->createImage(video::ECF_R8G8B8, - raw_image->getDimension()); + + if (!raw_image) + return; + + time_t t = time(NULL); + struct tm *tm = localtime(&t); + + char timetstamp_c[64]; + strftime(timetstamp_c, sizeof(timetstamp_c), "%Y%m%d_%H%M%S", tm); + + std::string filename_base = g_settings->get("screenshot_path") + + DIR_DELIM + + std::string("screenshot_") + + std::string(timetstamp_c); + std::string filename_ext = "." + g_settings->get("screenshot_format"); + std::string filename; + + u32 quality = (u32)g_settings->getS32("screenshot_quality"); + quality = MYMIN(MYMAX(quality, 0), 100) / 100.0 * 255; + + // Try to find a unique filename + unsigned serial = 0; + + while (serial < SCREENSHOT_MAX_SERIAL_TRIES) { + filename = filename_base + (serial > 0 ? ("_" + itos(serial)) : "") + filename_ext; + std::ifstream tmp(filename.c_str()); + if (!tmp.good()) + break; // File did not apparently exist, we'll go with it + serial++; + } + + if (serial == SCREENSHOT_MAX_SERIAL_TRIES) { + infostream << "Could not find suitable filename for screenshot" << std::endl; + } else { + irr::video::IImage* const image = + driver->createImage(video::ECF_R8G8B8, raw_image->getDimension()); if (image) { raw_image->copyTo(image); - irr::c8 filename[256]; - snprintf(filename, sizeof(filename), "%s" DIR_DELIM "screenshot_%u.png", - g_settings->get("screenshot_path").c_str(), - device->getTimer()->getRealTime()); + std::ostringstream sstr; - if (driver->writeImageToFile(image, filename)) { + if (driver->writeImageToFile(image, filename.c_str(), quality)) { sstr << "Saved screenshot to '" << filename << "'"; } else { sstr << "Failed to save screenshot '" << filename << "'"; } - m_chat_queue.push_back(narrow_to_wide(sstr.str())); + pushToChatQueue(narrow_to_wide(sstr.str())); infostream << sstr.str() << std::endl; image->drop(); } - raw_image->drop(); } + + raw_image->drop(); +} + +bool Client::shouldShowMinimap() const +{ + return !m_minimap_disabled_by_server; +} + +void Client::showGameChat(const bool show) +{ + m_game_ui_flags->show_chat = show; +} + +void Client::showGameHud(const bool show) +{ + m_game_ui_flags->show_hud = show; +} + +void Client::showMinimap(const bool show) +{ + m_game_ui_flags->show_minimap = show; +} + +void Client::showProfiler(const bool show) +{ + m_game_ui_flags->show_profiler_graph = show; +} + +void Client::showGameFog(const bool show) +{ + m_game_ui_flags->force_fog_off = !show; +} + +void Client::showGameDebug(const bool show) +{ + m_game_ui_flags->show_debug = show; } // IGameDef interface @@ -1706,9 +1856,10 @@ scene::ISceneManager* Client::getSceneManager() } u16 Client::allocateUnknownNodeId(const std::string &name) { - errorstream<<"Client::allocateUnknownNodeId(): " - <<"Client cannot allocate node IDs"<::const_iterator i = - m_mesh_data.find(filename); - if(i == m_mesh_data.end()){ - errorstream<<"Client::getMesh(): Mesh not found: \""<second; + const std::string &data = it->second; scene::ISceneManager *smgr = m_device->getSceneManager(); // Create the mesh, remove it from cache and return it @@ -1743,7 +1893,7 @@ scene::IAnimatedMesh* Client::getMesh(const std::string &filename) io::IFileSystem *irrfs = m_device->getFileSystem(); io::IReadFile *rfile = irrfs->createMemoryReadFile( *data_rw, data_rw.getSize(), filename.c_str()); - assert(rfile); + FATAL_ERROR_IF(!rfile, "Could not create/open RAM file"); scene::IAnimatedMesh *mesh = smgr->getMesh(rfile); rfile->drop(); @@ -1753,3 +1903,31 @@ scene::IAnimatedMesh* Client::getMesh(const std::string &filename) smgr->getMeshCache()->removeMesh(mesh); return mesh; } + +bool Client::registerModStorage(ModMetadata *storage) +{ + if (m_mod_storages.find(storage->getModName()) != m_mod_storages.end()) { + errorstream << "Unable to register same mod storage twice. Storage name: " + << storage->getModName() << std::endl; + return false; + } + + m_mod_storages[storage->getModName()] = storage; + return true; +} + +void Client::unregisterModStorage(const std::string &name) +{ + UNORDERED_MAP::const_iterator it = m_mod_storages.find(name); + if (it != m_mod_storages.end()) { + // Save unconditionaly on unregistration + it->second->save(getModStoragePath()); + m_mod_storages.erase(name); + } +} + +std::string Client::getModStoragePath() const +{ + return porting::path_user + DIR_DELIM + "client" + DIR_DELIM + "mod_storage"; +} +