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/Sources/RelayRoomPlugin.cs b/Ragon.Relay/Sources/RelayRoomPlugin.cs index 5a99cd3..c0d95a2 100644 --- a/Ragon.Relay/Sources/RelayRoomPlugin.cs +++ b/Ragon.Relay/Sources/RelayRoomPlugin.cs @@ -6,7 +6,7 @@ using Ragon.Server.Room; namespace Ragon.Relay; -public class RelayRoomPlugin: IRoomPlugin +public class RelayRoomPlugin: BaseRoomPlugin { public void Tick(float dt) { diff --git a/Ragon.Relay/Sources/RelayServerPlugin.cs b/Ragon.Relay/Sources/RelayServerPlugin.cs index f8eee07..c98ee4c 100644 --- a/Ragon.Relay/Sources/RelayServerPlugin.cs +++ b/Ragon.Relay/Sources/RelayServerPlugin.cs @@ -1,41 +1,24 @@ -using System.Net.Http; -using Ragon.Server; -using Ragon.Server.Lobby; +using System; +using Newtonsoft.Json; using Ragon.Server.Plugin; -using Ragon.Server.Room; namespace Ragon.Relay; -public class RelayServerPlugin: IServerPlugin +public class RelayServerPlugin: BaseServerPlugin { - private HttpClient httpClient; - public IRoomPlugin CreateRoomPlugin(RoomInformation information) - { - return new RelayRoomPlugin(); - } - - public RelayServerPlugin() - { - httpClient = new HttpClient(); - } - - public bool OnRoomCreate(RagonLobbyPlayer player, RagonRoom room) - { - return true; - } - - public bool OnRoomRemove(RagonLobbyPlayer player, RagonRoom room) - { - return true; - } - - public bool OnRoomLeave(RagonRoomPlayer player, RagonRoom room) - { - return true; - } - - public bool OnRoomJoin(RagonRoomPlayer player, RagonRoom room) + 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 7be2b89..3e61bde 100644 --- a/Ragon.Relay/relay.config.json +++ b/Ragon.Relay/relay.config.json @@ -4,6 +4,8 @@ "serverTickRate": 30, "gameProtocol": "1.0.0", "port": 5000, + "httpPort": 5001, + "httpKey": "defaultkey", "limitConnections": 4095, "limitPlayersPerRoom": 20, "limitRooms": 200, diff --git a/Ragon.Server/Sources/Handler/AuthorizationOperation.cs b/Ragon.Server/Sources/Handler/AuthorizationOperation.cs index 009681c..b76e6ee 100644 --- a/Ragon.Server/Sources/Handler/AuthorizationOperation.cs +++ b/Ragon.Server/Sources/Handler/AuthorizationOperation.cs @@ -26,17 +26,19 @@ namespace Ragon.Server.Handler; public sealed class AuthorizationOperation: IRagonOperation { private Logger _logger = LogManager.GetCurrentClassLogger(); - private readonly WebHookPlugin _webHook; + private readonly RagonWebHookPlugin _ragonWebHook; + private readonly RagonContextObserver _contextObserver; private readonly Configuration _configuration; private readonly RagonBuffer _writer; - public AuthorizationOperation( - WebHookPlugin webHook, + public AuthorizationOperation(RagonWebHookPlugin ragonWebHook, + RagonContextObserver contextObserver, RagonBuffer writer, Configuration configuration) { - _webHook = webHook; + _ragonWebHook = ragonWebHook; _configuration = configuration; + _contextObserver = contextObserver; _writer = writer; } @@ -60,10 +62,10 @@ public sealed class AuthorizationOperation: IRagonOperation if (key == _configuration.ServerKey) { - if (_webHook.RequestAuthorization(context, name, payload)) + if (_ragonWebHook.RequestAuthorization(context, name, payload)) return; - var lobbyPlayer = new RagonLobbyPlayer(Guid.NewGuid().ToString(), name, payload); + var lobbyPlayer = new RagonLobbyPlayer(context.Connection, Guid.NewGuid().ToString(), name, payload); context.SetPlayer(lobbyPlayer); Approve(context); @@ -77,7 +79,9 @@ public sealed class AuthorizationOperation: IRagonOperation 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; diff --git a/Ragon.Server/Sources/Handler/RoomCreateOperation.cs b/Ragon.Server/Sources/Handler/RoomCreateOperation.cs index b4673b6..011edd5 100644 --- a/Ragon.Server/Sources/Handler/RoomCreateOperation.cs +++ b/Ragon.Server/Sources/Handler/RoomCreateOperation.cs @@ -28,12 +28,12 @@ public sealed class RoomCreateOperation: IRagonOperation private readonly RagonRoomParameters _roomParameters = new(); private readonly Logger _logger = LogManager.GetCurrentClassLogger(); private readonly IServerPlugin _serverPlugin; - private readonly WebHookPlugin _webHookPlugin; + private readonly RagonWebHookPlugin _ragonWebHookPlugin; - public RoomCreateOperation(IServerPlugin serverPlugin, WebHookPlugin webHook) + public RoomCreateOperation(IServerPlugin serverPlugin, RagonWebHookPlugin ragonWebHook) { _serverPlugin = serverPlugin; - _webHookPlugin = webHook; + _ragonWebHookPlugin = ragonWebHook; } public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) @@ -79,11 +79,13 @@ public sealed class RoomCreateOperation: IRagonOperation var roomPlugin = _serverPlugin.CreateRoomPlugin(information); var room = new RagonRoom(roomId, information, roomPlugin); + roomPlayer.OnAttached(room); + context.Scheduler.Run(room); context.Lobby.Persist(room); context.SetRoom(room, roomPlayer); - _webHookPlugin.RoomCreated(context, room); + _ragonWebHookPlugin.RoomCreated(context, room, roomPlayer); _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} create room {room.Id} with map {information.Map}"); diff --git a/Ragon.Server/Sources/Handler/RoomJoinOperation.cs b/Ragon.Server/Sources/Handler/RoomJoinOperation.cs index f023dc2..c9f2cb6 100644 --- a/Ragon.Server/Sources/Handler/RoomJoinOperation.cs +++ b/Ragon.Server/Sources/Handler/RoomJoinOperation.cs @@ -26,12 +26,12 @@ public sealed class RoomJoinOperation : IRagonOperation { private readonly Logger _logger = LogManager.GetCurrentClassLogger(); private readonly IServerPlugin _serverPlugin; - private readonly WebHookPlugin _webHookPlugin; + private readonly RagonWebHookPlugin _ragonWebHookPlugin; - public RoomJoinOperation(IServerPlugin serverPlugin, WebHookPlugin plugin) + public RoomJoinOperation(IServerPlugin serverPlugin, RagonWebHookPlugin plugin) { _serverPlugin = serverPlugin; - _webHookPlugin = plugin; + _ragonWebHookPlugin = plugin; } public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) @@ -53,7 +53,7 @@ public sealed class RoomJoinOperation : IRagonOperation if (!_serverPlugin.OnRoomJoin(player, existsRoom)) return; - _webHookPlugin.RoomJoined(context, existsRoom, player); + _ragonWebHookPlugin.RoomJoined(context, existsRoom, player); JoinSuccess(context, existsRoom, writer); diff --git a/Ragon.Server/Sources/Handler/RoomJoinOrCreateOperation.cs b/Ragon.Server/Sources/Handler/RoomJoinOrCreateOperation.cs index 4d409c6..74f1a4d 100644 --- a/Ragon.Server/Sources/Handler/RoomJoinOrCreateOperation.cs +++ b/Ragon.Server/Sources/Handler/RoomJoinOrCreateOperation.cs @@ -28,12 +28,12 @@ public sealed class RoomJoinOrCreateOperation : IRagonOperation private readonly RagonRoomParameters _roomParameters = new(); private readonly Logger _logger = LogManager.GetCurrentClassLogger(); private readonly IServerPlugin _serverPlugin; - private readonly WebHookPlugin _webHookPlugin; + private readonly RagonWebHookPlugin _ragonWebHookPlugin; - public RoomJoinOrCreateOperation(IServerPlugin serverPlugin, WebHookPlugin plugin) + public RoomJoinOrCreateOperation(IServerPlugin serverPlugin, RagonWebHookPlugin plugin) { _serverPlugin = serverPlugin; - _webHookPlugin = plugin; + _ragonWebHookPlugin = plugin; } public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) @@ -54,7 +54,7 @@ public sealed class RoomJoinOrCreateOperation : IRagonOperation var player = new RagonRoomPlayer(context.Connection, lobbyPlayer.Id, lobbyPlayer.Name); context.SetRoom(existsRoom, player); - _webHookPlugin.RoomJoined(context, existsRoom, player); + _ragonWebHookPlugin.RoomJoined(context, existsRoom, player); JoinSuccess(player, existsRoom, writer); } @@ -71,7 +71,7 @@ public sealed class RoomJoinOrCreateOperation : IRagonOperation var roomPlugin = _serverPlugin.CreateRoomPlugin(information); var room = new RagonRoom(roomId, information, roomPlugin); - _webHookPlugin.RoomCreated(context, room); + _ragonWebHookPlugin.RoomCreated(context, room, roomPlayer); context.Lobby.Persist(room); context.Scheduler.Run(room); diff --git a/Ragon.Server/Sources/Handler/RoomLeaveOperation.cs b/Ragon.Server/Sources/Handler/RoomLeaveOperation.cs index a2b1960..4de5f6c 100644 --- a/Ragon.Server/Sources/Handler/RoomLeaveOperation.cs +++ b/Ragon.Server/Sources/Handler/RoomLeaveOperation.cs @@ -25,11 +25,11 @@ public sealed class RoomLeaveOperation: IRagonOperation { private readonly Logger _logger = LogManager.GetCurrentClassLogger(); private readonly IServerPlugin _serverPlugin; - private readonly WebHookPlugin _webHookPlugin; - public RoomLeaveOperation(IServerPlugin serverPlugin, WebHookPlugin plugin) + private readonly RagonWebHookPlugin _ragonWebHookPlugin; + public RoomLeaveOperation(IServerPlugin serverPlugin, RagonWebHookPlugin plugin) { _serverPlugin = serverPlugin; - _webHookPlugin = plugin; + _ragonWebHookPlugin = plugin; } public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) @@ -40,7 +40,7 @@ public sealed class RoomLeaveOperation: IRagonOperation if (room != null) { _serverPlugin.OnRoomLeave(roomPlayer, room); - _webHookPlugin.RoomLeaved(context, room, roomPlayer); + _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/Http/RagonHttpServer.cs b/Ragon.Server/Sources/Http/RagonHttpServer.cs new file mode 100644 index 0000000..20c8d45 --- /dev/null +++ b/Ragon.Server/Sources/Http/RagonHttpServer.cs @@ -0,0 +1,97 @@ +using NLog; +using System.Net; +using System.Text.Json; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +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 a93d38b..050b872 100644 --- a/Ragon.Server/Sources/IO/Executor.cs +++ b/Ragon.Server/Sources/IO/Executor.cs @@ -18,24 +18,29 @@ using System.Threading.Channels; 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 Task Run(Action action) + public Task Run(Action action, TaskCreationOptions task = TaskCreationOptions.None) { - return _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 b163838..da9f11d 100644 --- a/Ragon.Server/Sources/IO/IExecutor.cs +++ b/Ragon.Server/Sources/IO/IExecutor.cs @@ -18,5 +18,5 @@ namespace Ragon.Server.IO; public interface IExecutor { - public Task Run(Action action); + public Task Run(Action action, TaskCreationOptions task = TaskCreationOptions.None); } \ No newline at end of file diff --git a/Ragon.Server/Sources/IRagonContextObserver.cs b/Ragon.Server/Sources/IRagonContextObserver.cs new file mode 100644 index 0000000..667a301 --- /dev/null +++ b/Ragon.Server/Sources/IRagonContextObserver.cs @@ -0,0 +1,15 @@ +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..318774c --- /dev/null +++ b/Ragon.Server/Sources/IRagonServer.cs @@ -0,0 +1,9 @@ +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/RagonLobbyPlayer.cs b/Ragon.Server/Sources/Lobby/RagonLobbyPlayer.cs index 4c241e3..69377eb 100644 --- a/Ragon.Server/Sources/Lobby/RagonLobbyPlayer.cs +++ b/Ragon.Server/Sources/Lobby/RagonLobbyPlayer.cs @@ -14,6 +14,8 @@ * limitations under the License. */ +using Ragon.Server.IO; + namespace Ragon.Server.Lobby; public enum ConnectionStatus @@ -25,14 +27,16 @@ public enum ConnectionStatus public class RagonLobbyPlayer { + public INetworkConnection Connection { get; } public string Id { get; private set; } public string Name { get; private set; } public string Payload { get; private set; } - public RagonLobbyPlayer(string id, string name, string payload) + public RagonLobbyPlayer(INetworkConnection connection, string id, string name, string payload) { Id = id; Name = name; Payload = payload; + Connection = connection; } } \ 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..8c1562d --- /dev/null +++ b/Ragon.Server/Sources/Plugin/BaseRoomPlugin.cs @@ -0,0 +1,53 @@ +using Ragon.Server.Entity; +using Ragon.Server.IO; +using Ragon.Server.Lobby; +using Ragon.Server.Room; +using Ragon.Server.Time; + +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..2a064e0 --- /dev/null +++ b/Ragon.Server/Sources/Plugin/BaseServerPlugin.cs @@ -0,0 +1,62 @@ +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 index 6b261a4..679707e 100644 --- a/Ragon.Server/Sources/Plugin/IRoomPlugin.cs +++ b/Ragon.Server/Sources/Plugin/IRoomPlugin.cs @@ -22,7 +22,7 @@ namespace Ragon.Server.Plugin; public interface IRoomPlugin { void Tick(float dt); - void OnAttached(); + void OnAttached(IRagonRoom room); void OnDetached(); bool OnEntityCreate(RagonRoomPlayer creator, RagonEntity entity); bool OnEntityRemove(RagonRoomPlayer remover, RagonEntity entity); diff --git a/Ragon.Server/Sources/Plugin/IServerPlugin.cs b/Ragon.Server/Sources/Plugin/IServerPlugin.cs index 73f2410..cad278e 100644 --- a/Ragon.Server/Sources/Plugin/IServerPlugin.cs +++ b/Ragon.Server/Sources/Plugin/IServerPlugin.cs @@ -14,6 +14,7 @@ * limitations under the License. */ +using Ragon.Server.Http; using Ragon.Server.Lobby; using Ragon.Server.Room; @@ -21,10 +22,12 @@ 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..5703f64 --- /dev/null +++ b/Ragon.Server/Sources/Plugin/Web/Dto/PlayerDto.cs @@ -0,0 +1,16 @@ +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..bbe0b13 --- /dev/null +++ b/Ragon.Server/Sources/Plugin/Web/Dto/RoomDto.cs @@ -0,0 +1,23 @@ +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/WebHookPlugin.cs b/Ragon.Server/Sources/Plugin/Web/RagonWebHookPlugin.cs similarity index 79% rename from Ragon.Server/Sources/Plugin/Web/WebHookPlugin.cs rename to Ragon.Server/Sources/Plugin/Web/RagonWebHookPlugin.cs index 980bb18..b52a36a 100644 --- a/Ragon.Server/Sources/Plugin/Web/WebHookPlugin.cs +++ b/Ragon.Server/Sources/Plugin/Web/RagonWebHookPlugin.cs @@ -8,14 +8,14 @@ using Ragon.Server.Room; namespace Ragon.Server.Plugin.Web; -public class WebHookPlugin +public class RagonWebHookPlugin { private Dictionary _webHooks; private RagonServer _server; private HttpClient _httpClient; - public WebHookPlugin(RagonServer server, Configuration configuration) + public RagonWebHookPlugin(RagonServer server, Configuration configuration) { _webHooks = new Dictionary(configuration.WebHooks); _httpClient = new HttpClient(); @@ -30,7 +30,7 @@ public class WebHookPlugin var executor = context.Executor; executor.Run(async () => { - var authorizationOperation = (AuthorizationOperation) _server.Resolve(RagonOperation.AUTHORIZE); + var authorizationOperation = (AuthorizationOperation) _server.ResolveOperation(RagonOperation.AUTHORIZE); var response = await _httpClient.PostAsync(new Uri(value), httpContent); if (response.StatusCode != HttpStatusCode.OK) { @@ -42,7 +42,7 @@ public class WebHookPlugin var authorizationResponse = JsonConvert.DeserializeObject(content); if (authorizationResponse != null) { - var lobbyPlayer = new RagonLobbyPlayer(authorizationResponse.Id, authorizationResponse.Name, authorizationResponse.Payload); + var lobbyPlayer = new RagonLobbyPlayer(context.Connection, authorizationResponse.Id, authorizationResponse.Name, authorizationResponse.Payload); context.SetPlayer(lobbyPlayer); authorizationOperation.Approve(context); @@ -58,12 +58,14 @@ public class WebHookPlugin return false; } - public void RoomCreated(RagonContext context, RagonRoom room) + 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; @@ -75,8 +77,9 @@ public class WebHookPlugin { if (_webHooks.TryGetValue("room-removed", out var value) && !string.IsNullOrEmpty(value)) { - var request = new RoomCreatedRequest() + var request = new RoomRemovedRequest() { + Room = new RoomDto(ragonRoom) }; var content = JsonContent.Create(request); var executor = context.Executor; @@ -88,8 +91,10 @@ public class WebHookPlugin { if (_webHooks.TryGetValue("room-joined", out var value) && !string.IsNullOrEmpty(value)) { - var request = new RoomCreatedRequest() + var request = new RoomJoinedRequest() { + Room = new RoomDto(existsRoom), + Player = new PlayerDto(player) }; var content = JsonContent.Create(request); var executor = context.Executor; @@ -101,8 +106,10 @@ public class WebHookPlugin { if (_webHooks.TryGetValue("room-leaved", out var value) && !string.IsNullOrEmpty(value)) { - var request = new RoomCreatedRequest() + var request = new RoomLeavedRequest() { + Room = new RoomDto(room), + Player = new PlayerDto(roomPlayer) }; var content = JsonContent.Create(request); var executor = context.Executor; diff --git a/Ragon.Server/Sources/Plugin/Web/Request/RoomCreatedRequest.cs b/Ragon.Server/Sources/Plugin/Web/Request/RoomCreatedRequest.cs index 539d8c1..1d54a20 100644 --- a/Ragon.Server/Sources/Plugin/Web/Request/RoomCreatedRequest.cs +++ b/Ragon.Server/Sources/Plugin/Web/Request/RoomCreatedRequest.cs @@ -1,7 +1,9 @@ + 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 index 51474c8..54722ae 100644 --- a/Ragon.Server/Sources/Plugin/Web/Request/RoomJoinedRequest.cs +++ b/Ragon.Server/Sources/Plugin/Web/Request/RoomJoinedRequest.cs @@ -2,5 +2,6 @@ namespace Ragon.Server.Plugin.Web; public class RoomJoinedRequest { - -} \ No newline at end of file + 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 index 495a9f0..1787a4c 100644 --- a/Ragon.Server/Sources/Plugin/Web/Request/RoomLeavedRequest.cs +++ b/Ragon.Server/Sources/Plugin/Web/Request/RoomLeavedRequest.cs @@ -3,5 +3,6 @@ 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 index 09731d7..e0917b3 100644 --- a/Ragon.Server/Sources/Plugin/Web/Request/RoomRemovedRequest.cs +++ b/Ragon.Server/Sources/Plugin/Web/Request/RoomRemovedRequest.cs @@ -1,7 +1,9 @@ +using Ragon.Server.Plugin.Web; + 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/RagonContext.cs b/Ragon.Server/Sources/RagonContext.cs index 5c72877..4aafa22 100644 --- a/Ragon.Server/Sources/RagonContext.cs +++ b/Ragon.Server/Sources/RagonContext.cs @@ -23,9 +23,8 @@ namespace Ragon.Server; public class RagonContext { - public INetworkConnection Connection { get; } public ConnectionStatus ConnectionStatus { get; set; } - + public INetworkConnection Connection { get; } public IExecutor Executor { get; private set; } public IRagonLobby Lobby { get; private set; } diff --git a/Ragon.Server/Sources/RagonServer.cs b/Ragon.Server/Sources/RagonServer.cs index d47955c..d49c336 100644 --- a/Ragon.Server/Sources/RagonServer.cs +++ b/Ragon.Server/Sources/RagonServer.cs @@ -18,6 +18,7 @@ using System.Diagnostics; using NLog; using Ragon.Protocol; using Ragon.Server.Handler; +using Ragon.Server.Http; using Ragon.Server.IO; using Ragon.Server.Lobby; using Ragon.Server.Plugin; @@ -26,20 +27,23 @@ 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 WebHookPlugin _webhooks; 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 readonly Dictionary _contextsByConnection; + private readonly Dictionary _contextsByPlayerId; private readonly Stopwatch _timer; private readonly long _tickRate = 0; @@ -51,20 +55,26 @@ public class RagonServer : INetworkListener _server = server; _executor = _server.Executor; _configuration = configuration; - _contexts = new Dictionary(); + _serverPlugin = plugin; + _contextsByConnection = new Dictionary(); + _contextsByPlayerId = new Dictionary(); _lobby = new LobbyInMemory(); _scheduler = new RagonScheduler(); - _webhooks = new WebHookPlugin(this, configuration); + _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; _timer = new Stopwatch(); + var contextObserver = new RagonContextObserver(_contextsByPlayerId); + + _serverPlugin.OnAttached(this); + _handlers = new IRagonOperation[byte.MaxValue]; - _handlers[(byte) RagonOperation.AUTHORIZE] = new AuthorizationOperation(_webhooks, _writer, configuration); + _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); @@ -78,8 +88,6 @@ public class RagonServer : INetworkListener _logger.Trace($"Server Tick Rate: {_configuration.ServerTickRate}"); } - - public IRagonOperation Resolve(RagonOperation operation) => _handlers[(byte)operation]; public void Execute() { @@ -88,11 +96,11 @@ public class RagonServer : INetworkListener { if (_timer.ElapsedMilliseconds > _tickRate) { - _executor.Update(); _scheduler.Update(_timer.ElapsedMilliseconds / 1000.0f); _timer.Restart(); } + _executor.Update(); _server.Update(); Thread.Sleep(1); } @@ -108,6 +116,7 @@ public class RagonServer : INetworkListener Port = _configuration.Port, }; + _httpServer.Start(_configuration); _server.Start(this, networkConfiguration); if (executeInDedicatedThread) @@ -118,6 +127,7 @@ public class RagonServer : INetworkListener public void Dispose() { + _serverPlugin.OnDetached(); _server.Stop(); _dedicatedThread.Interrupt(); } @@ -127,12 +137,12 @@ public class RagonServer : INetworkListener 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) @@ -152,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) @@ -173,7 +183,7 @@ 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(); @@ -188,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 5b14c50..c27a388 100644 --- a/Ragon.Server/Sources/RagonServerConfiguration.cs +++ b/Ragon.Server/Sources/RagonServerConfiguration.cs @@ -38,6 +38,8 @@ 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; diff --git a/Ragon.Server/Sources/Room/IRagonRoom.cs b/Ragon.Server/Sources/Room/IRagonRoom.cs new file mode 100644 index 0000000..c397d8b --- /dev/null +++ b/Ragon.Server/Sources/Room/IRagonRoom.cs @@ -0,0 +1,9 @@ +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 af65d47..e5018dd 100644 --- a/Ragon.Server/Sources/Room/RagonRoom.cs +++ b/Ragon.Server/Sources/Room/RagonRoom.cs @@ -16,12 +16,13 @@ using Ragon.Protocol; using Ragon.Server.Entity; +using Ragon.Server.IO; using Ragon.Server.Plugin; using Ragon.Server.Time; namespace Ragon.Server.Room; -public class RagonRoom : IRagonAction +public class RagonRoom : IRagonRoom, IRagonAction { public string Id { get; private set; } public string Map { get; private set; } @@ -44,7 +45,7 @@ public class RagonRoom : IRagonAction public List EntityList { get; private set; } private readonly HashSet _entitiesDirtySet; - + public RagonRoom(string roomId, RoomInformation info, IRoomPlugin roomPlugin) { Id = roomId; @@ -204,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