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
*/
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_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),
if (g_settings->getBool("enable_minimap")) {
m_minimap = new Minimap(this);
}
+
m_cache_save_interval = g_settings->getU16("server_map_save_interval");
}
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
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 +
scanModIntoMemory(mod.name, mod.path);
}
- // Load and run "mod" scripts
+ // Run them
for (const ModSpec &mod : m_mods)
m_script->loadModFromMemory(mod.name);
// Run a callback when mods are loaded
m_script->on_mods_loaded();
+
+ // 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);
-}
-
-bool Client::checkBuiltinIntegrity()
-{
- // TODO
- return true;
+ if (m_minimap)
+ m_script->on_minimap_ready(m_minimap);
}
void Client::scanModSubfolder(const std::string &mod_name, const std::string &mod_path,
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);
-
- if (mesh)
- RenderingEngine::get_mesh_cache()->removeMesh(mesh);
- }
+ m_rendering_engine->cleanupMeshCache();
delete m_minimap;
+ m_minimap = nullptr;
+
delete m_media_downloader;
}
{
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
- << "):"<<std::endl;
+ infostream << "Client packetcounter (" << counter << "s): "
+ << "sum=" << sum << " avg=" << avg << "/s" << std::endl;
m_packetcounter.print(infostream);
m_packetcounter.clear();
}
/*
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);
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<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;
}
// Write server map
}
}
-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<char> data_rw(data.c_str(), data.size());
-
std::string name;
const char *image_ext[] = {
};
name = removeStringEnd(filename, image_ext);
if (!name.empty()) {
- verbosestream<<"Client: Attempting to load image "
- <<"file \""<<filename<<"\""<<std::endl;
+ 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();
- // 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.");
};
name = removeStringEnd(filename, sound_ext);
if (!name.empty()) {
- verbosestream<<"Client: Attempting to load sound "
- <<"file \""<<filename<<"\""<<std::endl;
- m_sound->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: "
};
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;
}
return false;
}
+bool Client::extractZipFile(const char *filename, const std::string &destination)
+{
+ auto fs = m_rendering_engine->get_filesystem();
+
+ if (!fs->addFileArchive(filename, false, false, io::EFAT_ZIP)) {
+ return false;
+ }
+
+ sanity_check(fs->getFileArchiveCount() > 0);
+
+ /**********************************************************************/
+ /* WARNING this is not threadsafe!! */
+ /**********************************************************************/
+ io::IFileArchive* opened_zip = fs->getFileArchive(fs->getFileArchiveCount() - 1);
+
+ const io::IFileList* files_in_zip = opened_zip->getFileList();
+
+ unsigned int number_of_files = files_in_zip->getFileCount();
+
+ for (unsigned int i=0; i < number_of_files; i++) {
+ std::string fullpath = destination;
+ fullpath += DIR_DELIM;
+ fullpath += files_in_zip->getFullFileName(i).c_str();
+ std::string fullpath_dir = fs::RemoveLastPathComponent(fullpath);
+
+ if (!files_in_zip->isDirectory(i)) {
+ if (!fs::PathExists(fullpath_dir) && !fs::CreateAllDirs(fullpath_dir)) {
+ fs->removeFileArchive(fs->getFileArchiveCount()-1);
+ return false;
+ }
+
+ io::IReadFile* toread = opened_zip->createAndOpenFile(i);
+
+ FILE *targetfile = fopen(fullpath.c_str(),"wb");
+
+ if (targetfile == NULL) {
+ fs->removeFileArchive(fs->getFileArchiveCount()-1);
+ return false;
+ }
+
+ char read_buffer[1024];
+ long total_read = 0;
+
+ while (total_read < toread->getSize()) {
+
+ unsigned int bytes_read =
+ toread->read(read_buffer,sizeof(read_buffer));
+ if ((bytes_read == 0 ) ||
+ (fwrite(read_buffer, 1, bytes_read, targetfile) != bytes_read))
+ {
+ fclose(targetfile);
+ fs->removeFileArchive(fs->getFileArchiveCount() - 1);
+ return false;
+ }
+ total_read += bytes_read;
+ }
+
+ fclose(targetfile);
+ }
+
+ }
+
+ fs->removeFileArchive(fs->getFileArchiveCount() - 1);
+ return true;
+}
+
// Virtual methods from con::PeerHandler
void Client::peerAdded(con::Peer *peer)
{
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)
// 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;
+ if (m_activeobjects_received && player->isDead())
+ return;
if (
player->last_position == player->getPosition() &&
player->last_pitch == player->getPitch() &&
player->last_yaw == player->getYaw() &&
player->last_keyPressed == player->keyPressed &&
- player->last_camera_fov == camera_fov &&
+ player->last_camera_fov == camera_fov &&
player->last_wanted_range == wanted_range)
return;
return true;
}
+irr::scene::ISceneManager* Client::getSceneManager()
+{
+ return m_rendering_engine->get_scene_manager();
+}
+
Inventory* Client::getInventory(const InventoryLocation &loc)
{
switch(loc.type){
return event;
}
-bool Client::connectedToServer()
-{
- return m_con->Connected();
-}
-
const Address Client::getServerAddress()
{
return m_con->GetPeerAddress(PEER_ID_SERVER);
// 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())
- 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;
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)
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;
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();
}
{
return m_tsrc;
}
-IShaderSource* Client::getShaderSource()
+IWritableShaderSource* Client::getShaderSource()
{
return m_shsrc;
}
// 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;
}