]> git.lizzy.rs Git - dragonfireclient.git/blobdiff - src/server.cpp
Fix doc and forceloading crash.
[dragonfireclient.git] / src / server.cpp
index 4268bb8098fe3009c5e8a58d81f24b8846249914..b257448a1c4e2de8b1b7091ffd74e103352e4c2a 100644 (file)
@@ -22,19 +22,23 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include <queue>
 #include <algorithm>
 #include "clientserver.h"
+#include "ban.h"
+#include "environment.h"
 #include "map.h"
-#include "jmutexautolock.h"
+#include "jthread/jmutexautolock.h"
 #include "main.h"
 #include "constants.h"
 #include "voxel.h"
 #include "config.h"
+#include "version.h"
 #include "filesys.h"
 #include "mapblock.h"
 #include "serverobject.h"
+#include "genericobject.h"
 #include "settings.h"
 #include "profiler.h"
 #include "log.h"
-#include "script/cpp_api/scriptapi.h"
+#include "scripting_game.h"
 #include "nodedef.h"
 #include "itemdef.h"
 #include "craftdef.h"
@@ -58,19 +62,44 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "util/mathconstants.h"
 #include "rollback.h"
 #include "util/serialize.h"
+#include "util/thread.h"
 #include "defaultsettings.h"
 
-void * ServerThread::Thread()
+class ClientNotFoundException : public BaseException
 {
-       ThreadStarted();
+public:
+       ClientNotFoundException(const char *s):
+               BaseException(s)
+       {}
+};
 
+class ServerThread : public JThread
+{
+       Server *m_server;
+
+public:
+
+       ServerThread(Server *server):
+               JThread(),
+               m_server(server)
+       {
+       }
+
+       void * Thread();
+};
+
+void * ServerThread::Thread()
+{
        log_register_thread("ServerThread");
 
        DSTACK(__FUNCTION_NAME);
-
        BEGIN_DEBUG_EXCEPTION_HANDLER
 
-       while(getRun())
+       m_server->AsyncRunStep(true);
+
+       ThreadStarted();
+
+       while(!StopRequested())
        {
                try{
                        //TimeTaker timer("AsyncRunStep() + Receive()");
@@ -90,6 +119,9 @@ void * ServerThread::Thread()
                {
                        infostream<<"Server: PeerNotFoundException"<<std::endl;
                }
+               catch(ClientNotFoundException &e)
+               {
+               }
                catch(con::ConnectionBindFailed &e)
                {
                        m_server->setAsyncFatalError(e.what());
@@ -602,45 +634,23 @@ void RemoteClient::SetBlocksNotSent(std::map<v3s16, MapBlock*> &blocks)
        }
 }
 
-/*
-       PlayerInfo
-*/
-
-PlayerInfo::PlayerInfo()
-{
-       name[0] = 0;
-       avg_rtt = 0;
-}
-
-void PlayerInfo::PrintLine(std::ostream *s)
-{
-       (*s)<<id<<": ";
-       (*s)<<"\""<<name<<"\" ("
-                       <<(position.X/10)<<","<<(position.Y/10)
-                       <<","<<(position.Z/10)<<") ";
-       address.print(s);
-       (*s)<<" avg_rtt="<<avg_rtt;
-       (*s)<<std::endl;
-}
-
 /*
        Server
 */
 
 Server::Server(
                const std::string &path_world,
-               const std::string &path_config,
                const SubgameSpec &gamespec,
                bool simple_singleplayer_mode
        ):
        m_path_world(path_world),
-       m_path_config(path_config),
        m_gamespec(gamespec),
        m_simple_singleplayer_mode(simple_singleplayer_mode),
        m_async_fatal_error(""),
        m_env(NULL),
-       m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this),
-       m_banmanager(path_world+DIR_DELIM+"ipban.txt"),
+       m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT,
+             g_settings->getBool("enable_ipv6") && g_settings->getBool("ipv6_server"), this),
+       m_banmanager(NULL),
        m_rollback(NULL),
        m_rollback_sink_enabled(true),
        m_enable_rollback_recording(false),
@@ -650,7 +660,7 @@ Server::Server(
        m_nodedef(createNodeDefManager()),
        m_craftdef(createCraftDefManager()),
        m_event(new EventManager()),
-       m_thread(this),
+       m_thread(NULL),
        m_time_of_day_send_timer(0),
        m_uptime(0),
        m_shutdown_requested(false),
@@ -664,12 +674,9 @@ Server::Server(
        m_objectdata_timer = 0.0;
        m_emergethread_trigger_timer = 0.0;
        m_savemap_timer = 0.0;
-       m_clients_number = 0;
 
-       m_env_mutex.Init();
-       m_con_mutex.Init();
-       m_step_dtime_mutex.Init();
        m_step_dtime = 0.0;
+       m_lag = g_settings->getFloat("dedicated_server_step");
 
        if(path_world == "")
                throw ServerError("Supplied empty world path");
@@ -683,7 +690,6 @@ Server::Server(
        else
                infostream<<std::endl;
        infostream<<"- world:  "<<m_path_world<<std::endl;
-       infostream<<"- config: "<<m_path_config<<std::endl;
        infostream<<"- game:   "<<m_gamespec.path<<std::endl;
 
        // Initialize default settings and override defaults with those provided
@@ -692,23 +698,30 @@ Server::Server(
        Settings gamedefaults;
        getGameMinetestConfig(gamespec.path, gamedefaults);
        override_default_settings(g_settings, &gamedefaults);
-       
+
+       // Create server thread
+       m_thread = new ServerThread(this);
+
        // Create emerge manager
        m_emerge = new EmergeManager(this);
-       
-       // Create rollback manager
-       std::string rollback_path = m_path_world+DIR_DELIM+"rollback.txt";
-       m_rollback = createRollbackManager(rollback_path, this);
 
        // Create world if it doesn't exist
        if(!initializeWorld(m_path_world, m_gamespec.id))
                throw ServerError("Failed to initialize world");
 
+       // Create ban manager
+       std::string ban_path = m_path_world+DIR_DELIM+"ipban.txt";
+       m_banmanager = new BanManager(ban_path);
+
+       // Create rollback manager
+       std::string rollback_path = m_path_world+DIR_DELIM+"rollback.txt";
+       m_rollback = createRollbackManager(rollback_path, this);
+
        ModConfiguration modconf(m_path_world);
        m_mods = modconf.getMods();
        std::vector<ModSpec> unsatisfied_mods = modconf.getUnsatisfiedMods();
        // complain about mods with unsatisfied dependencies
-       if(!modconf.isConsistent())     
+       if(!modconf.isConsistent())
        {
                for(std::vector<ModSpec>::iterator it = unsatisfied_mods.begin();
                        it != unsatisfied_mods.end(); ++it)
@@ -727,10 +740,10 @@ Server::Server(
        worldmt_settings.readConfigFile(worldmt.c_str());
        std::vector<std::string> names = worldmt_settings.getNames();
        std::set<std::string> load_mod_names;
-       for(std::vector<std::string>::iterator it = names.begin(); 
+       for(std::vector<std::string>::iterator it = names.begin();
                it != names.end(); ++it)
-       {       
-               std::string name = *it;  
+       {
+               std::string name = *it;
                if(name.compare(0,9,"load_mod_")==0 && worldmt_settings.getBool(name))
                        load_mod_names.insert(name.substr(9));
        }
@@ -742,7 +755,7 @@ Server::Server(
                        it != unsatisfied_mods.end(); ++it)
                load_mod_names.erase((*it).name);
        if(!load_mod_names.empty())
-       {               
+       {
                errorstream << "The following mods could not be found:";
                for(std::set<std::string>::iterator it = load_mod_names.begin();
                        it != load_mod_names.end(); ++it)
@@ -761,7 +774,7 @@ Server::Server(
 
        infostream<<"Server: Initializing Lua"<<std::endl;
 
-       m_script = new ScriptApi(this);
+       m_script = new GameScripting(this);
 
 
        // Load and run builtin.lua
@@ -804,9 +817,15 @@ Server::Server(
 
        // Initialize Environment
        ServerMap *servermap = new ServerMap(path_world, this, m_emerge);
-       m_env = new ServerEnvironment(servermap, m_script, this, this);
+       m_env = new ServerEnvironment(servermap, m_script, this, m_emerge);
        
-       m_emerge->initMapgens(servermap->getMapgenParams());
+       // Run some callbacks after the MG params have been set up but before activation
+       MapgenParams *mgparams = servermap->getMapgenParams();
+       m_script->environment_OnMapgenInit(mgparams);
+       
+       // Initialize mapgens
+       m_emerge->initMapgens(mgparams);
+       servermap->setMapgenParams(m_emerge->params);
 
        // Give environment reference to scripting api
        m_script->initializeEnvironment(m_env);
@@ -896,6 +915,7 @@ Server::~Server()
                Stop threads
        */
        stop();
+       delete m_thread;
 
        //shutdown all emerge threads first!
        delete m_emerge;
@@ -919,6 +939,7 @@ Server::~Server()
        // Delete things in the reverse order of creation
        delete m_env;
        delete m_rollback;
+       delete m_banmanager;
        delete m_event;
        delete m_itemdef;
        delete m_nodedef;
@@ -944,15 +965,14 @@ void Server::start(unsigned short port)
        infostream<<"Starting server on port "<<port<<"..."<<std::endl;
 
        // Stop thread if already running
-       m_thread.stop();
+       m_thread->Stop();
 
        // Initialize connection
        m_con.SetTimeoutMs(30);
        m_con.Serve(port);
 
        // Start thread
-       m_thread.setRun(true);
-       m_thread.Start();
+       m_thread->Start();
 
        // ASCII art for the win!
        actionstream
@@ -974,9 +994,9 @@ void Server::stop()
        infostream<<"Server: Stopping and waiting threads"<<std::endl;
 
        // Stop threads (set run=false first so both start stopping)
-       m_thread.setRun(false);
+       m_thread->Stop();
        //m_emergethread.setRun(false);
-       m_thread.stop();
+       m_thread->Wait();
        //m_emergethread.stop();
 
        infostream<<"Server: Threads stopped"<<std::endl;
@@ -999,7 +1019,7 @@ void Server::step(float dtime)
        }
 }
 
-void Server::AsyncRunStep()
+void Server::AsyncRunStep(bool initial_step)
 {
        DSTACK(__FUNCTION_NAME);
 
@@ -1016,7 +1036,7 @@ void Server::AsyncRunStep()
                SendBlocks(dtime);
        }
 
-       if(dtime < 0.001)
+       if((dtime < 0.001) && (initial_step == false))
                return;
 
        g_profiler->add("Server::AsyncRunStep with dtime (num)", 1);
@@ -1069,21 +1089,31 @@ void Server::AsyncRunStep()
                        //JMutexAutoLock envlock(m_env_mutex);
                        JMutexAutoLock conlock(m_con_mutex);
 
+                       u16 time = m_env->getTimeOfDay();
+                       float time_speed = g_settings->getFloat("time_speed");
+
                        for(std::map<u16, RemoteClient*>::iterator
                                i = m_clients.begin();
                                i != m_clients.end(); ++i)
                        {
                                RemoteClient *client = i->second;
-                               SharedBuffer<u8> data = makePacket_TOCLIENT_TIME_OF_DAY(
-                                               m_env->getTimeOfDay(), g_settings->getFloat("time_speed"));
-                               // Send as reliable
-                               m_con.Send(client->peer_id, 0, data, true);
+                               SendTimeOfDay(client->peer_id, time, time_speed);
                        }
                }
        }
 
        {
                JMutexAutoLock lock(m_env_mutex);
+               // 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;
+                       max_lag = dtime;
+               }
+               m_env->reportMaxLagEstimate(max_lag);
                // Step environment
                ScopeProfiler sp(g_profiler, "SEnv step");
                ScopeProfiler sp2(g_profiler, "SEnv step avg", SPT_AVG);
@@ -1133,6 +1163,13 @@ void Server::AsyncRunStep()
                                        SendPlayerHP(client->peer_id);
                        }
 
+                       /*
+                               Send player breath if changed
+                       */
+                       if(playersao->m_breath_not_sent){
+                               SendPlayerBreath(client->peer_id);
+                       }
+
                        /*
                                Send player inventories if necessary
                        */
@@ -1205,7 +1242,7 @@ void Server::AsyncRunStep()
                        counter = 0.0;
 
                        JMutexAutoLock lock2(m_con_mutex);
-                       m_clients_number = 0;
+                       m_clients_names.clear();
                        if(m_clients.size() != 0)
                                infostream<<"Players:"<<std::endl;
                        for(std::map<u16, RemoteClient*>::iterator
@@ -1219,19 +1256,20 @@ void Server::AsyncRunStep()
                                        continue;
                                infostream<<"* "<<player->getName()<<"\t";
                                client->PrintInfo(infostream);
-                               ++m_clients_number;
+                               m_clients_names.push_back(player->getName());
                        }
                }
        }
 
 
+       m_lag += (m_lag > dtime ? -1 : 1) * dtime/100;
 #if USE_CURL
        // send masterserver announce
        {
                float &counter = m_masterserver_timer;
-               if((!counter || counter >= 300.0) && g_settings->getBool("server_announce") == true)
+               if(!isSingleplayer() && (!counter || counter >= 300.0) && g_settings->getBool("server_announce") == true)
                {
-                       ServerList::sendAnnounce(!counter ? "start" : "update", m_clients_number, m_uptime.get(), m_gamespec.id);
+                       ServerList::sendAnnounce(!counter ? "start" : "update", m_clients_names, m_uptime.get(), m_env->getGameTime(), m_lag, m_gamespec.id, m_mods);
                        counter = 0.01;
                }
                counter += dtime;
@@ -1491,7 +1529,7 @@ void Server::AsyncRunStep()
                                memcpy((char*)&reply[2], unreliable_data.c_str(),
                                                unreliable_data.size());
                                // Send as unreliable
-                               m_con.Send(client->peer_id, 0, reply, false);
+                               m_con.Send(client->peer_id, 1, reply, false);
                        }
 
                        /*if(reliable_data.size() > 0 || unreliable_data.size() > 0)
@@ -1544,16 +1582,16 @@ void Server::AsyncRunStep()
                        // for them.
                        std::list<u16> far_players;
 
-                       if(event->type == MEET_ADDNODE)
+                       if(event->type == MEET_ADDNODE || event->type == MEET_SWAPNODE)
                        {
                                //infostream<<"Server: MEET_ADDNODE"<<std::endl;
                                prof.add("MEET_ADDNODE", 1);
                                if(disable_single_change_sending)
                                        sendAddNode(event->p, event->n, event->already_known_by_peer,
-                                                       &far_players, 5);
+                                                       &far_players, 5, event->type == MEET_ADDNODE);
                                else
                                        sendAddNode(event->p, event->n, event->already_known_by_peer,
-                                                       &far_players, 30);
+                                                       &far_players, 30, event->type == MEET_ADDNODE);
                        }
                        else if(event->type == MEET_REMOVENODE)
                        {
@@ -1646,8 +1684,7 @@ void Server::AsyncRunStep()
                {
                        counter = 0.0;
 
-                       for (unsigned int i = 0; i != m_emerge->emergethread.size(); i++)
-                               m_emerge->emergethread[i]->trigger();
+                       m_emerge->startAllThreads();
 
                        // Update m_enable_rollback_recording here too
                        m_enable_rollback_recording =
@@ -1667,8 +1704,8 @@ void Server::AsyncRunStep()
                        ScopeProfiler sp(g_profiler, "Server: saving stuff");
 
                        //Ban stuff
-                       if(m_banmanager.isModified())
-                               m_banmanager.save();
+                       if(m_banmanager->isModified())
+                               m_banmanager->save();
 
                        // Save changed parts of map
                        m_env->getMap().save(MOD_STATE_WRITE_NEEDED);
@@ -1732,19 +1769,20 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 
        ScopeProfiler sp(g_profiler, "Server::ProcessData");
 
+       std::string addr_s;
        try{
                Address address = m_con.GetPeerAddress(peer_id);
-               std::string addr_s = address.serializeString();
+               addr_s = address.serializeString();
 
                // drop player if is ip is banned
-               if(m_banmanager.isIpBanned(addr_s)){
+               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 "
-                                       <<m_banmanager.getBanName(addr_s)<<std::endl;
+                                       <<ban_name<<std::endl;
                        // This actually doesn't seem to transfer to the client
-                       SendAccessDenied(m_con, peer_id,
-                                       L"Your ip is banned. Banned name was "
-                                       +narrow_to_wide(m_banmanager.getBanName(addr_s)));
+                       DenyAccess(peer_id, L"Your ip is banned. Banned name was "
+                                       +narrow_to_wide(ban_name));
                        m_con.DeletePeer(peer_id);
                        return;
                }
@@ -1756,8 +1794,6 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                return;
        }
 
-       std::string addr_s = m_con.GetPeerAddress(peer_id).serializeString();
-
        u8 peer_ser_ver = getClient(peer_id)->serialization_version;
 
        try
@@ -1771,20 +1807,36 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
        if(command == TOSERVER_INIT)
        {
                // [0] u16 TOSERVER_INIT
-               // [2] u8 SER_FMT_VER_HIGHEST
+               // [2] u8 SER_FMT_VER_HIGHEST_READ
                // [3] u8[20] player_name
                // [23] u8[28] password <--- can be sent without this, from old versions
 
                if(datasize < 2+1+PLAYERNAME_SIZE)
                        return;
 
-               verbosestream<<"Server: Got TOSERVER_INIT from "
-                               <<peer_id<<std::endl;
+               // If net_proto_version is set, this client has already been handled
+               if(getClient(peer_id)->net_proto_version != 0){
+                       verbosestream<<"Server: Ignoring multiple TOSERVER_INITs from "
+                                       <<addr_s<<" (peer_id="<<peer_id<<")"<<std::endl;
+                       return;
+               }
+
+               verbosestream<<"Server: Got TOSERVER_INIT from "<<addr_s<<" (peer_id="
+                               <<peer_id<<")"<<std::endl;
+
+               // Do not allow multiple players in simple singleplayer mode.
+               // This isn't a perfect way to do it, but will suffice for now.
+               if(m_simple_singleplayer_mode && m_clients.size() > 1){
+                       infostream<<"Server: Not allowing another client ("<<addr_s
+                                       <<") to connect in simple singleplayer mode"<<std::endl;
+                       DenyAccess(peer_id, L"Running in simple singleplayer mode.");
+                       return;
+               }
 
                // First byte after command is maximum supported
                // serialization version
                u8 client_max = data[2];
-               u8 our_max = SER_FMT_VER_HIGHEST;
+               u8 our_max = SER_FMT_VER_HIGHEST_READ;
                // Use the highest version supported by both
                u8 deployed = std::min(client_max, our_max);
                // If it's lower than the lowest supported, give up.
@@ -1798,13 +1850,12 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                {
                        actionstream<<"Server: A mismatched client tried to connect from "
                                        <<addr_s<<std::endl;
-                       infostream<<"Server: Cannot negotiate "
-                                       "serialization version with peer "
-                                       <<peer_id<<std::endl;
-                       SendAccessDenied(m_con, peer_id, std::wstring(
+                       infostream<<"Server: Cannot negotiate serialization version with "
+                                       <<addr_s<<std::endl;
+                       DenyAccess(peer_id, std::wstring(
                                        L"Your client's version is not supported.\n"
                                        L"Server version is ")
-                                       + narrow_to_wide(VERSION_STRING) + L"."
+                                       + narrow_to_wide(minetest_version_simple) + L"."
                        );
                        return;
                }
@@ -1838,7 +1889,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                                net_proto_version = max_net_proto_version;
                }
 
-               verbosestream<<"Server: "<<peer_id<<" Protocol version: min: "
+               verbosestream<<"Server: "<<addr_s<<": Protocol version: min: "
                                <<min_net_proto_version<<", max: "<<max_net_proto_version
                                <<", chosen: "<<net_proto_version<<std::endl;
 
@@ -1847,12 +1898,12 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                if(net_proto_version < SERVER_PROTOCOL_VERSION_MIN ||
                                net_proto_version > SERVER_PROTOCOL_VERSION_MAX)
                {
-                       actionstream<<"Server: A mismatched client tried to connect from "<<addr_s
-                                       <<std::endl;
-                       SendAccessDenied(m_con, peer_id, std::wstring(
+                       actionstream<<"Server: A mismatched client tried to connect from "
+                                       <<addr_s<<std::endl;
+                       DenyAccess(peer_id, std::wstring(
                                        L"Your client's version is not supported.\n"
                                        L"Server version is ")
-                                       + narrow_to_wide(VERSION_STRING) + L",\n"
+                                       + narrow_to_wide(minetest_version_simple) + L",\n"
                                        + L"server's PROTOCOL_VERSION is "
                                        + narrow_to_wide(itos(SERVER_PROTOCOL_VERSION_MIN))
                                        + L"..."
@@ -1871,10 +1922,10 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                        {
                                actionstream<<"Server: A mismatched (strict) client tried to "
                                                <<"connect from "<<addr_s<<std::endl;
-                               SendAccessDenied(m_con, peer_id, std::wstring(
+                               DenyAccess(peer_id, std::wstring(
                                                L"Your client's version is not supported.\n"
                                                L"Server version is ")
-                                               + narrow_to_wide(VERSION_STRING) + L",\n"
+                                               + narrow_to_wide(minetest_version_simple) + L",\n"
                                                + L"server's PROTOCOL_VERSION (strict) is "
                                                + narrow_to_wide(itos(LATEST_PROTOCOL_VERSION))
                                                + L", client's PROTOCOL_VERSION is "
@@ -1902,8 +1953,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                {
                        actionstream<<"Server: Player with an empty name "
                                        <<"tried to connect from "<<addr_s<<std::endl;
-                       SendAccessDenied(m_con, peer_id,
-                                       L"Empty name");
+                       DenyAccess(peer_id, L"Empty name");
                        return;
                }
 
@@ -1911,13 +1961,33 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                {
                        actionstream<<"Server: Player with an invalid name "
                                        <<"tried to connect from "<<addr_s<<std::endl;
-                       SendAccessDenied(m_con, peer_id,
-                                       L"Name contains unallowed characters");
+                       DenyAccess(peer_id, L"Name contains unallowed characters");
                        return;
                }
 
+               if(!isSingleplayer() && strcasecmp(playername, "singleplayer") == 0)
+               {
+                       actionstream<<"Server: Player with the name \"singleplayer\" "
+                                       <<"tried to connect from "<<addr_s<<std::endl;
+                       DenyAccess(peer_id, L"Name is not allowed");
+                       return;
+               }
+
+               {
+                       std::string reason;
+                       if(m_script->on_prejoinplayer(playername, addr_s, reason))
+                       {
+                               actionstream<<"Server: Player with the name \""<<playername<<"\" "
+                                               <<"tried to connect from "<<addr_s<<" "
+                                               <<"but it was disallowed for the following reason: "
+                                               <<reason<<std::endl;
+                               DenyAccess(peer_id, narrow_to_wide(reason.c_str()));
+                               return;
+                       }
+               }
+
                infostream<<"Server: New connection: \""<<playername<<"\" from "
-                               <<m_con.GetPeerAddress(peer_id).serializeString()<<std::endl;
+                               <<addr_s<<" (peer_id="<<peer_id<<")"<<std::endl;
 
                // Get password
                char given_password[PASSWORD_SIZE];
@@ -1936,9 +2006,25 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                }
 
                if(!base64_is_valid(given_password)){
-                       infostream<<"Server: "<<playername
+                       actionstream<<"Server: "<<playername
                                        <<" supplied invalid password hash"<<std::endl;
-                       SendAccessDenied(m_con, peer_id, L"Invalid password hash");
+                       DenyAccess(peer_id, L"Invalid password hash");
+                       return;
+               }
+
+               // Enforce user limit.
+               // Don't enforce for users that have some admin right
+               if(m_clients.size() >= g_settings->getU16("max_users") &&
+                               !checkPriv(playername, "server") &&
+                               !checkPriv(playername, "ban") &&
+                               !checkPriv(playername, "privs") &&
+                               !checkPriv(playername, "password") &&
+                               playername != g_settings->get("name"))
+               {
+                       actionstream<<"Server: "<<playername<<" tried to join, but there"
+                                       <<" are already max_users="
+                                       <<g_settings->getU16("max_users")<<" players."<<std::endl;
+                       DenyAccess(peer_id, L"Too many users.");
                        return;
                }
 
@@ -1950,7 +2036,9 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                        if(!isSingleplayer() &&
                                        g_settings->getBool("disallow_empty_password") &&
                                        std::string(given_password) == ""){
-                               SendAccessDenied(m_con, peer_id, L"Empty passwords are "
+                               actionstream<<"Server: "<<playername
+                                               <<" supplied empty password"<<std::endl;
+                               DenyAccess(peer_id, L"Empty passwords are "
                                                L"disallowed. Set a password and try again.");
                                return;
                        }
@@ -1969,41 +2057,16 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                has_auth = m_script->getAuth(playername, &checkpwd, NULL);
 
                if(!has_auth){
-                       SendAccessDenied(m_con, peer_id, L"Not allowed to login");
+                       actionstream<<"Server: "<<playername<<" cannot be authenticated"
+                                       <<" (auth handler does not work?)"<<std::endl;
+                       DenyAccess(peer_id, L"Not allowed to login");
                        return;
                }
 
                if(given_password != checkpwd){
-                       infostream<<"Server: peer_id="<<peer_id
-                                       <<": supplied invalid password for "
-                                       <<playername<<std::endl;
-                       SendAccessDenied(m_con, peer_id, L"Invalid password");
-                       return;
-               }
-
-               // Do not allow multiple players in simple singleplayer mode.
-               // This isn't a perfect way to do it, but will suffice for now.
-               if(m_simple_singleplayer_mode && m_clients.size() > 1){
-                       infostream<<"Server: Not allowing another client to connect in"
-                                       <<" simple singleplayer mode"<<std::endl;
-                       SendAccessDenied(m_con, peer_id,
-                                       L"Running in simple singleplayer mode.");
-                       return;
-               }
-
-               // Enforce user limit.
-               // Don't enforce for users that have some admin right
-               if(m_clients.size() >= g_settings->getU16("max_users") &&
-                               !checkPriv(playername, "server") &&
-                               !checkPriv(playername, "ban") &&
-                               !checkPriv(playername, "privs") &&
-                               !checkPriv(playername, "password") &&
-                               playername != g_settings->get("name"))
-               {
-                       actionstream<<"Server: "<<playername<<" tried to join, but there"
-                                       <<" are already max_users="
-                                       <<g_settings->getU16("max_users")<<" players."<<std::endl;
-                       SendAccessDenied(m_con, peer_id, L"Too many users.");
+                       actionstream<<"Server: "<<playername<<" supplied wrong password"
+                                       <<std::endl;
+                       DenyAccess(peer_id, L"Wrong password");
                        return;
                }
 
@@ -2013,8 +2076,19 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                // If failed, cancel
                if(playersao == NULL)
                {
-                       errorstream<<"Server: peer_id="<<peer_id
-                                       <<": failed to emerge player"<<std::endl;
+                       RemotePlayer *player =
+                                       static_cast<RemotePlayer*>(m_env->getPlayer(playername));
+                       if(player && player->peer_id != 0){
+                               errorstream<<"Server: "<<playername<<": Failed to emerge player"
+                                               <<" (player allocated to an another client)"<<std::endl;
+                               DenyAccess(peer_id, L"Another client is connected with this "
+                                               L"name. If your client closed unexpectedly, try again in "
+                                               L"a minute.");
+                       } else {
+                               errorstream<<"Server: "<<playername<<": Failed to emerge player"
+                                               <<std::endl;
+                               DenyAccess(peer_id, L"Could not allocate player.");
+                       }
                        return;
                }
 
@@ -2090,6 +2164,9 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                if(g_settings->getBool("enable_damage"))
                        SendPlayerHP(peer_id);
 
+               // Send Breath
+               SendPlayerBreath(peer_id);
+
                // Send detached inventories
                sendDetachedInventories(peer_id);
 
@@ -2099,9 +2176,9 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 
                // Send time of day
                {
-                       SharedBuffer<u8> data = makePacket_TOCLIENT_TIME_OF_DAY(
-                                       m_env->getTimeOfDay(), g_settings->getFloat("time_speed"));
-                       m_con.Send(peer_id, 0, data, true);
+                       u16 time = m_env->getTimeOfDay();
+                       float time_speed = g_settings->getFloat("time_speed");
+                       SendTimeOfDay(peer_id, time, time_speed);
                }
 
                // Note things in chat if not in simple singleplayer mode
@@ -2153,7 +2230,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                                os<<player->getName()<<" ";
                        }
 
-                       actionstream<<player->getName()<<" joins game. List of players: "
+                       actionstream<<player->getName()<<" ["<<addr_s<<"] "<<"joins game. List of players: "
                                        <<os.str()<<std::endl;
                }
 
@@ -2217,6 +2294,12 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                player->control.LMB = (bool)(keyPressed&128);
                player->control.RMB = (bool)(keyPressed&256);
 
+               bool cheated = playersao->checkMovementCheat();
+               if(cheated){
+                       // Call callbacks
+                       m_script->on_cheat(playersao, "moved_too_fast");
+               }
+
                /*infostream<<"Server::ProcessData(): Moved player "<<peer_id<<" to "
                                <<"("<<position.X<<","<<position.Y<<","<<position.Z<<")"
                                <<" pitch="<<pitch<<" yaw="<<yaw<<std::endl;*/
@@ -2401,6 +2484,18 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 
                        setInventoryModified(da->from_inv);
 
+                       /*
+                               Disable dropping items out of craftpreview
+                       */
+                       if(da->from_list == "craftpreview")
+                       {
+                               infostream<<"Ignoring IDropAction from "
+                                               <<(da->from_inv.dump())<<":"<<da->from_list
+                                               <<" because src is "<<da->from_list<<std::endl;
+                               delete a;
+                               return;
+                       }
+
                        // Disallow dropping items if not allowed to interact
                        if(!checkPriv(player->getName(), "interact"))
                        {
@@ -2556,6 +2651,13 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                                SendPlayerHP(peer_id);
                }
        }
+       else if(command == TOSERVER_BREATH)
+       {
+               std::string datastring((char*)&data[2], datasize-2);
+               std::istringstream is(datastring, std::ios_base::binary);
+               u16 breath = readU16(is);
+               playersao->setBreath(breath);
+       }
        else if(command == TOSERVER_PASSWORD)
        {
                /*
@@ -2645,7 +2747,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                std::string datastring((char*)&data[2], datasize-2);
                std::istringstream is(datastring, std::ios_base::binary);
 
-               std::list<MediaRequest> tosend;
+               std::list<std::string> tosend;
                u16 numfiles = readU16(is);
 
                infostream<<"Sending "<<numfiles<<" files to "
@@ -2654,7 +2756,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 
                for(int i = 0; i < numfiles; i++) {
                        std::string name = deSerializeString(is);
-                       tosend.push_back(MediaRequest(name));
+                       tosend.push_back(name);
                        verbosestream<<"TOSERVER_REQUEST_MEDIA: requested file "
                                        <<name<<std::endl;
                }
@@ -2756,6 +2858,8 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                                RemoteClient *client = getClient(peer_id);
                                v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
                                client->SetBlockNotSent(blockpos);
+                               // Call callbacks
+                               m_script->on_cheat(playersao, "interacted_too_far");
                                // Do nothing else
                                return;
                        }
@@ -2884,6 +2988,8 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                                                                <<PP(nocheat_p)<<" and completed digging "
                                                                <<PP(p_under)<<"; not digging."<<std::endl;
                                                is_valid_dig = false;
+                                               // Call callbacks
+                                               m_script->on_cheat(playersao, "finished_unknown_dig");
                                        }
                                        // Get player's wielded item
                                        ItemStack playeritem;
@@ -2909,15 +3015,33 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                                                                <<", which is not diggable with tool. not digging."
                                                                <<std::endl;
                                                is_valid_dig = false;
+                                               // Call callbacks
+                                               m_script->on_cheat(playersao, "dug_unbreakable");
+                                       }
+                                       // Check digging time
+                                       // If already invalidated, we don't have to
+                                       if(!is_valid_dig){
+                                               // Well not our problem then
+                                       }
+                                       // Clean and long dig
+                                       else if(params.time > 2.0 && nocheat_t * 1.2 > params.time){
+                                               // All is good, but grab time from pool; don't care if
+                                               // it's actually available
+                                               playersao->getDigPool().grab(params.time);
                                        }
-                                       // If time is considerably too short, ignore dig
-                                       // Check time only for medium and slow timed digs
-                                       if(params.diggable && params.time > 0.3 && nocheat_t < 0.5 * params.time){
+                                       // Short or laggy dig
+                                       // Try getting the time from pool
+                                       else if(playersao->getDigPool().grab(params.time)){
+                                               // All is good
+                                       }
+                                       // Dig not possible
+                                       else{
                                                infostream<<"Server: NoCheat: "<<player->getName()
-                                                               <<" completed digging "
-                                                               <<PP(p_under)<<" in "<<nocheat_t<<"s; expected "
-                                                               <<params.time<<"s; not digging."<<std::endl;
+                                                               <<" completed digging "<<PP(p_under)
+                                                               <<"too fast; not digging."<<std::endl;
                                                is_valid_dig = false;
+                                               // Call callbacks
+                                               m_script->on_cheat(playersao, "dug_too_fast");
                                        }
                                }
 
@@ -3096,6 +3220,12 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
        }
 }
 
+void Server::setTimeOfDay(u32 time)
+{
+       m_env->setTimeOfDay(time);
+       m_time_of_day_send_timer = 0;
+}
+
 void Server::onMapEditEvent(MapEditEvent *event)
 {
        //infostream<<"Server::onMapEditEvent()"<<std::endl;
@@ -3186,48 +3316,6 @@ void Server::setInventoryModified(const InventoryLocation &loc)
        }
 }
 
-//std::list<PlayerInfo> Server::getPlayerInfo()
-//{
-//     DSTACK(__FUNCTION_NAME);
-//     JMutexAutoLock envlock(m_env_mutex);
-//     JMutexAutoLock conlock(m_con_mutex);
-//
-//     std::list<PlayerInfo> list;
-//
-//     std::list<Player*> players = m_env->getPlayers();
-//
-//     std::list<Player*>::iterator i;
-//     for(i = players.begin();
-//                     i != players.end(); ++i)
-//     {
-//             PlayerInfo info;
-//
-//             Player *player = *i;
-//
-//             try{
-//                     // Copy info from connection to info struct
-//                     info.id = player->peer_id;
-//                     info.address = m_con.GetPeerAddress(player->peer_id);
-//                     info.avg_rtt = m_con.GetPeerAvgRTT(player->peer_id);
-//             }
-//             catch(con::PeerNotFoundException &e)
-//             {
-//                     // Set dummy peer info
-//                     info.id = 0;
-//                     info.address = Address(0,0,0,0,0);
-//                     info.avg_rtt = 0.0;
-//             }
-//
-//             snprintf(info.name, PLAYERNAME_SIZE, "%s", player->getName());
-//             info.position = player->getPosition();
-//
-//             list.push_back(info);
-//     }
-//
-//     return list;
-//}
-
-
 void Server::peerAdded(con::Peer *peer)
 {
        DSTACK(__FUNCTION_NAME);
@@ -3299,6 +3387,21 @@ void Server::SendHP(con::Connection &con, u16 peer_id, u8 hp)
        con.Send(peer_id, 0, data, true);
 }
 
+void Server::SendBreath(con::Connection &con, u16 peer_id, u16 breath)
+{
+       DSTACK(__FUNCTION_NAME);
+       std::ostringstream os(std::ios_base::binary);
+
+       writeU16(os, TOCLIENT_BREATH);
+       writeU16(os, breath);
+
+       // Make data buffer
+       std::string s = os.str();
+       SharedBuffer<u8> data((u8*)s.c_str(), s.size());
+       // Send as reliable
+       con.Send(peer_id, 0, data, true);
+}
+
 void Server::SendAccessDenied(con::Connection &con, u16 peer_id,
                const std::wstring &reason)
 {
@@ -3619,7 +3722,7 @@ void Server::SendHUDAdd(u16 peer_id, u32 id, HudElement *form)
        std::string s = os.str();
        SharedBuffer<u8> data((u8*)s.c_str(), s.size());
        // Send as reliable
-       m_con.Send(peer_id, 0, data, true);
+       m_con.Send(peer_id, 1, data, true);
 }
 
 void Server::SendHUDRemove(u16 peer_id, u32 id)
@@ -3634,7 +3737,8 @@ void Server::SendHUDRemove(u16 peer_id, u32 id)
        std::string s = os.str();
        SharedBuffer<u8> data((u8*)s.c_str(), s.size());
        // Send as reliable
-       m_con.Send(peer_id, 0, data, true);
+
+       m_con.Send(peer_id, 1, data, true);
 }
 
 void Server::SendHUDChange(u16 peer_id, u32 id, HudElementStat stat, void *value)
@@ -3719,6 +3823,20 @@ void Server::BroadcastChatMessage(const std::wstring &message)
        }
 }
 
+void Server::SendTimeOfDay(u16 peer_id, u16 time, f32 time_speed)
+{
+       DSTACK(__FUNCTION_NAME);
+
+       // Make packet
+       SharedBuffer<u8> data(2+2+4);
+       writeU16(&data[0], TOCLIENT_TIME_OF_DAY);
+       writeU16(&data[2], time);
+       writeF1000(&data[4], time_speed);
+
+       // Send as reliable
+       m_con.Send(peer_id, 0, data, true);
+}
+
 void Server::SendPlayerHP(u16 peer_id)
 {
        DSTACK(__FUNCTION_NAME);
@@ -3726,6 +3844,20 @@ void Server::SendPlayerHP(u16 peer_id)
        assert(playersao);
        playersao->m_hp_not_sent = false;
        SendHP(m_con, peer_id, playersao->getHP());
+
+       // Send to other clients
+       std::string str = gob_cmd_punched(playersao->readDamage(), playersao->getHP());
+       ActiveObjectMessage aom(playersao->getId(), true, str);
+       playersao->m_messages_out.push_back(aom);
+}
+
+void Server::SendPlayerBreath(u16 peer_id)
+{
+       DSTACK(__FUNCTION_NAME);
+       PlayerSAO *playersao = getPlayerSAO(peer_id);
+       assert(playersao);
+       playersao->m_breath_not_sent = false;
+       SendBreath(m_con, peer_id, playersao->getBreath());
 }
 
 void Server::SendMovePlayer(u16 peer_id)
@@ -3952,7 +4084,8 @@ void Server::sendRemoveNode(v3s16 p, u16 ignore_id,
 }
 
 void Server::sendAddNode(v3s16 p, MapNode n, u16 ignore_id,
-               std::list<u16> *far_players, float far_d_nodes)
+               std::list<u16> *far_players, float far_d_nodes,
+               bool remove_metadata)
 {
        float maxd = far_d_nodes*BS;
        v3f p_f = intToFloat(p, BS);
@@ -3988,13 +4121,23 @@ void Server::sendAddNode(v3s16 p, MapNode n, u16 ignore_id,
                }
 
                // Create packet
-               u32 replysize = 8 + MapNode::serializedLength(client->serialization_version);
+               u32 replysize = 9 + MapNode::serializedLength(client->serialization_version);
                SharedBuffer<u8> reply(replysize);
                writeU16(&reply[0], TOCLIENT_ADDNODE);
                writeS16(&reply[2], p.X);
                writeS16(&reply[4], p.Y);
                writeS16(&reply[6], p.Z);
                n.serialize(&reply[8], client->serialization_version);
+               u32 index = 8 + MapNode::serializedLength(client->serialization_version);
+               writeU8(&reply[index], remove_metadata ? 0 : 1);
+               
+               if (!remove_metadata) {
+                       if (client->net_proto_version <= 21) {
+                               // Old clients always clear metadata; fix it
+                               // by sending the full block again.
+                               client->SetBlockNotSent(p);
+                       }
+               }
 
                // Send as reliable
                m_con.Send(client->peer_id, 0, reply, true);
@@ -4012,7 +4155,7 @@ void Server::setBlockNotSent(v3s16 p)
        }
 }
 
-void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver)
+void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver, u16 net_proto_version)
 {
        DSTACK(__FUNCTION_NAME);
 
@@ -4045,6 +4188,7 @@ void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver)
 
        std::ostringstream os(std::ios_base::binary);
        block->serialize(os, ver, false);
+       block->serializeNetworkSpecific(os, net_proto_version);
        std::string s = os.str();
        SharedBuffer<u8> blockdata((u8*)s.c_str(), s.size());
 
@@ -4062,7 +4206,7 @@ void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver)
        /*
                Send packet
        */
-       m_con.Send(peer_id, 1, reply, true);
+       m_con.Send(peer_id, 2, reply, true);
 }
 
 void Server::SendBlocks(float dtime)
@@ -4126,9 +4270,13 @@ void Server::SendBlocks(float dtime)
                        continue;
                }
 
-               RemoteClient *client = getClient(q.peer_id);
+               RemoteClient *client = getClientNoEx(q.peer_id);
+               if(!client)
+                       continue;
+               if(client->denied)
+                       continue;
 
-               SendBlockNoLock(q.peer_id, block, client->serialization_version);
+               SendBlockNoLock(q.peer_id, block, client->serialization_version, client->net_proto_version);
 
                client->SentBlock(q.pos);
 
@@ -4152,8 +4300,7 @@ void Server::fillMediaCache()
                paths.push_back(mod.path + DIR_DELIM + "media");
                paths.push_back(mod.path + DIR_DELIM + "models");
        }
-       std::string path_all = "textures";
-       paths.push_back(path_all + DIR_DELIM + "all");
+       paths.push_back(porting::path_user + DIR_DELIM + "textures" + DIR_DELIM + "server");
 
        // Collect media file information from paths into cache
        for(std::list<std::string>::iterator i = paths.begin();
@@ -4309,7 +4456,7 @@ struct SendableMedia
 };
 
 void Server::sendRequestedMedia(u16 peer_id,
-               const std::list<MediaRequest> &tosend)
+               const std::list<std::string> &tosend)
 {
        DSTACK(__FUNCTION_NAME);
 
@@ -4326,17 +4473,19 @@ void Server::sendRequestedMedia(u16 peer_id,
 
        u32 file_size_bunch_total = 0;
 
-       for(std::list<MediaRequest>::const_iterator i = tosend.begin();
+       for(std::list<std::string>::const_iterator i = tosend.begin();
                        i != tosend.end(); ++i)
        {
-               if(m_media.find(i->name) == m_media.end()){
+               const std::string &name = *i;
+
+               if(m_media.find(name) == m_media.end()){
                        errorstream<<"Server::sendRequestedMedia(): Client asked for "
-                                       <<"unknown file \""<<(i->name)<<"\""<<std::endl;
+                                       <<"unknown file \""<<(name)<<"\""<<std::endl;
                        continue;
                }
 
                //TODO get path + name
-               std::string tpath = m_media[(*i).name].path;
+               std::string tpath = m_media[name].path;
 
                // Read data
                std::ifstream fis(tpath.c_str(), std::ios_base::binary);
@@ -4362,14 +4511,14 @@ void Server::sendRequestedMedia(u16 peer_id,
                }
                if(bad){
                        errorstream<<"Server::sendRequestedMedia(): Failed to read \""
-                                       <<(*i).name<<"\""<<std::endl;
+                                       <<name<<"\""<<std::endl;
                        continue;
                }
                /*infostream<<"Server::sendRequestedMedia(): Loaded \""
                                <<tname<<"\""<<std::endl;*/
                // Put in list
                file_bunches[file_bunches.size()-1].push_back(
-                               SendableMedia((*i).name, tpath, tmp_os.str()));
+                               SendableMedia(name, tpath, tmp_os.str()));
 
                // Start next bunch if got enough data
                if(file_size_bunch_total >= bytes_per_bunch){
@@ -4419,7 +4568,7 @@ void Server::sendRequestedMedia(u16 peer_id,
                                <<" size=" <<s.size()<<std::endl;
                SharedBuffer<u8> data((u8*)s.c_str(), s.size());
                // Send as reliable
-               m_con.Send(peer_id, 0, data, true);
+               m_con.Send(peer_id, 2, data, true);
        }
 }
 
@@ -4512,6 +4661,138 @@ void Server::RespawnPlayer(u16 peer_id)
        }
 }
 
+void Server::DenyAccess(u16 peer_id, const std::wstring &reason)
+{
+       DSTACK(__FUNCTION_NAME);
+
+       SendAccessDenied(m_con, peer_id, reason);
+
+       RemoteClient *client = getClientNoEx(peer_id);
+       if(client)
+               client->denied = true;
+
+       // If there are way too many clients, get rid of denied new ones immediately
+       if((int)m_clients.size() > 2 * g_settings->getU16("max_users")){
+               verbosestream<<"Server: DenyAccess: Too many clients; getting rid of "
+                               <<"peer_id="<<peer_id<<" immediately"<<std::endl;
+               // Delete peer to stop sending it data
+               m_con.DeletePeer(peer_id);
+               // Delete client also to stop block sends and other stuff
+               DeleteClient(peer_id, CDR_DENY);
+       }
+}
+
+void Server::DeleteClient(u16 peer_id, ClientDeletionReason reason)
+{
+       DSTACK(__FUNCTION_NAME);
+
+       // Error check
+       std::map<u16, RemoteClient*>::iterator n;
+       n = m_clients.find(peer_id);
+       // The client may not exist; clients are immediately removed if their
+       // access is denied, and this event occurs later then.
+       if(n == m_clients.end())
+               return;
+
+       /*
+               Mark objects to be not known by the client
+       */
+       RemoteClient *client = n->second;
+       // Handle objects
+       for(std::set<u16>::iterator
+                       i = client->m_known_objects.begin();
+                       i != client->m_known_objects.end(); ++i)
+       {
+               // Get object
+               u16 id = *i;
+               ServerActiveObject* obj = m_env->getActiveObject(id);
+
+               if(obj && obj->m_known_by_count > 0)
+                       obj->m_known_by_count--;
+       }
+
+       /*
+               Clear references to playing sounds
+       */
+       for(std::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.size() == 0)
+                       m_playing_sounds.erase(i++);
+               else
+                       i++;
+       }
+
+       Player *player = m_env->getPlayer(peer_id);
+
+       // Collect information about leaving in chat
+       std::wstring message;
+       {
+               if(player != NULL && reason != CDR_DENY)
+               {
+                       std::wstring name = narrow_to_wide(player->getName());
+                       message += L"*** ";
+                       message += name;
+                       message += L" left the game.";
+                       if(reason == CDR_TIMEOUT)
+                               message += L" (timed out)";
+               }
+       }
+
+       /* Run scripts and remove from environment */
+       {
+               if(player != NULL)
+               {
+                       PlayerSAO *playersao = player->getPlayerSAO();
+                       assert(playersao);
+
+                       m_script->on_leaveplayer(playersao);
+
+                       playersao->disconnected();
+               }
+       }
+
+       /*
+               Print out action
+       */
+       {
+               if(player != NULL && reason != CDR_DENY)
+               {
+                       std::ostringstream os(std::ios_base::binary);
+                       for(std::map<u16, RemoteClient*>::iterator
+                               i = m_clients.begin();
+                               i != m_clients.end(); ++i)
+                       {
+                               RemoteClient *client = i->second;
+                               assert(client->peer_id == i->first);
+                               if(client->serialization_version == SER_FMT_VER_INVALID)
+                                       continue;
+                               // Get player
+                               Player *player = m_env->getPlayer(client->peer_id);
+                               if(!player)
+                                       continue;
+                               // Get name of player
+                               os<<player->getName()<<" ";
+                       }
+
+                       actionstream<<player->getName()<<" "
+                                       <<(reason==CDR_TIMEOUT?"times out.":"leaves game.")
+                                       <<" List of players: "<<os.str()<<std::endl;
+               }
+       }
+
+       // Delete client
+       delete m_clients[peer_id];
+       m_clients.erase(peer_id);
+
+       // Send leave chat message to all remaining clients
+       if(message.length() != 0)
+               BroadcastChatMessage(message);
+}
+
 void Server::UpdateCrafting(u16 peer_id)
 {
        DSTACK(__FUNCTION_NAME);
@@ -4521,7 +4802,10 @@ void Server::UpdateCrafting(u16 peer_id)
 
        // Get a preview for crafting
        ItemStack preview;
+       InventoryLocation loc;
+       loc.setPlayer(player->getName());
        getCraftingResult(&player->inventory, preview, false, this);
+       m_env->getScriptIface()->item_CraftPredict(preview, player->getPlayerSAO(), (&player->inventory)->getList("craft"), loc);
 
        // Put the new preview in
        InventoryList *plist = player->inventory.getList("craftpreview");
@@ -4532,23 +4816,48 @@ void Server::UpdateCrafting(u16 peer_id)
 
 RemoteClient* Server::getClient(u16 peer_id)
 {
-       DSTACK(__FUNCTION_NAME);
-       //JMutexAutoLock lock(m_con_mutex);
+       RemoteClient *client = getClientNoEx(peer_id);
+       if(!client)
+               throw ClientNotFoundException("Client not found");
+       return client;
+}
+RemoteClient* Server::getClientNoEx(u16 peer_id)
+{
        std::map<u16, RemoteClient*>::iterator n;
        n = m_clients.find(peer_id);
-       // A client should exist for all peers
-       assert(n != m_clients.end());
+       // The client may not exist; clients are immediately removed if their
+       // access is denied, and this event occurs later then.
+       if(n == m_clients.end())
+               return NULL;
        return n->second;
 }
 
+std::string Server::getPlayerName(u16 peer_id)
+{
+       Player *player = m_env->getPlayer(peer_id);
+       if(player == NULL)
+               return "[id="+itos(peer_id)+"]";
+       return player->getName();
+}
+
+PlayerSAO* Server::getPlayerSAO(u16 peer_id)
+{
+       Player *player = m_env->getPlayer(peer_id);
+       if(player == NULL)
+               return NULL;
+       return player->getPlayerSAO();
+}
+
 std::wstring Server::getStatusString()
 {
        std::wostringstream os(std::ios_base::binary);
        os<<L"# Server: ";
        // Version
-       os<<L"version="<<narrow_to_wide(VERSION_STRING);
+       os<<L"version="<<narrow_to_wide(minetest_version_simple);
        // Uptime
        os<<L", uptime="<<m_uptime.get();
+       // Max lag estimate
+       os<<L", max_lag="<<m_env->getMaxLagEstimate();
        // Information about clients
        std::map<u16, RemoteClient*>::iterator i;
        bool first;
@@ -4627,11 +4936,19 @@ void Server::reportInventoryFormspecModified(const std::string &name)
        SendPlayerInventoryFormspec(player->peer_id);
 }
 
-// Saves g_settings to configpath given at initialization
-void Server::saveConfig()
+void Server::setIpBanned(const std::string &ip, const std::string &name)
+{
+       m_banmanager->add(ip, name);
+}
+
+void Server::unsetIpBanned(const std::string &ip_or_name)
 {
-       if(m_path_config != "")
-               g_settings->updateConfigFile(m_path_config.c_str());
+       m_banmanager->remove(ip_or_name);
+}
+
+std::string Server::getBanDescription(const std::string &ip_or_name)
+{
+       return m_banmanager->getBanDescription(ip_or_name);
 }
 
 void Server::notifyPlayer(const char *name, const std::wstring msg, const bool prepend = true)
@@ -4663,7 +4980,7 @@ u32 Server::hudAdd(Player *player, HudElement *form) {
        if (!player)
                return -1;
 
-       u32 id = hud_get_free_id(player);
+       u32 id = player->getFreeHudID();
        if (id < player->hud.size())
                player->hud[id] = form;
        else
@@ -4712,6 +5029,20 @@ bool Server::hudSetHotbarItemcount(Player *player, s32 hotbar_itemcount) {
        return true;
 }
 
+void Server::hudSetHotbarImage(Player *player, std::string name) {
+       if (!player)
+               return;
+
+       SendHUDSetParam(player->peer_id, HUD_PARAM_HOTBAR_IMAGE, name);
+}
+
+void Server::hudSetHotbarSelectedImage(Player *player, std::string name) {
+       if (!player)
+               return;
+
+       SendHUDSetParam(player->peer_id, HUD_PARAM_HOTBAR_SELECTED_IMAGE, name);
+}
+
 void Server::notifyPlayers(const std::wstring msg)
 {
        BroadcastChatMessage(msg);
@@ -4822,11 +5153,6 @@ void Server::deleteParticleSpawnerAll(u32 id)
        SendDeleteParticleSpawnerAll(id);
 }
 
-void Server::queueBlockEmerge(v3s16 blockpos, bool allow_generate)
-{
-       m_emerge->enqueueBlockEmerge(PEER_ID_INEXISTENT, blockpos, allow_generate);
-}
-
 Inventory* Server::createDetachedInventory(const std::string &name)
 {
        if(m_detached_inventories.count(name) > 0){
@@ -5141,113 +5467,7 @@ void Server::handlePeerChange(PeerChange &c)
                        Delete
                */
 
-               // Error check
-               std::map<u16, RemoteClient*>::iterator n;
-               n = m_clients.find(c.peer_id);
-               // The client should exist
-               assert(n != m_clients.end());
-
-               /*
-                       Mark objects to be not known by the client
-               */
-               RemoteClient *client = n->second;
-               // Handle objects
-               for(std::set<u16>::iterator
-                               i = client->m_known_objects.begin();
-                               i != client->m_known_objects.end(); ++i)
-               {
-                       // Get object
-                       u16 id = *i;
-                       ServerActiveObject* obj = m_env->getActiveObject(id);
-
-                       if(obj && obj->m_known_by_count > 0)
-                               obj->m_known_by_count--;
-               }
-
-               /*
-                       Clear references to playing sounds
-               */
-               for(std::map<s32, ServerPlayingSound>::iterator
-                               i = m_playing_sounds.begin();
-                               i != m_playing_sounds.end();)
-               {
-                       ServerPlayingSound &psound = i->second;
-                       psound.clients.erase(c.peer_id);
-                       if(psound.clients.size() == 0)
-                               m_playing_sounds.erase(i++);
-                       else
-                               i++;
-               }
-
-               Player *player = m_env->getPlayer(c.peer_id);
-
-               // Collect information about leaving in chat
-               std::wstring message;
-               {
-                       if(player != NULL)
-                       {
-                               std::wstring name = narrow_to_wide(player->getName());
-                               message += L"*** ";
-                               message += name;
-                               message += L" left the game.";
-                               if(c.timeout)
-                                       message += L" (timed out)";
-                       }
-               }
-
-               /* Run scripts and remove from environment */
-               {
-                       if(player != NULL)
-                       {
-                               PlayerSAO *playersao = player->getPlayerSAO();
-                               assert(playersao);
-
-                               m_script->on_leaveplayer(playersao);
-
-                               playersao->disconnected();
-                       }
-               }
-
-               /*
-                       Print out action
-               */
-               {
-                       if(player != NULL)
-                       {
-                               std::ostringstream os(std::ios_base::binary);
-                               for(std::map<u16, RemoteClient*>::iterator
-                                       i = m_clients.begin();
-                                       i != m_clients.end(); ++i)
-                               {
-                                       RemoteClient *client = i->second;
-                                       assert(client->peer_id == i->first);
-                                       if(client->serialization_version == SER_FMT_VER_INVALID)
-                                               continue;
-                                       // Get player
-                                       Player *player = m_env->getPlayer(client->peer_id);
-                                       if(!player)
-                                               continue;
-                                       // Get name of player
-                                       os<<player->getName()<<" ";
-                               }
-
-                               actionstream<<player->getName()<<" "
-                                               <<(c.timeout?"times out.":"leaves game.")
-                                               <<" List of players: "
-                                               <<os.str()<<std::endl;
-                       }
-               }
-
-               // Delete client
-               delete m_clients[c.peer_id];
-               m_clients.erase(c.peer_id);
-
-               // Send player info to all remaining clients
-               //SendPlayerInfos();
-
-               // Send leave chat message to all remaining clients
-               if(message.length() != 0)
-                       BroadcastChatMessage(message);
+               DeleteClient(c.peer_id, c.timeout?CDR_TIMEOUT:CDR_LEAVE);
 
        } // PEER_REMOVED
        else