]> git.lizzy.rs Git - minetest.git/blobdiff - src/network/serverpackethandler.cpp
Dual wielding
[minetest.git] / src / network / serverpackethandler.cpp
index 4d79f375c7028ef5e47eba16c26bed91b594f22d..1e49538dc863a9babddd44952e9d3209b46b3fe3 100644 (file)
@@ -40,6 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "util/pointedthing.h"
 #include "util/serialize.h"
 #include "util/srp.h"
+#include "clientdynamicinfo.h"
 
 void Server::handleCommand_Deprecated(NetworkPacket* pkt)
 {
@@ -86,7 +87,7 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
 
        // 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.getClientIDs().size() > 1) {
+       if (m_simple_singleplayer_mode && !m_clients.getClientIDs().empty()) {
                infostream << "Server: Not allowing another client (" << addr_s <<
                        ") to connect in simple singleplayer mode" << std::endl;
                DenyAccess(peer_id, SERVER_ACCESSDENIED_SINGLEPLAYER);
@@ -108,8 +109,10 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
        // Use the highest version supported by both
        u8 depl_serial_v = std::min(client_max, our_max);
        // If it's lower than the lowest supported, give up.
+#if SER_FMT_VER_LOWEST_READ > 0
        if (depl_serial_v < SER_FMT_VER_LOWEST_READ)
                depl_serial_v = SER_FMT_VER_INVALID;
+#endif
 
        if (depl_serial_v == SER_FMT_VER_INVALID) {
                actionstream << "Server: A mismatched client tried to connect from " <<
@@ -174,6 +177,16 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
                return;
        }
 
+       RemotePlayer *player = m_env->getPlayer(playername);
+
+       // If player is already connected, cancel
+       if (player && player->getPeerId() != PEER_ID_INEXISTENT) {
+               actionstream << "Server: Player with name \"" << playername <<
+                       "\" tried to connect, but player with same name is already connected" << std::endl;
+               DenyAccess(peer_id, SERVER_ACCESSDENIED_ALREADY_CONNECTED);
+               return;
+       }
+
        m_clients.setPlayerName(peer_id, playername);
        //TODO (later) case insensitivity
 
@@ -217,7 +230,7 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
                Compose auth methods for answer
        */
        std::string encpwd; // encrypted Password field for the user
-       bool has_auth = m_script->getAuth(playername, &encpwd, NULL);
+       bool has_auth = m_script->getAuth(playername, &encpwd, nullptr);
        u32 auth_mechs = 0;
 
        client->chosen_mech = AUTH_MECHANISM_NONE;
@@ -352,16 +365,15 @@ void Server::handleCommand_RequestMedia(NetworkPacket* pkt)
        session_t peer_id = pkt->getPeerId();
        infostream << "Sending " << numfiles << " files to " <<
                getPlayerName(peer_id) << std::endl;
-       verbosestream << "TOSERVER_REQUEST_MEDIA: " << std::endl;
+       verbosestream << "TOSERVER_REQUEST_MEDIA: requested file(s)" << std::endl;
 
        for (u16 i = 0; i < numfiles; i++) {
                std::string name;
 
                *pkt >> name;
 
-               tosend.push_back(name);
-               verbosestream << "TOSERVER_REQUEST_MEDIA: requested file "
-                               << name << std::endl;
+               tosend.emplace_back(name);
+               verbosestream << "  " << name << std::endl;
        }
 
        sendRequestedMedia(peer_id, tosend);
@@ -371,55 +383,47 @@ void Server::handleCommand_ClientReady(NetworkPacket* pkt)
 {
        session_t peer_id = pkt->getPeerId();
 
-       PlayerSAO* playersao = StageTwoClientInit(peer_id);
-
-       if (playersao == NULL) {
-               errorstream << "TOSERVER_CLIENT_READY stage 2 client init failed "
-                       "peer_id=" << peer_id << std::endl;
-               DisconnectPeer(peer_id);
-               return;
-       }
-
-
-       if (pkt->getSize() < 8) {
-               errorstream << "TOSERVER_CLIENT_READY client sent inconsistent data, "
-                       "disconnecting peer_id: " << peer_id << std::endl;
-               DisconnectPeer(peer_id);
-               return;
-       }
-
+       // decode all information first
        u8 major_ver, minor_ver, patch_ver, reserved;
+       u16 formspec_ver = 1; // v1 for clients older than 5.1.0-dev
        std::string full_ver;
+
        *pkt >> major_ver >> minor_ver >> patch_ver >> reserved >> full_ver;
+       if (pkt->getRemainingBytes() >= 2)
+               *pkt >> formspec_ver;
 
        m_clients.setClientVersion(peer_id, major_ver, minor_ver, patch_ver,
                full_ver);
 
-       if (pkt->getRemainingBytes() >= 2)
-               *pkt >> playersao->getPlayer()->formspec_version;
-
-       const std::vector<std::string> &players = m_clients.getPlayerNames();
-       NetworkPacket list_pkt(TOCLIENT_UPDATE_PLAYER_LIST, 0, peer_id);
-       list_pkt << (u8) PLAYER_LIST_INIT << (u16) players.size();
-       for (const std::string &player: players) {
-               list_pkt <<  player;
+       // Emerge player
+       PlayerSAO* playersao = StageTwoClientInit(peer_id);
+       if (!playersao) {
+               errorstream << "Server: stage 2 client init failed "
+                       "peer_id=" << peer_id << std::endl;
+               DisconnectPeer(peer_id);
+               return;
        }
-       m_clients.send(peer_id, 0, &list_pkt, true);
 
-       NetworkPacket notice_pkt(TOCLIENT_UPDATE_PLAYER_LIST, 0, PEER_ID_INEXISTENT);
-       // (u16) 1 + std::string represents a pseudo vector serialization representation
-       notice_pkt << (u8) PLAYER_LIST_ADD << (u16) 1 << std::string(playersao->getPlayer()->getName());
-       m_clients.sendToAll(&notice_pkt);
+       playersao->getPlayer()->formspec_version = formspec_ver;
        m_clients.event(peer_id, CSE_SetClientReady);
 
+       // Send player list to this client
+       {
+               const std::vector<std::string> &players = m_clients.getPlayerNames();
+               NetworkPacket list_pkt(TOCLIENT_UPDATE_PLAYER_LIST, 0, peer_id);
+               list_pkt << (u8) PLAYER_LIST_INIT << (u16) players.size();
+               for (const auto &player : players)
+                       list_pkt << player;
+               Send(peer_id, &list_pkt);
+       }
+
        s64 last_login;
        m_script->getAuth(playersao->getPlayer()->getName(), nullptr, nullptr, &last_login);
        m_script->on_joinplayer(playersao, last_login);
 
        // Send shutdown timer if shutdown has been scheduled
-       if (m_shutdown_state.isTimerRunning()) {
+       if (m_shutdown_state.isTimerRunning())
                SendChatMessage(peer_id, m_shutdown_state.getShutdownTimerMessage());
-       }
 }
 
 void Server::handleCommand_GotBlocks(NetworkPacket* pkt)
@@ -443,7 +447,7 @@ void Server::handleCommand_GotBlocks(NetworkPacket* pkt)
                                ("GOTBLOCKS length is too short");
        }
 
-       m_clients.lock();
+       ClientInterface::AutoLock lock(m_clients);
        RemoteClient *client = m_clients.lockedGetClientNoEx(pkt->getPeerId());
 
        for (u16 i = 0; i < count; i++) {
@@ -451,7 +455,6 @@ void Server::handleCommand_GotBlocks(NetworkPacket* pkt)
                *pkt >> p;
                client->GotBlock(p);
        }
-       m_clients.unlock();
 }
 
 void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao,
@@ -473,7 +476,6 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao,
        f32 yaw = (f32)f32yaw / 100.0f;
        u32 keyPressed = 0;
 
-       // default behavior (in case an old client doesn't send these)
        f32 fov = 0;
        u8 wanted_range = 0;
 
@@ -488,24 +490,18 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao,
        pitch = modulo360f(pitch);
        yaw = wrapDegrees_0_360(yaw);
 
-       playersao->setBasePosition(position);
-       player->setSpeed(speed);
+       if (!playersao->isAttached()) {
+               // Only update player positions when moving freely
+               // to not interfere with attachment handling
+               playersao->setBasePosition(position);
+               player->setSpeed(speed);
+       }
        playersao->setLookPitch(pitch);
        playersao->setPlayerYaw(yaw);
        playersao->setFov(fov);
        playersao->setWantedRange(wanted_range);
 
-       player->keyPressed = keyPressed;
-       player->control.up    = (keyPressed & (0x1 << 0));
-       player->control.down  = (keyPressed & (0x1 << 1));
-       player->control.left  = (keyPressed & (0x1 << 2));
-       player->control.right = (keyPressed & (0x1 << 3));
-       player->control.jump  = (keyPressed & (0x1 << 4));
-       player->control.aux1  = (keyPressed & (0x1 << 5));
-       player->control.sneak = (keyPressed & (0x1 << 6));
-       player->control.dig   = (keyPressed & (0x1 << 7));
-       player->control.place = (keyPressed & (0x1 << 8));
-       player->control.zoom  = (keyPressed & (0x1 << 9));
+       player->control.unpackKeysPressed(keyPressed);
 
        if (playersao->checkMovementCheat()) {
                // Call callbacks
@@ -622,21 +618,36 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
 
        const bool player_has_interact = checkPriv(player->getName(), "interact");
 
-       auto check_inv_access = [player, player_has_interact] (
+       auto check_inv_access = [player, player_has_interact, this] (
                        const InventoryLocation &loc) -> bool {
-               if (loc.type == InventoryLocation::CURRENT_PLAYER)
-                       return false; // Only used internally on the client, never sent
-               if (loc.type == InventoryLocation::PLAYER) {
-                       // Allow access to own inventory in all cases
-                       return loc.name == player->getName();
-               }
 
-               if (!player_has_interact) {
+               // Players without interact may modify their own inventory
+               if (!player_has_interact && loc.type != InventoryLocation::PLAYER) {
                        infostream << "Cannot modify foreign inventory: "
                                        << "No interact privilege" << std::endl;
                        return false;
                }
-               return true;
+
+               switch (loc.type) {
+               case InventoryLocation::CURRENT_PLAYER:
+                       // Only used internally on the client, never sent
+                       return false;
+               case InventoryLocation::PLAYER:
+                       // Allow access to own inventory in all cases
+                       return loc.name == player->getName();
+               case InventoryLocation::NODEMETA:
+                       {
+                               // Check for out-of-range interaction
+                               v3f node_pos   = intToFloat(loc.p, BS);
+                               v3f player_pos = player->getPlayerSAO()->getEyePosition();
+                               f32 d = player_pos.getDistanceFrom(node_pos);
+                               return checkInteractDistance(player, d, "inventory");
+                       }
+               case InventoryLocation::DETACHED:
+                       return getInventoryMgr()->checkDetachedInventoryAccess(loc, player->getName());
+               default:
+                       return false;
+               }
        };
 
        /*
@@ -656,18 +667,6 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
                                !check_inv_access(ma->to_inv))
                        return;
 
-               InventoryLocation *remote = ma->from_inv.type == InventoryLocation::PLAYER ?
-                       &ma->to_inv : &ma->from_inv;
-
-               // Check for out-of-range interaction
-               if (remote->type == InventoryLocation::NODEMETA) {
-                       v3f node_pos   = intToFloat(remote->p, BS);
-                       v3f player_pos = player->getPlayerSAO()->getEyePosition();
-                       f32 d = player_pos.getDistanceFrom(node_pos);
-                       if (!checkInteractDistance(player, d, "inventory"))
-                               return;
-               }
-
                /*
                        Disable moving items out of craftpreview
                */
@@ -752,21 +751,8 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
 
 void Server::handleCommand_ChatMessage(NetworkPacket* pkt)
 {
-       /*
-               u16 command
-               u16 length
-               wstring message
-       */
-       u16 len;
-       *pkt >> len;
-
        std::wstring message;
-       for (u16 i = 0; i < len; i++) {
-               u16 tmp_wchar;
-               *pkt >> tmp_wchar;
-
-               message += (wchar_t)tmp_wchar;
-       }
+       *pkt >> message;
 
        session_t peer_id = pkt->getPeerId();
        RemotePlayer *player = m_env->getPlayer(peer_id);
@@ -778,15 +764,13 @@ void Server::handleCommand_ChatMessage(NetworkPacket* pkt)
                return;
        }
 
-       // Get player name of this client
        std::string name = player->getName();
-       std::wstring wname = narrow_to_wide(name);
 
-       std::wstring answer_to_sender = handleChat(name, wname, message, true, player);
+       std::wstring answer_to_sender = handleChat(name, message, true, player);
        if (!answer_to_sender.empty()) {
                // Send the answer to sender
-               SendChatMessage(peer_id, ChatMessage(CHATMESSAGE_TYPE_NORMAL,
-                       answer_to_sender, wname));
+               SendChatMessage(peer_id, ChatMessage(CHATMESSAGE_TYPE_SYSTEM,
+                       answer_to_sender));
        }
 }
 
@@ -829,8 +813,7 @@ void Server::handleCommand_Damage(NetworkPacket* pkt)
                                << std::endl;
 
                PlayerHPChangeReason reason(PlayerHPChangeReason::FALL);
-               playersao->setHP((s32)playersao->getHP() - (s32)damage, reason);
-               SendPlayerHPOrDie(playersao, reason);
+               playersao->setHP((s32)playersao->getHP() - (s32)damage, reason, true);
        }
 }
 
@@ -904,10 +887,10 @@ void Server::handleCommand_Respawn(NetworkPacket* pkt)
 
 bool Server::checkInteractDistance(RemotePlayer *player, const f32 d, const std::string &what)
 {
-       ItemStack selected_item, hand_item;
-       player->getWieldedItem(&selected_item, &hand_item);
+       ItemStack selected_item, main_item;
+       player->getWieldedItem(&selected_item, &main_item);
        f32 max_d = BS * getToolRange(selected_item.getDefinition(m_itemdef),
-                       hand_item.getDefinition(m_itemdef));
+                       main_item.getDefinition(m_itemdef));
 
        // Cube diagonal * 1.5 for maximal supported node extents:
        // sqrt(3) * 1.5 ≅ 2.6
@@ -924,6 +907,21 @@ bool Server::checkInteractDistance(RemotePlayer *player, const f32 d, const std:
        return true;
 }
 
+// Tiny helper to retrieve the selected item into an Optional
+static inline void getWieldedItem(const PlayerSAO *playersao, Optional<ItemStack> &ret)
+{
+       ret = ItemStack();
+       playersao->getWieldedItem(&(*ret));
+}
+
+static inline bool getOffhandWieldedItem(const PlayerSAO *playersao, Optional<ItemStack> &offhand,
+       Optional<ItemStack> &place, IItemDefManager *idef, const PointedThing &pointed)
+{
+       offhand = ItemStack();
+       place = ItemStack();
+       return playersao->getOffhandWieldedItem(&(*offhand), &(*place), idef, pointed);
+}
+
 void Server::handleCommand_Interact(NetworkPacket *pkt)
 {
        /*
@@ -1049,6 +1047,12 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
                if (pointed.type == POINTEDTHING_NODE) {
                        target_pos = intToFloat(pointed.node_undersurface, BS);
                } else if (pointed.type == POINTEDTHING_OBJECT) {
+                       if (playersao->getId() == pointed_object->getId()) {
+                               actionstream << "Server: " << player->getName()
+                                       << " attempted to interact with themselves" << std::endl;
+                               m_script->on_cheat(playersao, "interacted_with_self");
+                               return;
+                       }
                        target_pos = pointed_object->getBasePosition();
                }
                float d = playersao->getEyePosition().getDistanceFrom(target_pos);
@@ -1109,11 +1113,8 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
                float time_from_last_punch =
                        playersao->resetTimeFromLastPunch();
 
-               u16 src_original_hp = pointed_object->getHP();
-               u16 dst_origin_hp = playersao->getHP();
-
-               u16 wear = pointed_object->punch(dir, &toolcap, playersao,
-                               time_from_last_punch);
+               u32 wear = pointed_object->punch(dir, &toolcap, playersao,
+                               time_from_last_punch, tool_item.wear);
 
                // Callback may have changed item, so get it again
                playersao->getWieldedItem(&selected_item);
@@ -1121,18 +1122,6 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
                if (changed)
                        playersao->setWieldedItem(selected_item);
 
-               // If the object is a player and its HP changed
-               if (src_original_hp != pointed_object->getHP() &&
-                               pointed_object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
-                       SendPlayerHPOrDie((PlayerSAO *)pointed_object,
-                                       PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, playersao));
-               }
-
-               // If the puncher is a player and its HP changed
-               if (dst_origin_hp != playersao->getHP())
-                       SendPlayerHPOrDie(playersao,
-                                       PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, pointed_object));
-
                return;
        } // action == INTERACT_START_DIGGING
 
@@ -1173,16 +1162,17 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
 
                        // Get player's wielded item
                        // See also: Game::handleDigging
-                       ItemStack selected_item, hand_item;
-                       playersao->getPlayer()->getWieldedItem(&selected_item, &hand_item);
+                       ItemStack selected_item, main_item;
+                       playersao->getPlayer()->getWieldedItem(&selected_item, &main_item);
 
                        // Get diggability and expected digging time
                        DigParams params = getDigParams(m_nodedef->get(n).groups,
-                                       &selected_item.getToolCapabilities(m_itemdef));
+                                       &selected_item.getToolCapabilities(m_itemdef),
+                                       selected_item.wear);
                        // If can't dig, try hand
                        if (!params.diggable) {
                                params = getDigParams(m_nodedef->get(n).groups,
-                                       &hand_item.getToolCapabilities(m_itemdef));
+                                       &main_item.getToolCapabilities(m_itemdef));
                        }
                        // If can't dig, ignore dig
                        if (!params.diggable) {
@@ -1240,14 +1230,18 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
 
        // Place block or right-click object
        case INTERACT_PLACE: {
-               ItemStack selected_item;
-               playersao->getWieldedItem(&selected_item, nullptr);
+               Optional<ItemStack> main_item, offhand_item, place_item;
+               getWieldedItem(playersao, main_item);
+               bool use_offhand = getOffhandWieldedItem(playersao, offhand_item, place_item, m_itemdef, pointed);
 
                // Reset build time counter
                if (pointed.type == POINTEDTHING_NODE &&
-                               selected_item.getDefinition(m_itemdef).type == ITEM_NODE)
+                               place_item->getDefinition(m_itemdef).type == ITEM_NODE)
                        getClient(peer_id)->m_time_from_building = 0.0;
 
+               const bool had_prediction = !place_item->getDefinition(m_itemdef).
+                       node_placement_prediction.empty();
+
                if (pointed.type == POINTEDTHING_OBJECT) {
                        // Right click object
 
@@ -1260,19 +1254,21 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
                                        << pointed_object->getDescription() << std::endl;
 
                        // Do stuff
-                       if (m_script->item_OnSecondaryUse(
-                                       selected_item, playersao, pointed)) {
-                               if (playersao->setWieldedItem(selected_item)) {
+                       if (m_script->item_OnSecondaryUse(use_offhand ? offhand_item : main_item, playersao, pointed)) {
+                               if (use_offhand
+                                               ? (offhand_item.has_value() && playersao->setOffhandWieldedItem(*offhand_item))
+                                               : (main_item.has_value() && playersao->setWieldedItem(*main_item)))
                                        SendInventory(playersao, true);
-                               }
                        }
 
                        pointed_object->rightClick(playersao);
-               } else if (m_script->item_OnPlace(selected_item, playersao, pointed)) {
+               } else if (m_script->item_OnPlace(use_offhand ? offhand_item : main_item, playersao, pointed)) {
                        // Placement was handled in lua
 
                        // Apply returned ItemStack
-                       if (playersao->setWieldedItem(selected_item))
+                       if (use_offhand
+                                       ? (offhand_item.has_value() && playersao->setOffhandWieldedItem(*offhand_item))
+                                       : (main_item.has_value() && playersao->setWieldedItem(*main_item)))
                                SendInventory(playersao, true);
                }
 
@@ -1284,8 +1280,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
                RemoteClient *client = getClient(peer_id);
                v3s16 blockpos = getNodeBlockPos(pointed.node_abovesurface);
                v3s16 blockpos2 = getNodeBlockPos(pointed.node_undersurface);
-               if (!selected_item.getDefinition(m_itemdef
-                               ).node_placement_prediction.empty()) {
+               if (had_prediction) {
                        client->SetBlockNotSent(blockpos);
                        if (blockpos2 != blockpos)
                                client->SetBlockNotSent(blockpos2);
@@ -1299,15 +1294,15 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
        } // action == INTERACT_PLACE
 
        case INTERACT_USE: {
-               ItemStack selected_item;
-               playersao->getWieldedItem(&selected_item, nullptr);
+               Optional<ItemStack> selected_item;
+               getWieldedItem(playersao, selected_item);
 
-               actionstream << player->getName() << " uses " << selected_item.name
+               actionstream << player->getName() << " uses " << selected_item->name
                                << ", pointing at " << pointed.dump() << std::endl;
 
                if (m_script->item_OnUse(selected_item, playersao, pointed)) {
                        // Apply returned ItemStack
-                       if (playersao->setWieldedItem(selected_item))
+                       if (selected_item.has_value() && playersao->setWieldedItem(*selected_item))
                                SendInventory(playersao, true);
                }
 
@@ -1316,16 +1311,20 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
 
        // Rightclick air
        case INTERACT_ACTIVATE: {
-               ItemStack selected_item;
-               playersao->getWieldedItem(&selected_item, nullptr);
+               Optional<ItemStack> main_item, offhand_item, place_item;
+               getWieldedItem(playersao, main_item);
+               bool use_offhand = getOffhandWieldedItem(playersao, offhand_item, place_item, m_itemdef, pointed);
 
                actionstream << player->getName() << " activates "
-                               << selected_item.name << std::endl;
+                               << place_item->name << std::endl;
 
                pointed.type = POINTEDTHING_NOTHING; // can only ever be NOTHING
 
-               if (m_script->item_OnSecondaryUse(selected_item, playersao, pointed)) {
-                       if (playersao->setWieldedItem(selected_item))
+               if (m_script->item_OnSecondaryUse(use_offhand ? offhand_item : main_item, playersao, pointed)) {
+                       // Apply returned ItemStack
+                       if (use_offhand
+                                       ? (offhand_item.has_value() && playersao->setOffhandWieldedItem(*offhand_item))
+                                       : (main_item.has_value() && playersao->setWieldedItem(*main_item)))
                                SendInventory(playersao, true);
                }
 
@@ -1481,11 +1480,9 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt)
        session_t peer_id = pkt->getPeerId();
        RemoteClient *client = getClient(peer_id, CS_Invalid);
        ClientState cstate = client->getState();
+       const std::string playername = client->getName();
 
-       std::string playername = client->getName();
-
-       std::string salt;
-       std::string verification_key;
+       std::string salt, verification_key;
 
        std::string addr_s = getPeerAddress(peer_id).serializeString();
        u8 is_empty;
@@ -1495,6 +1492,9 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt)
        verbosestream << "Server: Got TOSERVER_FIRST_SRP from " << addr_s
                << ", with is_empty=" << (is_empty == 1) << std::endl;
 
+       const bool empty_disallowed = !isSingleplayer() && is_empty == 1 &&
+               g_settings->getBool("disallow_empty_password");
+
        // Either this packet is sent because the user is new or to change the password
        if (cstate == CS_HelloSent) {
                if (!client->isMechAllowed(AUTH_MECHANISM_FIRST_SRP)) {
@@ -1505,9 +1505,7 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt)
                        return;
                }
 
-               if (!isSingleplayer() &&
-                               g_settings->getBool("disallow_empty_password") &&
-                               is_empty == 1) {
+               if (empty_disallowed) {
                        actionstream << "Server: " << playername
                                        << " supplied empty password from " << addr_s << std::endl;
                        DenyAccess(peer_id, SERVER_ACCESSDENIED_EMPTY_PASSWORD);
@@ -1515,8 +1513,19 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt)
                }
 
                std::string initial_ver_key;
-
                initial_ver_key = encode_srp_verifier(verification_key, salt);
+
+               // It is possible for multiple connections to get this far with the same
+               // player name. In the end only one player with a given name will be emerged
+               // (see Server::StateTwoClientInit) but we still have to be careful here.
+               if (m_script->getAuth(playername, nullptr, nullptr)) {
+                       // Another client beat us to it
+                       actionstream << "Server: Client from " << addr_s
+                               << " tried to register " << playername << " a second time."
+                               << std::endl;
+                       DenyAccess(peer_id, SERVER_ACCESSDENIED_ALREADY_CONNECTED);
+                       return;
+               }
                m_script->createAuth(playername, initial_ver_key);
                m_script->on_authplayer(playername, addr_s, true);
 
@@ -1529,6 +1538,15 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt)
                        return;
                }
                m_clients.event(peer_id, CSE_SudoLeave);
+
+               if (empty_disallowed) {
+                       actionstream << "Server: " << playername
+                                       << " supplied empty password" << std::endl;
+                       SendChatMessage(peer_id, ChatMessage(CHATMESSAGE_TYPE_SYSTEM,
+                               L"Changing to an empty password is not allowed."));
+                       return;
+               }
+
                std::string pw_db_field = encode_srp_verifier(verification_key, salt);
                bool success = m_script->setPassword(playername, pw_db_field);
                if (success) {
@@ -1550,8 +1568,6 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
        RemoteClient *client = getClient(peer_id, CS_Invalid);
        ClientState cstate = client->getState();
 
-       bool wantSudo = (cstate == CS_Active);
-
        if (!((cstate == CS_HelloSent) || (cstate == CS_Active))) {
                actionstream << "Server: got SRP _A packet in wrong state " << cstate <<
                        " from " << getPeerAddress(peer_id).serializeString() <<
@@ -1559,6 +1575,8 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
                return;
        }
 
+       const bool wantSudo = (cstate == CS_Active);
+
        if (client->chosen_mech != AUTH_MECHANISM_NONE) {
                actionstream << "Server: got SRP _A packet, while auth is already "
                        "going on with mech " << client->chosen_mech << " from " <<
@@ -1605,8 +1623,7 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
 
        client->chosen_mech = chosen;
 
-       std::string salt;
-       std::string verifier;
+       std::string salt, verifier;
 
        if (based_on == 0) {
 
@@ -1638,6 +1655,7 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
                        << std::endl;
                if (wantSudo) {
                        DenySudoAccess(peer_id);
+                       client->resetChosenMech();
                        return;
                }
 
@@ -1655,10 +1673,10 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt)
        session_t peer_id = pkt->getPeerId();
        RemoteClient *client = getClient(peer_id, CS_Invalid);
        ClientState cstate = client->getState();
-       std::string addr_s = getPeerAddress(pkt->getPeerId()).serializeString();
-       std::string playername = client->getName();
+       const std::string addr_s = client->getAddress().serializeString();
+       const std::string playername = client->getName();
 
-       bool wantSudo = (cstate == CS_Active);
+       const bool wantSudo = (cstate == CS_Active);
 
        verbosestream << "Server: Received TOSERVER_SRP_BYTES_M." << std::endl;
 
@@ -1704,6 +1722,7 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt)
                                << " tried to change their password, but supplied wrong"
                                << " (SRP) password for authentication." << std::endl;
                        DenySudoAccess(peer_id);
+                       client->resetChosenMech();
                        return;
                }
 
@@ -1717,8 +1736,7 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt)
        if (client->create_player_on_auth_success) {
                m_script->createAuth(playername, client->enc_pwd);
 
-               std::string checkpwd; // not used, but needed for passing something
-               if (!m_script->getAuth(playername, &checkpwd, NULL)) {
+               if (!m_script->getAuth(playername, nullptr, nullptr)) {
                        errorstream << "Server: " << playername <<
                                " cannot be authenticated (auth handler does not work?)" <<
                                std::endl;
@@ -1813,3 +1831,45 @@ void Server::handleCommand_ModChannelMsg(NetworkPacket *pkt)
 
        broadcastModChannelMessage(channel_name, channel_msg, peer_id);
 }
+
+void Server::handleCommand_HaveMedia(NetworkPacket *pkt)
+{
+       std::vector<u32> tokens;
+       u8 numtokens;
+
+       *pkt >> numtokens;
+       for (u16 i = 0; i < numtokens; i++) {
+               u32 n;
+               *pkt >> n;
+               tokens.emplace_back(n);
+       }
+
+       const session_t peer_id = pkt->getPeerId();
+       auto player = m_env->getPlayer(peer_id);
+
+       for (const u32 token : tokens) {
+               auto it = m_pending_dyn_media.find(token);
+               if (it == m_pending_dyn_media.end())
+                       continue;
+               if (it->second.waiting_players.count(peer_id)) {
+                       it->second.waiting_players.erase(peer_id);
+                       if (player)
+                               getScriptIface()->on_dynamic_media_added(token, player->getName());
+               }
+       }
+}
+
+void Server::handleCommand_UpdateClientInfo(NetworkPacket *pkt)
+{
+       ClientDynamicInfo info;
+       *pkt >> info.render_target_size.X;
+       *pkt >> info.render_target_size.Y;
+       *pkt >> info.real_gui_scaling;
+       *pkt >> info.real_hud_scaling;
+       *pkt >> info.max_fs_size.X;
+       *pkt >> info.max_fs_size.Y;
+
+       session_t peer_id = pkt->getPeerId();
+       RemoteClient *client = getClient(peer_id, CS_Invalid);
+       client->setDynamicInfo(info);
+}