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
if (!modconf.isConsistent()) {
modconf.printUnsatisfiedModsError();
+ return;
}
// Print 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 +
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,
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) {
- std::string filename = j.name;
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);
}
}
}
// 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;
}
{
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);
}
{
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)
{
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);
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()="
- <<e.what()<<std::endl;
+ << e.what() << std::endl;
}
}
}
-void Client::Receive()
-{
- NetworkPacket pkt;
- m_con->Receive(&pkt);
- ProcessData(&pkt);
-}
-
inline void Client::handleCommand(NetworkPacket* pkt)
{
const ToClientCommandHandler& opHandle = toClientCommandTable[pkt->getCommand()];
//infostream<<"Client: received command="<<command<<std::endl;
m_packetcounter.add((u16)command);
+ g_profiler->graphAdd("client_received_packets", 1);
/*
If this check is removed, be sure to change the queue
pkt << (u16) (server_ids & 0xFFFF);
- for (int sound_id : soundList)
+ for (s32 sound_id : soundList)
pkt << sound_id;
Send(&pkt);
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)
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)
+ // Save bandwidth by only updating position when
+ // player is not dead and something changed
+
+ if (m_activeobjects_received && player->isDead())
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;
+ 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;
+
+ 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;
NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4 + 1 + 1);
- writePlayerPos(myplayer, &map, &pkt);
+ writePlayerPos(player, &map, &pkt);
Send(&pkt);
}
* @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);
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<int>(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<int>(pos.X, (int)ppos.X - range, (int)ppos.X + range),
+ core::clamp<int>(pos.Y, (int)ppos.Y - range, (int)ppos.Y + range),
+ core::clamp<int>(pos.Z, (int)ppos.Z - range, (int)ppos.Z + range)
+ );
+}
+
void Client::addNode(v3s16 p, MapNode n, bool remove_metadata)
{
//TimeTaker timer1("Client::addNode()");
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;
}
-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;
}