X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fclient%2Fclient.cpp;h=17661c2421daea99821362e2904626c805281507;hb=a049e8267fabd101cb5c6528b3270214cb0647f0;hp=17ee3a10aae685186ccbaeb6ef2b97b13109c0e4;hpb=5f1cd555cd9d1c64426e173b30b5b792d211c835;p=dragonfireclient.git diff --git a/src/client/client.cpp b/src/client/client.cpp index 17ee3a10a..17661c242 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -60,6 +60,28 @@ with this program; if not, write to the Free Software Foundation, Inc., extern gui::IGUIEnvironment* guienv; +/* + Utility classes +*/ + +u32 PacketCounter::sum() const +{ + u32 n = 0; + for (const auto &it : m_packets) + n += it.second; + return n; +} + +void PacketCounter::print(std::ostream &o) const +{ + for (const auto &it : m_packets) { + auto name = it.first >= TOCLIENT_NUM_MSG_TYPES ? "?" + : toClientCommandTable[it.first].name; + o << "cmd " << it.first << " (" << name << ") count " + << it.second << std::endl; + } +} + /* Client */ @@ -75,6 +97,7 @@ Client::Client( NodeDefManager *nodedef, ISoundManager *sound, MtEventManager *event, + RenderingEngine *rendering_engine, bool ipv6, GameUI *game_ui ): @@ -84,9 +107,10 @@ Client::Client( m_nodedef(nodedef), m_sound(sound), m_event(event), + m_rendering_engine(rendering_engine), m_mesh_update_thread(this), m_env( - new ClientMap(this, control, 666), + new ClientMap(this, rendering_engine, control, 666), tsrc, this ), m_particle_manager(&m_env), @@ -107,60 +131,51 @@ Client::Client( if (g_settings->getBool("enable_minimap")) { m_minimap = new Minimap(this); } - m_cache_save_interval = g_settings->getU16("server_map_save_interval"); - m_modding_enabled = g_settings->getBool("enable_client_modding"); - // Only create the client script environment if client modding is enabled - if (m_modding_enabled) { - m_script = new ClientScripting(this); - m_env.setScript(m_script); - m_script->setEnv(&m_env); - } + m_cache_save_interval = g_settings->getU16("server_map_save_interval"); } void Client::loadMods() { - // Don't load mods twice - if (m_mods_loaded) { + // Don't load mods twice. + // If client scripting is disabled by the client, don't load builtin or + // client-provided mods. + if (m_mods_loaded || !g_settings->getBool("enable_client_modding")) return; - } - // If client modding is not enabled, don't load client-provided CSM mods or - // builtin. - if (!m_modding_enabled) { - warningstream << "Client side mods are disabled by configuration." << std::endl; + // If client scripting is disabled by the server, don't load builtin or + // client-provided mods. + // TODO Delete this code block when server-sent CSM and verifying of builtin are + // complete. + if (checkCSMRestrictionFlag(CSMRestrictionFlags::CSM_RF_LOAD_CLIENT_MODS)) { + warningstream << "Client-provided mod loading is disabled by server." << + std::endl; return; } + m_script = new ClientScripting(this); + m_env.setScript(m_script); + m_script->setEnv(&m_env); + // Load builtin scanModIntoMemory(BUILTIN_MOD_NAME, getBuiltinLuaPath()); m_script->loadModFromMemory(BUILTIN_MOD_NAME); - // If the server has disabled client-provided CSM mod loading, don't load - // client-provided CSM mods. Builtin is loaded so needs verfying. - if (checkCSMRestrictionFlag(CSMRestrictionFlags::CSM_RF_LOAD_CLIENT_MODS)) { - warningstream << "Client side mods are disabled by server." << std::endl; - // If builtin integrity is wrong, disconnect user - if (!checkBuiltinIntegrity()) { - // @TODO disconnect user - } - return; - } - ClientModConfiguration modconf(getClientModsLuaPath()); m_mods = modconf.getMods(); // complain about mods with unsatisfied dependencies if (!modconf.isConsistent()) { modconf.printUnsatisfiedModsError(); + return; } // Print mods - infostream << "Client Loading mods: "; + infostream << "Client loading mods: "; for (const ModSpec &mod : m_mods) infostream << mod.name << " "; infostream << std::endl; - // Load and run "mod" scripts + // Load "mod" scripts for (const ModSpec &mod : m_mods) { if (!string_allowed(mod.name, MODNAME_ALLOWED_CHARS)) { throw ModError("Error loading mod \"" + mod.name + @@ -170,19 +185,23 @@ void Client::loadMods() scanModIntoMemory(mod.name, mod.path); } - // Load and run "mod" scripts + // Run them for (const ModSpec &mod : m_mods) m_script->loadModFromMemory(mod.name); + // Mods are done loading. Unlock callbacks + m_mods_loaded = true; + // Run a callback when mods are loaded m_script->on_mods_loaded(); - m_mods_loaded = true; -} -bool Client::checkBuiltinIntegrity() -{ - // @TODO - return true; + // Create objects if they're ready + if (m_state == LC_Ready) + m_script->on_client_ready(m_env.getLocalPlayer()); + if (m_camera) + m_script->on_camera_ready(m_camera); + if (m_minimap) + m_script->on_minimap_ready(m_minimap); } void Client::scanModSubfolder(const std::string &mod_name, const std::string &mod_path, @@ -191,14 +210,28 @@ void Client::scanModSubfolder(const std::string &mod_name, const std::string &mo 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.name[0] == '.') + continue; + if (j.dir) { - scanModSubfolder(mod_name, mod_path, mod_subpath - + filename + DIR_DELIM); + scanModSubfolder(mod_name, mod_path, mod_subpath + j.name + 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; + std::replace(mod_subpath.begin(), mod_subpath.end(), DIR_DELIM_CHAR, '/'); + + std::string real_path = full_path + j.name; + std::string vfs_path = mod_name + ":" + mod_subpath + j.name; + infostream << "Client::scanModSubfolder(): Loading \"" << real_path + << "\" as \"" << vfs_path << "\"." << std::endl; + + std::string contents; + if (!fs::ReadFile(real_path, contents)) { + errorstream << "Client::scanModSubfolder(): Can't read file \"" + << real_path << "\"." << std::endl; + continue; + } + + m_mod_vfs.emplace(vfs_path, contents); } } @@ -228,7 +261,7 @@ const ModSpec* Client::getModSpec(const std::string &modname) const void Client::Stop() { m_shutdown = true; - if (m_modding_enabled) + if (m_mods_loaded) m_script->on_shutdown(); //request all client managed threads to stop m_mesh_update_thread.stop(); @@ -238,7 +271,7 @@ void Client::Stop() m_localdb->endSave(); } - if (m_modding_enabled) + if (m_mods_loaded) delete m_script; } @@ -270,14 +303,11 @@ Client::~Client() } // cleanup 3d model meshes on client shutdown - while (RenderingEngine::get_mesh_cache()->getMeshCount() != 0) { - scene::IAnimatedMesh *mesh = RenderingEngine::get_mesh_cache()->getMeshByIndex(0); - - if (mesh) - RenderingEngine::get_mesh_cache()->removeMesh(mesh); - } + m_rendering_engine->cleanupMeshCache(); delete m_minimap; + m_minimap = nullptr; + delete m_media_downloader; } @@ -285,6 +315,7 @@ void Client::connect(Address address, bool is_local_server) { initLocalMapSaving(address, m_address_name, is_local_server); + // Since we use TryReceive() a timeout here would be ineffective anyway m_con->SetTimeoutMs(0); m_con->Connect(address); } @@ -309,12 +340,14 @@ void Client::step(float dtime) { float &counter = m_packetcounter_timer; counter -= dtime; - if(counter <= 0.0) + if(counter <= 0.0f) { - counter = 20.0; + counter = 30.0f; + u32 sum = m_packetcounter.sum(); + float avg = sum / counter; - infostream << "Client packetcounter (" << m_packetcounter_timer - << "):"< deleted_blocks; m_env.getMap().timerUpdate(map_timer_and_unload_dtime, g_settings->getFloat("client_unload_unused_data_timeout"), @@ -405,12 +437,9 @@ void Client::step(float dtime) /* Handle environment */ - // Control local player (0ms) LocalPlayer *player = m_env.getLocalPlayer(); - assert(player); - player->applyControl(dtime, &m_env); - // Step environment + // Step environment (also handles player controls) m_env.step(dtime); m_sound->step(dtime); @@ -421,7 +450,7 @@ void Client::step(float dtime) ClientEnvEvent envEvent = m_env.getClientEnvEvent(); if (envEvent.type == CEE_PLAYER_DAMAGE) { - u8 damage = envEvent.player_damage.amount; + u16 damage = envEvent.player_damage.amount; if (envEvent.player_damage.send_to_server) sendDamage(damage); @@ -443,7 +472,7 @@ void Client::step(float dtime) counter = 0.0; // connectedAndInitialized() is true, peer exists. float avg_rtt = getRTT(); - infostream << "Client: average rtt: " << avg_rtt << std::endl; + infostream << "Client: avg_rtt=" << avg_rtt << std::endl; } /* @@ -464,6 +493,7 @@ void Client::step(float dtime) */ { int num_processed_meshes = 0; + std::vector blocks_to_ack; while (!m_mesh_update_thread.m_queue_out.empty()) { num_processed_meshes++; @@ -502,14 +532,18 @@ void Client::step(float dtime) m_minimap->addBlock(r.p, minimap_mapblock); if (r.ack_block_to_server) { - /* - Acknowledge block - [0] u8 count - [1] v3s16 pos_0 - */ - sendGotBlocks(r.p); + if (blocks_to_ack.size() == 255) { + sendGotBlocks(blocks_to_ack); + blocks_to_ack.clear(); + } + + blocks_to_ack.emplace_back(r.p); } } + if (blocks_to_ack.size() > 0) { + // Acknowledge block(s) + sendGotBlocks(blocks_to_ack); + } if (num_processed_meshes > 0) g_profiler->graphAdd("num_processed_meshes", num_processed_meshes); @@ -542,8 +576,8 @@ void Client::step(float dtime) if (count_after != count_before) { // Do this every seconds after TOCLIENT_INVENTORY // Reset the locally changed inventory to the authoritative inventory - m_env.getLocalPlayer()->inventory = *m_inventory_from_server; - m_inventory_updated = true; + player->inventory = *m_inventory_from_server; + m_update_wielded_item = true; } } @@ -590,14 +624,17 @@ 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"); + int n = 0; 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()); + n++; } } + if (n > 0) + infostream << "Saved " << n << " modified mod storages." << std::endl; } // Write server map @@ -608,11 +645,9 @@ void Client::step(float dtime) } } -bool Client::loadMedia(const std::string &data, const std::string &filename) +bool Client::loadMedia(const std::string &data, const std::string &filename, + bool from_media_push) { - // Silly irrlicht's const-incorrectness - Buffer data_rw(data.c_str(), data.size()); - std::string name; const char *image_ext[] = { @@ -622,15 +657,14 @@ bool Client::loadMedia(const std::string &data, const std::string &filename) }; name = removeStringEnd(filename, image_ext); if (!name.empty()) { - verbosestream<<"Client: Attempting to load image " - <<"file \""<get_filesystem(); + video::IVideoDriver *vdrv = m_rendering_engine->get_video_driver(); - // Create an irrlicht memory file io::IReadFile *rfile = irrfs->createMemoryReadFile( - *data_rw, data_rw.getSize(), "_tempreadfile"); + data.c_str(), data.size(), "_tempreadfile"); FATAL_ERROR_IF(!rfile, "Could not create irrlicht memory file."); @@ -656,17 +690,15 @@ bool Client::loadMedia(const std::string &data, const std::string &filename) }; name = removeStringEnd(filename, sound_ext); if (!name.empty()) { - verbosestream<<"Client: Attempting to load sound " - <<"file \""<loadSoundData(name, data); - return true; + TRACESTREAM(<< "Client: Attempting to load sound " + << "file \"" << filename << "\"" << std::endl); + return m_sound->loadSoundData(name, data); } const char *model_ext[] = { ".x", ".b3d", ".md2", ".obj", NULL }; - name = removeStringEnd(filename, model_ext); if (!name.empty()) { verbosestream<<"Client: Storing model into memory: " @@ -683,9 +715,11 @@ bool Client::loadMedia(const std::string &data, const std::string &filename) }; name = removeStringEnd(filename, translate_ext); if (!name.empty()) { - verbosestream << "Client: Loading translation: " - << "\"" << filename << "\"" << std::endl; - g_translations->loadTranslation(data); + if (from_media_push) + return false; + TRACESTREAM(<< "Client: Loading translation: " + << "\"" << filename << "\"" << std::endl); + g_client_translations->loadTranslation(data); return true; } @@ -751,11 +785,20 @@ void Client::initLocalMapSaving(const Address &address, return; } - const std::string world_path = porting::path_user - + DIR_DELIM + "worlds" - + DIR_DELIM + "server_" + std::string world_path; +#define set_world_path(hostname) \ + world_path = porting::path_user \ + + DIR_DELIM + "worlds" \ + + DIR_DELIM + "server_" \ + hostname + "_" + std::to_string(address.getPort()); + set_world_path(hostname); + if (!fs::IsDir(world_path)) { + std::string hostname_escaped = hostname; + str_replace(hostname_escaped, ':', '_'); + set_world_path(hostname_escaped); + } +#undef set_world_path fs::CreateAllDirs(world_path); m_localdb = new MapDatabaseSQLite3(world_path); @@ -765,36 +808,31 @@ void Client::initLocalMapSaving(const Address &address, void Client::ReceiveAll() { + NetworkPacket pkt; u64 start_ms = porting::getTimeMs(); - for(;;) - { + const u64 budget = 100; + for(;;) { // Limit time even if there would be huge amounts of data to // process - if(porting::getTimeMs() > start_ms + 100) + if (porting::getTimeMs() > start_ms + budget) { + infostream << "Client::ReceiveAll(): " + "Packet processing budget exceeded." << std::endl; break; + } + pkt.clear(); try { - Receive(); - g_profiler->graphAdd("client_received_packets", 1); - } - catch(con::NoIncomingDataException &e) { - break; - } - catch(con::InvalidIncomingDataException &e) { - infostream<<"Client::ReceiveAll(): " + if (!m_con->TryReceive(&pkt)) + break; + ProcessData(&pkt); + } catch (const con::InvalidIncomingDataException &e) { + infostream << "Client::ReceiveAll(): " "InvalidIncomingDataException: what()=" - <Receive(&pkt); - ProcessData(&pkt); -} - inline void Client::handleCommand(NetworkPacket* pkt) { const ToClientCommandHandler& opHandle = toClientCommandTable[pkt->getCommand()]; @@ -811,6 +849,7 @@ void Client::ProcessData(NetworkPacket *pkt) //infostream<<"Client: received command="<graphAdd("client_received_packets", 1); /* If this check is removed, be sure to change the queue @@ -892,7 +931,7 @@ void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket * *pkt << fov << wanted_range; } -void Client::interact(u8 action, const PointedThing& pointed) +void Client::interact(InteractAction action, const PointedThing& pointed) { if(m_state != LC_Ready) { errorstream << "Client::interact() " @@ -912,19 +951,12 @@ void Client::interact(u8 action, const PointedThing& pointed) [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) - 2: digging completed - 3: place block or item (to abovesurface) - 4: use item - 5: perform secondary action of item */ NetworkPacket pkt(TOSERVER_INTERACT, 1 + 2 + 0); - pkt << action; - pkt << (u16)getPlayerItem(); + pkt << (u8)action; + pkt << myplayer->getWieldIndex(); std::ostringstream tmp_os(std::ios::binary); pointed.serialize(tmp_os); @@ -1058,10 +1090,13 @@ void Client::sendDeletedBlocks(std::vector &blocks) Send(&pkt); } -void Client::sendGotBlocks(v3s16 block) +void Client::sendGotBlocks(const std::vector &blocks) { - NetworkPacket pkt(TOSERVER_GOTBLOCKS, 1 + 6); - pkt << (u8) 1 << block; + NetworkPacket pkt(TOSERVER_GOTBLOCKS, 1 + 6 * blocks.size()); + pkt << (u8) blocks.size(); + for (const v3s16 &block : blocks) + pkt << block; + Send(&pkt); } @@ -1074,7 +1109,7 @@ void Client::sendRemovedSounds(std::vector &soundList) pkt << (u16) (server_ids & 0xFFFF); - for (int sound_id : soundList) + for (s32 sound_id : soundList) pkt << sound_id; Send(&pkt); @@ -1157,7 +1192,7 @@ void Client::sendChatMessage(const std::wstring &message) if (canSendChatMessage()) { u32 now = time(NULL); float time_passed = now - m_last_chat_message_sent; - m_last_chat_message_sent = time(NULL); + m_last_chat_message_sent = now; 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) @@ -1184,7 +1219,7 @@ void Client::clearOutChatQueue() } void Client::sendChangePassword(const std::string &oldpassword, - const std::string &newpassword) + const std::string &newpassword) { LocalPlayer *player = m_env.getLocalPlayer(); if (player == NULL) @@ -1197,9 +1232,9 @@ void Client::sendChangePassword(const std::string &oldpassword, } -void Client::sendDamage(u8 damage) +void Client::sendDamage(u16 damage) { - NetworkPacket pkt(TOSERVER_DAMAGE, sizeof(u8)); + NetworkPacket pkt(TOSERVER_DAMAGE, sizeof(u16)); pkt << damage; Send(&pkt); } @@ -1213,60 +1248,53 @@ void Client::sendRespawn() void Client::sendReady() { NetworkPacket pkt(TOSERVER_CLIENT_READY, - 1 + 1 + 1 + 1 + 2 + sizeof(char) * strlen(g_version_hash)); + 1 + 1 + 1 + 1 + 2 + sizeof(char) * strlen(g_version_hash) + 2); pkt << (u8) VERSION_MAJOR << (u8) VERSION_MINOR << (u8) VERSION_PATCH << (u8) 0 << (u16) strlen(g_version_hash); pkt.putRawString(g_version_hash, (u16) strlen(g_version_hash)); + pkt << (u16)FORMSPEC_API_VERSION; Send(&pkt); } void Client::sendPlayerPos() { - LocalPlayer *myplayer = m_env.getLocalPlayer(); - if (!myplayer) + LocalPlayer *player = m_env.getLocalPlayer(); + if (!player) return; ClientMap &map = m_env.getClientMap(); + u8 camera_fov = map.getCameraFov(); + u8 wanted_range = map.getControl().wanted_range; - 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_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_camera_fov = camera_fov; - myplayer->last_wanted_range = wanted_range; + // Save bandwidth by only updating position when + // player is not dead and something changed - NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4 + 1 + 1); - - writePlayerPos(myplayer, &map, &pkt); - - Send(&pkt); -} + if (m_activeobjects_received && player->isDead()) + return; -void Client::sendPlayerItem(u16 item) -{ - LocalPlayer *myplayer = m_env.getLocalPlayer(); - if (!myplayer) + if ( + player->last_position == player->getPosition() && + player->last_speed == player->getSpeed() && + player->last_pitch == player->getPitch() && + player->last_yaw == player->getYaw() && + player->last_keyPressed == player->keyPressed && + player->last_camera_fov == camera_fov && + player->last_wanted_range == wanted_range) return; - NetworkPacket pkt(TOSERVER_PLAYERITEM, 2); + player->last_position = player->getPosition(); + player->last_speed = player->getSpeed(); + player->last_pitch = player->getPitch(); + player->last_yaw = player->getYaw(); + player->last_keyPressed = player->keyPressed; + player->last_camera_fov = camera_fov; + player->last_wanted_range = wanted_range; - pkt << item; + NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4 + 1 + 1); + + writePlayerPos(player, &map, &pkt); Send(&pkt); } @@ -1293,7 +1321,7 @@ void Client::removeNode(v3s16 p) * @param is_valid_position * @return */ -MapNode Client::getNode(v3s16 p, bool *is_valid_position) +MapNode Client::CSMGetNode(v3s16 p, bool *is_valid_position) { if (checkCSMRestrictionFlag(CSMRestrictionFlags::CSM_RF_LOOKUP_NODES)) { v3s16 ppos = floatToInt(m_env.getLocalPlayer()->getPosition(), BS); @@ -1302,7 +1330,32 @@ MapNode Client::getNode(v3s16 p, bool *is_valid_position) return {}; } } - return m_env.getMap().getNodeNoEx(p, is_valid_position); + return m_env.getMap().getNode(p, is_valid_position); +} + +int Client::CSMClampRadius(v3s16 pos, int radius) +{ + if (!checkCSMRestrictionFlag(CSMRestrictionFlags::CSM_RF_LOOKUP_NODES)) + return radius; + // This is approximate and will cause some allowed nodes to be excluded + v3s16 ppos = floatToInt(m_env.getLocalPlayer()->getPosition(), BS); + u32 distance = ppos.getDistanceFrom(pos); + if (distance >= m_csm_restriction_noderange) + return 0; + return std::min(radius, m_csm_restriction_noderange - distance); +} + +v3s16 Client::CSMClampPos(v3s16 pos) +{ + if (!checkCSMRestrictionFlag(CSMRestrictionFlags::CSM_RF_LOOKUP_NODES)) + return pos; + v3s16 ppos = floatToInt(m_env.getLocalPlayer()->getPosition(), BS); + const int range = m_csm_restriction_noderange; + return v3s16( + core::clamp(pos.X, (int)ppos.X - range, (int)ppos.X + range), + core::clamp(pos.Y, (int)ppos.Y - range, (int)ppos.Y + range), + core::clamp(pos.Z, (int)ppos.Z - range, (int)ppos.Z + range) + ); } void Client::addNode(v3s16 p, MapNode n, bool remove_metadata) @@ -1330,28 +1383,38 @@ void Client::setPlayerControl(PlayerControl &control) player->control = control; } -void Client::selectPlayerItem(u16 item) +void Client::setPlayerItem(u16 item) { - m_playeritem = item; - m_inventory_updated = true; - sendPlayerItem(item); -} + m_env.getLocalPlayer()->setWieldIndex(item); + m_update_wielded_item = true; -// Returns true if the inventory of the local player has been -// updated from the server. If it is true, it is set to false. -bool Client::getLocalInventoryUpdated() -{ - bool updated = m_inventory_updated; - m_inventory_updated = false; - return updated; + NetworkPacket pkt(TOSERVER_PLAYERITEM, 2); + pkt << item; + Send(&pkt); } -// Copies the inventory of the local player to parameter -void Client::getLocalInventory(Inventory &dst) +// Returns true once after the inventory of the local player +// has been updated from the server. +bool Client::updateWieldedItem() { + if (!m_update_wielded_item) + return false; + + m_update_wielded_item = false; + LocalPlayer *player = m_env.getLocalPlayer(); assert(player); - dst = player->inventory; + if (auto *list = player->inventory.getList("main")) + list->setModified(false); + if (auto *list = player->inventory.getList("hand")) + list->setModified(false); + + return true; +} + +scene::ISceneManager* Client::getSceneManager() +{ + return m_rendering_engine->get_scene_manager(); } Inventory* Client::getInventory(const InventoryLocation &loc) @@ -1494,22 +1557,11 @@ void Client::typeChatMessage(const std::wstring &message) return; // If message was consumed by script API, don't send it to server - if (m_modding_enabled && m_script->on_sending_message(wide_to_utf8(message))) + if (m_mods_loaded && m_script->on_sending_message(wide_to_utf8(message))) return; // Send to others sendChatMessage(message); - - // Show locally - 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) @@ -1595,11 +1647,6 @@ ClientEvent *Client::getClientEvent() return event; } -bool Client::connectedToServer() -{ - return m_con->Connected(); -} - const Address Client::getServerAddress() { return m_con->GetPeerAddress(PEER_ID_SERVER); @@ -1621,7 +1668,7 @@ typedef struct TextureUpdateArgs { ITextureSource *tsrc; } TextureUpdateArgs; -void texture_update_progress(void *args, u32 progress, u32 max_progress) +void Client::showUpdateProgressTexture(void *args, u32 progress, u32 max_progress) { TextureUpdateArgs* targs = (TextureUpdateArgs*) args; u16 cur_percent = ceil(progress / (double) max_progress * 100.); @@ -1640,7 +1687,7 @@ 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 << "%..."; - RenderingEngine::draw_load_screen(strm.str(), targs->guienv, targs->tsrc, 0, + m_rendering_engine->draw_load_screen(strm.str(), targs->guienv, targs->tsrc, 0, 72 + (u16) ((18. / 100.) * (double) targs->last_percent), true); } } @@ -1661,24 +1708,27 @@ void Client::afterContentReceived() // Rebuild inherited images and recreate textures infostream<<"- Rebuilding images and textures"<draw_load_screen(text, guienv, m_tsrc, 0, 70); m_tsrc->rebuildImagesAndTextures(); delete[] text; // Rebuild shaders infostream<<"- Rebuilding shaders"<draw_load_screen(text, guienv, m_tsrc, 0, 71); m_shsrc->rebuildShaders(); delete[] text; // Update node aliases infostream<<"- Updating node aliases"<draw_load_screen(text, guienv, m_tsrc, 0, 72); m_nodedef->updateAliases(m_itemdef); - for (const auto &path : getTextureDirs()) - m_nodedef->applyTextureOverrides(path + DIR_DELIM + "override.txt"); + for (const auto &path : getTextureDirs()) { + TextureOverrideSource override_source(path + DIR_DELIM + "override.txt"); + m_nodedef->applyTextureOverrides(override_source.getNodeTileOverrides()); + m_itemdef->applyTextureOverrides(override_source.getItemTextureOverrides()); + } m_nodedef->setNodeRegistrationStatus(true); m_nodedef->runNodeResolveCallbacks(); delete[] text; @@ -1691,7 +1741,7 @@ void Client::afterContentReceived() 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); + m_nodedef->updateTextures(this, &tu_args); delete[] tu_args.text_base; // Start mesh update thread after setting up content definitions @@ -1701,27 +1751,18 @@ void Client::afterContentReceived() m_state = LC_Ready; sendReady(); - if (g_settings->getBool("enable_client_modding")) { + if (m_mods_loaded) m_script->on_client_ready(m_env.getLocalPlayer()); - } text = wgettext("Done!"); - RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 100); + m_rendering_engine->draw_load_screen(text, guienv, m_tsrc, 0, 100); infostream<<"Client::afterContentReceived() done"<getPeerStat(PEER_ID_SERVER, con::AVG_RTT); - float time_from_last_rtt = - m_con->getPeerStat(PEER_ID_SERVER, con::TIMEOUT_COUNTER); - if (avg_rtt + 2.0f > time_from_last_rtt) - return avg_rtt; - return time_from_last_rtt; + return m_con->getPeerStat(PEER_ID_SERVER,con::AVG_RTT); } float Client::getCurRate() @@ -1732,7 +1773,7 @@ float Client::getCurRate() void Client::makeScreenshot() { - irr::video::IVideoDriver *driver = RenderingEngine::get_video_driver(); + irr::video::IVideoDriver *driver = m_rendering_engine->get_video_driver(); irr::video::IImage* const raw_image = driver->createScreenShot(); if (!raw_image) @@ -1744,13 +1785,24 @@ void Client::makeScreenshot() 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") + std::string screenshot_dir; + + if (fs::IsPathAbsolute(g_settings->get("screenshot_path"))) + screenshot_dir = g_settings->get("screenshot_path"); + else + screenshot_dir = porting::path_user + DIR_DELIM + g_settings->get("screenshot_path"); + + std::string filename_base = screenshot_dir + DIR_DELIM + std::string("screenshot_") + std::string(timetstamp_c); std::string filename_ext = "." + g_settings->get("screenshot_format"); std::string filename; + // Create the directory if it doesn't already exist. + // Otherwise, saving the screenshot would fail. + fs::CreateDir(screenshot_dir); + u32 quality = (u32)g_settings->getS32("screenshot_quality"); quality = MYMIN(MYMAX(quality, 0), 100) / 100.0 * 255; @@ -1781,7 +1833,7 @@ void Client::makeScreenshot() sstr << "Failed to save screenshot '" << filename << "'"; } pushToChatQueue(new ChatMessage(CHATMESSAGE_TYPE_SYSTEM, - narrow_to_wide(sstr.str()))); + utf8_to_wide(sstr.str()))); infostream << sstr.str() << std::endl; image->drop(); } @@ -1824,7 +1876,7 @@ ITextureSource* Client::getTextureSource() { return m_tsrc; } -IShaderSource* Client::getShaderSource() +IWritableShaderSource* Client::getShaderSource() { return m_shsrc; } @@ -1863,27 +1915,34 @@ scene::IAnimatedMesh* Client::getMesh(const std::string &filename, bool cache) // 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::IReadFile *rfile = RenderingEngine::get_filesystem()->createMemoryReadFile( - *data_rw, data_rw.getSize(), filename.c_str()); + io::IReadFile *rfile = m_rendering_engine->get_filesystem()->createMemoryReadFile( + data.c_str(), data.size(), filename.c_str()); FATAL_ERROR_IF(!rfile, "Could not create/open RAM file"); - scene::IAnimatedMesh *mesh = RenderingEngine::get_scene_manager()->getMesh(rfile); + scene::IAnimatedMesh *mesh = m_rendering_engine->get_scene_manager()->getMesh(rfile); rfile->drop(); + if (!mesh) + return nullptr; mesh->grab(); if (!cache) - RenderingEngine::get_mesh_cache()->removeMesh(mesh); + m_rendering_engine->removeMesh(mesh); return mesh; } -const std::string* Client::getModFile(const std::string &filename) +const std::string* Client::getModFile(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; - } + // strip dir delimiter from beginning of path + auto pos = filename.find_first_of(':'); + if (pos == std::string::npos) + return nullptr; + pos++; + auto pos2 = filename.find_first_not_of('/', pos); + if (pos2 > pos) + filename.erase(pos, pos2 - pos); + + StringMap::const_iterator it = m_mod_vfs.find(filename); + if (it == m_mod_vfs.end()) + return nullptr; return &it->second; }