aboutsummaryrefslogtreecommitdiff
path: root/src/server.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/server.cpp')
-rw-r--r--src/server.cpp670
1 files changed, 311 insertions, 359 deletions
diff --git a/src/server.cpp b/src/server.cpp
index 239ddd92e..fe2bb3840 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -34,8 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "version.h"
#include "filesys.h"
#include "mapblock.h"
-#include "serverobject.h"
-#include "genericobject.h"
+#include "server/serveractiveobject.h"
#include "settings.h"
#include "profiler.h"
#include "log.h"
@@ -48,7 +47,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "mapgen/mg_biome.h"
#include "content_mapnode.h"
#include "content_nodemeta.h"
-#include "content_sao.h"
#include "content/mods.h"
#include "modchannels.h"
#include "serverlist.h"
@@ -65,6 +63,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "chatmessage.h"
#include "chat_interface.h"
#include "remoteplayer.h"
+#include "server/player_sao.h"
+#include "server/serverinventorymgr.h"
+#include "translation.h"
class ClientNotFoundException : public BaseException
{
@@ -229,18 +230,46 @@ Server::Server(
m_nodedef(createNodeDefManager()),
m_craftdef(createCraftDefManager()),
m_thread(new ServerThread(this)),
- m_uptime(0),
m_clients(m_con),
m_admin_chat(iface),
m_modchannel_mgr(new ModChannelMgr())
{
- m_lag = g_settings->getFloat("dedicated_server_step");
-
if (m_path_world.empty())
throw ServerError("Supplied empty world path");
if (!gamespec.isValid())
throw ServerError("Supplied invalid gamespec");
+
+#if USE_PROMETHEUS
+ m_metrics_backend = std::unique_ptr<MetricsBackend>(createPrometheusMetricsBackend());
+#else
+ m_metrics_backend = std::unique_ptr<MetricsBackend>(new MetricsBackend());
+#endif
+
+ m_uptime_counter = m_metrics_backend->addCounter("minetest_core_server_uptime", "Server uptime (in seconds)");
+ m_player_gauge = m_metrics_backend->addGauge("minetest_core_player_number", "Number of connected players");
+
+ m_timeofday_gauge = m_metrics_backend->addGauge(
+ "minetest_core_timeofday",
+ "Time of day value");
+
+ m_lag_gauge = m_metrics_backend->addGauge(
+ "minetest_core_latency",
+ "Latency value (in seconds)");
+
+ m_aom_buffer_counter = m_metrics_backend->addCounter(
+ "minetest_core_aom_generated_count",
+ "Number of active object messages generated");
+
+ m_packet_recv_counter = m_metrics_backend->addCounter(
+ "minetest_core_server_packet_recv",
+ "Processable packets received");
+
+ m_packet_recv_processed_counter = m_metrics_backend->addCounter(
+ "minetest_core_server_packet_recv_processed",
+ "Valid received packets processed");
+
+ m_lag_gauge->set(g_settings->getFloat("dedicated_server_step"));
}
Server::~Server()
@@ -310,11 +339,6 @@ Server::~Server()
infostream << "Server: Deinitializing scripting" << std::endl;
delete m_script;
- // Delete detached inventories
- for (auto &detached_inventory : m_detached_inventories) {
- delete detached_inventory.second;
- }
-
while (!m_unsent_map_edit_queue.empty()) {
delete m_unsent_map_edit_queue.front();
m_unsent_map_edit_queue.pop();
@@ -353,13 +377,16 @@ void Server::init()
MutexAutoLock envlock(m_env_mutex);
// Create the Map (loads map_meta.txt, overriding configured mapgen params)
- ServerMap *servermap = new ServerMap(m_path_world, this, m_emerge);
+ ServerMap *servermap = new ServerMap(m_path_world, this, m_emerge, m_metrics_backend.get());
// Initialize scripting
infostream << "Server: Initializing Lua" << std::endl;
m_script = new ServerScripting(this);
+ // Must be created before mod loading because we have some inventory creation
+ m_inventory_mgr = std::unique_ptr<ServerInventoryManager>(new ServerInventoryManager());
+
m_script->loadMod(getBuiltinLuaPath() + DIR_DELIM "init.lua", BUILTIN_MOD_NAME);
m_modmgr->loadMods(m_script);
@@ -374,16 +401,19 @@ void Server::init()
std::vector<std::string> paths;
fs::GetRecursiveDirs(paths, g_settings->get("texture_path"));
fs::GetRecursiveDirs(paths, m_gamespec.path + DIR_DELIM + "textures");
- for (const std::string &path : paths)
- m_nodedef->applyTextureOverrides(path + DIR_DELIM + "override.txt");
+ for (const std::string &path : paths) {
+ TextureOverrideSource override_source(path + DIR_DELIM + "override.txt");
+ m_nodedef->applyTextureOverrides(override_source.getNodeTileOverrides());
+ m_itemdef->applyTextureOverrides(override_source.getItemTextureOverrides());
+ }
m_nodedef->setNodeRegistrationStatus(true);
// Perform pending node name resolutions
m_nodedef->runNodeResolveCallbacks();
- // unmap node names for connected nodeboxes
- m_nodedef->mapNodeboxConnections();
+ // unmap node names in cross-references
+ m_nodedef->resolveCrossrefs();
// init the recipe hashes to speed up crafting
m_craftdef->initHashes(this);
@@ -391,6 +421,7 @@ void Server::init()
// Initialize Environment
m_env = new ServerEnvironment(servermap, m_script, this, m_path_world);
+ m_inventory_mgr->setEnv(m_env);
m_clients.setEnv(m_env);
if (!servermap->settings_mgr.makeMapgenParams())
@@ -412,6 +443,8 @@ void Server::init()
m_env->loadMeta();
+ // Those settings can be overwritten in world.mt, they are
+ // intended to be cached after environment loading.
m_liquid_transform_every = g_settings->getFloat("liquid_update");
m_max_chatmessage_length = g_settings->getU16("chat_message_max_size");
m_csm_restriction_flags = g_settings->getU64("csm_restriction_flags");
@@ -420,6 +453,8 @@ void Server::init()
void Server::start()
{
+ init();
+
infostream << "Starting server on " << m_bind_addr.serializeString()
<< "..." << std::endl;
@@ -508,9 +543,7 @@ void Server::AsyncRunStep(bool initial_step)
/*
Update uptime
*/
- {
- m_uptime.set(m_uptime.get() + dtime);
- }
+ m_uptime_counter->increment(dtime);
handlePeerChanges();
@@ -524,11 +557,13 @@ void Server::AsyncRunStep(bool initial_step)
*/
m_time_of_day_send_timer -= dtime;
- if(m_time_of_day_send_timer < 0.0) {
+ if (m_time_of_day_send_timer < 0.0) {
m_time_of_day_send_timer = g_settings->getFloat("time_send_interval");
u16 time = m_env->getTimeOfDay();
float time_speed = g_settings->getFloat("time_speed");
SendTimeOfDay(PEER_ID_INEXISTENT, time, time_speed);
+
+ m_timeofday_gauge->set(time);
}
{
@@ -600,7 +635,7 @@ void Server::AsyncRunStep(bool initial_step)
}
m_clients.step(dtime);
- m_lag += (m_lag > dtime ? -1 : 1) * dtime/100;
+ m_lag_gauge->increment((m_lag_gauge->get() > dtime ? -1 : 1) * dtime/100);
#if USE_CURL
// send masterserver announce
{
@@ -611,9 +646,9 @@ void Server::AsyncRunStep(bool initial_step)
ServerList::AA_START,
m_bind_addr.getPort(),
m_clients.getPlayerNames(),
- m_uptime.get(),
+ m_uptime_counter->get(),
m_env->getGameTime(),
- m_lag,
+ m_lag_gauge->get(),
m_gamespec.id,
Mapgen::getMapgenName(m_emerge->mgparams->mgtype),
m_modmgr->getMods(),
@@ -635,6 +670,7 @@ void Server::AsyncRunStep(bool initial_step)
const RemoteClientMap &clients = m_clients.getClientList();
ScopeProfiler sp(g_profiler, "Server: update objects within range");
+ m_player_gauge->set(clients.size());
for (const auto &client_it : clients) {
RemoteClient *client = client_it.second;
@@ -656,14 +692,17 @@ void Server::AsyncRunStep(bool initial_step)
// Save mod storages if modified
m_mod_storage_save_timer -= dtime;
if (m_mod_storage_save_timer <= 0.0f) {
- infostream << "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;
}
}
@@ -679,32 +718,35 @@ void Server::AsyncRunStep(bool initial_step)
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(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
@@ -718,7 +760,7 @@ void Server::AsyncRunStep(bool initial_step)
// Go through every message
for (const ActiveObjectMessage &aom : *list) {
// Send position updates to players who do not see the attachment
- if (aom.datastring[0] == GENERIC_CMD_UPDATE_POSITION) {
+ if (aom.datastring[0] == AO_CMD_UPDATE_POSITION) {
if (sao->getId() == player->getId())
continue;
@@ -729,19 +771,15 @@ void Server::AsyncRunStep(bool initial_step)
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(serializeString(aom.datastring));
}
}
/*
@@ -809,7 +847,6 @@ void Server::AsyncRunStep(bool initial_step)
disable_single_change_sending ? 5 : 30);
break;
case MEET_BLOCK_NODE_METADATA_CHANGED: {
- verbosestream << "Server: MEET_BLOCK_NODE_METADATA_CHANGED" << std::endl;
prof.add("MEET_BLOCK_NODE_METADATA_CHANGED", 1);
if (!event->is_private_change) {
// Don't send the change yet. Collect them to eliminate dupes.
@@ -825,7 +862,6 @@ void Server::AsyncRunStep(bool initial_step)
break;
}
case MEET_OTHER:
- infostream << "Server: MEET_OTHER" << std::endl;
prof.add("MEET_OTHER", 1);
for (const v3s16 &modified_block : event->modified_blocks) {
m_clients.markBlockposAsNotSent(modified_block);
@@ -939,7 +975,9 @@ void Server::Receive()
}
peer_id = pkt.getPeerId();
+ m_packet_recv_counter->increment();
ProcessData(&pkt);
+ m_packet_recv_processed_counter->increment();
} catch (const con::InvalidIncomingDataException &e) {
infostream << "Server::Receive(): InvalidIncomingDataException: what()="
<< e.what() << std::endl;
@@ -1143,82 +1181,6 @@ void Server::onMapEditEvent(const MapEditEvent &event)
m_unsent_map_edit_queue.push(new MapEditEvent(event));
}
-Inventory* Server::getInventory(const InventoryLocation &loc)
-{
- switch (loc.type) {
- case InventoryLocation::UNDEFINED:
- case InventoryLocation::CURRENT_PLAYER:
- break;
- case InventoryLocation::PLAYER:
- {
- RemotePlayer *player = m_env->getPlayer(loc.name.c_str());
- if(!player)
- return NULL;
- PlayerSAO *playersao = player->getPlayerSAO();
- if(!playersao)
- return NULL;
- return playersao->getInventory();
- }
- break;
- case InventoryLocation::NODEMETA:
- {
- NodeMetadata *meta = m_env->getMap().getNodeMetadata(loc.p);
- if(!meta)
- return NULL;
- return meta->getInventory();
- }
- break;
- case InventoryLocation::DETACHED:
- {
- if(m_detached_inventories.count(loc.name) == 0)
- return NULL;
- return m_detached_inventories[loc.name];
- }
- break;
- default:
- sanity_check(false); // abort
- break;
- }
- return NULL;
-}
-
-void Server::setInventoryModified(const InventoryLocation &loc)
-{
- switch(loc.type){
- case InventoryLocation::UNDEFINED:
- break;
- case InventoryLocation::PLAYER:
- {
-
- RemotePlayer *player = m_env->getPlayer(loc.name.c_str());
-
- if (!player)
- return;
-
- player->setModified(true);
- player->inventory.setModified(true);
- // Updates are sent in ServerEnvironment::step()
- }
- break;
- case InventoryLocation::NODEMETA:
- {
- MapEditEvent event;
- event.type = MEET_BLOCK_NODE_METADATA_CHANGED;
- event.p = loc.p;
- m_env->getMap().dispatchEvent(event);
- }
- break;
- case InventoryLocation::DETACHED:
- {
- // Updates are sent in ServerEnvironment::step()
- }
- break;
- default:
- sanity_check(false); // abort
- break;
- }
-}
-
void Server::SetBlocksNotSent(std::map<v3s16, MapBlock *>& block)
{
std::vector<session_t> clients = m_clients.getClientIDs();
@@ -1263,7 +1225,8 @@ bool Server::getClientInfo(
u8* major,
u8* minor,
u8* patch,
- std::string* vers_string
+ std::string* vers_string,
+ std::string* lang_code
)
{
*state = m_clients.getClientState(peer_id);
@@ -1283,6 +1246,7 @@ bool Server::getClientInfo(
*minor = client->getMinor();
*patch = client->getPatch();
*vers_string = client->getFull();
+ *lang_code = client->getLangCode();
m_clients.unlock();
@@ -1537,17 +1501,15 @@ void Server::SendShowFormspecMessage(session_t peer_id, const std::string &forms
// 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);
@@ -1559,76 +1521,78 @@ void Server::SendSpawnParticle(session_t peer_id, u16 protocol_version,
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);
}
@@ -1637,7 +1601,6 @@ void Server::SendDeleteParticleSpawner(session_t peer_id, u32 id)
{
NetworkPacket pkt(TOCLIENT_DELETE_PARTICLESPAWNER, 4, peer_id);
- // Ugly error in this packet
pkt << id;
if (peer_id != PEER_ID_INEXISTENT)
@@ -1654,7 +1617,7 @@ void Server::SendHUDAdd(session_t peer_id, u32 id, HudElement *form)
pkt << id << (u8) form->type << form->pos << form->name << form->scale
<< form->text << form->number << form->item << form->dir
<< form->align << form->offset << form->world_pos << form->size
- << form->z_index;
+ << form->z_index << form->text2;
Send(&pkt);
}
@@ -1680,6 +1643,7 @@ void Server::SendHUDChange(session_t peer_id, u32 id, HudElementStat stat, void
break;
case HUD_STAT_NAME:
case HUD_STAT_TEXT:
+ case HUD_STAT_TEXT2:
pkt << *(std::string *) value;
break;
case HUD_STAT_WORLD_POS:
@@ -1731,8 +1695,8 @@ void Server::SendSetSky(session_t peer_id, const SkyboxParams &params)
pkt << params.clouds;
} else { // Handle current clients and future clients
pkt << params.bgcolor << params.type
- << params.clouds << params.sun_tint
- << params.moon_tint << params.tint_type;
+ << params.clouds << params.fog_sun_tint
+ << params.fog_moon_tint << params.fog_tint_type;
if (params.type == "skybox") {
pkt << (u16) params.textures.size();
@@ -1818,9 +1782,7 @@ void Server::SendPlayerHP(session_t peer_id)
m_script->player_event(playersao,"health_changed");
// Send to other clients
- std::string str = gob_cmd_punched(playersao->getHP());
- ActiveObjectMessage aom(playersao->getId(), true, str);
- playersao->m_messages_out.push(aom);
+ playersao->sendPunchCommand();
}
void Server::SendPlayerBreath(PlayerSAO *sao)
@@ -1855,10 +1817,10 @@ void Server::SendMovePlayer(session_t peer_id)
void Server::SendPlayerFov(session_t peer_id)
{
- NetworkPacket pkt(TOCLIENT_FOV, 4 + 1, peer_id);
+ NetworkPacket pkt(TOCLIENT_FOV, 4 + 1 + 4, peer_id);
PlayerFovSpec fov_spec = m_env->getPlayer(peer_id)->getFov();
- pkt << fov_spec.fov << fov_spec.is_multiplier;
+ pkt << fov_spec.fov << fov_spec.is_multiplier << fov_spec.transition_time;
Send(&pkt);
}
@@ -2443,9 +2405,87 @@ bool Server::SendBlock(session_t peer_id, const v3s16 &blockpos)
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::ifstream fis(filepath.c_str(), std::ios_base::binary);
+ if (!fis.good()) {
+ errorstream << "Server::addMediaFile(): Could not open \""
+ << filename << "\" for reading" << std::endl;
+ return false;
+ }
+ std::string filedata;
+ bool bad = false;
+ for (;;) {
+ char buf[1024];
+ fis.read(buf, sizeof(buf));
+ std::streamsize len = fis.gcount();
+ filedata.append(buf, len);
+ if (fis.eof())
+ break;
+ if (!fis.good()) {
+ bad = true;
+ break;
+ }
+ }
+ if (bad) {
+ errorstream << "Server::addMediaFile(): Failed to read \""
+ << filename << "\"" << std::endl;
+ return false;
+ } else 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;
@@ -2457,87 +2497,19 @@ void Server::fillMediaCache()
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
- 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;
+ if (dln.dir) // Ignore dirs
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);
-
- // 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(dln.name);
+ addMediaFile(dln.name, filepath);
}
}
+
+ infostream << "Server: " << m_media.size() << " media files collected" << std::endl;
}
void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_code)
{
- verbosestream << "Server: Announcing files to id(" << peer_id << ")"
- << std::endl;
-
// Make packet
NetworkPacket pkt(TOCLIENT_ANNOUNCE_MEDIA, 0, peer_id);
@@ -2560,6 +2532,9 @@ void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_co
pkt << g_settings->get("remote_media");
Send(&pkt);
+
+ verbosestream << "Server: Announcing files to id(" << peer_id
+ << "): count=" << media_sent << " size=" << pkt.getSize() << std::endl;
}
struct SendableMedia
@@ -2675,40 +2650,20 @@ void Server::sendRequestedMedia(session_t peer_id,
}
}
-void Server::sendDetachedInventory(const std::string &name, session_t peer_id)
+void Server::sendDetachedInventory(Inventory *inventory, const std::string &name, session_t peer_id)
{
- const auto &inv_it = m_detached_inventories.find(name);
- const auto &player_it = m_detached_inventories_player.find(name);
-
- if (player_it == m_detached_inventories_player.end() ||
- player_it->second.empty()) {
- // OK. Send to everyone
- } else {
- if (!m_env)
- return; // Mods are not done loading
-
- RemotePlayer *p = m_env->getPlayer(player_it->second.c_str());
- if (!p)
- return; // Player is offline
-
- if (peer_id != PEER_ID_INEXISTENT && peer_id != p->getPeerId())
- return; // Caller requested send to a different player, so don't send.
-
- peer_id = p->getPeerId();
- }
-
NetworkPacket pkt(TOCLIENT_DETACHED_INVENTORY, 0, peer_id);
pkt << name;
- if (inv_it == m_detached_inventories.end()) {
+ if (!inventory) {
pkt << false; // Remove inventory
} else {
pkt << true; // Update inventory
// Serialization & NetworkPacket isn't a love story
std::ostringstream os(std::ios_base::binary);
- inv_it->second->serialize(os);
- inv_it->second->setModified(false);
+ inventory->serialize(os);
+ inventory->setModified(false);
const std::string &os_str = os.str();
pkt << static_cast<u16>(os_str.size()); // HACK: to keep compatibility with 5.0.0 clients
@@ -2723,16 +2678,17 @@ void Server::sendDetachedInventory(const std::string &name, session_t peer_id)
void Server::sendDetachedInventories(session_t peer_id, bool incremental)
{
- for (const auto &detached_inventory : m_detached_inventories) {
- const std::string &name = detached_inventory.first;
- if (incremental) {
- Inventory *inv = detached_inventory.second;
- if (!inv || !inv->checkModified())
- continue;
- }
-
- sendDetachedInventory(name, peer_id);
+ // Lookup player name, to filter detached inventories just after
+ std::string peer_name;
+ if (peer_id != PEER_ID_INEXISTENT) {
+ peer_name = getClient(peer_id, CS_Created)->getName();
}
+
+ auto send_cb = [this, peer_id](const std::string &name, Inventory *inv) {
+ sendDetachedInventory(inv, name, peer_id);
+ };
+
+ m_inventory_mgr->sendDetachedInventories(peer_name, incremental, send_cb);
}
/*
@@ -2938,10 +2894,8 @@ void Server::UpdateCrafting(RemotePlayer *player)
if (!clist || clist->getSize() == 0)
return;
- if (!clist->checkModified()) {
- verbosestream << "Skip Server::UpdateCrafting(): list unmodified" << std::endl;
+ if (!clist->checkModified())
return;
- }
// Get a preview for crafting
ItemStack preview;
@@ -3032,8 +2986,16 @@ std::wstring Server::handleChat(const std::string &name, const std::wstring &wna
line += L"-!- You don't have permission to shout.";
broadcast_line = false;
} else {
+ /*
+ Workaround for fixing chat on Android. Lua doesn't handle
+ the Cyrillic alphabet and some characters on older Android devices
+ */
+#ifdef __ANDROID__
+ line += L"<" + wname + L"> " + wmessage;
+#else
line += narrow_to_wide(m_script->formatChatMessage(name,
wide_to_narrow(wmessage)));
+#endif
}
/*
@@ -3117,7 +3079,7 @@ std::wstring Server::getStatusString()
// Version
os << L"version=" << narrow_to_wide(g_version_string);
// Uptime
- os << L", uptime=" << m_uptime.get();
+ os << L", uptime=" << m_uptime_counter->get();
// Max lag estimate
os << L", max_lag=" << (m_env ? m_env->getMaxLagEstimate() : 0);
@@ -3399,15 +3361,12 @@ void Server::setClouds(RemotePlayer *player, const CloudParams &params)
SendCloudParams(player->getPeerId(), params);
}
-bool Server::overrideDayNightRatio(RemotePlayer *player, bool do_override,
+void Server::overrideDayNightRatio(RemotePlayer *player, bool do_override,
float ratio)
{
- if (!player)
- return false;
-
+ sanity_check(player);
player->overrideDayNightRatio(do_override, ratio);
SendOverrideDayNightRatio(player->getPeerId(), do_override, ratio);
- return true;
}
void Server::notifyPlayers(const std::wstring &msg)
@@ -3415,12 +3374,8 @@ void Server::notifyPlayers(const std::wstring &msg)
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)
@@ -3436,18 +3391,11 @@ void Server::spawnParticle(const std::string &playername, v3f pos,
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)
@@ -3467,16 +3415,11 @@ u32 Server::addParticleSpawner(u16 amount, float spawntime,
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;
}
@@ -3498,49 +3441,41 @@ void Server::deleteParticleSpawner(const std::string &playername, u32 id)
SendDeleteParticleSpawner(peer_id, id);
}
-Inventory* Server::createDetachedInventory(const std::string &name, const std::string &player)
+bool Server::dynamicAddMedia(const std::string &filepath)
{
- if(m_detached_inventories.count(name) > 0){
- infostream<<"Server clearing detached inventory \""<<name<<"\""<<std::endl;
- delete m_detached_inventories[name];
- } else {
- infostream<<"Server creating detached inventory \""<<name<<"\""<<std::endl;
+ 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;
}
- Inventory *inv = new Inventory(m_itemdef);
- sanity_check(inv);
- m_detached_inventories[name] = inv;
- if (!player.empty())
- m_detached_inventories_player[name] = player;
- //TODO find a better way to do this
- sendDetachedInventory(name,PEER_ID_INEXISTENT);
- return inv;
-}
-
-bool Server::removeDetachedInventory(const std::string &name)
-{
- const auto &inv_it = m_detached_inventories.find(name);
- if (inv_it == m_detached_inventories.end())
+ // 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;
- delete inv_it->second;
- m_detached_inventories.erase(inv_it);
-
- if (!m_env) // Mods are not done loading
- return true;
-
- const auto &player_it = m_detached_inventories_player.find(name);
- if (player_it != m_detached_inventories_player.end()) {
- RemotePlayer *player = m_env->getPlayer(player_it->second.c_str());
+ // Push file to existing clients
+ NetworkPacket pkt(TOCLIENT_MEDIA_PUSH, 0);
+ pkt << raw_hash << filename << (bool) true;
+ pkt.putLongString(filedata);
- if (player && player->getPeerId() != PEER_ID_INEXISTENT)
- sendDetachedInventory(name, player->getPeerId());
-
- m_detached_inventories_player.erase(player_it);
- } else {
- // Notify all players about the change
- sendDetachedInventory(name, PEER_ID_INEXISTENT);
+ 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;
}
@@ -3564,7 +3499,7 @@ bool Server::rollbackRevertActions(const std::list<RollbackAction> &actions,
for (const RollbackAction &action : actions) {
num_tried++;
- bool success = action.applyRevert(map, this, this);
+ bool success = action.applyRevert(map, m_inventory_mgr.get(), this);
if(!success){
num_failed++;
std::ostringstream os;
@@ -3930,3 +3865,20 @@ void Server::broadcastModChannelMessage(const std::string &channel,
m_script->on_modchannel_message(channel, sender, message);
}
}
+
+void Server::loadTranslationLanguage(const std::string &lang_code)
+{
+ if (g_server_translations->count(lang_code))
+ return; // Already loaded
+
+ 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);
+ }
+ }
+}