wip
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user