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
*/
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 scripting is enabled by the
- // client.
- if (m_modding_enabled) {
- m_script = new ClientScripting(this);
- m_env.setScript(m_script);
- m_script->setEnv(&m_env);
- }
}
void Client::loadMods()
// 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 || !m_modding_enabled) {
+ if (m_mods_loaded || !g_settings->getBool("enable_client_modding"))
return;
- }
// If client scripting is disabled by the server, don't load builtin or
// client-provided mods.
if (checkCSMRestrictionFlag(CSMRestrictionFlags::CSM_RF_LOAD_CLIENT_MODS)) {
warningstream << "Client-provided mod loading is disabled by server." <<
std::endl;
- // This line is needed because builtin is not loaded
- m_modding_enabled = false;
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);
// 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);
+ // 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;
+
+ // 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);
}
bool Client::checkBuiltinIntegrity()
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::ifstream is(real_path, std::ios::binary | std::ios::ate);
+ if(!is.good()) {
+ 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);
}
}
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();
m_localdb->endSave();
}
- if (m_modding_enabled)
+ if (m_mods_loaded)
delete m_script;
}
{
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();
}
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();
+ // 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");
};
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;
}
- 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);
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;
-
- // 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)
+ 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;
+
+ 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;
- 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;
+ 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;
// 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
text = wgettext("Initializing nodes...");
RenderingEngine::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_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);
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;
{
return m_tsrc;
}
-IShaderSource* Client::getShaderSource()
+IWritableShaderSource* Client::getShaderSource()
{
return m_shsrc;
}
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;
}