diff --git a/SDK/include/network.hpp b/SDK/include/network.hpp index 37eba30f0..e52ceb4c2 100644 --- a/SDK/include/network.hpp +++ b/SDK/include/network.hpp @@ -78,7 +78,9 @@ enum class ClientVersion : uint8_t { ClientVersion_SAMP_037, ClientVersion_SAMP_03DL, - ClientVersion_openmp + ClientVersion_openmp, + + ClientVersion_none = 255 }; struct PeerRequestParams diff --git a/SDK/include/player.hpp b/SDK/include/player.hpp index dd039185d..9294a8443 100644 --- a/SDK/include/player.hpp +++ b/SDK/include/player.hpp @@ -1080,3 +1080,13 @@ struct IPlayerPool : public IExtensible, public IReadOnlyPool /// Get the colour assigned to a player ID when it first connects. virtual Colour getDefaultColour(int pid) const = 0; }; + +static const UID IPlayerReserveExtension_UID = UID(0x7DEF26D24A04F2FD); +struct IPlayerReserveExtension : public IExtension +{ + PROVIDE_EXT_UID(IPlayerReserveExtension_UID) + + /// Request a new player with the given network parameters + virtual Pair reservePlayer(const PeerNetworkData& netData) = 0; + virtual NewConnectionResult finalizePlayer(IPlayer* peer, const PeerRequestParams& params) = 0; +}; diff --git a/Server/Components/LegacyNetwork/legacy_network_impl.cpp b/Server/Components/LegacyNetwork/legacy_network_impl.cpp index c4412b682..6009c4baf 100644 --- a/Server/Components/LegacyNetwork/legacy_network_impl.cpp +++ b/Server/Components/LegacyNetwork/legacy_network_impl.cpp @@ -317,18 +317,6 @@ IPlayer* RakNetLegacyNetwork::OnPeerConnect(RakNet::RPCParameters* rpcParams, bo { const RakNet::PlayerID rid = rpcParams->sender; - if (playerFromRakIndex[rpcParams->senderIndex]) - { - // Connection already exists - return nullptr; - } - - PeerNetworkData netData {}; - netData.networkID.address.ipv6 = false; - netData.networkID.address.v4 = rid.binaryAddress; - netData.networkID.port = rid.port; - netData.network = this; - Pair newConnectionResult { NewConnectionResult_Ignore, nullptr }; const bool isDL = version == LegacyClientVersion_03DL && (SAMPRakNet::GetToken() == (challenge ^ LegacyClientVersion_03DL)); @@ -344,7 +332,35 @@ IPlayer* RakNetLegacyNetwork::OnPeerConnect(RakNet::RPCParameters* rpcParams, bo params.bot = isNPC; params.serial = serial; params.isUsingOfficialClient = isUsingOfficialClient; - newConnectionResult = core->getPlayers().requestPlayer(netData, params); + + if (reservePlayers) + { + if (!playerFromRakIndex[rpcParams->senderIndex]) + { + // Player not reserved + rakNetServer.Kick(rid); + return nullptr; + } + + newConnectionResult.second = playerFromRakIndex[rpcParams->senderIndex]; + newConnectionResult.first = reservePlayers->finalizePlayer(newConnectionResult.second, params); + } + else + { + if (playerFromRakIndex[rpcParams->senderIndex]) + { + // Connection already exists + return nullptr; + } + + PeerNetworkData netData {}; + netData.networkID.address.ipv6 = false; + netData.networkID.address.v4 = rid.binaryAddress; + netData.networkID.port = rid.port; + netData.network = this; + + newConnectionResult = core->getPlayers().requestPlayer(netData, params); + } } else { @@ -362,7 +378,15 @@ IPlayer* RakNetLegacyNetwork::OnPeerConnect(RakNet::RPCParameters* rpcParams, bo if (newConnectionResult.first != NewConnectionResult_VersionMismatch) { - rakNetServer.Kick(rid); + if (reservePlayers) + { + // Player is reserved, kick them + newConnectionResult.second->kick(); + } + else + { + rakNetServer.Kick(rid); + } } } return nullptr; @@ -774,6 +798,7 @@ void RakNetLegacyNetwork::update() void RakNetLegacyNetwork::init(ICore* c) { core = c; + reservePlayers = queryExtension(core->getPlayers()); core->getEventDispatcher().addEventHandler(this); core->getPlayers().getPlayerChangeDispatcher().addEventHandler(this); @@ -872,6 +897,25 @@ void RakNetLegacyNetwork::onTick(Microseconds elapsed, TimePoint now) } IPlayer* player = playerFromRakIndex[pkt->playerIndex]; + if (!player && reservePlayers) + { + PeerNetworkData netData {}; + netData.networkID.address.ipv6 = false; + netData.networkID.address.v4 = pkt->playerId.binaryAddress; + netData.networkID.port = pkt->playerId.port; + netData.network = this; + + Pair newConnectionResult = reservePlayers->reservePlayer(netData); + if (newConnectionResult.first == NewConnectionResult_Success) + { + playerFromRakIndex[pkt->playerIndex] = newConnectionResult.second; + } + else + { + rakNetServer.Kick(pkt->playerId); + } + } + if (player) { NetworkBitStream bs(pkt->data, pkt->length, false); diff --git a/Server/Components/LegacyNetwork/legacy_network_impl.hpp b/Server/Components/LegacyNetwork/legacy_network_impl.hpp index 0db5a0916..5e60667df 100644 --- a/Server/Components/LegacyNetwork/legacy_network_impl.hpp +++ b/Server/Components/LegacyNetwork/legacy_network_impl.hpp @@ -35,6 +35,7 @@ class RakNetLegacyNetwork final : public Network, public CoreEventHandler, publi { private: ICore* core = nullptr; + IPlayerReserveExtension* reservePlayers = nullptr; Query query; RakNet::RakServerInterface& rakNetServer; std::array playerFromRakIndex; diff --git a/Server/Source/player.cpp b/Server/Source/player.cpp index 76eaeeb81..0d3dac634 100644 --- a/Server/Source/player.cpp +++ b/Server/Source/player.cpp @@ -40,13 +40,13 @@ EPlayerNameStatus Player::setName(StringView name) return EPlayerNameStatus::Taken; } - const auto oldName = name_; - name_ = name; + const auto oldName = clientParams_.name; + clientParams_.name = name; pool_.playerChangeDispatcher.dispatch(&PlayerChangeEventHandler::onPlayerNameChange, *this, oldName); NetCode::RPC::SetPlayerName setPlayerNameRPC; setPlayerNameRPC.PlayerID = poolID; - setPlayerNameRPC.Name = StringView(name_); + setPlayerNameRPC.Name = StringView(clientParams_.name); setPlayerNameRPC.Success = true; PacketHelper::broadcast(setPlayerNameRPC, pool_); return EPlayerNameStatus::Updated; @@ -365,7 +365,7 @@ void Player::ban(StringView reason) { PeerAddress::AddressString address; PeerAddress::ToString(netData_.networkID.address, address); - const BanEntry entry(address, name_, reason); + const BanEntry entry(address, clientParams_.name, reason); for (INetwork* network : pool_.core.getNetworks()) { network->ban(entry); diff --git a/Server/Source/player_impl.hpp b/Server/Source/player_impl.hpp index c90921d04..f72cdac0c 100644 --- a/Server/Source/player_impl.hpp +++ b/Server/Source/player_impl.hpp @@ -53,18 +53,40 @@ enum SecondarySyncUpdateType SecondarySyncUpdateType_Trailer = (1 << 2), }; +struct ClientParams +{ + HybridString<16> versionName; + HybridString name; + HybridString<16> serial; + ClientVersion version; + bool isBot; + bool isUsingOfficialClient; + + ClientParams() + : version(ClientVersion::ClientVersion_none) + { + } + + ClientParams(const PeerRequestParams& params) + : versionName(params.versionName) + , name(params.name) + , serial(params.serial) + , version(params.version) + , isBot(params.bot) + , isUsingOfficialClient(params.isUsingOfficialClient) + { + } +}; + struct Player final : public IPlayer, public PoolIDProvider, public NoCopy { PlayerPool& pool_; PeerNetworkData netData_; - ClientVersion version_; - HybridString<16> versionName_; + ClientParams clientParams_; Vector3 pos_; Vector3 cameraPos_; Vector3 cameraLookAt_; GTAQuat rot_; - HybridString name_; - HybridString<16> serial_; WeaponSlots weapons_; Colour colour_; FlatHashMap othersColours_; @@ -107,7 +129,6 @@ struct Player final : public IPlayer, public PoolIDProvider, public NoCopy int targetPlayer_, targetActor_; TimePoint chatBubbleExpiration_; PlayerChatBubble chatBubble_; - const bool isBot_; bool toSpawn_; TimePoint lastGameTimeUpdate_; PlayerSpectateData spectateData_; @@ -116,7 +137,6 @@ struct Player final : public IPlayer, public PoolIDProvider, public NoCopy int defaultObjectsRemoved_; bool allowWeapons_; bool allowTeleport_; - bool isUsingOfficialClient_; PrimarySyncUpdateType primarySyncUpdateType_; int secondarySyncUpdateType_; @@ -203,16 +223,12 @@ struct Player final : public IPlayer, public PoolIDProvider, public NoCopy IExtensible::resetExtensions(); } - Player(PlayerPool& pool, const PeerNetworkData& netData, const PeerRequestParams& params, bool* allAnimationLibraries, bool* validateAnimations, bool* allowInteriorWeapons, IFixesComponent* fixesComponent) + Player(PlayerPool& pool, const PeerNetworkData& netData, bool* allAnimationLibraries, bool* validateAnimations, bool* allowInteriorWeapons, IFixesComponent* fixesComponent) : pool_(pool) , netData_(netData) - , version_(params.version) - , versionName_(params.versionName) , pos_(0.0f, 0.0f, 0.0f) , cameraPos_(0.f, 0.f, 0.f) , cameraLookAt_(0.f, 0.f, 0.f) - , name_(params.name) - , serial_(params.serial) , virtualWorld_(0) , score_(0) , fightingStyle_(PlayerFightingStyle_Normal) @@ -245,7 +261,6 @@ struct Player final : public IPlayer, public PoolIDProvider, public NoCopy , targetPlayer_(INVALID_PLAYER_ID) , targetActor_(INVALID_ACTOR_ID) , chatBubbleExpiration_(Time::now()) - , isBot_(params.bot) , toSpawn_(false) , lastGameTimeUpdate_() , spectateData_({ false, INVALID_PLAYER_ID, PlayerSpectateData::ESpectateType::None }) @@ -254,7 +269,6 @@ struct Player final : public IPlayer, public PoolIDProvider, public NoCopy , defaultObjectsRemoved_(0) , allowWeapons_(true) , allowTeleport_(false) - , isUsingOfficialClient_(params.isUsingOfficialClient) , primarySyncUpdateType_(PrimarySyncUpdateType::None) , secondarySyncUpdateType_(0) , lastScoresAndPings_(Time::now()) @@ -299,22 +313,22 @@ struct Player final : public IPlayer, public PoolIDProvider, public NoCopy ClientVersion getClientVersion() const override { - return version_; + return clientParams_.version; } StringView getClientVersionName() const override { - return versionName_; + return clientParams_.versionName; } bool isBot() const override { - return isBot_; + return clientParams_.isBot; } bool isUsingOfficialClient() const override { - return isUsingOfficialClient_; + return clientParams_.isUsingOfficialClient; } void setState(PlayerState state, bool dispatchEvents = true); @@ -1091,12 +1105,12 @@ struct Player final : public IPlayer, public PoolIDProvider, public NoCopy StringView getName() const override { - return name_; + return clientParams_.name; } StringView getSerial() const override { - return serial_; + return clientParams_.serial; } int getID() const override @@ -1548,7 +1562,7 @@ struct Player final : public IPlayer, public PoolIDProvider, public NoCopy virtualWorld_ = vw; - if (version_ == ClientVersion::ClientVersion_SAMP_037) + if (clientParams_.version == ClientVersion::ClientVersion_SAMP_037) return; NetCode::RPC::SetPlayerVirtualWorld setWorld; diff --git a/Server/Source/player_pool.hpp b/Server/Source/player_pool.hpp index 7f5b32445..2caa8c124 100644 --- a/Server/Source/player_pool.hpp +++ b/Server/Source/player_pool.hpp @@ -12,7 +12,7 @@ #include #include -struct PlayerPool final : public IPlayerPool, public NetworkEventHandler, public PlayerUpdateEventHandler, public CoreEventHandler +struct PlayerPool final : public IPlayerPool, public NetworkEventHandler, public PlayerUpdateEventHandler, public CoreEventHandler, public IPlayerReserveExtension { ICore& core; const FlatPtrHashSet& networks; @@ -382,7 +382,7 @@ struct PlayerPool final : public IPlayerPool, public NetworkEventHandler, public self.core.logLn( LogLevel::Message, "[death] %.*s died %d", - PRINT_VIEW(player.name_), + PRINT_VIEW(player.clientParams_.name), reason); } else @@ -391,7 +391,7 @@ struct PlayerPool final : public IPlayerPool, public NetworkEventHandler, public LogLevel::Message, "[kill] %.*s killed %.*s %.*s", PRINT_VIEW(killer->getName()), - PRINT_VIEW(player.name_), + PRINT_VIEW(player.clientParams_.name), PRINT_VIEW(self.core.getWeaponName(PlayerWeapon(reason)))); } } @@ -447,7 +447,7 @@ struct PlayerPool final : public IPlayerPool, public NetworkEventHandler, public bool onReceive(IPlayer& peer, NetworkBitStream& bs) override { Player& player = static_cast(peer); - if (player.toSpawn_ || player.isBot_) + if (player.toSpawn_ || player.clientParams_.isBot) { player.setState(PlayerState_Spawned); player.controllable_ = true; @@ -1328,8 +1328,9 @@ struct PlayerPool final : public IPlayerPool, public NetworkEventHandler, public return Colour::FromRGBA(colours[pid % GLM_COUNTOF(colours)]); } - void initPlayer(Player& player) + void initPlayer(Player& player, const PeerRequestParams& params) { + player.clientParams_ = params; player.streamedFor_.add(player.poolID, player); player.colour_ = getDefaultColour(player.poolID); } @@ -1669,16 +1670,16 @@ struct PlayerPool final : public IPlayerPool, public NetworkEventHandler, public return { NewConnectionResult_BadName, nullptr }; } - Player* result = storage.emplace(*this, netData, params, useAllAnimations_, validateAnimations_, allowInteriorWeapons_, fixesComponent_); + Player* result = storage.emplace(*this, netData, useAllAnimations_, validateAnimations_, allowInteriorWeapons_, fixesComponent_); if (!result) { return { NewConnectionResult_NoPlayerSlot, nullptr }; } - auto& secondaryPool = result->isBot_ ? botList : playerList; + auto& secondaryPool = result->clientParams_.isBot ? botList : playerList; secondaryPool.emplace(result); - initPlayer(*result); + initPlayer(*result, params); return { NewConnectionResult_Success, result }; } @@ -1706,8 +1707,8 @@ struct PlayerPool final : public IPlayerPool, public NetworkEventHandler, public NetCode::RPC::PlayerJoin playerJoinPacket; playerJoinPacket.PlayerID = player.poolID; playerJoinPacket.Col = player.colour_; - playerJoinPacket.IsNPC = player.isBot_; - playerJoinPacket.Name = StringView(player.name_); + playerJoinPacket.IsNPC = player.clientParams_.isBot; + playerJoinPacket.Name = StringView(player.clientParams_.name); PacketHelper::broadcastToSome(playerJoinPacket, storage.entries(), &peer); for (IPlayer* other : storage.entries()) @@ -1721,8 +1722,8 @@ struct PlayerPool final : public IPlayerPool, public NetworkEventHandler, public NetCode::RPC::PlayerJoin otherJoinPacket; otherJoinPacket.PlayerID = otherPlayer->poolID; otherJoinPacket.Col = otherPlayer->colour_; - otherJoinPacket.IsNPC = otherPlayer->isBot_; - otherJoinPacket.Name = StringView(otherPlayer->name_); + otherJoinPacket.IsNPC = otherPlayer->clientParams_.isBot; + otherJoinPacket.Name = StringView(otherPlayer->clientParams_.name); PacketHelper::send(otherJoinPacket, peer); } @@ -1740,8 +1741,8 @@ struct PlayerPool final : public IPlayerPool, public NetworkEventHandler, public core.logLn( LogLevel::Message, "[%sjoin] %.*s has joined the server (%d:%s)", - player.isBot_ ? "npc:" : "", - PRINT_VIEW(player.name_), + player.clientParams_.isBot ? "npc:" : "", + PRINT_VIEW(player.clientParams_.name), player.poolID, addressString.data()); } @@ -1755,6 +1756,12 @@ struct PlayerPool final : public IPlayerPool, public NetworkEventHandler, public void clearPlayer(Player& player, PeerDisconnectReason reason) { + // Player not finalized, nothing to do + if (static_cast(player).clientParams_.version == ClientVersion::ClientVersion_none) + { + return; + } + for (IPlayer* p : storage.entries()) { if (p == &player) @@ -1800,13 +1807,13 @@ struct PlayerPool final : public IPlayerPool, public NetworkEventHandler, public core.logLn( LogLevel::Message, "[%spart] %.*s has left the server (%d:%d)", - player.isBot_ ? "npc:" : "", - PRINT_VIEW(player.name_), + player.clientParams_.isBot ? "npc:" : "", + PRINT_VIEW(player.clientParams_.name), player.poolID, reason); } - auto& secondaryPool = player.isBot_ ? botList : playerList; + auto& secondaryPool = player.clientParams_.isBot ? botList : playerList; secondaryPool.erase(&player); } @@ -2248,4 +2255,50 @@ struct PlayerPool final : public IPlayerPool, public NetworkEventHandler, public core.removeNetworkEventHandler(this); core.getEventDispatcher().removeEventHandler(this); } + + IExtension* getExtension(UID id) override + { + if (id == IPlayerReserveExtension::ExtensionIID) + { + return static_cast(this); + } + return nullptr; + } + + // From IExtension + void reset() override { } + + // From IPlayerReserveExtension_UID + Pair reservePlayer(const PeerNetworkData& netData) override + { + Player* result = storage.emplace(*this, netData, useAllAnimations_, validateAnimations_, allowInteriorWeapons_, fixesComponent_); + if (!result) + { + return { NewConnectionResult_NoPlayerSlot, nullptr }; + } + + return { NewConnectionResult_Success, result }; + } + + // From IPlayerReserveExtension_UID + NewConnectionResult finalizePlayer(IPlayer* peer, const PeerRequestParams& params) override + { + Player* player = static_cast(peer); + + if (params.bot && botList.size() >= *maxBots) + { + return NewConnectionResult_NoPlayerSlot; + } + + if (!isNameValid(params.name) || isNameTaken(params.name, nullptr)) + { + return NewConnectionResult_BadName; + } + + auto& secondaryPool = params.bot ? botList : playerList; + secondaryPool.emplace(player); + + initPlayer(*player, params); + return NewConnectionResult_Success; + } };