]> git.lizzy.rs Git - minetest.git/blobdiff - src/server.cpp
Disable Prometheus in singleplayer mode
[minetest.git] / src / server.cpp
index d85a2e95ab5ce98d00f63dc4505547277bdc21e6..bced71afa3b67998af6bf81ce3092046bc8633b4 100644 (file)
@@ -67,6 +67,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "server/serverinventorymgr.h"
 #include "translation.h"
 #include "database/database-sqlite3.h"
+#if USE_POSTGRESQL
+#include "database/database-postgresql.h"
+#endif
 #include "database/database-files.h"
 #include "database/database-dummy.h"
 #include "gameparams.h"
@@ -136,24 +139,31 @@ void *ServerThread::run()
        return nullptr;
 }
 
-v3f ServerSoundParams::getPos(ServerEnvironment *env, bool *pos_exists) const
+v3f ServerPlayingSound::getPos(ServerEnvironment *env, bool *pos_exists) const
 {
-       if(pos_exists) *pos_exists = false;
-       switch(type){
-       case SSP_LOCAL:
+       if (pos_exists)
+               *pos_exists = false;
+
+       switch (type ){
+       case SoundLocation::Local:
                return v3f(0,0,0);
-       case SSP_POSITIONAL:
-               if(pos_exists) *pos_exists = true;
+       case SoundLocation::Position:
+               if (pos_exists)
+                       *pos_exists = true;
                return pos;
-       case SSP_OBJECT: {
-               if(object == 0)
-                       return v3f(0,0,0);
-               ServerActiveObject *sao = env->getActiveObject(object);
-               if(!sao)
-                       return v3f(0,0,0);
-               if(pos_exists) *pos_exists = true;
-               return sao->getBasePosition(); }
+       case SoundLocation::Object:
+               {
+                       if (object == 0)
+                               return v3f(0,0,0);
+                       ServerActiveObject *sao = env->getActiveObject(object);
+                       if (!sao)
+                               return v3f(0,0,0);
+                       if (pos_exists)
+                               *pos_exists = true;
+                       return sao->getBasePosition();
+               }
        }
+
        return v3f(0,0,0);
 }
 
@@ -243,7 +253,6 @@ Server::Server(
        m_clients(m_con),
        m_admin_chat(iface),
        m_on_shutdown_errmsg(on_shutdown_errmsg),
-       m_async_globals_data(""),
        m_modchannel_mgr(new ModChannelMgr())
 {
        if (m_path_world.empty())
@@ -253,10 +262,13 @@ Server::Server(
                throw ServerError("Supplied invalid gamespec");
 
 #if USE_PROMETHEUS
-       m_metrics_backend = std::unique_ptr<MetricsBackend>(createPrometheusMetricsBackend());
+       if (!simple_singleplayer_mode)
+               m_metrics_backend = std::unique_ptr<MetricsBackend>(createPrometheusMetricsBackend());
+       else
 #else
-       m_metrics_backend = std::make_unique<MetricsBackend>();
+       if (true)
 #endif
+               m_metrics_backend = std::make_unique<MetricsBackend>();
 
        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");
@@ -269,9 +281,15 @@ Server::Server(
                        "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");
+
+       const std::string aom_types[] = {"reliable", "unreliable"};
+       for (u32 i = 0; i < ARRLEN(aom_types); i++) {
+               std::string help_str("Number of active object messages generated (");
+               help_str.append(aom_types[i]).append(")");
+               m_aom_buffer_counter[i] = m_metrics_backend->addCounter(
+                               "minetest_core_aom_generated_count", help_str,
+                               {{"type", aom_types[i]}});
+       }
 
        m_packet_recv_counter = m_metrics_backend->addCounter(
                        "minetest_core_server_packet_recv",
@@ -281,6 +299,10 @@ Server::Server(
                        "minetest_core_server_packet_recv_processed",
                        "Valid received packets processed");
 
+       m_map_edit_event_counter = m_metrics_backend->addCounter(
+                       "minetest_core_map_edit_events",
+                       "Number of map edit events");
+
        m_lag_gauge->set(g_settings->getFloat("dedicated_server_step"));
 }
 
@@ -397,7 +419,7 @@ void Server::init()
        }
 
        // Create emerge manager
-       m_emerge = new EmergeManager(this);
+       m_emerge = new EmergeManager(this, m_metrics_backend.get());
 
        // Create ban manager
        std::string ban_path = m_path_world + DIR_DELIM "ipban.txt";
@@ -408,10 +430,11 @@ void Server::init()
        m_mod_storage_database->beginSave();
 
        m_modmgr = std::make_unique<ServerModManager>(m_path_world);
-       std::vector<ModSpec> unsatisfied_mods = m_modmgr->getUnsatisfiedMods();
+
        // complain about mods with unsatisfied dependencies
        if (!m_modmgr->isConsistent()) {
-               m_modmgr->printUnsatisfiedModsError();
+               std::string error = m_modmgr->getUnsatisfiedModsError();
+               throw ServerError(error);
        }
 
        //lock environment
@@ -430,7 +453,9 @@ void Server::init()
        m_inventory_mgr = std::make_unique<ServerInventoryManager>();
 
        m_script->loadMod(getBuiltinLuaPath() + DIR_DELIM "init.lua", BUILTIN_MOD_NAME);
+       m_script->checkSetByBuiltin();
 
+       m_gamespec.checkAndLog();
        m_modmgr->loadMods(m_script);
 
        // Read Textures and calculate sha1 sums
@@ -462,7 +487,9 @@ void Server::init()
 
        // Initialize Environment
        m_startup_server_map = nullptr; // Ownership moved to ServerEnvironment
-       m_env = new ServerEnvironment(servermap, m_script, this, m_path_world);
+       m_env = new ServerEnvironment(servermap, m_script, this,
+               m_path_world, m_metrics_backend.get());
+       m_env->init();
 
        m_inventory_mgr->setEnv(m_env);
        m_clients.setEnv(m_env);
@@ -623,6 +650,7 @@ void Server::AsyncRunStep(bool initial_step)
                        max_lag = dtime;
                }
                m_env->reportMaxLagEstimate(max_lag);
+
                // Step environment
                m_env->step(dtime);
        }
@@ -634,8 +662,8 @@ void Server::AsyncRunStep(bool initial_step)
                // Run Map's timers and unload unused data
                ScopeProfiler sp(g_profiler, "Server: map timer and unload");
                m_env->getMap().timerUpdate(map_timer_and_unload_dtime,
-                       g_settings->getFloat("server_unload_unused_data_timeout"),
-                       U32_MAX);
+                       std::max(g_settings->getFloat("server_unload_unused_data_timeout"), 0.0f),
+                       -1);
        }
 
        /*
@@ -671,11 +699,11 @@ void Server::AsyncRunStep(bool initial_step)
                std::map<v3s16, MapBlock*> modified_blocks;
                m_env->getServerMap().transformLiquids(modified_blocks, m_env);
 
-               /*
-                       Set the modified blocks unsent for all the clients
-               */
                if (!modified_blocks.empty()) {
-                       SetBlocksNotSent(modified_blocks);
+                       MapEditEvent event;
+                       event.type = MEET_OTHER;
+                       event.setModifiedBlocks(modified_blocks);
+                       m_env->getMap().dispatchEvent(event);
                }
        }
        m_clients.step(dtime);
@@ -773,10 +801,14 @@ void Server::AsyncRunStep(bool initial_step)
 
                // Get active object messages from environment
                ActiveObjectMessage aom(0);
-               u32 aom_count = 0;
+               u32 count_reliable = 0, count_unreliable = 0;
                for(;;) {
                        if (!m_env->getActiveObjectMessage(&aom))
                                break;
+                       if (aom.reliable)
+                               count_reliable++;
+                       else
+                               count_unreliable++;
 
                        std::vector<ActiveObjectMessage>* message_list = nullptr;
                        auto n = buffered_messages.find(aom.id);
@@ -787,10 +819,10 @@ void Server::AsyncRunStep(bool initial_step)
                                message_list = n->second;
                        }
                        message_list->push_back(std::move(aom));
-                       aom_count++;
                }
 
-               m_aom_buffer_counter->increment(aom_count);
+               m_aom_buffer_counter[0]->increment(count_reliable);
+               m_aom_buffer_counter[1]->increment(count_unreliable);
 
                {
                        ClientInterface::AutoLock clientlock(m_clients);
@@ -864,20 +896,18 @@ void Server::AsyncRunStep(bool initial_step)
                // We will be accessing the environment
                MutexAutoLock lock(m_env_mutex);
 
-               // Don't send too many at a time
-               //u32 count = 0;
-
-               // Single change sending is disabled if queue size is not small
+               // Single change sending is disabled if queue size is big
                bool disable_single_change_sending = false;
                if(m_unsent_map_edit_queue.size() >= 4)
                        disable_single_change_sending = true;
 
-               int event_count = m_unsent_map_edit_queue.size();
+               const auto event_count = m_unsent_map_edit_queue.size();
+               m_map_edit_event_counter->increment(event_count);
 
                // We'll log the amount of each
                Profiler prof;
 
-               std::list<v3s16> node_meta_updates;
+               std::unordered_set<v3s16> node_meta_updates;
 
                while (!m_unsent_map_edit_queue.empty()) {
                        MapEditEvent* event = m_unsent_map_edit_queue.front();
@@ -904,9 +934,7 @@ void Server::AsyncRunStep(bool initial_step)
                        case MEET_BLOCK_NODE_METADATA_CHANGED: {
                                prof.add("MEET_BLOCK_NODE_METADATA_CHANGED", 1);
                                if (!event->is_private_change) {
-                                       // Don't send the change yet. Collect them to eliminate dupes.
-                                       node_meta_updates.remove(event->p);
-                                       node_meta_updates.push_back(event->p);
+                                       node_meta_updates.emplace(event->p);
                                }
 
                                if (MapBlock *block = m_env->getMap().getBlockNoCreateNoEx(
@@ -959,7 +987,7 @@ void Server::AsyncRunStep(bool initial_step)
                }
 
                // Send all metadata updates
-               if (node_meta_updates.size())
+               if (!node_meta_updates.empty())
                        sendMetadataChanged(node_meta_updates);
        }
 
@@ -1070,7 +1098,7 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id)
        if (!playersao || !player) {
                if (player && player->getPeerId() != PEER_ID_INEXISTENT) {
                        actionstream << "Server: Failed to emerge player \"" << playername
-                                       << "\" (player allocated to an another client)" << std::endl;
+                                       << "\" (player allocated to another client)" << std::endl;
                        DenyAccess(peer_id, SERVER_ACCESSDENIED_ALREADY_CONNECTED);
                } else {
                        errorstream << "Server: " << playername << ": Failed to emerge player"
@@ -1095,7 +1123,7 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id)
        SendInventory(playersao, false);
 
        // Send HP
-       SendPlayerHP(playersao);
+       SendPlayerHP(playersao, false);
 
        // Send death screen
        if (playersao->isDead())
@@ -1228,17 +1256,6 @@ void Server::onMapEditEvent(const MapEditEvent &event)
        m_unsent_map_edit_queue.push(new MapEditEvent(event));
 }
 
-void Server::SetBlocksNotSent(std::map<v3s16, MapBlock *>& block)
-{
-       std::vector<session_t> clients = m_clients.getClientIDs();
-       ClientInterface::AutoLock clientlock(m_clients);
-       // Set the modified blocks unsent for all the clients
-       for (const session_t client_id : clients) {
-                       if (RemoteClient *client = m_clients.lockedGetClientNoEx(client_id))
-                               client->SetBlocksNotSent(block);
-       }
-}
-
 void Server::peerAdded(con::Peer *peer)
 {
        verbosestream<<"Server::peerAdded(): peer->id="
@@ -1339,8 +1356,6 @@ void Server::Send(session_t peer_id, NetworkPacket *pkt)
 
 void Server::SendMovement(session_t peer_id)
 {
-       std::ostringstream os(std::ios_base::binary);
-
        NetworkPacket pkt(TOCLIENT_MOVEMENT, 12 * sizeof(float), peer_id);
 
        pkt << g_settings->getFloat("movement_acceleration_default");
@@ -1362,7 +1377,7 @@ void Server::SendMovement(session_t peer_id)
 void Server::HandlePlayerHPChange(PlayerSAO *playersao, const PlayerHPChangeReason &reason)
 {
        m_script->player_event(playersao, "health_changed");
-       SendPlayerHP(playersao);
+       SendPlayerHP(playersao, reason.type != PlayerHPChangeReason::SET_HP_MAX);
 
        // Send to other clients
        playersao->sendPunchCommand();
@@ -1371,15 +1386,15 @@ void Server::HandlePlayerHPChange(PlayerSAO *playersao, const PlayerHPChangeReas
                HandlePlayerDeath(playersao, reason);
 }
 
-void Server::SendPlayerHP(PlayerSAO *playersao)
+void Server::SendPlayerHP(PlayerSAO *playersao, bool effect)
 {
-       SendHP(playersao->getPeerID(), playersao->getHP());
+       SendHP(playersao->getPeerID(), playersao->getHP(), effect);
 }
 
-void Server::SendHP(session_t peer_id, u16 hp)
+void Server::SendHP(session_t peer_id, u16 hp, bool effect)
 {
-       NetworkPacket pkt(TOCLIENT_HP, 1, peer_id);
-       pkt << hp;
+       NetworkPacket pkt(TOCLIENT_HP, 3, peer_id);
+       pkt << hp << effect;
        Send(&pkt);
 }
 
@@ -1581,7 +1596,12 @@ void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version,
 
        if (peer_id == PEER_ID_INEXISTENT) {
                std::vector<session_t> clients = m_clients.getClientIDs();
-               const v3f pos = (p.minpos + p.maxpos) / 2.0f * BS;
+               const v3f pos = (
+                       p.pos.start.min.val +
+                       p.pos.start.max.val +
+                       p.pos.end.min.val +
+                       p.pos.end.max.val
+               ) / 4.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. */
@@ -1609,11 +1629,19 @@ void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version,
 
        NetworkPacket pkt(TOCLIENT_ADD_PARTICLESPAWNER, 100, peer_id);
 
-       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 << p.amount << p.time;
+       { // serialize legacy fields
+               std::ostringstream os(std::ios_base::binary);
+               p.pos.start.legacySerialize(os);
+               p.vel.start.legacySerialize(os);
+               p.acc.start.legacySerialize(os);
+               p.exptime.start.legacySerialize(os);
+               p.size.start.legacySerialize(os);
+               pkt.putRawString(os.str());
+       }
+       pkt << p.collisiondetection;
 
-       pkt.putLongString(p.texture);
+       pkt.putLongString(p.texture.string);
 
        pkt << id << p.vertical << p.collision_removal << attached_id;
        {
@@ -1624,6 +1652,51 @@ void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version,
        pkt << p.glow << p.object_collision;
        pkt << p.node.param0 << p.node.param2 << p.node_tile;
 
+       { // serialize new fields
+               // initial bias for older properties
+               pkt << p.pos.start.bias
+                       << p.vel.start.bias
+                       << p.acc.start.bias
+                       << p.exptime.start.bias
+                       << p.size.start.bias;
+
+               std::ostringstream os(std::ios_base::binary);
+
+               // final tween frames of older properties
+               p.pos.end.serialize(os);
+               p.vel.end.serialize(os);
+               p.acc.end.serialize(os);
+               p.exptime.end.serialize(os);
+               p.size.end.serialize(os);
+
+               // properties for legacy texture field
+               p.texture.serialize(os, protocol_version, true);
+
+               // new properties
+               p.drag.serialize(os);
+               p.jitter.serialize(os);
+               p.bounce.serialize(os);
+               ParticleParamTypes::serializeParameterValue(os, p.attractor_kind);
+               if (p.attractor_kind != ParticleParamTypes::AttractorKind::none) {
+                       p.attract.serialize(os);
+                       p.attractor_origin.serialize(os);
+                       writeU16(os, p.attractor_attachment); /* object ID */
+                       writeU8(os, p.attractor_kill);
+                       if (p.attractor_kind != ParticleParamTypes::AttractorKind::point) {
+                               p.attractor_direction.serialize(os);
+                               writeU16(os, p.attractor_direction_attachment);
+                       }
+               }
+               p.radius.serialize(os);
+
+               ParticleParamTypes::serializeParameterValue(os, (u16)p.texpool.size());
+               for (const auto& tex : p.texpool) {
+                       tex.serialize(os, protocol_version);
+               }
+
+               pkt.putRawString(os.str());
+       }
+
        Send(&pkt);
 }
 
@@ -1763,7 +1836,8 @@ void Server::SendSetStars(session_t peer_id, const StarParams &params)
        NetworkPacket pkt(TOCLIENT_SET_STARS, 0, peer_id);
 
        pkt << params.visible << params.count
-               << params.starcolor << params.scale;
+               << params.starcolor << params.scale
+               << params.day_opacity;
 
        Send(&pkt);
 }
@@ -1791,8 +1865,16 @@ void Server::SendSetLighting(session_t peer_id, const Lighting &lighting)
 {
        NetworkPacket pkt(TOCLIENT_SET_LIGHTING,
                        4, peer_id);
-       
+
        pkt << lighting.shadow_intensity;
+       pkt << lighting.saturation;
+
+       pkt << lighting.exposure.luminance_min
+                       << lighting.exposure.luminance_max
+                       << lighting.exposure.exposure_correction
+                       << lighting.exposure.speed_dark_bright
+                       << lighting.exposure.speed_bright_dark
+                       << lighting.exposure.center_weight_power;
 
        Send(&pkt);
 }
@@ -2054,14 +2136,13 @@ inline s32 Server::nextSoundId()
        return ret;
 }
 
-s32 Server::playSound(const SimpleSoundSpec &spec,
-               const ServerSoundParams &params, bool ephemeral)
+s32 Server::playSound(ServerPlayingSound &params, bool ephemeral)
 {
        // Find out initial position of sound
        bool pos_exists = false;
-       v3f pos = params.getPos(m_env, &pos_exists);
+       const v3f pos = params.getPos(m_env, &pos_exists);
        // If position is not found while it should be, cancel sound
-       if(pos_exists != (params.type != ServerSoundParams::SSP_LOCAL))
+       if(pos_exists != (params.type != SoundLocation::Local))
                return -1;
 
        // Filter destination clients
@@ -2106,150 +2187,98 @@ s32 Server::playSound(const SimpleSoundSpec &spec,
        if(dst_clients.empty())
                return -1;
 
-       // Create the sound
-       s32 id;
-       ServerPlayingSound *psound = nullptr;
-       if (ephemeral) {
-               id = -1; // old clients will still use this, so pick a reserved ID
-       } else {
-               id = nextSoundId();
-               // The sound will exist as a reference in m_playing_sounds
-               m_playing_sounds[id] = ServerPlayingSound();
-               psound = &m_playing_sounds[id];
-               psound->params = params;
-               psound->spec = spec;
-       }
+       // old clients will still use this, so pick a reserved ID (-1)
+       const s32 id = ephemeral ? -1 : nextSoundId();
 
-       float gain = params.gain * spec.gain;
+       float gain = params.gain * params.spec.gain;
        NetworkPacket pkt(TOCLIENT_PLAY_SOUND, 0);
-       pkt << id << spec.name << gain
+       pkt << id << params.spec.name << gain
                        << (u8) params.type << pos << params.object
-                       << params.loop << params.fade << params.pitch
+                       << params.spec.loop << params.spec.fade << params.spec.pitch
                        << ephemeral;
 
        bool as_reliable = !ephemeral;
 
-       for (const u16 dst_client : dst_clients) {
-               if (psound)
-                       psound->clients.insert(dst_client);
-               m_clients.send(dst_client, 0, &pkt, as_reliable);
+       for (const session_t peer_id : dst_clients) {
+               if (!ephemeral)
+                       params.clients.insert(peer_id);
+               m_clients.send(peer_id, 0, &pkt, as_reliable);
        }
+
+       if (!ephemeral)
+               m_playing_sounds[id] = std::move(params);
        return id;
 }
 void Server::stopSound(s32 handle)
 {
-       // Get sound reference
-       std::unordered_map<s32, ServerPlayingSound>::iterator i =
-               m_playing_sounds.find(handle);
-       if (i == m_playing_sounds.end())
+       auto it = m_playing_sounds.find(handle);
+       if (it == m_playing_sounds.end())
                return;
-       ServerPlayingSound &psound = i->second;
+
+       ServerPlayingSound &psound = it->second;
 
        NetworkPacket pkt(TOCLIENT_STOP_SOUND, 4);
        pkt << handle;
 
-       for (std::unordered_set<session_t>::const_iterator si = psound.clients.begin();
-                       si != psound.clients.end(); ++si) {
+       for (session_t peer_id : psound.clients) {
                // Send as reliable
-               m_clients.send(*si, 0, &pkt, true);
+               m_clients.send(peer_id, 0, &pkt, true);
        }
+
        // Remove sound reference
-       m_playing_sounds.erase(i);
+       m_playing_sounds.erase(it);
 }
 
 void Server::fadeSound(s32 handle, float step, float gain)
 {
-       // Get sound reference
-       std::unordered_map<s32, ServerPlayingSound>::iterator i =
-               m_playing_sounds.find(handle);
-       if (i == m_playing_sounds.end())
+       auto it = m_playing_sounds.find(handle);
+       if (it == m_playing_sounds.end())
                return;
 
-       ServerPlayingSound &psound = i->second;
-       psound.params.gain = gain;
+       ServerPlayingSound &psound = it->second;
+       psound.gain = gain; // destination gain
 
        NetworkPacket pkt(TOCLIENT_FADE_SOUND, 4);
        pkt << handle << step << gain;
 
-       // Backwards compability
-       bool play_sound = gain > 0;
-       ServerPlayingSound compat_psound = psound;
-       compat_psound.clients.clear();
-
-       NetworkPacket compat_pkt(TOCLIENT_STOP_SOUND, 4);
-       compat_pkt << handle;
-
-       for (std::unordered_set<u16>::iterator it = psound.clients.begin();
-                       it != psound.clients.end();) {
-               if (m_clients.getProtocolVersion(*it) >= 32) {
-                       // Send as reliable
-                       m_clients.send(*it, 0, &pkt, true);
-                       ++it;
-               } else {
-                       compat_psound.clients.insert(*it);
-                       // Stop old sound
-                       m_clients.send(*it, 0, &compat_pkt, true);
-                       psound.clients.erase(it++);
-               }
+       for (session_t peer_id : psound.clients) {
+               // Send as reliable
+               m_clients.send(peer_id, 0, &pkt, true);
        }
 
        // Remove sound reference
-       if (!play_sound || psound.clients.empty())
-               m_playing_sounds.erase(i);
-
-       if (play_sound && !compat_psound.clients.empty()) {
-               // Play new sound volume on older clients
-               playSound(compat_psound.spec, compat_psound.params);
-       }
+       if (gain <= 0 || psound.clients.empty())
+               m_playing_sounds.erase(it);
 }
 
 void Server::sendRemoveNode(v3s16 p, std::unordered_set<u16> *far_players,
                float far_d_nodes)
 {
-       float maxd = far_d_nodes * BS;
        v3f p_f = intToFloat(p, BS);
        v3s16 block_pos = getNodeBlockPos(p);
 
        NetworkPacket pkt(TOCLIENT_REMOVENODE, 6);
        pkt << p;
 
-       std::vector<session_t> clients = m_clients.getClientIDs();
-       ClientInterface::AutoLock clientlock(m_clients);
-
-       for (session_t client_id : clients) {
-               RemoteClient *client = m_clients.lockedGetClientNoEx(client_id);
-               if (!client)
-                       continue;
-
-               RemotePlayer *player = m_env->getPlayer(client_id);
-               PlayerSAO *sao = player ? player->getPlayerSAO() : nullptr;
-
-               // If player is far away, only set modified blocks not sent
-               if (!client->isBlockSent(block_pos) || (sao &&
-                               sao->getBasePosition().getDistanceFrom(p_f) > maxd)) {
-                       if (far_players)
-                               far_players->emplace(client_id);
-                       else
-                               client->SetBlockNotSent(block_pos);
-                       continue;
-               }
-
-               // Send as reliable
-               m_clients.send(client_id, 0, &pkt, true);
-       }
+       sendNodeChangePkt(pkt, block_pos, p_f, far_d_nodes, far_players);
 }
 
 void Server::sendAddNode(v3s16 p, MapNode n, std::unordered_set<u16> *far_players,
                float far_d_nodes, bool remove_metadata)
 {
-       float maxd = far_d_nodes * BS;
        v3f p_f = intToFloat(p, BS);
        v3s16 block_pos = getNodeBlockPos(p);
 
        NetworkPacket pkt(TOCLIENT_ADDNODE, 6 + 2 + 1 + 1 + 1);
        pkt << p << n.param0 << n.param1 << n.param2
                        << (u8) (remove_metadata ? 0 : 1);
+       sendNodeChangePkt(pkt, block_pos, p_f, far_d_nodes, far_players);
+}
 
+void Server::sendNodeChangePkt(NetworkPacket &pkt, v3s16 block_pos,
+               v3f p, float far_d_nodes, std::unordered_set<u16> *far_players)
+{
+       float maxd = far_d_nodes * BS;
        std::vector<session_t> clients = m_clients.getClientIDs();
        ClientInterface::AutoLock clientlock(m_clients);
 
@@ -2263,7 +2292,7 @@ void Server::sendAddNode(v3s16 p, MapNode n, std::unordered_set<u16> *far_player
 
                // If player is far away, only set modified blocks not sent
                if (!client->isBlockSent(block_pos) || (sao &&
-                               sao->getBasePosition().getDistanceFrom(p_f) > maxd)) {
+                               sao->getBasePosition().getDistanceFrom(p) > maxd)) {
                        if (far_players)
                                far_players->emplace(client_id);
                        else
@@ -2276,12 +2305,12 @@ void Server::sendAddNode(v3s16 p, MapNode n, std::unordered_set<u16> *far_player
        }
 }
 
-void Server::sendMetadataChanged(const std::list<v3s16> &meta_updates, float far_d_nodes)
+void Server::sendMetadataChanged(const std::unordered_set<v3s16> &positions, float far_d_nodes)
 {
-       float maxd = far_d_nodes * BS;
        NodeMetadataList meta_updates_list(false);
-       std::vector<session_t> clients = m_clients.getClientIDs();
+       std::ostringstream os(std::ios::binary);
 
+       std::vector<session_t> clients = m_clients.getClientIDs();
        ClientInterface::AutoLock clientlock(m_clients);
 
        for (session_t i : clients) {
@@ -2289,18 +2318,20 @@ void Server::sendMetadataChanged(const std::list<v3s16> &meta_updates, float far
                if (!client)
                        continue;
 
-               ServerActiveObject *player = m_env->getActiveObject(i);
-               v3f player_pos = player ? player->getBasePosition() : v3f();
+               ServerActiveObject *player = getPlayerSAO(i);
+               v3s16 player_pos;
+               if (player)
+                       player_pos = floatToInt(player->getBasePosition(), BS);
 
-               for (const v3s16 &pos : meta_updates) {
+               for (const v3s16 pos : positions) {
                        NodeMetadata *meta = m_env->getMap().getNodeMetadata(pos);
 
                        if (!meta)
                                continue;
 
                        v3s16 block_pos = getNodeBlockPos(pos);
-                       if (!client->isBlockSent(block_pos) || (player &&
-                                       player_pos.getDistanceFrom(intToFloat(pos, BS)) > maxd)) {
+                       if (!client->isBlockSent(block_pos) ||
+                                       player_pos.getDistanceFrom(pos) > far_d_nodes) {
                                client->SetBlockNotSent(block_pos);
                                continue;
                        }
@@ -2312,14 +2343,15 @@ void Server::sendMetadataChanged(const std::list<v3s16> &meta_updates, float far
                        continue;
 
                // Send the meta changes
-               std::ostringstream os(std::ios::binary);
+               os.str("");
                meta_updates_list.serialize(os, client->serialization_version, false, true, true);
-               std::ostringstream oss(std::ios::binary);
-               compressZlib(os.str(), oss);
+               std::string raw = os.str();
+               os.str("");
+               compressZlib(raw, os);
 
-               NetworkPacket pkt(TOCLIENT_NODEMETA_CHANGED, 0);
-               pkt.putLongString(oss.str());
-               m_clients.send(i, 0, &pkt, true);
+               NetworkPacket pkt(TOCLIENT_NODEMETA_CHANGED, 0, i);
+               pkt.putLongString(os.str());
+               Send(&pkt);
 
                meta_updates_list.clear();
        }
@@ -2770,9 +2802,10 @@ void Server::RespawnPlayer(session_t peer_id)
                        << playersao->getPlayer()->getName()
                        << " respawns" << std::endl;
 
-       playersao->setHP(playersao->accessObjectProperties()->hp_max,
+       const auto *prop = playersao->accessObjectProperties();
+       playersao->setHP(prop->hp_max,
                        PlayerHPChangeReason(PlayerHPChangeReason::RESPAWN));
-       playersao->setBreath(playersao->accessObjectProperties()->breath_max);
+       playersao->setBreath(prop->breath_max);
 
        bool repositioned = m_script->on_respawnplayer(playersao);
        if (!repositioned) {
@@ -3096,7 +3129,7 @@ std::string Server::getStatusString()
        // Version
        os << "version: " << g_version_string;
        // Game
-       os << " | game: " << (m_gamespec.name.empty() ? m_gamespec.id : m_gamespec.name);
+       os << " | game: " << (m_gamespec.title.empty() ? m_gamespec.id : m_gamespec.title);
        // Uptime
        os << " | uptime: " << duration_to_string((int) m_uptime_counter->get());
        // Max lag estimate
@@ -3278,7 +3311,7 @@ bool Server::hudSetFlags(RemotePlayer *player, u32 flags, u32 mask)
        u32 new_hud_flags = (player->hud_flags & ~mask) | flags;
        if (new_hud_flags == player->hud_flags) // no change
                return true;
-       
+
        SendHUDSetFlags(player->getPeerId(), flags, mask);
        player->hud_flags = new_hud_flags;
 
@@ -3703,8 +3736,8 @@ v3f Server::findSpawnPos()
                s32 range = MYMIN(1 + i, range_max);
                // We're going to try to throw the player to this position
                v2s16 nodepos2d = v2s16(
-                       -range + (myrand() % (range * 2)),
-                       -range + (myrand() % (range * 2)));
+                       -range + myrand_range(0, range*2),
+                       -range + myrand_range(0, range*2));
                // Get spawn level at point
                s16 spawn_level = m_emerge->getSpawnLevelAtPoint(nodepos2d);
                // Continue if MAX_MAP_GENERATION_LIMIT was returned by the mapgen to
@@ -3835,25 +3868,6 @@ PlayerSAO* Server::emergePlayer(const char *name, session_t peer_id, u16 proto_v
        return playersao;
 }
 
-bool Server::registerModStorage(ModMetadata *storage)
-{
-       if (m_mod_storages.find(storage->getModName()) != m_mod_storages.end()) {
-               errorstream << "Unable to register same mod storage twice. Storage name: "
-                               << storage->getModName() << std::endl;
-               return false;
-       }
-
-       m_mod_storages[storage->getModName()] = storage;
-       return true;
-}
-
-void Server::unregisterModStorage(const std::string &name)
-{
-       std::unordered_map<std::string, ModMetadata *>::const_iterator it = m_mod_storages.find(name);
-       if (it != m_mod_storages.end())
-               m_mod_storages.erase(name);
-}
-
 void dedicated_server_loop(Server &server, bool &kill)
 {
        verbosestream<<"dedicated_server_loop()"<<std::endl;
@@ -3990,7 +4004,7 @@ Translations *Server::getTranslationLanguage(const std::string &lang_code)
        return translations;
 }
 
-ModMetadataDatabase *Server::openModStorageDatabase(const std::string &world_path)
+ModStorageDatabase *Server::openModStorageDatabase(const std::string &world_path)
 {
        std::string world_mt_path = world_path + DIR_DELIM + "world.mt";
        Settings world_mt;
@@ -4008,14 +4022,22 @@ ModMetadataDatabase *Server::openModStorageDatabase(const std::string &world_pat
        return openModStorageDatabase(backend, world_path, world_mt);
 }
 
-ModMetadataDatabase *Server::openModStorageDatabase(const std::string &backend,
+ModStorageDatabase *Server::openModStorageDatabase(const std::string &backend,
                const std::string &world_path, const Settings &world_mt)
 {
        if (backend == "sqlite3")
-               return new ModMetadataDatabaseSQLite3(world_path);
+               return new ModStorageDatabaseSQLite3(world_path);
+
+#if USE_POSTGRESQL
+       if (backend == "postgresql") {
+               std::string connect_string;
+               world_mt.getNoEx("pgsql_mod_storage_connection", connect_string);
+               return new ModStorageDatabasePostgreSQL(connect_string);
+       }
+#endif // USE_POSTGRESQL
 
        if (backend == "files")
-               return new ModMetadataDatabaseFiles(world_path);
+               return new ModStorageDatabaseFiles(world_path);
 
        if (backend == "dummy")
                return new Database_Dummy();
@@ -4041,8 +4063,8 @@ bool Server::migrateModStorageDatabase(const GameParams &game_params, const Sett
                return false;
        }
 
-       ModMetadataDatabase *srcdb = nullptr;
-       ModMetadataDatabase *dstdb = nullptr;
+       ModStorageDatabase *srcdb = nullptr;
+       ModStorageDatabase *dstdb = nullptr;
 
        bool succeeded = false;