#include "clientmap.h"
#include "clientmedia.h"
#include "version.h"
+#include "database/database-files.h"
#include "database/database-sqlite3.h"
#include "serialization.h"
#include "guiscalingfilter.h"
NodeDefManager *nodedef,
ISoundManager *sound,
MtEventManager *event,
+ RenderingEngine *rendering_engine,
bool ipv6,
GameUI *game_ui
):
m_nodedef(nodedef),
m_sound(sound),
m_event(event),
+ m_rendering_engine(rendering_engine),
m_env(
- new ClientMap(this, control, 666),
+ new ClientMap(this, rendering_engine, control, 666),
tsrc, this
),
m_particle_manager(&m_env),
// Add local player
m_env.setLocalPlayer(new LocalPlayer(this, playername));
+ // Make the mod storage database and begin the save for later
+ m_mod_storage_database =
+ new ModMetadataDatabaseSQLite3(porting::path_user + DIR_DELIM + "client");
+ m_mod_storage_database->beginSave();
+
if (g_settings->getBool("enable_minimap")) {
m_minimap = new Minimap(this);
}
+
m_cache_save_interval = g_settings->getU16("server_map_save_interval");
}
+void Client::migrateModStorage()
+{
+ std::string mod_storage_dir = porting::path_user + DIR_DELIM + "client";
+ std::string old_mod_storage = mod_storage_dir + DIR_DELIM + "mod_storage";
+ if (fs::IsDir(old_mod_storage)) {
+ infostream << "Migrating client mod storage to SQLite3 database" << std::endl;
+ {
+ ModMetadataDatabaseFiles files_db(mod_storage_dir);
+ std::vector<std::string> mod_list;
+ files_db.listMods(&mod_list);
+ for (const std::string &modname : mod_list) {
+ infostream << "Migrating client mod storage for mod " << modname << std::endl;
+ StringMap meta;
+ files_db.getModEntries(modname, &meta);
+ for (const auto &pair : meta) {
+ m_mod_storage_database->setModEntry(modname, pair.first, pair.second);
+ }
+ }
+ }
+ if (!fs::Rename(old_mod_storage, old_mod_storage + ".bak")) {
+ // Execution cannot move forward if the migration does not complete.
+ throw BaseException("Could not finish migrating client mod storage");
+ }
+ infostream << "Finished migration of client mod storage" << std::endl;
+ }
+}
+
void Client::loadMods()
{
// Don't load mods twice.
scanModIntoMemory(BUILTIN_MOD_NAME, getBuiltinLuaPath());
m_script->loadModFromMemory(BUILTIN_MOD_NAME);
- // TODO Uncomment when server-sent CSM and verifying of builtin are complete
- /*
- // Don't load client-provided mods if disabled by server
- if (checkCSMRestrictionFlag(CSMRestrictionFlags::CSM_RF_LOAD_CLIENT_MODS)) {
- warningstream << "Client-provided mod loading is 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
// Load "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.");
- }
+ mod.checkAndLog();
scanModIntoMemory(mod.name, mod.path);
}
m_script->on_minimap_ready(m_minimap);
}
-bool Client::checkBuiltinIntegrity()
-{
- // TODO
- return true;
-}
-
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<fs::DirListNode> mod = fs::GetDirListing(full_path);
for (const fs::DirListNode &j : mod) {
+ if (j.name[0] == '.')
+ continue;
+
if (j.dir) {
scanModSubfolder(mod_name, mod_path, mod_subpath + j.name + DIR_DELIM);
continue;
infostream << "Client::scanModSubfolder(): Loading \"" << real_path
<< "\" as \"" << vfs_path << "\"." << std::endl;
- std::ifstream is(real_path, std::ios::binary | std::ios::ate);
- if(!is.good()) {
+ std::string contents;
+ if (!fs::ReadFile(real_path, contents)) {
errorstream << "Client::scanModSubfolder(): Can't read file \""
<< real_path << "\"." << std::endl;
continue;
}
- auto size = is.tellg();
- std::string contents(size, '\0');
- is.seekg(0);
- is.read(&contents[0], size);
- infostream << " size: " << size << " bytes" << std::endl;
m_mod_vfs.emplace(vfs_path, contents);
}
}
}
// cleanup 3d model meshes on client shutdown
- while (RenderingEngine::get_mesh_cache()->getMeshCount() != 0) {
- scene::IAnimatedMesh *mesh = RenderingEngine::get_mesh_cache()->getMeshByIndex(0);
+ m_rendering_engine->cleanupMeshCache();
- if (mesh)
- RenderingEngine::get_mesh_cache()->removeMesh(mesh);
- }
+ guiScalingCacheClear();
delete m_minimap;
+ m_minimap = nullptr;
+
delete m_media_downloader;
+
+ // Write the changes and delete
+ if (m_mod_storage_database)
+ m_mod_storage_database->endSave();
+ delete m_mod_storage_database;
}
void Client::connect(Address address, bool is_local_server)
m_media_downloader = NULL;
}
}
+ {
+ // Acknowledge dynamic media downloads to server
+ std::vector<u32> done;
+ for (auto it = m_pending_media_downloads.begin();
+ it != m_pending_media_downloads.end();) {
+ assert(it->second->isStarted());
+ it->second->step(this);
+ if (it->second->isDone()) {
+ done.emplace_back(it->first);
+
+ it = m_pending_media_downloads.erase(it);
+ } else {
+ it++;
+ }
+
+ if (done.size() == 255) { // maximum in one packet
+ sendHaveMedia(done);
+ done.clear();
+ }
+ }
+ if (!done.empty())
+ sendHaveMedia(done);
+ }
/*
If the server didn't update the inventory in a while, revert
}
}
+ // Write changes to the mod storage
m_mod_storage_save_timer -= dtime;
if (m_mod_storage_save_timer <= 0.0f) {
m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval");
- int n = 0;
- for (std::unordered_map<std::string, ModMetadata *>::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;
+ m_mod_storage_database->endSave();
+ m_mod_storage_database->beginSave();
}
// Write server map
TRACESTREAM(<< "Client: Attempting to load image "
<< "file \"" << filename << "\"" << std::endl);
- io::IFileSystem *irrfs = RenderingEngine::get_filesystem();
- video::IVideoDriver *vdrv = RenderingEngine::get_video_driver();
+ io::IFileSystem *irrfs = m_rendering_engine->get_filesystem();
+ video::IVideoDriver *vdrv = m_rendering_engine->get_video_driver();
- // Silly irrlicht's const-incorrectness
- Buffer<char> data_rw(data.c_str(), data.size());
-
- // 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.");
Send(&pkt);
infostream << "Client: Sending media request list to server ("
- << file_requests.size() << " files. packet size)" << std::endl;
+ << file_requests.size() << " files, packet size "
+ << pkt.getSize() << ")" << std::endl;
}
void Client::initLocalMapSaving(const Address &address,
*/
if(sender_peer_id != PEER_ID_SERVER) {
infostream << "Client::ProcessData(): Discarding data not "
- "coming from server: peer_id=" << sender_peer_id
+ "coming from server: peer_id=" << sender_peer_id << " command=" << pkt->getCommand()
<< std::endl;
return;
}
// Will fill up 12 + 12 + 4 + 4 + 4 bytes
void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket *pkt)
-{
+{
v3f pf = myplayer->getLegitPosition() * 100;
- v3f sf = myplayer->getLegitSpeed() * 100;
+ v3f sf = myplayer->getSendSpeed() * 100;
s32 pitch = myplayer->getPitch() * 100;
s32 yaw = myplayer->getYaw() * 100;
- u32 keyPressed = myplayer->keyPressed;
+ u32 keyPressed = myplayer->control.getKeysPressed();
// scaled by 80, so that pi can fit into a u8
u8 fov = clientMap->getCameraFov() * 80;
u8 wanted_range = MYMIN(255,
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)
if (!player)
return;
+ // Save bandwidth by only updating position when
+ // player is not dead and something changed
+
+ if (m_activeobjects_received && player->isDead())
+ 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
- // player is not dead and something changed
-
- // FIXME: This part causes breakages in mods like 3d_armor, and has been commented for now
- // if (m_activeobjects_received && player->isDead())
- // return;
+ u32 keyPressed = player->control.getKeysPressed();
if (
player->last_position == pos &&
- player->last_speed == player->getLegitSpeed() &&
+ player->last_speed == player->getSendSpeed() &&
player->last_pitch == player->getPitch() &&
player->last_yaw == player->getYaw() &&
- player->last_keyPressed == player->keyPressed &&
- player->last_camera_fov == camera_fov &&
+ player->last_keyPressed == keyPressed &&
+ player->last_camera_fov == camera_fov &&
player->last_wanted_range == wanted_range)
return;
player->last_position = pos;
- player->last_speed = player->getLegitSpeed();
+ player->last_speed = player->getSendSpeed();
player->last_pitch = player->getPitch();
player->last_yaw = player->getYaw();
- player->last_keyPressed = player->keyPressed;
+ player->last_keyPressed = keyPressed;
player->last_camera_fov = camera_fov;
player->last_wanted_range = wanted_range;
sendPlayerPos(player->getLegitPosition());
}
+void Client::sendHaveMedia(const std::vector<u32> &tokens)
+{
+ NetworkPacket pkt(TOSERVER_HAVE_MEDIA, 1 + tokens.size() * 4);
+
+ sanity_check(tokens.size() < 256);
+
+ pkt << static_cast<u8>(tokens.size());
+ for (u32 token : tokens)
+ pkt << token;
+
+ Send(&pkt);
+}
+
void Client::removeNode(v3s16 p)
{
std::map<v3s16, MapBlock*> modified_blocks;
return true;
}
+scene::ISceneManager* Client::getSceneManager()
+{
+ return m_rendering_engine->get_scene_manager();
+}
+
Inventory* Client::getInventory(const InventoryLocation &loc)
{
switch(loc.type){
void Client::addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server, bool urgent)
{
- try{
- addUpdateMeshTask(blockpos, ack_to_server, urgent);
- }
- catch(InvalidPositionException &e){}
-
- // Leading edge
- for (int i=0;i<6;i++)
- {
- try{
- v3s16 p = blockpos + g_6dirs[i];
- addUpdateMeshTask(p, false, urgent);
- }
- catch(InvalidPositionException &e){}
- }
+ m_mesh_update_thread.updateBlock(&m_env.getMap(), blockpos, ack_to_server, urgent, true);
}
void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool urgent)
<<std::endl;
}
- v3s16 blockpos = getNodeBlockPos(nodepos);
+ v3s16 blockpos = getNodeBlockPos(nodepos);
v3s16 blockpos_relative = blockpos * MAP_BLOCKSIZE;
-
- try{
- addUpdateMeshTask(blockpos, ack_to_server, urgent);
- }
- catch(InvalidPositionException &e) {}
-
+ m_mesh_update_thread.updateBlock(&m_env.getMap(), blockpos, ack_to_server, urgent, false);
// Leading edge
- if(nodepos.X == blockpos_relative.X){
- try{
- v3s16 p = blockpos + v3s16(-1,0,0);
- addUpdateMeshTask(p, false, urgent);
- }
- catch(InvalidPositionException &e){}
- }
-
- if(nodepos.Y == blockpos_relative.Y){
- try{
- v3s16 p = blockpos + v3s16(0,-1,0);
- addUpdateMeshTask(p, false, urgent);
- }
- catch(InvalidPositionException &e){}
- }
-
- if(nodepos.Z == blockpos_relative.Z){
- try{
- v3s16 p = blockpos + v3s16(0,0,-1);
- addUpdateMeshTask(p, false, urgent);
- }
- catch(InvalidPositionException &e){}
- }
+ if (nodepos.X == blockpos_relative.X)
+ addUpdateMeshTask(blockpos + v3s16(-1, 0, 0), false, urgent);
+ if (nodepos.Y == blockpos_relative.Y)
+ addUpdateMeshTask(blockpos + v3s16(0, -1, 0), false, urgent);
+ if (nodepos.Z == blockpos_relative.Z)
+ addUpdateMeshTask(blockpos + v3s16(0, 0, -1), false, urgent);
}
void Client::updateAllMapBlocks()
{
v3s16 currentBlock = getNodeBlockPos(floatToInt(m_env.getLocalPlayer()->getPosition(), BS));
-
+
for (s16 X = currentBlock.X - 2; X <= currentBlock.X + 2; X++)
for (s16 Y = currentBlock.Y - 2; Y <= currentBlock.Y + 2; Y++)
for (s16 Z = currentBlock.Z - 2; Z <= currentBlock.Z + 2; Z++)
addUpdateMeshTask(v3s16(X, Y, Z), false, true);
-
- std::map<v2s16, MapSector*> *sectors = m_env.getMap().getSectorsPtr();
-
- for (auto §or_it : *sectors) {
- MapSector *sector = sector_it.second;
- MapBlockVect blocks;
- sector->getBlocks(blocks);
- for (MapBlock *block : blocks) {
- addUpdateMeshTask(block->getPos(), false, false);
- }
+
+ Map &map = m_env.getMap();
+
+ std::vector<v3s16> positions;
+ map.listAllLoadedBlocks(positions);
+
+ for (v3s16 p : positions) {
+ addUpdateMeshTask(p, false, false);
}
}
return event;
}
-bool Client::connectedToServer()
-{
- return m_con->Connected();
-}
-
const Address Client::getServerAddress()
{
return m_con->GetPeerAddress(PEER_ID_SERVER);
return 1.0; // downloader only exists when not yet done
}
-typedef struct TextureUpdateArgs {
+struct TextureUpdateArgs {
gui::IGUIEnvironment *guienv;
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)
+void Client::showUpdateProgressTexture(void *args, u32 progress, u32 max_progress)
{
TextureUpdateArgs* targs = (TextureUpdateArgs*) args;
u16 cur_percent = ceil(progress / (double) max_progress * 100.);
if (do_draw) {
targs->last_time_ms = time_ms;
- std::basic_stringstream<wchar_t> strm;
- strm << targs->text_base << " " << targs->last_percent << "%...";
- RenderingEngine::draw_load_screen(strm.str(), targs->guienv, targs->tsrc, 0,
+ std::wostringstream strm;
+ strm << targs->text_base << L" " << targs->last_percent << L"%...";
+ m_rendering_engine->draw_load_screen(strm.str(), targs->guienv, targs->tsrc, 0,
72 + (u16) ((18. / 100.) * (double) targs->last_percent), true);
}
}
// Rebuild inherited images and recreate textures
infostream<<"- Rebuilding images and textures"<<std::endl;
- RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 70);
+ m_rendering_engine->draw_load_screen(text, guienv, m_tsrc, 0, 70);
m_tsrc->rebuildImagesAndTextures();
delete[] text;
// Rebuild shaders
infostream<<"- Rebuilding shaders"<<std::endl;
text = wgettext("Rebuilding shaders...");
- RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 71);
+ m_rendering_engine->draw_load_screen(text, guienv, m_tsrc, 0, 71);
m_shsrc->rebuildShaders();
delete[] text;
// Update node aliases
infostream<<"- Updating node aliases"<<std::endl;
text = wgettext("Initializing nodes...");
- RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 72);
+ m_rendering_engine->draw_load_screen(text, guienv, m_tsrc, 0, 72);
m_nodedef->updateAliases(m_itemdef);
for (const auto &path : getTextureDirs()) {
TextureOverrideSource override_source(path + DIR_DELIM + "override.txt");
tu_args.guienv = guienv;
tu_args.last_time_ms = porting::getTimeMs();
tu_args.last_percent = 0;
- tu_args.text_base = wgettext("Initializing nodes");
+ 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
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"<<std::endl;
delete[] text;
}
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)
return;
- time_t t = time(NULL);
- struct tm *tm = localtime(&t);
+ const struct tm tm = mt_localtime();
char timetstamp_c[64];
- strftime(timetstamp_c, sizeof(timetstamp_c), "%Y%m%d_%H%M%S", tm);
+ strftime(timetstamp_c, sizeof(timetstamp_c), "%Y%m%d_%H%M%S", &tm);
std::string screenshot_dir;
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();
}
// Create the mesh, remove it from cache and return it
// This allows unique vertex colors and other properties for each instance
- Buffer<char> 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;
}
{
std::unordered_map<std::string, ModMetadata *>::const_iterator it =
m_mod_storages.find(name);
- if (it != m_mod_storages.end()) {
- // Save unconditionaly on unregistration
- it->second->save(getModStoragePath());
+ if (it != m_mod_storages.end())
m_mod_storages.erase(name);
- }
-}
-
-std::string Client::getModStoragePath() const
-{
- return porting::path_user + DIR_DELIM + "client" + DIR_DELIM + "mod_storage";
}
/*