wip
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
using System.Diagnostics;
|
||||
using NLog;
|
||||
using Ragon.Common;
|
||||
using Ragon.Core.Lobby;
|
||||
using Ragon.Core.Time;
|
||||
using Ragon.Server;
|
||||
using Ragon.Server.ENet;
|
||||
|
||||
namespace Ragon.Core;
|
||||
|
||||
public class Application : INetworkListener
|
||||
{
|
||||
private Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private INetworkServer _server;
|
||||
private Thread _dedicatedThread;
|
||||
private Configuration _configuration;
|
||||
private HandlerRegistry _handlerRegistry;
|
||||
private ILobby _lobby;
|
||||
private Scheduler _scheduler;
|
||||
private Dictionary<ushort, PlayerContext> _contexts = new();
|
||||
|
||||
public Application(Configuration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_dedicatedThread = new Thread(Execute);
|
||||
_dedicatedThread.IsBackground = true;
|
||||
|
||||
_handlerRegistry = new HandlerRegistry();
|
||||
_lobby = new LobbyInMemory();
|
||||
_scheduler = new Scheduler();
|
||||
|
||||
if (configuration.Socket == "enet")
|
||||
_server = new ENetServer();
|
||||
|
||||
Debug.Assert(_server != null, $"Socket type not supported: {configuration.Socket}. Supported: [enet, websocket]");
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
_scheduler.Tick();
|
||||
_server.Poll();
|
||||
|
||||
Thread.Sleep((int) 1000.0f / _configuration.SendRate);
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
var networkConfiguration = new NetworkConfiguration()
|
||||
{
|
||||
LimitConnections = _configuration.LimitConnections,
|
||||
Protocol = RagonVersion.Parse(_configuration.Protocol),
|
||||
Address = "0.0.0.0",
|
||||
Port = _configuration.Port,
|
||||
};
|
||||
|
||||
_server.Start(this, networkConfiguration);
|
||||
_dedicatedThread.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_server.Stop();
|
||||
_dedicatedThread.Interrupt();
|
||||
}
|
||||
|
||||
public void OnConnected(INetworkConnection connection)
|
||||
{
|
||||
var context = new PlayerContext(connection, new LobbyPlayer(connection));
|
||||
context.Lobby = _lobby;
|
||||
context.Scheduler = _scheduler;
|
||||
|
||||
_logger.Trace($"Connected {connection.Id}");
|
||||
_contexts.Add(connection.Id, context);
|
||||
}
|
||||
|
||||
public void OnDisconnected(INetworkConnection connection)
|
||||
{
|
||||
_logger.Trace($"Disconnected {connection.Id}");
|
||||
|
||||
if (_contexts.Remove(connection.Id, out var context))
|
||||
{
|
||||
context.Room?.RemovePlayer(context.RoomPlayer);
|
||||
context.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnTimeout(INetworkConnection connection)
|
||||
{
|
||||
if (_contexts.Remove(connection.Id, out var context))
|
||||
context.Dispose();
|
||||
}
|
||||
|
||||
public void OnData(INetworkConnection connection, byte[] data)
|
||||
{
|
||||
if (_contexts.TryGetValue(connection.Id, out var context))
|
||||
_handlerRegistry.Handle(context, data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
|
||||
namespace Ragon.Core;
|
||||
|
||||
[Serializable]
|
||||
public struct Configuration
|
||||
{
|
||||
public string Key;
|
||||
public string Protocol;
|
||||
public string Socket;
|
||||
public ushort SendRate;
|
||||
public ushort Port;
|
||||
public int SkipTimeout;
|
||||
public int ReconnectTimeout;
|
||||
public int LimitConnections;
|
||||
public int LimitPlayersPerRoom;
|
||||
public int LimitRooms;
|
||||
|
||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||
private static readonly string ServerVersion = "1.1.0-rc";
|
||||
|
||||
private static void CopyrightInfo()
|
||||
{
|
||||
Logger.Info($"Server Version: {ServerVersion}");
|
||||
Logger.Info($"Machine Name: {Environment.MachineName}");
|
||||
Logger.Info($"OS: {Environment.OSVersion}");
|
||||
Logger.Info($"Processors: {Environment.ProcessorCount}");
|
||||
Logger.Info($"Runtime Version: {Environment.Version}");
|
||||
Logger.Info("==================================");
|
||||
Logger.Info("| |");
|
||||
Logger.Info("| Ragon |");
|
||||
Logger.Info("| |");
|
||||
Logger.Info("==================================");
|
||||
}
|
||||
|
||||
public static Configuration Load(string filePath)
|
||||
{
|
||||
CopyrightInfo();
|
||||
|
||||
var data = File.ReadAllText(filePath);
|
||||
var configuration = JsonConvert.DeserializeObject<Configuration>(data);
|
||||
return configuration;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using NLog.LayoutRenderers.Wrappers;
|
||||
|
||||
namespace Ragon.Core;
|
||||
|
||||
public class Executor: TaskScheduler
|
||||
{
|
||||
private ChannelReader<Task> _reader;
|
||||
private ChannelWriter<Task> _writer;
|
||||
private Queue<Task> _pendingTasks;
|
||||
|
||||
public void Run(Task task)
|
||||
{
|
||||
task.Start(this);
|
||||
}
|
||||
|
||||
public Executor()
|
||||
{
|
||||
var channel = Channel.CreateUnbounded<Task>();
|
||||
_reader = channel.Reader;
|
||||
_writer = channel.Writer;
|
||||
|
||||
_pendingTasks = new Queue<Task>();
|
||||
}
|
||||
|
||||
protected override IEnumerable<Task>? GetScheduledTasks()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
protected override void QueueTask(Task task)
|
||||
{
|
||||
_writer.TryWrite(task);
|
||||
}
|
||||
|
||||
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
while (_reader.TryRead(out var task))
|
||||
{
|
||||
TryExecuteTask(task);
|
||||
|
||||
if (task.Status == TaskStatus.Running)
|
||||
_pendingTasks.Enqueue(task);
|
||||
}
|
||||
|
||||
while (_pendingTasks.TryDequeue(out var task))
|
||||
_writer.TryWrite(task);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
using Ragon.Common;
|
||||
|
||||
namespace Ragon.Core.Game;
|
||||
|
||||
public class Entity
|
||||
{
|
||||
private static ushort _idGenerator = 0;
|
||||
|
||||
public ushort Id { get; private set; }
|
||||
public RoomPlayer Owner { get; private set; }
|
||||
public RagonAuthority Authority { get; private set; }
|
||||
public EntityState State { get; private set; }
|
||||
public byte[] Payload { get; private set; }
|
||||
public ushort StaticId { get; private set; }
|
||||
public ushort Type { get; private set; }
|
||||
|
||||
private List<EntityEvent> _bufferedEvents;
|
||||
|
||||
public Entity(RoomPlayer owner, ushort type, ushort staticId, RagonAuthority eventAuthority)
|
||||
{
|
||||
Owner = owner;
|
||||
StaticId = staticId;
|
||||
Type = type;
|
||||
Id = _idGenerator++;
|
||||
Payload = Array.Empty<byte>();
|
||||
Authority = eventAuthority;
|
||||
State = new EntityState(this);
|
||||
|
||||
_bufferedEvents = new List<EntityEvent>();
|
||||
}
|
||||
|
||||
public void SetPayload(byte[] payload)
|
||||
{
|
||||
Payload = payload;
|
||||
}
|
||||
|
||||
public void SetOwner(RoomPlayer owner)
|
||||
{
|
||||
Owner = owner;
|
||||
}
|
||||
|
||||
public void RestoreBufferedEvents(RoomPlayer roomPlayer, RagonSerializer writer)
|
||||
{
|
||||
foreach (var bufferedEvent in _bufferedEvents)
|
||||
{
|
||||
writer.Clear();
|
||||
writer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
|
||||
writer.WriteUShort(bufferedEvent.EventId);
|
||||
writer.WriteUShort(bufferedEvent.PeerId);
|
||||
writer.WriteByte((byte) RagonReplicationMode.Server);
|
||||
writer.WriteUShort(Id);
|
||||
|
||||
ReadOnlySpan<byte> data = bufferedEvent.EventData.AsSpan();
|
||||
writer.WriteData(ref data);
|
||||
|
||||
var sendData = writer.ToArray();
|
||||
roomPlayer.Connection.ReliableChannel.Send(sendData);
|
||||
}
|
||||
}
|
||||
|
||||
public void Create()
|
||||
{
|
||||
var room = Owner.Room;
|
||||
var serializer = room.Writer;
|
||||
|
||||
serializer.Clear();
|
||||
serializer.WriteOperation(RagonOperation.CREATE_ENTITY);
|
||||
serializer.WriteUShort(Type);
|
||||
serializer.WriteUShort(Id);
|
||||
serializer.WriteUShort(Owner.Connection.Id);
|
||||
|
||||
ReadOnlySpan<byte> entityPayload = Payload.AsSpan();
|
||||
serializer.WriteUShort((ushort) entityPayload.Length);
|
||||
serializer.WriteData(ref entityPayload);
|
||||
|
||||
var sendData = serializer.ToArray();
|
||||
foreach (var player in room.ReadyPlayersList)
|
||||
player.Connection.ReliableChannel.Send(sendData);
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
var room = Owner.Room;
|
||||
var serializer = room.Writer;
|
||||
|
||||
serializer.Clear();
|
||||
serializer.WriteOperation(RagonOperation.DESTROY_ENTITY);
|
||||
serializer.WriteInt(Id);
|
||||
serializer.WriteUShort(0);
|
||||
// serializer.WriteData(ref Payload);
|
||||
|
||||
var sendData = serializer.ToArray();
|
||||
foreach (var player in room.ReadyPlayersList)
|
||||
player.Connection.ReliableChannel.Send(sendData);
|
||||
}
|
||||
|
||||
public void ReplicateEvent(
|
||||
RoomPlayer caller,
|
||||
ushort eventId,
|
||||
ReadOnlySpan<byte> payload,
|
||||
RagonReplicationMode eventMode,
|
||||
RoomPlayer targetPlayer
|
||||
)
|
||||
{
|
||||
var room = Owner.Room;
|
||||
var serializer = room.Writer;
|
||||
|
||||
serializer.Clear();
|
||||
serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
|
||||
serializer.WriteUShort(eventId);
|
||||
serializer.WriteUShort(caller.Connection.Id);
|
||||
serializer.WriteByte((byte) eventMode);
|
||||
serializer.WriteUShort(Id);
|
||||
serializer.WriteData(ref payload);
|
||||
|
||||
var sendData = serializer.ToArray();
|
||||
targetPlayer.Connection.ReliableChannel.Send(sendData);
|
||||
}
|
||||
|
||||
public void ReplicateEvent(
|
||||
RoomPlayer caller,
|
||||
ushort eventId,
|
||||
ReadOnlySpan<byte> payload,
|
||||
RagonReplicationMode eventMode,
|
||||
RagonTarget targetMode
|
||||
)
|
||||
{
|
||||
if (Authority == RagonAuthority.OwnerOnly &&
|
||||
Owner.Connection.Id != caller.Connection.Id)
|
||||
{
|
||||
Console.WriteLine($"Player have not enought authority for event with Id {eventId}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (eventMode == RagonReplicationMode.Buffered && targetMode != RagonTarget.Owner)
|
||||
{
|
||||
var bufferedEvent = new EntityEvent()
|
||||
{
|
||||
EventData = payload.ToArray(),
|
||||
Target = targetMode,
|
||||
EventId = eventId,
|
||||
PeerId = caller.Connection.Id,
|
||||
};
|
||||
_bufferedEvents.Add(bufferedEvent);
|
||||
}
|
||||
|
||||
var room = Owner.Room;
|
||||
var serializer = room.Writer;
|
||||
|
||||
serializer.Clear();
|
||||
serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
|
||||
serializer.WriteUShort(eventId);
|
||||
serializer.WriteUShort(caller.Connection.Id);
|
||||
serializer.WriteByte((byte) eventMode);
|
||||
serializer.WriteUShort(Id);
|
||||
serializer.WriteData(ref payload);
|
||||
|
||||
var sendData = serializer.ToArray();
|
||||
|
||||
switch (targetMode)
|
||||
{
|
||||
case RagonTarget.Owner:
|
||||
{
|
||||
Owner.Connection.ReliableChannel.Send(sendData);
|
||||
break;
|
||||
}
|
||||
case RagonTarget.ExceptOwner:
|
||||
{
|
||||
foreach (var roomPlayer in room.ReadyPlayersList)
|
||||
{
|
||||
if (roomPlayer.Connection.Id != Owner.Connection.Id)
|
||||
roomPlayer.Connection.ReliableChannel.Send(sendData);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case RagonTarget.ExceptInvoker:
|
||||
{
|
||||
foreach (var roomPlayer in room.ReadyPlayersList)
|
||||
{
|
||||
if (roomPlayer.Connection.Id != caller.Connection.Id)
|
||||
roomPlayer.Connection.ReliableChannel.Send(sendData);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case RagonTarget.All:
|
||||
{
|
||||
foreach (var roomPlayer in room.ReadyPlayersList)
|
||||
roomPlayer.Connection.ReliableChannel.Send(sendData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Ragon.Common;
|
||||
|
||||
namespace Ragon.Core.Game;
|
||||
|
||||
public class EntityEvent
|
||||
{
|
||||
public ushort PeerId { get; set; }
|
||||
public ushort EventId { get; set; }
|
||||
public byte[] EventData { get; set; }
|
||||
public RagonTarget Target { set; get; }
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
namespace Ragon.Core.Game;
|
||||
|
||||
public class EntityList
|
||||
{
|
||||
private List<Entity> _dynamicEntitiesList = new List<Entity>();
|
||||
private List<Entity> _staticEntitesList = new List<Entity>();
|
||||
private Dictionary<ushort, Entity> _entitiesMap = new Dictionary<ushort, Entity>();
|
||||
|
||||
public IReadOnlyList<Entity> StaticList => _staticEntitesList;
|
||||
public IReadOnlyList<Entity> DynamicList => _dynamicEntitiesList;
|
||||
public IReadOnlyDictionary<ushort, Entity> Map => _entitiesMap;
|
||||
|
||||
public void Add(Entity entity)
|
||||
{
|
||||
if (entity.StaticId != 0)
|
||||
_staticEntitesList.Add(entity);
|
||||
else
|
||||
_dynamicEntitiesList.Add(entity);
|
||||
|
||||
_entitiesMap.Add(entity.Id, entity);
|
||||
}
|
||||
|
||||
public Entity Remove(Entity entity)
|
||||
{
|
||||
if (_entitiesMap.Remove(entity.Id, out var existEntity))
|
||||
{
|
||||
_staticEntitesList.Remove(entity);
|
||||
_dynamicEntitiesList.Remove(entity);
|
||||
|
||||
return existEntity;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using NLog;
|
||||
using Ragon.Common;
|
||||
|
||||
namespace Ragon.Core.Game;
|
||||
|
||||
public class EntityState
|
||||
{
|
||||
private Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
private List<EntityStateProperty> _properties;
|
||||
private Entity _entity;
|
||||
|
||||
public EntityState(Entity entity, int capacity = 10)
|
||||
{
|
||||
_entity = entity;
|
||||
_properties = new List<EntityStateProperty>(10);
|
||||
}
|
||||
|
||||
public void AddProperty(EntityStateProperty property)
|
||||
{
|
||||
_properties.Add(property);
|
||||
}
|
||||
|
||||
public void Write(RagonSerializer serializer)
|
||||
{
|
||||
serializer.WriteUShort(_entity.Id);
|
||||
|
||||
for (int propertyIndex = 0; propertyIndex < _properties.Count; propertyIndex++)
|
||||
{
|
||||
var property = _properties[propertyIndex];
|
||||
if (property.IsDirty)
|
||||
{
|
||||
serializer.WriteBool(true);
|
||||
var span = serializer.GetWritableData(property.Size);
|
||||
var data = property.Read();
|
||||
data.CopyTo(span);
|
||||
property.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
serializer.WriteBool(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Read(RagonSerializer serializer)
|
||||
{
|
||||
for (var i = 0; i < _properties.Count; i++)
|
||||
{
|
||||
if (serializer.ReadBool())
|
||||
{
|
||||
var property = _properties[i];
|
||||
var size = property.Size;
|
||||
if (!property.IsFixed)
|
||||
size = serializer.ReadUShort();
|
||||
|
||||
if (size > property.Capacity)
|
||||
{
|
||||
Console.WriteLine($"Property {i} payload too large, size: {size}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var propertyPayload = serializer.ReadData(size);
|
||||
property.Write(ref propertyPayload);
|
||||
property.Size = size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Snapshot(RagonSerializer serializer)
|
||||
{
|
||||
ReadOnlySpan<byte> payload = _entity.Payload.AsSpan();
|
||||
|
||||
serializer.WriteUShort(_entity.Type);
|
||||
serializer.WriteUShort(_entity.Id);
|
||||
|
||||
if (_entity.StaticId != 0)
|
||||
serializer.WriteUShort(_entity.StaticId);
|
||||
|
||||
serializer.WriteUShort(_entity.Owner.Connection.Id);
|
||||
serializer.WriteUShort((ushort) payload.Length);
|
||||
serializer.WriteData(ref payload);
|
||||
|
||||
for (int propertyIndex = 0; propertyIndex < _properties.Count; propertyIndex++)
|
||||
{
|
||||
var property = _properties[propertyIndex];
|
||||
var hasPayload = property.IsFixed || property.Size > 0 && !property.IsFixed;
|
||||
if (hasPayload)
|
||||
{
|
||||
serializer.WriteBool(true);
|
||||
var span = serializer.GetWritableData(property.Size);
|
||||
var data = property.Read();
|
||||
data.CopyTo(span);
|
||||
}
|
||||
else
|
||||
{
|
||||
serializer.WriteBool(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using Ragon.Common;
|
||||
|
||||
namespace Ragon.Core.Game;
|
||||
|
||||
public class EntityStateProperty
|
||||
{
|
||||
public int Size { get; set; }
|
||||
public int Capacity { get; set; }
|
||||
public bool IsDirty { get; private set; }
|
||||
public bool IsFixed { get; private set; }
|
||||
private byte[] _data;
|
||||
|
||||
public EntityStateProperty(int size, bool isFixed)
|
||||
{
|
||||
Capacity = 512;
|
||||
Size = size;
|
||||
IsFixed = isFixed;
|
||||
IsDirty = true;
|
||||
|
||||
_data = new byte[Capacity];
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> Read()
|
||||
{
|
||||
var dataSpan = _data.AsSpan();
|
||||
|
||||
return dataSpan.Slice(0, Size);
|
||||
}
|
||||
|
||||
public void Write(ref ReadOnlySpan<byte> src)
|
||||
{
|
||||
src.CopyTo(_data);
|
||||
IsDirty = true;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
IsDirty = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
using Ragon.Common;
|
||||
|
||||
namespace Ragon.Core.Game;
|
||||
|
||||
public class Room
|
||||
{
|
||||
public string Id { get; }
|
||||
public RoomInformation Info { get; }
|
||||
public RoomPlayer Owner { get; set; }
|
||||
|
||||
public Dictionary<ushort, RoomPlayer> Players { get; }
|
||||
public List<RoomPlayer> WaitPlayersList { get; private set; }
|
||||
public List<RoomPlayer> ReadyPlayersList { get; private set; }
|
||||
public List<RoomPlayer> PlayerList { get; private set; }
|
||||
|
||||
public Dictionary<ushort, Entity> Entities { get; private set; }
|
||||
public List<Entity> DynamicEntitiesList { get; private set; }
|
||||
public List<Entity> StaticEntitiesList { get; private set; }
|
||||
public List<Entity> EntityList { get; private set; }
|
||||
|
||||
private HashSet<Entity> _entitiesDirtySet;
|
||||
private RagonSerializer _writer;
|
||||
|
||||
public RagonSerializer Writer => _writer;
|
||||
public PluginBase Plugin { get; set; }
|
||||
|
||||
public Room(string roomId, RoomInformation info, PluginBase plugin)
|
||||
{
|
||||
Id = roomId;
|
||||
Info = info;
|
||||
|
||||
Plugin = plugin;
|
||||
|
||||
Players = new Dictionary<ushort, RoomPlayer>(info.Max);
|
||||
WaitPlayersList = new List<RoomPlayer>(info.Max);
|
||||
ReadyPlayersList = new List<RoomPlayer>(info.Max);
|
||||
PlayerList = new List<RoomPlayer>(info.Max);
|
||||
|
||||
Entities = new Dictionary<ushort, Entity>();
|
||||
DynamicEntitiesList = new List<Entity>();
|
||||
StaticEntitiesList = new List<Entity>();
|
||||
EntityList = new List<Entity>();
|
||||
|
||||
_entitiesDirtySet = new HashSet<Entity>();
|
||||
_writer = new RagonSerializer(512);
|
||||
}
|
||||
|
||||
public void AttachEntity(RoomPlayer newOwner, Entity entity)
|
||||
{
|
||||
Entities.Add(entity.Id, entity);
|
||||
EntityList.Add(entity);
|
||||
|
||||
if (entity.StaticId == 0)
|
||||
DynamicEntitiesList.Add(entity);
|
||||
else
|
||||
StaticEntitiesList.Add(entity);
|
||||
|
||||
entity.Create();
|
||||
|
||||
newOwner.Entities.Add(entity);
|
||||
}
|
||||
|
||||
public void DetachEntity(RoomPlayer currentOwner, Entity entity, byte[] payload)
|
||||
{
|
||||
Entities.Remove(entity.Id);
|
||||
EntityList.Remove(entity);
|
||||
StaticEntitiesList.Remove(entity);
|
||||
DynamicEntitiesList.Remove(entity);
|
||||
_entitiesDirtySet.Remove(entity);
|
||||
|
||||
entity.Destroy();
|
||||
currentOwner.Entities.Remove(entity);
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
var entities = (ushort) _entitiesDirtySet.Count;
|
||||
if (entities > 0)
|
||||
{
|
||||
_writer.Clear();
|
||||
_writer.WriteOperation(RagonOperation.REPLICATE_ENTITY_STATE);
|
||||
_writer.WriteUShort(entities);
|
||||
|
||||
foreach (var entity in _entitiesDirtySet)
|
||||
entity.State.Write(_writer);
|
||||
|
||||
_entitiesDirtySet.Clear();
|
||||
|
||||
var sendData = _writer.ToArray();
|
||||
foreach (var roomPlayer in ReadyPlayersList)
|
||||
roomPlayer.Connection.UnreliableChannel.Send(sendData);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddPlayer(RoomPlayer player)
|
||||
{
|
||||
if (Players.Count == 0)
|
||||
Owner = player;
|
||||
|
||||
player.Attach(this);
|
||||
|
||||
PlayerList.Add(player);
|
||||
Players.Add(player.Connection.Id, player);
|
||||
}
|
||||
|
||||
public void RemovePlayer(RoomPlayer roomPlayer)
|
||||
{
|
||||
if (Players.Remove(roomPlayer.Connection.Id, out var player))
|
||||
{
|
||||
PlayerList.Remove(player);
|
||||
|
||||
{
|
||||
_writer.Clear();
|
||||
_writer.WriteOperation(RagonOperation.PLAYER_LEAVED);
|
||||
_writer.WriteString(player.Id);
|
||||
|
||||
var entitiesToDelete = player.Entities.DynamicList;
|
||||
_writer.WriteUShort((ushort) entitiesToDelete.Count);
|
||||
foreach (var entity in entitiesToDelete)
|
||||
{
|
||||
_writer.WriteUShort(entity.Id);
|
||||
EntityList.Remove(entity);
|
||||
}
|
||||
|
||||
var sendData = _writer.ToArray();
|
||||
Broadcast(sendData);
|
||||
}
|
||||
|
||||
if (roomPlayer.Connection.Id == Owner.Connection.Id && PlayerList.Count > 0)
|
||||
{
|
||||
var nextOwner = PlayerList[0];
|
||||
|
||||
Owner = nextOwner;
|
||||
|
||||
var entitiesToUpdate = roomPlayer.Entities.StaticList;
|
||||
|
||||
_writer.Clear();
|
||||
_writer.WriteOperation(RagonOperation.OWNERSHIP_CHANGED);
|
||||
_writer.WriteString(Owner.Id);
|
||||
_writer.WriteUShort((ushort) entitiesToUpdate.Count);
|
||||
|
||||
foreach (var entity in entitiesToUpdate)
|
||||
{
|
||||
_writer.WriteUShort(entity.Id);
|
||||
|
||||
entity.SetOwner(nextOwner);
|
||||
nextOwner.Entities.Add(entity);
|
||||
}
|
||||
|
||||
var sendData = _writer.ToArray();
|
||||
Broadcast(sendData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateReadyPlayerList()
|
||||
{
|
||||
ReadyPlayersList = PlayerList.Where(p => p.IsLoaded).ToList();
|
||||
}
|
||||
|
||||
public void Track(Entity entity)
|
||||
{
|
||||
_entitiesDirtySet.Add(entity);
|
||||
}
|
||||
|
||||
public void Broadcast(byte[] data)
|
||||
{
|
||||
foreach (var readyPlayer in ReadyPlayersList)
|
||||
readyPlayer.Connection.ReliableChannel.Send(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Ragon.Core.Game;
|
||||
|
||||
public class RoomInformation
|
||||
{
|
||||
public string Map { get; set; }
|
||||
public int Min { get; set; }
|
||||
public int Max { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Map: {Map} Count: {Min}/{Max}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using Ragon.Server;
|
||||
|
||||
namespace Ragon.Core.Game;
|
||||
|
||||
public class RoomPlayer
|
||||
{
|
||||
public INetworkConnection Connection { get; }
|
||||
public string Id { get; }
|
||||
public string Name { get; }
|
||||
public bool IsLoaded { get; private set; }
|
||||
public Room Room { get; private set; }
|
||||
public EntityList Entities { get; private set; }
|
||||
|
||||
public RoomPlayer(INetworkConnection connection, string id, string name)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
Connection = connection;
|
||||
Entities = new EntityList();
|
||||
}
|
||||
|
||||
public void Attach(Room room)
|
||||
{
|
||||
Room = room;
|
||||
}
|
||||
|
||||
public void Detach()
|
||||
{
|
||||
Room = null!;
|
||||
}
|
||||
|
||||
public void SetReady()
|
||||
{
|
||||
IsLoaded = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using NLog;
|
||||
using Ragon.Common;
|
||||
using Ragon.Core.Handlers;
|
||||
|
||||
namespace Ragon.Core;
|
||||
|
||||
public sealed class HandlerRegistry
|
||||
{
|
||||
private IHandler _entityEventHandler;
|
||||
private IHandler _entityCreateHandler;
|
||||
private IHandler _entityDestroyHandler;
|
||||
private IHandler _entityStateHandler;
|
||||
private IHandler _sceneLoadedHandler;
|
||||
|
||||
private IHandler _authorizationHandler;
|
||||
private IHandler _joinOrCreateHandler;
|
||||
private IHandler _createHandler;
|
||||
private IHandler _joinHandler;
|
||||
private IHandler _leaveHandler;
|
||||
|
||||
private Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
private RagonSerializer _reader;
|
||||
private RagonSerializer _writer;
|
||||
|
||||
public HandlerRegistry()
|
||||
{
|
||||
_reader = new RagonSerializer(2048);
|
||||
_writer = new RagonSerializer(2048);
|
||||
|
||||
_authorizationHandler = new AuthHandler();
|
||||
_joinOrCreateHandler = new JoinOrCreateHandler();
|
||||
_sceneLoadedHandler = new SceneLoadedHandler();
|
||||
_createHandler = new CreateHandler();
|
||||
_joinHandler = new JoinHandler();
|
||||
_leaveHandler = new LeaveHandler();
|
||||
|
||||
_entityEventHandler = new EntityEventHandler();
|
||||
_entityCreateHandler = new EntityCreateHandler();
|
||||
_entityDestroyHandler = new EntityDestroyHandler();
|
||||
_entityStateHandler = new EntityStateHandler();
|
||||
}
|
||||
|
||||
public void Handle(PlayerContext context, byte[] data)
|
||||
{
|
||||
_writer.Clear();
|
||||
_reader.Clear();
|
||||
_reader.FromArray(data);
|
||||
|
||||
var operation = _reader.ReadOperation();
|
||||
switch (operation)
|
||||
{
|
||||
case RagonOperation.REPLICATE_ENTITY_EVENT:
|
||||
{
|
||||
if (context.RoomPlayer != null)
|
||||
_entityEventHandler.Handle(context, _reader, _writer);
|
||||
break;
|
||||
}
|
||||
case RagonOperation.REPLICATE_ENTITY_STATE:
|
||||
{
|
||||
if (context.RoomPlayer != null)
|
||||
_entityStateHandler.Handle(context, _reader, _writer);
|
||||
break;
|
||||
}
|
||||
case RagonOperation.CREATE_ENTITY:
|
||||
{
|
||||
if (context.RoomPlayer != null)
|
||||
_entityCreateHandler.Handle(context, _reader, _writer);
|
||||
break;
|
||||
}
|
||||
case RagonOperation.DESTROY_ENTITY:
|
||||
{
|
||||
if (context.RoomPlayer != null)
|
||||
_entityDestroyHandler.Handle(context, _reader, _writer);
|
||||
break;
|
||||
}
|
||||
case RagonOperation.SCENE_LOADED:
|
||||
{
|
||||
if (context.RoomPlayer != null)
|
||||
_sceneLoadedHandler.Handle(context, _reader, _writer);
|
||||
break;
|
||||
}
|
||||
case RagonOperation.JOIN_OR_CREATE_ROOM:
|
||||
{
|
||||
_joinOrCreateHandler.Handle(context, _reader, _writer);
|
||||
break;
|
||||
}
|
||||
case RagonOperation.CREATE_ROOM:
|
||||
{
|
||||
_createHandler.Handle(context, _reader, _writer);
|
||||
break;
|
||||
}
|
||||
case RagonOperation.JOIN_ROOM:
|
||||
{
|
||||
_joinHandler.Handle(context, _reader, _writer);
|
||||
break;
|
||||
}
|
||||
case RagonOperation.LEAVE_ROOM:
|
||||
{
|
||||
_leaveHandler.Handle(context, _reader, _writer);
|
||||
break;
|
||||
}
|
||||
case RagonOperation.AUTHORIZE:
|
||||
{
|
||||
_authorizationHandler.Handle(context, _reader, _writer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Ragon.Common;
|
||||
|
||||
namespace Ragon.Core;
|
||||
|
||||
public interface IHandler
|
||||
{
|
||||
public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using NLog;
|
||||
using Ragon.Common;
|
||||
using Ragon.Core.Lobby;
|
||||
|
||||
namespace Ragon.Core.Handlers;
|
||||
|
||||
public sealed class AuthHandler: IHandler
|
||||
{
|
||||
private Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer)
|
||||
{
|
||||
if (context.LobbyPlayer.Status == LobbyPlayerStatus.Authorized)
|
||||
{
|
||||
_logger.Warn("Player already authorized");
|
||||
return;
|
||||
}
|
||||
|
||||
var key = reader.ReadString();
|
||||
var playerName = reader.ReadString();
|
||||
var additionalData = reader.ReadData(reader.Size);
|
||||
|
||||
context.LobbyPlayer.Name = playerName;
|
||||
context.LobbyPlayer.AdditionalData = additionalData.ToArray();
|
||||
context.LobbyPlayer.Status = LobbyPlayerStatus.Authorized;
|
||||
|
||||
var playerId = context.LobbyPlayer.Id;
|
||||
|
||||
writer.Clear();
|
||||
writer.WriteOperation(RagonOperation.AUTHORIZED_SUCCESS);
|
||||
writer.WriteString(playerId);
|
||||
writer.WriteString(playerName);
|
||||
|
||||
var sendData = writer.ToArray();
|
||||
context.Connection.ReliableChannel.Send(sendData);
|
||||
|
||||
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} authorized");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using NLog;
|
||||
using Ragon.Common;
|
||||
using Ragon.Core.Game;
|
||||
|
||||
|
||||
namespace Ragon.Core.Handlers;
|
||||
|
||||
public sealed class EntityCreateHandler: IHandler
|
||||
{
|
||||
private Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer)
|
||||
{
|
||||
var entityType = reader.ReadUShort();
|
||||
var eventAuthority = (RagonAuthority) reader.ReadByte();
|
||||
var propertiesCount = reader.ReadUShort();
|
||||
|
||||
var entity = new Entity(context.RoomPlayer, entityType, 0, eventAuthority);
|
||||
for (var i = 0; i < propertiesCount; i++)
|
||||
{
|
||||
var propertyType = reader.ReadBool();
|
||||
var propertySize = reader.ReadUShort();
|
||||
entity.State.AddProperty(new EntityStateProperty(propertySize, propertyType));
|
||||
}
|
||||
|
||||
var entityPayload = reader.ReadData(reader.Size);
|
||||
entity.SetPayload(entityPayload.ToArray());
|
||||
|
||||
context.Room.AttachEntity(context.RoomPlayer, entity);
|
||||
|
||||
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} created entity {entity.Id}:{entity.Type}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using NLog;
|
||||
using Ragon.Common;
|
||||
|
||||
namespace Ragon.Core.Handlers;
|
||||
|
||||
public sealed class EntityDestroyHandler: IHandler
|
||||
{
|
||||
private Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer)
|
||||
{
|
||||
var entityId = reader.ReadUShort();
|
||||
if (context.Room.Entities.TryGetValue(entityId, out var entity))
|
||||
{
|
||||
var player = context.RoomPlayer;
|
||||
var payload = reader.ReadData(reader.Size);
|
||||
|
||||
context.Room.DetachEntity(player, entity, Array.Empty<byte>());
|
||||
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} destoyed entity {entity.Id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using NLog;
|
||||
using Ragon.Common;
|
||||
|
||||
namespace Ragon.Core.Handlers;
|
||||
|
||||
public sealed class EntityEventHandler: IHandler
|
||||
{
|
||||
private Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer)
|
||||
{
|
||||
var player = context.RoomPlayer;
|
||||
var room = context.Room;
|
||||
var entityId = reader.ReadUShort();
|
||||
|
||||
if (!room.Entities.TryGetValue(entityId, out var ent))
|
||||
{
|
||||
_logger.Warn($"Entity not found for event with Id {entityId}");
|
||||
return;
|
||||
}
|
||||
|
||||
var eventId = reader.ReadUShort();
|
||||
var eventMode = (RagonReplicationMode) reader.ReadByte();
|
||||
var targetMode = (RagonTarget) reader.ReadByte();
|
||||
var payloadData = reader.ReadData(reader.Size);
|
||||
var targetPlayerPeerId = reader.ReadUShort();
|
||||
|
||||
if (targetMode == RagonTarget.Player && context.Room.Players.TryGetValue(targetPlayerPeerId, out var targetPlayer))
|
||||
{
|
||||
Span<byte> payloadRaw = stackalloc byte[payloadData.Length];
|
||||
ReadOnlySpan<byte> payload = payloadRaw;
|
||||
payloadData.CopyTo(payloadRaw);
|
||||
|
||||
_logger.Trace($"Event {eventId} Payload: {payloadData.Length} to {targetMode}");
|
||||
ent.ReplicateEvent(player, eventId, payload, eventMode, targetPlayer);
|
||||
}
|
||||
else
|
||||
{
|
||||
Span<byte> payloadRaw = stackalloc byte[payloadData.Length];
|
||||
ReadOnlySpan<byte> payload = payloadRaw;
|
||||
payloadData.CopyTo(payloadRaw);
|
||||
|
||||
_logger.Trace($"Event {eventId} Payload: {payloadData.Length} to {targetMode}");
|
||||
ent.ReplicateEvent(player, eventId, payload, eventMode, targetMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using NLog;
|
||||
using Ragon.Common;
|
||||
|
||||
namespace Ragon.Core.Handlers;
|
||||
|
||||
public sealed class EntityStateHandler: IHandler
|
||||
{
|
||||
private ILogger _logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer)
|
||||
{
|
||||
var room = context.Room;
|
||||
var entitiesCount = reader.ReadUShort();
|
||||
for (var entityIndex = 0; entityIndex < entitiesCount; entityIndex++)
|
||||
{
|
||||
var entityId = reader.ReadUShort();
|
||||
if (room.Entities.TryGetValue(entityId, out var entity))
|
||||
{
|
||||
entity.State.Read(reader);
|
||||
room.Track(entity);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error($"Entity with Id {entityId} not found, replication interrupted");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using NLog;
|
||||
using Ragon.Common;
|
||||
using Ragon.Core.Game;
|
||||
using Ragon.Core.Lobby;
|
||||
|
||||
namespace Ragon.Core.Handlers;
|
||||
|
||||
public sealed class CreateHandler: IHandler
|
||||
{
|
||||
private RagonRoomParameters _roomParameters = new();
|
||||
private Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer)
|
||||
{
|
||||
if (context.LobbyPlayer.Status == LobbyPlayerStatus.Unauthorized)
|
||||
{
|
||||
_logger.Warn($"Player {context.Connection.Id} not authorized for this request");
|
||||
return;
|
||||
}
|
||||
|
||||
var custom = reader.ReadBool();
|
||||
var roomId = Guid.NewGuid().ToString();
|
||||
|
||||
if (custom)
|
||||
{
|
||||
roomId = reader.ReadString();
|
||||
if (context.Lobby.FindRoomById(roomId, out _))
|
||||
{
|
||||
writer.Clear();
|
||||
writer.WriteOperation(RagonOperation.JOIN_FAILED);
|
||||
writer.WriteString($"Room with id {roomId} already exists");
|
||||
|
||||
var sendData = writer.ToArray();
|
||||
context.Connection.ReliableChannel.Send(sendData);
|
||||
|
||||
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} join failed to room {roomId}, room already exist");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_roomParameters.Deserialize(reader);
|
||||
|
||||
var information = new RoomInformation()
|
||||
{
|
||||
Map = _roomParameters.Map,
|
||||
Max = _roomParameters.Max,
|
||||
Min = _roomParameters.Min,
|
||||
};
|
||||
|
||||
var lobbyPlayer = context.LobbyPlayer;
|
||||
var roomPlayer = new RoomPlayer(lobbyPlayer.Connection, lobbyPlayer.Id, lobbyPlayer.Name);
|
||||
|
||||
var room = new Room(roomId, information, new PluginBase());
|
||||
room.AddPlayer(roomPlayer);
|
||||
|
||||
context.Room?.RemovePlayer(context.RoomPlayer);
|
||||
context.Room = room;
|
||||
context.RoomPlayer = roomPlayer;
|
||||
context.Lobby.Persist(room);
|
||||
|
||||
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} create room {room.Id} {information}");
|
||||
|
||||
JoinSuccess(roomPlayer, room, writer);
|
||||
|
||||
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} joined to room {room.Id}");
|
||||
}
|
||||
|
||||
private void JoinSuccess(RoomPlayer player, Room room, RagonSerializer writer)
|
||||
{
|
||||
writer.Clear();
|
||||
writer.WriteOperation(RagonOperation.JOIN_SUCCESS);
|
||||
writer.WriteString(room.Id);
|
||||
writer.WriteString(player.Id);
|
||||
writer.WriteString(room.Owner.Id);
|
||||
writer.WriteUShort((ushort) room.Info.Min);
|
||||
writer.WriteUShort((ushort) room.Info.Max);
|
||||
writer.WriteString(room.Info.Map);
|
||||
|
||||
var sendData = writer.ToArray();
|
||||
player.Connection.ReliableChannel.Send(sendData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using NLog;
|
||||
using Ragon.Common;
|
||||
using Ragon.Core.Game;
|
||||
using Ragon.Core.Lobby;
|
||||
|
||||
namespace Ragon.Core.Handlers;
|
||||
|
||||
public sealed class JoinHandler : IHandler
|
||||
{
|
||||
private Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer)
|
||||
{
|
||||
var roomId = reader.ReadString();
|
||||
var lobbyPlayer = context.LobbyPlayer;
|
||||
|
||||
if (!context.Lobby.FindRoomById(roomId, out var existsRoom))
|
||||
{
|
||||
JoinFailed(lobbyPlayer, writer);
|
||||
|
||||
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} failed to join room {roomId}");
|
||||
return;
|
||||
}
|
||||
|
||||
var roomPlayer = new RoomPlayer(lobbyPlayer.Connection, lobbyPlayer.Id, lobbyPlayer.Name);
|
||||
|
||||
context.Room?.RemovePlayer(context.RoomPlayer);
|
||||
context.Room = existsRoom;
|
||||
context.RoomPlayer = roomPlayer;
|
||||
|
||||
existsRoom.AddPlayer(roomPlayer);
|
||||
|
||||
JoinSuccess(roomPlayer, existsRoom, writer);
|
||||
|
||||
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} joined to {existsRoom.Id}");
|
||||
}
|
||||
|
||||
private void JoinSuccess(RoomPlayer player, Room room, RagonSerializer writer)
|
||||
{
|
||||
writer.Clear();
|
||||
writer.WriteOperation(RagonOperation.JOIN_SUCCESS);
|
||||
writer.WriteString(room.Id);
|
||||
writer.WriteString(player.Id);
|
||||
writer.WriteString(room.Owner.Id);
|
||||
writer.WriteUShort((ushort) room.Info.Min);
|
||||
writer.WriteUShort((ushort) room.Info.Max);
|
||||
writer.WriteString(room.Info.Map);
|
||||
|
||||
var sendData = writer.ToArray();
|
||||
player.Connection.ReliableChannel.Send(sendData);
|
||||
}
|
||||
|
||||
private void JoinFailed(LobbyPlayer player, RagonSerializer writer)
|
||||
{
|
||||
writer.Clear();
|
||||
writer.WriteOperation(RagonOperation.JOIN_FAILED);
|
||||
writer.WriteString($"Room not exists");
|
||||
|
||||
var sendData = writer.ToArray();
|
||||
player.Connection.ReliableChannel.Send(sendData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using NLog;
|
||||
using Ragon.Common;
|
||||
using Ragon.Core.Game;
|
||||
using Ragon.Core.Lobby;
|
||||
|
||||
namespace Ragon.Core.Handlers;
|
||||
|
||||
public sealed class JoinOrCreateHandler : IHandler
|
||||
{
|
||||
private RagonRoomParameters _roomParameters = new();
|
||||
private Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer)
|
||||
{
|
||||
if (context.LobbyPlayer.Status == LobbyPlayerStatus.Unauthorized)
|
||||
{
|
||||
_logger.Warn("Player not authorized for this request");
|
||||
return;
|
||||
}
|
||||
|
||||
var roomId = Guid.NewGuid().ToString();
|
||||
var lobbyPlayer = context.LobbyPlayer;
|
||||
|
||||
_roomParameters.Deserialize(reader);
|
||||
|
||||
if (context.Lobby.FindRoomByMap(_roomParameters.Map, out var existsRoom))
|
||||
{
|
||||
var roomPlayer = new RoomPlayer(lobbyPlayer.Connection, lobbyPlayer.Id, lobbyPlayer.Name);
|
||||
|
||||
context.Room?.RemovePlayer(context.RoomPlayer);
|
||||
context.Room = existsRoom;
|
||||
context.RoomPlayer = roomPlayer;
|
||||
|
||||
existsRoom.AddPlayer(roomPlayer);
|
||||
|
||||
JoinSuccess(roomPlayer, existsRoom, writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
var information = new RoomInformation()
|
||||
{
|
||||
Map = _roomParameters.Map,
|
||||
Max = _roomParameters.Max,
|
||||
Min = _roomParameters.Min,
|
||||
};
|
||||
|
||||
var room = new Room(roomId, information, new PluginBase());
|
||||
context.Lobby.Persist(room);
|
||||
|
||||
var roomPlayer = new RoomPlayer(lobbyPlayer.Connection, lobbyPlayer.Id, lobbyPlayer.Name);
|
||||
room.AddPlayer(roomPlayer);
|
||||
|
||||
context.Room?.RemovePlayer(context.RoomPlayer);
|
||||
context.Room = room;
|
||||
context.RoomPlayer = roomPlayer;
|
||||
|
||||
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} create room {room.Id} {information}");
|
||||
|
||||
JoinSuccess(roomPlayer, room, writer);
|
||||
}
|
||||
}
|
||||
|
||||
private void JoinSuccess(RoomPlayer player, Room room, RagonSerializer writer)
|
||||
{
|
||||
writer.Clear();
|
||||
writer.WriteOperation(RagonOperation.JOIN_SUCCESS);
|
||||
writer.WriteString(room.Id);
|
||||
writer.WriteString(player.Id);
|
||||
writer.WriteString(room.Owner.Id);
|
||||
writer.WriteUShort((ushort) room.Info.Min);
|
||||
writer.WriteUShort((ushort) room.Info.Max);
|
||||
writer.WriteString(room.Info.Map);
|
||||
|
||||
var sendData = writer.ToArray();
|
||||
player.Connection.ReliableChannel.Send(sendData);
|
||||
|
||||
_logger.Trace($"Joined to room {room.Id}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using NLog;
|
||||
using Ragon.Common;
|
||||
|
||||
namespace Ragon.Core.Handlers;
|
||||
|
||||
public sealed class LeaveHandler: IHandler
|
||||
{
|
||||
private Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer)
|
||||
{
|
||||
var room = context.Room;
|
||||
var roomPlayer = context.RoomPlayer;
|
||||
if (room != null)
|
||||
{
|
||||
context.Room?.RemovePlayer(roomPlayer);
|
||||
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} leaved from {room.Id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using NLog;
|
||||
using Ragon.Common;
|
||||
using Ragon.Core.Game;
|
||||
using Ragon.Core.Lobby;
|
||||
|
||||
namespace Ragon.Core.Handlers;
|
||||
|
||||
public sealed class SceneLoadedHandler : IHandler
|
||||
{
|
||||
private Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer)
|
||||
{
|
||||
if (context.LobbyPlayer.Status == LobbyPlayerStatus.Unauthorized)
|
||||
return;
|
||||
|
||||
var owner = context.Room.Owner;
|
||||
var player = context.RoomPlayer;
|
||||
var room = context.Room;
|
||||
|
||||
if (player == owner)
|
||||
{
|
||||
var statics = reader.ReadUShort();
|
||||
for (var staticIndex = 0; staticIndex < statics; staticIndex++)
|
||||
{
|
||||
var entityType = reader.ReadUShort();
|
||||
var eventAuthority = (RagonAuthority) reader.ReadByte();
|
||||
var staticId = reader.ReadUShort();
|
||||
var propertiesCount = reader.ReadUShort();
|
||||
|
||||
var entity = new Entity(player, entityType, staticId, eventAuthority);
|
||||
for (var propertyIndex = 0; propertyIndex < propertiesCount; propertyIndex++)
|
||||
{
|
||||
var propertyType = reader.ReadBool();
|
||||
var propertySize = reader.ReadUShort();
|
||||
entity.State.AddProperty(new EntityStateProperty(propertySize, propertyType));
|
||||
}
|
||||
|
||||
room.AttachEntity(player, entity);
|
||||
}
|
||||
|
||||
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} loaded with {statics} scene entities");
|
||||
|
||||
room.WaitPlayersList.Add(player);
|
||||
|
||||
foreach (var roomPlayer in room.WaitPlayersList)
|
||||
{
|
||||
DispatchPlayerJoinExcludePlayer(room, roomPlayer, writer);
|
||||
|
||||
roomPlayer.SetReady();
|
||||
}
|
||||
|
||||
room.UpdateReadyPlayerList();
|
||||
|
||||
DispatchSnapshot(room, room.WaitPlayersList, writer);
|
||||
|
||||
room.WaitPlayersList.Clear();
|
||||
}
|
||||
else if (owner.IsLoaded)
|
||||
{
|
||||
player.SetReady();
|
||||
|
||||
DispatchPlayerJoinExcludePlayer(room, player, writer);
|
||||
|
||||
room.UpdateReadyPlayerList();
|
||||
|
||||
DispatchSnapshot(room, new List<RoomPlayer>() { player }, writer);
|
||||
|
||||
foreach (var entity in room.EntityList)
|
||||
entity.RestoreBufferedEvents(player, writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Trace($"Player {player.Connection.Id}|{context.LobbyPlayer.Name} waiting owner of room");
|
||||
room.WaitPlayersList.Add(player);
|
||||
}
|
||||
}
|
||||
|
||||
private void DispatchPlayerJoinExcludePlayer(Room room, RoomPlayer roomPlayer, RagonSerializer writer)
|
||||
{
|
||||
writer.Clear();
|
||||
writer.WriteOperation(RagonOperation.PLAYER_JOINED);
|
||||
writer.WriteUShort(roomPlayer.Connection.Id);
|
||||
writer.WriteString(roomPlayer.Id);
|
||||
writer.WriteString(roomPlayer.Name);
|
||||
|
||||
var sendData = writer.ToArray();
|
||||
foreach (var awaiter in room.ReadyPlayersList)
|
||||
{
|
||||
if (awaiter != roomPlayer)
|
||||
awaiter.Connection.ReliableChannel.Send(sendData);
|
||||
}
|
||||
}
|
||||
|
||||
private void DispatchSnapshot(Room room, List<RoomPlayer> receviersList, RagonSerializer writer)
|
||||
{
|
||||
writer.Clear();
|
||||
writer.WriteOperation(RagonOperation.SNAPSHOT);
|
||||
writer.WriteUShort((ushort) room.ReadyPlayersList.Count);
|
||||
foreach (var roomPlayer in room.ReadyPlayersList)
|
||||
{
|
||||
writer.WriteUShort(roomPlayer.Connection.Id);
|
||||
writer.WriteString(roomPlayer.Id);
|
||||
writer.WriteString(roomPlayer.Name);
|
||||
}
|
||||
|
||||
var dynamicEntities = room.DynamicEntitiesList;
|
||||
var dynamicEntitiesCount = (ushort) dynamicEntities.Count;
|
||||
writer.WriteUShort(dynamicEntitiesCount);
|
||||
foreach (var entity in dynamicEntities)
|
||||
entity.State.Snapshot(writer);
|
||||
|
||||
var staticEntities = room.StaticEntitiesList;
|
||||
var staticEntitiesCount = (ushort) staticEntities.Count;
|
||||
writer.WriteUShort(staticEntitiesCount);
|
||||
foreach (var entity in staticEntities)
|
||||
entity.State.Snapshot(writer);
|
||||
|
||||
var sendData = writer.ToArray();
|
||||
foreach (var player in receviersList)
|
||||
player.Connection.ReliableChannel.Send(sendData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Ragon.Core.Game;
|
||||
|
||||
namespace Ragon.Core.Lobby;
|
||||
|
||||
public interface ILobby
|
||||
{
|
||||
public bool FindRoomById(string roomId, out Room room);
|
||||
public bool FindRoomByMap(string map, out Room room);
|
||||
public void Persist(Room room);
|
||||
public void Remove(Room room);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System.Collections.Generic;
|
||||
using NLog;
|
||||
using Ragon.Core.Game;
|
||||
|
||||
namespace Ragon.Core.Lobby;
|
||||
|
||||
public class LobbyInMemory: ILobby
|
||||
{
|
||||
private readonly List<Room> _rooms = new();
|
||||
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public bool FindRoomById(string roomId, out Room room)
|
||||
{
|
||||
foreach (var existRoom in _rooms)
|
||||
{
|
||||
var info = existRoom.Info;
|
||||
if (existRoom.Id == roomId && info.Min < info.Max)
|
||||
{
|
||||
room = existRoom;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
room = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool FindRoomByMap(string map, out Room room)
|
||||
{
|
||||
foreach (var existRoom in _rooms)
|
||||
{
|
||||
var info = existRoom.Info;
|
||||
if (info.Map == map && existRoom.Players.Count < info.Max)
|
||||
{
|
||||
room = existRoom;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
room = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Persist(Room room)
|
||||
{
|
||||
_rooms.Add(room);
|
||||
|
||||
foreach (var r in _rooms)
|
||||
_logger.Trace($"{r.Id} {r.Info}");
|
||||
}
|
||||
|
||||
public void Remove(Room room)
|
||||
{
|
||||
_rooms.Remove(room);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Ragon.Server;
|
||||
|
||||
namespace Ragon.Core.Lobby;
|
||||
|
||||
public enum LobbyPlayerStatus
|
||||
{
|
||||
Unauthorized,
|
||||
Authorized,
|
||||
}
|
||||
|
||||
public class LobbyPlayer
|
||||
{
|
||||
public string Id { get; private set; }
|
||||
public string Name { get; set; }
|
||||
public byte[] AdditionalData { get; set; }
|
||||
public LobbyPlayerStatus Status { get; set; }
|
||||
public INetworkConnection Connection { get; private set; }
|
||||
|
||||
public LobbyPlayer(INetworkConnection connection)
|
||||
{
|
||||
Id = Guid.NewGuid().ToString();
|
||||
Connection = connection;
|
||||
Status = LobbyPlayerStatus.Unauthorized;
|
||||
Name = "None";
|
||||
AdditionalData = Array.Empty<byte>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using NLog;
|
||||
using Ragon.Core.Game;
|
||||
using Ragon.Core.Lobby;
|
||||
using Ragon.Core.Time;
|
||||
using Ragon.Server;
|
||||
|
||||
namespace Ragon.Core;
|
||||
|
||||
public class PlayerContext: IDisposable
|
||||
{
|
||||
public INetworkConnection Connection { get; }
|
||||
public Scheduler Scheduler;
|
||||
public ILobby Lobby { get; set; }
|
||||
public LobbyPlayer LobbyPlayer { private set; get; }
|
||||
public Room? Room { get; set; }
|
||||
public RoomPlayer? RoomPlayer { get; set; }
|
||||
|
||||
public PlayerContext(INetworkConnection conn, LobbyPlayer player)
|
||||
{
|
||||
Connection = conn;
|
||||
LobbyPlayer = player;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
RoomPlayer?.Room.RemovePlayer(RoomPlayer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2-beta2" />
|
||||
<PackageReference Include="NLog" Version="5.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ragon.Protocol\Ragon.Protocol.csproj" />
|
||||
<ProjectReference Include="..\Ragon.Server.ENet\Ragon.Server.ENet.csproj" />
|
||||
<ProjectReference Include="..\Ragon.Server.WebSockets\Ragon.Server.WebSockets.csproj" />
|
||||
<ProjectReference Include="..\Ragon.Server\Ragon.Server.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Ragon.Core.Time;
|
||||
|
||||
public interface IScheduleTask
|
||||
{
|
||||
public void Tick();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace Ragon.Core.Time;
|
||||
|
||||
public class Scheduler
|
||||
{
|
||||
private List<IScheduleTask> _tasks;
|
||||
|
||||
public Scheduler()
|
||||
{
|
||||
_tasks = new List<IScheduleTask>(35);
|
||||
}
|
||||
|
||||
public void Add(IScheduleTask task)
|
||||
{
|
||||
_tasks.Add(task);
|
||||
}
|
||||
|
||||
public void Remove(IScheduleTask task)
|
||||
{
|
||||
_tasks.Remove(task);
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
foreach (var task in _tasks)
|
||||
task.Tick();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user