X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fclient.cpp;h=2d1572d8ee9d346b8652937e70850a4181eaf22f;hb=fd9f195fcc2e4e592dbad3290876486eb08318b2;hp=483b22caa1a642655ff4c4ce7b9ff411086136d6;hpb=b11720af459d44a553cb5d23ef776a632fb30a65;p=minetest.git diff --git a/src/client.cpp b/src/client.cpp index 483b22caa..2d1572d8e 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -20,183 +20,52 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include +#include #include +#include "client.h" +#include "network/clientopcodes.h" +#include "network/connection.h" +#include "network/networkpacket.h" #include "threading/mutex_auto_lock.h" +#include "client/clientevent.h" +#include "client/renderingengine.h" +#include "client/tile.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 "filesys.h" -#include "porting.h" #include "mapblock_mesh.h" #include "mapblock.h" #include "minimap.h" -#include "settings.h" +#include "modchannels.h" +#include "mods.h" #include "profiler.h" -#include "gettext.h" -#include "log.h" -#include "nodemetadata.h" -#include "itemdef.h" #include "shader.h" +#include "gettext.h" #include "clientmap.h" #include "clientmedia.h" -#include "sound.h" -#include "IMeshCache.h" -#include "config.h" #include "version.h" -#include "drawscene.h" -#include "database-sqlite3.h" +#include "database/database-sqlite3.h" #include "serialization.h" #include "guiscalingfilter.h" +#include "script/scripting_client.h" +#include "game.h" +#include "chatmessage.h" +#include "translation.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() -{ - MutexAutoLock 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); // pre-condition - - MutexAutoLock 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() -{ - MutexAutoLock 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::enqueueUpdate(v3s16 p, MeshMakeData *data, - bool ack_block_to_server, bool urgent) -{ - m_queue_in.addBlock(p, data, ack_block_to_server, urgent); - deferUpdate(); -} - -void MeshUpdateThread::doUpdate() -{ - QueuedMeshUpdate *q; - while ((q = m_queue_in.pop())) { - - ScopeProfiler sp(g_profiler, "Client: Mesh making"); - - MapBlockMesh *mesh_new = new MapBlockMesh(q->data, m_camera_offset); - - 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; - } -} - /* Client */ Client::Client( - IrrlichtDevice *device, const char *playername, - std::string password, + const std::string &password, + const std::string &address_name, MapDrawControl &control, IWritableTextureSource *tsrc, IWritableShaderSource *shsrc, @@ -204,73 +73,139 @@ 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), - m_avg_rtt_timer(0.0), - m_playerpos_send_timer(0.0), - m_ignore_damage_timer(0.0), m_tsrc(tsrc), m_shsrc(shsrc), m_itemdef(itemdef), m_nodedef(nodedef), m_sound(sound), m_event(event), - m_mesh_update_thread(), + m_mesh_update_thread(this), m_env( - new ClientMap(this, this, control, - device->getSceneManager()->getRootSceneNode(), - device->getSceneManager(), 666), - device->getSceneManager(), - tsrc, this, device + new ClientMap(this, control, 666), + tsrc, this ), 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_con(new con::Connection(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, ipv6, this)), + m_address_name(address_name), 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_animation_time(0), - m_crack_level(-1), - m_crack_pos(0,0,0), - m_map_seed(0), + m_last_chat_message_sent(time(NULL)), 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()), - m_time_of_day_set(false), - m_last_time_of_day_f(-1), - m_time_of_day_update_timer(0), - m_recommended_send_interval(0.1), - m_removed_sounds_check_timer(0), m_state(LC_Created), - m_localdb(NULL) + m_game_ui_flags(game_ui_flags), + m_modchannel_mgr(new ModChannelMgr()) { // Add local player - m_env.addPlayer(new LocalPlayer(this, playername)); + m_env.setLocalPlayer(new LocalPlayer(this, playername)); - m_mapper = new Mapper(device, this); + if (g_settings->getBool("enable_minimap")) { + m_minimap = new Minimap(this); + } 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"); - m_cache_use_tangent_vertices = m_cache_enable_shaders && ( - g_settings->getBool("enable_bumpmapping") || - g_settings->getBool("enable_parallax_occlusion")); + 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::loadMods() +{ + // Load builtin + scanModIntoMemory(BUILTIN_MOD_NAME, getBuiltinLuaPath()); + + // If modding is not enabled, don't load mods, just builtin + if (!m_modding_enabled) { + return; + } + ClientModConfiguration modconf(getClientModsLuaPath()); + m_mods = modconf.getMods(); + std::vector unsatisfied_mods = modconf.getUnsatisfiedMods(); + // complain about mods with unsatisfied dependencies + if (!modconf.isConsistent()) { + modconf.printUnsatisfiedModsError(); + } + + // Print mods + infostream << "Client Loading mods: "; + for (const ModSpec &mod : m_mods) + infostream << mod.name << " "; + infostream << std::endl; + + // Load and run "mod" scripts + for (const ModSpec &mod : m_mods) { + if (!string_allowed(mod.name, MODNAME_ALLOWED_CHARS)) { + throw ModError("Error loading mod \"" + mod.name + + "\": Mod name does not follow naming conventions: " + "Only characters [a-z0-9_] are allowed."); + } + scanModIntoMemory(mod.name, mod.path); + } +} + +void Client::scanModSubfolder(const std::string &mod_name, const std::string &mod_path, + std::string mod_subpath) +{ + std::string full_path = mod_path + DIR_DELIM + mod_subpath; + std::vector mod = fs::GetDirListing(full_path); + for (const fs::DirListNode &j : mod) { + std::string filename = j.name; + if (j.dir) { + scanModSubfolder(mod_name, mod_path, mod_subpath + + filename + DIR_DELIM); + continue; + } + std::replace( mod_subpath.begin(), mod_subpath.end(), DIR_DELIM_CHAR, '/'); + m_mod_files[mod_name + ":" + mod_subpath + filename] = full_path + filename; + } +} + +void Client::initMods() +{ + m_script->loadModFromMemory(BUILTIN_MOD_NAME); + + // If modding is not enabled, don't load mods, just builtin + if (!m_modding_enabled) { + return; + } + + // Load and run "mod" scripts + for (const ModSpec &mod : m_mods) + m_script->loadModFromMemory(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(); // Save local server map @@ -278,19 +213,19 @@ void Client::Stop() 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_con.Disconnect(); + m_shutdown = true; + m_con->Disconnect(); m_mesh_update_thread.stop(); m_mesh_update_thread.wait(); @@ -303,40 +238,31 @@ Client::~Client() delete m_inventory_from_server; // Delete detached inventories - for (std::map::iterator - i = m_detached_inventories.begin(); - i != m_detached_inventories.end(); ++i) { - delete i->second; + for (auto &m_detached_inventorie : m_detached_inventories) { + delete m_detached_inventorie.second; } // cleanup 3d model meshes on client shutdown - while (m_device->getSceneManager()->getMeshCache()->getMeshCount() != 0) { - scene::IAnimatedMesh *mesh = - m_device->getSceneManager()->getMeshCache()->getMeshByIndex(0); + while (RenderingEngine::get_mesh_cache()->getMeshCount() != 0) { + scene::IAnimatedMesh *mesh = RenderingEngine::get_mesh_cache()->getMeshByIndex(0); - if (mesh != NULL) - m_device->getSceneManager()->getMeshCache()->removeMesh(mesh); + if (mesh) + RenderingEngine::get_mesh_cache()->removeMesh(mesh); } - delete m_mapper; + delete m_minimap; } -void Client::connect(Address address, - const std::string &address_name, - bool is_local_server) +void Client::connect(Address address, bool is_local_server) { - DSTACK(FUNCTION_NAME); - - initLocalMapSaving(address, address_name, is_local_server); + initLocalMapSaving(address, m_address_name, is_local_server); - m_con.SetTimeoutMs(0); - m_con.Connect(address); + m_con->SetTimeoutMs(0); + m_con->Connect(address); } void Client::step(float dtime) { - DSTACK(FUNCTION_NAME); - // Limit a bit if(dtime > 2.0) dtime = 2.0; @@ -383,34 +309,10 @@ void Client::step(float dtime) if(counter <= 0.0) { counter = 2.0; - Player *myplayer = m_env.getLocalPlayer(); + LocalPlayer *myplayer = m_env.getLocalPlayer(); FATAL_ERROR_IF(myplayer == NULL, "Local player not found in environment."); - u16 proto_version_min = g_settings->getFlag("send_pre_v25_init") ? - CLIENT_PROTOCOL_VERSION_MIN_LEGACY : CLIENT_PROTOCOL_VERSION_MIN; - - 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) - - char pName[PLAYERNAME_SIZE]; - char pPassword[PASSWORD_SIZE]; - memset(pName, 0, PLAYERNAME_SIZE * sizeof(char)); - memset(pPassword, 0, PASSWORD_SIZE * sizeof(char)); - - 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()); - - sendLegacyInit(pName, pPassword); - } - if (CLIENT_PROTOCOL_VERSION_MAX >= 25) - sendInit(myplayer->getName()); + sendInit(myplayer->getName()); } // Not connected, return @@ -465,43 +367,46 @@ void Client::step(float dtime) } } + /* + Send pending messages on out chat queue + */ + if (!m_out_chat_queue.empty() && canSendChatMessage()) { + sendChatMessage(m_out_chat_queue.front()); + m_out_chat_queue.pop(); + } + /* Handle environment */ // Control local player (0ms) LocalPlayer *player = m_env.getLocalPlayer(); - assert(player != NULL); - player->applyControl(dtime); + assert(player); + player->applyControl(dtime, &m_env); // Step environment m_env.step(dtime); + m_sound->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; + while (m_env.hasClientEnvEvents()) { + ClientEnvEvent envEvent = m_env.getClientEnvEvent(); - if(event.player_damage.send_to_server) + if (envEvent.type == CEE_PLAYER_DAMAGE) { + if (m_ignore_damage_timer <= 0) { + u8 damage = envEvent.player_damage.amount; + + if (envEvent.player_damage.send_to_server) sendDamage(damage); // Add to ClientEvent queue - ClientEvent event; - event.type = CE_PLAYER_DAMAGE; - event.player_damage.amount = damage; + ClientEvent *event = new ClientEvent(); + 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); - } } /* @@ -545,29 +450,31 @@ void Client::step(float dtime) MapBlock *block = m_env.getMap().getBlockNoCreateNoEx(r.p); if (block) { // Delete the old mesh - if (block->mesh != NULL) { - delete block->mesh; - block->mesh = NULL; - } + delete block->mesh; + block->mesh = nullptr; 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; + bool is_empty = true; + for (int l = 0; l < MAX_TILE_LAYERS; l++) + if (r.mesh->getMesh(l)->getMeshBufferCount() != 0) + is_empty = false; + + if (is_empty) + delete r.mesh; + else + // Replace with the new mesh + block->mesh = r.mesh; } } else { delete r.mesh; } - if (do_mapper_update) - m_mapper->addBlock(r.p, minimap_mapblock); + if (m_minimap && do_mapper_update) + m_minimap->addBlock(r.p, minimap_mapblock); if (r.ack_block_to_server) { /* @@ -589,7 +496,6 @@ void Client::step(float dtime) 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; } @@ -613,7 +519,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; } @@ -623,17 +529,13 @@ 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) - { - int client_id = i->first; - u16 object_id = i->second; + for (auto &m_sounds_to_object : m_sounds_to_objects) { + int client_id = m_sounds_to_object.first; + u16 object_id = m_sounds_to_object.second; ClientActiveObject *cao = m_env.getActiveObject(object_id); - if(!cao) + if (!cao) continue; - v3f pos = cao->getPosition(); - m_sound->updateSoundPosition(client_id, pos); + m_sound->updateSoundPosition(client_id, cao->getPosition()); } } @@ -645,8 +547,7 @@ void Client::step(float dtime) m_removed_sounds_check_timer = 0; // Find removed sounds and clear references to them std::vector removed_server_ids; - for(std::map::iterator - i = m_sounds_server_to_client.begin(); + for (std::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; @@ -665,6 +566,18 @@ void Client::step(float dtime) } } + 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 (std::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)) { @@ -686,13 +599,12 @@ bool Client::loadMedia(const std::string &data, const std::string &filename) NULL }; name = removeStringEnd(filename, image_ext); - if(name != "") - { + if (!name.empty()) { verbosestream<<"Client: Attempting to load image " <<"file \""<getFileSystem(); - video::IVideoDriver *vdrv = m_device->getVideoDriver(); + io::IFileSystem *irrfs = RenderingEngine::get_filesystem(); + video::IVideoDriver *vdrv = RenderingEngine::get_video_driver(); // Create an irrlicht memory file io::IReadFile *rfile = irrfs->createMemoryReadFile( @@ -702,18 +614,17 @@ bool Client::loadMedia(const std::string &data, const std::string &filename) // Read image video::IImage *img = vdrv->createImageFromFile(rfile); - if(!img){ + if (!img) { errorstream<<"Client: Cannot create image from data of " <<"file \""<drop(); return false; } - else { - m_tsrc->insertSourceImage(filename, img); - img->drop(); - rfile->drop(); - return true; - } + + m_tsrc->insertSourceImage(filename, img); + img->drop(); + rfile->drop(); + return true; } const char *sound_ext[] = { @@ -722,8 +633,7 @@ bool Client::loadMedia(const std::string &data, const std::string &filename) ".ogg", NULL }; name = removeStringEnd(filename, sound_ext); - if(name != "") - { + if (!name.empty()) { verbosestream<<"Client: Attempting to load sound " <<"file \""<loadSoundData(name, data); @@ -734,9 +644,9 @@ bool Client::loadMedia(const std::string &data, const std::string &filename) ".x", ".b3d", ".md2", ".obj", NULL }; + name = removeStringEnd(filename, model_ext); - if(name != "") - { + if (!name.empty()) { verbosestream<<"Client: Storing model into memory: " <<"\""<loadTranslation(data); + return true; + } + + errorstream << "Client: Don't know how to load file \"" + << filename << "\"" << std::endl; return false; } @@ -790,9 +711,8 @@ void Client::request_media(const std::vector &file_requests) pkt << (u16) (file_requests_size & 0xFFFF); - for(std::vector::const_iterator i = file_requests.begin(); - i != file_requests.end(); ++i) { - pkt << (*i); + for (const std::string &file_request : file_requests) { + pkt << file_request; } Send(&pkt); @@ -801,14 +721,6 @@ void Client::request_media(const std::vector &file_requests) << 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) @@ -824,15 +736,14 @@ void Client::initLocalMapSaving(const Address &address, fs::CreateAllDirs(world_path); - m_localdb = new Database_SQLite3(world_path); + m_localdb = new MapDatabaseSQLite3(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); - u32 start_ms = porting::getTimeMs(); + u64 start_ms = porting::getTimeMs(); for(;;) { // Limit time even if there would be huge amounts of data to @@ -857,9 +768,8 @@ void Client::ReceiveAll() void Client::Receive() { - DSTACK(FUNCTION_NAME); NetworkPacket pkt; - m_con.Receive(&pkt); + m_con->Receive(&pkt); ProcessData(&pkt); } @@ -874,8 +784,6 @@ inline void Client::handleCommand(NetworkPacket* pkt) */ void Client::ProcessData(NetworkPacket *pkt) { - DSTACK(FUNCTION_NAME); - ToClientCommand command = (ToClientCommand) pkt->getCommand(); u32 sender_peer_id = pkt->getPeerId(); @@ -926,12 +834,42 @@ void Client::ProcessData(NetworkPacket *pkt) void Client::Send(NetworkPacket* pkt) { - m_con.Send(PEER_ID_SERVER, + m_con->Send(PEER_ID_SERVER, serverCommandFactoryTable[pkt->getCommand()].channel, pkt, serverCommandFactoryTable[pkt->getCommand()].reliable); } +// Will fill up 12 + 12 + 4 + 4 + 4 bytes +void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket *pkt) +{ + v3f pf = myplayer->getPosition() * 100; + v3f sf = myplayer->getSpeed() * 100; + s32 pitch = myplayer->getPitch() * 100; + s32 yaw = myplayer->getYaw() * 100; + u32 keyPressed = myplayer->keyPressed; + // scaled by 80, so that pi can fit into a u8 + u8 fov = clientMap->getCameraFov() * 80; + u8 wanted_range = MYMIN(255, + std::ceil(clientMap->getControl().wanted_range / MAP_BLOCKSIZE)); + + v3s32 position(pf.X, pf.Y, pf.Z); + v3s32 speed(sf.X, sf.Y, sf.Z); + + /* + 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 + [12+12+4+4+4] u8 fov*80 + [12+12+4+4+4+1] u8 ceil(wanted_range / MAP_BLOCKSIZE) + */ + *pkt << position << speed << pitch << yaw << keyPressed; + *pkt << fov << wanted_range; +} + void Client::interact(u8 action, const PointedThing& pointed) { if(m_state != LC_Ready) { @@ -941,12 +879,17 @@ void Client::interact(u8 action, const PointedThing& pointed) return; } + LocalPlayer *myplayer = m_env.getLocalPlayer(); + if (myplayer == NULL) + return; + /* [0] u16 command [2] u8 action [3] u16 item - [5] u32 length of the next item + [5] u32 length of the next item (plen) [9] serialized PointedThing + [9 + plen] player position information actions: 0: start digging (from undersurface) or use 1: stop digging (all parameters ignored) @@ -966,6 +909,8 @@ void Client::interact(u8 action, const PointedThing& pointed) pkt.putLongString(tmp_os.str()); + writePlayerPos(myplayer, &m_env.getClientMap(), &pkt); + Send(&pkt); } @@ -1003,22 +948,6 @@ AuthMechanism Client::choseAuthMech(const u32 mechs) 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())); @@ -1026,11 +955,8 @@ void Client::sendInit(const std::string &playerName) // 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 << (u16) CLIENT_PROTOCOL_VERSION_MIN << (u16) CLIENT_PROTOCOL_VERSION_MAX; pkt << playerName; Send(&pkt); @@ -1049,7 +975,7 @@ void Client::startAuth(AuthMechanism chosen_auth_mechanism) &verifier, &salt); NetworkPacket resp_pkt(TOSERVER_FIRST_SRP, 0); - resp_pkt << salt << verifier << (u8)((m_password == "") ? 1 : 0); + resp_pkt << salt << verifier << (u8)((m_password.empty()) ? 1 : 0); Send(&resp_pkt); break; @@ -1091,12 +1017,8 @@ void Client::sendDeletedBlocks(std::vector &blocks) pkt << (u8) blocks.size(); - u32 k = 0; - for(std::vector::iterator - j = blocks.begin(); - j != blocks.end(); ++j) { - pkt << *j; - k++; + for (const v3s16 &block : blocks) { + pkt << block; } Send(&pkt); @@ -1118,9 +1040,8 @@ void Client::sendRemovedSounds(std::vector &soundList) pkt << (u16) (server_ids & 0xFFFF); - for(std::vector::iterator i = soundList.begin(); - i != soundList.end(); ++i) - pkt << *i; + for (int sound_id : soundList) + pkt << sound_id; Send(&pkt); } @@ -1182,76 +1103,81 @@ void Client::sendInventoryAction(InventoryAction *a) Send(&pkt); } -void Client::sendChatMessage(const std::wstring &message) +bool Client::canSendChatMessage() const { - NetworkPacket pkt(TOSERVER_CHAT_MESSAGE, 2 + message.size() * sizeof(u16)); + u32 now = time(NULL); + float time_passed = now - m_last_chat_message_sent; - pkt << message; + float virt_chat_message_allowance = m_chat_message_allowance + time_passed * + (CLIENT_CHAT_MESSAGE_LIMIT_PER_10S / 8.0f); - Send(&pkt); + if (virt_chat_message_allowance < 1.0f) + return false; + + return true; } -void Client::sendChangePassword(const std::string &oldpassword, - const std::string &newpassword) +void Client::sendChatMessage(const std::wstring &message) { - Player *player = m_env.getLocalPlayer(); - if (player == NULL) - return; + const s16 max_queue_size = g_settings->getS16("max_out_chat_queue_size"); + if (canSendChatMessage()) { + u32 now = time(NULL); + float time_passed = now - m_last_chat_message_sent; + m_last_chat_message_sent = time(NULL); - std::string playername = player->getName(); - 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); + m_chat_message_allowance += time_passed * (CLIENT_CHAT_MESSAGE_LIMIT_PER_10S / 8.0f); + if (m_chat_message_allowance > CLIENT_CHAT_MESSAGE_LIMIT_PER_10S) + m_chat_message_allowance = CLIENT_CHAT_MESSAGE_LIMIT_PER_10S; - NetworkPacket pkt(TOSERVER_PASSWORD_LEGACY, 2 * PASSWORD_SIZE); + m_chat_message_allowance -= 1.0f; - for (u8 i = 0; i < PASSWORD_SIZE; i++) { - pkt << (u8) (i < oldpwd.length() ? oldpwd[i] : 0); - } + NetworkPacket pkt(TOSERVER_CHAT_MESSAGE, 2 + message.size() * sizeof(u16)); + + pkt << message; - for (u8 i = 0; i < PASSWORD_SIZE; i++) { - pkt << (u8) (i < newpwd.length() ? newpwd[i] : 0); - } Send(&pkt); + } else if (m_out_chat_queue.size() < (u16) max_queue_size || max_queue_size == -1) { + m_out_chat_queue.push(message); + } else { + infostream << "Could not queue chat message because maximum out chat queue size (" + << max_queue_size << ") is reached." << std::endl; } } +void Client::clearOutChatQueue() +{ + m_out_chat_queue = std::queue(); +} -void Client::sendDamage(u8 damage) +void Client::sendChangePassword(const std::string &oldpassword, + const std::string &newpassword) { - DSTACK(FUNCTION_NAME); + LocalPlayer *player = m_env.getLocalPlayer(); + if (player == NULL) + return; - NetworkPacket pkt(TOSERVER_DAMAGE, sizeof(u8)); - pkt << damage; - Send(&pkt); + // get into sudo mode and then send new password to server + m_password = oldpassword; + m_new_password = newpassword; + startAuth(choseAuthMech(m_sudo_auth_methods)); } -void Client::sendBreath(u16 breath) -{ - DSTACK(FUNCTION_NAME); - NetworkPacket pkt(TOSERVER_BREATH, sizeof(u16)); - pkt << breath; +void Client::sendDamage(u8 damage) +{ + NetworkPacket pkt(TOSERVER_DAMAGE, sizeof(u8)); + pkt << damage; Send(&pkt); } void Client::sendRespawn() { - DSTACK(FUNCTION_NAME); - NetworkPacket pkt(TOSERVER_RESPAWN, 0); Send(&pkt); } void Client::sendReady() { - DSTACK(FUNCTION_NAME); - NetworkPacket pkt(TOSERVER_CLIENT_READY, 1 + 1 + 1 + 1 + 2 + sizeof(char) * strlen(g_version_hash)); @@ -1265,72 +1191,45 @@ void Client::sendReady() void Client::sendPlayerPos() { LocalPlayer *myplayer = m_env.getLocalPlayer(); - if(myplayer == NULL) + if (!myplayer) 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; - - u16 our_peer_id; - { - //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; - - assert(myplayer->peer_id == our_peer_id); + 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; - v3f pf = myplayer->getPosition(); - v3f sf = myplayer->getSpeed(); - s32 pitch = myplayer->getPitch() * 100; - s32 yaw = myplayer->getYaw() * 100; - u32 keyPressed = myplayer->keyPressed; + NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4 + 1 + 1); - 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 - */ - - NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4); - - pkt << position << speed << pitch << yaw << keyPressed; + writePlayerPos(myplayer, &map, &pkt); Send(&pkt); } void Client::sendPlayerItem(u16 item) { - Player *myplayer = m_env.getLocalPlayer(); - if(myplayer == NULL) + LocalPlayer *myplayer = m_env.getLocalPlayer(); + if (!myplayer) return; - u16 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; - assert(myplayer->peer_id == our_peer_id); - NetworkPacket pkt(TOSERVER_PLAYERITEM, 2); pkt << item; @@ -1348,11 +1247,28 @@ void Client::removeNode(v3s16 p) catch(InvalidPositionException &e) { } - for(std::map::iterator - i = modified_blocks.begin(); - i != modified_blocks.end(); ++i) { - addUpdateMeshTaskWithEdge(i->first, false, true); + for (const auto &modified_block : modified_blocks) { + addUpdateMeshTaskWithEdge(modified_block.first, false, true); + } +} + +/** + * Helper function for Client Side Modding + * Flavour is applied there, this should not be used for core engine + * @param p + * @param is_valid_position + * @return + */ +MapNode Client::getNode(v3s16 p, bool *is_valid_position) +{ + if (checkCSMFlavourLimit(CSMFlavourLimit::CSM_FL_LOOKUP_NODES)) { + v3s16 ppos = floatToInt(m_env.getLocalPlayer()->getPosition(), BS); + if ((u32) ppos.getDistanceFrom(p) > m_csm_noderange_limit) { + *is_valid_position = false; + return {}; + } } + return m_env.getMap().getNodeNoEx(p, is_valid_position); } void Client::addNode(v3s16 p, MapNode n, bool remove_metadata) @@ -1368,17 +1284,15 @@ void Client::addNode(v3s16 p, MapNode n, bool remove_metadata) catch(InvalidPositionException &e) { } - for(std::map::iterator - i = modified_blocks.begin(); - i != modified_blocks.end(); ++i) { - addUpdateMeshTaskWithEdge(i->first, false, true); + for (const auto &modified_block : modified_blocks) { + addUpdateMeshTaskWithEdge(modified_block.first, false, true); } } void Client::setPlayerControl(PlayerControl &control) { LocalPlayer *player = m_env.getLocalPlayer(); - assert(player != NULL); + assert(player); player->control = control; } @@ -1401,8 +1315,8 @@ bool Client::getLocalInventoryUpdated() // Copies the inventory of the local player to parameter void Client::getLocalInventory(Inventory &dst) { - Player *player = m_env.getLocalPlayer(); - assert(player != NULL); + LocalPlayer *player = m_env.getLocalPlayer(); + assert(player); dst = player->inventory; } @@ -1414,15 +1328,16 @@ Inventory* Client::getInventory(const InventoryLocation &loc) break; case InventoryLocation::CURRENT_PLAYER: { - Player *player = m_env.getLocalPlayer(); - assert(player != NULL); + LocalPlayer *player = m_env.getLocalPlayer(); + assert(player); 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; } @@ -1437,7 +1352,7 @@ 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]; } @@ -1465,49 +1380,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; igetSelectionBox(); - if(selection_box == NULL) - continue; - - v3f pos = obj->getPosition(); - - aabb3f 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; @@ -1518,6 +1390,11 @@ int Client::getCrackLevel() return m_crack_level; } +v3s16 Client::getCrackPos() +{ + return m_crack_pos; +} + void Client::setCrack(int level, v3s16 pos) { int old_crack_level = m_crack_level; @@ -1540,74 +1417,78 @@ void Client::setCrack(int level, v3s16 pos) u16 Client::getHP() { - Player *player = m_env.getLocalPlayer(); - assert(player != NULL); + LocalPlayer *player = m_env.getLocalPlayer(); + assert(player); return player->hp; } -u16 Client::getBreath() -{ - Player *player = m_env.getLocalPlayer(); - assert(player != NULL); - return player->getBreath(); -} - -bool Client::getChatMessage(std::wstring &message) +bool Client::getChatMessage(std::wstring &res) { - if(m_chat_queue.size() == 0) + if (m_chat_queue.empty()) return false; - message = m_chat_queue.front(); + + ChatMessage *chatMessage = m_chat_queue.front(); m_chat_queue.pop(); + + res = L""; + + switch (chatMessage->type) { + case CHATMESSAGE_TYPE_RAW: + case CHATMESSAGE_TYPE_ANNOUNCE: + case CHATMESSAGE_TYPE_SYSTEM: + res = chatMessage->message; + break; + case CHATMESSAGE_TYPE_NORMAL: { + if (!chatMessage->sender.empty()) + res = L"<" + chatMessage->sender + L"> " + chatMessage->message; + else + res = chatMessage->message; + break; + } + default: + break; + } + + delete chatMessage; return true; } void Client::typeChatMessage(const std::wstring &message) { // Discard empty line - if(message == L"") + if (message.empty()) 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((std::wstring)L"issued command: " + message); - } - else - { - LocalPlayer *player = m_env.getLocalPlayer(); - assert(player != NULL); - std::wstring name = narrow_to_wide(player->getName()); - m_chat_queue.push((std::wstring)L"<" + name + L"> " + message); + if (message[0] != L'/') { + // compatibility code + if (m_proto_ver < 29) { + LocalPlayer *player = m_env.getLocalPlayer(); + assert(player); + std::wstring name = narrow_to_wide(player->getName()); + pushToChatQueue(new ChatMessage(CHATMESSAGE_TYPE_NORMAL, message, name)); + } } } 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, - m_cache_use_tangent_vertices); - - { - //TimeTaker timer("data fill"); - // Release: ~0ms - // Debug: 1-6ms, avg=2ms - data->fill(b); - data->setCrack(m_crack_level, m_crack_pos); - data->setSmoothLighting(m_cache_smooth_lighting); - } - - // Add task to queue - m_mesh_update_thread.enqueueUpdate(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) @@ -1671,33 +1552,40 @@ void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool ur } } -ClientEvent Client::getClientEvent() +ClientEvent *Client::getClientEvent() { - ClientEvent event; - if(m_client_event_queue.size() == 0) { - event.type = CE_NONE; - } - else { - event = m_client_event_queue.front(); - m_client_event_queue.pop(); - } + FATAL_ERROR_IF(m_client_event_queue.empty(), + "Cannot getClientEvent, queue is empty."); + + ClientEvent *event = m_client_event_queue.front(); + m_client_event_queue.pop(); return event; } +bool Client::connectedToServer() +{ + return m_con->Connected(); +} + +const Address Client::getServerAddress() +{ + return m_con->GetPeerAddress(PEER_ID_SERVER); +} + float Client::mediaReceiveProgress() { if (m_media_downloader) return m_media_downloader->getProgress(); - else - return 1.0; // downloader only exists when not yet done + + return 1.0; // downloader only exists when not yet done } typedef struct TextureUpdateArgs { - IrrlichtDevice *device; gui::IGUIEnvironment *guienv; - u32 last_time_ms; + u64 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) @@ -1707,10 +1595,10 @@ void texture_update_progress(void *args, u32 progress, u32 max_progress) // update the loading menu -- if neccessary bool do_draw = false; - u32 time_ms = targs->last_time_ms; + u64 time_ms = targs->last_time_ms; if (cur_percent != targs->last_percent) { targs->last_percent = cur_percent; - time_ms = getTimeMs(); + time_ms = porting::getTimeMs(); // only draw when the user will notice something: do_draw = (time_ms - targs->last_time_ms > 100); } @@ -1719,12 +1607,12 @@ void texture_update_progress(void *args, u32 progress, u32 max_progress) 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, 0, - 72 + (u16) ((18. / 100.) * (double) targs->last_percent)); + RenderingEngine::draw_load_screen(strm.str(), targs->guienv, targs->tsrc, 0, + 72 + (u16) ((18. / 100.) * (double) targs->last_percent), true); } } -void Client::afterContentReceived(IrrlichtDevice *device) +void Client::afterContentReceived() { infostream<<"Client::afterContentReceived() started"<getVideoDriver()); + guiScalingCacheClear(); // 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"); + for (const auto &path : getTextureDirs()) + m_nodedef->applyTextureOverrides(path + DIR_DELIM + "override.txt"); m_nodedef->setNodeRegistrationStatus(true); m_nodedef->runNodeResolveCallbacks(); delete[] text; @@ -1766,11 +1653,11 @@ void Client::afterContentReceived(IrrlichtDevice *device) // Update node textures and assign shaders to each tile infostream<<"- Updating node textures"<updateTextures(this, texture_update_progress, &tu_args); delete[] tu_args.text_base; @@ -1780,32 +1667,32 @@ void Client::afterContentReceived(IrrlichtDevice *device) m_state = LC_Ready; sendReady(); + + if (g_settings->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); + RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 100); infostream<<"Client::afterContentReceived() done"<getPeerStat(PEER_ID_SERVER,con::AVG_RTT); } -float Client::getAvgRate(void) +float Client::getCurRate() { - return ( m_con.getLocalStat(con::AVG_INC_RATE) + - m_con.getLocalStat(con::AVG_DL_RATE)); + return (m_con->getLocalStat(con::CUR_INC_RATE) + + m_con->getLocalStat(con::CUR_DL_RATE)); } -void Client::makeScreenshot(IrrlichtDevice *device) +void Client::makeScreenshot() { - irr::video::IVideoDriver *driver = device->getVideoDriver(); + irr::video::IVideoDriver *driver = RenderingEngine::get_video_driver(); irr::video::IImage* const raw_image = driver->createScreenShot(); if (!raw_image) @@ -1853,7 +1740,8 @@ void Client::makeScreenshot(IrrlichtDevice *device) } else { sstr << "Failed to save screenshot '" << filename << "'"; } - m_chat_queue.push(narrow_to_wide(sstr.str())); + pushToChatQueue(new ChatMessage(CHATMESSAGE_TYPE_SYSTEM, + narrow_to_wide(sstr.str()))); infostream << sstr.str() << std::endl; image->drop(); } @@ -1862,6 +1750,46 @@ void Client::makeScreenshot(IrrlichtDevice *device) raw_image->drop(); } +bool Client::shouldShowMinimap() const +{ + return !m_minimap_disabled_by_server; +} + +void Client::pushToEventQueue(ClientEvent *event) +{ + m_client_event_queue.push(event); +} + +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 // Under envlock IItemDefManager* Client::getItemDefManager() @@ -1885,10 +1813,7 @@ IShaderSource* Client::getShaderSource() { return m_shsrc; } -scene::ISceneManager* Client::getSceneManager() -{ - return m_device->getSceneManager(); -} + u16 Client::allocateUnknownNodeId(const std::string &name) { errorstream << "Client::allocateUnknownNodeId(): " @@ -1911,7 +1836,7 @@ ParticleManager* Client::getParticleManager() return &m_particle_manager; } -scene::IAnimatedMesh* Client::getMesh(const std::string &filename) +scene::IAnimatedMesh* Client::getMesh(const std::string &filename, bool cache) { StringMap::const_iterator it = m_mesh_data.find(filename); if (it == m_mesh_data.end()) { @@ -1920,21 +1845,111 @@ scene::IAnimatedMesh* Client::getMesh(const std::string &filename) return NULL; } const std::string &data = it->second; - scene::ISceneManager *smgr = m_device->getSceneManager(); // Create the mesh, remove it from cache and return it // This allows unique vertex colors and other properties for each instance Buffer data_rw(data.c_str(), data.size()); // Const-incorrect Irrlicht - io::IFileSystem *irrfs = m_device->getFileSystem(); - io::IReadFile *rfile = irrfs->createMemoryReadFile( + io::IReadFile *rfile = RenderingEngine::get_filesystem()->createMemoryReadFile( *data_rw, data_rw.getSize(), filename.c_str()); FATAL_ERROR_IF(!rfile, "Could not create/open RAM file"); - scene::IAnimatedMesh *mesh = smgr->getMesh(rfile); + scene::IAnimatedMesh *mesh = RenderingEngine::get_scene_manager()->getMesh(rfile); rfile->drop(); - // NOTE: By playing with Irrlicht refcounts, maybe we could cache a bunch - // of uniquely named instances and re-use them mesh->grab(); - smgr->getMeshCache()->removeMesh(mesh); + if (!cache) + RenderingEngine::get_mesh_cache()->removeMesh(mesh); return mesh; } + +const std::string* Client::getModFile(const std::string &filename) +{ + StringMap::const_iterator it = m_mod_files.find(filename); + if (it == m_mod_files.end()) { + errorstream << "Client::getModFile(): File not found: \"" << filename + << "\"" << std::endl; + return NULL; + } + return &it->second; +} + +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) +{ + std::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"; +} + +/* + * Mod channels + */ + +bool Client::joinModChannel(const std::string &channel) +{ + if (m_modchannel_mgr->channelRegistered(channel)) + return false; + + NetworkPacket pkt(TOSERVER_MODCHANNEL_JOIN, 2 + channel.size()); + pkt << channel; + Send(&pkt); + + m_modchannel_mgr->joinChannel(channel, 0); + return true; +} + +bool Client::leaveModChannel(const std::string &channel) +{ + if (!m_modchannel_mgr->channelRegistered(channel)) + return false; + + NetworkPacket pkt(TOSERVER_MODCHANNEL_LEAVE, 2 + channel.size()); + pkt << channel; + Send(&pkt); + + m_modchannel_mgr->leaveChannel(channel, 0); + return true; +} + +bool Client::sendModChannelMessage(const std::string &channel, const std::string &message) +{ + if (!m_modchannel_mgr->canWriteOnChannel(channel)) + return false; + + if (message.size() > STRING_MAX_LEN) { + warningstream << "ModChannel message too long, dropping before sending " + << " (" << message.size() << " > " << STRING_MAX_LEN << ", channel: " + << channel << ")" << std::endl; + return false; + } + + // @TODO: do some client rate limiting + NetworkPacket pkt(TOSERVER_MODCHANNEL_MSG, 2 + channel.size() + 2 + message.size()); + pkt << channel << message; + Send(&pkt); + return true; +} + +ModChannel* Client::getModChannel(const std::string &channel) +{ + return m_modchannel_mgr->getModChannel(channel); +}