]> git.lizzy.rs Git - dragonfireclient.git/blobdiff - src/server.cpp
Merge branch 'master' of https://github.com/minetest/minetest
[dragonfireclient.git] / src / server.cpp
index b9c68654ac579baa88e8fe91d54ebab1584e0f63..b6330c96a839705d0ce7b02dc8ecd413b8411aaa 100644 (file)
@@ -66,17 +66,27 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "server/player_sao.h"
 #include "server/serverinventorymgr.h"
 #include "translation.h"
+#include "database/database-sqlite3.h"
+#include "database/database-files.h"
+#include "database/database-dummy.h"
+#include "gameparams.h"
 
 class ClientNotFoundException : public BaseException
 {
 public:
-       ClientNotFoundException(const char *s) : BaseException(s) {}
+       ClientNotFoundException(const char *s):
+               BaseException(s)
+       {}
 };
 
 class ServerThread : public Thread
 {
 public:
-       ServerThread(Server *server) : Thread("Server"), m_server(server) {}
+
+       ServerThread(Server *server):
+               Thread("Server"),
+               m_server(server)
+       {}
 
        void *run();
 
@@ -97,7 +107,13 @@ void *ServerThread::run()
         * doesn't busy wait) and will process any remaining packets.
         */
 
-       m_server->AsyncRunStep(true);
+       try {
+               m_server->AsyncRunStep(true);
+       } catch (con::ConnectionBindFailed &e) {
+               m_server->setAsyncFatalError(e.what());
+       } catch (LuaError &e) {
+               m_server->setAsyncFatalError(e);
+       }
 
        while (!stopRequested()) {
                try {
@@ -106,13 +122,12 @@ void *ServerThread::run()
                        m_server->Receive();
 
                } catch (con::PeerNotFoundException &e) {
-                       infostream << "Server: PeerNotFoundException" << std::endl;
+                       infostream<<"Server: PeerNotFoundException"<<std::endl;
                } catch (ClientNotFoundException &e) {
                } catch (con::ConnectionBindFailed &e) {
                        m_server->setAsyncFatalError(e.what());
                } catch (LuaError &e) {
-                       m_server->setAsyncFatalError("ServerThread::run Lua: " +
-                                                    std::string(e.what()));
+                       m_server->setAsyncFatalError(e);
                }
        }
 
@@ -123,27 +138,23 @@ void *ServerThread::run()
 
 v3f ServerSoundParams::getPos(ServerEnvironment *env, bool *pos_exists) const
 {
-       if (pos_exists)
-               *pos_exists = false;
-       switch (type) {
+       if(pos_exists) *pos_exists = false;
+       switch(type){
        case SSP_LOCAL:
-               return v3f(0, 0, 0);
+               return v3f(0,0,0);
        case SSP_POSITIONAL:
-               if (pos_exists)
-                       *pos_exists = true;
+               if(pos_exists) *pos_exists = true;
                return pos;
        case SSP_OBJECT: {
-               if (object == 0)
-                       return v3f(0, 0, 0);
+               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();
+               if(!sao)
+                       return v3f(0,0,0);
+               if(pos_exists) *pos_exists = true;
+               return sao->getBasePosition(); }
        }
-       }
-       return v3f(0, 0, 0);
+       return v3f(0,0,0);
 }
 
 void Server::ShutdownState::reset()
@@ -167,8 +178,10 @@ void Server::ShutdownState::tick(float dtime, Server *server)
                return;
 
        // Timed shutdown
-       static const float shutdown_msg_times[] = {1, 2, 3, 4, 5, 10, 20, 40, 60, 120,
-                       180, 300, 600, 1200, 1800, 3600};
+       static const float shutdown_msg_times[] =
+       {
+               1, 2, 3, 4, 5, 10, 20, 40, 60, 120, 180, 300, 600, 1200, 1800, 3600
+       };
 
        // Automated messages
        if (m_timer < shutdown_msg_times[ARRLEN(shutdown_msg_times) - 1]) {
@@ -177,8 +190,7 @@ void Server::ShutdownState::tick(float dtime, Server *server)
                        if (m_timer > t && m_timer - dtime < t) {
                                std::wstring periodicMsg = getShutdownTimerMessage();
 
-                               infostream << wide_to_utf8(periodicMsg).c_str()
-                                          << std::endl;
+                               infostream << wide_to_utf8(periodicMsg).c_str() << std::endl;
                                server->SendChatMessage(PEER_ID_INEXISTENT, periodicMsg);
                                break;
                        }
@@ -196,7 +208,7 @@ std::wstring Server::ShutdownState::getShutdownTimerMessage() const
 {
        std::wstringstream ws;
        ws << L"*** Server shutting down in "
-          << duration_to_string(myround(m_timer)).c_str() << ".";
+               << duration_to_string(myround(m_timer)).c_str() << ".";
        return ws.str();
 }
 
@@ -204,19 +216,34 @@ std::wstring Server::ShutdownState::getShutdownTimerMessage() const
        Server
 */
 
-Server::Server(const std::string &path_world, const SubgameSpec &gamespec,
-               bool simple_singleplayer_mode, Address bind_addr, bool dedicated,
-               ChatInterface *iface) :
-               m_bind_addr(bind_addr),
-               m_path_world(path_world), m_gamespec(gamespec),
-               m_simple_singleplayer_mode(simple_singleplayer_mode),
-               m_dedicated(dedicated), m_async_fatal_error(""),
-               m_con(std::make_shared<con::Connection>(PROTOCOL_ID, 512,
-                               CONNECTION_TIMEOUT, m_bind_addr.isIPv6(), this)),
-               m_itemdef(createItemDefManager()), m_nodedef(createNodeDefManager()),
-               m_craftdef(createCraftDefManager()), m_thread(new ServerThread(this)),
-               m_clients(m_con), m_admin_chat(iface),
-               m_modchannel_mgr(new ModChannelMgr())
+Server::Server(
+               const std::string &path_world,
+               const SubgameSpec &gamespec,
+               bool simple_singleplayer_mode,
+               Address bind_addr,
+               bool dedicated,
+               ChatInterface *iface,
+               std::string *on_shutdown_errmsg
+       ):
+       m_bind_addr(bind_addr),
+       m_path_world(path_world),
+       m_gamespec(gamespec),
+       m_simple_singleplayer_mode(simple_singleplayer_mode),
+       m_dedicated(dedicated),
+       m_async_fatal_error(""),
+       m_con(std::make_shared<con::Connection>(PROTOCOL_ID,
+                       512,
+                       CONNECTION_TIMEOUT,
+                       m_bind_addr.isIPv6(),
+                       this)),
+       m_itemdef(createItemDefManager()),
+       m_nodedef(createNodeDefManager()),
+       m_craftdef(createCraftDefManager()),
+       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())
                throw ServerError("Supplied empty world path");
@@ -225,35 +252,44 @@ Server::Server(const std::string &path_world, const SubgameSpec &gamespec,
                throw ServerError("Supplied invalid gamespec");
 
 #if USE_PROMETHEUS
-       m_metrics_backend =
-                       std::unique_ptr<MetricsBackend>(createPrometheusMetricsBackend());
+       m_metrics_backend = std::unique_ptr<MetricsBackend>(createPrometheusMetricsBackend());
 #else
-       m_metrics_backend = std::unique_ptr<MetricsBackend>(new MetricsBackend());
+       m_metrics_backend = std::make_unique<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_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");
+                       "minetest_core_timeofday",
+                       "Time of day value");
 
        m_lag_gauge = m_metrics_backend->addGauge(
-                       "minetest_core_latency", "Latency value (in seconds)");
+                       "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",
-                                       "Processable packets received");
+       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_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"));
 }
 
@@ -262,7 +298,7 @@ Server::~Server()
 
        // Send shutdown message
        SendChatMessage(PEER_ID_INEXISTENT, ChatMessage(CHATMESSAGE_TYPE_ANNOUNCE,
-                                                           L"*** Server shutting down"));
+                       L"*** Server shutting down"));
 
        if (m_env) {
                MutexAutoLock envlock(m_env_mutex);
@@ -281,7 +317,8 @@ Server::~Server()
                        kick_msg = g_settings->get("kick_msg_shutdown");
                }
                m_env->saveLoadedPlayers(true);
-               m_env->kickAllPlayers(SERVER_ACCESSDENIED_SHUTDOWN, kick_msg, reconnect);
+               m_env->kickAllPlayers(SERVER_ACCESSDENIED_SHUTDOWN,
+                       kick_msg, reconnect);
        }
 
        actionstream << "Server: Shutting down" << std::endl;
@@ -298,7 +335,18 @@ Server::~Server()
 
                // 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();
@@ -310,10 +358,15 @@ Server::~Server()
                delete m_thread;
        }
 
+       // Write any changes before deletion.
+       if (m_mod_storage_database)
+               m_mod_storage_database->endSave();
+
        // Delete things in the reverse order of creation
        delete m_emerge;
        delete m_env;
        delete m_rollback;
+       delete m_mod_storage_database;
        delete m_banmanager;
        delete m_itemdef;
        delete m_nodedef;
@@ -322,6 +375,8 @@ Server::~Server()
        // Deinitialize scripting
        infostream << "Server: Deinitializing scripting" << std::endl;
        delete m_script;
+       delete m_startup_server_map; // if available
+       delete m_game_settings;
 
        while (!m_unsent_map_edit_queue.empty()) {
                delete m_unsent_map_edit_queue.front();
@@ -339,30 +394,41 @@ void Server::init()
        infostream << "- world:  " << m_path_world << std::endl;
        infostream << "- game:   " << m_gamespec.path << std::endl;
 
+       m_game_settings = Settings::createLayer(SL_GAME);
+
        // 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_emerge = new EmergeManager(this, m_metrics_backend.get());
 
        // Create ban manager
        std::string ban_path = m_path_world + DIR_DELIM "ipban.txt";
        m_banmanager = new BanManager(ban_path);
 
-       m_modmgr = std::unique_ptr<ServerModManager>(new ServerModManager(m_path_world));
+       // Create mod storage database and begin a save for later
+       m_mod_storage_database = openModStorageDatabase(m_path_world);
+       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();
        }
 
-       // lock environment
+       //lock environment
        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, m_metrics_backend.get());
+       ServerMap *servermap = new ServerMap(m_path_world, this, m_emerge, m_metrics_backend.get());
+       m_startup_server_map = servermap;
 
        // Initialize scripting
        infostream << "Server: Initializing Lua" << std::endl;
@@ -370,11 +436,11 @@ void Server::init()
        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_inventory_mgr = std::make_unique<ServerInventoryManager>();
 
        m_script->loadMod(getBuiltinLuaPath() + DIR_DELIM "init.lua", BUILTIN_MOD_NAME);
 
+       m_gamespec.checkAndLog();
        m_modmgr->loadMods(m_script);
 
        // Read Textures and calculate sha1 sums
@@ -390,8 +456,7 @@ void Server::init()
        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_itemdef->applyTextureOverrides(override_source.getItemTextureOverrides());
        }
 
        m_nodedef->setNodeRegistrationStatus(true);
@@ -406,7 +471,9 @@ void Server::init()
        m_craftdef->initHashes(this);
 
        // Initialize Environment
-       m_env = new ServerEnvironment(servermap, m_script, this, m_path_world);
+       m_startup_server_map = nullptr; // Ownership moved to ServerEnvironment
+       m_env = new ServerEnvironment(servermap, m_script, this,
+               m_path_world, m_metrics_backend.get());
 
        m_inventory_mgr->setEnv(m_env);
        m_clients.setEnv(m_env);
@@ -425,6 +492,9 @@ void Server::init()
        // Give environment reference to scripting api
        m_script->initializeEnvironment(m_env);
 
+       // Do this after regular script init is done
+       m_script->initAsync();
+
        // Register us to receive map edit events
        servermap->addEventReceiver(this);
 
@@ -442,8 +512,8 @@ void Server::start()
 {
        init();
 
-       infostream << "Starting server on " << m_bind_addr.serializeString() << "..."
-                  << std::endl;
+       infostream << "Starting server on " << m_bind_addr.serializeString()
+                       << "..." << std::endl;
 
        // Stop thread if already running
        m_thread->stop();
@@ -456,32 +526,29 @@ void Server::start()
        m_thread->start();
 
        // ASCII art for the win!
-       std::cerr << "        .__               __                   __   " << std::endl
-                 << "  _____ |__| ____   _____/  |_  ____   _______/  |_ " << std::endl
-                 << " /     \\|  |/    \\_/ __ \\   __\\/ __ \\ /  ___/\\   __\\"
-                 << std::endl
-                 << "|  Y Y  \\  |   |  \\  ___/|  | \\  ___/ \\___ \\  |  |  "
-                 << std::endl
-                 << "|__|_|  /__|___|  /\\___  >__|  \\___  >____  > |__|  " << std::endl
-                 << "      \\/        \\/     \\/          \\/     \\/        "
-                 << std::endl;
+       std::cerr
+               << "         __.               __.                 __.  " << std::endl
+               << "  _____ |__| ____   _____ /  |_  _____  _____ /  |_ " << std::endl
+               << " /     \\|  |/    \\ /  __ \\    _\\/  __ \\/   __>    _\\" << std::endl
+               << "|  Y Y  \\  |   |  \\   ___/|  | |   ___/\\___  \\|  |  " << std::endl
+               << "|__|_|  /  |___|  /\\______>  |  \\______>_____/|  |  " << std::endl
+               << "      \\/ \\/     \\/         \\/                  \\/   " << std::endl;
        actionstream << "World at [" << m_path_world << "]" << std::endl;
-       actionstream << "Server for gameid=\"" << m_gamespec.id << "\" listening on "
-                    << m_bind_addr.serializeString() << ":" << m_bind_addr.getPort()
-                    << "." << std::endl;
+       actionstream << "Server for gameid=\"" << m_gamespec.id
+                       << "\" listening on ";
+       m_bind_addr.print(actionstream);
+       actionstream << "." << std::endl;
 }
 
 void Server::stop()
 {
-       infostream << "Server: Stopping and waiting threads" << std::endl;
+       infostream<<"Server: Stopping and waiting threads"<<std::endl;
 
        // Stop threads (set run=false first so both start stopping)
        m_thread->stop();
-       // m_emergethread.setRun(false);
        m_thread->wait();
-       // m_emergethread.stop();
 
-       infostream << "Server: Threads stopped" << std::endl;
+       infostream<<"Server: Threads stopped"<<std::endl;
 }
 
 void Server::step(float dtime)
@@ -498,8 +565,8 @@ void Server::step(float dtime)
        if (!async_err.empty()) {
                if (!m_simple_singleplayer_mode) {
                        m_env->kickAllPlayers(SERVER_ACCESSDENIED_CRASH,
-                                       g_settings->get("kick_msg_crash"),
-                                       g_settings->getBool("ask_reconnect_on_crash"));
+                               g_settings->get("kick_msg_crash"),
+                               g_settings->getBool("ask_reconnect_on_crash"));
                }
                throw ServerError("AsyncErr: " + async_err);
        }
@@ -519,7 +586,7 @@ void Server::AsyncRunStep(bool initial_step)
                SendBlocks(dtime);
        }
 
-       if ((dtime < 0.001) && !initial_step)
+       if((dtime < 0.001) && !initial_step)
                return;
 
        ScopeProfiler sp(g_profiler, "Server::AsyncRunStep()", SPT_AVG);
@@ -560,25 +627,27 @@ void Server::AsyncRunStep(bool initial_step)
                // Figure out and report maximum lag to environment
                float max_lag = m_env->getMaxLagEstimate();
                max_lag *= 0.9998; // Decrease slowly (about half per 5 minutes)
-               if (dtime > max_lag) {
-                       if (dtime > 0.1 && dtime > max_lag * 2.0)
-                               infostream << "Server: Maximum lag peaked to " << dtime
-                                          << " s" << std::endl;
+               if(dtime > max_lag){
+                       if(dtime > 0.1 && dtime > max_lag * 2.0)
+                               infostream<<"Server: Maximum lag peaked to "<<dtime
+                                               <<" s"<<std::endl;
                        max_lag = dtime;
                }
                m_env->reportMaxLagEstimate(max_lag);
+
                // Step environment
                m_env->step(dtime);
        }
 
        static const float map_timer_and_unload_dtime = 2.92;
-       if (m_map_timer_and_unload_interval.step(dtime, map_timer_and_unload_dtime)) {
+       if(m_map_timer_and_unload_interval.step(dtime, map_timer_and_unload_dtime))
+       {
                MutexAutoLock lock(m_env_mutex);
                // 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);
+                       g_settings->getFloat("server_unload_unused_data_timeout"),
+                       U32_MAX);
        }
 
        /*
@@ -588,14 +657,13 @@ void Server::AsyncRunStep(bool initial_step)
                if (!m_admin_chat->command_queue.empty()) {
                        MutexAutoLock lock(m_env_mutex);
                        while (!m_admin_chat->command_queue.empty()) {
-                               ChatEvent *evt = m_admin_chat->command_queue
-                                                                .pop_frontNoEx();
+                               ChatEvent *evt = m_admin_chat->command_queue.pop_frontNoEx();
                                handleChatInterfaceEvent(evt);
                                delete evt;
                        }
                }
-               m_admin_chat->outgoing_queue.push_back(new ChatEventTimeInfo(
-                               m_env->getGameTime(), m_env->getTimeOfDay()));
+               m_admin_chat->outgoing_queue.push_back(
+                       new ChatEventTimeInfo(m_env->getGameTime(), m_env->getTimeOfDay()));
        }
 
        /*
@@ -604,15 +672,16 @@ void Server::AsyncRunStep(bool initial_step)
 
        /* Transform liquids */
        m_liquid_transform_timer += dtime;
-       if (m_liquid_transform_timer >= m_liquid_transform_every) {
+       if(m_liquid_transform_timer >= m_liquid_transform_every)
+       {
                m_liquid_transform_timer -= m_liquid_transform_every;
 
                MutexAutoLock lock(m_env_mutex);
 
                ScopeProfiler sp(g_profiler, "Server: liquid transform");
 
-               std::map<v3s16, MapBlock *> modified_blocks;
-               m_env->getMap().transformLiquids(modified_blocks, m_env);
+               std::map<v3s16, MapBlock*> modified_blocks;
+               m_env->getServerMap().transformLiquids(modified_blocks, m_env);
 
                /*
                        Set the modified blocks unsent for all the clients
@@ -623,20 +692,40 @@ void Server::AsyncRunStep(bool initial_step)
        }
        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);
+       }
+
+       {
+               float &counter = m_step_pending_dyn_media_timer;
+               counter += dtime;
+               if (counter >= 5.0f) {
+                       stepPendingDynMediaCallbacks(counter);
+                       counter = 0;
+               }
+       }
+
+
 #if USE_CURL
        // send masterserver announce
        {
                float &counter = m_masterserver_timer;
                if (!isSingleplayer() && (!counter || counter >= 300.0) &&
                                g_settings->getBool("server_announce")) {
-                       ServerList::sendAnnounce(counter ? ServerList::AA_UPDATE
-                                                        : ServerList::AA_START,
-                                       m_bind_addr.getPort(), m_clients.getPlayerNames(),
-                                       m_uptime_counter->get(), m_env->getGameTime(),
-                                       m_lag_gauge->get(), m_gamespec.id,
+                       ServerList::sendAnnounce(counter ? ServerList::AA_UPDATE :
+                                               ServerList::AA_START,
+                                       m_bind_addr.getPort(),
+                                       m_clients.getPlayerNames(),
+                                       m_uptime_counter->get(),
+                                       m_env->getGameTime(),
+                                       m_lag_gauge->get(),
+                                       m_gamespec.id,
                                        Mapgen::getMapgenName(m_emerge->mgparams->mgtype),
-                                       m_modmgr->getMods(), m_dedicated);
+                                       m_modmgr->getMods(),
+                                       m_dedicated);
                        counter = 0.01;
                }
                counter += dtime;
@@ -647,51 +736,39 @@ void Server::AsyncRunStep(bool initial_step)
                Check added and deleted active objects
        */
        {
-               // infostream<<"Server: Checking added and deleted active
-               // objects"<<std::endl;
+               //infostream<<"Server: Checking added and deleted active objects"<<std::endl;
                MutexAutoLock envlock(m_env_mutex);
 
-               m_clients.lock();
-               const RemoteClientMap &clients = m_clients.getClientList();
-               ScopeProfiler sp(g_profiler, "Server: update objects within range");
+               {
+                       ClientInterface::AutoLock clientlock(m_clients);
+                       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;
+                       m_player_gauge->set(clients.size());
+                       for (const auto &client_it : clients) {
+                               RemoteClient *client = client_it.second;
 
-                       if (client->getState() < CS_DefinitionsSent)
-                               continue;
+                               if (client->getState() < CS_DefinitionsSent)
+                                       continue;
 
-                       // This can happen if the client times out somehow
-                       if (!m_env->getPlayer(client->peer_id))
-                               continue;
+                               // This can happen if the client times out somehow
+                               if (!m_env->getPlayer(client->peer_id))
+                                       continue;
 
-                       PlayerSAO *playersao = getPlayerSAO(client->peer_id);
-                       if (!playersao)
-                               continue;
+                               PlayerSAO *playersao = getPlayerSAO(client->peer_id);
+                               if (!playersao)
+                                       continue;
 
-                       SendActiveObjectRemoveAdd(client, playersao);
+                               SendActiveObjectRemoveAdd(client, playersao);
+                       }
                }
-               m_clients.unlock();
 
-               // Save mod storages if modified
+               // Write changes to the mod storage
                m_mod_storage_save_timer -= dtime;
                if (m_mod_storage_save_timer <= 0.0f) {
-                       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;
+                       m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval");
+                       m_mod_storage_database->endSave();
+                       m_mod_storage_database->beginSave();
                }
        }
 
@@ -704,17 +781,20 @@ void Server::AsyncRunStep(bool initial_step)
 
                // Key = object id
                // Value = data sent by object
-               std::unordered_map<u16, std::vector<ActiveObjectMessage> *>
-                               buffered_messages;
+               std::unordered_map<u16, std::vector<ActiveObjectMessage>*> buffered_messages;
 
                // Get active object messages from environment
                ActiveObjectMessage aom(0);
-               u32 aom_count = 0;
-               for (;;) {
+               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;
+                       std::vector<ActiveObjectMessage>message_list = nullptr;
                        auto n = buffered_messages.find(aom.id);
                        if (n == buffered_messages.end()) {
                                message_list = new std::vector<ActiveObjectMessage>;
@@ -723,80 +803,69 @@ 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_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);
-                       // 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
-                               u16 id = buffered_message.first;
-                               ServerActiveObject *sao = m_env->getActiveObject(id);
-                               if (!sao || client->m_known_objects.find(id) ==
-                                                               client->m_known_objects
-                                                                               .end())
-                                       continue;
+               }
 
-                               // Get message list of object
-                               std::vector<ActiveObjectMessage> *list =
-                                               buffered_message.second;
-                               // Go through every message
-                               for (const ActiveObjectMessage &aom : *list) {
-                                       // Send position updates to players who do not see
-                                       // the attachment
-                                       if (aom.datastring[0] == AO_CMD_UPDATE_POSITION) {
-                                               if (sao->getId() == player->getId())
-                                                       continue;
-
-                                               // Do not send position updates for
-                                               // attached players as long the parent is
-                                               // known to the client
-                                               ServerActiveObject *parent =
-                                                               sao->getParent();
-                                               if (parent && client->m_known_objects.find(
-                                                                             parent->getId()) !=
-                                                                               client->m_known_objects
-                                                                                               .end())
-                                                       continue;
-                                       }
+               m_aom_buffer_counter[0]->increment(count_reliable);
+               m_aom_buffer_counter[1]->increment(count_unreliable);
 
-                                       // 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));
+               {
+                       ClientInterface::AutoLock clientlock(m_clients);
+                       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);
+                               // 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
+                                       u16 id = buffered_message.first;
+                                       ServerActiveObject *sao = m_env->getActiveObject(id);
+                                       if (!sao || client->m_known_objects.find(id) == client->m_known_objects.end())
+                                               continue;
+
+                                       // Get message list of object
+                                       std::vector<ActiveObjectMessage>* list = buffered_message.second;
+                                       // Go through every message
+                                       for (const ActiveObjectMessage &aom : *list) {
+                                               // Send position updates to players who do not see the attachment
+                                               if (aom.datastring[0] == AO_CMD_UPDATE_POSITION) {
+                                                       if (sao->getId() == player->getId())
+                                                               continue;
+
+                                                       // Do not send position updates for attached players
+                                                       // as long the parent is known to the client
+                                                       ServerActiveObject *parent = sao->getParent();
+                                                       if (parent && client->m_known_objects.find(parent->getId()) !=
+                                                                       client->m_known_objects.end())
+                                                               continue;
+                                               }
+
+                                               // 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));
+                                       }
+                               }
+                               /*
+                                       reliable_data and unreliable_data are now ready.
+                                       Send them.
+                               */
+                               if (!reliable_data.empty()) {
+                                       SendActiveObjectMessages(client->peer_id, reliable_data);
                                }
-                       }
-                       /*
-                               reliable_data and unreliable_data are now ready.
-                               Send them.
-                       */
-                       if (!reliable_data.empty()) {
-                               SendActiveObjectMessages(client->peer_id, reliable_data);
-                       }
 
-                       if (!unreliable_data.empty()) {
-                               SendActiveObjectMessages(
-                                               client->peer_id, unreliable_data, false);
+                               if (!unreliable_data.empty()) {
+                                       SendActiveObjectMessages(client->peer_id, unreliable_data, false);
+                               }
                        }
                }
-               m_clients.unlock();
 
                // Clear buffered_messages
                for (auto &buffered_message : buffered_messages) {
@@ -811,15 +880,13 @@ 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)
+               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;
@@ -827,7 +894,7 @@ void Server::AsyncRunStep(bool initial_step)
                std::list<v3s16> node_meta_updates;
 
                while (!m_unsent_map_edit_queue.empty()) {
-                       MapEditEvent *event = m_unsent_map_edit_queue.front();
+                       MapEditEventevent = m_unsent_map_edit_queue.front();
                        m_unsent_map_edit_queue.pop();
 
                        // Players far away from the change are stored here.
@@ -851,30 +918,28 @@ 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.
+                                       // Don't send the change yet. Collect them to eliminate dupes.
                                        node_meta_updates.remove(event->p);
                                        node_meta_updates.push_back(event->p);
                                }
 
                                if (MapBlock *block = m_env->getMap().getBlockNoCreateNoEx(
-                                                   getNodeBlockPos(event->p))) {
+                                               getNodeBlockPos(event->p))) {
                                        block->raiseModified(MOD_STATE_WRITE_NEEDED,
-                                                       MOD_REASON_REPORT_META_CHANGE);
+                                               MOD_REASON_REPORT_META_CHANGE);
                                }
                                break;
                        }
                        case MEET_OTHER:
                                prof.add("MEET_OTHER", 1);
-                               for (const v3s16 &modified_block :
-                                               event->modified_blocks) {
+                               for (const v3s16 &modified_block : event->modified_blocks) {
                                        m_clients.markBlockposAsNotSent(modified_block);
                                }
                                break;
                        default:
                                prof.add("unknown", 1);
                                warningstream << "Server: Unknown MapEditEvent "
-                                             << ((u32)event->type) << std::endl;
+                                               << ((u32)event->type) << std::endl;
                                break;
                        }
 
@@ -883,19 +948,16 @@ void Server::AsyncRunStep(bool initial_step)
                        */
                        if (!far_players.empty()) {
                                // Convert list format to that wanted by SetBlocksNotSent
-                               std::map<v3s16, MapBlock *> modified_blocks2;
-                               for (const v3s16 &modified_block :
-                                               event->modified_blocks) {
+                               std::map<v3s16, MapBlock*> modified_blocks2;
+                               for (const v3s16 &modified_block : event->modified_blocks) {
                                        modified_blocks2[modified_block] =
-                                                       m_env->getMap().getBlockNoCreateNoEx(
-                                                                       modified_block);
+                                                       m_env->getMap().getBlockNoCreateNoEx(modified_block);
                                }
 
                                // Set blocks not sent
                                for (const u16 far_player : far_players) {
                                        if (RemoteClient *client = getClient(far_player))
-                                               client->SetBlocksNotSent(
-                                                               modified_blocks2);
+                                               client->SetBlocksNotSent(modified_blocks2);
                                }
                        }
 
@@ -916,14 +978,14 @@ void Server::AsyncRunStep(bool initial_step)
        }
 
        /*
-               Trigger emergethread (it somehow gets to a non-triggered but
-               bysy state sometimes)
+               Trigger emerge thread
+               Doing this every 2s is left over from old code, unclear if this is still needed.
        */
        {
                float &counter = m_emergethread_trigger_timer;
-               counter += dtime;
-               if (counter >= 2.0) {
-                       counter = 0.0;
+               counter -= dtime;
+               if (counter <= 0.0f) {
+                       counter = 2.0f;
 
                        m_emerge->startThreads();
                }
@@ -934,7 +996,7 @@ void Server::AsyncRunStep(bool initial_step)
                float &counter = m_savemap_timer;
                counter += dtime;
                static thread_local const float save_interval =
-                               g_settings->getFloat("server_map_save_interval");
+                       g_settings->getFloat("server_map_save_interval");
                if (counter >= save_interval) {
                        counter = 0.0;
                        MutexAutoLock lock(m_env_mutex);
@@ -970,9 +1032,8 @@ void Server::Receive()
                peer_id = 0;
                try {
                        /*
-                               In the first iteration *wait* for a packet, afterwards
-                          process all packets that are immediately available (no
-                          waiting).
+                               In the first iteration *wait* for a packet, afterwards process
+                               all packets that are immediately available (no waiting).
                        */
                        if (first) {
                                m_con->Receive(&pkt);
@@ -987,19 +1048,15 @@ void Server::Receive()
                        ProcessData(&pkt);
                        m_packet_recv_processed_counter->increment();
                } catch (const con::InvalidIncomingDataException &e) {
-                       infostream << "Server::Receive(): InvalidIncomingDataException: "
-                                     "what()="
-                                  << e.what() << std::endl;
+                       infostream << "Server::Receive(): InvalidIncomingDataException: what()="
+                                       << e.what() << std::endl;
                } catch (const SerializationError &e) {
                        infostream << "Server::Receive(): SerializationError: what()="
-                                  << e.what() << std::endl;
+                                       << e.what() << std::endl;
                } catch (const ClientStateError &e) {
-                       errorstream << "ProcessData: peer=" << peer_id
-                                   << " what()=" << e.what() << std::endl;
-                       DenyAccess_Legacy(peer_id,
-                                       L"Your client sent something server didn't "
-                                       L"expect."
-                                       L"Try reconnecting or updating your client");
+                       errorstream << "ProcessData: peer=" << peer_id << " what()="
+                                        << e.what() << std::endl;
+                       DenyAccess(peer_id, SERVER_ACCESSDENIED_UNEXPECTED_DATA);
                } catch (const con::PeerNotFoundException &e) {
                        // Do nothing
                } catch (const con::NoIncomingDataException &e) {
@@ -1008,24 +1065,18 @@ void Server::Receive()
        }
 }
 
-PlayerSAO *Server::StageTwoClientInit(session_t peer_id)
+PlayerSAOServer::StageTwoClientInit(session_t peer_id)
 {
        std::string playername;
        PlayerSAO *playersao = NULL;
-       m_clients.lock();
-       try {
-               RemoteClient *client =
-                               m_clients.lockedGetClientNoEx(peer_id, CS_InitDone);
+       {
+               ClientInterface::AutoLock clientlock(m_clients);
+               RemoteClient* client = m_clients.lockedGetClientNoEx(peer_id, CS_InitDone);
                if (client) {
                        playername = client->getName();
-                       playersao = emergePlayer(playername.c_str(), peer_id,
-                                       client->net_proto_version);
+                       playersao = emergePlayer(playername.c_str(), peer_id, client->net_proto_version);
                }
-       } catch (std::exception &e) {
-               m_clients.unlock();
-               throw;
        }
-       m_clients.unlock();
 
        RemotePlayer *player = m_env->getPlayer(playername.c_str());
 
@@ -1033,19 +1084,14 @@ 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;
-                       DenyAccess_Legacy(peer_id,
-                                       L"Another client is connected with this "
-                                       L"name. If your client closed unexpectedly, try "
-                                       L"again in "
-                                       L"a minute.");
+                                       << "\" (player allocated to an another client)" << std::endl;
+                       DenyAccess(peer_id, SERVER_ACCESSDENIED_ALREADY_CONNECTED);
                } else {
-                       errorstream << "Server: " << playername
-                                   << ": Failed to emerge player" << std::endl;
-                       DenyAccess_Legacy(peer_id, L"Could not allocate player.");
+                       errorstream << "Server: " << playername << ": Failed to emerge player"
+                                       << std::endl;
+                       DenyAccess(peer_id, SERVER_ACCESSDENIED_SERVER_FAIL);
                }
-               return NULL;
+               return nullptr;
        }
 
        /*
@@ -1062,31 +1108,31 @@ PlayerSAO *Server::StageTwoClientInit(session_t peer_id)
        // Send inventory
        SendInventory(playersao, false);
 
-       // Send HP or death screen
+       // Send HP
+       SendPlayerHP(playersao);
+
+       // Send death screen
        if (playersao->isDead())
-               SendDeathscreen(peer_id, false, v3f(0, 0, 0));
-       else
-               SendPlayerHPOrDie(playersao,
-                               PlayerHPChangeReason(PlayerHPChangeReason::SET_HP));
+               SendDeathscreen(peer_id, false, v3f(0,0,0));
 
        // Send Breath
        SendPlayerBreath(playersao);
 
        /*
-               Print out action
+               Update player list and print action
        */
        {
-               Address addr = getPeerAddress(player->getPeerId());
-               std::string ip_str = addr.serializeString();
-               const std::vector<std::string> &names = m_clients.getPlayerNames();
-
-               actionstream << player->getName() << " [" << ip_str
-                            << "] joins game. List of players: ";
+               NetworkPacket notice_pkt(TOCLIENT_UPDATE_PLAYER_LIST, 0, PEER_ID_INEXISTENT);
+               notice_pkt << (u8) PLAYER_LIST_ADD << (u16) 1 << std::string(player->getName());
+               m_clients.sendToAll(&notice_pkt);
+       }
+       {
+               std::string ip_str = getPeerAddress(player->getPeerId()).serializeString();
+               const auto &names = m_clients.getPlayerNames();
 
-               for (const std::string &name : names) {
+               actionstream << player->getName() << " [" << ip_str << "] joins game. List of players: ";
+               for (const std::string &name : names)
                        actionstream << name << " ";
-               }
-
                actionstream << player->getName() << std::endl;
        }
        return playersao;
@@ -1110,15 +1156,13 @@ void Server::ProcessData(NetworkPacket *pkt)
                Address address = getPeerAddress(peer_id);
                std::string addr_s = address.serializeString();
 
+               // FIXME: Isn't it a bit excessive to check this for every packet?
                if (m_banmanager->isIpBanned(addr_s)) {
                        std::string ban_name = m_banmanager->getBanName(addr_s);
                        infostream << "Server: A banned client tried to connect from "
-                                  << addr_s << "; banned name was " << ban_name
-                                  << std::endl;
-                       // This actually doesn't seem to transfer to the client
-                       DenyAccess_Legacy(
-                                       peer_id, L"Your ip is banned. Banned name was " +
-                                                                utf8_to_wide(ban_name));
+                                       << addr_s << "; banned name was " << ban_name << std::endl;
+                       DenyAccess(peer_id, SERVER_ACCESSDENIED_CUSTOM_STRING,
+                               "Your IP is banned. Banned name was " + ban_name);
                        return;
                }
        } catch (con::PeerNotFoundException &e) {
@@ -1128,18 +1172,18 @@ void Server::ProcessData(NetworkPacket *pkt)
                 * respond for some time, your server was overloaded or
                 * things like that.
                 */
-               infostream << "Server::ProcessData(): Canceling: peer " << peer_id
-                          << " not found" << std::endl;
+               infostream << "Server::ProcessData(): Canceling: peer "
+                               << peer_id << " not found" << std::endl;
                return;
        }
 
        try {
-               ToServerCommand command = (ToServerCommand)pkt->getCommand();
+               ToServerCommand command = (ToServerCommand) pkt->getCommand();
 
                // Command must be handled into ToServerCommandHandler
                if (command >= TOSERVER_NUM_MSG_TYPES) {
-                       infostream << "Server: Ignoring unknown command " << command
-                                  << std::endl;
+                       infostream << "Server: Ignoring unknown command "
+                                        << command << std::endl;
                        return;
                }
 
@@ -1150,11 +1194,10 @@ void Server::ProcessData(NetworkPacket *pkt)
 
                u8 peer_ser_ver = getClient(peer_id, CS_InitDone)->serialization_version;
 
-               if (peer_ser_ver == SER_FMT_VER_INVALID) {
+               if(peer_ser_ver == SER_FMT_VER_INVALID) {
                        errorstream << "Server::ProcessData(): Cancelling: Peer"
-                                      " serialization format invalid or not initialized."
-                                      " Skipping incoming command="
-                                   << command << std::endl;
+                                       " serialization format invalid or not initialized."
+                                       " Skipping incoming command=" << command << std::endl;
                        return;
                }
 
@@ -1165,23 +1208,23 @@ void Server::ProcessData(NetworkPacket *pkt)
                }
 
                if (m_clients.getClientState(peer_id) < CS_Active) {
-                       if (command == TOSERVER_PLAYERPOS)
-                               return;
+                       if (command == TOSERVER_PLAYERPOS) return;
 
-                       errorstream << "Got packet command: " << command
-                                   << " for peer id " << peer_id
-                                   << " but client isn't active yet. Dropping packet "
-                                   << std::endl;
+                       errorstream << "Got packet command: " << command << " for peer id "
+                                       << peer_id << " but client isn't active yet. Dropping packet "
+                                       << std::endl;
                        return;
                }
 
                handleCommand(pkt);
        } catch (SendFailedException &e) {
                errorstream << "Server::ProcessData(): SendFailedException: "
-                           << "what=" << e.what() << std::endl;
+                               << "what=" << e.what()
+                               << std::endl;
        } catch (PacketError &e) {
                actionstream << "Server::ProcessData(): PacketError: "
-                            << "what=" << e.what() << std::endl;
+                               << "what=" << e.what()
+                               << std::endl;
        }
 }
 
@@ -1199,85 +1242,83 @@ void Server::onMapEditEvent(const MapEditEvent &event)
        m_unsent_map_edit_queue.push(new MapEditEvent(event));
 }
 
-void Server::SetBlocksNotSent(std::map<v3s16, MapBlock *> &block)
+void Server::SetBlocksNotSent(std::map<v3s16, MapBlock *>block)
 {
        std::vector<session_t> clients = m_clients.getClientIDs();
-       m_clients.lock();
+       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);
+                       if (RemoteClient *client = m_clients.lockedGetClientNoEx(client_id))
+                               client->SetBlocksNotSent(block);
        }
-       m_clients.unlock();
 }
 
 void Server::peerAdded(con::Peer *peer)
 {
-       verbosestream << "Server::peerAdded(): peer->id=" << peer->id << std::endl;
+       verbosestream<<"Server::peerAdded(): peer->id="
+                       <<peer->id<<std::endl;
 
        m_peer_change_queue.push(con::PeerChange(con::PEER_ADDED, peer->id, false));
 }
 
 void Server::deletingPeer(con::Peer *peer, bool timeout)
 {
-       verbosestream << "Server::deletingPeer(): peer->id=" << peer->id
-                     << ", timeout=" << timeout << std::endl;
+       verbosestream<<"Server::deletingPeer(): peer->id="
+                       <<peer->id<<", timeout="<<timeout<<std::endl;
 
        m_clients.event(peer->id, CSE_Disconnect);
        m_peer_change_queue.push(con::PeerChange(con::PEER_REMOVED, peer->id, timeout));
 }
 
-bool Server::getClientConInfo(session_t peer_id, con::rtt_stat_type type, float *retval)
+bool Server::getClientConInfo(session_t peer_id, con::rtt_stat_type type, floatretval)
 {
-       *retval = m_con->getPeerStat(peer_id, type);
+       *retval = m_con->getPeerStat(peer_id,type);
        return *retval != -1;
 }
 
-bool Server::getClientInfo(session_t peer_id, ClientState *state, u32 *uptime,
-               u8 *ser_vers, u16 *prot_vers, u8 *major, u8 *minor, u8 *patch,
-               std::string *vers_string, std::string *lang_code)
+bool Server::getClientInfo(session_t peer_id, ClientInfo &ret)
 {
-       *state = m_clients.getClientState(peer_id);
-       m_clients.lock();
-       RemoteClient *client = m_clients.lockedGetClientNoEx(peer_id, CS_Invalid);
+       ClientInterface::AutoLock clientlock(m_clients);
+       RemoteClient* client = m_clients.lockedGetClientNoEx(peer_id, CS_Invalid);
 
-       if (!client) {
-               m_clients.unlock();
+       if (!client)
                return false;
-       }
 
-       *uptime = client->uptime();
-       *ser_vers = client->serialization_version;
-       *prot_vers = client->net_proto_version;
+       ret.state = client->getState();
+       ret.addr = client->getAddress();
+       ret.uptime = client->uptime();
+       ret.ser_vers = client->serialization_version;
+       ret.prot_vers = client->net_proto_version;
 
-       *major = client->getMajor();
-       *minor = client->getMinor();
-       *patch = client->getPatch();
-       *vers_string = client->getFull();
-       *lang_code = client->getLangCode();
+       ret.major = client->getMajor();
+       ret.minor = client->getMinor();
+       ret.patch = client->getPatch();
+       ret.vers_string = client->getFullVer();
 
-       m_clients.unlock();
+       ret.lang_code = client->getLangCode();
 
        return true;
 }
 
 void Server::handlePeerChanges()
 {
-       while (!m_peer_change_queue.empty()) {
+       while(!m_peer_change_queue.empty())
+       {
                con::PeerChange c = m_peer_change_queue.front();
                m_peer_change_queue.pop();
 
-               verbosestream << "Server: Handling peer change: "
-                             << "id=" << c.peer_id << ", timeout=" << c.timeout
-                             << std::endl;
+               verbosestream<<"Server: Handling peer change: "
+                               <<"id="<<c.peer_id<<", timeout="<<c.timeout
+                               <<std::endl;
 
-               switch (c.type) {
+               switch(c.type)
+               {
                case con::PEER_ADDED:
                        m_clients.CreateClient(c.peer_id);
                        break;
 
                case con::PEER_REMOVED:
-                       DeleteClient(c.peer_id, c.timeout ? CDR_TIMEOUT : CDR_LEAVE);
+                       DeleteClient(c.peer_id, c.timeout?CDR_TIMEOUT:CDR_LEAVE);
                        break;
 
                default:
@@ -1291,7 +1332,7 @@ void Server::printToConsoleOnly(const std::string &text)
 {
        if (m_admin_chat) {
                m_admin_chat->outgoing_queue.push_back(
-                               new ChatEventChat("", utf8_to_wide(text)));
+                       new ChatEventChat("", utf8_to_wide(text)));
        } else {
                std::cout << text << std::endl;
        }
@@ -1304,8 +1345,10 @@ void Server::Send(NetworkPacket *pkt)
 
 void Server::Send(session_t peer_id, NetworkPacket *pkt)
 {
-       m_clients.send(peer_id, clientCommandFactoryTable[pkt->getCommand()].channel, pkt,
-                       clientCommandFactoryTable[pkt->getCommand()].reliable);
+       m_clients.send(peer_id,
+               clientCommandFactoryTable[pkt->getCommand()].channel,
+               pkt,
+               clientCommandFactoryTable[pkt->getCommand()].reliable);
 }
 
 void Server::SendMovement(session_t peer_id)
@@ -1330,18 +1373,21 @@ void Server::SendMovement(session_t peer_id)
        Send(&pkt);
 }
 
-void Server::SendPlayerHPOrDie(PlayerSAO *playersao, const PlayerHPChangeReason &reason)
+void Server::HandlePlayerHPChange(PlayerSAO *playersao, const PlayerHPChangeReason &reason)
 {
-       if (playersao->isImmortal())
-               return;
+       m_script->player_event(playersao, "health_changed");
+       SendPlayerHP(playersao);
 
-       session_t peer_id = playersao->getPeerID();
-       bool is_alive = playersao->getHP() > 0;
+       // Send to other clients
+       playersao->sendPunchCommand();
 
-       if (is_alive)
-               SendPlayerHP(peer_id);
-       else
-               DiePlayer(peer_id, reason);
+       if (playersao->isDead())
+               HandlePlayerDeath(playersao, reason);
+}
+
+void Server::SendPlayerHP(PlayerSAO *playersao)
+{
+       SendHP(playersao->getPeerID(), playersao->getHP());
 }
 
 void Server::SendHP(session_t peer_id, u16 hp)
@@ -1354,7 +1400,7 @@ void Server::SendHP(session_t peer_id, u16 hp)
 void Server::SendBreath(session_t peer_id, u16 breath)
 {
        NetworkPacket pkt(TOCLIENT_BREATH, 2, peer_id);
-       pkt << (u16)breath;
+       pkt << (u16) breath;
        Send(&pkt);
 }
 
@@ -1373,23 +1419,16 @@ void Server::SendAccessDenied(session_t peer_id, AccessDeniedCode reason,
        Send(&pkt);
 }
 
-void Server::SendAccessDenied_Legacy(session_t peer_id, const std::wstring &reason)
-{
-       NetworkPacket pkt(TOCLIENT_ACCESS_DENIED_LEGACY, 0, peer_id);
-       pkt << reason;
-       Send(&pkt);
-}
-
-void Server::SendDeathscreen(
-               session_t peer_id, bool set_camera_point_target, v3f camera_point_target)
+void Server::SendDeathscreen(session_t peer_id, bool set_camera_point_target,
+               v3f camera_point_target)
 {
        NetworkPacket pkt(TOCLIENT_DEATHSCREEN, 1 + sizeof(v3f), peer_id);
        pkt << set_camera_point_target << camera_point_target;
        Send(&pkt);
 }
 
-void Server::SendItemDef(
-               session_t peer_id, IItemDefManager *itemdef, u16 protocol_version)
+void Server::SendItemDef(session_t peer_id,
+               IItemDefManager *itemdef, u16 protocol_version)
 {
        NetworkPacket pkt(TOCLIENT_ITEMDEF, 0, peer_id);
 
@@ -1406,13 +1445,13 @@ void Server::SendItemDef(
 
        // Make data buffer
        verbosestream << "Server: Sending item definitions to id(" << peer_id
-                     << "): size=" << pkt.getSize() << std::endl;
+                       << "): size=" << pkt.getSize() << std::endl;
 
        Send(&pkt);
 }
 
-void Server::SendNodeDef(
-               session_t peer_id, const NodeDefManager *nodedef, u16 protocol_version)
+void Server::SendNodeDef(session_t peer_id,
+       const NodeDefManager *nodedef, u16 protocol_version)
 {
        NetworkPacket pkt(TOCLIENT_NODEDEF, 0, peer_id);
 
@@ -1430,7 +1469,7 @@ void Server::SendNodeDef(
 
        // Make data buffer
        verbosestream << "Server: Sending node definitions to id(" << peer_id
-                     << "): size=" << pkt.getSize() << std::endl;
+                       << "): size=" << pkt.getSize() << std::endl;
 
        Send(&pkt);
 }
@@ -1469,8 +1508,8 @@ void Server::SendChatMessage(session_t peer_id, const ChatMessage &message)
        NetworkPacket pkt(TOCLIENT_CHAT_MESSAGE, 0, peer_id);
        u8 version = 1;
        u8 type = message.type;
-       pkt << version << type << std::wstring(L"") << message.message
-           << (u64)message.timestamp;
+       pkt << version << type << message.sender << message.message
+               << static_cast<u64>(message.timestamp);
 
        if (peer_id != PEER_ID_INEXISTENT) {
                RemotePlayer *player = m_env->getPlayer(peer_id);
@@ -1484,12 +1523,12 @@ void Server::SendChatMessage(session_t peer_id, const ChatMessage &message)
 }
 
 void Server::SendShowFormspecMessage(session_t peer_id, const std::string &formspec,
-               const std::string &formname)
+       const std::string &formname)
 {
        NetworkPacket pkt(TOCLIENT_SHOW_FORMSPEC, 0, peer_id);
-       if (formspec.empty()) {
-               // the client should close the formspec
-               // but make sure there wasn't another one open in meantime
+       if (formspec.empty()){
+               //the client should close the formspec
+               //but make sure there wasn't another one open in meantime
                const auto it = m_formspec_state_data.find(peer_id);
                if (it != m_formspec_state_data.end() && it->second == formname) {
                        m_formspec_state_data.erase(peer_id);
@@ -1505,12 +1544,11 @@ 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, const ParticleParameters &p)
+void Server::SendSpawnParticle(session_t peer_id, u16 protocol_version,
+       const ParticleParameters &p)
 {
        static thread_local const float radius =
-                       g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE *
-                       BS;
+                       g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE * BS;
 
        if (peer_id == PEER_ID_INEXISTENT) {
                std::vector<session_t> clients = m_clients.getClientIDs();
@@ -1550,11 +1588,10 @@ void Server::SendSpawnParticle(
 
 // Adds a ParticleSpawner on peer with peer_id
 void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version,
-               const ParticleSpawnerParameters &p, u16 attached_id, u32 id)
+       const ParticleSpawnerParameters &p, u16 attached_id, u32 id)
 {
        static thread_local const float radius =
-                       g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE *
-                       BS;
+                       g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE * BS;
 
        if (peer_id == PEER_ID_INEXISTENT) {
                std::vector<session_t> clients = m_clients.getClientIDs();
@@ -1573,13 +1610,12 @@ void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version,
                                PlayerSAO *sao = player->getPlayerSAO();
                                if (!sao)
                                        continue;
-                               if (sao->getBasePosition().getDistanceFromSQ(pos) >
-                                               radius_sq)
+                               if (sao->getBasePosition().getDistanceFromSQ(pos) > radius_sq)
                                        continue;
                        }
 
-                       SendAddParticleSpawner(client_id, player->protocol_version, p,
-                                       attached_id, id);
+                       SendAddParticleSpawner(client_id, player->protocol_version,
+                               p, attached_id, id);
                }
                return;
        }
@@ -1587,9 +1623,9 @@ 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 << p.minpos << p.maxpos << p.minvel
+               << p.maxvel << p.minacc << p.maxacc << p.minexptime << p.maxexptime
+               << p.minsize << p.maxsize << p.collisiondetection;
 
        pkt.putLongString(p.texture);
 
@@ -1615,16 +1651,17 @@ void Server::SendDeleteParticleSpawner(session_t peer_id, u32 id)
                Send(&pkt);
        else
                m_clients.sendToAll(&pkt);
+
 }
 
 void Server::SendHUDAdd(session_t peer_id, u32 id, HudElement *form)
 {
-       NetworkPacket pkt(TOCLIENT_HUDADD, 0, peer_id);
+       NetworkPacket pkt(TOCLIENT_HUDADD, 0 , peer_id);
 
-       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->text2;
+       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->text2 << form->style;
 
        Send(&pkt);
 }
@@ -1639,32 +1676,29 @@ void Server::SendHUDRemove(session_t peer_id, u32 id)
 void Server::SendHUDChange(session_t peer_id, u32 id, HudElementStat stat, void *value)
 {
        NetworkPacket pkt(TOCLIENT_HUDCHANGE, 0, peer_id);
-       pkt << id << (u8)stat;
+       pkt << id << (u8) stat;
 
        switch (stat) {
-       case HUD_STAT_POS:
-       case HUD_STAT_SCALE:
-       case HUD_STAT_ALIGN:
-       case HUD_STAT_OFFSET:
-               pkt << *(v2f *)value;
-               break;
-       case HUD_STAT_NAME:
-       case HUD_STAT_TEXT:
-       case HUD_STAT_TEXT2:
-               pkt << *(std::string *)value;
-               break;
-       case HUD_STAT_WORLD_POS:
-               pkt << *(v3f *)value;
-               break;
-       case HUD_STAT_SIZE:
-               pkt << *(v2s32 *)value;
-               break;
-       case HUD_STAT_NUMBER:
-       case HUD_STAT_ITEM:
-       case HUD_STAT_DIR:
-       default:
-               pkt << *(u32 *)value;
-               break;
+               case HUD_STAT_POS:
+               case HUD_STAT_SCALE:
+               case HUD_STAT_ALIGN:
+               case HUD_STAT_OFFSET:
+                       pkt << *(v2f *) value;
+                       break;
+               case HUD_STAT_NAME:
+               case HUD_STAT_TEXT:
+               case HUD_STAT_TEXT2:
+                       pkt << *(std::string *) value;
+                       break;
+               case HUD_STAT_WORLD_POS:
+                       pkt << *(v3f *) value;
+                       break;
+               case HUD_STAT_SIZE:
+                       pkt << *(v2s32 *) value;
+                       break;
+               default: // all other types
+                       pkt << *(u32 *) value;
+                       break;
        }
 
        Send(&pkt);
@@ -1694,26 +1728,26 @@ void Server::SendSetSky(session_t peer_id, const SkyboxParams &params)
 
        // Handle prior clients here
        if (m_clients.getProtocolVersion(peer_id) < 39) {
-               pkt << params.bgcolor << params.type << (u16)params.textures.size();
+               pkt << params.bgcolor << params.type << (u16) params.textures.size();
 
-               for (const std::string &texture : params.textures)
+               for (const std::stringtexture : params.textures)
                        pkt << texture;
 
                pkt << params.clouds;
        } else { // Handle current clients and future clients
-               pkt << params.bgcolor << params.type << params.clouds
-                   << params.fog_sun_tint << params.fog_moon_tint
-                   << params.fog_tint_type;
+               pkt << params.bgcolor << params.type
+               << params.clouds << params.fog_sun_tint
+               << params.fog_moon_tint << params.fog_tint_type;
 
                if (params.type == "skybox") {
-                       pkt << (u16)params.textures.size();
+                       pkt << (u16) params.textures.size();
                        for (const std::string &texture : params.textures)
                                pkt << texture;
                } else if (params.type == "regular") {
                        pkt << params.sky_color.day_sky << params.sky_color.day_horizon
-                           << params.sky_color.dawn_sky << params.sky_color.dawn_horizon
-                           << params.sky_color.night_sky
-                           << params.sky_color.night_horizon << params.sky_color.indoors;
+                               << params.sky_color.dawn_sky << params.sky_color.dawn_horizon
+                               << params.sky_color.night_sky << params.sky_color.night_horizon
+                               << params.sky_color.indoors;
                }
        }
 
@@ -1723,8 +1757,9 @@ void Server::SendSetSky(session_t peer_id, const SkyboxParams &params)
 void Server::SendSetSun(session_t peer_id, const SunParams &params)
 {
        NetworkPacket pkt(TOCLIENT_SET_SUN, 0, peer_id);
-       pkt << params.visible << params.texture << params.tonemap << params.sunrise
-           << params.sunrise_visible << params.scale;
+       pkt << params.visible << params.texture
+               << params.tonemap << params.sunrise
+               << params.sunrise_visible << params.scale;
 
        Send(&pkt);
 }
@@ -1732,7 +1767,8 @@ void Server::SendSetMoon(session_t peer_id, const MoonParams &params)
 {
        NetworkPacket pkt(TOCLIENT_SET_MOON, 0, peer_id);
 
-       pkt << params.visible << params.texture << params.tonemap << params.scale;
+       pkt << params.visible << params.texture
+               << params.tonemap << params.scale;
 
        Send(&pkt);
 }
@@ -1740,7 +1776,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;
+       pkt << params.visible << params.count
+               << params.starcolor << params.scale;
 
        Send(&pkt);
 }
@@ -1749,15 +1786,27 @@ void Server::SendCloudParams(session_t peer_id, const CloudParams &params)
 {
        NetworkPacket pkt(TOCLIENT_CLOUD_PARAMS, 0, peer_id);
        pkt << params.density << params.color_bright << params.color_ambient
-           << params.height << params.thickness << params.speed;
+                       << params.height << params.thickness << params.speed;
        Send(&pkt);
 }
 
-void Server::SendOverrideDayNightRatio(session_t peer_id, bool do_override, float ratio)
+void Server::SendOverrideDayNightRatio(session_t peer_id, bool do_override,
+               float ratio)
 {
-       NetworkPacket pkt(TOCLIENT_OVERRIDE_DAY_NIGHT_RATIO, 1 + 2, peer_id);
+       NetworkPacket pkt(TOCLIENT_OVERRIDE_DAY_NIGHT_RATIO,
+                       1 + 2, peer_id);
 
-       pkt << do_override << (u16)(ratio * 65535);
+       pkt << do_override << (u16) (ratio * 65535);
+
+       Send(&pkt);
+}
+
+void Server::SendSetLighting(session_t peer_id, const Lighting &lighting)
+{
+       NetworkPacket pkt(TOCLIENT_SET_LIGHTING,
+                       4, peer_id);
+       
+       pkt << lighting.shadow_intensity;
 
        Send(&pkt);
 }
@@ -1769,23 +1818,12 @@ void Server::SendTimeOfDay(session_t peer_id, u16 time, f32 time_speed)
 
        if (peer_id == PEER_ID_INEXISTENT) {
                m_clients.sendToAll(&pkt);
-       } else {
+       }
+       else {
                Send(&pkt);
        }
 }
 
-void Server::SendPlayerHP(session_t peer_id)
-{
-       PlayerSAO *playersao = getPlayerSAO(peer_id);
-       assert(playersao);
-
-       SendHP(peer_id, playersao->getHP());
-       m_script->player_event(playersao, "health_changed");
-
-       // Send to other clients
-       playersao->sendPunchCommand();
-}
-
 void Server::SendPlayerBreath(PlayerSAO *sao)
 {
        assert(sao);
@@ -1801,15 +1839,19 @@ void Server::SendMovePlayer(session_t peer_id)
        PlayerSAO *sao = player->getPlayerSAO();
        assert(sao);
 
+       // Send attachment updates instantly to the client prior updating position
+       sao->sendOutdatedData();
+
        NetworkPacket pkt(TOCLIENT_MOVE_PLAYER, sizeof(v3f) + sizeof(f32) * 2, peer_id);
        pkt << sao->getBasePosition() << sao->getLookPitch() << sao->getRotation().Y;
 
        {
                v3f pos = sao->getBasePosition();
                verbosestream << "Server: Sending TOCLIENT_MOVE_PLAYER"
-                             << " pos=(" << pos.X << "," << pos.Y << "," << pos.Z << ")"
-                             << " pitch=" << sao->getLookPitch()
-                             << " yaw=" << sao->getRotation().Y << std::endl;
+                               << " pos=(" << pos.X << "," << pos.Y << "," << pos.Z << ")"
+                               << " pitch=" << sao->getLookPitch()
+                               << " yaw=" << sao->getRotation().Y
+                               << std::endl;
        }
 
        Send(&pkt);
@@ -1825,13 +1867,14 @@ void Server::SendPlayerFov(session_t peer_id)
        Send(&pkt);
 }
 
-void Server::SendLocalPlayerAnimations(
-               session_t peer_id, v2s32 animation_frames[4], f32 animation_speed)
+void Server::SendLocalPlayerAnimations(session_t peer_id, v2s32 animation_frames[4],
+               f32 animation_speed)
 {
-       NetworkPacket pkt(TOCLIENT_LOCAL_PLAYER_ANIMATIONS, 0, peer_id);
+       NetworkPacket pkt(TOCLIENT_LOCAL_PLAYER_ANIMATIONS, 0,
+               peer_id);
 
        pkt << animation_frames[0] << animation_frames[1] << animation_frames[2]
-           << animation_frames[3] << animation_speed;
+                       << animation_frames[3] << animation_speed;
 
        Send(&pkt);
 }
@@ -1847,14 +1890,14 @@ void Server::SendPlayerPrivileges(session_t peer_id)
 {
        RemotePlayer *player = m_env->getPlayer(peer_id);
        assert(player);
-       if (player->getPeerId() == PEER_ID_INEXISTENT)
+       if(player->getPeerId() == PEER_ID_INEXISTENT)
                return;
 
        std::set<std::string> privs;
        m_script->getAuth(player->getName(), NULL, &privs);
 
        NetworkPacket pkt(TOCLIENT_PRIVILEGES, 0, peer_id);
-       pkt << (u16)privs.size();
+       pkt << (u16) privs.size();
 
        for (const std::string &priv : privs) {
                pkt << priv;
@@ -1892,20 +1935,18 @@ void Server::SendActiveObjectRemoveAdd(RemoteClient *client, PlayerSAO *playersa
 {
        // Radius inside which objects are active
        static thread_local const s16 radius =
-                       g_settings->getS16("active_object_send_range_blocks") *
-                       MAP_BLOCKSIZE;
+               g_settings->getS16("active_object_send_range_blocks") * MAP_BLOCKSIZE;
 
        // Radius inside which players are active
        static thread_local const bool is_transfer_limited =
-                       g_settings->exists("unlimited_player_transfer_distance") &&
-                       !g_settings->getBool("unlimited_player_transfer_distance");
+               g_settings->exists("unlimited_player_transfer_distance") &&
+               !g_settings->getBool("unlimited_player_transfer_distance");
 
        static thread_local const s16 player_transfer_dist =
-                       g_settings->getS16("player_transfer_distance") * MAP_BLOCKSIZE;
+               g_settings->getS16("player_transfer_distance") * MAP_BLOCKSIZE;
 
-       s16 player_radius = player_transfer_dist == 0 && is_transfer_limited
-                                           ? radius
-                                           : player_transfer_dist;
+       s16 player_radius = player_transfer_dist == 0 && is_transfer_limited ?
+               radius : player_transfer_dist;
 
        s16 my_radius = MYMIN(radius, playersao->getWantedRange() * MAP_BLOCKSIZE);
        if (my_radius <= 0)
@@ -1913,12 +1954,12 @@ void Server::SendActiveObjectRemoveAdd(RemoteClient *client, PlayerSAO *playersa
 
        std::queue<u16> removed_objects, added_objects;
        m_env->getRemovedActiveObjects(playersao, my_radius, player_radius,
-                       client->m_known_objects, removed_objects);
+               client->m_known_objects, removed_objects);
        m_env->getAddedActiveObjects(playersao, my_radius, player_radius,
-                       client->m_known_objects, added_objects);
+               client->m_known_objects, added_objects);
 
        int removed_count = removed_objects.size();
-       int added_count = added_objects.size();
+       int added_count   = added_objects.size();
 
        if (removed_objects.empty() && added_objects.empty())
                return;
@@ -1927,15 +1968,15 @@ void Server::SendActiveObjectRemoveAdd(RemoteClient *client, PlayerSAO *playersa
        std::string data;
 
        // Handle removed objects
-       writeU16((u8 *)buf, removed_objects.size());
+       writeU16((u8*)buf, removed_objects.size());
        data.append(buf, 2);
        while (!removed_objects.empty()) {
                // Get object
                u16 id = removed_objects.front();
-               ServerActiveObject *obj = m_env->getActiveObject(id);
+               ServerActiveObjectobj = m_env->getActiveObject(id);
 
                // Add to data buffer for sending
-               writeU16((u8 *)buf, id);
+               writeU16((u8*)buf, id);
                data.append(buf, 2);
 
                // Remove from known objects
@@ -1948,7 +1989,7 @@ void Server::SendActiveObjectRemoveAdd(RemoteClient *client, PlayerSAO *playersa
        }
 
        // Handle added objects
-       writeU16((u8 *)buf, added_objects.size());
+       writeU16((u8*)buf, added_objects.size());
        data.append(buf, 2);
        while (!added_objects.empty()) {
                // Get object
@@ -1957,8 +1998,8 @@ void Server::SendActiveObjectRemoveAdd(RemoteClient *client, PlayerSAO *playersa
                added_objects.pop();
 
                if (!obj) {
-                       warningstream << FUNCTION_NAME << ": NULL object id=" << (int)id
-                                     << std::endl;
+                       warningstream << FUNCTION_NAME << ": NULL object id="
+                               << (int)id << std::endl;
                        continue;
                }
 
@@ -1966,13 +2007,13 @@ void Server::SendActiveObjectRemoveAdd(RemoteClient *client, PlayerSAO *playersa
                u8 type = obj->getSendType();
 
                // Add to data buffer for sending
-               writeU16((u8 *)buf, id);
+               writeU16((u8*)buf, id);
                data.append(buf, 2);
-               writeU8((u8 *)buf, type);
+               writeU8((u8*)buf, type);
                data.append(buf, 1);
 
-               data.append(serializeLongString(obj->getClientInitializationData(
-                               client->net_proto_version)));
+               data.append(serializeString32(
+                       obj->getClientInitializationData(client->net_proto_version)));
 
                // Add to known objects
                client->m_known_objects.insert(id);
@@ -1980,35 +2021,32 @@ void Server::SendActiveObjectRemoveAdd(RemoteClient *client, PlayerSAO *playersa
                obj->m_known_by_count++;
        }
 
-       NetworkPacket pkt(
-                       TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD, data.size(), client->peer_id);
+       NetworkPacket pkt(TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD, data.size(), client->peer_id);
        pkt.putRawString(data.c_str(), data.size());
        Send(&pkt);
 
-       verbosestream << "Server::SendActiveObjectRemoveAdd: " << removed_count
-                     << " removed, " << added_count << " added, "
-                     << "packet size is " << pkt.getSize() << std::endl;
+       verbosestream << "Server::SendActiveObjectRemoveAdd: "
+               << removed_count << " removed, " << added_count << " added, "
+               << "packet size is " << pkt.getSize() << std::endl;
 }
 
-void Server::SendActiveObjectMessages(
-               session_t peer_id, const std::string &datas, bool reliable)
+void Server::SendActiveObjectMessages(session_t peer_id, const std::string &datas,
+               bool reliable)
 {
-       NetworkPacket pkt(TOCLIENT_ACTIVE_OBJECT_MESSAGES, datas.size(), peer_id);
+       NetworkPacket pkt(TOCLIENT_ACTIVE_OBJECT_MESSAGES,
+                       datas.size(), peer_id);
 
        pkt.putRawString(datas.c_str(), datas.size());
 
        m_clients.send(pkt.getPeerId(),
-                       reliable ? clientCommandFactoryTable[pkt.getCommand()].channel
-                                : 1,
+                       reliable ? clientCommandFactoryTable[pkt.getCommand()].channel : 1,
                        &pkt, reliable);
 }
 
 void Server::SendCSMRestrictionFlags(session_t peer_id)
 {
        NetworkPacket pkt(TOCLIENT_CSM_RESTRICTION_FLAGS,
-                       sizeof(m_csm_restriction_flags) +
-                                       sizeof(m_csm_restriction_noderange),
-                       peer_id);
+               sizeof(m_csm_restriction_flags) + sizeof(m_csm_restriction_noderange), peer_id);
        pkt << m_csm_restriction_flags << m_csm_restriction_noderange;
        Send(&pkt);
 }
@@ -2030,28 +2068,28 @@ inline s32 Server::nextSoundId()
        return ret;
 }
 
-s32 Server::playSound(const SimpleSoundSpec &spec, const ServerSoundParams &params,
-               bool ephemeral)
+s32 Server::playSound(const SimpleSoundSpec &spec,
+               const ServerSoundParams &params, bool ephemeral)
 {
        // Find out initial position of sound
        bool pos_exists = false;
        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 != ServerSoundParams::SSP_LOCAL))
                return -1;
 
        // Filter destination clients
        std::vector<session_t> dst_clients;
        if (!params.to_player.empty()) {
                RemotePlayer *player = m_env->getPlayer(params.to_player.c_str());
-               if (!player) {
-                       infostream << "Server::playSound: Player \"" << params.to_player
-                                  << "\" not found" << std::endl;
+               if(!player){
+                       infostream<<"Server::playSound: Player \""<<params.to_player
+                                       <<"\" not found"<<std::endl;
                        return -1;
                }
                if (player->getPeerId() == PEER_ID_INEXISTENT) {
-                       infostream << "Server::playSound: Player \"" << params.to_player
-                                  << "\" not connected" << std::endl;
+                       infostream<<"Server::playSound: Player \""<<params.to_player
+                                       <<"\" not connected"<<std::endl;
                        return -1;
                }
                dst_clients.push_back(player->getPeerId());
@@ -2071,7 +2109,7 @@ s32 Server::playSound(const SimpleSoundSpec &spec, const ServerSoundParams &para
                                continue;
 
                        if (pos_exists) {
-                               if (sao->getBasePosition().getDistanceFrom(pos) >
+                               if(sao->getBasePosition().getDistanceFrom(pos) >
                                                params.max_hear_distance)
                                        continue;
                        }
@@ -2079,7 +2117,7 @@ s32 Server::playSound(const SimpleSoundSpec &spec, const ServerSoundParams &para
                }
        }
 
-       if (dst_clients.empty())
+       if(dst_clients.empty())
                return -1;
 
        // Create the sound
@@ -2098,8 +2136,10 @@ s32 Server::playSound(const SimpleSoundSpec &spec, const ServerSoundParams &para
 
        float gain = params.gain * spec.gain;
        NetworkPacket pkt(TOCLIENT_PLAY_SOUND, 0);
-       pkt << id << spec.name << gain << (u8)params.type << pos << params.object
-           << params.loop << params.fade << params.pitch << ephemeral;
+       pkt << id << spec.name << gain
+                       << (u8) params.type << pos << params.object
+                       << params.loop << params.fade << params.pitch
+                       << ephemeral;
 
        bool as_reliable = !ephemeral;
 
@@ -2114,7 +2154,7 @@ void Server::stopSound(s32 handle)
 {
        // Get sound reference
        std::unordered_map<s32, ServerPlayingSound>::iterator i =
-                       m_playing_sounds.find(handle);
+               m_playing_sounds.find(handle);
        if (i == m_playing_sounds.end())
                return;
        ServerPlayingSound &psound = i->second;
@@ -2135,7 +2175,7 @@ void Server::fadeSound(s32 handle, float step, float gain)
 {
        // Get sound reference
        std::unordered_map<s32, ServerPlayingSound>::iterator i =
-                       m_playing_sounds.find(handle);
+               m_playing_sounds.find(handle);
        if (i == m_playing_sounds.end())
                return;
 
@@ -2177,8 +2217,8 @@ void Server::fadeSound(s32 handle, float step, float gain)
        }
 }
 
-void Server::sendRemoveNode(
-               v3s16 p, std::unordered_set<u16> *far_players, float far_d_nodes)
+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);
@@ -2188,7 +2228,7 @@ void Server::sendRemoveNode(
        pkt << p;
 
        std::vector<session_t> clients = m_clients.getClientIDs();
-       m_clients.lock();
+       ClientInterface::AutoLock clientlock(m_clients);
 
        for (session_t client_id : clients) {
                RemoteClient *client = m_clients.lockedGetClientNoEx(client_id);
@@ -2199,9 +2239,8 @@ void Server::sendRemoveNode(
                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 (!client->isBlockSent(block_pos) || (sao &&
+                               sao->getBasePosition().getDistanceFrom(p_f) > maxd)) {
                        if (far_players)
                                far_players->emplace(client_id);
                        else
@@ -2212,8 +2251,6 @@ void Server::sendRemoveNode(
                // Send as reliable
                m_clients.send(client_id, 0, &pkt, true);
        }
-
-       m_clients.unlock();
 }
 
 void Server::sendAddNode(v3s16 p, MapNode n, std::unordered_set<u16> *far_players,
@@ -2224,10 +2261,11 @@ void Server::sendAddNode(v3s16 p, MapNode n, std::unordered_set<u16> *far_player
        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);
+       pkt << p << n.param0 << n.param1 << n.param2
+                       << (u8) (remove_metadata ? 0 : 1);
 
        std::vector<session_t> clients = m_clients.getClientIDs();
-       m_clients.lock();
+       ClientInterface::AutoLock clientlock(m_clients);
 
        for (session_t client_id : clients) {
                RemoteClient *client = m_clients.lockedGetClientNoEx(client_id);
@@ -2238,9 +2276,8 @@ void Server::sendAddNode(v3s16 p, MapNode n, std::unordered_set<u16> *far_player
                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 (!client->isBlockSent(block_pos) || (sao &&
+                               sao->getBasePosition().getDistanceFrom(p_f) > maxd)) {
                        if (far_players)
                                far_players->emplace(client_id);
                        else
@@ -2251,8 +2288,6 @@ void Server::sendAddNode(v3s16 p, MapNode n, std::unordered_set<u16> *far_player
                // Send as reliable
                m_clients.send(client_id, 0, &pkt, true);
        }
-
-       m_clients.unlock();
 }
 
 void Server::sendMetadataChanged(const std::list<v3s16> &meta_updates, float far_d_nodes)
@@ -2261,7 +2296,7 @@ void Server::sendMetadataChanged(const std::list<v3s16> &meta_updates, float far
        NodeMetadataList meta_updates_list(false);
        std::vector<session_t> clients = m_clients.getClientIDs();
 
-       m_clients.lock();
+       ClientInterface::AutoLock clientlock(m_clients);
 
        for (session_t i : clients) {
                RemoteClient *client = m_clients.lockedGetClientNoEx(i);
@@ -2278,9 +2313,8 @@ void Server::sendMetadataChanged(const std::list<v3s16> &meta_updates, float far
                                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 &&
+                                       player_pos.getDistanceFrom(intToFloat(pos, BS)) > maxd)) {
                                client->SetBlockNotSent(block_pos);
                                continue;
                        }
@@ -2293,7 +2327,7 @@ void Server::sendMetadataChanged(const std::list<v3s16> &meta_updates, float far
 
                // Send the meta changes
                std::ostringstream os(std::ios::binary);
-               meta_updates_list.serialize(os, client->net_proto_version, false, true);
+               meta_updates_list.serialize(os, client->serialization_version, false, true, true);
                std::ostringstream oss(std::ios::binary);
                compressZlib(os.str(), oss);
 
@@ -2303,55 +2337,65 @@ void Server::sendMetadataChanged(const std::list<v3s16> &meta_updates, float far
 
                meta_updates_list.clear();
        }
-
-       m_clients.unlock();
 }
 
-void Server::SendBlockNoLock(
-               session_t peer_id, MapBlock *block, u8 ver, u16 net_proto_version)
+void Server::SendBlockNoLock(session_t peer_id, MapBlock *block, u8 ver,
+               u16 net_proto_version, SerializedBlockCache *cache)
 {
-       /*
-               Create a packet with the block in the right format
-       */
+       thread_local const int net_compression_level = rangelim(g_settings->getS16("map_compression_level_net"), -1, 9);
+       std::string s, *sptr = nullptr;
 
-       std::ostringstream os(std::ios_base::binary);
-       block->serialize(os, ver, false);
-       block->serializeNetworkSpecific(os);
-       std::string s = os.str();
+       if (cache) {
+               auto it = cache->find({block->getPos(), ver});
+               if (it != cache->end())
+                       sptr = &it->second;
+       }
 
-       NetworkPacket pkt(TOCLIENT_BLOCKDATA, 2 + 2 + 2 + 2 + s.size(), peer_id);
+       // Serialize the block in the right format
+       if (!sptr) {
+               std::ostringstream os(std::ios_base::binary);
+               block->serialize(os, ver, false, net_compression_level);
+               block->serializeNetworkSpecific(os);
+               s = os.str();
+               sptr = &s;
+       }
 
+       NetworkPacket pkt(TOCLIENT_BLOCKDATA, 2 + 2 + 2 + sptr->size(), peer_id);
        pkt << block->getPos();
-       pkt.putRawString(s.c_str(), s.size());
+       pkt.putRawString(*sptr);
        Send(&pkt);
+
+       // Store away in cache
+       if (cache && sptr == &s)
+               (*cache)[{block->getPos(), ver}] = std::move(s);
 }
 
 void Server::SendBlocks(float dtime)
 {
        MutexAutoLock envlock(m_env_mutex);
-       // TODO check if one big lock could be faster then multiple small ones
+       //TODO check if one big lock could be faster then multiple small ones
 
        std::vector<PrioritySortedBlockTransfer> queue;
 
-       u32 total_sending = 0;
+       u32 total_sending = 0, unique_clients = 0;
 
        {
                ScopeProfiler sp2(g_profiler, "Server::SendBlocks(): Collect list");
 
                std::vector<session_t> clients = m_clients.getClientIDs();
 
-               m_clients.lock();
+               ClientInterface::AutoLock clientlock(m_clients);
                for (const session_t client_id : clients) {
-                       RemoteClient *client = m_clients.lockedGetClientNoEx(
-                                       client_id, CS_Active);
+                       RemoteClient *client = m_clients.lockedGetClientNoEx(client_id, CS_Active);
 
                        if (!client)
                                continue;
 
                        total_sending += client->getSendingCount();
-                       client->GetNextBlocks(m_env, m_emerge, dtime, queue);
+                       const auto old_count = queue.size();
+                       client->GetNextBlocks(m_env,m_emerge, dtime, queue);
+                       unique_clients += queue.size() > old_count ? 1 : 0;
                }
-               m_clients.unlock();
        }
 
        // Sort.
@@ -2359,20 +2403,22 @@ void Server::SendBlocks(float dtime)
        // Lowest is most important.
        std::sort(queue.begin(), queue.end());
 
-       m_clients.lock();
+       ClientInterface::AutoLock clientlock(m_clients);
 
        // Maximal total count calculation
        // The per-client block sends is halved with the maximal online users
-       u32 max_blocks_to_send =
-                       (m_env->getPlayerCount() + g_settings->getU32("max_users")) *
-                                       g_settings->getU32("max_simultaneous_block_sends_"
-                                                          "per_client") /
-                                       4 +
-                       1;
+       u32 max_blocks_to_send = (m_env->getPlayerCount() + g_settings->getU32("max_users")) *
+               g_settings->getU32("max_simultaneous_block_sends_per_client") / 4 + 1;
 
        ScopeProfiler sp(g_profiler, "Server::SendBlocks(): Send to clients");
        Map &map = m_env->getMap();
 
+       SerializedBlockCache cache, *cache_ptr = nullptr;
+       if (unique_clients > 1) {
+               // caching is pointless with a single client
+               cache_ptr = &cache;
+       }
+
        for (const PrioritySortedBlockTransfer &block_to_send : queue) {
                if (total_sending >= max_blocks_to_send)
                        break;
@@ -2381,18 +2427,17 @@ void Server::SendBlocks(float dtime)
                if (!block)
                        continue;
 
-               RemoteClient *client = m_clients.lockedGetClientNoEx(
-                               block_to_send.peer_id, CS_Active);
+               RemoteClient *client = m_clients.lockedGetClientNoEx(block_to_send.peer_id,
+                               CS_Active);
                if (!client)
                        continue;
 
-               SendBlockNoLock(block_to_send.peer_id, block,
-                               client->serialization_version, client->net_proto_version);
+               SendBlockNoLock(block_to_send.peer_id, block, client->serialization_version,
+                               client->net_proto_version, cache_ptr);
 
                client->SentBlock(block_to_send.pos);
                total_sending++;
        }
-       m_clients.unlock();
 }
 
 bool Server::SendBlock(session_t peer_id, const v3s16 &blockpos)
@@ -2401,68 +2446,53 @@ bool Server::SendBlock(session_t peer_id, const v3s16 &blockpos)
        if (!block)
                return false;
 
-       m_clients.lock();
+       ClientInterface::AutoLock clientlock(m_clients);
        RemoteClient *client = m_clients.lockedGetClientNoEx(peer_id, CS_Active);
-       if (!client || client->isBlockSent(blockpos)) {
-               m_clients.unlock();
+       if (!client || client->isBlockSent(blockpos))
                return false;
-       }
        SendBlockNoLock(peer_id, block, client->serialization_version,
                        client->net_proto_version);
-       m_clients.unlock();
 
        return true;
 }
 
-bool Server::addMediaFile(const std::string &filename, const std::string &filepath,
-               std::string *filedata_to, std::string *digest_to)
+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;
+               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};
+       const char *supported_ext[] = {
+               ".png", ".jpg", ".bmp", ".tga",
+               ".ogg",
+               ".x", ".b3d", ".obj",
+               // Custom translation file format
+               ".tr",
+               NULL
+       };
        if (removeStringEnd(filename, supported_ext).empty()) {
                infostream << "Server: ignoring unsupported file extension: \""
-                          << filename << "\"" << std::endl;
+                               << 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;
+       if (!fs::ReadFile(filepath, filedata)) {
+               errorstream << "Server::addMediaFile(): Failed to open \""
+                                       << filename << "\" for reading" << std::endl;
                return false;
-       } else if (filedata.empty()) {
-               errorstream << "Server::addMediaFile(): Empty file \"" << filepath << "\""
-                           << std::endl;
+       }
+
+       if (filedata.empty()) {
+               errorstream << "Server::addMediaFile(): Empty file \""
+                               << filepath << "\"" << std::endl;
                return false;
        }
 
@@ -2471,14 +2501,15 @@ bool Server::addMediaFile(const std::string &filename, const std::string &filepa
 
        unsigned char *digest = sha1.getDigest();
        std::string sha1_base64 = base64_encode(digest, 20);
-       std::string sha1_hex = hex_encode((char *)digest, 20);
+       std::string sha1_hex = hex_encode((char*) digest, 20);
        if (digest_to)
-               *digest_to = std::string((char *)digest, 20);
+               *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;
+       verbosestream << "Server: " << sha1_hex << " is " << filename
+                       << std::endl;
 
        if (filedata_to)
                *filedata_to = std::move(filedata);
@@ -2491,25 +2522,31 @@ void Server::fillMediaCache()
 
        // Collect all media file paths
        std::vector<std::string> paths;
-       m_modmgr->getModsMediaPaths(paths);
+
+       // ordered in descending priority
+       paths.push_back(getBuiltinLuaPath() + DIR_DELIM + "locale");
+       fs::GetRecursiveDirs(paths, porting::path_user + DIR_DELIM + "textures" + DIR_DELIM + "server");
        fs::GetRecursiveDirs(paths, m_gamespec.path + DIR_DELIM + "textures");
-       fs::GetRecursiveDirs(paths, porting::path_user + DIR_DELIM + "textures" +
-                                                   DIR_DELIM + "server");
+       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) // Ignore dirs
+                       if (dln.dir) // Ignore dirs (already in paths)
+                               continue;
+
+                       const std::string &filename = dln.name;
+                       if (m_media.find(filename) != m_media.end()) // Do not override
                                continue;
+
                        std::string filepath = mediapath;
-                       filepath.append(DIR_DELIM).append(dln.name);
-                       addMediaFile(dln.name, filepath);
+                       filepath.append(DIR_DELIM).append(filename);
+                       addMediaFile(filename, filepath);
                }
        }
 
-       infostream << "Server: " << m_media.size() << " media files collected"
-                  << std::endl;
+       infostream << "Server: " << m_media.size() << " media files collected" << std::endl;
 }
 
 void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_code)
@@ -2521,6 +2558,8 @@ void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_co
        std::string lang_suffix;
        lang_suffix.append(".").append(lang_code).append(".tr");
        for (const auto &i : m_media) {
+               if (i.second.no_announce)
+                       continue;
                if (str_ends_with(i.first, ".tr") && !str_ends_with(i.first, lang_suffix))
                        continue;
                media_sent++;
@@ -2529,6 +2568,8 @@ void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_co
        pkt << media_sent;
 
        for (const auto &i : m_media) {
+               if (i.second.no_announce)
+                       continue;
                if (str_ends_with(i.first, ".tr") && !str_ends_with(i.first, lang_suffix))
                        continue;
                pkt << i.first << i.second.sha1_digest;
@@ -2538,8 +2579,7 @@ void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_co
        Send(&pkt);
 
        verbosestream << "Server: Announcing files to id(" << peer_id
-                     << "): count=" << media_sent << " size=" << pkt.getSize()
-                     << std::endl;
+               << "): count=" << media_sent << " size=" << pkt.getSize() << std::endl;
 }
 
 struct SendableMedia
@@ -2548,77 +2588,55 @@ struct SendableMedia
        std::string path;
        std::string data;
 
-       SendableMedia(const std::string &name_ = "", const std::string &path_ = "",
-                       const std::string &data_ = "") :
-                       name(name_),
-                       path(path_), data(data_)
-       {
-       }
+       SendableMedia(const std::string &name, const std::string &path,
+                       std::string &&data):
+               name(name), path(path), data(std::move(data))
+       {}
 };
 
-void Server::sendRequestedMedia(session_t peer_id, const std::vector<std::string> &tosend)
+void Server::sendRequestedMedia(session_t peer_id,
+               const std::vector<std::string> &tosend)
 {
-       verbosestream << "Server::sendRequestedMedia(): "
-                     << "Sending files to client" << std::endl;
+       verbosestream<<"Server::sendRequestedMedia(): "
+                       <<"Sending files to client"<<std::endl;
 
        /* Read files */
 
        // Put 5kB in one bunch (this is not accurate)
        u32 bytes_per_bunch = 5000;
 
-       std::vector<std::vector<SendableMedia>> file_bunches;
+       std::vector< std::vector<SendableMedia> > file_bunches;
        file_bunches.emplace_back();
 
        u32 file_size_bunch_total = 0;
 
        for (const std::string &name : tosend) {
                if (m_media.find(name) == m_media.end()) {
-                       errorstream << "Server::sendRequestedMedia(): Client asked for "
-                                   << "unknown file \"" << (name) << "\"" << std::endl;
+                       errorstream<<"Server::sendRequestedMedia(): Client asked for "
+                                       <<"unknown file \""<<(name)<<"\""<<std::endl;
                        continue;
                }
 
-               // TODO get path + name
-               std::string tpath = m_media[name].path;
+               const auto &m = m_media[name];
 
                // Read data
-               std::ifstream fis(tpath.c_str(), std::ios_base::binary);
-               if (!fis.good()) {
-                       errorstream << "Server::sendRequestedMedia(): Could not open \""
-                                   << tpath << "\" 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);
-                       file_size_bunch_total += len;
-                       if (fis.eof())
-                               break;
-                       if (!fis.good()) {
-                               bad = true;
-                               break;
-                       }
-               }
-               if (bad) {
+               std::string data;
+               if (!fs::ReadFile(m.path, data)) {
                        errorstream << "Server::sendRequestedMedia(): Failed to read \""
-                                   << name << "\"" << std::endl;
+                                       << name << "\"" << std::endl;
                        continue;
                }
-               /*infostream<<"Server::sendRequestedMedia(): Loaded \""
-                               <<tname<<"\""<<std::endl;*/
+               file_size_bunch_total += data.size();
+
                // Put in list
-               file_bunches[file_bunches.size() - 1].emplace_back(
-                               name, tpath, tmp_os.str());
+               file_bunches.back().emplace_back(name, m.path, std::move(data));
 
                // Start next bunch if got enough data
-               if (file_size_bunch_total >= bytes_per_bunch) {
+               if(file_size_bunch_total >= bytes_per_bunch) {
                        file_bunches.emplace_back();
                        file_size_bunch_total = 0;
                }
+
        }
 
        /* Create and send packets */
@@ -2639,22 +2657,66 @@ void Server::sendRequestedMedia(session_t peer_id, const std::vector<std::string
                */
 
                NetworkPacket pkt(TOCLIENT_MEDIA, 4 + 0, peer_id);
-               pkt << num_bunches << i << (u32)file_bunches[i].size();
+               pkt << num_bunches << i << (u32) file_bunches[i].size();
 
                for (const SendableMedia &j : file_bunches[i]) {
                        pkt << j.name;
                        pkt.putLongString(j.data);
                }
 
-               verbosestream << "Server::sendRequestedMedia(): bunch " << i << "/"
-                             << num_bunches << " files=" << file_bunches[i].size()
-                             << " size=" << pkt.getSize() << std::endl;
+               verbosestream << "Server::sendRequestedMedia(): bunch "
+                               << i << "/" << num_bunches
+                               << " files=" << file_bunches[i].size()
+                               << " size="  << pkt.getSize() << std::endl;
                Send(&pkt);
        }
 }
 
-void Server::sendDetachedInventory(
-               Inventory *inventory, const std::string &name, session_t peer_id)
+void Server::stepPendingDynMediaCallbacks(float dtime)
+{
+       MutexAutoLock lock(m_env_mutex);
+
+       for (auto it = m_pending_dyn_media.begin(); it != m_pending_dyn_media.end();) {
+               it->second.expiry_timer -= dtime;
+               bool del = it->second.waiting_players.empty() || it->second.expiry_timer < 0;
+
+               if (!del) {
+                       it++;
+                       continue;
+               }
+
+               const auto &name = it->second.filename;
+               if (!name.empty()) {
+                       assert(m_media.count(name));
+                       // if no_announce isn't set we're definitely deleting the wrong file!
+                       sanity_check(m_media[name].no_announce);
+
+                       fs::DeleteSingleFileOrEmptyDirectory(m_media[name].path);
+                       m_media.erase(name);
+               }
+               getScriptIface()->freeDynamicMediaCallback(it->first);
+               it = m_pending_dyn_media.erase(it);
+       }
+}
+
+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);
        pkt << name;
@@ -2670,8 +2732,7 @@ void Server::sendDetachedInventory(
                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
+               pkt << static_cast<u16>(os_str.size()); // HACK: to keep compatibility with 5.0.0 clients
                pkt.putRawString(os_str);
        }
 
@@ -2700,22 +2761,18 @@ void Server::sendDetachedInventories(session_t peer_id, bool incremental)
        Something random
 */
 
-void Server::DiePlayer(session_t peer_id, const PlayerHPChangeReason &reason)
+void Server::HandlePlayerDeath(PlayerSAO *playersao, const PlayerHPChangeReason &reason)
 {
-       PlayerSAO *playersao = getPlayerSAO(peer_id);
-       assert(playersao);
-
-       infostream << "Server::DiePlayer(): Player " << playersao->getPlayer()->getName()
-                  << " dies" << std::endl;
+       infostream << "Server::DiePlayer(): Player "
+                       << playersao->getPlayer()->getName()
+                       << " dies" << std::endl;
 
-       playersao->setHP(0, reason);
        playersao->clearParentAttachment();
 
        // Trigger scripted stuff
        m_script->on_dieplayer(playersao, reason);
 
-       SendPlayerHP(peer_id);
-       SendDeathscreen(peer_id, false, v3f(0, 0, 0));
+       SendDeathscreen(playersao->getPeerID(), false, v3f(0,0,0));
 }
 
 void Server::RespawnPlayer(session_t peer_id)
@@ -2724,7 +2781,8 @@ void Server::RespawnPlayer(session_t peer_id)
        assert(playersao);
 
        infostream << "Server::RespawnPlayer(): Player "
-                  << playersao->getPlayer()->getName() << " respawns" << std::endl;
+                       << playersao->getPlayer()->getName()
+                       << " respawns" << std::endl;
 
        playersao->setHP(playersao->accessObjectProperties()->hp_max,
                        PlayerHPChangeReason(PlayerHPChangeReason::RESPAWN));
@@ -2735,38 +2793,20 @@ void Server::RespawnPlayer(session_t peer_id)
                // setPos will send the new position to client
                playersao->setPos(findSpawnPos());
        }
-
-       SendPlayerHP(peer_id);
 }
 
+
 void Server::DenySudoAccess(session_t peer_id)
 {
        NetworkPacket pkt(TOCLIENT_DENY_SUDO_MODE, 0, peer_id);
        Send(&pkt);
 }
 
-void Server::DenyAccessVerCompliant(session_t peer_id, u16 proto_ver,
-               AccessDeniedCode reason, const std::string &str_reason, bool reconnect)
-{
-       SendAccessDenied(peer_id, reason, str_reason, reconnect);
-
-       m_clients.event(peer_id, CSE_SetDenied);
-       DisconnectPeer(peer_id);
-}
 
 void Server::DenyAccess(session_t peer_id, AccessDeniedCode reason,
-               const std::string &custom_reason)
-{
-       SendAccessDenied(peer_id, reason, custom_reason);
-       m_clients.event(peer_id, CSE_SetDenied);
-       DisconnectPeer(peer_id);
-}
-
-// 13/03/15: remove this function when protocol version 25 will become
-// the minimum version for MT users, maybe in 1 year
-void Server::DenyAccess_Legacy(session_t peer_id, const std::wstring &reason)
+               const std::string &custom_reason, bool reconnect)
 {
-       SendAccessDenied_Legacy(peer_id, reason);
+       SendAccessDenied(peer_id, reason, custom_reason, reconnect);
        m_clients.event(peer_id, CSE_SetDenied);
        DisconnectPeer(peer_id);
 }
@@ -2780,7 +2820,7 @@ void Server::DisconnectPeer(session_t peer_id)
 void Server::acceptAuth(session_t peer_id, bool forSudoMode)
 {
        if (!forSudoMode) {
-               RemoteClient *client = getClient(peer_id, CS_Invalid);
+               RemoteClientclient = getClient(peer_id, CS_Invalid);
 
                NetworkPacket resp_pkt(TOCLIENT_AUTH_ACCEPT, 1 + 6 + 8 + 4, peer_id);
 
@@ -2788,9 +2828,9 @@ void Server::acceptAuth(session_t peer_id, bool forSudoMode)
                u32 sudo_auth_mechs = client->allowed_auth_mechs;
                client->allowed_sudo_mechs = sudo_auth_mechs;
 
-               resp_pkt << v3f(0, 0, 0) << (u64)m_env->getServerMap().getSeed()
-                        << g_settings->getFloat("dedicated_server_step")
-                        << sudo_auth_mechs;
+               resp_pkt << v3f(0,0,0) << (u64) m_env->getServerMap().getSeed()
+                               << g_settings->getFloat("dedicated_server_step")
+                               << sudo_auth_mechs;
 
                Send(&resp_pkt);
                m_clients.event(peer_id, CSE_AuthAccept);
@@ -2813,9 +2853,8 @@ void Server::DeleteClient(session_t peer_id, ClientDeletionReason reason)
                /*
                        Clear references to playing sounds
                */
-               for (std::unordered_map<s32, ServerPlayingSound>::iterator i =
-                                               m_playing_sounds.begin();
-                               i != m_playing_sounds.end();) {
+               for (std::unordered_map<s32, ServerPlayingSound>::iterator
+                                i = m_playing_sounds.begin(); i != m_playing_sounds.end();) {
                        ServerPlayingSound &psound = i->second;
                        psound.clients.erase(peer_id);
                        if (psound.clients.empty())
@@ -2839,11 +2878,9 @@ void Server::DeleteClient(session_t peer_id, ClientDeletionReason reason)
 
                        // inform connected clients
                        const std::string &player_name = player->getName();
-                       NetworkPacket notice(TOCLIENT_UPDATE_PLAYER_LIST, 0,
-                                       PEER_ID_INEXISTENT);
-                       // (u16) 1 + std::string represents a vector serialization
-                       // representation
-                       notice << (u8)PLAYER_LIST_REMOVE << (u16)1 << player_name;
+                       NetworkPacket notice(TOCLIENT_UPDATE_PLAYER_LIST, 0, PEER_ID_INEXISTENT);
+                       // (u16) 1 + std::string represents a vector serialization representation
+                       notice << (u8) PLAYER_LIST_REMOVE  << (u16) 1 << player_name;
                        m_clients.sendToAll(&notice);
                        // run scripts
                        m_script->on_leaveplayer(playersao, reason == CDR_TIMEOUT);
@@ -2861,8 +2898,7 @@ void Server::DeleteClient(session_t peer_id, ClientDeletionReason reason)
 
                                for (const session_t client_id : clients) {
                                        // Get player
-                                       RemotePlayer *player =
-                                                       m_env->getPlayer(client_id);
+                                       RemotePlayer *player = m_env->getPlayer(client_id);
                                        if (!player)
                                                continue;
 
@@ -2872,14 +2908,11 @@ void Server::DeleteClient(session_t peer_id, ClientDeletionReason reason)
 
                                std::string name = player->getName();
                                actionstream << name << " "
-                                            << (reason == CDR_TIMEOUT ? "times out."
-                                                                      : "leaves game.")
-                                            << " List of players: " << os.str()
-                                            << std::endl;
+                                               << (reason == CDR_TIMEOUT ? "times out." : "leaves game.")
+                                               << " List of players: " << os.str() << std::endl;
                                if (m_admin_chat)
                                        m_admin_chat->outgoing_queue.push_back(
-                                                       new ChatEventNick(CET_NICK_REMOVE,
-                                                                       name));
+                                               new ChatEventNick(CET_NICK_REMOVE, name));
                        }
                }
                {
@@ -2910,8 +2943,8 @@ void Server::UpdateCrafting(RemotePlayer *player)
        loc.setPlayer(player->getName());
        std::vector<ItemStack> output_replacements;
        getCraftingResult(&player->inventory, preview, output_replacements, false, this);
-       m_env->getScriptIface()->item_CraftPredict(
-                       preview, player->getPlayerSAO(), clist, loc);
+       m_env->getScriptIface()->item_CraftPredict(preview, player->getPlayerSAO(),
+                       clist, loc);
 
        InventoryList *plist = player->inventory.getList("craftpreview");
        if (plist && plist->getSize() >= 1) {
@@ -2927,17 +2960,11 @@ void Server::handleChatInterfaceEvent(ChatEvent *evt)
                m_admin_nick = ((ChatEventNick *)evt)->nick;
                if (!m_script->getAuth(m_admin_nick, NULL, NULL)) {
                        errorstream << "You haven't set up an account." << std::endl
-                                   << "Please log in using the client as '"
-                                   << m_admin_nick << "' with a secure password."
-                                   << std::endl
-                                   << "Until then, you can't execute admin tasks via "
-                                      "the console,"
-                                   << std::endl
-                                   << "and everybody can claim the user account instead "
-                                      "of you,"
-                                   << std::endl
-                                   << "giving them full control over this server."
-                                   << std::endl;
+                               << "Please log in using the client as '"
+                               << m_admin_nick << "' with a secure password." << std::endl
+                               << "Until then, you can't execute admin tasks via the console," << std::endl
+                               << "and everybody can claim the user account instead of you," << std::endl
+                               << "giving them full control over this server." << std::endl;
                }
        } else {
                assert(evt->type == CET_CHAT);
@@ -2945,11 +2972,12 @@ void Server::handleChatInterfaceEvent(ChatEvent *evt)
        }
 }
 
-std::wstring Server::handleChat(const std::string &name, const std::wstring &wname,
-               std::wstring wmessage, bool check_shout_priv, RemotePlayer *player)
+std::wstring Server::handleChat(const std::string &name,
+       std::wstring wmessage, bool check_shout_priv, RemotePlayer *player)
 {
        // If something goes wrong, this player is to blame
-       RollbackScopeActor rollback_scope(m_rollback, std::string("player:") + name);
+       RollbackScopeActor rollback_scope(m_rollback,
+                       std::string("player:") + name);
 
        if (g_settings->getBool("strip_color_codes"))
                wmessage = unescape_enriched(wmessage);
@@ -2959,13 +2987,13 @@ std::wstring Server::handleChat(const std::string &name, const std::wstring &wna
                case RPLAYER_CHATRESULT_FLOODING: {
                        std::wstringstream ws;
                        ws << L"You cannot send more messages. You are limited to "
-                          << g_settings->getFloat("chat_message_limit_per_10sec")
-                          << L" messages per 10 seconds.";
+                                       << g_settings->getFloat("chat_message_limit_per_10sec")
+                                       << L" messages per 10 seconds.";
                        return ws.str();
                }
                case RPLAYER_CHATRESULT_KICK:
-                       DenyAccess_Legacy(player->getPeerId(),
-                                       L"You have been kicked due to message flooding.");
+                       DenyAccess(player->getPeerId(), SERVER_ACCESSDENIED_CUSTOM_STRING,
+                               "You have been kicked due to message flooding.");
                        return L"";
                case RPLAYER_CHATRESULT_OK:
                        break;
@@ -2974,16 +3002,18 @@ std::wstring Server::handleChat(const std::string &name, const std::wstring &wna
                }
        }
 
-       if (m_max_chatmessage_length > 0 &&
-                       wmessage.length() > m_max_chatmessage_length) {
-               return L"Your message exceed the maximum chat message limit set on the "
-                      L"server. "
-                      L"It was refused. Send a shorter message";
+       if (m_max_chatmessage_length > 0
+                       && wmessage.length() > m_max_chatmessage_length) {
+               return L"Your message exceed the maximum chat message limit set on the server. "
+                               L"It was refused. Send a shorter message";
        }
 
        auto message = trim(wide_to_utf8(wmessage));
+       if (message.empty())
+               return L"";
+
        if (message.find_first_of("\n\r") != std::wstring::npos) {
-               return L"New lines are not permitted in chat messages";
+               return L"Newlines are not permitted in chat messages";
        }
 
        // Run script hook, exit if script ate the chat message
@@ -3004,10 +3034,10 @@ std::wstring Server::handleChat(const std::string &name, const std::wstring &wna
                        the Cyrillic alphabet and some characters on older Android devices
                */
 #ifdef __ANDROID__
-               line += L"<" + wname + L"> " + wmessage;
+               line += L"<" + utf8_to_wide(name) + L"> " + wmessage;
 #else
-               line += narrow_to_wide(m_script->formatChatMessage(
-                               name, wide_to_narrow(wmessage)));
+               line += utf8_to_wide(m_script->formatChatMessage(name,
+                               wide_to_utf8(wmessage)));
 #endif
        }
 
@@ -3020,35 +3050,23 @@ std::wstring Server::handleChat(const std::string &name, const std::wstring &wna
        /*
                Send the message to others
        */
-       actionstream << "CHAT: " << wide_to_narrow(unescape_enriched(line)) << std::endl;
+       actionstream << "CHAT: " << wide_to_utf8(unescape_enriched(line)) << std::endl;
 
-       std::vector<session_t> clients = m_clients.getClientIDs();
+       ChatMessage chatmsg(line);
 
-       /*
-               Send the message back to the inital sender
-               if they are using protocol version >= 29
-       */
-
-       session_t peer_id_to_avoid_sending =
-                       (player ? player->getPeerId() : PEER_ID_INEXISTENT);
-
-       if (player && player->protocol_version >= 29)
-               peer_id_to_avoid_sending = PEER_ID_INEXISTENT;
+       std::vector<session_t> clients = m_clients.getClientIDs();
+       for (u16 cid : clients)
+               SendChatMessage(cid, chatmsg);
 
-       for (u16 cid : clients) {
-               if (cid != peer_id_to_avoid_sending)
-                       SendChatMessage(cid, ChatMessage(line));
-       }
        return L"";
 }
 
 void Server::handleAdminChat(const ChatEventChat *evt)
 {
        std::string name = evt->nick;
-       std::wstring wname = utf8_to_wide(name);
        std::wstring wmessage = evt->evt_msg;
 
-       std::wstring answer = handleChat(name, wname, wmessage);
+       std::wstring answer = handleChat(name, wmessage);
 
        // If asked to send answer to sender
        if (!answer.empty()) {
@@ -3058,8 +3076,8 @@ void Server::handleAdminChat(const ChatEventChat *evt)
 
 RemoteClient *Server::getClient(session_t peer_id, ClientState state_min)
 {
-       RemoteClient *client = getClientNoEx(peer_id, state_min);
-       if (!client)
+       RemoteClient *client = getClientNoEx(peer_id,state_min);
+       if(!client)
                throw ClientNotFoundException("Client not found");
 
        return client;
@@ -3073,7 +3091,7 @@ std::string Server::getPlayerName(session_t peer_id)
 {
        RemotePlayer *player = m_env->getPlayer(peer_id);
        if (!player)
-               return "[id=" + itos(peer_id) + "]";
+               return "[id="+itos(peer_id)+"]";
        return player->getName();
 }
 
@@ -3085,47 +3103,45 @@ PlayerSAO *Server::getPlayerSAO(session_t peer_id)
        return player->getPlayerSAO();
 }
 
-std::wstring Server::getStatusString()
+std::string Server::getStatusString()
 {
-       std::wostringstream os(std::ios_base::binary);
-       os << L"# Server: ";
+       std::ostringstream os(std::ios_base::binary);
+       os << "# Server: ";
        // Version
-       os << L"version=" << narrow_to_wide(g_version_string);
+       os << "version: " << g_version_string;
+       // Game
+       os << " | game: " << (m_gamespec.title.empty() ? m_gamespec.id : m_gamespec.title);
        // Uptime
-       os << L", uptime=" << m_uptime_counter->get();
+       os << " | uptime: " << duration_to_string((int) m_uptime_counter->get());
        // Max lag estimate
-       os << L", max_lag=" << (m_env ? m_env->getMaxLagEstimate() : 0);
+       os << " | max lag: " << std::setprecision(3);
+       os << (m_env ? m_env->getMaxLagEstimate() : 0) << "s";
 
        // Information about clients
        bool first = true;
-       os << L", clients={";
+       os << " | clients: ";
        if (m_env) {
                std::vector<session_t> clients = m_clients.getClientIDs();
                for (session_t client_id : clients) {
                        RemotePlayer *player = m_env->getPlayer(client_id);
 
                        // Get name of player
-                       std::wstring name = L"unknown";
-                       if (player)
-                               name = narrow_to_wide(player->getName());
+                       const char *name = player ? player->getName() : "<unknown>";
 
                        // Add name to information string
                        if (!first)
-                               os << L", ";
+                               os << ", ";
                        else
                                first = false;
-
                        os << name;
                }
        }
-       os << L"}";
 
-       if (m_env && !((ServerMap *)(&m_env->getMap()))->isSavingEnabled())
-               os << std::endl << L"# Server: " << " WARNING: Map saving is disabled.";
+       if (m_env && !((ServerMap*)(&m_env->getMap()))->isSavingEnabled())
+               os << std::endl << "# Server: " << " WARNING: Map saving is disabled.";
 
        if (!g_settings->get("motd").empty())
-               os << std::endl
-                  << L"# Server: " << narrow_to_wide(g_settings->get("motd"));
+               os << std::endl << "# Server: " << g_settings->get("motd");
 
        return os.str();
 }
@@ -3157,9 +3173,11 @@ void Server::reportPrivsModified(const std::string &name)
                        return;
                SendPlayerPrivileges(player->getPeerId());
                PlayerSAO *sao = player->getPlayerSAO();
-               if (!sao)
+               if(!sao)
                        return;
-               sao->updatePrivileges(getPlayerEffectivePrivs(name), isSingleplayer());
+               sao->updatePrivileges(
+                               getPlayerEffectivePrivs(name),
+                               isSingleplayer());
        }
 }
 
@@ -3216,7 +3234,7 @@ void Server::notifyPlayer(const char *name, const std::wstring &msg)
 }
 
 bool Server::showFormspec(const char *playername, const std::string &formspec,
-               const std::string &formname)
+       const std::string &formname)
 {
        // m_env will be NULL if the server is initializing
        if (!m_env)
@@ -3242,12 +3260,11 @@ u32 Server::hudAdd(RemotePlayer *player, HudElement *form)
        return id;
 }
 
-bool Server::hudRemove(RemotePlayer *player, u32 id)
-{
+bool Server::hudRemove(RemotePlayer *player, u32 id) {
        if (!player)
                return false;
 
-       HudElement *todel = player->removeHud(id);
+       HudElementtodel = player->removeHud(id);
 
        if (!todel)
                return false;
@@ -3272,11 +3289,14 @@ bool Server::hudSetFlags(RemotePlayer *player, u32 flags, u32 mask)
        if (!player)
                return false;
 
+       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 &= ~mask;
-       player->hud_flags |= flags;
+       player->hud_flags = new_hud_flags;
 
-       PlayerSAO *playersao = player->getPlayerSAO();
+       PlayerSAOplayersao = player->getPlayerSAO();
 
        if (!playersao)
                return false;
@@ -3320,11 +3340,12 @@ void Server::hudSetHotbarSelectedImage(RemotePlayer *player, const std::string &
 
 Address Server::getPeerAddress(session_t peer_id)
 {
-       return m_con->GetPeerAddress(peer_id);
+       // Note that this is only set after Init was received in Server::handleCommand_Init
+       return getClient(peer_id, CS_Invalid)->getAddress();
 }
 
-void Server::setLocalPlayerAnimations(
-               RemotePlayer *player, v2s32 animation_frames[4], f32 frame_speed)
+void Server::setLocalPlayerAnimations(RemotePlayer *player,
+               v2s32 animation_frames[4], f32 frame_speed)
 {
        sanity_check(player);
        player->setLocalAnimations(animation_frames, frame_speed);
@@ -3374,19 +3395,28 @@ void Server::setClouds(RemotePlayer *player, const CloudParams &params)
        SendCloudParams(player->getPeerId(), params);
 }
 
-void Server::overrideDayNightRatio(RemotePlayer *player, bool do_override, float ratio)
+void Server::overrideDayNightRatio(RemotePlayer *player, bool do_override,
+       float ratio)
 {
        sanity_check(player);
        player->overrideDayNightRatio(do_override, ratio);
        SendOverrideDayNightRatio(player->getPeerId(), do_override, ratio);
 }
 
+void Server::setLighting(RemotePlayer *player, const Lighting &lighting)
+{
+       sanity_check(player);
+       player->setLighting(lighting);
+       SendSetLighting(player->getPeerId(), lighting);
+}
+
 void Server::notifyPlayers(const std::wstring &msg)
 {
        SendChatMessage(PEER_ID_INEXISTENT, ChatMessage(msg));
 }
 
-void Server::spawnParticle(const std::string &playername, const ParticleParameters &p)
+void Server::spawnParticle(const std::string &playername,
+       const ParticleParameters &p)
 {
        // m_env will be NULL if the server is initializing
        if (!m_env)
@@ -3406,7 +3436,7 @@ void Server::spawnParticle(const std::string &playername, const ParticleParamete
 }
 
 u32 Server::addParticleSpawner(const ParticleSpawnerParameters &p,
-               ServerActiveObject *attached, const std::string &playername)
+       ServerActiveObject *attached, const std::string &playername)
 {
        // m_env will be NULL if the server is initializing
        if (!m_env)
@@ -3438,8 +3468,7 @@ void Server::deleteParticleSpawner(const std::string &playername, u32 id)
 {
        // m_env will be NULL if the server is initializing
        if (!m_env)
-               throw ServerError(
-                               "Can't delete particle spawners during initialisation!");
+               throw ServerError("Can't delete particle spawners during initialisation!");
 
        session_t peer_id = PEER_ID_INEXISTENT;
        if (!playername.empty()) {
@@ -3453,13 +3482,18 @@ void Server::deleteParticleSpawner(const std::string &playername, u32 id)
        SendDeleteParticleSpawner(peer_id, id);
 }
 
-bool Server::dynamicAddMedia(const std::string &filepath)
+bool Server::dynamicAddMedia(std::string filepath,
+       const u32 token, const std::string &to_player, bool ephemeral)
 {
        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;
+       auto it = m_media.find(filename);
+       if (it != m_media.end()) {
+               // Allow the same path to be "added" again in certain conditions
+               if (ephemeral || it->second.path != filepath) {
+                       errorstream << "Server::dynamicAddMedia(): file \"" << filename
+                               << "\" already exists in media cache" << std::endl;
+                       return false;
+               }
        }
 
        // Load the file and add it to our media cache
@@ -3468,36 +3502,116 @@ bool Server::dynamicAddMedia(const std::string &filepath)
        if (!ok)
                return false;
 
+       if (ephemeral) {
+               // Create a copy of the file and swap out the path, this removes the
+               // requirement that mods keep the file accessible at the original path.
+               filepath = fs::CreateTempFile();
+               bool ok = ([&] () -> bool {
+                       if (filepath.empty())
+                               return false;
+                       std::ofstream os(filepath.c_str(), std::ios::binary);
+                       if (!os.good())
+                               return false;
+                       os << filedata;
+                       os.close();
+                       return !os.fail();
+               })();
+               if (!ok) {
+                       errorstream << "Server: failed to create a copy of media file "
+                               << "\"" << filename << "\"" << std::endl;
+                       m_media.erase(filename);
+                       return false;
+               }
+               verbosestream << "Server: \"" << filename << "\" temporarily copied to "
+                       << filepath << std::endl;
+
+               m_media[filename].path = filepath;
+               m_media[filename].no_announce = true;
+               // stepPendingDynMediaCallbacks will clean this up later.
+       } else if (!to_player.empty()) {
+               m_media[filename].no_announce = true;
+       }
+
        // Push file to existing clients
        NetworkPacket pkt(TOCLIENT_MEDIA_PUSH, 0);
-       pkt << raw_hash << filename << (bool)true;
-       pkt.putLongString(filedata);
+       pkt << raw_hash << filename << (bool)ephemeral;
 
-       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);
+       NetworkPacket legacy_pkt = pkt;
+
+       // Newer clients get asked to fetch the file (asynchronous)
+       pkt << token;
+       // Older clients have an awful hack that just throws the data at them
+       legacy_pkt.putLongString(filedata);
+
+       std::unordered_set<session_t> delivered, waiting;
+       {
+               ClientInterface::AutoLock clientlock(m_clients);
+               for (auto &pair : m_clients.getClientList()) {
+                       if (pair.second->getState() == CS_DefinitionsSent && !ephemeral) {
+                               /*
+                                       If a client is in the DefinitionsSent state it is too late to
+                                       transfer the file via sendMediaAnnouncement() but at the same
+                                       time the client cannot accept a media push yet.
+                                       Short of artificially delaying the joining process there is no
+                                       way for the server to resolve this so we (currently) opt not to.
+                               */
+                               warningstream << "The media \"" << filename << "\" (dynamic) could "
+                                       "not be delivered to " << pair.second->getName()
+                                       << " due to a race condition." << std::endl;
+                               continue;
+                       }
+                       if (pair.second->getState() < CS_Active)
+                               continue;
+
+                       const auto proto_ver = pair.second->net_proto_version;
+                       if (proto_ver < 39)
+                               continue;
+
+                       const session_t peer_id = pair.second->peer_id;
+                       if (!to_player.empty() && getPlayerName(peer_id) != to_player)
+                               continue;
+
+                       if (proto_ver < 40) {
+                               delivered.emplace(peer_id);
+                               /*
+                                       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 channels 1 and 0.
+                               */
+                               m_clients.send(peer_id, 1, &legacy_pkt, true);
+                               m_clients.send(peer_id, 0, &legacy_pkt, true);
+                       } else {
+                               waiting.emplace(peer_id);
+                               Send(peer_id, &pkt);
+                       }
+               }
        }
 
+       // Run callback for players that already had the file delivered (legacy-only)
+       for (session_t peer_id : delivered) {
+               if (auto player = m_env->getPlayer(peer_id))
+                       getScriptIface()->on_dynamic_media_added(token, player->getName());
+       }
+
+       // Save all others in our pending state
+       auto &state = m_pending_dyn_media[token];
+       state.waiting_players = std::move(waiting);
+       // regardless of success throw away the callback after a while
+       state.expiry_timer = 60.0f;
+       if (ephemeral)
+               state.filename = filename;
+
        return true;
 }
 
 // actions: time-reversed list
 // Return value: success/failure
-bool Server::rollbackRevertActions(
-               const std::list<RollbackAction> &actions, std::list<std::string> *log)
+bool Server::rollbackRevertActions(const std::list<RollbackAction> &actions,
+               std::list<std::string> *log)
 {
-       infostream << "Server::rollbackRevertActions(len=" << actions.size() << ")"
-                  << std::endl;
-       ServerMap *map = (ServerMap *)(&m_env->getMap());
+       infostream<<"Server::rollbackRevertActions(len="<<actions.size()<<")"<<std::endl;
+       ServerMap *map = (ServerMap*)(&m_env->getMap());
 
        // Fail if no actions to handle
        if (actions.empty()) {
@@ -3512,31 +3626,27 @@ bool Server::rollbackRevertActions(
        for (const RollbackAction &action : actions) {
                num_tried++;
                bool success = action.applyRevert(map, m_inventory_mgr.get(), this);
-               if (!success) {
+               if(!success){
                        num_failed++;
                        std::ostringstream os;
-                       os << "Revert of step (" << num_tried << ") " << action.toString()
-                          << " failed";
-                       infostream << "Map::rollbackRevertActions(): " << os.str()
-                                  << std::endl;
+                       os<<"Revert of step ("<<num_tried<<") "<<action.toString()<<" failed";
+                       infostream<<"Map::rollbackRevertActions(): "<<os.str()<<std::endl;
                        if (log)
                                log->push_back(os.str());
-               } else {
+               }else{
                        std::ostringstream os;
-                       os << "Successfully reverted step (" << num_tried << ") "
-                          << action.toString();
-                       infostream << "Map::rollbackRevertActions(): " << os.str()
-                                  << std::endl;
+                       os<<"Successfully reverted step ("<<num_tried<<") "<<action.toString();
+                       infostream<<"Map::rollbackRevertActions(): "<<os.str()<<std::endl;
                        if (log)
                                log->push_back(os.str());
                }
        }
 
-       infostream << "Map::rollbackRevertActions(): " << num_failed << "/" << num_tried
-                  << " failed" << std::endl;
+       infostream<<"Map::rollbackRevertActions(): "<<num_failed<<"/"<<num_tried
+                       <<" failed"<<std::endl;
 
        // Call it done if less than half failed
-       return num_failed <= num_tried / 2;
+       return num_failed <= num_tried/2;
 }
 
 // IGameDef interface
@@ -3576,7 +3686,7 @@ IWritableCraftDefManager *Server::getWritableCraftDefManager()
        return m_craftdef;
 }
 
-const std::vector<ModSpec> &Server::getMods() const
+const std::vector<ModSpec> & Server::getMods() const
 {
        return m_modmgr->getMods();
 }
@@ -3586,21 +3696,11 @@ const ModSpec *Server::getModSpec(const std::string &modname) const
        return m_modmgr->getModSpec(modname);
 }
 
-void Server::getModNames(std::vector<std::string> &modlist)
-{
-       m_modmgr->getModNames(modlist);
-}
-
 std::string Server::getBuiltinLuaPath()
 {
        return porting::path_share + DIR_DELIM + "builtin";
 }
 
-std::string Server::getModStoragePath() const
-{
-       return m_path_world + DIR_DELIM + "mod_storage";
-}
-
 v3f Server::findSpawnPos()
 {
        ServerMap &map = m_env->getServerMap();
@@ -3616,8 +3716,9 @@ v3f Server::findSpawnPos()
        for (s32 i = 0; i < 4000 && !is_good; i++) {
                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)));
+               v2s16 nodepos2d = v2s16(
+                       -range + (myrand() % (range * 2)),
+                       -range + (myrand() % (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
@@ -3640,10 +3741,9 @@ v3f Server::findSpawnPos()
                        map.emergeBlock(blockpos, true);
                        content_t c = map.getNode(nodepos).getContent();
 
-                       // In generated mapblocks allow spawn in all 'airlike' drawtype
-                       // nodes. In ungenerated mapblocks allow spawn in 'ignore' nodes.
-                       if (m_nodedef->get(c).drawtype == NDT_AIRLIKE ||
-                                       c == CONTENT_IGNORE) {
+                       // In generated mapblocks allow spawn in all 'airlike' drawtype nodes.
+                       // In ungenerated mapblocks allow spawn in 'ignore' nodes.
+                       if (m_nodedef->get(c).drawtype == NDT_AIRLIKE || c == CONTENT_IGNORE) {
                                air_count++;
                                if (air_count >= 2) {
                                        // Spawn in lower empty node
@@ -3651,12 +3751,10 @@ v3f Server::findSpawnPos()
                                        nodeposf = intToFloat(nodepos, BS);
                                        // Don't spawn the player outside map boundaries
                                        if (objectpos_over_limit(nodeposf))
-                                               // Exit this loop, positions above are
-                                               // probably over limit
+                                               // Exit this loop, positions above are probably over limit
                                                break;
 
-                                       // Good position found, cause an exit from main
-                                       // loop
+                                       // Good position found, cause an exit from main loop
                                        is_good = true;
                                        break;
                                }
@@ -3677,7 +3775,7 @@ v3f Server::findSpawnPos()
 void Server::requestShutdown(const std::string &msg, bool reconnect, float delay)
 {
        if (delay == 0.0f) {
-               // No delay, shutdown immediately
+       // No delay, shutdown immediately
                m_shutdown_state.is_requested = true;
                // only print to the infostream, a chat message saying
                // "Server Shutting Down" is sent when the server destructs.
@@ -3694,11 +3792,12 @@ void Server::requestShutdown(const std::string &msg, bool reconnect, float delay
                // m_shutdown_* are already handled, skip.
                return;
        } else if (delay > 0.0f) {
-               // Positive delay, tell the clients when the server will shut down
+       // Positive delay, tell the clients when the server will shut down
                std::wstringstream ws;
 
                ws << L"*** Server shutting down in "
-                  << duration_to_string(myround(delay)).c_str() << ".";
+                               << duration_to_string(myround(delay)).c_str()
+                               << ".";
 
                infostream << wide_to_utf8(ws.str()).c_str() << std::endl;
                SendChatMessage(PEER_ID_INEXISTENT, ws.str());
@@ -3707,7 +3806,7 @@ void Server::requestShutdown(const std::string &msg, bool reconnect, float delay
        m_shutdown_state.trigger(delay, msg, reconnect);
 }
 
-PlayerSAO *Server::emergePlayer(const char *name, session_t peer_id, u16 proto_version)
+PlayerSAOServer::emergePlayer(const char *name, session_t peer_id, u16 proto_version)
 {
        /*
                Try to get an existing player
@@ -3716,7 +3815,7 @@ PlayerSAO *Server::emergePlayer(const char *name, session_t peer_id, u16 proto_v
 
        // If player is already connected, cancel
        if (player && player->getPeerId() != PEER_ID_INEXISTENT) {
-               infostream << "emergePlayer(): Player already connected" << std::endl;
+               infostream<<"emergePlayer(): Player already connected"<<std::endl;
                return NULL;
        }
 
@@ -3724,9 +3823,8 @@ PlayerSAO *Server::emergePlayer(const char *name, session_t peer_id, u16 proto_v
                If player with the wanted peer_id already exists, cancel.
        */
        if (m_env->getPlayer(peer_id)) {
-               infostream << "emergePlayer(): Player with wrong name but same"
-                             " peer_id already exists"
-                          << std::endl;
+               infostream<<"emergePlayer(): Player with wrong name but same"
+                               " peer_id already exists"<<std::endl;
                return NULL;
        }
 
@@ -3737,8 +3835,7 @@ PlayerSAO *Server::emergePlayer(const char *name, session_t peer_id, u16 proto_v
        bool newplayer = false;
 
        // Load player
-       PlayerSAO *playersao =
-                       m_env->loadPlayer(player, &newplayer, peer_id, isSingleplayer());
+       PlayerSAO *playersao = m_env->loadPlayer(player, &newplayer, peer_id, isSingleplayer());
 
        // Complete init with server parts
        playersao->finalize(player, getPlayerEffectivePrivs(player->getName()));
@@ -3756,7 +3853,7 @@ 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;
+                               << storage->getModName() << std::endl;
                return false;
        }
 
@@ -3766,18 +3863,14 @@ bool Server::registerModStorage(ModMetadata *storage)
 
 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()) {
-               // Save unconditionaly on unregistration
-               it->second->save(getModStoragePath());
+       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;
+       verbosestream<<"dedicated_server_loop()"<<std::endl;
 
        IntervalLimiter m_profiler_interval;
 
@@ -3791,10 +3884,10 @@ void dedicated_server_loop(Server &server, bool &kill)
         * provides a way to main.cpp to kill the server externally (bool &kill).
         */
 
-       for (;;) {
+       for(;;) {
                // This is kind of a hack but can be done like this
                // because server.step() is very light
-               sleep_ms((int)(steplen * 1000.0));
+               sleep_ms((int)(steplen*1000.0));
                server.step(steplen);
 
                if (server.isShutdownRequested() || kill)
@@ -3804,8 +3897,9 @@ void dedicated_server_loop(Server &server, bool &kill)
                        Profiler
                */
                if (profiler_print_interval != 0) {
-                       if (m_profiler_interval.step(steplen, profiler_print_interval)) {
-                               infostream << "Profiler:" << std::endl;
+                       if(m_profiler_interval.step(steplen, profiler_print_interval))
+                       {
+                               infostream<<"Profiler:"<<std::endl;
                                g_profiler->print(infostream);
                                g_profiler->clear();
                        }
@@ -3815,8 +3909,8 @@ void dedicated_server_loop(Server &server, bool &kill)
        infostream << "Dedicated server quitting" << std::endl;
 #if USE_CURL
        if (g_settings->getBool("server_announce"))
-               ServerList::sendAnnounce(
-                               ServerList::AA_DELETE, server.m_bind_addr.getPort());
+               ServerList::sendAnnounce(ServerList::AA_DELETE,
+                       server.m_bind_addr.getPort());
 #endif
 }
 
@@ -3824,10 +3918,11 @@ void dedicated_server_loop(Server &server, bool &kill)
  * Mod channels
  */
 
+
 bool Server::joinModChannel(const std::string &channel)
 {
        return m_modchannel_mgr->joinChannel(channel, PEER_ID_SERVER) &&
-              m_modchannel_mgr->setChannelState(channel, MODCHANNEL_STATE_READ_WRITE);
+                       m_modchannel_mgr->setChannelState(channel, MODCHANNEL_STATE_READ_WRITE);
 }
 
 bool Server::leaveModChannel(const std::string &channel)
@@ -3844,7 +3939,7 @@ bool Server::sendModChannelMessage(const std::string &channel, const std::string
        return true;
 }
 
-ModChannel *Server::getModChannel(const std::string &channel)
+ModChannelServer::getModChannel(const std::string &channel)
 {
        return m_modchannel_mgr->getModChannel(channel);
 }
@@ -3858,8 +3953,8 @@ void Server::broadcastModChannelMessage(const std::string &channel,
 
        if (message.size() > STRING_MAX_LEN) {
                warningstream << "ModChannel message too long, dropping before sending "
-                             << " (" << message.size() << " > " << STRING_MAX_LEN
-                             << ", channel: " << channel << ")" << std::endl;
+                               << " (" << message.size() << " > " << STRING_MAX_LEN << ", channel: "
+                               << channel << ")" << std::endl;
                return;
        }
 
@@ -3884,19 +3979,130 @@ void Server::broadcastModChannelMessage(const std::string &channel,
        }
 }
 
-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>());
+                       std::string data;
+                       if (fs::ReadFile(i.second.path, data)) {
+                               translations->loadTranslation(data);
+                       }
+               }
+       }
+
+       return translations;
+}
+
+ModMetadataDatabase *Server::openModStorageDatabase(const std::string &world_path)
+{
+       std::string world_mt_path = world_path + DIR_DELIM + "world.mt";
+       Settings world_mt;
+       if (!world_mt.readConfigFile(world_mt_path.c_str()))
+               throw BaseException("Cannot read world.mt!");
+
+       std::string backend = world_mt.exists("mod_storage_backend") ?
+               world_mt.get("mod_storage_backend") : "files";
+       if (backend == "files")
+               warningstream << "/!\\ You are using the old mod storage files backend. "
+                       << "This backend is deprecated and may be removed in a future release /!\\"
+                       << std::endl << "Switching to SQLite3 is advised, "
+                       << "please read http://wiki.minetest.net/Database_backends." << std::endl;
+
+       return openModStorageDatabase(backend, world_path, world_mt);
+}
+
+ModMetadataDatabase *Server::openModStorageDatabase(const std::string &backend,
+               const std::string &world_path, const Settings &world_mt)
+{
+       if (backend == "sqlite3")
+               return new ModMetadataDatabaseSQLite3(world_path);
+
+       if (backend == "files")
+               return new ModMetadataDatabaseFiles(world_path);
+
+       if (backend == "dummy")
+               return new Database_Dummy();
 
-                       (*g_server_translations)[lang_code].loadTranslation(data);
+       throw BaseException("Mod storage database backend " + backend + " not supported");
+}
+
+bool Server::migrateModStorageDatabase(const GameParams &game_params, const Settings &cmd_args)
+{
+       std::string migrate_to = cmd_args.get("migrate-mod-storage");
+       Settings world_mt;
+       std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
+       if (!world_mt.readConfigFile(world_mt_path.c_str())) {
+               errorstream << "Cannot read world.mt!" << std::endl;
+               return false;
+       }
+
+       std::string backend = world_mt.exists("mod_storage_backend") ?
+               world_mt.get("mod_storage_backend") : "files";
+       if (backend == migrate_to) {
+               errorstream << "Cannot migrate: new backend is same"
+                       << " as the old one" << std::endl;
+               return false;
+       }
+
+       ModMetadataDatabase *srcdb = nullptr;
+       ModMetadataDatabase *dstdb = nullptr;
+
+       bool succeeded = false;
+
+       try {
+               srcdb = Server::openModStorageDatabase(backend, game_params.world_path, world_mt);
+               dstdb = Server::openModStorageDatabase(migrate_to, game_params.world_path, world_mt);
+
+               dstdb->beginSave();
+
+               std::vector<std::string> mod_list;
+               srcdb->listMods(&mod_list);
+               for (const std::string &modname : mod_list) {
+                       StringMap meta;
+                       srcdb->getModEntries(modname, &meta);
+                       for (const auto &pair : meta) {
+                               dstdb->setModEntry(modname, pair.first, pair.second);
+                       }
                }
+
+               dstdb->endSave();
+
+               succeeded = true;
+
+               actionstream << "Successfully migrated the metadata of "
+                       << mod_list.size() << " mods" << std::endl;
+               world_mt.set("mod_storage_backend", migrate_to);
+               if (!world_mt.updateConfigFile(world_mt_path.c_str()))
+                       errorstream << "Failed to update world.mt!" << std::endl;
+               else
+                       actionstream << "world.mt updated" << std::endl;
+
+       } catch (BaseException &e) {
+               errorstream << "An error occurred during migration: " << e.what() << std::endl;
        }
+
+       delete srcdb;
+       delete dstdb;
+
+       if (succeeded && backend == "files") {
+               // Back up files
+               const std::string storage_path = game_params.world_path + DIR_DELIM + "mod_storage";
+               const std::string backup_path = game_params.world_path + DIR_DELIM + "mod_storage.bak";
+               if (!fs::Rename(storage_path, backup_path))
+                       warningstream << "After migration, " << storage_path
+                               << " could not be renamed to " << backup_path << std::endl;
+       }
+
+       return succeeded;
 }