Compare commits

...

38 Commits

Author SHA1 Message Date
edmand46 0a8d761cc1 🐛 empty payload 2023-07-23 15:56:08 +03:00
edmand46 f38c7e98de ⬆️ deps 2023-07-23 11:23:38 +03:00
edmand46 0479a21980 feat: added safe get entity by id 2023-07-09 07:40:06 +03:00
edmand46 1406b17d62 🚑 independent buffers in properties 2023-07-01 08:12:40 +03:00
edmand46 105457ffa0 added transfer ownership, limit buffered events 2023-07-01 07:47:57 +03:00
edmand46 20662ae24d wip 2023-06-27 23:41:30 +03:00
edmand46 6c441d9dee feat: Entity.OnEvent now support resubscribing 2023-05-25 11:28:18 +03:00
edmand46 907bd2611e added reason of disconnect at callback 2023-05-08 00:16:43 +03:00
edmand46 91d8516ac9 independent buffers for ragon properties, extended buffer functionality 2023-05-07 23:54:07 +03:00
edmand46 ecdafeab00 ♻️ naming changing 2023-05-07 18:16:46 +03:00
edmand46 88baff9fee compressor extensions for write/read data and reduce boilerplate 2023-05-07 18:07:56 +03:00
edmand46 fdb41649b2 ♻️ added checks in client sdk 2023-05-07 12:46:39 +03:00
edmand46 aa607a7eb9 Update README.md 2023-05-06 15:30:16 +04:00
edmand46 efebf4ceda 🐛 scene entities 2023-04-14 17:42:27 +04:00
edmand46 17d1b7307d ♻️ plugin api 2023-04-14 14:32:24 +04:00
edmand46 fc28f512ba ♻️ plugin api 2023-04-14 14:32:04 +04:00
edmand46 6c4a51534a Merge pull request #12 from edmand46/develop
Plugins
2023-04-13 20:49:31 +04:00
edmand46 c91551ae08 📝 update headers 2023-04-13 20:49:00 +04:00
edmand46 e1a9ad476c http-commands 2023-04-13 20:42:05 +04:00
edmand46 24c9aa2043 🎨 namespaces 2023-04-09 11:06:52 +04:00
edmand46 bfd6c1b54b 🚧 plugin system, webhook system 2023-04-09 10:52:18 +04:00
edmand46 f2edc94958 chore: move sources to Sources folder 2023-04-05 18:53:21 +04:00
edmand46 b8dfc4cf41 chore: removed unused project 2023-04-05 18:48:37 +04:00
edmand46 bd7713bfcb fixed: cache sending on disconnecting 2023-03-31 12:56:23 +04:00
edmand46 043523d712 fixed: checking owner 2023-03-25 20:52:36 +04:00
edmand46 0dc5307b92 fixed: tickrate 2023-03-23 19:17:54 +04:00
edmand46 7b581b9afe chore: added tickrate logging 2023-03-23 18:07:48 +04:00
edmand46 8c5e063ef0 fixed: initial dirty property not tracked 2023-03-23 14:05:03 +04:00
edmand46 1a5f72a815 fixed: property update 2023-03-23 05:28:47 +04:00
edmand46 828da0d3da fix: wrong initial capacity 2023-03-22 11:04:14 +04:00
edmand46 951174e491 feat: split interface IRagonListener 2023-03-07 13:12:48 +04:00
edmand46 10b85867af feat: split interface IRagonListener 2023-03-07 00:57:13 +04:00
edmand46 252a46a713 chore: naming 2023-03-06 10:46:02 +04:00
edmand46 273c167c87 chore: naming 2023-03-06 10:31:42 +04:00
edmand46 192fb9e8eb chore: grammarly mistake 2023-03-06 10:29:22 +04:00
edmand46 a8ddc40268 Merge pull request #11 from edmand46/next
Update readme
2023-03-06 10:23:13 +04:00
edmand46 1ae545b353 chore: update readme.md 2023-03-06 10:22:27 +04:00
edmand46 b2058d21ce Merge pull request #10 from edmand46/next
Major update
2023-03-06 10:11:32 +04:00
111 changed files with 2069 additions and 756 deletions
+2
View File
@@ -4,3 +4,5 @@
obj obj
bin bin
*.user *.user
*.dylib
*.dll
+1 -4
View File
@@ -6,7 +6,7 @@
Ragon is fully free, small and high perfomance room based game server with plugin based architecture. Ragon is fully free, small and high perfomance room based game server with plugin based architecture.
<a href="https://www.ragon-server.com/docs/installation">Documentation</a> <a href="https://www.ragon-server.com/docs/overview">Documentation</a>
### Features: ### Features:
- Effective - Effective
@@ -26,9 +26,6 @@ Ragon is fully free, small and high perfomance room based game server with plugi
### Dependencies ### Dependencies
* ENet-Sharp [v2.4.8] * ENet-Sharp [v2.4.8]
### License
MIT
### Tips ### Tips
\* Limited to 4095 CCU by library ENet-Sharp (1) \* Limited to 4095 CCU by library ENet-Sharp (1)
\* Non finally (2) \* Non finally (2)
+4 -2
View File
@@ -1,5 +1,7 @@
using Ragon.Protocol;
namespace Ragon.Client.Simulation; namespace Ragon.Client.Simulation;
public class Game : IRagonListener public class Game : IRagonListener
@@ -18,7 +20,7 @@ public class Game : IRagonListener
public void OnConnected(RagonClient client) public void OnConnected(RagonClient client)
{ {
RagonLog.Trace("Connected"); RagonLog.Trace("Connected");
_client.Session.AuthorizeWithKey("defaultkey", "Player Eduard", Array.Empty<byte>()); _client.Session.AuthorizeWithKey("defaultkey", "Player Eduard");
} }
public void OnAuthorizationSuccess(RagonClient client, string playerId, string playerName) public void OnAuthorizationSuccess(RagonClient client, string playerId, string playerName)
@@ -63,7 +65,7 @@ public class Game : IRagonListener
RagonLog.Trace("Left"); RagonLog.Trace("Left");
} }
public void OnDisconnected(RagonClient client) public void OnDisconnected(RagonClient client, RagonDisconnect ragonDisconnect)
{ {
RagonLog.Trace("Disconnected"); RagonLog.Trace("Disconnected");
} }
@@ -16,6 +16,7 @@
using ENet; using ENet;
using Ragon.Protocol;
using Event = ENet.Event; using Event = ENet.Event;
using EventType = ENet.EventType; using EventType = ENet.EventType;
@@ -31,7 +32,7 @@ namespace Ragon.Client
public Action<byte[]> OnData { get; set; } public Action<byte[]> OnData { get; set; }
public Action OnConnected { get; set; } public Action OnConnected { get; set; }
public Action<DisconnectReason> OnDisconnected { get; set; } public Action<RagonDisconnect> OnDisconnected { get; set; }
public ulong BytesSent { get; } public ulong BytesSent { get; }
public ulong BytesReceived { get; } public ulong BytesReceived { get; }
public int Ping { get; } public int Ping { get; }
@@ -98,10 +99,10 @@ namespace Ragon.Client
OnConnected?.Invoke(); OnConnected?.Invoke();
break; break;
case EventType.Disconnect: case EventType.Disconnect:
OnDisconnected?.Invoke(DisconnectReason.MANUAL); OnDisconnected?.Invoke(RagonDisconnect.SERVER);
break; break;
case EventType.Timeout: case EventType.Timeout:
OnDisconnected?.Invoke(DisconnectReason.TIMEOUT); OnDisconnected?.Invoke(RagonDisconnect.TIMEOUT);
break; break;
case EventType.Receive: case EventType.Receive:
var data = new byte[_netEvent.Packet.Length]; var data = new byte[_netEvent.Packet.Length];
@@ -16,6 +16,7 @@
using ENet; using ENet;
using Ragon.Protocol;
using Event = ENet.Event; using Event = ENet.Event;
using EventType = ENet.EventType; using EventType = ENet.EventType;
@@ -31,7 +32,7 @@ namespace Ragon.Client
public Action<byte[]> OnData { get; set; } public Action<byte[]> OnData { get; set; }
public Action OnConnected { get; set; } public Action OnConnected { get; set; }
public Action<DisconnectReason> OnDisconnected { get; set; } public Action<RagonDisconnect> OnDisconnected { get; set; }
public ulong BytesSent { get; } public ulong BytesSent { get; }
public ulong BytesReceived { get; } public ulong BytesReceived { get; }
public int Ping { get; } public int Ping { get; }
@@ -99,10 +100,10 @@ namespace Ragon.Client
OnConnected?.Invoke(); OnConnected?.Invoke();
break; break;
case EventType.Disconnect: case EventType.Disconnect:
OnDisconnected?.Invoke(DisconnectReason.MANUAL); OnDisconnected?.Invoke(RagonDisconnect.SERVER);
break; break;
case EventType.Timeout: case EventType.Timeout:
OnDisconnected?.Invoke(DisconnectReason.TIMEOUT); OnDisconnected?.Invoke(RagonDisconnect.TIMEOUT);
break; break;
case EventType.Receive: case EventType.Receive:
var data = new byte[_netEvent.Packet.Length]; var data = new byte[_netEvent.Packet.Length];
+1 -1
View File
@@ -12,7 +12,7 @@
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DebugType>none</DebugType> <DebugType>none</DebugType>
<OutputPath>/Users/edmand46/RagonProjects/ragon-unity-sdk/Assets/Ragon/Runtime/Plugins</OutputPath> <OutputPath>/Users/edmand46/RagonProjects/ragon-oss-sdk/Assets/Ragon/Plugins/</OutputPath>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+45 -14
View File
@@ -22,6 +22,7 @@ namespace Ragon.Client
public sealed class RagonEntity public sealed class RagonEntity
{ {
private delegate void OnEventDelegate(RagonPlayer player, RagonBuffer serializer); private delegate void OnEventDelegate(RagonPlayer player, RagonBuffer serializer);
private RagonClient _client; private RagonClient _client;
public ushort Id { get; private set; } public ushort Id { get; private set; }
@@ -83,21 +84,35 @@ namespace Ragon.Client
internal T GetPayload<T>(RagonPayload data) where T : IRagonPayload, new() internal T GetPayload<T>(RagonPayload data) where T : IRagonPayload, new()
{ {
var payload = new T();
if (data.Size <= 0) return payload;
var buffer = new RagonBuffer(); var buffer = new RagonBuffer();
data.Write(buffer); data.Write(buffer);
var payload = new T();
payload.Deserialize(buffer); payload.Deserialize(buffer);
return payload; return payload;
} }
public T GetSpawnPayload<T>() where T : IRagonPayload, new() public void AttachPayload(IRagonPayload? payload)
{
if (payload == null) return;
var buffer = new RagonBuffer();
payload.Serialize(buffer);
_spawnPayload = new RagonPayload();
_spawnPayload.Read(buffer);
}
public T GetAttachPayload<T>() where T : IRagonPayload, new()
{ {
return GetPayload<T>(_spawnPayload); return GetPayload<T>(_spawnPayload);
} }
public T GetDestroyPayload<T>() where T : IRagonPayload, new() public T GetDetachPayload<T>() where T : IRagonPayload, new()
{ {
return GetPayload<T>(_destroyPayload); return GetPayload<T>(_destroyPayload);
} }
@@ -105,6 +120,12 @@ namespace Ragon.Client
public void ReplicateEvent<TEvent>(TEvent evnt, RagonPlayer target, RagonReplicationMode replicationMode) public void ReplicateEvent<TEvent>(TEvent evnt, RagonPlayer target, RagonReplicationMode replicationMode)
where TEvent : IRagonEvent, new() where TEvent : IRagonEvent, new()
{ {
if (!IsAttached)
{
RagonLog.Error("Entity not attached");
return;
}
var evntId = _client.Event.GetEventCode(evnt); var evntId = _client.Event.GetEventCode(evnt);
var buffer = _client.Buffer; var buffer = _client.Buffer;
@@ -112,9 +133,9 @@ namespace Ragon.Client
buffer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT); buffer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
buffer.WriteUShort(Id); buffer.WriteUShort(Id);
buffer.WriteUShort(evntId); buffer.WriteUShort(evntId);
buffer.WriteByte((byte) replicationMode); buffer.WriteByte((byte)replicationMode);
buffer.WriteByte((byte) RagonTarget.Player); buffer.WriteByte((byte)RagonTarget.Player);
buffer.WriteUShort((ushort) target.PeerId); buffer.WriteUShort(target.PeerId);
evnt.Serialize(buffer); evnt.Serialize(buffer);
@@ -128,6 +149,12 @@ namespace Ragon.Client
RagonReplicationMode replicationMode = RagonReplicationMode.Server) RagonReplicationMode replicationMode = RagonReplicationMode.Server)
where TEvent : IRagonEvent, new() where TEvent : IRagonEvent, new()
{ {
if (!IsAttached)
{
RagonLog.Error("Entity not attached");
return;
}
if (target != RagonTarget.ExceptOwner) if (target != RagonTarget.ExceptOwner)
{ {
if (replicationMode == RagonReplicationMode.Local) if (replicationMode == RagonReplicationMode.Local)
@@ -151,8 +178,8 @@ namespace Ragon.Client
buffer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT); buffer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
buffer.WriteUShort(Id); buffer.WriteUShort(Id);
buffer.WriteUShort(evntId); buffer.WriteUShort(evntId);
buffer.WriteByte((byte) replicationMode); buffer.WriteByte((byte)replicationMode);
buffer.WriteByte((byte) target); buffer.WriteByte((byte)target);
evnt.Serialize(buffer); evnt.Serialize(buffer);
@@ -167,11 +194,13 @@ namespace Ragon.Client
if (_events.ContainsKey(eventCode)) if (_events.ContainsKey(eventCode))
{ {
RagonLog.Warn($"Event already {eventCode} subscribed"); _events.Remove(eventCode);
return; _localEvents.Remove(eventCode);
RagonLog.Warn($"Event already {eventCode} subscribed, removed old one!");
} }
_localEvents.Add(eventCode, (player, eventData) => { callback.Invoke(player, (TEvent) eventData); }); _localEvents.Add(eventCode, (player, eventData) => { callback.Invoke(player, (TEvent)eventData); });
_events.Add(eventCode, (player, serializer) => _events.Add(eventCode, (player, serializer) =>
{ {
t.Deserialize(serializer); t.Deserialize(serializer);
@@ -196,8 +225,10 @@ namespace Ragon.Client
internal void Event(ushort eventCode, RagonPlayer caller, RagonBuffer buffer) internal void Event(ushort eventCode, RagonPlayer caller, RagonBuffer buffer)
{ {
if (_events.ContainsKey(eventCode)) if (_events.TryGetValue(eventCode, out var evnt))
_events[eventCode]?.Invoke(caller, buffer); evnt?.Invoke(caller, buffer);
else
RagonLog.Warn($"Handler event on entity {Id} with eventCode {eventCode} not defined");
} }
internal void TrackChangedProperty(RagonProperty property) internal void TrackChangedProperty(RagonProperty property)
@@ -210,7 +241,7 @@ namespace Ragon.Client
var prevOwner = Owner; var prevOwner = Owner;
Owner = player; Owner = player;
HasAuthority = player.PeerId == _client.Room.Local.PeerId; HasAuthority = player.IsLocal;
OwnershipChanged?.Invoke(prevOwner, player); OwnershipChanged?.Invoke(prevOwner, player);
} }
@@ -53,7 +53,7 @@ public sealed class RagonEntityState
{ {
var changed = buffer.ReadBool(); var changed = buffer.ReadBool();
if (changed) if (changed)
property.Deserialize(buffer); property.Read(buffer);
} }
} }
+2 -2
View File
@@ -21,8 +21,8 @@ namespace Ragon.Client;
public struct RagonPayload public struct RagonPayload
{ {
private uint[] _data = new uint[128]; private readonly uint[] _data = new uint[128];
private int _size = 0; private readonly int _size = 0;
public RagonPayload(int capacity) public RagonPayload(int capacity)
{ {
+40 -18
View File
@@ -29,22 +29,25 @@ namespace Ragon.Client
public bool IsFixed => _fixed; public bool IsFixed => _fixed;
public int Size => _size; public int Size => _size;
private bool _fixed; private RagonBuffer _propertyBuffer;
private string _name;
protected bool _invokeLocal;
private RagonEntity _entity; private RagonEntity _entity;
private bool _dirty; private bool _dirty;
private int _size; private int _size;
private int _ticks; private int _ticks;
private int _priority; private int _priority;
private bool _fixed;
private string _name;
protected bool InvokeLocal;
protected RagonProperty(int priority, bool invokeLocal) protected RagonProperty(int priority, bool invokeLocal)
{ {
_size = 0; _size = 0;
_priority = priority; _priority = priority;
_fixed = false; _fixed = false;
_invokeLocal = invokeLocal; _propertyBuffer = new RagonBuffer();
InvokeLocal = invokeLocal;
} }
public void SetName(string name) public void SetName(string name)
@@ -60,7 +63,7 @@ namespace Ragon.Client
protected void InvokeChanged() protected void InvokeChanged()
{ {
if (!_invokeLocal) if (!InvokeLocal)
return; return;
Changed?.Invoke(); Changed?.Invoke();
@@ -70,10 +73,11 @@ namespace Ragon.Client
{ {
InvokeChanged(); InvokeChanged();
if (_dirty) return; if (_dirty || _entity == null)
_dirty = true; return;
_entity?.TrackChangedProperty(this); _dirty = true;
_entity.TrackChangedProperty(this);
} }
internal void Flush() internal void Flush()
@@ -87,29 +91,47 @@ namespace Ragon.Client
_ticks++; _ticks++;
} }
internal void AssignEntity(RagonEntity obj) internal void AssignEntity(RagonEntity ent)
{ {
_entity = obj; _entity = ent;
Changed?.Invoke(); Changed?.Invoke();
} }
internal void Write(RagonBuffer buffer) internal void Write(RagonBuffer buffer)
{ {
_propertyBuffer.Clear();
if (_fixed) if (_fixed)
{ {
Serialize(buffer); Serialize(_propertyBuffer);
buffer.FromBuffer(_propertyBuffer, _size);
return; return;
} }
var sizeOffset = buffer.WriteOffset; Serialize(_propertyBuffer);
buffer.Write(0, 16);
var propOffset = buffer.WriteOffset;
Serialize(buffer); var propertySize = (ushort) _propertyBuffer.WriteOffset;
buffer.WriteUShort(propertySize);;
buffer.FromBuffer(_propertyBuffer, propertySize);
}
var propSize = (uint) (buffer.WriteOffset - propOffset); internal void Read(RagonBuffer buffer)
buffer.Write(propSize, 16, sizeOffset); {
_propertyBuffer.Clear();
if (_fixed)
{
buffer.ToBuffer(_propertyBuffer, _size);
Deserialize(_propertyBuffer);
return;
}
var propSize = buffer.ReadUShort();
buffer.ToBuffer(_propertyBuffer, propSize);
Deserialize(_propertyBuffer);
} }
public virtual void Serialize(RagonBuffer buffer) public virtual void Serialize(RagonBuffer buffer)
@@ -32,7 +32,8 @@ internal class AuthorizeSuccessHandler: Handler
{ {
var playerId = buffer.ReadString(); var playerId = buffer.ReadString();
var playerName = buffer.ReadString(); var playerName = buffer.ReadString();
var playerPayload = buffer.ReadString();
_listenerList.OnAuthorizationSuccess(playerId, playerName); _listenerList.OnAuthorizationSuccess(playerId, playerName, playerPayload);
} }
} }
@@ -51,7 +51,7 @@ internal class EntityCreateHandler : Handler
return; return;
} }
var hasAuthority = _playerCache.LocalPlayer.Id == player.Id; var hasAuthority = _playerCache.Local.Id == player.Id;
var entity = _entityCache.OnCreate(attachId, entityType, 0, entityId, hasAuthority); var entity = _entityCache.OnCreate(attachId, entityType, 0, entityId, hasAuthority);
entity.Attach(_client, entityId, entityType, hasAuthority, player, payload); entity.Attach(_client, entityId, entityType, hasAuthority, player, payload);
} }
@@ -49,7 +49,7 @@ internal class EntityEventHandler : Handler
return; return;
} }
if (player.IsMe && executionMode == RagonReplicationMode.LocalAndServer) if (player.IsLocal && executionMode == RagonReplicationMode.LocalAndServer)
return; return;
_entityCache.OnEvent(player, entityId, eventCode, buffer); _entityCache.OnEvent(player, entityId, eventCode, buffer);
@@ -19,13 +19,13 @@ using Ragon.Protocol;
namespace Ragon.Client; namespace Ragon.Client;
internal class OwnershipHandler: Handler internal class EntityOwnershipHandler: Handler
{ {
private readonly RagonListenerList _listenerList; private readonly RagonListenerList _listenerList;
private readonly RagonPlayerCache _playerCache; private readonly RagonPlayerCache _playerCache;
private readonly RagonEntityCache _entityCache; private readonly RagonEntityCache _entityCache;
public OwnershipHandler( public EntityOwnershipHandler(
RagonListenerList listenerList, RagonListenerList listenerList,
RagonPlayerCache playerCache, RagonPlayerCache playerCache,
RagonEntityCache entityCache) RagonEntityCache entityCache)
@@ -37,17 +37,16 @@ internal class OwnershipHandler: Handler
public void Handle(RagonBuffer buffer) public void Handle(RagonBuffer buffer)
{ {
var newOwnerId = buffer.ReadString(); var newOwnerId = buffer.ReadUShort();
var player = _playerCache.GetPlayerById(newOwnerId);
_playerCache.OnOwnershipChanged(newOwnerId);
_listenerList.OnOwnershipChanged(player);
var entities = buffer.ReadUShort(); var entities = buffer.ReadUShort();
var player = _playerCache.GetPlayerByPeer(newOwnerId);
for (var i = 0; i < entities; i++) for (var i = 0; i < entities; i++)
{ {
var entityId = buffer.ReadUShort(); var entityId = buffer.ReadUShort();
_entityCache.OnOwnershipChanged(player, entityId); _entityCache.OnOwnershipChanged(player, entityId);
RagonLog.Trace("Entity changed owner: " + entityId);
} }
} }
} }
@@ -19,11 +19,11 @@ using Ragon.Protocol;
namespace Ragon.Client; namespace Ragon.Client;
internal class EntityDestroyHandler: Handler internal class EntityRemoveHandler: Handler
{ {
private readonly RagonEntityCache _entityCache; private readonly RagonEntityCache _entityCache;
public EntityDestroyHandler(RagonEntityCache entityCache) public EntityRemoveHandler(RagonEntityCache entityCache)
{ {
_entityCache = entityCache; _entityCache = entityCache;
} }
@@ -0,0 +1,46 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* 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.Client;
internal class OwnershipRoomHandler: Handler
{
private readonly RagonListenerList _listenerList;
private readonly RagonPlayerCache _playerCache;
private readonly RagonEntityCache _entityCache;
public OwnershipRoomHandler(
RagonListenerList listenerList,
RagonPlayerCache playerCache,
RagonEntityCache entityCache)
{
_listenerList = listenerList;
_playerCache = playerCache;
_entityCache = entityCache;
}
public void Handle(RagonBuffer buffer)
{
var newOwnerId = buffer.ReadUShort();
var player = _playerCache.GetPlayerByPeer(newOwnerId);
_playerCache.OnOwnershipChanged(newOwnerId);
_listenerList.OnOwnershipChanged(player);
}
}
@@ -15,6 +15,7 @@
*/ */
using System.Diagnostics;
using Ragon.Protocol; using Ragon.Protocol;
namespace Ragon.Client; namespace Ragon.Client;
@@ -64,7 +65,7 @@ internal class SnapshotHandler : Handler
var payload = new RagonPayload(payloadSize); var payload = new RagonPayload(payloadSize);
payload.Read(buffer); payload.Read(buffer);
var hasAuthority = _playerCache.LocalPlayer.Id == player.Id; var hasAuthority = _playerCache.Local.Id == player.Id;
var entity = _entityCache.OnCreate(0, entityType, 0, entityId, hasAuthority); var entity = _entityCache.OnCreate(0, entityType, 0, entityId, hasAuthority);
entity.Read(buffer); entity.Read(buffer);
@@ -85,7 +86,7 @@ internal class SnapshotHandler : Handler
var payload = new RagonPayload(payloadSize); var payload = new RagonPayload(payloadSize);
payload.Read(buffer); payload.Read(buffer);
var hasAuthority = _playerCache.LocalPlayer.Id == player.Id; var hasAuthority = _playerCache.Local.Id == player.Id;
var entity = _entityCache.OnCreate(0, entityType, staticId, entityId, hasAuthority); var entity = _entityCache.OnCreate(0, entityType, staticId, entityId, hasAuthority);
entity.Read(buffer); entity.Read(buffer);
@@ -15,6 +15,8 @@
*/ */
using Ragon.Protocol;
namespace Ragon.Client; namespace Ragon.Client;
public interface INetworkConnection: IRagonConnection public interface INetworkConnection: IRagonConnection
@@ -23,7 +25,7 @@ public interface INetworkConnection: IRagonConnection
public INetworkChannel Unreliable { get; } public INetworkChannel Unreliable { get; }
public Action<byte[]> OnData { get; set; } public Action<byte[]> OnData { get; set; }
public Action OnConnected { get; set; } public Action OnConnected { get; set; }
public Action<DisconnectReason> OnDisconnected { get; set; } public Action<RagonDisconnect> OnDisconnected { get; set; }
public ulong BytesSent { get; } public ulong BytesSent { get; }
public ulong BytesReceived { get; } public ulong BytesReceived { get; }
public int Ping { get; } public int Ping { get; }
@@ -14,10 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
using Ragon.Protocol;
namespace Ragon.Client; namespace Ragon.Client;
public interface IRagonConnectedListener public interface IRagonConnectionListener
{ {
void OnConnected(RagonClient client); void OnConnected(RagonClient client);
void OnDisconnected(RagonClient client); void OnDisconnected(RagonClient client, RagonDisconnect reason);
} }
@@ -18,7 +18,7 @@ namespace Ragon.Client
{ {
public interface IRagonListener : public interface IRagonListener :
IRagonAuthorizationListener, IRagonAuthorizationListener,
IRagonConnectedListener, IRagonConnectionListener,
IRagonFailedListener, IRagonFailedListener,
IRagonJoinListener, IRagonJoinListener,
IRagonLeftListener, IRagonLeftListener,
+77 -47
View File
@@ -21,9 +21,9 @@ namespace Ragon.Client
public sealed class RagonClient public sealed class RagonClient
{ {
private readonly INetworkConnection _connection; private readonly INetworkConnection _connection;
private readonly IRagonEntityListener _entityListener; private IRagonEntityListener _entityListener;
private readonly IRagonSceneCollector _sceneCollector; private IRagonSceneCollector _sceneCollector;
private Handler[] _processors; private Handler[] _handlers;
private RagonBuffer _readBuffer; private RagonBuffer _readBuffer;
private RagonBuffer _writeBuffer; private RagonBuffer _writeBuffer;
private RagonRoom _room; private RagonRoom _room;
@@ -35,8 +35,8 @@ namespace Ragon.Client
private RagonStatus _status; private RagonStatus _status;
private NetworkStatistics _stats; private NetworkStatistics _stats;
private float _replicatationRate = 0; private float _replicationRate = 0;
private float _replicatationTime = 0; private float _replicationTime = 0;
public IRagonConnection Connection => _connection; public IRagonConnection Connection => _connection;
public RagonStatus Status => _status; public RagonStatus Status => _status;
@@ -52,41 +52,48 @@ namespace Ragon.Client
#region PUBLIC #region PUBLIC
public RagonClient( public RagonClient(INetworkConnection connection, int rate)
INetworkConnection connection,
IRagonEntityListener entityListener,
IRagonSceneCollector sceneCollector,
int rate)
{ {
_listenerList = new RagonListenerList(this);
_entityListener = entityListener;
_sceneCollector = sceneCollector;
_connection = connection; _connection = connection;
_connection.OnData += OnData; _connection.OnData += OnData;
_connection.OnConnected += OnConnected; _connection.OnConnected += OnConnected;
_connection.OnDisconnected += OnDisconnected; _connection.OnDisconnected += OnDisconnected;
_replicatationRate = (1000.0f / rate) / 1000.0f; _listenerList = new RagonListenerList(this);
_replicatationTime = 0;
_replicationRate = (1000.0f / rate) / 1000.0f;
_replicationTime = 0;
_eventCache = new RagonEventCache(); _eventCache = new RagonEventCache();
_stats = new NetworkStatistics(); _stats = new NetworkStatistics();
_status = RagonStatus.DISCONNECTED; _status = RagonStatus.DISCONNECTED;
} }
public void AddListener(IRagonListener listener)
public void Configure(IRagonSceneCollector sceneCollector)
{ {
_listenerList.Add(listener); _sceneCollector = sceneCollector;
} }
public void RemoveListener(IRagonListener listener) public void Configure(IRagonEntityListener listener)
{ {
_listenerList.Remove(listener); _entityListener = listener;
} }
public void Connect(string address, ushort port, string protocol) public void Connect(string address, ushort port, string protocol)
{ {
if (_sceneCollector == null)
{
RagonLog.Error("Scene collector is not defined!");
return;
}
if (_entityListener == null)
{
RagonLog.Error("Entity Listener is not defined!");
return;
}
_writeBuffer = new RagonBuffer(); _writeBuffer = new RagonBuffer();
_readBuffer = new RagonBuffer(); _readBuffer = new RagonBuffer();
_session = new RagonSession(this, _readBuffer); _session = new RagonSession(this, _readBuffer);
@@ -94,26 +101,22 @@ namespace Ragon.Client
_playerCache = new RagonPlayerCache(); _playerCache = new RagonPlayerCache();
_entityCache = new RagonEntityCache(this, _playerCache, _entityListener, _sceneCollector); _entityCache = new RagonEntityCache(this, _playerCache, _entityListener, _sceneCollector);
_processors = new Handler[byte.MaxValue]; _handlers = new Handler[byte.MaxValue];
_processors[(byte)RagonOperation.AUTHORIZED_SUCCESS] = new AuthorizeSuccessHandler(_listenerList); _handlers[(byte)RagonOperation.AUTHORIZED_SUCCESS] = new AuthorizeSuccessHandler(_listenerList);
_processors[(byte)RagonOperation.AUTHORIZED_FAILED] = new AuthorizeFailedHandler(_listenerList); _handlers[(byte)RagonOperation.AUTHORIZED_FAILED] = new AuthorizeFailedHandler(_listenerList);
_processors[(byte)RagonOperation.JOIN_SUCCESS] = _handlers[(byte)RagonOperation.JOIN_SUCCESS] = new JoinSuccessHandler(this, _readBuffer, _listenerList, _playerCache, _entityCache);
new JoinSuccessHandler(this, _readBuffer, _listenerList, _playerCache, _entityCache); _handlers[(byte)RagonOperation.JOIN_FAILED] = new JoinFailedHandler(_listenerList);
_processors[(byte)RagonOperation.JOIN_FAILED] = new JoinFailedHandler(_listenerList); _handlers[(byte)RagonOperation.LEAVE_ROOM] = new LeaveRoomHandler(this, _listenerList, _entityCache);
_processors[(byte)RagonOperation.LEAVE_ROOM] = new LeaveRoomHandler(this, _listenerList, _entityCache); _handlers[(byte)RagonOperation.OWNERSHIP_ROOM_CHANGED] = new OwnershipRoomHandler(_listenerList, _playerCache, _entityCache);
_processors[(byte)RagonOperation.OWNERSHIP_CHANGED] = _handlers[(byte)RagonOperation.OWNERSHIP_ENTITY_CHANGED] = new EntityOwnershipHandler(_listenerList, _playerCache, _entityCache);
new OwnershipHandler(_listenerList, _playerCache, _entityCache); _handlers[(byte)RagonOperation.PLAYER_JOINED] = new PlayerJoinHandler(_playerCache, _listenerList);
_processors[(byte)RagonOperation.PLAYER_JOINED] = new PlayerJoinHandler(_playerCache, _listenerList); _handlers[(byte)RagonOperation.PLAYER_LEAVED] = new PlayerLeftHandler(_entityCache, _playerCache, _listenerList);
_processors[(byte)RagonOperation.PLAYER_LEAVED] = _handlers[(byte)RagonOperation.LOAD_SCENE] = new SceneLoadHandler(this, _listenerList);
new PlayerLeftHandler(_entityCache, _playerCache, _listenerList); _handlers[(byte)RagonOperation.CREATE_ENTITY] = new EntityCreateHandler(this, _playerCache, _entityCache);
_processors[(byte)RagonOperation.LOAD_SCENE] = new SceneLoadHandler(this, _listenerList); _handlers[(byte)RagonOperation.REMOVE_ENTITY] = new EntityRemoveHandler(_entityCache);
_processors[(byte)RagonOperation.CREATE_ENTITY] = new EntityCreateHandler(this, _playerCache, _entityCache); _handlers[(byte)RagonOperation.REPLICATE_ENTITY_STATE] = new StateEntityHandler(_entityCache);
_processors[(byte)RagonOperation.DESTROY_ENTITY] = new EntityDestroyHandler(_entityCache); _handlers[(byte)RagonOperation.REPLICATE_ENTITY_EVENT] = new EntityEventHandler(this, _playerCache, _entityCache);
_processors[(byte)RagonOperation.REPLICATE_ENTITY_STATE] = new StateEntityHandler(_entityCache); _handlers[(byte)RagonOperation.SNAPSHOT] = new SnapshotHandler(this, _listenerList, _entityCache, _playerCache);
_processors[(byte)RagonOperation.REPLICATE_ENTITY_EVENT] =
new EntityEventHandler(this, _playerCache, _entityCache);
_processors[(byte)RagonOperation.SNAPSHOT] =
new SnapshotHandler(this, _listenerList, _entityCache, _playerCache);
var protocolRaw = RagonVersion.Parse(protocol); var protocolRaw = RagonVersion.Parse(protocol);
_connection.Connect(address, port, protocolRaw); _connection.Connect(address, port, protocolRaw);
@@ -124,19 +127,24 @@ namespace Ragon.Client
_status = RagonStatus.DISCONNECTED; _status = RagonStatus.DISCONNECTED;
_room.Cleanup(); _room.Cleanup();
_connection.Disconnect(); _connection.Disconnect();
OnDisconnected(DisconnectReason.MANUAL);
OnDisconnected(RagonDisconnect.MANUAL);
} }
public void Update(float dt) public void Update(float dt)
{ {
_replicatationTime += dt; if (_status != RagonStatus.DISCONNECTED)
if (_replicatationTime >= _replicatationRate) {
_replicationTime += dt;
if (_replicationTime >= _replicationRate)
{ {
_entityCache.WriteState(_readBuffer); _entityCache.WriteState(_readBuffer);
_replicatationTime = 0; _replicationTime = 0;
} }
_stats.Update(_connection.BytesSent, _connection.BytesReceived, _connection.Ping, dt); _stats.Update(_connection.BytesSent, _connection.BytesReceived, _connection.Ping, dt);
}
_connection.Update(); _connection.Update();
} }
@@ -147,6 +155,28 @@ namespace Ragon.Client
_connection.Dispose(); _connection.Dispose();
} }
public void AddListener(IRagonListener listener) => _listenerList.Add(listener);
public void AddListener(IRagonAuthorizationListener 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);
public void AddListener(IRagonLevelListener listener) => _listenerList.Add(listener);
public void AddListener(IRagonOwnershipChangedListener listener) => _listenerList.Add(listener);
public void AddListener(IRagonPlayerJoinListener listener) => _listenerList.Add(listener);
public void AddListener(IRagonPlayerLeftListener listener) => _listenerList.Add(listener);
public void RemoveListener(IRagonListener listener) => _listenerList.Remove(listener);
public void RemoveListener(IRagonAuthorizationListener 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);
public void RemoveListener(IRagonLevelListener listener) => _listenerList.Remove(listener);
public void RemoveListener(IRagonOwnershipChangedListener listener) => _listenerList.Remove(listener);
public void RemoveListener(IRagonPlayerJoinListener listener) => _listenerList.Remove(listener);
public void RemoveListener(IRagonPlayerLeftListener listener) => _listenerList.Remove(listener);
#endregion #endregion
#region INTERNAL #region INTERNAL
@@ -169,11 +199,11 @@ namespace Ragon.Client
_status = RagonStatus.CONNECTED; _status = RagonStatus.CONNECTED;
} }
private void OnDisconnected(DisconnectReason reason) private void OnDisconnected(RagonDisconnect reason)
{ {
RagonLog.Trace($"Disconnected: {reason}"); RagonLog.Trace($"Disconnected: {reason}");
_listenerList.OnDisconnected(); _listenerList.OnDisconnected(reason);
_status = RagonStatus.DISCONNECTED; _status = RagonStatus.DISCONNECTED;
} }
@@ -183,7 +213,7 @@ namespace Ragon.Client
_readBuffer.FromArray(data); _readBuffer.FromArray(data);
var operation = _readBuffer.ReadByte(); var operation = _readBuffer.ReadByte();
_processors[operation].Handle(_readBuffer); _handlers[operation].Handle(_readBuffer);
} }
#endregion #endregion
+31 -8
View File
@@ -45,21 +45,21 @@ public sealed class RagonEntityCache
_playerCache = playerCache; _playerCache = playerCache;
} }
public RagonEntity FindById(ushort id) public bool TryGetEntity(ushort id, out RagonEntity entity)
{ {
return _entityMap[id]; return _entityMap.TryGetValue(id, out entity);
} }
public void Create(RagonEntity entity, IRagonPayload? spawnPayload) public void Create(RagonEntity entity, IRagonPayload? spawnPayload)
{ {
var attachId = (ushort) (_playerCache.LocalPlayer.PeerId + _localEntitiesCounter++) ; var attachId = (ushort)(_playerCache.Local.PeerId + _localEntitiesCounter++);
var buffer = _client.Buffer; var buffer = _client.Buffer;
buffer.Clear(); buffer.Clear();
buffer.WriteOperation(RagonOperation.CREATE_ENTITY); buffer.WriteOperation(RagonOperation.CREATE_ENTITY);
buffer.WriteUShort(attachId); buffer.WriteUShort(attachId);
buffer.WriteUShort(entity.Type); buffer.WriteUShort(entity.Type);
buffer.WriteByte((byte) entity.Authority); buffer.WriteByte((byte)entity.Authority);
entity.State.WriteInfo(buffer); entity.State.WriteInfo(buffer);
@@ -71,6 +71,19 @@ public sealed class RagonEntityCache
_client.Reliable.Send(sendData); _client.Reliable.Send(sendData);
} }
public void Transfer(RagonEntity entity, RagonPlayer player)
{
var buffer = _client.Buffer;
buffer.Clear();
buffer.WriteOperation(RagonOperation.TRANSFER_ENTITY_OWNERSHIP);
buffer.WriteUShort(entity.Id);
buffer.WriteUShort(player.PeerId);
var sendData = buffer.ToArray();
_client.Reliable.Send(sendData);
}
public void Destroy(RagonEntity entity, IRagonPayload? destroyPayload) public void Destroy(RagonEntity entity, IRagonPayload? destroyPayload)
{ {
if (!entity.IsAttached) if (!entity.IsAttached)
@@ -78,10 +91,11 @@ public sealed class RagonEntityCache
RagonLog.Warn("Can't destroy object, he is not created"); RagonLog.Warn("Can't destroy object, he is not created");
return; return;
} }
var buffer = _client.Buffer; var buffer = _client.Buffer;
buffer.Clear(); buffer.Clear();
buffer.WriteOperation(RagonOperation.DESTROY_ENTITY); buffer.WriteOperation(RagonOperation.REMOVE_ENTITY);
buffer.WriteUShort(entity.Id); buffer.WriteUShort(entity.Id);
destroyPayload?.Serialize(buffer); destroyPayload?.Serialize(buffer);
@@ -124,11 +138,11 @@ public sealed class RagonEntityCache
_sceneEntities.Clear(); _sceneEntities.Clear();
var entities = _sceneCollector.Collect(); var entities = _sceneCollector.Collect();
buffer.WriteUShort((ushort) entities.Length); buffer.WriteUShort((ushort)entities.Length);
foreach (var entity in entities) foreach (var entity in entities)
{ {
buffer.WriteUShort(entity.Type); buffer.WriteUShort(entity.Type);
buffer.WriteByte((byte) entity.Authority); buffer.WriteByte((byte)entity.Authority);
buffer.WriteUShort(entity.SceneId); buffer.WriteUShort(entity.SceneId);
entity.State.WriteInfo(buffer); entity.State.WriteInfo(buffer);
@@ -225,8 +239,17 @@ public sealed class RagonEntityCache
internal void OnOwnershipChanged(RagonPlayer player, ushort entityId) internal void OnOwnershipChanged(RagonPlayer player, ushort entityId)
{ {
if (_entityMap.TryGetValue(entityId, out var entity)) if (_entityMap.TryGetValue(entityId, out var entity))
entity.OnOwnershipChanged(player); {
if (player.IsLocal)
_entityList.Add(entity);
else else
_entityList.Remove(entity);
entity.OnOwnershipChanged(player);
}
else
{
RagonLog.Warn($"Entity {entityId} not found!"); RagonLog.Warn($"Entity {entityId} not found!");
} }
}
} }
+6 -2
View File
@@ -26,8 +26,12 @@ public class RagonEventCache
public ushort GetEventCode<TEvent>(TEvent _) where TEvent : IRagonEvent public ushort GetEventCode<TEvent>(TEvent _) where TEvent : IRagonEvent
{ {
var type = typeof(TEvent); var type = typeof(TEvent);
var evntCode = _eventsRegistryByType[type]; if (!_eventsRegistryByType.TryGetValue(type, out var eventCode))
return evntCode; {
RagonLog.Error($"Event with type {type} not registered");
return 0;
}
return eventCode;
} }
public void Register<T>() where T : IRagonEvent, new() public void Register<T>() where T : IRagonEvent, new()
+134 -19
View File
@@ -14,13 +14,22 @@
* limitations under the License. * limitations under the License.
*/ */
using Ragon.Protocol;
namespace Ragon.Client namespace Ragon.Client
{ {
internal class RagonListenerList internal class RagonListenerList
{ {
public int Count => _listeners.Count; private readonly RagonClient _client;
private List<IRagonListener> _listeners = new(); private readonly List<IRagonAuthorizationListener> _authorizationListeners = new();
private RagonClient _client; private readonly List<IRagonConnectionListener> _connectionListeners = new();
private readonly List<IRagonFailedListener> _failedListeners = new();
private readonly List<IRagonJoinListener> _joinListeners = new();
private readonly List<IRagonLeftListener> _leftListeners = new();
private readonly List<IRagonLevelListener> _levelListeners = new();
private readonly List<IRagonOwnershipChangedListener> _ownershipChangedListeners = new();
private readonly List<IRagonPlayerJoinListener> _playerJoinListeners = new();
private readonly List<IRagonPlayerLeftListener> _playerLeftListeners = new();
public RagonListenerList(RagonClient client) public RagonListenerList(RagonClient client)
{ {
@@ -29,78 +38,184 @@ namespace Ragon.Client
public void Add(IRagonListener listener) public void Add(IRagonListener listener)
{ {
_listeners.Add(listener); _authorizationListeners.Add(listener);
_connectionListeners.Add(listener);
_failedListeners.Add(listener);
_joinListeners.Add(listener);
_leftListeners.Add(listener);
_levelListeners.Add(listener);
_ownershipChangedListeners.Add(listener);
_playerJoinListeners.Add(listener);
_playerLeftListeners.Add(listener);
} }
public void Remove(IRagonListener listener) public void Remove(IRagonListener listener)
{ {
_listeners.Remove(listener); _authorizationListeners.Remove(listener);
_connectionListeners.Remove(listener);
_failedListeners.Remove(listener);
_joinListeners.Remove(listener);
_leftListeners.Remove(listener);
_levelListeners.Remove(listener);
_ownershipChangedListeners.Remove(listener);
_playerJoinListeners.Remove(listener);
_playerLeftListeners.Remove(listener);
} }
public void OnAuthorizationSuccess(string playerId, string playerName) public void Add(IRagonAuthorizationListener listener)
{ {
foreach (var listener in _listeners) _authorizationListeners.Add(listener);
}
public void Add(IRagonConnectionListener listener)
{
_connectionListeners.Add(listener);
}
public void Add(IRagonFailedListener listener)
{
_failedListeners.Add(listener);
}
public void Add(IRagonJoinListener listener)
{
_joinListeners.Add(listener);
}
public void Add(IRagonLeftListener listener)
{
_leftListeners.Add(listener);
}
public void Add(IRagonLevelListener listener)
{
_levelListeners.Add(listener);
}
public void Add(IRagonOwnershipChangedListener listener)
{
_ownershipChangedListeners.Add(listener);
}
public void Add(IRagonPlayerJoinListener listener)
{
_playerJoinListeners.Add(listener);
}
public void Add(IRagonPlayerLeftListener listener)
{
_playerLeftListeners.Add(listener);
}
public void Remove(IRagonAuthorizationListener listener)
{
_authorizationListeners.Remove(listener);
}
public void Remove(IRagonConnectionListener listener)
{
_connectionListeners.Remove(listener);
}
public void Remove(IRagonFailedListener listener)
{
_failedListeners.Remove(listener);
}
public void Remove(IRagonJoinListener listener)
{
_joinListeners.Remove(listener);
}
public void Remove(IRagonLeftListener listener)
{
_leftListeners.Remove(listener);
}
public void Remove(IRagonLevelListener listener)
{
_levelListeners.Remove(listener);
}
public void Remove(IRagonOwnershipChangedListener listener)
{
_ownershipChangedListeners.Remove(listener);
}
public void Remove(IRagonPlayerJoinListener listener)
{
_playerJoinListeners.Remove(listener);
}
public void Remove(IRagonPlayerLeftListener listener)
{
_playerLeftListeners.Remove(listener);
}
public void OnAuthorizationSuccess(string playerId, string playerName, string payload)
{
foreach (var listener in _authorizationListeners)
listener.OnAuthorizationSuccess(_client, playerId, playerName); listener.OnAuthorizationSuccess(_client, playerId, playerName);
} }
public void OnAuthorizationFailed(string message) public void OnAuthorizationFailed(string message)
{ {
foreach (var listener in _listeners) foreach (var listener in _authorizationListeners)
listener.OnAuthorizationFailed(_client, message); listener.OnAuthorizationFailed(_client, message);
} }
public void OnLeft() public void OnLeft()
{ {
foreach (var listener in _listeners) foreach (var listener in _leftListeners)
listener.OnLeft(_client); listener.OnLeft(_client);
} }
public void OnFailed(string message) public void OnFailed(string message)
{ {
foreach (var listener in _listeners) foreach (var listener in _failedListeners)
listener.OnFailed(_client, message); listener.OnFailed(_client, message);
} }
public void OnOwnershipChanged(RagonPlayer player) public void OnOwnershipChanged(RagonPlayer player)
{ {
foreach (var listener in _listeners) foreach (var listener in _ownershipChangedListeners)
listener.OnOwnershipChanged(_client, player); listener.OnOwnershipChanged(_client, player);
} }
public void OnPlayerLeft(RagonPlayer player) public void OnPlayerLeft(RagonPlayer player)
{ {
foreach (var listener in _listeners) foreach (var listener in _playerLeftListeners)
listener.OnPlayerLeft(_client, player); listener.OnPlayerLeft(_client, player);
} }
public void OnPlayerJoined(RagonPlayer player) public void OnPlayerJoined(RagonPlayer player)
{ {
foreach (var listener in _listeners) foreach (var listener in _playerJoinListeners)
listener.OnPlayerJoined(_client, player); listener.OnPlayerJoined(_client, player);
} }
public void OnLevel(string sceneName) public void OnLevel(string sceneName)
{ {
foreach (var listener in _listeners) foreach (var listener in _levelListeners)
listener.OnLevel(_client, sceneName); listener.OnLevel(_client, sceneName);
} }
public void OnJoined() public void OnJoined()
{ {
foreach (var listener in _listeners) foreach (var listener in _joinListeners)
listener.OnJoined(_client); listener.OnJoined(_client);
} }
public void OnConnected() public void OnConnected()
{ {
foreach (var listener in _listeners) foreach (var listener in _connectionListeners)
listener.OnConnected(_client); listener.OnConnected(_client);
} }
public void OnDisconnected() public void OnDisconnected(RagonDisconnect disconnect)
{ {
foreach (var listener in _listeners) foreach (var listener in _connectionListeners)
listener.OnDisconnected(_client); listener.OnDisconnected(_client, disconnect);
} }
} }
} }
+3 -3
View File
@@ -23,13 +23,13 @@ namespace Ragon.Client
public string Name { get; set; } public string Name { get; set; }
public ushort PeerId { get; set; } public ushort PeerId { get; set; }
public bool IsRoomOwner { get; set; } public bool IsRoomOwner { get; set; }
public bool IsMe { get; set; } public bool IsLocal { get; set; }
public RagonPlayer(ushort peerId, string playerId, string name, bool isRoomOwner, bool isMe) public RagonPlayer(ushort peerId, string playerId, string name, bool isRoomOwner, bool isLocal)
{ {
PeerId = peerId; PeerId = peerId;
IsRoomOwner = isRoomOwner; IsRoomOwner = isRoomOwner;
IsMe = isMe; IsLocal = isLocal;
Name = name; Name = name;
Id = playerId; Id = playerId;
} }
+12 -9
View File
@@ -18,12 +18,13 @@ namespace Ragon.Client;
public sealed class RagonPlayerCache public sealed class RagonPlayerCache
{ {
private List<RagonPlayer> _players = new List<RagonPlayer>(); private readonly List<RagonPlayer> _players = new();
private Dictionary<string, RagonPlayer> _playersById = new(); private readonly Dictionary<string, RagonPlayer> _playersById = new();
private Dictionary<ushort, RagonPlayer> _playersByConnection = new(); private readonly Dictionary<ushort, RagonPlayer> _playersByConnection = new();
public IReadOnlyList<RagonPlayer> Players => _players;
public RagonPlayer Owner { get; private set; } public RagonPlayer Owner { get; private set; }
public RagonPlayer LocalPlayer { get; private set; } public RagonPlayer Local { get; private set; }
public bool IsRoomOwner => _ownerId == _localId; public bool IsRoomOwner => _ownerId == _localId;
public RagonPlayer? GetPlayerById(string playerId) => _playersById[playerId]; public RagonPlayer? GetPlayerById(string playerId) => _playersById[playerId];
@@ -50,8 +51,8 @@ public sealed class RagonPlayerCache
var player = new RagonPlayer(peerId, playerId, playerName, isOwner, isLocal); var player = new RagonPlayer(peerId, playerId, playerName, isOwner, isLocal);
if (player.IsMe) if (player.IsLocal)
LocalPlayer = player; Local = player;
if (player.IsRoomOwner) if (player.IsRoomOwner)
Owner = player; Owner = player;
@@ -70,13 +71,15 @@ public sealed class RagonPlayerCache
} }
} }
public void OnOwnershipChanged(string playerId) public void OnOwnershipChanged(ushort playerPeerId)
{ {
foreach (var player in _players) foreach (var player in _players)
{ {
if (player.Id == playerId) if (player.PeerId == playerPeerId)
{
Owner = player; Owner = player;
player.IsRoomOwner = player.Id == playerId; Owner.IsRoomOwner = true;
}
} }
} }
+3 -1
View File
@@ -28,7 +28,8 @@ namespace Ragon.Client
public int MinPlayers => _information.Min; public int MinPlayers => _information.Min;
public int MaxPlayers => _information.Max; public int MaxPlayers => _information.Max;
public RagonPlayer Local => _playerCache.LocalPlayer; public IReadOnlyList<RagonPlayer> Players => _playerCache.Players;
public RagonPlayer Local => _playerCache.Local;
public RagonPlayer Owner => _playerCache.Owner; public RagonPlayer Owner => _playerCache.Owner;
public RagonRoom(RagonClient client, public RagonRoom(RagonClient client,
@@ -55,6 +56,7 @@ namespace Ragon.Client
public void CreateEntity(RagonEntity entity) => CreateEntity(entity, null); public void CreateEntity(RagonEntity entity) => CreateEntity(entity, null);
public void CreateEntity(RagonEntity entity, IRagonPayload? payload) => _entityCache.Create(entity, payload); public void CreateEntity(RagonEntity entity, IRagonPayload? payload) => _entityCache.Create(entity, payload);
public void TransferEntity(RagonEntity entity, RagonPlayer player) => _entityCache.Transfer(entity, player);
public void DestroyEntity(RagonEntity entityId) => DestroyEntity(entityId, null); public void DestroyEntity(RagonEntity entityId) => DestroyEntity(entityId, null);
public void DestroyEntity(RagonEntity entityId, IRagonPayload? payload) => _entityCache.Destroy(entityId, payload); public void DestroyEntity(RagonEntity entityId, IRagonPayload? payload) => _entityCache.Destroy(entityId, payload);
+2 -2
View File
@@ -93,13 +93,13 @@ namespace Ragon.Client
_client.Reliable.Send(sendData); _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.Clear();
_buffer.WriteOperation(RagonOperation.AUTHORIZE); _buffer.WriteOperation(RagonOperation.AUTHORIZE);
_buffer.WriteString(key); _buffer.WriteString(key);
_buffer.WriteString(playerName); _buffer.WriteString(playerName);
_buffer.WriteBytes(additonalData); _buffer.WriteString(payload);
var sendData = _buffer.ToArray(); var sendData = _buffer.ToArray();
_client.Reliable.Send(sendData); _client.Reliable.Send(sendData);
@@ -0,0 +1,27 @@
using Ragon.Client.Compressor;
using Ragon.Protocol;
namespace Ragon.Client.Utils;
public static class CompressorExtension
{
public static float Read(this FloatCompressor compressor, RagonBuffer buffer)
{
return compressor.Decompress(buffer.Read(compressor.RequiredBits));
}
public static void Write(this FloatCompressor compressor, RagonBuffer buffer, float value)
{
buffer.Write(compressor.Compress(value), compressor.RequiredBits);
}
public static float Read(this IntCompressor compressor, RagonBuffer buffer)
{
return compressor.Decompress(buffer.Read(compressor.RequiredBits));
}
public static void Write(this IntCompressor compressor, RagonBuffer buffer, int value)
{
buffer.Write(compressor.Compress(value), compressor.RequiredBits);
}
}
+11 -3
View File
@@ -4,8 +4,16 @@
<ImplicitUsings>disable</ImplicitUsings> <ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable> <Nullable>disable</Nullable>
<LangVersion>8</LangVersion> <LangVersion>8</LangVersion>
<TargetFramework>netstandard2.1</TargetFramework>
<RootNamespace>Ragon.Common</RootNamespace> <RootNamespace>Ragon.Common</RootNamespace>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>1.2.4-rc</Version>
<Title>Ragon.Protocol</Title>
<Copyright>Eduard Kargin</Copyright>
<PackageProjectUrl>https://ragon-server.com</PackageProjectUrl>
<RepositoryUrl>https://github.com/edmand46/Ragon</RepositoryUrl>
<RepositoryType>Source</RepositoryType>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
@@ -16,8 +24,8 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<OutputPath>C:\Users\edmand46\RagonProjects\ragon-unity-sdk\Assets\Ragon-Unity-SDK\Runtime\Plugins</OutputPath> <OutputPath></OutputPath>
<DefineConstants>TRACE;NETSTACK_SPAN</DefineConstants> <DefineConstants>TRACE;</DefineConstants>
<DebugType>none</DebugType> <DebugType>none</DebugType>
</PropertyGroup> </PropertyGroup>
</Project> </Project>
+15
View File
@@ -321,6 +321,7 @@ namespace Ragon.Protocol
_read += size; _read += size;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteSpan(ref ReadOnlySpan<uint> data, int size) public void WriteSpan(ref ReadOnlySpan<uint> data, int size)
{ {
var used = _write & 0x0000001F; var used = _write & 0x0000001F;
@@ -352,6 +353,20 @@ namespace Ragon.Protocol
_write = 0; _write = 0;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromBuffer(RagonBuffer buffer, int size)
{
ReadOnlySpan<uint> data = buffer._buckets.AsSpan();
WriteSpan(ref data, size);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ToBuffer(RagonBuffer buffer, int size)
{
var data = buffer._buckets.AsSpan();
ReadSpan(ref data, size);
}
public void FromArray(byte[] data) public void FromArray(byte[] data)
{ {
var length = data.Length; var length = data.Length;
@@ -0,0 +1,9 @@
namespace Ragon.Protocol
{
public enum RagonDisconnect
{
MANUAL,
TIMEOUT,
SERVER,
}
}
+5 -2
View File
@@ -26,7 +26,8 @@ namespace Ragon.Protocol
CREATE_ROOM, CREATE_ROOM,
JOIN_ROOM, JOIN_ROOM,
LEAVE_ROOM, LEAVE_ROOM,
OWNERSHIP_CHANGED, OWNERSHIP_ENTITY_CHANGED,
OWNERSHIP_ROOM_CHANGED,
JOIN_SUCCESS, JOIN_SUCCESS,
JOIN_FAILED, JOIN_FAILED,
LOAD_SCENE, LOAD_SCENE,
@@ -34,9 +35,11 @@ namespace Ragon.Protocol
PLAYER_JOINED, PLAYER_JOINED,
PLAYER_LEAVED, PLAYER_LEAVED,
CREATE_ENTITY, CREATE_ENTITY,
DESTROY_ENTITY, REMOVE_ENTITY,
SNAPSHOT, SNAPSHOT,
REPLICATE_ENTITY_STATE, REPLICATE_ENTITY_STATE,
REPLICATE_ENTITY_EVENT, REPLICATE_ENTITY_EVENT,
TRANSFER_ROOM_OWNERSHIP,
TRANSFER_ENTITY_OWNERSHIP,
} }
} }
+1 -1
View File
@@ -21,7 +21,7 @@ namespace Ragon.Protocol
{ {
public static uint Parse(string version) public static uint Parse(string version)
{ {
var strings = version.Split("."); var strings = version.Split('.');
if (strings.Length < 3) if (strings.Length < 3)
return 0; return 0;
+1 -1
View File
@@ -24,7 +24,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ragon.Server.ENet\Ragon.Server.ENet.csproj" /> <ProjectReference Include="..\Ragon.Server.ENet\Ragon.Server.ENet.csproj" />
<ProjectReference Include="..\Ragon.Server.DotNetWebSockets\Ragon.Server.DotNetWebSockets.csproj" /> <ProjectReference Include="..\Ragon.Server.WebSocketServer\Ragon.Server.WebSocketServer.csproj" />
<ProjectReference Include="..\Ragon.Server\Ragon.Server.csproj" /> <ProjectReference Include="..\Ragon.Server\Ragon.Server.csproj" />
</ItemGroup> </ItemGroup>
@@ -0,0 +1,6 @@
namespace Ragon.Relay;
public class KickPlayerCommand
{
public string Id;
}
@@ -17,8 +17,9 @@
using NLog; using NLog;
using Ragon.Server; using Ragon.Server;
using Ragon.Server.ENet; using Ragon.Server.ENet;
using Ragon.Server.DotNetWebsockets; using Ragon.Server.WebSocketServer;
using Ragon.Server.IO;
using Ragon.Server.Plugin;
namespace Ragon.Relay; namespace Ragon.Relay;
@@ -29,24 +30,22 @@ public class Relay
var logger = LogManager.GetLogger("Ragon.Relay"); var logger = LogManager.GetLogger("Ragon.Relay");
logger.Info("Relay Application"); logger.Info("Relay Application");
var configuration = Configuration.Load("relay.config.json"); var configuration = RagonServerConfiguration.Load("relay.config.json");
var serverType = Configuration.GetServerType(configuration.ServerType); var serverType = RagonServerConfiguration.GetServerType(configuration.ServerType);
INetworkServer server = null; INetworkServer networkServer = new ENetServer();
IServerPlugin plugin = new RelayServerPlugin();
switch (serverType) switch (serverType)
{ {
case ServerType.ENET: case ServerType.ENET:
server = new ENetServer(); networkServer = new ENetServer();
break; break;
case ServerType.WEBSOCKET: case ServerType.WEBSOCKET:
server = new DotNetWebSocketServer(); networkServer = new WebSocketServer();
break;
default:
server = new ENetServer();
break; break;
} }
var relay = new RagonServer(server, configuration); var relay = new RagonServer(networkServer, plugin, configuration);
logger.Info("Started"); logger.Info("Started");
relay.Start(); relay.Start();
} }
+37
View File
@@ -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;
}
}
+24
View File
@@ -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<KickPlayerCommand>(payload);
var player = Server.GetPlayerById(commandPayload.Id);
if (player != null)
player.Connection.Close();
else
Console.WriteLine($"Player not found with Id {commandPayload.Id}");
}
return true;
}
}
+12 -2
View File
@@ -3,8 +3,18 @@
"serverType": "enet", "serverType": "enet",
"serverTickRate": 30, "serverTickRate": 30,
"gameProtocol": "1.0.0", "gameProtocol": "1.0.0",
"port": 5001, "port": 5000,
"httpPort": 5001,
"httpKey": "defaultkey",
"limitConnections": 4095, "limitConnections": 4095,
"limitPlayersPerRoom": 20, "limitPlayersPerRoom": 20,
"limitRooms": 200 "limitRooms": 200,
"limitBufferedEvents": 50,
"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"
}
} }
+2 -2
View File
@@ -1,15 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<RootNamespace>Ragon.ENet</RootNamespace> <RootNamespace>Ragon.ENet</RootNamespace>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ENet-CSharp" Version="2.4.8" /> <PackageReference Include="ENet-CSharp" Version="2.4.8" />
<PackageReference Include="NLog" Version="5.1.1" /> <PackageReference Include="NLog" Version="5.2.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -15,6 +15,7 @@
*/ */
using ENet; using ENet;
using Ragon.Server.IO;
namespace Ragon.Server.ENet; namespace Ragon.Server.ENet;
@@ -23,11 +24,19 @@ public sealed class ENetConnection: INetworkConnection
public ushort Id { get; } public ushort Id { get; }
public INetworkChannel Reliable { get; private set; } public INetworkChannel Reliable { get; private set; }
public INetworkChannel Unreliable { get; private set; } public INetworkChannel Unreliable { get; private set; }
private Peer _peer;
public ENetConnection(Peer peer) public ENetConnection(Peer peer)
{ {
_peer = peer;
Id = (ushort) peer.ID; Id = (ushort) peer.ID;
Reliable = new ENetReliableChannel(peer, 0); Reliable = new ENetReliableChannel(peer, 0);
Unreliable = new ENetUnreliableChannel(peer, 1); Unreliable = new ENetUnreliableChannel(peer, 1);
} }
public void Close()
{
_peer.Disconnect(0);
}
} }
@@ -15,6 +15,7 @@
*/ */
using ENet; using ENet;
using Ragon.Server.IO;
namespace Ragon.Server.ENet; namespace Ragon.Server.ENet;
+1
View File
@@ -17,6 +17,7 @@
using ENet; using ENet;
using NLog; using NLog;
using Ragon.Protocol; using Ragon.Protocol;
using Ragon.Server.IO;
namespace Ragon.Server.ENet namespace Ragon.Server.ENet
{ {
@@ -15,6 +15,7 @@
*/ */
using ENet; using ENet;
using Ragon.Server.IO;
namespace Ragon.Server.ENet; namespace Ragon.Server.ENet;
@@ -1,14 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<RootNamespace>Ragon.WebSockets</RootNamespace> <RootNamespace>Ragon.WebSockets</RootNamespace>
<TargetFrameworks>net7.0;net6.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="NLog" Version="5.1.1" /> <PackageReference Include="NLog" Version="5.2.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -14,10 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
using System.Net.WebSockets;
using NLog; using NLog;
using System.Net.WebSockets;
using Ragon.Server.IO;
namespace Ragon.Server.DotNetWebsockets; namespace Ragon.Server.WebSocketServer;
public sealed class WebSocketConnection : INetworkConnection public sealed class WebSocketConnection : INetworkConnection
{ {
@@ -43,6 +44,11 @@ public sealed class WebSocketConnection : INetworkConnection
Unreliable = unreliableChannel; Unreliable = unreliableChannel;
} }
public void Close()
{
Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
}
public async Task Flush() public async Task Flush()
{ {
foreach (var channel in _channels) foreach (var channel in _channels)
@@ -15,9 +15,9 @@
*/ */
using System.Net.WebSockets; using System.Net.WebSockets;
using Ragon.Server; using Ragon.Server.IO;
namespace Ragon.Server.DotNetWebsockets; namespace Ragon.Server.WebSocketServer;
public class WebSocketReliableChannel : INetworkChannel public class WebSocketReliableChannel : INetworkChannel
{ {
@@ -18,10 +18,11 @@ using System.Net;
using System.Net.WebSockets; using System.Net.WebSockets;
using NLog; using NLog;
using Ragon.Protocol; using Ragon.Protocol;
using Ragon.Server.IO;
namespace Ragon.Server.DotNetWebsockets; namespace Ragon.Server.WebSocketServer;
public class DotNetWebSocketServer : INetworkServer public class WebSocketServer : INetworkServer
{ {
public Executor Executor => _executor; public Executor Executor => _executor;
@@ -34,7 +35,7 @@ public class DotNetWebSocketServer : INetworkServer
private List<WebSocketConnection> _activeConnections; private List<WebSocketConnection> _activeConnections;
private CancellationTokenSource _cancellationTokenSource; private CancellationTokenSource _cancellationTokenSource;
public DotNetWebSocketServer() public WebSocketServer()
{ {
_sequencer = new Stack<ushort>(); _sequencer = new Stack<ushort>();
_connections = Array.Empty<WebSocketConnection>(); _connections = Array.Empty<WebSocketConnection>();
+12 -3
View File
@@ -1,15 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<RootNamespace>Ragon.Core</RootNamespace> <RootNamespace>Ragon.Core</RootNamespace>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>1.2.4-rc</Version>
<Title>Ragon.Server</Title>
<Copyright>Eduard Kargin</Copyright>
<PackageProjectUrl>https://ragon-server.com</PackageProjectUrl>
<RepositoryUrl>https://github.com/edmand46/Ragon</RepositoryUrl>
<RepositoryType>Source</RepositoryType>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<LangVersion>10</LangVersion>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.2-beta2" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.1.1" /> <PackageReference Include="NLog" Version="5.2.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -0,0 +1,16 @@
using Ragon.Protocol;
using Ragon.Server.Room;
namespace Ragon.Server.Entity;
public interface IRagonEntity
{
public ushort Id { get; }
public ushort Type { get; }
public ushort StaticId { get; }
public ushort AttachId { get; }
public RagonRoomPlayer Owner { get; }
public RagonAuthority Authority { get; }
public RagonPayload Payload { get; }
public IRagonEntityState State { get; }
}
@@ -0,0 +1,8 @@
using Ragon.Protocol;
namespace Ragon.Server.Entity;
public interface IRagonEntityState
{
}
+45 -15
View File
@@ -16,10 +16,11 @@
using Ragon.Protocol; using Ragon.Protocol;
using Ragon.Server.Room;
namespace Ragon.Server; namespace Ragon.Server.Entity;
public class RagonEntity public class RagonEntity : IRagonEntity
{ {
private static ushort _idGenerator = 100; private static ushort _idGenerator = 100;
public ushort Id { get; private set; } public ushort Id { get; private set; }
@@ -29,30 +30,36 @@ public class RagonEntity
public RagonRoomPlayer Owner { get; private set; } public RagonRoomPlayer Owner { get; private set; }
public RagonAuthority Authority { get; private set; } public RagonAuthority Authority { get; private set; }
public RagonPayload Payload { get; private set; } public RagonPayload Payload { get; private set; }
public RagonEntityState State { get; private set; } public IRagonEntityState State => _state;
private readonly List<RagonEvent> _bufferedEvents; private readonly List<RagonEvent> _bufferedEvents;
private readonly int _limitBufferedEvents;
private readonly RagonEntityState _state;
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++; Id = _idGenerator++;
Authority = eventAuthority;
State = new RagonEntityState(this); StaticId = parameters.StaticId;
Type = parameters.Type;
AttachId = parameters.AttachId;
Authority = parameters.Authority;
Payload = new RagonPayload(); Payload = new RagonPayload();
_state = new RagonEntityState(this);
_bufferedEvents = new List<RagonEvent>(); _bufferedEvents = new List<RagonEvent>();
_limitBufferedEvents = parameters.BufferedEvents;
} }
public void Attach(RagonRoomPlayer owner)
public void SetOwner(RagonRoomPlayer owner)
{ {
Owner = owner; Owner = owner;
} }
public void Detach()
{
}
public void RestoreBufferedEvents(RagonRoomPlayer roomPlayer, RagonBuffer writer) public void RestoreBufferedEvents(RagonRoomPlayer roomPlayer, RagonBuffer writer)
{ {
foreach (var evnt in _bufferedEvents) foreach (var evnt in _bufferedEvents)
@@ -96,7 +103,7 @@ public class RagonEntity
var buffer = room.Writer; var buffer = room.Writer;
buffer.Clear(); buffer.Clear();
buffer.WriteOperation(RagonOperation.DESTROY_ENTITY); buffer.WriteOperation(RagonOperation.REMOVE_ENTITY);
buffer.WriteUShort(Id); buffer.WriteUShort(Id);
Payload.Write(buffer); Payload.Write(buffer);
@@ -116,7 +123,8 @@ public class RagonEntity
buffer.WriteUShort(Payload.Size); buffer.WriteUShort(Payload.Size);
Payload.Write(buffer); Payload.Write(buffer);
State.Snapshot(buffer);
_state.Snapshot(buffer);
} }
public void ReplicateEvent( public void ReplicateEvent(
@@ -156,7 +164,9 @@ public class RagonEntity
return; return;
} }
if (eventMode == RagonReplicationMode.Buffered && targetMode != RagonTarget.Owner) if (eventMode == RagonReplicationMode.Buffered &&
targetMode != RagonTarget.Owner &&
_bufferedEvents.Count < _limitBufferedEvents)
{ {
_bufferedEvents.Add(evnt); _bufferedEvents.Add(evnt);
} }
@@ -209,4 +219,24 @@ public class RagonEntity
} }
} }
} }
public void AddProperty(RagonProperty property)
{
_state.AddProperty(property);
}
public void WriteState(RagonBuffer writer)
{
_state.Write(writer);
}
public bool TryReadState(RagonRoomPlayer player, RagonBuffer reader)
{
if (Owner.Connection.Id != player.Connection.Id)
return false;
_state.Read(reader);
return true;
}
} }
@@ -14,37 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
using Ragon.Client;
using Ragon.Protocol; using Ragon.Protocol;
using Xunit.Abstractions;
namespace Ragon.Core.Tests; namespace Ragon.Server.Entity;
public class RagonXUnitLogger: IRagonLogger public ref struct RagonEntityParameters
{ {
private ITestOutputHelper _outputHelper; public ushort Type;
public RagonXUnitLogger(ITestOutputHelper outputHelper) public ushort StaticId;
{ public ushort AttachId;
_outputHelper = outputHelper; public RagonAuthority Authority;
} public int BufferedEvents;
public void Warn(string message)
{
_outputHelper.WriteLine($"[Warn] {message}");
}
public void Trace(string message)
{
_outputHelper.WriteLine($"[Trace] {message}");
}
public void Info(string message)
{
_outputHelper.WriteLine($"[Info] {message}");
}
public void Error(string message)
{
_outputHelper.WriteLine($"[Error] {message}");
}
} }
@@ -17,17 +17,19 @@
using Ragon.Protocol; using Ragon.Protocol;
namespace Ragon.Server; namespace Ragon.Server.Entity;
public class RagonEntityState public class RagonEntityState: IRagonEntityState
{ {
private List<RagonProperty> _properties; private List<RagonProperty> _properties;
private RagonEntity _entity; private RagonEntity _entity;
private RagonBuffer _buffer;
public RagonEntityState(RagonEntity entity, int capacity = 10) public RagonEntityState(RagonEntity entity, int capacity = 10)
{ {
_entity = entity; _entity = entity;
_properties = new List<RagonProperty>(10); _buffer = new RagonBuffer(8);
_properties = new List<RagonProperty>(capacity);
} }
public void AddProperty(RagonProperty property) public void AddProperty(RagonProperty property)
@@ -65,8 +67,8 @@ public class RagonEntityState
{ {
foreach (var property in _properties) foreach (var property in _properties)
{ {
var hasPayload = property.IsFixed || !property.IsFixed && property.Size > 0; var hasPayloadOrFixed = property.IsFixed || property is { IsFixed: false, Size: > 0 };
if (hasPayload) if (hasPayloadOrFixed)
{ {
buffer.WriteBool(true); buffer.WriteBool(true);
property.Write(buffer); property.Write(buffer);
+2 -2
View File
@@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
using Ragon.Protocol; using Ragon.Protocol;
using Ragon.Server.Room;
namespace Ragon.Server; namespace Ragon.Server.Entity;
public class RagonEvent public class RagonEvent
{ {
+1 -1
View File
@@ -17,7 +17,7 @@
using Ragon.Protocol; using Ragon.Protocol;
namespace Ragon.Server; namespace Ragon.Server.Entity;
public class RagonPayload public class RagonPayload
{ {
+1 -3
View File
@@ -14,11 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
using System.ComponentModel;
using Ragon.Protocol; using Ragon.Protocol;
namespace Ragon.Server; namespace Ragon.Server.Entity;
public class RagonProperty : RagonPayload public class RagonProperty : RagonPayload
{ {
@@ -16,40 +16,97 @@
using NLog; using NLog;
using Ragon.Protocol; 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 public sealed class AuthorizationOperation: IRagonOperation
{ {
private Logger _logger = LogManager.GetCurrentClassLogger(); private Logger _logger = LogManager.GetCurrentClassLogger();
private readonly RagonWebHookPlugin _ragonWebHook;
private readonly RagonContextObserver _contextObserver;
private readonly RagonBuffer _writer;
public AuthorizationOperation(
RagonWebHookPlugin ragonWebHook,
RagonContextObserver contextObserver,
RagonBuffer writer)
{
_ragonWebHook = ragonWebHook;
_contextObserver = contextObserver;
_writer = writer;
}
public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer 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; return;
} }
var key = reader.ReadString(); if (context.ConnectionStatus == ConnectionStatus.InProcess)
var playerName = reader.ReadString(); {
var additionalPayload = new RagonPayload(); _logger.Warn("Player already request authorization!");
additionalPayload.Read(reader); return;
}
context.LobbyPlayer.Name = playerName; var configuration = context.Configuration;
context.LobbyPlayer.AdditionalData = Array.Empty<byte>(); var key = reader.ReadString();
context.LobbyPlayer.Status = LobbyPlayerStatus.Authorized; 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);
}
}
public void Approve(RagonContext context)
{
context.ConnectionStatus = ConnectionStatus.Authorized;
_contextObserver.OnAuthorized(context);
var playerId = context.LobbyPlayer.Id; var playerId = context.LobbyPlayer.Id;
var playerName = context.LobbyPlayer.Name;
var playerPayload = context.LobbyPlayer.Payload;
writer.Clear(); _writer.Clear();
writer.WriteOperation(RagonOperation.AUTHORIZED_SUCCESS); _writer.WriteOperation(RagonOperation.AUTHORIZED_SUCCESS);
writer.WriteString(playerId); _writer.WriteString(playerId);
writer.WriteString(playerName); _writer.WriteString(playerName);
_writer.WriteString(playerPayload);
var sendData = writer.ToArray(); var sendData = _writer.ToArray();
context.Connection.Reliable.Send(sendData); 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}");
}
} }
@@ -16,12 +16,13 @@
using NLog; using NLog;
using Ragon.Protocol; using Ragon.Protocol;
using Ragon.Server.Entity;
namespace Ragon.Server; namespace Ragon.Server.Handler;
public sealed class EntityCreateOperation : IRagonOperation 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) public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer)
{ {
@@ -32,18 +33,32 @@ public sealed class EntityCreateOperation : IRagonOperation
var eventAuthority = (RagonAuthority) reader.ReadByte(); var eventAuthority = (RagonAuthority) reader.ReadByte();
var propertiesCount = reader.ReadUShort(); 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,
BufferedEvents = context.Configuration.LimitBufferedEvents,
};
var entity = new RagonEntity(entityParameters);
for (var i = 0; i < propertiesCount; i++) for (var i = 0; i < propertiesCount; i++)
{ {
var propertyType = reader.ReadBool(); var propertyType = reader.ReadBool();
var propertySize = reader.ReadUShort(); var propertySize = reader.ReadUShort();
entity.State.AddProperty(new RagonProperty(propertySize, propertyType)); entity.AddProperty(new RagonProperty(propertySize, propertyType));
} }
if (reader.Capacity > 0) if (reader.Capacity > 0)
entity.Payload.Read(reader); entity.Payload.Read(reader);
var plugin = room.Plugin;
if (!plugin.OnEntityCreate(player, entity))
return;
entity.Attach(player);
room.AttachEntity(entity); room.AttachEntity(entity);
player.AttachEntity(entity); player.AttachEntity(entity);
@@ -16,12 +16,13 @@
using NLog; using NLog;
using Ragon.Protocol; using Ragon.Protocol;
using Ragon.Server.Entity;
namespace Ragon.Server; namespace Ragon.Server.Handler;
public sealed class EntityEventOperation : IRagonOperation 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) public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer)
{ {
@@ -43,16 +44,15 @@ public sealed class EntityEventOperation : IRagonOperation
if (targetMode == RagonTarget.Player) if (targetMode == RagonTarget.Player)
targetPlayerPeerId = reader.ReadUShort(); targetPlayerPeerId = reader.ReadUShort();
var ragonEvent = new RagonEvent(player, eventId); var @event = new RagonEvent(player, eventId);
ragonEvent.Read(reader); @event.Read(reader);
if (targetMode == RagonTarget.Player && if (targetMode == RagonTarget.Player && room.Players.TryGetValue(targetPlayerPeerId, out var targetPlayer))
context.Room.Players.TryGetValue(targetPlayerPeerId, out var targetPlayer))
{ {
ent.ReplicateEvent(player, ragonEvent, eventMode, targetPlayer); ent.ReplicateEvent(player, @event, eventMode, targetPlayer);
return; return;
} }
ent.ReplicateEvent(player, ragonEvent, eventMode, targetMode); ent.ReplicateEvent(player, @event, eventMode, targetMode);
} }
} }
@@ -0,0 +1,53 @@
using NLog;
using Ragon.Protocol;
namespace Ragon.Server.Handler;
public sealed class EntityOwnershipOperation : IRagonOperation
{
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer)
{
var currentOwner = context.RoomPlayer;
var room = context.Room;
var entityId = reader.ReadUShort();
var playerPeerId = reader.ReadUShort();
if (!room.Entities.TryGetValue(entityId, out var entity))
{
_logger.Error($"Entity not found with id {entityId}");
return;
}
if (entity.Owner.Connection.Id != currentOwner.Connection.Id)
{
_logger.Error($"Player not owner of entity with id {entityId}");
return;
}
if (!room.Players.TryGetValue(playerPeerId, out var nextOwner))
{
_logger.Error($"Player not found with id {entityId}");
return;
}
currentOwner.Entities.Remove(entity);
nextOwner.Entities.Add(entity);
entity.Attach(nextOwner);
_logger.Trace($"Entity {entity.Id} next owner {nextOwner.Connection.Id}");
writer.Clear();
writer.WriteOperation(RagonOperation.OWNERSHIP_ENTITY_CHANGED);
writer.WriteUShort(playerPeerId);
writer.WriteUShort(1);
writer.WriteUShort(entity.Id);
var sendData = writer.ToArray();
foreach (var player in room.PlayerList)
player.Connection.Reliable.Send(sendData);
}
}
@@ -16,12 +16,13 @@
using NLog; using NLog;
using Ragon.Protocol; using Ragon.Protocol;
using Ragon.Server.Entity;
namespace Ragon.Server; namespace Ragon.Server.Handler;
public sealed class EntityDestroyOperation: IRagonOperation 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) public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer)
{ {
@@ -17,23 +17,23 @@
using NLog; using NLog;
using Ragon.Protocol; using Ragon.Protocol;
namespace Ragon.Server; namespace Ragon.Server.Handler;
public sealed class EntityStateOperation: IRagonOperation 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) public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer)
{ {
var room = context.Room; var room = context.Room;
var player = context.RoomPlayer;
var entitiesCount = reader.ReadUShort(); var entitiesCount = reader.ReadUShort();
for (var entityIndex = 0; entityIndex < entitiesCount; entityIndex++) for (var entityIndex = 0; entityIndex < entitiesCount; entityIndex++)
{ {
var entityId = reader.ReadUShort(); var entityId = reader.ReadUShort();
if (room.Entities.TryGetValue(entityId, out var entity)) if (room.Entities.TryGetValue(entityId, out var entity) && entity.TryReadState(player, reader))
{ {
entity.State.Read(reader);
room.Track(entity); room.Track(entity);
} }
else else
@@ -16,7 +16,7 @@
using Ragon.Protocol; using Ragon.Protocol;
namespace Ragon.Server; namespace Ragon.Server.Handler;
public interface IRagonOperation public interface IRagonOperation
{ {
@@ -16,17 +16,29 @@
using NLog; using NLog;
using Ragon.Protocol; 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 public sealed class RoomCreateOperation: IRagonOperation
{ {
private RagonRoomParameters _roomParameters = new(); private readonly RagonRoomParameters _roomParameters = new();
private Logger _logger = LogManager.GetCurrentClassLogger(); 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) 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"); _logger.Warn($"Player {context.Connection.Id} not authorized for this request");
return; return;
@@ -62,17 +74,22 @@ public sealed class RoomCreateOperation: IRagonOperation
}; };
var lobbyPlayer = context.LobbyPlayer; 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.Scheduler.Run(room);
context.Lobby.Persist(room); context.Lobby.Persist(room);
context.SetRoom(room, roomPlayer);
var player = new RagonRoomPlayer(lobbyPlayer.Connection, lobbyPlayer.Id, lobbyPlayer.Name); _ragonWebHookPlugin.RoomCreated(context, room, roomPlayer);
context.SetRoom(room, player);
_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}"); _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(room.Id);
writer.WriteString(player.Id); writer.WriteString(player.Id);
writer.WriteString(room.Owner.Id); writer.WriteString(room.Owner.Id);
writer.WriteUShort((ushort) room.Info.Min); writer.WriteUShort((ushort) room.PlayerMin);
writer.WriteUShort((ushort) room.Info.Max); writer.WriteUShort((ushort) room.PlayerMax);
writer.WriteString(room.Info.Map); writer.WriteString(room.Map);
var sendData = writer.ToArray(); var sendData = writer.ToArray();
player.Connection.Reliable.Send(sendData); player.Connection.Reliable.Send(sendData);
@@ -16,12 +16,22 @@
using NLog; using NLog;
using Ragon.Protocol; 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 public sealed class RoomJoinOperation : IRagonOperation
{ {
private Logger _logger = LogManager.GetCurrentClassLogger(); private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private readonly RagonWebHookPlugin _webHook;
public RoomJoinOperation(RagonWebHookPlugin plugin)
{
_webHook = plugin;
}
public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer)
{ {
@@ -30,42 +40,47 @@ public sealed class RoomJoinOperation : IRagonOperation
if (!context.Lobby.FindRoomById(roomId, out var existsRoom)) 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}"); _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} failed to join room {roomId}");
return; 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); context.SetRoom(existsRoom, player);
JoinSuccess(player, existsRoom, writer); if (!existsRoom.Plugin.OnPlayerJoined(player))
return;
_webHook.RoomJoined(context, existsRoom, player);
JoinSuccess(context, existsRoom, writer);
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} joined to {existsRoom.Id}"); _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.Clear();
writer.WriteOperation(RagonOperation.JOIN_SUCCESS); writer.WriteOperation(RagonOperation.JOIN_SUCCESS);
writer.WriteString(room.Id); writer.WriteString(room.Id);
writer.WriteString(player.Id); writer.WriteString(context.RoomPlayer.Id);
writer.WriteString(room.Owner.Id); writer.WriteString(room.Owner.Id);
writer.WriteUShort((ushort) room.Info.Min); writer.WriteUShort((ushort) room.PlayerMin);
writer.WriteUShort((ushort) room.Info.Max); writer.WriteUShort((ushort) room.PlayerMax);
writer.WriteString(room.Info.Map); writer.WriteString(room.Map);
var sendData = writer.ToArray(); 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.Clear();
writer.WriteOperation(RagonOperation.JOIN_FAILED); writer.WriteOperation(RagonOperation.JOIN_FAILED);
writer.WriteString($"Room not exists"); writer.WriteString($"Room not exists");
var sendData = writer.ToArray(); var sendData = writer.ToArray();
player.Connection.Reliable.Send(sendData); context.Connection.Reliable.Send(sendData);
} }
} }
@@ -16,17 +16,29 @@
using NLog; using NLog;
using Ragon.Protocol; 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 public sealed class RoomJoinOrCreateOperation : IRagonOperation
{ {
private RagonRoomParameters _roomParameters = new(); private readonly RagonRoomParameters _roomParameters = new();
private Logger _logger = LogManager.GetCurrentClassLogger(); 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) 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"); _logger.Warn("Player not authorized for this request");
return; return;
@@ -39,9 +51,11 @@ public sealed class RoomJoinOrCreateOperation : IRagonOperation
if (context.Lobby.FindRoomByMap(_roomParameters.Map, out var existsRoom)) 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); context.SetRoom(existsRoom, player);
_ragonWebHookPlugin.RoomJoined(context, existsRoom, player);
JoinSuccess(player, existsRoom, writer); JoinSuccess(player, existsRoom, writer);
} }
else else
@@ -53,14 +67,17 @@ public sealed class RoomJoinOrCreateOperation : IRagonOperation
Min = _roomParameters.Min, 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.Lobby.Persist(room);
context.Scheduler.Run(room); context.Scheduler.Run(room);
var roomPlayer = new RagonRoomPlayer(lobbyPlayer.Connection, lobbyPlayer.Id, lobbyPlayer.Name);
context.SetRoom(room, roomPlayer); 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); JoinSuccess(roomPlayer, room, writer);
} }
@@ -73,9 +90,9 @@ public sealed class RoomJoinOrCreateOperation : IRagonOperation
writer.WriteString(room.Id); writer.WriteString(room.Id);
writer.WriteString(player.Id); writer.WriteString(player.Id);
writer.WriteString(room.Owner.Id); writer.WriteString(room.Owner.Id);
writer.WriteUShort((ushort) room.Info.Min); writer.WriteUShort((ushort) room.PlayerMin);
writer.WriteUShort((ushort) room.Info.Max); writer.WriteUShort((ushort) room.PlayerMax);
writer.WriteString(room.Info.Map); writer.WriteString(room.Map);
var sendData = writer.ToArray(); var sendData = writer.ToArray();
player.Connection.Reliable.Send(sendData); player.Connection.Reliable.Send(sendData);
@@ -16,19 +16,34 @@
using NLog; using NLog;
using Ragon.Protocol; using Ragon.Protocol;
using Ragon.Server.Plugin;
using Ragon.Server.Plugin.Web;
namespace Ragon.Server; namespace Ragon.Server.Handler;
public sealed class RoomLeaveOperation: IRagonOperation public sealed class RoomLeaveOperation: IRagonOperation
{ {
private Logger _logger = LogManager.GetCurrentClassLogger(); private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private readonly RagonWebHookPlugin _webHook;
public RoomLeaveOperation(RagonWebHookPlugin plugin)
{
_webHook = plugin;
}
public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer)
{ {
var room = context.Room; var room = context.Room;
var roomPlayer = context.RoomPlayer; var roomPlayer = context.RoomPlayer;
if (room != null) if (room != null)
{ {
context.Room?.DetachPlayer(roomPlayer); var plugin = room.Plugin;
plugin.OnPlayerLeaved(roomPlayer);
room.DetachPlayer(roomPlayer);
_webHook.RoomLeaved(context, room, roomPlayer);
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} leaved from {room.Id}"); _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} leaved from {room.Id}");
} }
} }
@@ -0,0 +1,14 @@
using NLog;
using Ragon.Protocol;
using Ragon.Server.Entity;
namespace Ragon.Server.Handler;
public sealed class RoomOwnershipOperation : IRagonOperation
{
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer)
{
}
}
@@ -18,11 +18,11 @@
using NLog; using NLog;
using Ragon.Protocol; using Ragon.Protocol;
namespace Ragon.Server; namespace Ragon.Server.Handler;
public class SceneLoadOperation: IRagonOperation public class SceneLoadOperation: IRagonOperation
{ {
private Logger _logger = LogManager.GetCurrentClassLogger(); private readonly Logger _logger = LogManager.GetCurrentClassLogger();
public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer)
{ {
@@ -16,16 +16,24 @@
using NLog; using NLog;
using Ragon.Protocol; 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 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) public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer)
{ {
if (context.LobbyPlayer.Status == LobbyPlayerStatus.Unauthorized) if (context.ConnectionStatus == ConnectionStatus.Unauthorized)
return; return;
var owner = context.Room.Owner; var owner = context.Room.Owner;
@@ -34,6 +42,7 @@ public sealed class SceneLoadedOperation : IRagonOperation
if (player == owner) if (player == owner)
{ {
var statics = reader.ReadUShort(); var statics = reader.ReadUShort();
for (var staticIndex = 0; staticIndex < statics; staticIndex++) for (var staticIndex = 0; staticIndex < statics; staticIndex++)
{ {
@@ -42,19 +51,31 @@ public sealed class SceneLoadedOperation : IRagonOperation
var staticId = reader.ReadUShort(); var staticId = reader.ReadUShort();
var propertiesCount = 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++) for (var propertyIndex = 0; propertyIndex < propertiesCount; propertyIndex++)
{ {
var propertyType = reader.ReadBool(); var propertyType = reader.ReadBool();
var propertySize = reader.ReadUShort(); var propertySize = reader.ReadUShort();
entity.State.AddProperty(new RagonProperty(propertySize, propertyType)); entity.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 playerInfo = $"Player {context.Connection.Id}|{context.LobbyPlayer.Name}";
var entityInfo = $"{entity.Id}:{entity.Type}"; var entityInfo = $"{entity.Id}:{entity.Type}";
_logger.Trace($"{playerInfo} created entity {entityInfo}"); _logger.Trace($"{playerInfo} created entity {entityInfo}");
entity.Attach(player);
room.AttachEntity(entity); room.AttachEntity(entity);
player.AttachEntity(entity); player.AttachEntity(entity);
} }
@@ -0,0 +1,111 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* 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(RagonServerConfiguration 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();
}
}
+14 -9
View File
@@ -16,23 +16,28 @@
using System.Threading.Channels; using System.Threading.Channels;
namespace Ragon.Server; namespace Ragon.Server.IO;
public class Executor: TaskScheduler, IExecutor public class Executor : TaskScheduler, IExecutor
{ {
private ChannelReader<Task> _reader; private readonly ChannelReader<Task> _reader;
private ChannelWriter<Task> _writer; private readonly ChannelWriter<Task> _writer;
private Queue<Task> _pendingTasks; private readonly Queue<Task> _pendingTasks;
private TaskFactory _taskFactory; 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() public Executor()
{ {
var channel = Channel.CreateUnbounded<Task>(); var channel = Channel.CreateUnbounded<Task>(new UnboundedChannelOptions()
{
SingleReader = true,
SingleWriter = true,
});
_reader = channel.Reader; _reader = channel.Reader;
_writer = channel.Writer; _writer = channel.Writer;
+2 -2
View File
@@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
namespace Ragon.Server; namespace Ragon.Server.IO;
public interface IExecutor public interface IExecutor
{ {
public void Run(Action action); public Task Run(Action action, TaskCreationOptions task = TaskCreationOptions.None);
} }
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
namespace Ragon.Server; namespace Ragon.Server.IO;
public interface INetworkChannel public interface INetworkChannel
{ {
@@ -14,11 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
namespace Ragon.Server; namespace Ragon.Server.IO;
public interface INetworkConnection public interface INetworkConnection
{ {
public ushort Id { get; } public ushort Id { get; }
public INetworkChannel Reliable { get; } public INetworkChannel Reliable { get; }
public INetworkChannel Unreliable { get; } public INetworkChannel Unreliable { get; }
public void Close();
} }
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
namespace Ragon.Server; namespace Ragon.Server.IO;
public interface INetworkListener public interface INetworkListener
{ {
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
namespace Ragon.Server; namespace Ragon.Server.IO;
public interface INetworkServer public interface INetworkServer
{ {
@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
namespace Ragon.Server; namespace Ragon.Server.IO;
public struct NetworkConfiguration public struct NetworkConfiguration
{ {
@@ -0,0 +1,31 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* 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<string, RagonContext> _contexts;
public RagonContextObserver(Dictionary<string, RagonContext> contexts)
{
_contexts = contexts;
}
public void OnAuthorized(RagonContext context)
{
_contexts.Add(context.LobbyPlayer.Id, context);
}
}
+26
View File
@@ -0,0 +1,26 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* 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;
namespace Ragon.Server;
public interface IRagonServer
{
RagonLobbyPlayer? GetPlayerByConnection(INetworkConnection connection);
RagonLobbyPlayer? GetPlayerById(string id);
}
+3 -2
View File
@@ -15,13 +15,14 @@
*/ */
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Ragon.Server.Room;
namespace Ragon.Server; namespace Ragon.Server.Lobby;
public interface IRagonLobby public interface IRagonLobby
{ {
public bool FindRoomById(string roomId, [MaybeNullWhen(false)] out RagonRoom room); public bool FindRoomById(string roomId, [MaybeNullWhen(false)] out RagonRoom room);
public bool FindRoomByMap(string map, [MaybeNullWhen(false)] out RagonRoom room); public bool FindRoomByMap(string map, [MaybeNullWhen(false)] out RagonRoom room);
public void Persist(RagonRoom room); public void Persist(RagonRoom room);
public void RemoveIfEmpty(RagonRoom room); public bool RemoveIfEmpty(RagonRoom room);
} }
@@ -16,8 +16,9 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using NLog; using NLog;
using Ragon.Server.Room;
namespace Ragon.Server; namespace Ragon.Server.Lobby;
public class LobbyInMemory : IRagonLobby public class LobbyInMemory : IRagonLobby
{ {
@@ -28,8 +29,7 @@ public class LobbyInMemory : IRagonLobby
{ {
foreach (var existRagonRoom in _rooms) foreach (var existRagonRoom in _rooms)
{ {
var info = existRagonRoom.Info; if (existRagonRoom.Id == RagonRoomId && existRagonRoom.PlayerMin < existRagonRoom.PlayerMax)
if (existRagonRoom.Id == RagonRoomId && info.Min < info.Max)
{ {
room = existRagonRoom; room = existRagonRoom;
return true; return true;
@@ -44,8 +44,7 @@ public class LobbyInMemory : IRagonLobby
{ {
foreach (var existsRoom in _rooms) foreach (var existsRoom in _rooms)
{ {
var info = existsRoom.Info; if (existsRoom.Map == map && existsRoom.PlayerCount < existsRoom.PlayerMax)
if (info.Map == map && existsRoom.Players.Count < info.Max)
{ {
room = existsRoom; room = existsRoom;
return true; return true;
@@ -62,18 +61,23 @@ public class LobbyInMemory : IRagonLobby
_logger.Trace($"New room: {room.Id}"); _logger.Trace($"New room: {room.Id}");
foreach (var r in _rooms) 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) if (room.Players.Count == 0)
{ {
_rooms.Remove(room); _rooms.Remove(room);
_logger.Trace($"Room {room.Id} removed"); _logger.Trace($"Room {room.Id} removed");
result = true;
} }
foreach (var r in _rooms) 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;
} }
} }
+12 -11
View File
@@ -14,28 +14,29 @@
* limitations under the License. * limitations under the License.
*/ */
namespace Ragon.Server; using Ragon.Server.IO;
public enum LobbyPlayerStatus namespace Ragon.Server.Lobby;
public enum ConnectionStatus
{ {
Unauthorized, Unauthorized,
InProcess,
Authorized, Authorized,
} }
public class RagonLobbyPlayer public class RagonLobbyPlayer
{ {
public INetworkConnection Connection { get; }
public string Id { get; private set; } public string Id { get; private set; }
public string Name { get; set; } public string Name { get; private set; }
public byte[] AdditionalData { get; set; } public string Payload { get; private set; }
public LobbyPlayerStatus Status { get; set; }
public INetworkConnection Connection { 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; Connection = connection;
Status = LobbyPlayerStatus.Unauthorized;
Name = "None";
AdditionalData = Array.Empty<byte>();
} }
} }
@@ -0,0 +1,67 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* 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
{
public IRagonRoom Room { get; private set; }
public virtual void OnAttached(IRagonRoom room)
{
Room = room;
}
public virtual void OnDetached()
{
}
#region VIRTUAL
public virtual bool OnPlayerJoined(RagonRoomPlayer player)
{
return true;
}
public virtual bool OnPlayerLeaved(RagonRoomPlayer player)
{
return true;
}
public virtual void Tick(float dt)
{
}
public virtual bool OnEntityCreate(RagonRoomPlayer creator, IRagonEntity entity)
{
return true;
}
public virtual bool OnEntityRemove(RagonRoomPlayer remover, IRagonEntity entity)
{
return true;
}
#endregion
}
@@ -0,0 +1,56 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* 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
{
public IRagonServer Server { get; protected set; }
public virtual void OnAttached(IRagonServer server)
{
Server = server;
}
public virtual 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 OnCommand(string command, string payload)
{
return true;
}
public IRoomPlugin CreateRoomPlugin(RoomInformation information)
{
return new BaseRoomPlugin();
}
}
@@ -0,0 +1,31 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* 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 OnPlayerJoined(RagonRoomPlayer player);
bool OnPlayerLeaved(RagonRoomPlayer player);
bool OnEntityCreate(RagonRoomPlayer player, IRagonEntity entity);
bool OnEntityRemove(RagonRoomPlayer player, IRagonEntity entity);
}
@@ -0,0 +1,31 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* 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 OnCommand(string command, string payload);
IRoomPlugin CreateRoomPlugin(RoomInformation information);
}
@@ -0,0 +1,32 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* 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;
}
}
@@ -0,0 +1,39 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* 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();
}
}
@@ -0,0 +1,135 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* 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<string, string> _webHooks;
private RagonServer _server;
private HttpClient _httpClient;
public RagonWebHookPlugin(RagonServer server, RagonServerConfiguration configuration)
{
_webHooks = new Dictionary<string, string>(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<AuthorizationResponse>(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));
}
}
}
@@ -14,4 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
global using Xunit; namespace Ragon.Server.Plugin.Web;
[Serializable]
public class AuthorizationRequest
{
public string Name;
public string Token;
}
@@ -0,0 +1,24 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* 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; }
}
@@ -0,0 +1,23 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* 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; }
}
@@ -0,0 +1,24 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* 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; }
}
@@ -14,10 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
namespace Ragon.Client;
public enum DisconnectReason namespace Ragon.Server.Plugin.Web;
[Serializable]
public class RoomRemovedRequest
{ {
MANUAL, public RoomDto Room { get; set; }
TIMEOUT,
} }
@@ -0,0 +1,25 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* 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;
}
+20 -6
View File
@@ -14,31 +14,45 @@
* limitations under the License. * limitations under the License.
*/ */
using Ragon.Core.Time; using Ragon.Server.IO;
using Ragon.Server; using Ragon.Server.Lobby;
using Ragon.Server.Time;
using Ragon.Server.Room;
namespace Ragon.Server; namespace Ragon.Server;
public class RagonContext public class RagonContext
{ {
public ConnectionStatus ConnectionStatus { get; set; }
public INetworkConnection Connection { get; } public INetworkConnection Connection { get; }
public IExecutor Executor { get; private set; } public IExecutor Executor { get; private set; }
public RagonServerConfiguration Configuration { get; private set; }
public IRagonLobby Lobby { 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 RagonRoom? Room { get; private set; }
public RagonRoomPlayer? RoomPlayer { get; private set; } public RagonRoomPlayer? RoomPlayer { get; private set; }
public RagonScheduler Scheduler { 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,
RagonServerConfiguration configuration,
IExecutor executor,
IRagonLobby lobby,
RagonScheduler scheduler)
{ {
ConnectionStatus = ConnectionStatus.Unauthorized;
Configuration = configuration;
Connection = connection; Connection = connection;
Executor = executor; Executor = executor;
Lobby = lobby; Lobby = lobby;
Scheduler = scheduler; Scheduler = scheduler;
LobbyPlayer = lobbyPlayer; }
internal void SetPlayer(RagonLobbyPlayer player)
{
LobbyPlayer = player;
} }
internal void SetRoom(RagonRoom room, RagonRoomPlayer player) internal void SetRoom(RagonRoom room, RagonRoomPlayer player)
+2
View File
@@ -14,6 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
using Ragon.Server.Entity;
namespace Ragon.Server; namespace Ragon.Server;
public class RagonEntityCache public class RagonEntityCache

Some files were not shown because too many files have changed in this diff Show More