bool simple_singleplayer_mode,
Address bind_addr,
bool dedicated,
- ChatInterface *iface
+ ChatInterface *iface,
+ std::string *on_shutdown_errmsg
):
m_bind_addr(bind_addr),
m_path_world(path_world),
m_thread(new ServerThread(this)),
m_clients(m_con),
m_admin_chat(iface),
+ m_on_shutdown_errmsg(on_shutdown_errmsg),
m_modchannel_mgr(new ModChannelMgr())
{
if (m_path_world.empty())
// Execute script shutdown hooks
infostream << "Executing shutdown hooks" << std::endl;
- m_script->on_shutdown();
+ try {
+ m_script->on_shutdown();
+ } catch (ModError &e) {
+ errorstream << "ModError: " << e.what() << std::endl;
+ if (m_on_shutdown_errmsg) {
+ if (m_on_shutdown_errmsg->empty()) {
+ *m_on_shutdown_errmsg = std::string("ModError: ") + e.what();
+ } else {
+ *m_on_shutdown_errmsg += std::string("\nModError: ") + e.what();
+ }
+ }
+ }
infostream << "Server: Saving environment metadata" << std::endl;
m_env->saveMeta();
infostream << "- game: " << m_gamespec.path << std::endl;
// Create world if it doesn't exist
- if (!loadGameConfAndInitWorld(m_path_world, m_gamespec))
- throw ServerError("Failed to initialize world");
+ try {
+ loadGameConfAndInitWorld(m_path_world,
+ fs::GetFilenameFromPath(m_path_world.c_str()),
+ m_gamespec, false);
+ } catch (const BaseException &e) {
+ throw ServerError(std::string("Failed to initialize world: ") + e.what());
+ }
// Create emerge manager
m_emerge = new EmergeManager(this);
}
m_clients.step(dtime);
- m_lag_gauge->increment((m_lag_gauge->get() > dtime ? -1 : 1) * dtime/100);
+ // increase/decrease lag gauge gradually
+ if (m_lag_gauge->get() > dtime) {
+ m_lag_gauge->decrement(dtime/100);
+ } else {
+ m_lag_gauge->increment(dtime/100);
+ }
#if USE_CURL
// send masterserver announce
{
std::unordered_map<u16, std::vector<ActiveObjectMessage>*> buffered_messages;
// Get active object messages from environment
+ ActiveObjectMessage aom(0);
+ u32 aom_count = 0;
for(;;) {
- ActiveObjectMessage aom = m_env->getActiveObjectMessage();
- if (aom.id == 0)
+ if (!m_env->getActiveObjectMessage(&aom))
break;
std::vector<ActiveObjectMessage>* message_list = nullptr;
- std::unordered_map<u16, std::vector<ActiveObjectMessage>* >::iterator n;
- n = buffered_messages.find(aom.id);
+ auto n = buffered_messages.find(aom.id);
if (n == buffered_messages.end()) {
message_list = new std::vector<ActiveObjectMessage>;
buffered_messages[aom.id] = message_list;
- }
- else {
+ } else {
message_list = n->second;
}
- message_list->push_back(aom);
+ message_list->push_back(std::move(aom));
+ aom_count++;
}
- m_aom_buffer_counter->increment(buffered_messages.size());
+ m_aom_buffer_counter->increment(aom_count);
m_clients.lock();
const RemoteClientMap &clients = m_clients.getClientList();
// Route data to every client
+ std::string reliable_data, unreliable_data;
for (const auto &client_it : clients) {
+ reliable_data.clear();
+ unreliable_data.clear();
RemoteClient *client = client_it.second;
PlayerSAO *player = getPlayerSAO(client->peer_id);
- std::string reliable_data;
- std::string unreliable_data;
// Go through all objects in message buffer
for (const auto &buffered_message : buffered_messages) {
// If object does not exist or is not known by client, skip it
client->m_known_objects.end())
continue;
}
- // Compose the full new data with header
- std::string new_data;
- // Add object id
- char buf[2];
- writeU16((u8*)&buf[0], aom.id);
- new_data.append(buf, 2);
- // Add data
- new_data += serializeString(aom.datastring);
- // Add data to buffer
- if (aom.reliable)
- reliable_data += new_data;
- else
- unreliable_data += new_data;
+
+ // Add full new data to appropriate buffer
+ std::string &buffer = aom.reliable ? reliable_data : unreliable_data;
+ char idbuf[2];
+ writeU16((u8*) idbuf, aom.id);
+ // u16 id
+ // std::string data
+ buffer.append(idbuf, sizeof(idbuf));
+ buffer.append(serializeString16(aom.datastring));
}
}
/*
// Spawns a particle on peer with peer_id
void Server::SendSpawnParticle(session_t peer_id, u16 protocol_version,
- v3f pos, v3f velocity, v3f acceleration,
- float expirationtime, float size, bool collisiondetection,
- bool collision_removal, bool object_collision,
- bool vertical, const std::string &texture,
- const struct TileAnimationParams &animation, u8 glow)
+ const ParticleParameters &p)
{
static thread_local const float radius =
g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE * BS;
if (peer_id == PEER_ID_INEXISTENT) {
std::vector<session_t> clients = m_clients.getClientIDs();
+ const v3f pos = p.pos * BS;
+ const float radius_sq = radius * radius;
for (const session_t client_id : clients) {
RemotePlayer *player = m_env->getPlayer(client_id);
continue;
// Do not send to distant clients
- if (sao->getBasePosition().getDistanceFrom(pos * BS) > radius)
+ if (sao->getBasePosition().getDistanceFromSQ(pos) > radius_sq)
continue;
- SendSpawnParticle(client_id, player->protocol_version,
- pos, velocity, acceleration,
- expirationtime, size, collisiondetection, collision_removal,
- object_collision, vertical, texture, animation, glow);
+ SendSpawnParticle(client_id, player->protocol_version, p);
}
return;
}
+ assert(protocol_version != 0);
NetworkPacket pkt(TOCLIENT_SPAWN_PARTICLE, 0, peer_id);
- pkt << pos << velocity << acceleration << expirationtime
- << size << collisiondetection;
- pkt.putLongString(texture);
- pkt << vertical;
- pkt << collision_removal;
- // This is horrible but required (why are there two ways to serialize pkts?)
- std::ostringstream os(std::ios_base::binary);
- animation.serialize(os, protocol_version);
- pkt.putRawString(os.str());
- pkt << glow;
- pkt << object_collision;
+ {
+ // NetworkPacket and iostreams are incompatible...
+ std::ostringstream oss(std::ios_base::binary);
+ p.serialize(oss, protocol_version);
+ pkt.putRawString(oss.str());
+ }
Send(&pkt);
}
// Adds a ParticleSpawner on peer with peer_id
void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version,
- u16 amount, float spawntime, v3f minpos, v3f maxpos,
- v3f minvel, v3f maxvel, v3f minacc, v3f maxacc, float minexptime, float maxexptime,
- float minsize, float maxsize, bool collisiondetection, bool collision_removal,
- bool object_collision, u16 attached_id, bool vertical, const std::string &texture, u32 id,
- const struct TileAnimationParams &animation, u8 glow)
+ const ParticleSpawnerParameters &p, u16 attached_id, u32 id)
{
+ static thread_local const float radius =
+ g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE * BS;
+
if (peer_id == PEER_ID_INEXISTENT) {
- // This sucks and should be replaced:
std::vector<session_t> clients = m_clients.getClientIDs();
+ const v3f pos = (p.minpos + p.maxpos) / 2.0f * BS;
+ const float radius_sq = radius * radius;
+ /* Don't send short-lived spawners to distant players.
+ * This could be replaced with proper tracking at some point. */
+ const bool distance_check = !attached_id && p.time <= 1.0f;
+
for (const session_t client_id : clients) {
RemotePlayer *player = m_env->getPlayer(client_id);
if (!player)
continue;
+
+ if (distance_check) {
+ PlayerSAO *sao = player->getPlayerSAO();
+ if (!sao)
+ continue;
+ if (sao->getBasePosition().getDistanceFromSQ(pos) > radius_sq)
+ continue;
+ }
+
SendAddParticleSpawner(client_id, player->protocol_version,
- amount, spawntime, minpos, maxpos,
- minvel, maxvel, minacc, maxacc, minexptime, maxexptime,
- minsize, maxsize, collisiondetection, collision_removal,
- object_collision, attached_id, vertical, texture, id,
- animation, glow);
+ p, attached_id, id);
}
return;
}
+ assert(protocol_version != 0);
- NetworkPacket pkt(TOCLIENT_ADD_PARTICLESPAWNER, 0, peer_id);
+ NetworkPacket pkt(TOCLIENT_ADD_PARTICLESPAWNER, 100, peer_id);
- pkt << amount << spawntime << minpos << maxpos << minvel << maxvel
- << minacc << maxacc << minexptime << maxexptime << minsize
- << maxsize << collisiondetection;
+ pkt << p.amount << p.time << p.minpos << p.maxpos << p.minvel
+ << p.maxvel << p.minacc << p.maxacc << p.minexptime << p.maxexptime
+ << p.minsize << p.maxsize << p.collisiondetection;
- pkt.putLongString(texture);
+ pkt.putLongString(p.texture);
- pkt << id << vertical;
- pkt << collision_removal;
- pkt << attached_id;
- // This is horrible but required
- std::ostringstream os(std::ios_base::binary);
- animation.serialize(os, protocol_version);
- pkt.putRawString(os.str());
- pkt << glow;
- pkt << object_collision;
+ pkt << id << p.vertical << p.collision_removal << attached_id;
+ {
+ std::ostringstream os(std::ios_base::binary);
+ p.animation.serialize(os, protocol_version);
+ pkt.putRawString(os.str());
+ }
+ pkt << p.glow << p.object_collision;
+ pkt << p.node.param0 << p.node.param2 << p.node_tile;
Send(&pkt);
}
{
NetworkPacket pkt(TOCLIENT_DELETE_PARTICLESPAWNER, 4, peer_id);
- // Ugly error in this packet
pkt << id;
if (peer_id != PEER_ID_INEXISTENT)
writeU8((u8*)buf, type);
data.append(buf, 1);
- data.append(serializeLongString(
+ data.append(serializeString32(
obj->getClientInitializationData(client->net_proto_version)));
// Add to known objects
block->serializeNetworkSpecific(os);
std::string s = os.str();
- NetworkPacket pkt(TOCLIENT_BLOCKDATA, 2 + 2 + 2 + 2 + s.size(), peer_id);
+ NetworkPacket pkt(TOCLIENT_BLOCKDATA, 2 + 2 + 2 + s.size(), peer_id);
pkt << block->getPos();
pkt.putRawString(s.c_str(), s.size());
return true;
}
+bool Server::addMediaFile(const std::string &filename,
+ const std::string &filepath, std::string *filedata_to,
+ std::string *digest_to)
+{
+ // If name contains illegal characters, ignore the file
+ if (!string_allowed(filename, TEXTURENAME_ALLOWED_CHARS)) {
+ infostream << "Server: ignoring illegal file name: \""
+ << filename << "\"" << std::endl;
+ return false;
+ }
+ // If name is not in a supported format, ignore it
+ const char *supported_ext[] = {
+ ".png", ".jpg", ".bmp", ".tga",
+ ".pcx", ".ppm", ".psd", ".wal", ".rgb",
+ ".ogg",
+ ".x", ".b3d", ".md2", ".obj",
+ // Custom translation file format
+ ".tr",
+ NULL
+ };
+ if (removeStringEnd(filename, supported_ext).empty()) {
+ infostream << "Server: ignoring unsupported file extension: \""
+ << filename << "\"" << std::endl;
+ return false;
+ }
+ // Ok, attempt to load the file and add to cache
+
+ // Read data
+ std::string filedata;
+ if (!fs::ReadFile(filepath, filedata)) {
+ errorstream << "Server::addMediaFile(): Failed to open \""
+ << filename << "\" for reading" << std::endl;
+ return false;
+ }
+
+ if (filedata.empty()) {
+ errorstream << "Server::addMediaFile(): Empty file \""
+ << filepath << "\"" << std::endl;
+ return false;
+ }
+
+ SHA1 sha1;
+ sha1.addBytes(filedata.c_str(), filedata.length());
+
+ unsigned char *digest = sha1.getDigest();
+ std::string sha1_base64 = base64_encode(digest, 20);
+ std::string sha1_hex = hex_encode((char*) digest, 20);
+ if (digest_to)
+ *digest_to = std::string((char*) digest, 20);
+ free(digest);
+
+ // Put in list
+ m_media[filename] = MediaInfo(filepath, sha1_base64);
+ verbosestream << "Server: " << sha1_hex << " is " << filename
+ << std::endl;
+
+ if (filedata_to)
+ *filedata_to = std::move(filedata);
+ return true;
+}
+
void Server::fillMediaCache()
{
- infostream<<"Server: Calculating media file checksums"<<std::endl;
+ infostream << "Server: Calculating media file checksums" << std::endl;
// Collect all media file paths
std::vector<std::string> paths;
- m_modmgr->getModsMediaPaths(paths);
- fs::GetRecursiveDirs(paths, m_gamespec.path + DIR_DELIM + "textures");
+ // The paths are ordered in descending priority
fs::GetRecursiveDirs(paths, porting::path_user + DIR_DELIM + "textures" + DIR_DELIM + "server");
+ fs::GetRecursiveDirs(paths, m_gamespec.path + DIR_DELIM + "textures");
+ m_modmgr->getModsMediaPaths(paths);
// Collect media file information from paths into cache
for (const std::string &mediapath : paths) {
std::vector<fs::DirListNode> dirlist = fs::GetDirListing(mediapath);
for (const fs::DirListNode &dln : dirlist) {
- if (dln.dir) // Ignode dirs
+ if (dln.dir) // Ignore dirs (already in paths)
continue;
- std::string filename = dln.name;
- // If name contains illegal characters, ignore the file
- if (!string_allowed(filename, TEXTURENAME_ALLOWED_CHARS)) {
- infostream<<"Server: ignoring illegal file name: \""
- << filename << "\"" << std::endl;
- continue;
- }
- // If name is not in a supported format, ignore it
- const char *supported_ext[] = {
- ".png", ".jpg", ".bmp", ".tga",
- ".pcx", ".ppm", ".psd", ".wal", ".rgb",
- ".ogg",
- ".x", ".b3d", ".md2", ".obj",
- // Custom translation file format
- ".tr",
- NULL
- };
- if (removeStringEnd(filename, supported_ext).empty()){
- infostream << "Server: ignoring unsupported file extension: \""
- << filename << "\"" << std::endl;
- continue;
- }
- // Ok, attempt to load the file and add to cache
- std::string filepath;
- filepath.append(mediapath).append(DIR_DELIM).append(filename);
-
- // Read data
- std::ifstream fis(filepath.c_str(), std::ios_base::binary);
- if (!fis.good()) {
- errorstream << "Server::fillMediaCache(): Could not open \""
- << filename << "\" for reading" << std::endl;
- continue;
- }
- std::ostringstream tmp_os(std::ios_base::binary);
- bool bad = false;
- for(;;) {
- char buf[1024];
- fis.read(buf, 1024);
- std::streamsize len = fis.gcount();
- tmp_os.write(buf, len);
- if (fis.eof())
- break;
- if (!fis.good()) {
- bad = true;
- break;
- }
- }
- if(bad) {
- errorstream<<"Server::fillMediaCache(): Failed to read \""
- << filename << "\"" << std::endl;
- continue;
- }
- if(tmp_os.str().length() == 0) {
- errorstream << "Server::fillMediaCache(): Empty file \""
- << filepath << "\"" << std::endl;
- continue;
- }
-
- SHA1 sha1;
- sha1.addBytes(tmp_os.str().c_str(), tmp_os.str().length());
- unsigned char *digest = sha1.getDigest();
- std::string sha1_base64 = base64_encode(digest, 20);
- std::string sha1_hex = hex_encode((char*)digest, 20);
- free(digest);
+ const std::string &filename = dln.name;
+ if (m_media.find(filename) != m_media.end()) // Do not override
+ continue;
- // Put in list
- m_media[filename] = MediaInfo(filepath, sha1_base64);
- verbosestream << "Server: " << sha1_hex << " is " << filename
- << std::endl;
+ std::string filepath = mediapath;
+ filepath.append(DIR_DELIM).append(filename);
+ addMediaFile(filename, filepath);
}
}
+
+ infostream << "Server: " << m_media.size() << " media files collected" << std::endl;
}
void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_code)
}
}
+void Server::SendMinimapModes(session_t peer_id,
+ std::vector<MinimapMode> &modes, size_t wanted_mode)
+{
+ RemotePlayer *player = m_env->getPlayer(peer_id);
+ assert(player);
+ if (player->getPeerId() == PEER_ID_INEXISTENT)
+ return;
+
+ NetworkPacket pkt(TOCLIENT_MINIMAP_MODES, 0, peer_id);
+ pkt << (u16)modes.size() << (u16)wanted_mode;
+
+ for (auto &mode : modes)
+ pkt << (u16)mode.type << mode.label << mode.size << mode.texture << mode.scale;
+
+ Send(&pkt);
+}
+
void Server::sendDetachedInventory(Inventory *inventory, const std::string &name, session_t peer_id)
{
NetworkPacket pkt(TOCLIENT_DETACHED_INVENTORY, 0, peer_id);
SendChatMessage(PEER_ID_INEXISTENT, ChatMessage(msg));
}
-void Server::spawnParticle(const std::string &playername, v3f pos,
- v3f velocity, v3f acceleration,
- float expirationtime, float size, bool
- collisiondetection, bool collision_removal, bool object_collision,
- bool vertical, const std::string &texture,
- const struct TileAnimationParams &animation, u8 glow)
+void Server::spawnParticle(const std::string &playername,
+ const ParticleParameters &p)
{
// m_env will be NULL if the server is initializing
if (!m_env)
proto_ver = player->protocol_version;
}
- SendSpawnParticle(peer_id, proto_ver, pos, velocity, acceleration,
- expirationtime, size, collisiondetection, collision_removal,
- object_collision, vertical, texture, animation, glow);
+ SendSpawnParticle(peer_id, proto_ver, p);
}
-u32 Server::addParticleSpawner(u16 amount, float spawntime,
- v3f minpos, v3f maxpos, v3f minvel, v3f maxvel, v3f minacc, v3f maxacc,
- float minexptime, float maxexptime, float minsize, float maxsize,
- bool collisiondetection, bool collision_removal, bool object_collision,
- ServerActiveObject *attached, bool vertical, const std::string &texture,
- const std::string &playername, const struct TileAnimationParams &animation,
- u8 glow)
+u32 Server::addParticleSpawner(const ParticleSpawnerParameters &p,
+ ServerActiveObject *attached, const std::string &playername)
{
// m_env will be NULL if the server is initializing
if (!m_env)
u32 id;
if (attached_id == 0)
- id = m_env->addParticleSpawner(spawntime);
+ id = m_env->addParticleSpawner(p.time);
else
- id = m_env->addParticleSpawner(spawntime, attached_id);
-
- SendAddParticleSpawner(peer_id, proto_ver, amount, spawntime,
- minpos, maxpos, minvel, maxvel, minacc, maxacc,
- minexptime, maxexptime, minsize, maxsize, collisiondetection,
- collision_removal, object_collision, attached_id, vertical,
- texture, id, animation, glow);
+ id = m_env->addParticleSpawner(p.time, attached_id);
+ SendAddParticleSpawner(peer_id, proto_ver, p, attached_id, id);
return id;
}
SendDeleteParticleSpawner(peer_id, id);
}
+bool Server::dynamicAddMedia(const std::string &filepath)
+{
+ std::string filename = fs::GetFilenameFromPath(filepath.c_str());
+ if (m_media.find(filename) != m_media.end()) {
+ errorstream << "Server::dynamicAddMedia(): file \"" << filename
+ << "\" already exists in media cache" << std::endl;
+ return false;
+ }
+
+ // Load the file and add it to our media cache
+ std::string filedata, raw_hash;
+ bool ok = addMediaFile(filename, filepath, &filedata, &raw_hash);
+ if (!ok)
+ return false;
+
+ // Push file to existing clients
+ NetworkPacket pkt(TOCLIENT_MEDIA_PUSH, 0);
+ pkt << raw_hash << filename << (bool) true;
+ pkt.putLongString(filedata);
+
+ auto client_ids = m_clients.getClientIDs(CS_DefinitionsSent);
+ for (session_t client_id : client_ids) {
+ /*
+ The network layer only guarantees ordered delivery inside a channel.
+ Since the very next packet could be one that uses the media, we have
+ to push the media over ALL channels to ensure it is processed before
+ it is used.
+ In practice this means we have to send it twice:
+ - channel 1 (HUD)
+ - channel 0 (everything else: e.g. play_sound, object messages)
+ */
+ m_clients.send(client_id, 1, &pkt, true);
+ m_clients.send(client_id, 0, &pkt, true);
+ }
+
+ return true;
+}
+
// actions: time-reversed list
// Return value: success/failure
bool Server::rollbackRevertActions(const std::list<RollbackAction> &actions,
}
}
-void Server::loadTranslationLanguage(const std::string &lang_code)
+Translations *Server::getTranslationLanguage(const std::string &lang_code)
{
- if (g_server_translations->count(lang_code))
- return; // Already loaded
+ if (lang_code.empty())
+ return nullptr;
+
+ auto it = server_translations.find(lang_code);
+ if (it != server_translations.end())
+ return &it->second; // Already loaded
+
+ // [] will create an entry
+ auto *translations = &server_translations[lang_code];
std::string suffix = "." + lang_code + ".tr";
for (const auto &i : m_media) {
if (str_ends_with(i.first, suffix)) {
- std::ifstream t(i.second.path);
- std::string data((std::istreambuf_iterator<char>(t)),
- std::istreambuf_iterator<char>());
-
- (*g_server_translations)[lang_code].loadTranslation(data);
+ std::string data;
+ if (fs::ReadFile(i.second.path, data)) {
+ translations->loadTranslation(data);
+ }
}
}
+
+ return translations;
}