diff --git a/Ragon.Common/Protocol/RagonOperation.cs b/Ragon.Common/Protocol/RagonOperation.cs index d486d5c..adc1480 100644 --- a/Ragon.Common/Protocol/RagonOperation.cs +++ b/Ragon.Common/Protocol/RagonOperation.cs @@ -7,6 +7,7 @@ namespace Ragon.Common AUTHORIZED_FAILED, JOIN_OR_CREATE_ROOM, + CREATE_ROOM, JOIN_ROOM, LEAVE_ROOM, OWNERSHIP_CHANGED, diff --git a/Ragon.Common/Protocol/RagonRoomParameters.cs b/Ragon.Common/Protocol/RagonRoomParameters.cs new file mode 100644 index 0000000..218fa91 --- /dev/null +++ b/Ragon.Common/Protocol/RagonRoomParameters.cs @@ -0,0 +1,25 @@ +using NetStack.Serialization; + +namespace Ragon.Common +{ + public class RagonRoomParameters: IRagonSerializable + { + public string Map { get; set; } + public int Min { get; set; } + public int Max { get; set; } + + public void Serialize(BitBuffer buffer) + { + buffer.AddString(Map); + buffer.AddInt(Min); + buffer.AddInt(Max); + } + + public void Deserialize(BitBuffer buffer) + { + Map = buffer.ReadString(); + Min = buffer.ReadInt(); + Max = buffer.ReadInt(); + } + } +} \ No newline at end of file diff --git a/Ragon.Common/Protocol/RagonSerializer.cs b/Ragon.Common/Protocol/RagonSerializer.cs index d06cba5..1b70bd8 100644 --- a/Ragon.Common/Protocol/RagonSerializer.cs +++ b/Ragon.Common/Protocol/RagonSerializer.cs @@ -38,6 +38,24 @@ namespace Ragon.Common return value; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteBool(bool value) + { + ResizeIfNeed(1); + + _data[_offset] = value ? (byte) 1 : (byte) 0; + _offset += 1; + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ReadBool() + { + var value = _data[_offset]; + _offset += 1; + return value == 1; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteInt(int value) { diff --git a/Ragon/Sources/Game/GameRoom.cs b/Ragon/Sources/Game/GameRoom.cs index 9b9a50f..cc581c1 100755 --- a/Ragon/Sources/Game/GameRoom.cs +++ b/Ragon/Sources/Game/GameRoom.cs @@ -29,7 +29,7 @@ namespace Ragon.Core private uint[] _allPlayers = Array.Empty(); private Entity[] _entitiesAll = Array.Empty(); - public GameRoom(IGameThread gameThread, PluginBase pluginBase, string map, int min, int max) + public GameRoom(IGameThread gameThread, PluginBase pluginBase, string roomId, string map, int min, int max) { _gameThread = gameThread; _plugin = pluginBase; @@ -38,7 +38,7 @@ namespace Ragon.Core Map = map; PlayersMin = min; PlayersMax = max; - Id = Guid.NewGuid().ToString(); + Id = roomId; _plugin.Attach(this); } @@ -94,8 +94,6 @@ namespace Ragon.Core _allPlayers = _players.Select(p => p.Key).ToArray(); _readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray(); - var isOwnershipChange = player.PeerId == _owner; - { _plugin.OnPlayerLeaved(player); @@ -114,34 +112,12 @@ namespace Ragon.Core Broadcast(_readyPlayers, sendData); } - if (_allPlayers.Length > 0 && isOwnershipChange) - { - var newRoomOwnerId = _allPlayers[0]; - var newRoomOwner = _players[newRoomOwnerId]; - - _owner = newRoomOwnerId; - - { - _plugin.OnOwnershipChanged(newRoomOwner); - - _serializer.Clear(); - _serializer.WriteOperation(RagonOperation.OWNERSHIP_CHANGED); - _serializer.WriteString(newRoomOwner.Id); - - var sendData = _serializer.ToArray(); - Broadcast(_readyPlayers, sendData); - } - } - _entitiesAll = _entities.Values.ToArray(); } } - public void ProcessEvent(uint peerId, ReadOnlySpan rawData) + public void ProcessEvent(uint peerId, RagonOperation operation, ReadOnlySpan payloadRawData) { - var operation = (RagonOperation) rawData[0]; - var payloadRawData = rawData.Slice(1, rawData.Length - 1); - _serializer.Clear(); _serializer.FromSpan(ref payloadRawData); @@ -193,6 +169,21 @@ namespace Ragon.Core Broadcast(_readyPlayers, sendData, DeliveryType.Reliable); break; } + case RagonOperation.LOAD_SCENE: + { + var sceneName = _serializer.ReadString(); + _readyPlayers = Array.Empty(); + _entitiesAll = Array.Empty(); + _entities.Clear(); + + _serializer.Clear(); + _serializer.WriteOperation(RagonOperation.LOAD_SCENE); + _serializer.WriteString(sceneName); + + var sendData = _serializer.ToArray(); + Broadcast(_allPlayers, sendData, DeliveryType.Reliable); + break; + } case RagonOperation.REPLICATE_EVENT: { var evntId = _serializer.ReadUShort(); diff --git a/Ragon/Sources/Game/GameThread.cs b/Ragon/Sources/Game/GameThread.cs index 7ea3d6a..e718a6f 100755 --- a/Ragon/Sources/Game/GameThread.cs +++ b/Ragon/Sources/Game/GameThread.cs @@ -1,7 +1,7 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.Threading; +using Ragon.Common; using ENet; using NLog; @@ -29,10 +29,10 @@ namespace Ragon.Core var authorizationProvider = factory.CreateAuthorizationProvider(configuration); var dispatcher = new Dispatcher(); _dispatcherInternal = dispatcher; - + ThreadDispatcher = dispatcher; Server = new ENetServer(this); - + _deltaTime = 1000.0f / configuration.SendRate; _roomManager = new RoomManager(factory, this); @@ -69,7 +69,7 @@ namespace Ragon.Core while (true) { Server.Process(); - + _dispatcherInternal.Process(); var elapsedMilliseconds = _gameLoopTimer.ElapsedMilliseconds; @@ -109,14 +109,13 @@ namespace Ragon.Core evnt.Packet.CopyTo(dataRaw); var data = new ReadOnlySpan(dataRaw); + var operation = (RagonOperation) data[0]; + var payload = data.Slice(1, data.Length - 1); + if (_roomManager.RoomsBySocket.TryGetValue(peerId, out var room)) - { - room.ProcessEvent(peerId, data); - } - else - { - _lobby.ProcessEvent(peerId, data); - } + room.ProcessEvent(peerId, operation, payload); + + _lobby.ProcessEvent(peerId, operation, payload); } catch (Exception exception) { diff --git a/Ragon/Sources/Lobby/Lobby.cs b/Ragon/Sources/Lobby/Lobby.cs index 618c7f8..8ae052b 100644 --- a/Ragon/Sources/Lobby/Lobby.cs +++ b/Ragon/Sources/Lobby/Lobby.cs @@ -1,65 +1,129 @@ using System; using System.Collections.Generic; +using System.Linq; +using NetStack.Serialization; +using NLog; using Ragon.Common; namespace Ragon.Core; public class Lobby : ILobby { + private readonly ILogger _logger = LogManager.GetCurrentClassLogger(); private readonly RagonSerializer _serializer; + private readonly BitBuffer _buffer; private readonly RoomManager _roomManager; private readonly AuthorizationManager _authorizationManager; + private readonly IGameThread _gameThread; public AuthorizationManager AuthorizationManager => _authorizationManager; public Lobby(IAuthorizationProvider provider, RoomManager manager, IGameThread gameThread) { _roomManager = manager; + _gameThread = gameThread; + _buffer = new BitBuffer(); _serializer = new RagonSerializer(); _authorizationManager = new AuthorizationManager(provider, gameThread, this, _serializer); } - public void ProcessEvent(uint peerId, ReadOnlySpan data) + public void ProcessEvent(uint peerId, RagonOperation op, ReadOnlySpan payload) { - var op = (RagonOperation) data[0]; - var payload = data.Slice(1, data.Length - 1); - _serializer.Clear(); _serializer.FromSpan(ref payload); + if (op == RagonOperation.AUTHORIZE) + { + var key = _serializer.ReadString(); + var playerName = _serializer.ReadString(); + var protocol = _serializer.ReadByte(); + _authorizationManager.OnAuthorization(peerId, key, playerName, protocol); + return; + } + + var player = _authorizationManager.GetPlayer(peerId); + if (player == null) + { + _logger.Warn($"Peer not authorized {peerId} trying to {op}"); + return; + } + switch (op) { - case RagonOperation.AUTHORIZE: - { - var key = _serializer.ReadString(); - var playerName = _serializer.ReadString(); - var protocol = _serializer.ReadByte(); - _authorizationManager.OnAuthorization(peerId, key, playerName, protocol); - break; - } case RagonOperation.JOIN_ROOM: { var roomId = _serializer.ReadString(); - var player = _authorizationManager.GetPlayer(peerId); - if (player != null) - _roomManager.Join(player, roomId, Array.Empty()); + roomId = _serializer.ReadString(); + var exists = _roomManager.Rooms.Any(r => r.Id == roomId); + if (!exists) + { + _serializer.Clear(); + _serializer.WriteOperation(RagonOperation.JOIN_FAILED); + _serializer.WriteString($"Room with id {roomId} not exists"); + var sendData = _serializer.ToArray(); + _gameThread.Server.Send(peerId, sendData, DeliveryType.Reliable); + return; + } + + if (_roomManager.RoomsBySocket.ContainsKey(peerId)) + _roomManager.Left(player, Array.Empty()); + + _roomManager.Join(player, roomId, Array.Empty()); + break; + } + case RagonOperation.CREATE_ROOM: + { + var roomId = Guid.NewGuid().ToString(); + var custom = _serializer.ReadBool(); + if (custom) + { + roomId = _serializer.ReadString(); + var exists = _roomManager.Rooms.Any(r => r.Id == roomId); + if (exists) + { + _serializer.Clear(); + _serializer.WriteOperation(RagonOperation.JOIN_FAILED); + _serializer.WriteString($"Room with id {roomId} already exists"); + + var sendData = _serializer.ToArray(); + _gameThread.Server.Send(peerId, sendData, DeliveryType.Reliable); + return; + } + } + + var propertiesPayload = _serializer.ReadData(_serializer.Size); + _buffer.Clear(); + _buffer.FromSpan(ref propertiesPayload, propertiesPayload.Length); + + var roomProperties = new RagonRoomParameters(); + roomProperties.Deserialize(_buffer); + + if (_roomManager.RoomsBySocket.ContainsKey(peerId)) + _roomManager.Left(player, Array.Empty()); + + _roomManager.Create(player, roomId, roomProperties, Array.Empty()); break; } case RagonOperation.JOIN_OR_CREATE_ROOM: { - var min = _serializer.ReadUShort(); - var max = _serializer.ReadUShort(); - var map = _serializer.ReadString(); - var player = _authorizationManager.GetPlayer(peerId); - if (player != null) - _roomManager.JoinOrCreate(player, map, min, max, Array.Empty()); + var roomId = Guid.NewGuid().ToString(); + var roomProperties = new RagonRoomParameters(); + var propertiesPayload = _serializer.ReadData(_serializer.Size); + + _buffer.Clear(); + _buffer.FromSpan(ref propertiesPayload, propertiesPayload.Length); + + roomProperties.Deserialize(_buffer); + + if (_roomManager.RoomsBySocket.ContainsKey(peerId)) + _roomManager.Left(player, Array.Empty()); + + _roomManager.JoinOrCreate(player, roomId, roomProperties, Array.Empty()); break; } case RagonOperation.LEAVE_ROOM: { - var player = _authorizationManager.GetPlayer(peerId); - if (player != null) - _roomManager.Left(player, Array.Empty()); + _roomManager.Left(player, Array.Empty()); break; } } diff --git a/Ragon/Sources/Room/RoomManager.cs b/Ragon/Sources/Room/RoomManager.cs index c35144c..5bcc303 100644 --- a/Ragon/Sources/Room/RoomManager.cs +++ b/Ragon/Sources/Room/RoomManager.cs @@ -11,12 +11,12 @@ public class RoomManager private readonly IGameThread _gameThread; private readonly PluginFactory _factory; private readonly Logger _logger = LogManager.GetCurrentClassLogger(); - private readonly List _rooms = new List(); + private readonly List _rooms = new(); private readonly Dictionary _roomsBySocket; public IReadOnlyDictionary RoomsBySocket => _roomsBySocket; public IReadOnlyList Rooms => _rooms; - + public RoomManager(PluginFactory factory, IGameThread gameThread) { _gameThread = gameThread; @@ -26,6 +26,8 @@ public class RoomManager public void Join(Player player, string roomId, byte[] payload) { + _logger.Trace($"Player ({player.PlayerName}|{player.Id}) joined to room with Id {roomId}"); + if (_rooms.Count > 0) { foreach (var existRoom in _rooms) @@ -34,20 +36,46 @@ public class RoomManager { existRoom.Joined(player, payload); _roomsBySocket.Add(player.PeerId, existRoom); - break; + return; } } } } - public void JoinOrCreate(Player player, string map, int min, int max, byte[] payload) + public void Create(Player creator, string roomId, RagonRoomParameters parameters, byte[] payload) { + var map = parameters.Map; + var min = parameters.Min; + var max = parameters.Max; + + _logger.Trace($"Player ({creator.PlayerName}|{creator.Id}) create room with Id {roomId} and params ({map}|{min}|{max})"); + + var plugin = _factory.CreatePlugin(map); + if (plugin == null) + throw new NullReferenceException($"Plugin for map {map} is null"); + + var room = new GameRoom(_gameThread, plugin, roomId, map, min, max); + room.Joined(creator, payload); + room.Start(); + + _roomsBySocket.Add(creator.PeerId, room); + _rooms.Add(room); + } + + public void JoinOrCreate(Player player, string roomId, RagonRoomParameters parameters, byte[] payload) + { + var map = parameters.Map; + var min = parameters.Min; + var max = parameters.Max; + if (_rooms.Count > 0) { foreach (var existRoom in _rooms) { if (existRoom.Map == map && existRoom.PlayersCount < existRoom.PlayersMax) { + _logger.Trace($"Player ({player.PlayerName}|{player.Id}) joined to room with Id {roomId}"); + existRoom.Joined(player, payload); _roomsBySocket.Add(player.PeerId, existRoom); return; @@ -55,11 +83,13 @@ public class RoomManager } } + _logger.Trace($"Room not found for Player ({player.PlayerName}|{player.Id}), create room with Id {roomId} and params ({map}|{min}|{max})"); + var plugin = _factory.CreatePlugin(map); if (plugin == null) throw new NullReferenceException($"Plugin for map {map} is null"); - var room = new GameRoom(_gameThread, plugin, map, min, max); + var room = new GameRoom(_gameThread, plugin, roomId, map, min, max); room.Joined(player, payload); room.Start(); @@ -71,12 +101,16 @@ public class RoomManager { if (_roomsBySocket.Remove(player.PeerId, out var room)) { + _logger.Trace($"Player ({player.PlayerName}|{player.Id}) left room with Id {room.Id}"); room.Leave(player.PeerId); if (room.PlayersCount < room.PlayersMin) { + _logger.Trace($"Room with Id {room.Id} destroyed"); room.Stop(); _rooms.Remove(room); } + + _gameThread.Server.Send(player.PeerId, new byte[] {(byte) RagonOperation.LEAVE_ROOM}, DeliveryType.Reliable); } }