diff --git a/Ragon.Client.Simulation/Sources/Game.cs b/Ragon.Client.Simulation/Sources/Game.cs index 60a1c21..feca145 100644 --- a/Ragon.Client.Simulation/Sources/Game.cs +++ b/Ragon.Client.Simulation/Sources/Game.cs @@ -18,7 +18,7 @@ public class Game : IRagonListener public void OnConnected(RagonClient client) { RagonLog.Trace("Connected"); - _client.Session.AuthorizeWithKey("defaultkey", "Player Eduard", Array.Empty()); + _client.Session.AuthorizeWithKey("defaultkey", "Player Eduard"); } public void OnAuthorizationSuccess(RagonClient client, string playerId, string playerName) diff --git a/Ragon.Client/Ragon.Client.csproj b/Ragon.Client/Ragon.Client.csproj index ec23c4d..effaeb8 100644 --- a/Ragon.Client/Ragon.Client.csproj +++ b/Ragon.Client/Ragon.Client.csproj @@ -12,7 +12,7 @@ true none - /Users/edmand46/RagonProjects/ragon-unity-sdk/Assets/Ragon/Runtime/Plugins + /Users/edmand46/RagonProjects/ragon-oss-sdk/Assets/Ragon/Plugins/ diff --git a/Ragon.Client/Sources/Listener/IRagonConnectionListener.cs b/Ragon.Client/Sources/Listener/IRagonConnectionListener.cs index 5cec3e0..ec4e06a 100644 --- a/Ragon.Client/Sources/Listener/IRagonConnectionListener.cs +++ b/Ragon.Client/Sources/Listener/IRagonConnectionListener.cs @@ -16,7 +16,7 @@ namespace Ragon.Client; -public interface IRagonConnectedListener +public interface IRagonConnectionListener { void OnConnected(RagonClient client); void OnDisconnected(RagonClient client); diff --git a/Ragon.Client/Sources/Listener/IRagonListener.cs b/Ragon.Client/Sources/Listener/IRagonListener.cs index 933804e..d5f9c0d 100644 --- a/Ragon.Client/Sources/Listener/IRagonListener.cs +++ b/Ragon.Client/Sources/Listener/IRagonListener.cs @@ -18,7 +18,7 @@ namespace Ragon.Client { public interface IRagonListener : IRagonAuthorizationListener, - IRagonConnectedListener, + IRagonConnectionListener, IRagonFailedListener, IRagonJoinListener, IRagonLeftListener, diff --git a/Ragon.Client/Sources/RagonClient.cs b/Ragon.Client/Sources/RagonClient.cs index dabec77..a059782 100644 --- a/Ragon.Client/Sources/RagonClient.cs +++ b/Ragon.Client/Sources/RagonClient.cs @@ -87,23 +87,18 @@ namespace Ragon.Client _handlers = new Handler[byte.MaxValue]; _handlers[(byte)RagonOperation.AUTHORIZED_SUCCESS] = new AuthorizeSuccessHandler(_listenerList); _handlers[(byte)RagonOperation.AUTHORIZED_FAILED] = new AuthorizeFailedHandler(_listenerList); - _handlers[(byte)RagonOperation.JOIN_SUCCESS] = - new JoinSuccessHandler(this, _readBuffer, _listenerList, _playerCache, _entityCache); + _handlers[(byte)RagonOperation.JOIN_SUCCESS] = new JoinSuccessHandler(this, _readBuffer, _listenerList, _playerCache, _entityCache); _handlers[(byte)RagonOperation.JOIN_FAILED] = new JoinFailedHandler(_listenerList); _handlers[(byte)RagonOperation.LEAVE_ROOM] = new LeaveRoomHandler(this, _listenerList, _entityCache); - _handlers[(byte)RagonOperation.OWNERSHIP_CHANGED] = - new OwnershipHandler(_listenerList, _playerCache, _entityCache); + _handlers[(byte)RagonOperation.OWNERSHIP_CHANGED] = new OwnershipHandler(_listenerList, _playerCache, _entityCache); _handlers[(byte)RagonOperation.PLAYER_JOINED] = new PlayerJoinHandler(_playerCache, _listenerList); - _handlers[(byte)RagonOperation.PLAYER_LEAVED] = - new PlayerLeftHandler(_entityCache, _playerCache, _listenerList); + _handlers[(byte)RagonOperation.PLAYER_LEAVED] = new PlayerLeftHandler(_entityCache, _playerCache, _listenerList); _handlers[(byte)RagonOperation.LOAD_SCENE] = new SceneLoadHandler(this, _listenerList); _handlers[(byte)RagonOperation.CREATE_ENTITY] = new EntityCreateHandler(this, _playerCache, _entityCache); - _handlers[(byte)RagonOperation.DESTROY_ENTITY] = new EntityDestroyHandler(_entityCache); + _handlers[(byte)RagonOperation.REMOVE_ENTITY] = new EntityDestroyHandler(_entityCache); _handlers[(byte)RagonOperation.REPLICATE_ENTITY_STATE] = new StateEntityHandler(_entityCache); - _handlers[(byte)RagonOperation.REPLICATE_ENTITY_EVENT] = - new EntityEventHandler(this, _playerCache, _entityCache); - _handlers[(byte)RagonOperation.SNAPSHOT] = - new SnapshotHandler(this, _listenerList, _entityCache, _playerCache); + _handlers[(byte)RagonOperation.REPLICATE_ENTITY_EVENT] = new EntityEventHandler(this, _playerCache, _entityCache); + _handlers[(byte)RagonOperation.SNAPSHOT] = new SnapshotHandler(this, _listenerList, _entityCache, _playerCache); var protocolRaw = RagonVersion.Parse(protocol); _connection.Connect(address, port, protocolRaw); @@ -144,7 +139,7 @@ namespace Ragon.Client public void AddListener(IRagonListener listener) => _listenerList.Add(listener); public void AddListener(IRagonAuthorizationListener listener) => _listenerList.Add(listener); - public void AddListener(IRagonConnectedListener listener) => _listenerList.Add(listener); + public void AddListener(IRagonConnectionListener listener) => _listenerList.Add(listener); public void AddListener(IRagonFailedListener listener) => _listenerList.Add(listener); public void AddListener(IRagonJoinListener listener) => _listenerList.Add(listener); public void AddListener(IRagonLeftListener listener) => _listenerList.Add(listener); @@ -155,7 +150,7 @@ namespace Ragon.Client public void RemoveListener(IRagonListener listener) => _listenerList.Remove(listener); public void RemoveListener(IRagonAuthorizationListener listener) => _listenerList.Remove(listener); - public void RemoveListener(IRagonConnectedListener listener) => _listenerList.Remove(listener); + public void RemoveListener(IRagonConnectionListener listener) => _listenerList.Remove(listener); public void RemoveListener(IRagonFailedListener listener) => _listenerList.Remove(listener); public void RemoveListener(IRagonJoinListener listener) => _listenerList.Remove(listener); public void RemoveListener(IRagonLeftListener listener) => _listenerList.Remove(listener); diff --git a/Ragon.Client/Sources/RagonEntityCache.cs b/Ragon.Client/Sources/RagonEntityCache.cs index 0c95774..eabc6eb 100644 --- a/Ragon.Client/Sources/RagonEntityCache.cs +++ b/Ragon.Client/Sources/RagonEntityCache.cs @@ -81,7 +81,7 @@ public sealed class RagonEntityCache var buffer = _client.Buffer; buffer.Clear(); - buffer.WriteOperation(RagonOperation.DESTROY_ENTITY); + buffer.WriteOperation(RagonOperation.REMOVE_ENTITY); buffer.WriteUShort(entity.Id); destroyPayload?.Serialize(buffer); diff --git a/Ragon.Client/Sources/RagonListenerList.cs b/Ragon.Client/Sources/RagonListenerList.cs index eca5666..494d746 100644 --- a/Ragon.Client/Sources/RagonListenerList.cs +++ b/Ragon.Client/Sources/RagonListenerList.cs @@ -20,7 +20,7 @@ namespace Ragon.Client { private readonly RagonClient _client; private readonly List _authorizationListeners = new(); - private readonly List _connectionListeners = new(); + private readonly List _connectionListeners = new(); private readonly List _failedListeners = new(); private readonly List _joinListeners = new(); private readonly List _leftListeners = new(); @@ -65,7 +65,7 @@ namespace Ragon.Client _authorizationListeners.Add(listener); } - public void Add(IRagonConnectedListener listener) + public void Add(IRagonConnectionListener listener) { _connectionListeners.Add(listener); } @@ -110,7 +110,7 @@ namespace Ragon.Client _authorizationListeners.Remove(listener); } - public void Remove(IRagonConnectedListener listener) + public void Remove(IRagonConnectionListener listener) { _connectionListeners.Remove(listener); } diff --git a/Ragon.Client/Sources/RagonSession.cs b/Ragon.Client/Sources/RagonSession.cs index 29914ec..fba61ae 100644 --- a/Ragon.Client/Sources/RagonSession.cs +++ b/Ragon.Client/Sources/RagonSession.cs @@ -93,13 +93,13 @@ namespace Ragon.Client _client.Reliable.Send(sendData); } - public void AuthorizeWithKey(string key, string playerName, byte[] additonalData) + public void AuthorizeWithKey(string key, string playerName, string payload = "") { _buffer.Clear(); _buffer.WriteOperation(RagonOperation.AUTHORIZE); _buffer.WriteString(key); _buffer.WriteString(playerName); - _buffer.WriteBytes(additonalData); + _buffer.WriteString(payload); var sendData = _buffer.ToArray(); _client.Reliable.Send(sendData); diff --git a/Ragon.Protocol/Sources/RagonOperation.cs b/Ragon.Protocol/Sources/RagonOperation.cs index d4f110f..c492b68 100644 --- a/Ragon.Protocol/Sources/RagonOperation.cs +++ b/Ragon.Protocol/Sources/RagonOperation.cs @@ -34,7 +34,7 @@ namespace Ragon.Protocol PLAYER_JOINED, PLAYER_LEAVED, CREATE_ENTITY, - DESTROY_ENTITY, + REMOVE_ENTITY, SNAPSHOT, REPLICATE_ENTITY_STATE, REPLICATE_ENTITY_EVENT, diff --git a/Ragon.Relay/Sources/Command/KickPlayerCommand.cs b/Ragon.Relay/Sources/Command/KickPlayerCommand.cs new file mode 100644 index 0000000..8fdcd9a --- /dev/null +++ b/Ragon.Relay/Sources/Command/KickPlayerCommand.cs @@ -0,0 +1,6 @@ +namespace Ragon.Relay; + +public class KickPlayerCommand +{ + public string Id; +} \ No newline at end of file diff --git a/Ragon.Relay/Relay.cs b/Ragon.Relay/Sources/Relay.cs similarity index 79% rename from Ragon.Relay/Relay.cs rename to Ragon.Relay/Sources/Relay.cs index 0192094..a5d8a55 100644 --- a/Ragon.Relay/Relay.cs +++ b/Ragon.Relay/Sources/Relay.cs @@ -18,7 +18,8 @@ using NLog; using Ragon.Server; using Ragon.Server.ENet; using Ragon.Server.DotNetWebsockets; - +using Ragon.Server.IO; +using Ragon.Server.Plugin; namespace Ragon.Relay; @@ -32,21 +33,19 @@ public class Relay var configuration = Configuration.Load("relay.config.json"); var serverType = Configuration.GetServerType(configuration.ServerType); - INetworkServer server = null; + INetworkServer networkServer = new ENetServer(); + IServerPlugin plugin = new RelayServerPlugin(); switch (serverType) { case ServerType.ENET: - server = new ENetServer(); + networkServer = new ENetServer(); break; case ServerType.WEBSOCKET: - server = new DotNetWebSocketServer(); - break; - default: - server = new ENetServer(); + networkServer = new DotNetWebSocketServer(); break; } - var relay = new RagonServer(server, configuration); + var relay = new RagonServer(networkServer, plugin, configuration); logger.Info("Started"); relay.Start(); } diff --git a/Ragon.Relay/Sources/RelayRoomPlugin.cs b/Ragon.Relay/Sources/RelayRoomPlugin.cs new file mode 100644 index 0000000..c0d95a2 --- /dev/null +++ b/Ragon.Relay/Sources/RelayRoomPlugin.cs @@ -0,0 +1,37 @@ +using System; +using Ragon.Server; +using Ragon.Server.Entity; +using Ragon.Server.Plugin; +using Ragon.Server.Room; + +namespace Ragon.Relay; + +public class RelayRoomPlugin: BaseRoomPlugin +{ + public void Tick(float dt) + { + + } + + public void OnAttached() + { + Console.WriteLine("Room attached"); + } + + public void OnDetached() + { + Console.WriteLine("Room detached"); + } + + public bool OnEntityCreate(RagonRoomPlayer creator, RagonEntity entity) + { + Console.WriteLine($"Entity created: {entity.Id}"); + return true; + } + + public bool OnEntityRemove(RagonRoomPlayer destroyer, RagonEntity entity) + { + Console.WriteLine($"Entity destroyed: {entity.Id}"); + return true; + } +} \ No newline at end of file diff --git a/Ragon.Relay/Sources/RelayServerPlugin.cs b/Ragon.Relay/Sources/RelayServerPlugin.cs new file mode 100644 index 0000000..c98ee4c --- /dev/null +++ b/Ragon.Relay/Sources/RelayServerPlugin.cs @@ -0,0 +1,24 @@ +using System; +using Newtonsoft.Json; +using Ragon.Server.Plugin; + +namespace Ragon.Relay; + +public class RelayServerPlugin: BaseServerPlugin +{ + public override bool OnCommand(string command, string payload) + { + Console.WriteLine(command); + if (command == "kick-player") + { + var commandPayload = JsonConvert.DeserializeObject(payload); + var player = GetPlayerById(commandPayload.Id); + if (player != null) + player.Connection.Close(); + else + Console.WriteLine($"Player not found with Id {commandPayload.Id}"); + } + + return true; + } +} \ No newline at end of file diff --git a/Ragon.Relay/relay.config.json b/Ragon.Relay/relay.config.json index c6faf4a..3e61bde 100644 --- a/Ragon.Relay/relay.config.json +++ b/Ragon.Relay/relay.config.json @@ -4,7 +4,16 @@ "serverTickRate": 30, "gameProtocol": "1.0.0", "port": 5000, + "httpPort": 5001, + "httpKey": "defaultkey", "limitConnections": 4095, "limitPlayersPerRoom": 20, - "limitRooms": 200 + "limitRooms": 200, + "webHooks": + { + "room-created": "http://127.0.0.1:3000/service/create-room", + "room-removed": "http://127.0.0.1:3000/service/remove-room", + "room-joined": "http://127.0.0.1:3000/service/join-room", + "room-leaved": "http://127.0.0.1:3000/service/leave-room" + } } \ No newline at end of file diff --git a/Ragon.Server.DotNetWebSockets/Sources/WebSocketConnection.cs b/Ragon.Server.DotNetWebSockets/Sources/WebSocketConnection.cs index ca8a748..59182c8 100644 --- a/Ragon.Server.DotNetWebSockets/Sources/WebSocketConnection.cs +++ b/Ragon.Server.DotNetWebSockets/Sources/WebSocketConnection.cs @@ -14,8 +14,9 @@ * limitations under the License. */ -using System.Net.WebSockets; using NLog; +using System.Net.WebSockets; +using Ragon.Server.IO; namespace Ragon.Server.DotNetWebsockets; @@ -43,6 +44,11 @@ public sealed class WebSocketConnection : INetworkConnection Unreliable = unreliableChannel; } + public void Close() + { + Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None); + } + public async Task Flush() { foreach (var channel in _channels) diff --git a/Ragon.Server.DotNetWebSockets/Sources/WebSocketReliableChannel.cs b/Ragon.Server.DotNetWebSockets/Sources/WebSocketReliableChannel.cs index 79454df..b310b73 100644 --- a/Ragon.Server.DotNetWebSockets/Sources/WebSocketReliableChannel.cs +++ b/Ragon.Server.DotNetWebSockets/Sources/WebSocketReliableChannel.cs @@ -15,7 +15,7 @@ */ using System.Net.WebSockets; -using Ragon.Server; +using Ragon.Server.IO; namespace Ragon.Server.DotNetWebsockets; diff --git a/Ragon.Server.DotNetWebSockets/Sources/WebSocketServer.cs b/Ragon.Server.DotNetWebSockets/Sources/WebSocketServer.cs index 8e98712..4e94d90 100644 --- a/Ragon.Server.DotNetWebSockets/Sources/WebSocketServer.cs +++ b/Ragon.Server.DotNetWebSockets/Sources/WebSocketServer.cs @@ -18,6 +18,7 @@ using System.Net; using System.Net.WebSockets; using NLog; using Ragon.Protocol; +using Ragon.Server.IO; namespace Ragon.Server.DotNetWebsockets; diff --git a/Ragon.Server.ENet/Sources/ENetConnection.cs b/Ragon.Server.ENet/Sources/ENetConnection.cs index 837ca78..20b7aba 100644 --- a/Ragon.Server.ENet/Sources/ENetConnection.cs +++ b/Ragon.Server.ENet/Sources/ENetConnection.cs @@ -15,6 +15,7 @@ */ using ENet; +using Ragon.Server.IO; namespace Ragon.Server.ENet; @@ -23,11 +24,19 @@ public sealed class ENetConnection: INetworkConnection public ushort Id { get; } public INetworkChannel Reliable { get; private set; } public INetworkChannel Unreliable { get; private set; } + private Peer _peer; public ENetConnection(Peer peer) { + _peer = peer; + Id = (ushort) peer.ID; Reliable = new ENetReliableChannel(peer, 0); Unreliable = new ENetUnreliableChannel(peer, 1); } + + public void Close() + { + _peer.Disconnect(0); + } } \ No newline at end of file diff --git a/Ragon.Server.ENet/Sources/ENetReliableChannel.cs b/Ragon.Server.ENet/Sources/ENetReliableChannel.cs index 822ba3a..762ae3e 100644 --- a/Ragon.Server.ENet/Sources/ENetReliableChannel.cs +++ b/Ragon.Server.ENet/Sources/ENetReliableChannel.cs @@ -15,6 +15,7 @@ */ using ENet; +using Ragon.Server.IO; namespace Ragon.Server.ENet; diff --git a/Ragon.Server.ENet/Sources/ENetServer.cs b/Ragon.Server.ENet/Sources/ENetServer.cs index 853d934..c953a3d 100644 --- a/Ragon.Server.ENet/Sources/ENetServer.cs +++ b/Ragon.Server.ENet/Sources/ENetServer.cs @@ -17,6 +17,7 @@ using ENet; using NLog; using Ragon.Protocol; +using Ragon.Server.IO; namespace Ragon.Server.ENet { diff --git a/Ragon.Server.ENet/Sources/ENetUnreliableChannel.cs b/Ragon.Server.ENet/Sources/ENetUnreliableChannel.cs index 995fe7c..268128e 100644 --- a/Ragon.Server.ENet/Sources/ENetUnreliableChannel.cs +++ b/Ragon.Server.ENet/Sources/ENetUnreliableChannel.cs @@ -15,6 +15,7 @@ */ using ENet; +using Ragon.Server.IO; namespace Ragon.Server.ENet; diff --git a/Ragon.Server/Sources/Entity/RagonEntity.cs b/Ragon.Server/Sources/Entity/RagonEntity.cs index c55bacf..da75e00 100644 --- a/Ragon.Server/Sources/Entity/RagonEntity.cs +++ b/Ragon.Server/Sources/Entity/RagonEntity.cs @@ -16,8 +16,9 @@ using Ragon.Protocol; +using Ragon.Server.Room; -namespace Ragon.Server; +namespace Ragon.Server.Entity; public class RagonEntity { @@ -30,29 +31,33 @@ public class RagonEntity public RagonAuthority Authority { get; private set; } public RagonPayload Payload { get; private set; } public RagonEntityState State { get; private set; } - private readonly List _bufferedEvents; - - public RagonEntity(RagonRoomPlayer owner, ushort type, ushort staticId, ushort attachId, RagonAuthority eventAuthority) + + public RagonEntity(RagonEntityParameters parameters) { - Owner = owner; - StaticId = staticId; - Type = type; - AttachId = attachId; Id = _idGenerator++; - Authority = eventAuthority; + + StaticId = parameters.StaticId; + Type = parameters.Type; + AttachId = parameters.AttachId; + Authority = parameters.Authority; + State = new RagonEntityState(this); Payload = new RagonPayload(); - + _bufferedEvents = new List(); } - - - public void SetOwner(RagonRoomPlayer owner) + + public void Attach(RagonRoomPlayer owner) { Owner = owner; } + public void Detach() + { + + } + public void RestoreBufferedEvents(RagonRoomPlayer roomPlayer, RagonBuffer writer) { foreach (var evnt in _bufferedEvents) @@ -96,7 +101,7 @@ public class RagonEntity var buffer = room.Writer; buffer.Clear(); - buffer.WriteOperation(RagonOperation.DESTROY_ENTITY); + buffer.WriteOperation(RagonOperation.REMOVE_ENTITY); buffer.WriteUShort(Id); Payload.Write(buffer); @@ -209,4 +214,17 @@ public class RagonEntity } } } + + public void Write(RagonBuffer writer) + { + State.Write(writer); + } + + public void Read(RagonRoomPlayer player, RagonBuffer reader) + { + if (Owner.Connection.Id != player.Connection.Id) + return; + + State.Read(reader); + } } \ No newline at end of file diff --git a/Ragon.Server/Sources/Entity/RagonEntityParameters.cs b/Ragon.Server/Sources/Entity/RagonEntityParameters.cs new file mode 100644 index 0000000..36bea24 --- /dev/null +++ b/Ragon.Server/Sources/Entity/RagonEntityParameters.cs @@ -0,0 +1,27 @@ +/* + * Copyright 2023 Eduard Kargin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Ragon.Protocol; + +namespace Ragon.Server.Entity; + +public ref struct RagonEntityParameters +{ + public ushort Type; + public ushort StaticId; + public ushort AttachId; + public RagonAuthority Authority; +} \ No newline at end of file diff --git a/Ragon.Server/Sources/Entity/RagonEntityState.cs b/Ragon.Server/Sources/Entity/RagonEntityState.cs index bfccd59..140d7af 100644 --- a/Ragon.Server/Sources/Entity/RagonEntityState.cs +++ b/Ragon.Server/Sources/Entity/RagonEntityState.cs @@ -17,7 +17,7 @@ using Ragon.Protocol; -namespace Ragon.Server; +namespace Ragon.Server.Entity; public class RagonEntityState { diff --git a/Ragon.Server/Sources/Entity/RagonEvent.cs b/Ragon.Server/Sources/Entity/RagonEvent.cs index 48069ec..ae0a773 100644 --- a/Ragon.Server/Sources/Entity/RagonEvent.cs +++ b/Ragon.Server/Sources/Entity/RagonEvent.cs @@ -14,10 +14,10 @@ * limitations under the License. */ - using Ragon.Protocol; +using Ragon.Server.Room; -namespace Ragon.Server; +namespace Ragon.Server.Entity; public class RagonEvent { diff --git a/Ragon.Server/Sources/Entity/RagonPayload.cs b/Ragon.Server/Sources/Entity/RagonPayload.cs index 064eb5e..b461759 100644 --- a/Ragon.Server/Sources/Entity/RagonPayload.cs +++ b/Ragon.Server/Sources/Entity/RagonPayload.cs @@ -17,7 +17,7 @@ using Ragon.Protocol; -namespace Ragon.Server; +namespace Ragon.Server.Entity; public class RagonPayload { diff --git a/Ragon.Server/Sources/Entity/RagonProperty.cs b/Ragon.Server/Sources/Entity/RagonProperty.cs index 3f453c1..7738caf 100644 --- a/Ragon.Server/Sources/Entity/RagonProperty.cs +++ b/Ragon.Server/Sources/Entity/RagonProperty.cs @@ -14,11 +14,9 @@ * limitations under the License. */ - -using System.ComponentModel; using Ragon.Protocol; -namespace Ragon.Server; +namespace Ragon.Server.Entity; public class RagonProperty : RagonPayload { diff --git a/Ragon.Server/Sources/Handler/AuthorizationOperation.cs b/Ragon.Server/Sources/Handler/AuthorizationOperation.cs index 6dac1f5..b76e6ee 100644 --- a/Ragon.Server/Sources/Handler/AuthorizationOperation.cs +++ b/Ragon.Server/Sources/Handler/AuthorizationOperation.cs @@ -16,40 +16,98 @@ using NLog; using Ragon.Protocol; +using Ragon.Server.Lobby; +using Ragon.Server.Plugin; +using Ragon.Server.Plugin.Web; -namespace Ragon.Server; + +namespace Ragon.Server.Handler; public sealed class AuthorizationOperation: IRagonOperation { private Logger _logger = LogManager.GetCurrentClassLogger(); + private readonly RagonWebHookPlugin _ragonWebHook; + private readonly RagonContextObserver _contextObserver; + private readonly Configuration _configuration; + private readonly RagonBuffer _writer; + + public AuthorizationOperation(RagonWebHookPlugin ragonWebHook, + RagonContextObserver contextObserver, + RagonBuffer writer, + Configuration configuration) + { + _ragonWebHook = ragonWebHook; + _configuration = configuration; + _contextObserver = contextObserver; + _writer = writer; + } public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) { - if (context.LobbyPlayer.Status == LobbyPlayerStatus.Authorized) + if (context.ConnectionStatus == ConnectionStatus.Authorized) { - _logger.Warn("Player already authorized"); + _logger.Warn("Player already authorized!"); + return; + } + + if (context.ConnectionStatus == ConnectionStatus.InProcess) + { + _logger.Warn("Player already request authorization!"); return; } var key = reader.ReadString(); - var playerName = reader.ReadString(); - var additionalPayload = new RagonPayload(); - additionalPayload.Read(reader); + var name = reader.ReadString(); + var payload = reader.ReadString(); + + if (key == _configuration.ServerKey) + { + if (_ragonWebHook.RequestAuthorization(context, name, payload)) + return; + + var lobbyPlayer = new RagonLobbyPlayer(context.Connection, Guid.NewGuid().ToString(), name, payload); + context.SetPlayer(lobbyPlayer); + + Approve(context); + } + else + { + Reject(context); + } + } - context.LobbyPlayer.Name = playerName; - context.LobbyPlayer.AdditionalData = Array.Empty(); - context.LobbyPlayer.Status = LobbyPlayerStatus.Authorized; + public void Approve(RagonContext context) + { + context.ConnectionStatus = ConnectionStatus.Authorized; + _contextObserver.OnAuthorized(context); + var playerId = context.LobbyPlayer.Id; + var playerName = context.LobbyPlayer.Name; + var playerPayload = context.LobbyPlayer.Payload; + + _writer.Clear(); + _writer.WriteOperation(RagonOperation.AUTHORIZED_SUCCESS); + _writer.WriteString(playerId); + _writer.WriteString(playerName); + _writer.WriteString(playerPayload); - writer.Clear(); - writer.WriteOperation(RagonOperation.AUTHORIZED_SUCCESS); - writer.WriteString(playerId); - writer.WriteString(playerName); - - var sendData = writer.ToArray(); + var sendData = _writer.ToArray(); context.Connection.Reliable.Send(sendData); - _logger.Trace($"Connection {context.Connection.Id} as {playerId}|{context.LobbyPlayer.Name} authorized"); + _logger.Trace($"Connection {context.Connection.Id} as {playerId}|{context.LobbyPlayer.Name} authorized"); + } + + public void Reject(RagonContext context) + { + _writer.Clear(); + _writer.WriteOperation(RagonOperation.AUTHORIZED_FAILED); + + var sendData = _writer.ToArray(); + + context.Connection.Reliable.Send(sendData); + context.Connection.Close(); + + _logger.Trace($"Connection {context.Connection.Id}"); } } \ No newline at end of file diff --git a/Ragon.Server/Sources/Handler/EntityCreateOperation.cs b/Ragon.Server/Sources/Handler/EntityCreateOperation.cs index 32c0069..8fb83a6 100644 --- a/Ragon.Server/Sources/Handler/EntityCreateOperation.cs +++ b/Ragon.Server/Sources/Handler/EntityCreateOperation.cs @@ -16,12 +16,13 @@ using NLog; using Ragon.Protocol; +using Ragon.Server.Entity; -namespace Ragon.Server; +namespace Ragon.Server.Handler; public sealed class EntityCreateOperation : IRagonOperation { - private Logger _logger = LogManager.GetCurrentClassLogger(); + private readonly Logger _logger = LogManager.GetCurrentClassLogger(); public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) { @@ -32,7 +33,15 @@ public sealed class EntityCreateOperation : IRagonOperation var eventAuthority = (RagonAuthority) reader.ReadByte(); var propertiesCount = reader.ReadUShort(); - var entity = new RagonEntity(player, entityType, 0, attachId, eventAuthority); + var entityParameters = new RagonEntityParameters() + { + Type = entityType, + Authority = eventAuthority, + AttachId = attachId, + StaticId = 0 + }; + + var entity = new RagonEntity(entityParameters); for (var i = 0; i < propertiesCount; i++) { var propertyType = reader.ReadBool(); @@ -40,13 +49,18 @@ public sealed class EntityCreateOperation : IRagonOperation entity.State.AddProperty(new RagonProperty(propertySize, propertyType)); } - + if (reader.Capacity > 0) entity.Payload.Read(reader); - + + var roomPlugin = room.Plugin; + if (!roomPlugin.OnEntityCreate(player, entity)) + return; + + entity.Attach(player); room.AttachEntity(entity); player.AttachEntity(entity); - + entity.Create(); _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} created entity {entity.Id}:{entity.Type}"); diff --git a/Ragon.Server/Sources/Handler/EntityEventOperation.cs b/Ragon.Server/Sources/Handler/EntityEventOperation.cs index 284721a..18192b8 100644 --- a/Ragon.Server/Sources/Handler/EntityEventOperation.cs +++ b/Ragon.Server/Sources/Handler/EntityEventOperation.cs @@ -16,12 +16,13 @@ using NLog; using Ragon.Protocol; +using Ragon.Server.Entity; -namespace Ragon.Server; +namespace Ragon.Server.Handler; public sealed class EntityEventOperation : IRagonOperation { - private Logger _logger = LogManager.GetCurrentClassLogger(); + private readonly Logger _logger = LogManager.GetCurrentClassLogger(); public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) { diff --git a/Ragon.Server/Sources/Handler/EntityDestroyOperation.cs b/Ragon.Server/Sources/Handler/EntityRemoveOperation.cs similarity index 90% rename from Ragon.Server/Sources/Handler/EntityDestroyOperation.cs rename to Ragon.Server/Sources/Handler/EntityRemoveOperation.cs index a112529..e3ec76a 100644 --- a/Ragon.Server/Sources/Handler/EntityDestroyOperation.cs +++ b/Ragon.Server/Sources/Handler/EntityRemoveOperation.cs @@ -16,12 +16,13 @@ using NLog; using Ragon.Protocol; +using Ragon.Server.Entity; -namespace Ragon.Server; +namespace Ragon.Server.Handler; public sealed class EntityDestroyOperation: IRagonOperation { - private Logger _logger = LogManager.GetCurrentClassLogger(); + private readonly Logger _logger = LogManager.GetCurrentClassLogger(); public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) { diff --git a/Ragon.Server/Sources/Handler/EntityStateOperation.cs b/Ragon.Server/Sources/Handler/EntityStateOperation.cs index c463e77..c9ec700 100644 --- a/Ragon.Server/Sources/Handler/EntityStateOperation.cs +++ b/Ragon.Server/Sources/Handler/EntityStateOperation.cs @@ -17,29 +17,25 @@ using NLog; using Ragon.Protocol; -namespace Ragon.Server; +namespace Ragon.Server.Handler; public sealed class EntityStateOperation: IRagonOperation { - private ILogger _logger = LogManager.GetCurrentClassLogger(); + private readonly ILogger _logger = LogManager.GetCurrentClassLogger(); public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) { var room = context.Room; + var player = context.RoomPlayer; var entitiesCount = reader.ReadUShort(); for (var entityIndex = 0; entityIndex < entitiesCount; entityIndex++) { var entityId = reader.ReadUShort(); - if (room.Entities.TryGetValue(entityId, out var entity) && entity.Owner.Connection.Id == context.Connection.Id) - { - entity.State.Read(reader); - room.Track(entity); - } + if (room.Entities.TryGetValue(entityId, out var entity)) + entity.Read(player, reader); else - { _logger.Error($"Entity with Id {entityId} not found, replication interrupted"); - } } } } \ No newline at end of file diff --git a/Ragon.Server/Sources/IRagonOperation.cs b/Ragon.Server/Sources/Handler/IRagonOperation.cs similarity index 96% rename from Ragon.Server/Sources/IRagonOperation.cs rename to Ragon.Server/Sources/Handler/IRagonOperation.cs index e94882e..3db5c0a 100644 --- a/Ragon.Server/Sources/IRagonOperation.cs +++ b/Ragon.Server/Sources/Handler/IRagonOperation.cs @@ -16,7 +16,7 @@ using Ragon.Protocol; -namespace Ragon.Server; +namespace Ragon.Server.Handler; public interface IRagonOperation { diff --git a/Ragon.Server/Sources/Handler/RoomCreateOperation.cs b/Ragon.Server/Sources/Handler/RoomCreateOperation.cs index a97fad2..011edd5 100644 --- a/Ragon.Server/Sources/Handler/RoomCreateOperation.cs +++ b/Ragon.Server/Sources/Handler/RoomCreateOperation.cs @@ -16,17 +16,29 @@ using NLog; using Ragon.Protocol; +using Ragon.Server.Lobby; +using Ragon.Server.Plugin; +using Ragon.Server.Plugin.Web; +using Ragon.Server.Room; -namespace Ragon.Server; +namespace Ragon.Server.Handler; public sealed class RoomCreateOperation: IRagonOperation { - private RagonRoomParameters _roomParameters = new(); - private Logger _logger = LogManager.GetCurrentClassLogger(); + private readonly RagonRoomParameters _roomParameters = new(); + private readonly Logger _logger = LogManager.GetCurrentClassLogger(); + private readonly IServerPlugin _serverPlugin; + private readonly RagonWebHookPlugin _ragonWebHookPlugin; + public RoomCreateOperation(IServerPlugin serverPlugin, RagonWebHookPlugin ragonWebHook) + { + _serverPlugin = serverPlugin; + _ragonWebHookPlugin = ragonWebHook; + } + public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) { - if (context.LobbyPlayer.Status == LobbyPlayerStatus.Unauthorized) + if (context.ConnectionStatus == ConnectionStatus.Unauthorized) { _logger.Warn($"Player {context.Connection.Id} not authorized for this request"); return; @@ -62,17 +74,22 @@ public sealed class RoomCreateOperation: IRagonOperation }; var lobbyPlayer = context.LobbyPlayer; + var roomPlayer = new RagonRoomPlayer(context.Connection, lobbyPlayer.Id, lobbyPlayer.Name); + + var roomPlugin = _serverPlugin.CreateRoomPlugin(information); + var room = new RagonRoom(roomId, information, roomPlugin); + + roomPlayer.OnAttached(room); - var room = new RagonRoom(roomId, information); context.Scheduler.Run(room); context.Lobby.Persist(room); + context.SetRoom(room, roomPlayer); - var player = new RagonRoomPlayer(lobbyPlayer.Connection, lobbyPlayer.Id, lobbyPlayer.Name); - context.SetRoom(room, player); + _ragonWebHookPlugin.RoomCreated(context, room, roomPlayer); - _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} create room {room.Id} {information}"); + _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} create room {room.Id} with map {information.Map}"); - JoinSuccess(player, room, writer); + JoinSuccess(roomPlayer, room, writer); _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} joined to room {room.Id}"); } @@ -84,9 +101,9 @@ public sealed class RoomCreateOperation: IRagonOperation writer.WriteString(room.Id); writer.WriteString(player.Id); writer.WriteString(room.Owner.Id); - writer.WriteUShort((ushort) room.Info.Min); - writer.WriteUShort((ushort) room.Info.Max); - writer.WriteString(room.Info.Map); + writer.WriteUShort((ushort) room.PlayerMin); + writer.WriteUShort((ushort) room.PlayerMax); + writer.WriteString(room.Map); var sendData = writer.ToArray(); player.Connection.Reliable.Send(sendData); diff --git a/Ragon.Server/Sources/Handler/RoomJoinOperation.cs b/Ragon.Server/Sources/Handler/RoomJoinOperation.cs index a9eda75..c9f2cb6 100644 --- a/Ragon.Server/Sources/Handler/RoomJoinOperation.cs +++ b/Ragon.Server/Sources/Handler/RoomJoinOperation.cs @@ -16,12 +16,23 @@ using NLog; using Ragon.Protocol; +using Ragon.Server.Plugin; +using Ragon.Server.Plugin.Web; +using Ragon.Server.Room; -namespace Ragon.Server; +namespace Ragon.Server.Handler; public sealed class RoomJoinOperation : IRagonOperation { - private Logger _logger = LogManager.GetCurrentClassLogger(); + private readonly Logger _logger = LogManager.GetCurrentClassLogger(); + private readonly IServerPlugin _serverPlugin; + private readonly RagonWebHookPlugin _ragonWebHookPlugin; + + public RoomJoinOperation(IServerPlugin serverPlugin, RagonWebHookPlugin plugin) + { + _serverPlugin = serverPlugin; + _ragonWebHookPlugin = plugin; + } public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) { @@ -30,42 +41,47 @@ public sealed class RoomJoinOperation : IRagonOperation if (!context.Lobby.FindRoomById(roomId, out var existsRoom)) { - JoinFailed(lobbyPlayer, writer); + JoinFailed(context, writer); _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} failed to join room {roomId}"); return; } - var player = new RagonRoomPlayer(lobbyPlayer.Connection, lobbyPlayer.Id, lobbyPlayer.Name); + var player = new RagonRoomPlayer(context.Connection, lobbyPlayer.Id, lobbyPlayer.Name); context.SetRoom(existsRoom, player); - JoinSuccess(player, existsRoom, writer); + if (!_serverPlugin.OnRoomJoin(player, existsRoom)) + return; + + _ragonWebHookPlugin.RoomJoined(context, existsRoom, player); + + JoinSuccess(context, existsRoom, writer); _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} joined to {existsRoom.Id}"); } - private void JoinSuccess(RagonRoomPlayer player, RagonRoom room, RagonBuffer writer) + private void JoinSuccess(RagonContext context, RagonRoom room, RagonBuffer writer) { writer.Clear(); writer.WriteOperation(RagonOperation.JOIN_SUCCESS); writer.WriteString(room.Id); - writer.WriteString(player.Id); + writer.WriteString(context.RoomPlayer.Id); writer.WriteString(room.Owner.Id); - writer.WriteUShort((ushort) room.Info.Min); - writer.WriteUShort((ushort) room.Info.Max); - writer.WriteString(room.Info.Map); + writer.WriteUShort((ushort) room.PlayerMin); + writer.WriteUShort((ushort) room.PlayerMax); + writer.WriteString(room.Map); var sendData = writer.ToArray(); - player.Connection.Reliable.Send(sendData); + context.Connection.Reliable.Send(sendData); } - private void JoinFailed(RagonLobbyPlayer player, RagonBuffer writer) + private void JoinFailed(RagonContext context, RagonBuffer writer) { writer.Clear(); writer.WriteOperation(RagonOperation.JOIN_FAILED); writer.WriteString($"Room not exists"); var sendData = writer.ToArray(); - player.Connection.Reliable.Send(sendData); + context.Connection.Reliable.Send(sendData); } } \ No newline at end of file diff --git a/Ragon.Server/Sources/Handler/RoomJoinOrCreateOperation.cs b/Ragon.Server/Sources/Handler/RoomJoinOrCreateOperation.cs index 17f509f..74f1a4d 100644 --- a/Ragon.Server/Sources/Handler/RoomJoinOrCreateOperation.cs +++ b/Ragon.Server/Sources/Handler/RoomJoinOrCreateOperation.cs @@ -16,17 +16,29 @@ using NLog; using Ragon.Protocol; +using Ragon.Server.Lobby; +using Ragon.Server.Plugin; +using Ragon.Server.Plugin.Web; +using Ragon.Server.Room; -namespace Ragon.Server; +namespace Ragon.Server.Handler; public sealed class RoomJoinOrCreateOperation : IRagonOperation { - private RagonRoomParameters _roomParameters = new(); - private Logger _logger = LogManager.GetCurrentClassLogger(); + private readonly RagonRoomParameters _roomParameters = new(); + private readonly Logger _logger = LogManager.GetCurrentClassLogger(); + private readonly IServerPlugin _serverPlugin; + private readonly RagonWebHookPlugin _ragonWebHookPlugin; + public RoomJoinOrCreateOperation(IServerPlugin serverPlugin, RagonWebHookPlugin plugin) + { + _serverPlugin = serverPlugin; + _ragonWebHookPlugin = plugin; + } + public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) { - if (context.LobbyPlayer.Status == LobbyPlayerStatus.Unauthorized) + if (context.ConnectionStatus == ConnectionStatus.Unauthorized) { _logger.Warn("Player not authorized for this request"); return; @@ -39,9 +51,11 @@ public sealed class RoomJoinOrCreateOperation : IRagonOperation if (context.Lobby.FindRoomByMap(_roomParameters.Map, out var existsRoom)) { - var player = new RagonRoomPlayer(lobbyPlayer.Connection, lobbyPlayer.Id, lobbyPlayer.Name); + var player = new RagonRoomPlayer(context.Connection, lobbyPlayer.Id, lobbyPlayer.Name); context.SetRoom(existsRoom, player); + _ragonWebHookPlugin.RoomJoined(context, existsRoom, player); + JoinSuccess(player, existsRoom, writer); } else @@ -53,14 +67,17 @@ public sealed class RoomJoinOrCreateOperation : IRagonOperation Min = _roomParameters.Min, }; - var room = new RagonRoom(roomId, information); + var roomPlayer = new RagonRoomPlayer(context.Connection, lobbyPlayer.Id, lobbyPlayer.Name); + var roomPlugin = _serverPlugin.CreateRoomPlugin(information); + var room = new RagonRoom(roomId, information, roomPlugin); + + _ragonWebHookPlugin.RoomCreated(context, room, roomPlayer); + context.Lobby.Persist(room); context.Scheduler.Run(room); - - var roomPlayer = new RagonRoomPlayer(lobbyPlayer.Connection, lobbyPlayer.Id, lobbyPlayer.Name); context.SetRoom(room, roomPlayer); - _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} create room {room.Id} {information}"); + _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} create room {room.Id} with map {information.Map}"); JoinSuccess(roomPlayer, room, writer); } @@ -73,9 +90,9 @@ public sealed class RoomJoinOrCreateOperation : IRagonOperation writer.WriteString(room.Id); writer.WriteString(player.Id); writer.WriteString(room.Owner.Id); - writer.WriteUShort((ushort) room.Info.Min); - writer.WriteUShort((ushort) room.Info.Max); - writer.WriteString(room.Info.Map); + writer.WriteUShort((ushort) room.PlayerMin); + writer.WriteUShort((ushort) room.PlayerMax); + writer.WriteString(room.Map); var sendData = writer.ToArray(); player.Connection.Reliable.Send(sendData); diff --git a/Ragon.Server/Sources/Handler/RoomLeaveOperation.cs b/Ragon.Server/Sources/Handler/RoomLeaveOperation.cs index c118216..4de5f6c 100644 --- a/Ragon.Server/Sources/Handler/RoomLeaveOperation.cs +++ b/Ragon.Server/Sources/Handler/RoomLeaveOperation.cs @@ -16,18 +16,31 @@ using NLog; using Ragon.Protocol; +using Ragon.Server.Plugin; +using Ragon.Server.Plugin.Web; -namespace Ragon.Server; +namespace Ragon.Server.Handler; public sealed class RoomLeaveOperation: IRagonOperation { - private Logger _logger = LogManager.GetCurrentClassLogger(); + private readonly Logger _logger = LogManager.GetCurrentClassLogger(); + private readonly IServerPlugin _serverPlugin; + private readonly RagonWebHookPlugin _ragonWebHookPlugin; + public RoomLeaveOperation(IServerPlugin serverPlugin, RagonWebHookPlugin plugin) + { + _serverPlugin = serverPlugin; + _ragonWebHookPlugin = plugin; + } + public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) { var room = context.Room; var roomPlayer = context.RoomPlayer; + if (room != null) - { + { + _serverPlugin.OnRoomLeave(roomPlayer, room); + _ragonWebHookPlugin.RoomLeaved(context, room, roomPlayer); context.Room?.DetachPlayer(roomPlayer); _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} leaved from {room.Id}"); } diff --git a/Ragon.Server/Sources/Handler/SceneLoadOperation.cs b/Ragon.Server/Sources/Handler/SceneLoadOperation.cs index 7aae87c..c92069c 100644 --- a/Ragon.Server/Sources/Handler/SceneLoadOperation.cs +++ b/Ragon.Server/Sources/Handler/SceneLoadOperation.cs @@ -18,11 +18,11 @@ using NLog; using Ragon.Protocol; -namespace Ragon.Server; +namespace Ragon.Server.Handler; public class SceneLoadOperation: IRagonOperation { - private Logger _logger = LogManager.GetCurrentClassLogger(); + private readonly Logger _logger = LogManager.GetCurrentClassLogger(); public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) { diff --git a/Ragon.Server/Sources/Handler/SceneLoadedOperation.cs b/Ragon.Server/Sources/Handler/SceneLoadedOperation.cs index 7c20e6e..f489887 100644 --- a/Ragon.Server/Sources/Handler/SceneLoadedOperation.cs +++ b/Ragon.Server/Sources/Handler/SceneLoadedOperation.cs @@ -16,16 +16,24 @@ using NLog; using Ragon.Protocol; +using Ragon.Server.Entity; +using Ragon.Server.Lobby; +using Ragon.Server.Room; -namespace Ragon.Server; +namespace Ragon.Server.Handler; public sealed class SceneLoadedOperation : IRagonOperation { - private Logger _logger = LogManager.GetCurrentClassLogger(); + private readonly Logger _logger = LogManager.GetCurrentClassLogger(); + + public SceneLoadedOperation() + { + + } public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) { - if (context.LobbyPlayer.Status == LobbyPlayerStatus.Unauthorized) + if (context.ConnectionStatus == ConnectionStatus.Unauthorized) return; var owner = context.Room.Owner; @@ -34,6 +42,7 @@ public sealed class SceneLoadedOperation : IRagonOperation if (player == owner) { + var statics = reader.ReadUShort(); for (var staticIndex = 0; staticIndex < statics; staticIndex++) { @@ -41,20 +50,32 @@ public sealed class SceneLoadedOperation : IRagonOperation var eventAuthority = (RagonAuthority)reader.ReadByte(); var staticId = reader.ReadUShort(); var propertiesCount = reader.ReadUShort(); - - var entity = new RagonEntity(player, entityType, staticId, 0, eventAuthority); + + var entityParameters = new RagonEntityParameters() + { + Type = entityType, + Authority = eventAuthority, + AttachId = 0, + StaticId = staticId, + }; + + var entity = new RagonEntity(entityParameters); for (var propertyIndex = 0; propertyIndex < propertiesCount; propertyIndex++) { var propertyType = reader.ReadBool(); var propertySize = reader.ReadUShort(); entity.State.AddProperty(new RagonProperty(propertySize, propertyType)); } + + var roomPlugin = room.Plugin; + if (roomPlugin.OnEntityCreate(player, entity)) continue; + + var playerInfo = $"Player {context.Connection.Id}|{context.LobbyPlayer.Name}"; + var entityInfo = $"{entity.Id}:{entity.Type}"; - var playerInfo = $"Player {context.Connection.Id}|{context.LobbyPlayer.Name}"; - var entityInfo = $"{entity.Id}:{entity.Type}"; - _logger.Trace($"{playerInfo} created entity {entityInfo}"); - + + entity.Attach(player); room.AttachEntity(entity); player.AttachEntity(entity); } @@ -123,7 +144,7 @@ public sealed class SceneLoadedOperation : IRagonOperation writer.WriteString(roomPlayer.Id); writer.WriteString(roomPlayer.Name); } - + var dynamicEntities = room.DynamicEntitiesList; var dynamicEntitiesCount = (ushort)dynamicEntities.Count; writer.WriteUShort(dynamicEntitiesCount); @@ -135,7 +156,7 @@ public sealed class SceneLoadedOperation : IRagonOperation writer.WriteUShort(staticEntitiesCount); foreach (var entity in staticEntities) entity.Snapshot(writer); - + var sendData = writer.ToArray(); foreach (var player in receviersList) player.Connection.Reliable.Send(sendData); diff --git a/Ragon.Server/Sources/Http/RagonHttpServer.cs b/Ragon.Server/Sources/Http/RagonHttpServer.cs new file mode 100644 index 0000000..4c4b4c8 --- /dev/null +++ b/Ragon.Server/Sources/Http/RagonHttpServer.cs @@ -0,0 +1,111 @@ +/* + * Copyright 2023 Eduard Kargin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using NLog; +using System.Net; +using System.Text.Json; +using Ragon.Server.IO; +using Ragon.Server.Plugin; + +namespace Ragon.Server.Http; + +public class RagonHttpServer +{ + private readonly ILogger _logger = LogManager.GetCurrentClassLogger(); + private readonly IExecutor _executor; + private readonly IServerPlugin _serverPlugin; + private HttpListener _httpListener; + private CancellationTokenSource _cancellationTokenSource; + + public RagonHttpServer(IExecutor executor, IServerPlugin serverPlugin) + { + _serverPlugin = serverPlugin; + _executor = executor; + } + + public async void StartAccept(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + var context = await _httpListener.GetContextAsync(); + + if (context.Request.HttpMethod != "POST") + { + context.Response.StatusCode = 404; + context.Response.ContentLength64 = 0; + context.Response.Close(); + } + + var request = context.Request; + var reader = new StreamReader(request.InputStream, request.ContentEncoding); + var rawJson = await reader.ReadToEndAsync(); + var httpCommand = JsonDocument.Parse(rawJson); + if (httpCommand != null) + { + try + { + var command = httpCommand.RootElement.GetProperty("command"); + var payload = httpCommand.RootElement.GetProperty("payload"); + + if (_serverPlugin.OnCommand(command.GetString() ?? "none", payload.GetRawText())) + { + context.Response.StatusCode = 200; + context.Response.ContentLength64 = 0; + context.Response.Close(); + } + else + { + context.Response.StatusCode = 403; + context.Response.ContentLength64 = 0; + context.Response.Close(); + } + } + catch (Exception ex) + { + _logger.Error(ex); + + context.Response.StatusCode = 505; + context.Response.ContentLength64 = 0; + context.Response.Close(); + } + + continue; + } + + context.Response.StatusCode = 403; + context.Response.ContentLength64 = 0; + context.Response.Close(); + } + } + + public void Start(Configuration configuration) + { + _cancellationTokenSource = new CancellationTokenSource(); + _logger.Info($"Listen at http://0.0.0.0:{configuration.HttpPort}/"); + + _httpListener = new HttpListener(); + _httpListener.Prefixes.Add($"http://127.0.0.1:{configuration.HttpPort}/"); + _httpListener.Start(); + + _executor.Run(() => StartAccept(_cancellationTokenSource.Token), TaskCreationOptions.LongRunning); + } + + public void Stop() + { + _cancellationTokenSource.Cancel(); + _httpListener.Stop(); + } +} \ No newline at end of file diff --git a/Ragon.Server/Sources/IO/Executor.cs b/Ragon.Server/Sources/IO/Executor.cs index a503546..050b872 100644 --- a/Ragon.Server/Sources/IO/Executor.cs +++ b/Ragon.Server/Sources/IO/Executor.cs @@ -16,26 +16,31 @@ using System.Threading.Channels; -namespace Ragon.Server; +namespace Ragon.Server.IO; -public class Executor: TaskScheduler, IExecutor +public class Executor : TaskScheduler, IExecutor { - private ChannelReader _reader; - private ChannelWriter _writer; - private Queue _pendingTasks; - private TaskFactory _taskFactory; + private readonly ChannelReader _reader; + private readonly ChannelWriter _writer; + private readonly Queue _pendingTasks; + private readonly TaskFactory _taskFactory; - public void Run(Action action) + public Task Run(Action action, TaskCreationOptions task = TaskCreationOptions.None) { - _taskFactory.StartNew(action); + return _taskFactory.StartNew(action, task); } public Executor() { - var channel = Channel.CreateUnbounded(); + var channel = Channel.CreateUnbounded(new UnboundedChannelOptions() + { + SingleReader = true, + SingleWriter = true, + }); + _reader = channel.Reader; _writer = channel.Writer; - + _taskFactory = new TaskFactory(this); _pendingTasks = new Queue(); } @@ -60,7 +65,7 @@ public class Executor: TaskScheduler, IExecutor while (_reader.TryRead(out var task)) { TryExecuteTask(task); - + if (task.Status == TaskStatus.Running) _pendingTasks.Enqueue(task); } diff --git a/Ragon.Server/Sources/IO/IExecutor.cs b/Ragon.Server/Sources/IO/IExecutor.cs index e1560ba..da9f11d 100644 --- a/Ragon.Server/Sources/IO/IExecutor.cs +++ b/Ragon.Server/Sources/IO/IExecutor.cs @@ -14,9 +14,9 @@ * limitations under the License. */ -namespace Ragon.Server; +namespace Ragon.Server.IO; public interface IExecutor { - public void Run(Action action); + public Task Run(Action action, TaskCreationOptions task = TaskCreationOptions.None); } \ No newline at end of file diff --git a/Ragon.Server/Sources/IO/INetworkChannel.cs b/Ragon.Server/Sources/IO/INetworkChannel.cs index fa8c7e2..f2c8074 100644 --- a/Ragon.Server/Sources/IO/INetworkChannel.cs +++ b/Ragon.Server/Sources/IO/INetworkChannel.cs @@ -14,7 +14,7 @@ * limitations under the License. */ -namespace Ragon.Server; +namespace Ragon.Server.IO; public interface INetworkChannel { diff --git a/Ragon.Server/Sources/IO/INetworkConnection.cs b/Ragon.Server/Sources/IO/INetworkConnection.cs index 4ad3c86..8d79a5c 100644 --- a/Ragon.Server/Sources/IO/INetworkConnection.cs +++ b/Ragon.Server/Sources/IO/INetworkConnection.cs @@ -14,11 +14,12 @@ * limitations under the License. */ -namespace Ragon.Server; +namespace Ragon.Server.IO; public interface INetworkConnection { public ushort Id { get; } public INetworkChannel Reliable { get; } public INetworkChannel Unreliable { get; } + public void Close(); } \ No newline at end of file diff --git a/Ragon.Server/Sources/IO/INetworkListener.cs b/Ragon.Server/Sources/IO/INetworkListener.cs index 7dd3928..ff47db9 100644 --- a/Ragon.Server/Sources/IO/INetworkListener.cs +++ b/Ragon.Server/Sources/IO/INetworkListener.cs @@ -14,7 +14,7 @@ * limitations under the License. */ -namespace Ragon.Server; +namespace Ragon.Server.IO; public interface INetworkListener { diff --git a/Ragon.Server/Sources/IO/INetworkServer.cs b/Ragon.Server/Sources/IO/INetworkServer.cs index 107506a..0108e02 100644 --- a/Ragon.Server/Sources/IO/INetworkServer.cs +++ b/Ragon.Server/Sources/IO/INetworkServer.cs @@ -14,7 +14,7 @@ * limitations under the License. */ -namespace Ragon.Server; +namespace Ragon.Server.IO; public interface INetworkServer { diff --git a/Ragon.Server/Sources/IO/NetworkConfiguration.cs b/Ragon.Server/Sources/IO/NetworkConfiguration.cs index b76768b..90a1023 100644 --- a/Ragon.Server/Sources/IO/NetworkConfiguration.cs +++ b/Ragon.Server/Sources/IO/NetworkConfiguration.cs @@ -14,7 +14,7 @@ * limitations under the License. */ -namespace Ragon.Server; +namespace Ragon.Server.IO; public struct NetworkConfiguration { diff --git a/Ragon.Server/Sources/IRagonContextObserver.cs b/Ragon.Server/Sources/IRagonContextObserver.cs new file mode 100644 index 0000000..46e49e5 --- /dev/null +++ b/Ragon.Server/Sources/IRagonContextObserver.cs @@ -0,0 +1,31 @@ +/* + * Copyright 2023 Eduard Kargin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Ragon.Server; + +public class RagonContextObserver +{ + private Dictionary _contexts; + public RagonContextObserver(Dictionary contexts) + { + _contexts = contexts; + } + + public void OnAuthorized(RagonContext context) + { + _contexts.Add(context.LobbyPlayer.Id, context); + } +} \ No newline at end of file diff --git a/Ragon.Server/Sources/IRagonServer.cs b/Ragon.Server/Sources/IRagonServer.cs new file mode 100644 index 0000000..0dcba94 --- /dev/null +++ b/Ragon.Server/Sources/IRagonServer.cs @@ -0,0 +1,25 @@ +/* + * Copyright 2023 Eduard Kargin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Ragon.Server.IO; + +namespace Ragon.Server; + +public interface IRagonServer +{ + RagonContext? ResolveContext(INetworkConnection connection); + RagonContext? ResolveContext(string id); +} \ No newline at end of file diff --git a/Ragon.Server/Sources/Lobby/IRagonLobby.cs b/Ragon.Server/Sources/Lobby/IRagonLobby.cs index 7003df4..439b66d 100644 --- a/Ragon.Server/Sources/Lobby/IRagonLobby.cs +++ b/Ragon.Server/Sources/Lobby/IRagonLobby.cs @@ -15,13 +15,14 @@ */ using System.Diagnostics.CodeAnalysis; +using Ragon.Server.Room; -namespace Ragon.Server; +namespace Ragon.Server.Lobby; public interface IRagonLobby { public bool FindRoomById(string roomId, [MaybeNullWhen(false)] out RagonRoom room); public bool FindRoomByMap(string map, [MaybeNullWhen(false)] out RagonRoom room); public void Persist(RagonRoom room); - public void RemoveIfEmpty(RagonRoom room); + public bool RemoveIfEmpty(RagonRoom room); } \ No newline at end of file diff --git a/Ragon.Server/Sources/Lobby/RagonLobbyInMemory.cs b/Ragon.Server/Sources/Lobby/RagonLobbyInMemory.cs index 2f1f5f5..0d7b4c8 100644 --- a/Ragon.Server/Sources/Lobby/RagonLobbyInMemory.cs +++ b/Ragon.Server/Sources/Lobby/RagonLobbyInMemory.cs @@ -16,8 +16,9 @@ using System.Diagnostics.CodeAnalysis; using NLog; +using Ragon.Server.Room; -namespace Ragon.Server; +namespace Ragon.Server.Lobby; public class LobbyInMemory : IRagonLobby { @@ -28,8 +29,7 @@ public class LobbyInMemory : IRagonLobby { foreach (var existRagonRoom in _rooms) { - var info = existRagonRoom.Info; - if (existRagonRoom.Id == RagonRoomId && info.Min < info.Max) + if (existRagonRoom.Id == RagonRoomId && existRagonRoom.PlayerMin < existRagonRoom.PlayerMax) { room = existRagonRoom; return true; @@ -44,8 +44,7 @@ public class LobbyInMemory : IRagonLobby { foreach (var existsRoom in _rooms) { - var info = existsRoom.Info; - if (info.Map == map && existsRoom.Players.Count < info.Max) + if (existsRoom.Map == map && existsRoom.PlayerCount < existsRoom.PlayerMax) { room = existsRoom; return true; @@ -62,18 +61,23 @@ public class LobbyInMemory : IRagonLobby _logger.Trace($"New room: {room.Id}"); foreach (var r in _rooms) - _logger.Trace($"Room: {r.Id} {r.Info} Players: {r.Players.Count} Entities: {r.Entities.Count}"); + _logger.Trace($"Room: {r.Id} Map: {r.Map} Players: {r.Players.Count} Entities: {r.Entities.Count}"); } - public void RemoveIfEmpty(RagonRoom room) + public bool RemoveIfEmpty(RagonRoom room) { + var result = false; if (room.Players.Count == 0) { _rooms.Remove(room); _logger.Trace($"Room {room.Id} removed"); + + result = true; } foreach (var r in _rooms) - _logger.Trace($"Room: {r.Id} {r.Info} Players: {r.Players.Count} Entities: {r.Entities.Count}"); + _logger.Trace($"Room: {r.Id} Map: {r.Map} Players: {r.Players.Count} Entities: {r.Entities.Count}"); + + return result; } } \ No newline at end of file diff --git a/Ragon.Server/Sources/Lobby/RagonLobbyPlayer.cs b/Ragon.Server/Sources/Lobby/RagonLobbyPlayer.cs index 54bde74..69377eb 100644 --- a/Ragon.Server/Sources/Lobby/RagonLobbyPlayer.cs +++ b/Ragon.Server/Sources/Lobby/RagonLobbyPlayer.cs @@ -14,28 +14,29 @@ * limitations under the License. */ -namespace Ragon.Server; +using Ragon.Server.IO; -public enum LobbyPlayerStatus +namespace Ragon.Server.Lobby; + +public enum ConnectionStatus { Unauthorized, + InProcess, Authorized, } public class RagonLobbyPlayer { + public INetworkConnection Connection { get; } public string Id { get; private set; } - public string Name { get; set; } - public byte[] AdditionalData { get; set; } - public LobbyPlayerStatus Status { get; set; } - public INetworkConnection Connection { get; private set; } + public string Name { get; private set; } + public string Payload { get; private set; } - public RagonLobbyPlayer(INetworkConnection connection) + public RagonLobbyPlayer(INetworkConnection connection, string id, string name, string payload) { - Id = Guid.NewGuid().ToString(); + Id = id; + Name = name; + Payload = payload; Connection = connection; - Status = LobbyPlayerStatus.Unauthorized; - Name = "None"; - AdditionalData = Array.Empty(); } } \ No newline at end of file diff --git a/Ragon.Server/Sources/Plugin/BaseRoomPlugin.cs b/Ragon.Server/Sources/Plugin/BaseRoomPlugin.cs new file mode 100644 index 0000000..62626d0 --- /dev/null +++ b/Ragon.Server/Sources/Plugin/BaseRoomPlugin.cs @@ -0,0 +1,67 @@ +/* + * Copyright 2023 Eduard Kargin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Ragon.Server.Entity; +using Ragon.Server.IO; +using Ragon.Server.Room; + +namespace Ragon.Server.Plugin; + +public class BaseRoomPlugin: IRoomPlugin +{ + private IRagonRoom _ragonRoom; + + public RagonRoomPlayer GetPlayerById(string id) + { + var player = _ragonRoom.GetPlayerById(id); + return player; + } + + public RagonRoomPlayer GetPlayerByConnection(INetworkConnection connection) + { + var player = _ragonRoom.GetPlayerByConnection(connection); + return player; + } + + public virtual void OnAttached(IRagonRoom room) + { + _ragonRoom = room; + } + + public virtual void OnDetached() + { + + } + + #region VIRTUAL + + public virtual void Tick(float dt) + { + + } + + public virtual bool OnEntityCreate(RagonRoomPlayer creator, RagonEntity entity) + { + return true; + } + + public virtual bool OnEntityRemove(RagonRoomPlayer remover, RagonEntity entity) + { + return true; + } + + #endregion +} \ No newline at end of file diff --git a/Ragon.Server/Sources/Plugin/BaseServerPlugin.cs b/Ragon.Server/Sources/Plugin/BaseServerPlugin.cs new file mode 100644 index 0000000..fa69c1c --- /dev/null +++ b/Ragon.Server/Sources/Plugin/BaseServerPlugin.cs @@ -0,0 +1,78 @@ +/* + * Copyright 2023 Eduard Kargin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Ragon.Server.IO; +using Ragon.Server.Lobby; +using Ragon.Server.Room; + +namespace Ragon.Server.Plugin; + +public class BaseServerPlugin: IServerPlugin +{ + private IRagonServer _ragonServer; + + public RagonLobbyPlayer? GetPlayerById(string id) + { + var context = _ragonServer.ResolveContext(id); + return context?.LobbyPlayer; + } + + public RagonLobbyPlayer? GetPlayerByConnection(INetworkConnection connection) + { + var context = _ragonServer.ResolveContext(connection); + return context?.LobbyPlayer; + } + + public void OnAttached(IRagonServer server) + { + _ragonServer = server; + } + + public void OnDetached() + { + + } + + public virtual bool OnRoomCreate(RagonLobbyPlayer player, RagonRoom room) + { + return true; + } + + public virtual bool OnRoomRemove(RagonLobbyPlayer player, RagonRoom room) + { + return true; + } + + public virtual bool OnRoomLeave(RagonRoomPlayer player, RagonRoom room) + { + return true; + } + + public virtual bool OnRoomJoin(RagonRoomPlayer player, RagonRoom room) + { + return true; + } + + public virtual bool OnCommand(string command, string payload) + { + return true; + } + + public IRoomPlugin CreateRoomPlugin(RoomInformation information) + { + return new BaseRoomPlugin(); + } +} \ No newline at end of file diff --git a/Ragon.Server/Sources/Plugin/IRoomPlugin.cs b/Ragon.Server/Sources/Plugin/IRoomPlugin.cs new file mode 100644 index 0000000..679707e --- /dev/null +++ b/Ragon.Server/Sources/Plugin/IRoomPlugin.cs @@ -0,0 +1,29 @@ +/* + * Copyright 2023 Eduard Kargin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Ragon.Server.Entity; +using Ragon.Server.Room; + +namespace Ragon.Server.Plugin; + +public interface IRoomPlugin +{ + void Tick(float dt); + void OnAttached(IRagonRoom room); + void OnDetached(); + bool OnEntityCreate(RagonRoomPlayer creator, RagonEntity entity); + bool OnEntityRemove(RagonRoomPlayer remover, RagonEntity entity); +} \ No newline at end of file diff --git a/Ragon.Server/Sources/Plugin/IServerPlugin.cs b/Ragon.Server/Sources/Plugin/IServerPlugin.cs new file mode 100644 index 0000000..cad278e --- /dev/null +++ b/Ragon.Server/Sources/Plugin/IServerPlugin.cs @@ -0,0 +1,33 @@ +/* + * Copyright 2023 Eduard Kargin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Ragon.Server.Http; +using Ragon.Server.Lobby; +using Ragon.Server.Room; + +namespace Ragon.Server.Plugin; + +public interface IServerPlugin +{ + void OnAttached(IRagonServer server); + void OnDetached(); + bool OnRoomCreate(RagonLobbyPlayer player, RagonRoom room); + bool OnRoomRemove(RagonLobbyPlayer player, RagonRoom room); + bool OnRoomLeave(RagonRoomPlayer player, RagonRoom room); + bool OnRoomJoin(RagonRoomPlayer player, RagonRoom room); + bool OnCommand(string command, string payload); + IRoomPlugin CreateRoomPlugin(RoomInformation information); +} \ No newline at end of file diff --git a/Ragon.Server/Sources/Plugin/Web/Dto/PlayerDto.cs b/Ragon.Server/Sources/Plugin/Web/Dto/PlayerDto.cs new file mode 100644 index 0000000..faf07cd --- /dev/null +++ b/Ragon.Server/Sources/Plugin/Web/Dto/PlayerDto.cs @@ -0,0 +1,32 @@ +/* + * Copyright 2023 Eduard Kargin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Ragon.Server.Room; + +namespace Ragon.Server.Plugin.Web; + +[Serializable] +public class PlayerDto +{ + public string Id { get; set;} + public string Name { get; set; } + + public PlayerDto(RagonRoomPlayer ragonRoomPlayer) + { + Id = ragonRoomPlayer.Id; + Name = ragonRoomPlayer.Name; + } +} \ No newline at end of file diff --git a/Ragon.Server/Sources/Plugin/Web/Dto/RoomDto.cs b/Ragon.Server/Sources/Plugin/Web/Dto/RoomDto.cs new file mode 100644 index 0000000..a500fef --- /dev/null +++ b/Ragon.Server/Sources/Plugin/Web/Dto/RoomDto.cs @@ -0,0 +1,39 @@ +/* + * Copyright 2023 Eduard Kargin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Ragon.Server.Room; + +namespace Ragon.Server.Plugin.Web; + +[Serializable] +public class RoomDto +{ + public string Id { get; set;} + public int PlayerMin { get; set; } + public int PlayerMax { get; set; } + public int PlayerCount { get; set; } + public PlayerDto[] Players { get; set; } + + public RoomDto(RagonRoom room) + { + Id = room.Id; + PlayerMin = room.PlayerMin; + PlayerMax = room.PlayerMax; + PlayerCount = room.PlayerCount; + + Players = room.PlayerList.Select(p => new PlayerDto(p)).ToArray(); + } +} \ No newline at end of file diff --git a/Ragon.Server/Sources/Plugin/Web/RagonWebHookPlugin.cs b/Ragon.Server/Sources/Plugin/Web/RagonWebHookPlugin.cs new file mode 100644 index 0000000..71669ae --- /dev/null +++ b/Ragon.Server/Sources/Plugin/Web/RagonWebHookPlugin.cs @@ -0,0 +1,135 @@ +/* + * Copyright 2023 Eduard Kargin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Net; +using System.Net.Http.Json; +using Newtonsoft.Json; +using Ragon.Protocol; +using Ragon.Server.Handler; +using Ragon.Server.Lobby; +using Ragon.Server.Room; + +namespace Ragon.Server.Plugin.Web; + +public class RagonWebHookPlugin +{ + private Dictionary _webHooks; + + private RagonServer _server; + private HttpClient _httpClient; + + public RagonWebHookPlugin(RagonServer server, Configuration configuration) + { + _webHooks = new Dictionary(configuration.WebHooks); + _httpClient = new HttpClient(); + _server = server; + } + + public bool RequestAuthorization(RagonContext context, string name, string password) + { + if (_webHooks.TryGetValue("authorization-request", out var value)) + { + var httpContent = new StringContent(""); + var executor = context.Executor; + executor.Run(async () => + { + var authorizationOperation = (AuthorizationOperation) _server.ResolveOperation(RagonOperation.AUTHORIZE); + var response = await _httpClient.PostAsync(new Uri(value), httpContent); + if (response.StatusCode != HttpStatusCode.OK) + { + authorizationOperation.Reject(context); + return; + } + + var content = await response.Content.ReadAsStringAsync(); + var authorizationResponse = JsonConvert.DeserializeObject(content); + if (authorizationResponse != null) + { + var lobbyPlayer = new RagonLobbyPlayer(context.Connection, authorizationResponse.Id, authorizationResponse.Name, authorizationResponse.Payload); + + context.SetPlayer(lobbyPlayer); + authorizationOperation.Approve(context); + } + else + { + authorizationOperation.Reject(context); + } + }); + return true; + } + + return false; + } + + public void RoomCreated(RagonContext context, RagonRoom room, RagonRoomPlayer player) + { + if (_webHooks.TryGetValue("room-created", out var value) && !string.IsNullOrEmpty(value)) + { + var request = new RoomCreatedRequest() + { + Room = new RoomDto(room), + Player = new PlayerDto(player) + }; + var content = JsonContent.Create(request); + var executor = context.Executor; + executor.Run(() => _httpClient.PostAsync(new Uri(value), content, CancellationToken.None)); + } + } + + public void RoomRemoved(RagonContext context, RagonRoom ragonRoom) + { + if (_webHooks.TryGetValue("room-removed", out var value) && !string.IsNullOrEmpty(value)) + { + var request = new RoomRemovedRequest() + { + Room = new RoomDto(ragonRoom) + }; + var content = JsonContent.Create(request); + var executor = context.Executor; + executor.Run(() => _httpClient.PostAsync(new Uri(value), content, CancellationToken.None)); + } + } + + public void RoomJoined(RagonContext context, RagonRoom existsRoom, RagonRoomPlayer player) + { + if (_webHooks.TryGetValue("room-joined", out var value) && !string.IsNullOrEmpty(value)) + { + var request = new RoomJoinedRequest() + { + Room = new RoomDto(existsRoom), + Player = new PlayerDto(player) + }; + var content = JsonContent.Create(request); + var executor = context.Executor; + executor.Run(() => _httpClient.PostAsync(new Uri(value), content, CancellationToken.None)); + } + } + + public void RoomLeaved(RagonContext context, RagonRoom room, RagonRoomPlayer roomPlayer) + { + if (_webHooks.TryGetValue("room-leaved", out var value) && !string.IsNullOrEmpty(value)) + { + var request = new RoomLeavedRequest() + { + Room = new RoomDto(room), + Player = new PlayerDto(roomPlayer) + }; + var content = JsonContent.Create(request); + var executor = context.Executor; + executor.Run(() => _httpClient.PostAsync(new Uri(value), content, CancellationToken.None)); + } + } +} \ No newline at end of file diff --git a/Ragon.Server/Sources/Plugin/Web/Request/AuthorizationRequest.cs b/Ragon.Server/Sources/Plugin/Web/Request/AuthorizationRequest.cs new file mode 100644 index 0000000..66c89f0 --- /dev/null +++ b/Ragon.Server/Sources/Plugin/Web/Request/AuthorizationRequest.cs @@ -0,0 +1,24 @@ +/* + * Copyright 2023 Eduard Kargin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Ragon.Server.Plugin.Web; + +[Serializable] +public class AuthorizationRequest +{ + public string Name; + public string Token; +} \ No newline at end of file diff --git a/Ragon.Server/Sources/Plugin/Web/Request/RoomCreatedRequest.cs b/Ragon.Server/Sources/Plugin/Web/Request/RoomCreatedRequest.cs new file mode 100644 index 0000000..e398551 --- /dev/null +++ b/Ragon.Server/Sources/Plugin/Web/Request/RoomCreatedRequest.cs @@ -0,0 +1,24 @@ +/* + * Copyright 2023 Eduard Kargin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Ragon.Server.Plugin.Web; + +[Serializable] +public class RoomCreatedRequest +{ + public RoomDto Room { get; set; } + public PlayerDto Player { get; set; } +} \ No newline at end of file diff --git a/Ragon.Server/Sources/Plugin/Web/Request/RoomJoinedRequest.cs b/Ragon.Server/Sources/Plugin/Web/Request/RoomJoinedRequest.cs new file mode 100644 index 0000000..420b387 --- /dev/null +++ b/Ragon.Server/Sources/Plugin/Web/Request/RoomJoinedRequest.cs @@ -0,0 +1,23 @@ +/* + * Copyright 2023 Eduard Kargin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Ragon.Server.Plugin.Web; + +public class RoomJoinedRequest +{ + public RoomDto Room { get; set; } + public PlayerDto Player { get; set; } +} diff --git a/Ragon.Server/Sources/Plugin/Web/Request/RoomLeavedRequest.cs b/Ragon.Server/Sources/Plugin/Web/Request/RoomLeavedRequest.cs new file mode 100644 index 0000000..7914ed7 --- /dev/null +++ b/Ragon.Server/Sources/Plugin/Web/Request/RoomLeavedRequest.cs @@ -0,0 +1,24 @@ +/* + * Copyright 2023 Eduard Kargin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Ragon.Server.Plugin.Web; + +[Serializable] +public class RoomLeavedRequest +{ + public RoomDto Room { get; set; } + public PlayerDto Player { get; set; } +} \ No newline at end of file diff --git a/Ragon.Server/Sources/Plugin/Web/Request/RoomRemovedRequest.cs b/Ragon.Server/Sources/Plugin/Web/Request/RoomRemovedRequest.cs new file mode 100644 index 0000000..5d4af96 --- /dev/null +++ b/Ragon.Server/Sources/Plugin/Web/Request/RoomRemovedRequest.cs @@ -0,0 +1,24 @@ +/* + * Copyright 2023 Eduard Kargin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +namespace Ragon.Server.Plugin.Web; + +[Serializable] +public class RoomRemovedRequest +{ + public RoomDto Room { get; set; } +} \ No newline at end of file diff --git a/Ragon.Server/Sources/Plugin/Web/Response/AuthorizationResponse.cs b/Ragon.Server/Sources/Plugin/Web/Response/AuthorizationResponse.cs new file mode 100644 index 0000000..459ec53 --- /dev/null +++ b/Ragon.Server/Sources/Plugin/Web/Response/AuthorizationResponse.cs @@ -0,0 +1,25 @@ +/* + * Copyright 2023 Eduard Kargin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Ragon.Server.Plugin.Web; + +[Serializable] +public class AuthorizationResponse +{ + public string Id; + public string Name; + public string Payload; +} \ No newline at end of file diff --git a/Ragon.Server/Sources/RagonContext.cs b/Ragon.Server/Sources/RagonContext.cs index 1cdb384..4aafa22 100644 --- a/Ragon.Server/Sources/RagonContext.cs +++ b/Ragon.Server/Sources/RagonContext.cs @@ -14,33 +14,45 @@ * limitations under the License. */ -using Ragon.Core.Time; -using Ragon.Server; +using Ragon.Server.IO; +using Ragon.Server.Lobby; +using Ragon.Server.Time; +using Ragon.Server.Room; namespace Ragon.Server; public class RagonContext { + public ConnectionStatus ConnectionStatus { get; set; } public INetworkConnection Connection { get; } public IExecutor Executor { get; private set; } public IRagonLobby Lobby { get; private set; } - public RagonLobbyPlayer LobbyPlayer { get; private set; } + public RagonLobbyPlayer? LobbyPlayer { get; private set; } public RagonRoom? Room { get; private set; } public RagonRoomPlayer? RoomPlayer { get; private set; } public RagonScheduler Scheduler { get; private set; } - public RagonContext(INetworkConnection connection, IExecutor executor, IRagonLobby lobby, RagonScheduler scheduler, RagonLobbyPlayer lobbyPlayer) + public RagonContext( + INetworkConnection connection, + IExecutor executor, + IRagonLobby lobby, + RagonScheduler scheduler) { + ConnectionStatus = ConnectionStatus.Unauthorized; Connection = connection; Executor = executor; Lobby = lobby; Scheduler = scheduler; - LobbyPlayer = lobbyPlayer; } + internal void SetPlayer(RagonLobbyPlayer player) + { + LobbyPlayer = player; + } + internal void SetRoom(RagonRoom room, RagonRoomPlayer player) { Room?.DetachPlayer(RoomPlayer); diff --git a/Ragon.Server/Sources/RagonEntityCache.cs b/Ragon.Server/Sources/RagonEntityCache.cs index 9a55352..53f091b 100644 --- a/Ragon.Server/Sources/RagonEntityCache.cs +++ b/Ragon.Server/Sources/RagonEntityCache.cs @@ -14,6 +14,8 @@ * limitations under the License. */ +using Ragon.Server.Entity; + namespace Ragon.Server; public class RagonEntityCache diff --git a/Ragon.Server/Sources/RagonServer.cs b/Ragon.Server/Sources/RagonServer.cs index 8d310da..d49c336 100644 --- a/Ragon.Server/Sources/RagonServer.cs +++ b/Ragon.Server/Sources/RagonServer.cs @@ -16,54 +16,73 @@ using System.Diagnostics; using NLog; -using Ragon.Core.Time; using Ragon.Protocol; -using Ragon.Server; +using Ragon.Server.Handler; +using Ragon.Server.Http; +using Ragon.Server.IO; +using Ragon.Server.Lobby; +using Ragon.Server.Plugin; +using Ragon.Server.Plugin.Web; +using Ragon.Server.Time; namespace Ragon.Server; -public class RagonServer : INetworkListener +public class RagonServer : IRagonServer, INetworkListener { private readonly Logger _logger = LogManager.GetCurrentClassLogger(); private readonly INetworkServer _server; + private readonly IRagonOperation[] _handlers; + private readonly IRagonLobby _lobby; + private readonly IServerPlugin _serverPlugin; private readonly Thread _dedicatedThread; private readonly Executor _executor; private readonly Configuration _configuration; - private readonly IRagonOperation[] _handlers; + private readonly RagonWebHookPlugin _webhooks; + private readonly RagonHttpServer _httpServer; private readonly RagonBuffer _reader; private readonly RagonBuffer _writer; - private readonly IRagonLobby _lobby; private readonly RagonScheduler _scheduler; - private readonly Dictionary _contexts; - private long _tickrate = 0; - private Stopwatch _timer; + private readonly Dictionary _contextsByConnection; + private readonly Dictionary _contextsByPlayerId; + private readonly Stopwatch _timer; + private readonly long _tickRate = 0; - public RagonServer(INetworkServer server, Configuration configuration) + public RagonServer( + INetworkServer server, + IServerPlugin plugin, + Configuration configuration) { _server = server; _executor = _server.Executor; _configuration = configuration; - _dedicatedThread = new Thread(Execute); - _dedicatedThread.IsBackground = true; - _contexts = new Dictionary(); + _serverPlugin = plugin; + _contextsByConnection = new Dictionary(); + _contextsByPlayerId = new Dictionary(); _lobby = new LobbyInMemory(); _scheduler = new RagonScheduler(); - + _webhooks = new RagonWebHookPlugin(this, configuration); + _dedicatedThread = new Thread(Execute); + _dedicatedThread.IsBackground = true; + _httpServer = new RagonHttpServer(_executor, plugin); _reader = new RagonBuffer(); _writer = new RagonBuffer(); - _tickrate = 1000 / _configuration.ServerTickRate; + _tickRate = 1000 / _configuration.ServerTickRate; _timer = new Stopwatch(); + var contextObserver = new RagonContextObserver(_contextsByPlayerId); + + _serverPlugin.OnAttached(this); + _handlers = new IRagonOperation[byte.MaxValue]; - _handlers[(byte) RagonOperation.AUTHORIZE] = new AuthorizationOperation(); - _handlers[(byte) RagonOperation.JOIN_OR_CREATE_ROOM] = new RoomJoinOrCreateOperation(); - _handlers[(byte) RagonOperation.CREATE_ROOM] = new RoomCreateOperation(); - _handlers[(byte) RagonOperation.JOIN_ROOM] = new RoomJoinOperation(); - _handlers[(byte) RagonOperation.LEAVE_ROOM] = new RoomLeaveOperation(); + _handlers[(byte) RagonOperation.AUTHORIZE] = new AuthorizationOperation(_webhooks, contextObserver, _writer, configuration); + _handlers[(byte) RagonOperation.JOIN_OR_CREATE_ROOM] = new RoomJoinOrCreateOperation(plugin, _webhooks); + _handlers[(byte) RagonOperation.CREATE_ROOM] = new RoomCreateOperation(plugin, _webhooks); + _handlers[(byte) RagonOperation.JOIN_ROOM] = new RoomJoinOperation(plugin, _webhooks); + _handlers[(byte) RagonOperation.LEAVE_ROOM] = new RoomLeaveOperation(plugin, _webhooks); _handlers[(byte) RagonOperation.LOAD_SCENE] = new SceneLoadOperation(); _handlers[(byte) RagonOperation.SCENE_LOADED] = new SceneLoadedOperation(); _handlers[(byte) RagonOperation.CREATE_ENTITY] = new EntityCreateOperation(); - _handlers[(byte) RagonOperation.DESTROY_ENTITY] = new EntityDestroyOperation(); + _handlers[(byte) RagonOperation.REMOVE_ENTITY] = new EntityDestroyOperation(); _handlers[(byte) RagonOperation.REPLICATE_ENTITY_EVENT] = new EntityEventOperation(); _handlers[(byte) RagonOperation.REPLICATE_ENTITY_STATE] = new EntityStateOperation(); @@ -75,13 +94,13 @@ public class RagonServer : INetworkListener _timer.Start(); while (true) { - if (_timer.ElapsedMilliseconds > _tickrate) + if (_timer.ElapsedMilliseconds > _tickRate) { - _executor.Update(); - _scheduler.Update(); + _scheduler.Update(_timer.ElapsedMilliseconds / 1000.0f); _timer.Restart(); } + _executor.Update(); _server.Update(); Thread.Sleep(1); } @@ -97,6 +116,7 @@ public class RagonServer : INetworkListener Port = _configuration.Port, }; + _httpServer.Start(_configuration); _server.Start(this, networkConfiguration); if (executeInDedicatedThread) @@ -107,31 +127,32 @@ public class RagonServer : INetworkListener public void Dispose() { + _serverPlugin.OnDetached(); _server.Stop(); _dedicatedThread.Interrupt(); } public void OnConnected(INetworkConnection connection) { - var lobbyPlayer = new RagonLobbyPlayer(connection); - var context = new RagonContext(connection, _executor, _lobby, _scheduler, lobbyPlayer); - + var context = new RagonContext(connection, _executor, _lobby, _scheduler); + _logger.Trace($"Connected: {connection.Id}"); - _contexts.Add(connection.Id, context); + _contextsByConnection.Add(connection.Id, context); } public void OnDisconnected(INetworkConnection connection) { - if (_contexts.Remove(connection.Id, out var context)) + if (_contextsByConnection.Remove(connection.Id, out var context)) { var room = context.Room; if (room != null) { room.DetachPlayer(context.RoomPlayer); - _lobby.RemoveIfEmpty(room); + if (_lobby.RemoveIfEmpty(room)) + _webhooks.RoomRemoved(context, room); } - _logger.Trace($"Disconnected: {connection.Id}|{context.LobbyPlayer.Name}|{context.LobbyPlayer.Id}"); + _logger.Trace($"Disconnected: {connection.Id}"); } else { @@ -141,7 +162,7 @@ public class RagonServer : INetworkListener public void OnTimeout(INetworkConnection connection) { - if (_contexts.Remove(connection.Id, out var context)) + if (_contextsByConnection.Remove(connection.Id, out var context)) { var room = context.Room; if (room != null) @@ -162,13 +183,12 @@ public class RagonServer : INetworkListener { try { - if (_contexts.TryGetValue(connection.Id, out var context)) + if (_contextsByConnection.TryGetValue(connection.Id, out var context)) { _writer.Clear(); _reader.Clear(); _reader.FromArray(data); - // Console.WriteLine($"{string.Join(",", data.Select(d => d.ToString()))}"); var operation = _reader.ReadByte(); _handlers[operation].Handle(context, _reader, _writer); } @@ -178,4 +198,7 @@ public class RagonServer : INetworkListener _logger.Error(ex); } } + public IRagonOperation ResolveOperation(RagonOperation operation) => _handlers[(byte)operation]; + public RagonContext? ResolveContext(INetworkConnection connection) => _contextsByConnection.TryGetValue(connection.Id, out var context) ? context : null; + public RagonContext? ResolveContext(string playerId) => _contextsByPlayerId.TryGetValue(playerId, out var context) ? context : null; } \ No newline at end of file diff --git a/Ragon.Server/Sources/RagonServerConfiguration.cs b/Ragon.Server/Sources/RagonServerConfiguration.cs index e362e2c..c27a388 100644 --- a/Ragon.Server/Sources/RagonServerConfiguration.cs +++ b/Ragon.Server/Sources/RagonServerConfiguration.cs @@ -25,6 +25,11 @@ public enum ServerType WEBSOCKET, } +public class WebHook +{ + +} + [Serializable] public struct Configuration { @@ -33,32 +38,21 @@ public struct Configuration public ushort ServerTickRate; public string GameProtocol; public ushort Port; + public ushort HttpPort; + public string HttpKey; public int LimitConnections; public int LimitPlayersPerRoom; public int LimitRooms; + public Dictionary WebHooks; private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - private static readonly string ServerVersion = "1.1.3-rc"; + private static readonly string ServerVersion = "1.2.0-rc"; private static Dictionary _serverTypes = new Dictionary() { {"enet", Server.ServerType.ENET}, {"websocket", Server.ServerType.WEBSOCKET} }; - private static void CopyrightInfo() - { - Logger.Info($"Server Version: {ServerVersion}"); - Logger.Info($"Machine Name: {Environment.MachineName}"); - Logger.Info($"OS: {Environment.OSVersion}"); - Logger.Info($"Processors: {Environment.ProcessorCount}"); - Logger.Info($"Runtime Version: {Environment.Version}"); - Logger.Info("=================================="); - Logger.Info("| |"); - Logger.Info("| Ragon |"); - Logger.Info("| |"); - Logger.Info("=================================="); - } - public static Configuration Load(string filePath) { CopyrightInfo(); @@ -68,5 +62,20 @@ public struct Configuration return configuration; } + private static void CopyrightInfo() + { + Logger.Info($"Server Version: {ServerVersion}"); + Logger.Info($"Machine Name: {Environment.MachineName}"); + Logger.Info($"OS: {Environment.OSVersion}"); + Logger.Info($"Processors: {Environment.ProcessorCount}"); + Logger.Info($"Runtime Version: {Environment.Version}"); + Logger.Info("=================================="); + Logger.Info(@" ___ _ ___ ___ _ _ "); + Logger.Info(@" | _ \ /_\ / __|/ _ \| \| |"); + Logger.Info(@" | / / _ \ (_ | (_) | .` |"); + Logger.Info(@" |_|_\/_/ \_\___|\___/|_|\_|"); + Logger.Info("=================================="); + } + public static ServerType GetServerType(string type) => _serverTypes[type]; } \ No newline at end of file diff --git a/Ragon.Server/Sources/Room/IRagonRoom.cs b/Ragon.Server/Sources/Room/IRagonRoom.cs new file mode 100644 index 0000000..53c4a5d --- /dev/null +++ b/Ragon.Server/Sources/Room/IRagonRoom.cs @@ -0,0 +1,25 @@ +/* + * Copyright 2023 Eduard Kargin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Ragon.Server.IO; + +namespace Ragon.Server.Room; + +public interface IRagonRoom +{ + RagonRoomPlayer GetPlayerByConnection(INetworkConnection connection); + RagonRoomPlayer GetPlayerById(string id); +} \ No newline at end of file diff --git a/Ragon.Server/Sources/Room/RagonRoom.cs b/Ragon.Server/Sources/Room/RagonRoom.cs index 655820d..e5018dd 100644 --- a/Ragon.Server/Sources/Room/RagonRoom.cs +++ b/Ragon.Server/Sources/Room/RagonRoom.cs @@ -14,17 +14,26 @@ * limitations under the License. */ -using Ragon.Core.Time; using Ragon.Protocol; +using Ragon.Server.Entity; +using Ragon.Server.IO; +using Ragon.Server.Plugin; +using Ragon.Server.Time; -namespace Ragon.Server; +namespace Ragon.Server.Room; -public class RagonRoom: IRagonAction +public class RagonRoom : IRagonRoom, IRagonAction { public string Id { get; private set; } - public RoomInformation Info { get; private set; } + public string Map { get; private set; } + public int PlayerMax { get; private set; } + public int PlayerMin { get; private set; } + public int PlayerCount => WaitPlayersList.Count; + public RagonRoomPlayer Owner { get; private set; } - public RagonBuffer Writer { get; } + public RagonBuffer Writer { get; } + public IRoomPlugin Plugin { get; private set; } + public Dictionary Players { get; private set; } public List WaitPlayersList { get; private set; } public List ReadyPlayersList { get; private set; } @@ -36,11 +45,14 @@ public class RagonRoom: IRagonAction public List EntityList { get; private set; } private readonly HashSet _entitiesDirtySet; - - public RagonRoom(string roomId, RoomInformation info) + + public RagonRoom(string roomId, RoomInformation info, IRoomPlugin roomPlugin) { Id = roomId; - Info = info; + Map = info.Map; + PlayerMax = info.Max; + PlayerMin = info.Min; + Plugin = roomPlugin; Players = new Dictionary(info.Max); WaitPlayersList = new List(info.Max); @@ -53,7 +65,7 @@ public class RagonRoom: IRagonAction EntityList = new List(); _entitiesDirtySet = new HashSet(); - + Writer = new RagonBuffer(); } @@ -74,13 +86,13 @@ public class RagonRoom: IRagonAction EntityList.Remove(entity); StaticEntitiesList.Remove(entity); DynamicEntitiesList.Remove(entity); - + _entitiesDirtySet.Remove(entity); } - public void Tick() + public void Tick(float dt) { - var entities = (ushort) _entitiesDirtySet.Count; + var entities = (ushort)_entitiesDirtySet.Count; if (entities > 0) { Writer.Clear(); @@ -88,10 +100,10 @@ public class RagonRoom: IRagonAction Writer.WriteUShort(entities); foreach (var entity in _entitiesDirtySet) - entity.State.Write(Writer); + entity.Write(Writer); _entitiesDirtySet.Clear(); - + var sendData = Writer.ToArray(); foreach (var roomPlayer in ReadyPlayersList) roomPlayer.Connection.Unreliable.Send(sendData); @@ -121,7 +133,7 @@ public class RagonRoom: IRagonAction Writer.WriteString(player.Id); var entitiesToDelete = player.Entities.DynamicList; - Writer.WriteUShort((ushort) entitiesToDelete.Count); + Writer.WriteUShort((ushort)entitiesToDelete.Count); foreach (var entity in entitiesToDelete) { Writer.WriteUShort(entity.Id); @@ -131,34 +143,34 @@ public class RagonRoom: IRagonAction var sendData = Writer.ToArray(); Broadcast(sendData); } - + if (roomPlayer.Connection.Id == Owner.Connection.Id && PlayerList.Count > 0) { var nextOwner = PlayerList[0]; - + Owner = nextOwner; - - var entitiesToUpdate = roomPlayer.Entities.StaticList; - + + var entitiesToUpdate = roomPlayer.Entities.StaticList; + Writer.Clear(); Writer.WriteOperation(RagonOperation.OWNERSHIP_CHANGED); Writer.WriteString(Owner.Id); - Writer.WriteUShort((ushort) entitiesToUpdate.Count); - + Writer.WriteUShort((ushort)entitiesToUpdate.Count); + foreach (var entity in entitiesToUpdate) { Writer.WriteUShort(entity.Id); - - entity.SetOwner(nextOwner); + + entity.Attach(nextOwner); nextOwner.Entities.Add(entity); } var sendData = Writer.ToArray(); Broadcast(sendData); } - + player.OnDetached(); - + UpdateReadyPlayerList(); } } @@ -170,21 +182,16 @@ public class RagonRoom: IRagonAction public void UpdateMap(string sceneName) { - Info = new RoomInformation() - { - Max = Info.Max, - Min = Info.Min, - Map = sceneName, - }; - + Map = sceneName; + DynamicEntitiesList.Clear(); StaticEntitiesList.Clear(); Entities.Clear(); EntityList.Clear(); - + foreach (var player in PlayerList) player.UnsetReady(); - + UpdateReadyPlayerList(); } @@ -198,4 +205,7 @@ public class RagonRoom: IRagonAction foreach (var readyPlayer in ReadyPlayersList) readyPlayer.Connection.Reliable.Send(data); } + + public RagonRoomPlayer GetPlayerByConnection(INetworkConnection connection) => Players[connection.Id]; + public RagonRoomPlayer GetPlayerById(string id) => PlayerList.First(p => p.Id == id); } \ No newline at end of file diff --git a/Ragon.Server/Sources/Room/RagonRoomInformation.cs b/Ragon.Server/Sources/Room/RagonRoomInformation.cs index 6bfd205..1ef8376 100644 --- a/Ragon.Server/Sources/Room/RagonRoomInformation.cs +++ b/Ragon.Server/Sources/Room/RagonRoomInformation.cs @@ -16,14 +16,9 @@ namespace Ragon.Server; -public class RoomInformation +public ref struct RoomInformation { - public string Map { get; init; } = "none"; - public int Min { get; init; } - public int Max { get; init; } - - public override string ToString() - { - return $"Map: {Map} Count: {Min}/{Max}"; - } + public string Map; + public int Min; + public int Max; } \ No newline at end of file diff --git a/Ragon.Server/Sources/Room/RagonRoomPlayer.cs b/Ragon.Server/Sources/Room/RagonRoomPlayer.cs index 47ae625..5cc2c32 100644 --- a/Ragon.Server/Sources/Room/RagonRoomPlayer.cs +++ b/Ragon.Server/Sources/Room/RagonRoomPlayer.cs @@ -14,7 +14,10 @@ * limitations under the License. */ -namespace Ragon.Server; +using Ragon.Server.Entity; +using Ragon.Server.IO; + +namespace Ragon.Server.Room; public class RagonRoomPlayer { diff --git a/Ragon.Server/Sources/Time/IRagonAction.cs b/Ragon.Server/Sources/Time/IRagonAction.cs index 63c9adf..36ec070 100644 --- a/Ragon.Server/Sources/Time/IRagonAction.cs +++ b/Ragon.Server/Sources/Time/IRagonAction.cs @@ -14,9 +14,9 @@ * limitations under the License. */ -namespace Ragon.Core.Time; +namespace Ragon.Server.Time; public interface IRagonAction { - public void Tick(); + public void Tick(float dt); } \ No newline at end of file diff --git a/Ragon.Server/Sources/Time/RagonActionExecutor.cs b/Ragon.Server/Sources/Time/RagonActionExecutor.cs index e6198a7..538b709 100644 --- a/Ragon.Server/Sources/Time/RagonActionExecutor.cs +++ b/Ragon.Server/Sources/Time/RagonActionExecutor.cs @@ -14,7 +14,7 @@ * limitations under the License. */ -namespace Ragon.Core.Time; +namespace Ragon.Server.Time; public class RagonScheduler { @@ -35,9 +35,9 @@ public class RagonScheduler _tasks.Remove(task); } - public void Update() + public void Update(float dt) { foreach (var task in _tasks) - task.Tick(); + task.Tick(dt); } } \ No newline at end of file