From cd94865cc65925d70bc72e2751178b7c863361f1 Mon Sep 17 00:00:00 2001 From: cubic Date: Mon, 8 Apr 2024 01:39:09 +0100 Subject: [PATCH] Added dedicated server states, cleaned up dedicated instance code and a few comments --- .../DedicatedInstance.cs | 313 +++++++++--------- .../Managers/GameplayManager.cs | 1 - .../Managers/LobbyManager.cs | 2 +- .../PlayerRegistry.cs | 16 +- .../Models/BitMask128.cs | 12 +- 5 files changed, 178 insertions(+), 166 deletions(-) diff --git a/BeatTogether.DedicatedServer.Kernel/DedicatedInstance.cs b/BeatTogether.DedicatedServer.Kernel/DedicatedInstance.cs index 529d9400..11adc210 100644 --- a/BeatTogether.DedicatedServer.Kernel/DedicatedInstance.cs +++ b/BeatTogether.DedicatedServer.Kernel/DedicatedInstance.cs @@ -50,6 +50,7 @@ public sealed class DedicatedInstance : ENetServer, IDedicatedInstance private IPacketDispatcher PacketDispatcher; private PacketSource ConnectedMessageSource; + private readonly PlayerStateHash ServerStateHash = new(); private byte _connectionIdCount = 0; private int _lastSortIndex = -1; @@ -75,6 +76,10 @@ public DedicatedInstance( #region Public Methods + public void InstanceConfigUpdated() + { + UpdateInstanceEvent?.Invoke(this); + } public IPlayerRegistry GetPlayerRegistry() { @@ -131,7 +136,7 @@ public async Task Start(CancellationToken cancellationToken = default) } }, cancellationToken); } - + ServerStateHash.WriteToBitMask("dedicated_server"); await base.Start(); _ = Task.Run(() => SendSyncTime(_stopServerCts.Token), cancellationToken); @@ -155,10 +160,16 @@ public async Task Stop(CancellationToken cancellationToken = default) $"SongSelectionMode={_configuration.SongSelectionMode}, " + $"GameplayServerControlSettings={_configuration.GameplayServerControlSettings})." ); - - PacketDispatcher.SendToNearbyPlayers(new KickPlayerPacket - { - DisconnectedReason = DisconnectedReason.ServerTerminated + ServerStateHash.WriteToBitMask("terminating"); + PacketDispatcher.SendToNearbyPlayers(new INetSerializable[]{ + new PlayerStatePacket + { + PlayerState = ServerStateHash + }, + new KickPlayerPacket + { + DisconnectedReason = DisconnectedReason.ServerTerminated + } }, IgnoranceChannelTypes.Reliable); KickAllPeers(); @@ -218,10 +229,6 @@ public void SetState(MultiplayerGameState state) GameIsInLobby?.Invoke(_configuration.Secret, state == MultiplayerGameState.Lobby); } - #endregion - - #region EnetCompat - public override IPlayer? TryAcceptConnection(IPEndPoint endPoint, ref SpanBuffer Data) { bool PlayerNoJoin = false; @@ -323,27 +330,6 @@ public void SetState(MultiplayerGameState state) player.PlatformUserId = PlayerPlatformUserId; } - - PacketDispatcher.SendExcludingPlayer(player, new INetSerializable[]{ - new PlayerConnectedPacket - { - RemoteConnectionId = player.ConnectionId, - UserId = player.UserId, - UserName = player.UserName, - IsConnectionOwner = false - }, - new PlayerSortOrderPacket - { - UserId = player.UserId, - SortIndex = player.SortIndex - }, - new MpNodePoseSyncStatePacket - { - fullStateUpdateFrequency = 100L, - deltaUpdateFrequency = _playerRegistry.GetMillisBetweenSyncStatePackets() - }, - }, IgnoranceChannelTypes.Reliable); - return player; EndOfTryAccept: @@ -362,8 +348,6 @@ public override void OnReceive(EndPoint remoteEndPoint, ref SpanBuffer reader, I ConnectedMessageSource.OnReceive(remoteEndPoint, ref reader, method); } - #endregion - public override void OnConnect(EndPoint endPoint) { _logger.Information($"Endpoint connected (RemoteEndPoint='{endPoint}')"); @@ -379,12 +363,29 @@ public override void OnConnect(EndPoint endPoint) return; } PlayerConnectedEvent?.Invoke(player); - //The player has already been sent to every other player in the should connect function. - //Start of sending other players to player - //Now send all players already in the game to the new player - IPlayer[] PlayersAtJoin = _playerRegistry.Players; + //Send to other players that there is a new player + PacketDispatcher.SendExcludingPlayer(player, new INetSerializable[]{ + new PlayerConnectedPacket + { + RemoteConnectionId = player.ConnectionId, + UserId = player.UserId, + UserName = player.UserName, + IsConnectionOwner = false + }, + new PlayerSortOrderPacket + { + UserId = player.UserId, + SortIndex = player.SortIndex + }, + new MpNodePoseSyncStatePacket + { + fullStateUpdateFrequency = 100L, + deltaUpdateFrequency = _playerRegistry.GetMillisBetweenSyncStatePackets() + }, + }, IgnoranceChannelTypes.Reliable); + //Send server infomation to player var Player_ConnectPacket = new INetSerializable[] { new SyncTimePacket @@ -403,6 +404,10 @@ public override void OnConnect(EndPoint endPoint) UserName = _configuration.ServerName, IsConnectionOwner = true }, + new PlayerStatePacket + { + PlayerState = ServerStateHash + }, new SetIsStartButtonEnabledPacket// Disables start button if they are server owner without selected song { Reason = player.UserId == _configuration.ServerOwnerId ? CannotStartGameReason.NoSongSelected : CannotStartGameReason.None @@ -416,8 +421,9 @@ public override void OnConnect(EndPoint endPoint) PacketDispatcher.SendToPlayer(player, Player_ConnectPacket, IgnoranceChannelTypes.Reliable); + //Send other connected players to new player List MakeBigPacketToSendToPlayer = new(); - foreach (IPlayer p in PlayersAtJoin) + foreach (IPlayer p in _playerRegistry.Players) { if(p.ConnectionId != player.ConnectionId) { @@ -439,33 +445,34 @@ public override void OnConnect(EndPoint endPoint) { PacketDispatcher.SendToPlayer(player, SubPacket.ToArray(), IgnoranceChannelTypes.Reliable); } - //End of sending other players to player + //send player avatars and states of other players in server to new player + INetSerializable[] SendToPlayerFromPlayers = new INetSerializable[2]; + SendToPlayerFromPlayers[0] = new PlayerIdentityPacket(); + SendToPlayerFromPlayers[1] = new MpPlayerData(); - // Update permissions - constant manager possibly does not work - if ((_configuration.SetConstantManagerFromUserId == player.UserId || _playerRegistry.GetPlayerCount() == 1) && _configuration.GameplayServerMode == GameplayServerMode.Managed) + foreach (IPlayer p in _playerRegistry.Players) { - _configuration.ServerOwnerId = player.UserId; - PacketDispatcher.SendToNearbyPlayers(new SetPlayersPermissionConfigurationPacket //Sends updated value to all players.//TODO Make a function for updating players permissions as there will be commands for it soon + if (p.ConnectionId != player.ConnectionId) { - PermissionConfiguration = new PlayersPermissionConfiguration - { - PlayersPermission = new PlayerPermissionConfiguration[] - { - new PlayerPermissionConfiguration() - { - UserId = player.UserId, - IsServerOwner = player.IsServerOwner, - HasRecommendBeatmapsPermission = player.CanRecommendBeatmaps, - HasRecommendGameplayModifiersPermission = player.CanRecommendModifiers, - HasKickVotePermission = player.CanKickVote, - HasInvitePermission = player.CanInvite - } - } - } - }, IgnoranceChannelTypes.Reliable); + // Send player to player data to new player + ((PlayerIdentityPacket)SendToPlayerFromPlayers[0]).PlayerState = p.State; + ((PlayerIdentityPacket)SendToPlayerFromPlayers[0]).PlayerAvatar = p.Avatar; + ((PlayerIdentityPacket)SendToPlayerFromPlayers[0]).Random = new ByteArray { Data = p.Random }; + ((PlayerIdentityPacket)SendToPlayerFromPlayers[0]).PublicEncryptionKey = new ByteArray { Data = p.PublicEncryptionKey }; + ((MpPlayerData)SendToPlayerFromPlayers[1]).PlatformID = p.PlatformUserId; + ((MpPlayerData)SendToPlayerFromPlayers[1]).Platform = p.Platform.Convert(); + ((MpPlayerData)SendToPlayerFromPlayers[1]).ClientVersion = p.ClientVersion; + + // Send all player avatars and states to just joined player + PacketDispatcher.SendFromPlayerToPlayer(p, player, SendToPlayerFromPlayers, IgnoranceChannelTypes.Reliable); + } } + // Update permissions + if ((_configuration.SetConstantManagerFromUserId == player.UserId || _playerRegistry.GetPlayerCount() == 1) && _configuration.GameplayServerMode == GameplayServerMode.Managed) + SetNewServerOwner(player); + if (player.Platform == Enums.Platform.Test) //If the player is a bot, send permissions. Normal players request this in a packet when they join { bool HasManager = (_playerRegistry.TryGetPlayer(_configuration.ServerOwnerId, out var ServerOwner) && !player.IsServerOwner); @@ -498,34 +505,8 @@ public override void OnConnect(EndPoint endPoint) }, IgnoranceChannelTypes.Reliable); } - //Start of sending player avatars and states of other players to new player - INetSerializable[] SendToPlayerFromPlayers = new INetSerializable[2]; - SendToPlayerFromPlayers[0] = new PlayerIdentityPacket(); - SendToPlayerFromPlayers[1] = new MpPlayerData(); - - foreach (IPlayer p in PlayersAtJoin) - { - if (p.ConnectionId != player.ConnectionId) - { - // Send player to player data to new player - - ((PlayerIdentityPacket)SendToPlayerFromPlayers[0]).PlayerState = p.State; - ((PlayerIdentityPacket)SendToPlayerFromPlayers[0]).PlayerAvatar = p.Avatar; - ((PlayerIdentityPacket)SendToPlayerFromPlayers[0]).Random = new ByteArray { Data = p.Random }; - ((PlayerIdentityPacket)SendToPlayerFromPlayers[0]).PublicEncryptionKey = new ByteArray { Data = p.PublicEncryptionKey }; - ((MpPlayerData)SendToPlayerFromPlayers[1]).PlatformID = p.PlatformUserId; - ((MpPlayerData)SendToPlayerFromPlayers[1]).Platform = p.Platform.Convert(); - ((MpPlayerData)SendToPlayerFromPlayers[1]).ClientVersion = p.ClientVersion; - - - // Send all player avatars and states to just joined player - PacketDispatcher.SendFromPlayerToPlayer(p, player, SendToPlayerFromPlayers, IgnoranceChannelTypes.Reliable); - //Sends them one by one to avoid server lag - } - } - //End of sending avatars and states to new player - - foreach (IPlayer p in PlayersAtJoin) + //Send joining players mpcore data to everyone else + foreach (IPlayer p in _playerRegistry.Players) { if (p.ConnectionId != player.ConnectionId) { @@ -538,14 +519,11 @@ public override void OnConnect(EndPoint endPoint) } } - var packet = new MpcTextChatPacket { Text = player.UserName + " Joined, Platform: " + player.Platform.ToString() + " Version: " + player.ClientVersion }; - foreach (var p in PlayersAtJoin) - { - if (p.CanTextChat) - { - PacketDispatcher.SendToPlayer(p, packet, IgnoranceChannelTypes.Reliable); - } - } + PacketDispatcher.SendToNearbyPlayers(new MpcTextChatPacket + { + Text = player.UserName + " Joined, Platform: " + player.Platform.ToString() + " Version: " + player.ClientVersion + }, IgnoranceChannelTypes.Reliable); + } public void DisconnectPlayer(string UserId) //Used by master servers kick player event @@ -566,66 +544,37 @@ public override void OnDisconnect(EndPoint endPoint) ); - if (_playerRegistry.TryGetPlayer(endPoint, out var player)) + if (!_playerRegistry.TryGetPlayer(endPoint, out var player)) { - //Sends to all players that they have disconnected - - PacketDispatcher.SendFromPlayer(player, new PlayerDisconnectedPacket - { - DisconnectedReason = DisconnectedReason.ClientConnectionClosed - }, IgnoranceChannelTypes.Reliable); + return; + } + //Sends to all players that they have disconnected - if (_configuration.ServerOwnerId == player.UserId) - _configuration.ServerOwnerId = ""; + PacketDispatcher.SendFromPlayer(player, new PlayerDisconnectedPacket + { + DisconnectedReason = DisconnectedReason.ClientConnectionClosed + }, IgnoranceChannelTypes.Reliable); - _playerRegistry.RemovePlayer(player); - ReleaseSortIndex(player.SortIndex); - ReleaseConnectionId(player.ConnectionId); + _playerRegistry.RemovePlayer(player); + ReleaseSortIndex(player.SortIndex); + ReleaseConnectionId(player.ConnectionId); - PlayerDisconnectedEvent?.Invoke(player); - } + PlayerDisconnectedEvent?.Invoke(player); - if (_playerRegistry.GetPlayerCount() != 0 && string.IsNullOrEmpty(_configuration.ServerOwnerId) && _configuration.GameplayServerMode == GameplayServerMode.Managed) + if(_playerRegistry.GetPlayerCount() != 0) { - var serverOwner = _playerRegistry.Players[0]; - _configuration.ServerOwnerId = serverOwner.UserId; - - // Update permissions - PacketDispatcher.SendToNearbyPlayers(new SetPlayersPermissionConfigurationPacket + if (player.IsServerOwner && _configuration.GameplayServerMode == GameplayServerMode.Managed) { - PermissionConfiguration = new PlayersPermissionConfiguration - { - PlayersPermission = new PlayerPermissionConfiguration[] - { - new PlayerPermissionConfiguration() - { - UserId = serverOwner!.UserId, - IsServerOwner = serverOwner.IsServerOwner, - HasRecommendBeatmapsPermission = serverOwner.CanRecommendBeatmaps, - HasRecommendGameplayModifiersPermission = serverOwner.CanRecommendModifiers, - HasKickVotePermission = serverOwner.CanKickVote, - HasInvitePermission = serverOwner.CanInvite - } - } - } + // Update permissions + SetNewServerOwner(_playerRegistry.Players[0]); + } + PacketDispatcher.SendToNearbyPlayers(new MpNodePoseSyncStatePacket + { + fullStateUpdateFrequency = 100L, + deltaUpdateFrequency = _playerRegistry.GetMillisBetweenSyncStatePackets() }, IgnoranceChannelTypes.Reliable); - - // Disable start button if they are server owner without selected song - if (serverOwner.BeatmapIdentifier == null) - PacketDispatcher.SendToPlayer(serverOwner, new SetIsStartButtonEnabledPacket - { - Reason = CannotStartGameReason.NoSongSelected - }, IgnoranceChannelTypes.Reliable); } - - - PacketDispatcher.SendToNearbyPlayers(new MpNodePoseSyncStatePacket - { - fullStateUpdateFrequency = 100L, - deltaUpdateFrequency = _playerRegistry.GetMillisBetweenSyncStatePackets() - }, IgnoranceChannelTypes.Reliable); - - if (_playerRegistry.GetPlayerCount() == 0) + else { NoPlayersTime = RunTime; if (_configuration.DestroyInstanceTimeout != -1) @@ -647,8 +596,77 @@ public override void OnDisconnect(EndPoint endPoint) } } + #endregion + #region Private Methods + private void SetNewServerOwner(IPlayer NewOwner) + { + if (_playerRegistry.TryGetPlayer(_configuration.ServerOwnerId, out var OldOwner)) + { + _configuration.ServerOwnerId = NewOwner.UserId; + PacketDispatcher.SendToNearbyPlayers(new SetPlayersPermissionConfigurationPacket + { + PermissionConfiguration = new PlayersPermissionConfiguration + { + PlayersPermission = new PlayerPermissionConfiguration[] + { + new PlayerPermissionConfiguration() + { + UserId = OldOwner.UserId, + IsServerOwner = OldOwner.IsServerOwner, + HasRecommendBeatmapsPermission = OldOwner.CanRecommendBeatmaps, + HasRecommendGameplayModifiersPermission = OldOwner.CanRecommendModifiers, + HasKickVotePermission = OldOwner.CanKickVote, + HasInvitePermission = OldOwner.CanInvite + }, + new PlayerPermissionConfiguration() + { + UserId = NewOwner.UserId, + IsServerOwner = NewOwner.IsServerOwner, + HasRecommendBeatmapsPermission = NewOwner.CanRecommendBeatmaps, + HasRecommendGameplayModifiersPermission = NewOwner.CanRecommendModifiers, + HasKickVotePermission = NewOwner.CanKickVote, + HasInvitePermission = NewOwner.CanInvite + } + } + } + }, IgnoranceChannelTypes.Reliable); + PacketDispatcher.SendToPlayer(OldOwner, new SetIsStartButtonEnabledPacket + { + Reason = CannotStartGameReason.None + }, IgnoranceChannelTypes.Reliable); + } + else + { + _configuration.ServerOwnerId = NewOwner.UserId; + PacketDispatcher.SendToNearbyPlayers(new SetPlayersPermissionConfigurationPacket + { + PermissionConfiguration = new PlayersPermissionConfiguration + { + PlayersPermission = new PlayerPermissionConfiguration[] + { + new PlayerPermissionConfiguration() + { + UserId = NewOwner.UserId, + IsServerOwner = NewOwner.IsServerOwner, + HasRecommendBeatmapsPermission = NewOwner.CanRecommendBeatmaps, + HasRecommendGameplayModifiersPermission = NewOwner.CanRecommendModifiers, + HasKickVotePermission = NewOwner.CanKickVote, + HasInvitePermission = NewOwner.CanInvite + } + } + } + }, IgnoranceChannelTypes.Reliable); + } + //Disable start button if no map is selected + if (NewOwner.BeatmapIdentifier == null) + PacketDispatcher.SendToPlayer(NewOwner, new SetIsStartButtonEnabledPacket + { + Reason = CannotStartGameReason.NoSongSelected + }, IgnoranceChannelTypes.Reliable); + } + private async void SendSyncTime(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) @@ -671,11 +689,6 @@ private async void SendSyncTime(CancellationToken cancellationToken) } } - public void InstanceConfigUpdated() - { - UpdateInstanceEvent?.Invoke(this); - } - #endregion } } diff --git a/BeatTogether.DedicatedServer.Kernel/Managers/GameplayManager.cs b/BeatTogether.DedicatedServer.Kernel/Managers/GameplayManager.cs index e0a6081c..15448b7d 100644 --- a/BeatTogether.DedicatedServer.Kernel/Managers/GameplayManager.cs +++ b/BeatTogether.DedicatedServer.Kernel/Managers/GameplayManager.cs @@ -5,7 +5,6 @@ using BeatTogether.DedicatedServer.Messaging.Enums; using BeatTogether.DedicatedServer.Messaging.Models; using BeatTogether.DedicatedServer.Messaging.Packets.MultiplayerSession.GameplayRpc; -//using BeatTogether.LiteNetLib.Enums; using Serilog; using System; using System.Collections.Concurrent; diff --git a/BeatTogether.DedicatedServer.Kernel/Managers/LobbyManager.cs b/BeatTogether.DedicatedServer.Kernel/Managers/LobbyManager.cs index 9f81a019..4f6fb984 100644 --- a/BeatTogether.DedicatedServer.Kernel/Managers/LobbyManager.cs +++ b/BeatTogether.DedicatedServer.Kernel/Managers/LobbyManager.cs @@ -34,7 +34,7 @@ public sealed class LobbyManager : ILobbyManager, IDisposable public bool AllPlayersAreInLobby => _playerRegistry.Players.All(p => p.InMenu);//if all are going to be spectating public bool DoesEveryoneOwnBeatmap => SelectedBeatmap != null && !_playerRegistry.Players.Any(p => (p.GetEntitlement(SelectedBeatmap.LevelId) is EntitlementStatus.NotOwned or EntitlementStatus.Unknown) && !p.IsSpectating && p.WantsToPlayNextLevel); public bool SpectatingPlayersUpdated { get; set; } = false; - public bool ForceStartSelectedBeatmap { get; set; } = false; + public bool ForceStartSelectedBeatmap { get; set; } = false; //For future server-side things public BeatmapIdentifier? SelectedBeatmap { get; private set; } = null; public GameplayModifiers SelectedModifiers { get; private set; } = new(); diff --git a/BeatTogether.DedicatedServer.Kernel/PlayerRegistry.cs b/BeatTogether.DedicatedServer.Kernel/PlayerRegistry.cs index 5cfcef4d..12acbe03 100644 --- a/BeatTogether.DedicatedServer.Kernel/PlayerRegistry.cs +++ b/BeatTogether.DedicatedServer.Kernel/PlayerRegistry.cs @@ -78,10 +78,7 @@ public bool AddPlayer(IPlayer player) _playersByRemoteEndPoint.TryAdd(player.Endpoint, player); _playersByConnectionId.TryAdd(player.ConnectionId, player); _PlayerCount++; - lock (MillisBetweenSyncStatePackets_Lock) - { - MillisBetweenSyncStatePackets = (int)(0.84 * _PlayerCount + 15.789); - } + MillisBetweenSyncStatePackets = (int)(0.84 * _PlayerCount + 15.789); return true; } } @@ -97,10 +94,7 @@ public void RemovePlayer(IPlayer player) _playersByRemoteEndPoint.Remove(player.Endpoint, out _); _playersByConnectionId.Remove(player.ConnectionId, out _); _PlayerCount--; - lock (MillisBetweenSyncStatePackets_Lock) - { - MillisBetweenSyncStatePackets = (int)(0.84 * _PlayerCount + 15.789); - } + MillisBetweenSyncStatePackets = (int)(0.84 * _PlayerCount + 15.789); } } } @@ -127,14 +121,10 @@ public bool TryGetPlayer(string userId, [MaybeNullWhen(false)] out IPlayer playe } } - private readonly object MillisBetweenSyncStatePackets_Lock = new(); private int MillisBetweenSyncStatePackets = 0; public int GetMillisBetweenSyncStatePackets() { - lock (MillisBetweenSyncStatePackets_Lock) - { - return MillisBetweenSyncStatePackets; - } + return MillisBetweenSyncStatePackets; } } } diff --git a/BeatTogether.DedicatedServer.Messaging/Models/BitMask128.cs b/BeatTogether.DedicatedServer.Messaging/Models/BitMask128.cs index 1454b9ec..0cf66c08 100644 --- a/BeatTogether.DedicatedServer.Messaging/Models/BitMask128.cs +++ b/BeatTogether.DedicatedServer.Messaging/Models/BitMask128.cs @@ -23,7 +23,7 @@ public bool Contains(string value, int hashCount = 3, int hashBits = 8) uint hash = MurmurHash2(value); for (int i = 0; i < hashCount; i++) { - if (GetBits((int)((ulong)hash % (ulong)((long)BitCount)), 1) == 0UL) + if (GetBits((int)(hash % (ulong)((long)BitCount)), 1) == 0UL) { return false; } @@ -32,6 +32,16 @@ public bool Contains(string value, int hashCount = 3, int hashBits = 8) return true; } + public void WriteToBitMask(string value, int hashCount = 3, int hashBits = 8) + { + ulong hash = MurmurHash2(value); + for(int i = 0; i < hashCount; i++) + { + SetBits((int)(hash % (ulong)((long)BitCount)), 1UL); + hash >>= hashBits; + } + } + public ulong GetBits(int offset, int count) { ulong num = (1UL << count) - 1UL;