diff --git a/Ragon.Common/Sources/RagonAuthority.cs b/Ragon.Common/Sources/RagonAuthority.cs index d892f55..c71b0b3 100644 --- a/Ragon.Common/Sources/RagonAuthority.cs +++ b/Ragon.Common/Sources/RagonAuthority.cs @@ -2,6 +2,7 @@ namespace Ragon.Common { public enum RagonAuthority: byte { + NONE, OWNER_ONLY, ALL, } diff --git a/Ragon.Common/Sources/RagonReplicationMode.cs b/Ragon.Common/Sources/RagonReplicationMode.cs new file mode 100644 index 0000000..a43ed64 --- /dev/null +++ b/Ragon.Common/Sources/RagonReplicationMode.cs @@ -0,0 +1,10 @@ +namespace Ragon.Common +{ + public enum RagonReplicationMode: byte + { + LOCAL, + SERVER, + LOCAL_AND_SERVER, + BUFFERED, + } +} \ No newline at end of file diff --git a/Ragon/Sources/Configuration/ConfigurationLoader.cs b/Ragon/Sources/Configuration/ConfigurationLoader.cs index 1a5eaa0..2dc3402 100755 --- a/Ragon/Sources/Configuration/ConfigurationLoader.cs +++ b/Ragon/Sources/Configuration/ConfigurationLoader.cs @@ -9,7 +9,7 @@ namespace Ragon.Core public static class ConfigurationLoader { private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); - private static readonly string _serverVersion = "1.0.17-rc"; + private static readonly string _serverVersion = "1.0.18-rc"; private static void CopyrightInfo() { diff --git a/Ragon/Sources/Entity/Entity.cs b/Ragon/Sources/Entity/Entity.cs index 189e4f6..f9ff6f1 100644 --- a/Ragon/Sources/Entity/Entity.cs +++ b/Ragon/Sources/Entity/Entity.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Ragon.Common; namespace Ragon.Core; @@ -12,9 +13,11 @@ public class Entity public ushort OwnerId { get; private set; } public RagonAuthority Authority { get; private set; } public EntityProperty[] Properties { get; private set; } + public List BufferedEvents = new List(); + public byte[] Payload { get; set; } - public Entity(ushort ownerId, ushort entityType, ushort staticId, RagonAuthority stateAuthority, RagonAuthority eventAuthority, int props) + public Entity(ushort ownerId, ushort entityType, ushort staticId, RagonAuthority eventAuthority, int props) { OwnerId = ownerId; StaticId = staticId; diff --git a/Ragon/Sources/Entity/EntityEvent.cs b/Ragon/Sources/Entity/EntityEvent.cs new file mode 100644 index 0000000..24ba19e --- /dev/null +++ b/Ragon/Sources/Entity/EntityEvent.cs @@ -0,0 +1,10 @@ +using Ragon.Common; + +namespace Ragon.Core; + +public class EntityEvent +{ + public ushort EventId { get; set; } + public byte[] EventData { get; set; } + public RagonTarget Target { set; get; } +} \ No newline at end of file diff --git a/Ragon/Sources/Game/GameRoom.cs b/Ragon/Sources/Game/GameRoom.cs index aa3fa11..3b04063 100755 --- a/Ragon/Sources/Game/GameRoom.cs +++ b/Ragon/Sources/Game/GameRoom.cs @@ -1,11 +1,14 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using NLog; using Ragon.Common; namespace Ragon.Core { + // TODO: Replace all serialization and packing into dedicated structures + // TODO: Split class on different managers public class GameRoom : IGameRoom { public int PlayersMin { get; private set; } @@ -32,6 +35,7 @@ namespace Ragon.Core private HashSet _entitiesDirtySet = new HashSet(); private List _entitiesDirty = new List(); private List _peersCache = new List(); + private List _awaitingPeers = new List(); public GameRoom(IGameThread gameThread, PluginBase pluginBase, string roomId, string map, int min, int max) { @@ -94,12 +98,12 @@ namespace Ragon.Core _serializer.WriteOperation(RagonOperation.PLAYER_LEAVED); _serializer.WriteString(player.Id); - // TODO: DO NOT REMOVE STATIC ENTITIES - _serializer.WriteUShort((ushort) player.EntitiesIds.Count); - foreach (var entityId in player.EntitiesIds) + var entitiesToDelete = player.Entities.Where(e => e.StaticId == 0).ToArray(); + _serializer.WriteUShort((ushort) entitiesToDelete.Length); + foreach (var entity in entitiesToDelete) { - _serializer.WriteUShort(entityId); - _entities.Remove(entityId); + _serializer.WriteUShort(entity.EntityId); + _entities.Remove(entity.EntityId); } var sendData = _serializer.ToArray(); @@ -123,7 +127,8 @@ namespace Ragon.Core _entitiesAll = _entities.Values.ToArray(); } } - + + // TODO: Move this processing to specialized classes with structures public void ProcessEvent(ushort peerId, RagonOperation operation, ReadOnlySpan payloadRawData) { _serializer.Clear(); @@ -148,49 +153,109 @@ namespace Ragon.Core case RagonOperation.SCENE_LOADED: { var player = _players[peerId]; - if (peerId == _owner) { var statics = _serializer.ReadUShort(); for (var staticIndex = 0; staticIndex < statics; staticIndex++) { var entityType = _serializer.ReadUShort(); + var entityAuthority = (RagonAuthority) _serializer.ReadByte(); var staticId = _serializer.ReadUShort(); var propertiesCount = _serializer.ReadUShort(); - var entity = new Entity(peerId, entityType, staticId, RagonAuthority.ALL, RagonAuthority.OWNER_ONLY, propertiesCount); + var entity = new Entity(peerId, entityType, staticId, entityAuthority, propertiesCount); for (var propertyIndex = 0; propertyIndex < propertiesCount; propertyIndex++) { var propertyType = _serializer.ReadBool(); var propertySize = _serializer.ReadUShort(); entity.Properties[propertyIndex] = new EntityProperty(propertySize, propertyType); } - + player.Entities.Add(entity); player.EntitiesIds.Add(entity.EntityId); - + _entities.Add(entity.EntityId, entity); } - + _entitiesAll = _entities.Values.ToArray(); _logger.Trace($"Scene entities: {statics}"); + + _awaitingPeers.Add(peerId); + + foreach (var peer in _awaitingPeers) + { + var joinedPlayer = _players[peer]; + joinedPlayer.IsLoaded = true; + _plugin.OnPlayerJoined(joinedPlayer); + _logger.Trace($"[{_owner}][{peer}] Player {joinedPlayer.Id} restored"); + { + _serializer.Clear(); + _serializer.WriteOperation(RagonOperation.PLAYER_JOINED); + _serializer.WriteUShort((ushort) player.PeerId); + _serializer.WriteString(player.Id); + _serializer.WriteString(player.PlayerName); + + var sendData = _serializer.ToArray(); + var readyPlayersWithExcludedPeer = _readyPlayers.Where(p => p != peerId).ToArray(); + Broadcast(readyPlayersWithExcludedPeer, sendData, DeliveryType.Reliable); + } + } + + _readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray(); + foreach (var peer in _awaitingPeers) + { + ReplicateSnapshot(peer); + } + + _awaitingPeers.Clear(); + } + else if (GetOwner().IsLoaded) + { + _logger.Trace($"[{_owner}][{peerId}] Player {player.Id} restored instantly"); + player.IsLoaded = true; + + { + _serializer.Clear(); + _serializer.WriteOperation(RagonOperation.PLAYER_JOINED); + _serializer.WriteUShort((ushort) player.PeerId); + _serializer.WriteString(player.Id); + _serializer.WriteString(player.PlayerName); + + var sendData = _serializer.ToArray(); + var readyPlayersWithExcludedPeer = _readyPlayers.Where(p => p != peerId).ToArray(); + Broadcast(readyPlayersWithExcludedPeer, sendData, DeliveryType.Reliable); + } + + _readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray(); + _plugin.OnPlayerJoined(player); + + ReplicateSnapshot(peerId); + + foreach (var (key, value) in _entities) + { + foreach (var bufferedEvent in value.BufferedEvents) + { + _serializer.Clear(); + _serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT); + _serializer.WriteUShort(bufferedEvent.EventId); + _serializer.WriteUShort(peerId); + _serializer.WriteByte((byte) RagonReplicationMode.SERVER); + _serializer.WriteUShort(value.EntityId); + + ReadOnlySpan data = bufferedEvent.EventData.AsSpan(); + _serializer.WriteData(ref data); + + var sendData = _serializer.ToArray(); + SendEvent(value, bufferedEvent.Target, sendData); + } + } + } + else + { + _logger.Trace($"[{_owner}][{peerId}] Player {player.Id} waiting"); + _awaitingPeers.Add(peerId); } - ReplicateSnapshot(peerId); - - _serializer.Clear(); - _serializer.WriteOperation(RagonOperation.PLAYER_JOINED); - _serializer.WriteUShort((ushort) player.PeerId); - _serializer.WriteString(player.Id); - _serializer.WriteString(player.PlayerName); - - var sendData = _serializer.ToArray(); - var readyPlayersWithExcludedPeer = _readyPlayers.Where(p => p != peerId).ToArray(); - Broadcast(readyPlayersWithExcludedPeer, sendData, DeliveryType.Reliable); - - player.IsLoaded = true; - _readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray(); - _plugin.OnPlayerJoined(player); break; } case RagonOperation.REPLICATE_ENTITY_STATE: @@ -242,66 +307,63 @@ namespace Ragon.Core } case RagonOperation.REPLICATE_ENTITY_EVENT: { - var evntId = _serializer.ReadUShort(); - var evntMode = _serializer.ReadByte(); + var eventId = _serializer.ReadUShort(); + var eventMode = (RagonReplicationMode) _serializer.ReadByte(); var targetMode = (RagonTarget) _serializer.ReadByte(); var entityId = _serializer.ReadUShort(); - + if (!_entities.TryGetValue(entityId, out var ent)) + { + _logger.Warn($"Entity not found for event with Id {eventId}"); return; + } if (ent.Authority == RagonAuthority.OWNER_ONLY && ent.OwnerId != peerId) + { + _logger.Warn($"Player have not enought authority for event with Id {eventId}"); return; + } Span payloadRaw = stackalloc byte[_serializer.Size]; var payloadData = _serializer.ReadData(_serializer.Size); payloadData.CopyTo(payloadRaw); ReadOnlySpan payload = payloadRaw; - if (_plugin.InternalHandle(peerId, entityId, evntId, ref payload)) + if (_plugin.InternalHandle(peerId, entityId, eventId, ref payload)) return; + if (eventMode == RagonReplicationMode.BUFFERED && targetMode != RagonTarget.OWNER) + { + var bufferedEvent = new EntityEvent() + { + EventData = payload.ToArray(), + Target = targetMode, + EventId = eventId, + }; + ent.BufferedEvents.Add(bufferedEvent); + } + _serializer.Clear(); _serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT); - _serializer.WriteUShort(evntId); + _serializer.WriteUShort(eventId); _serializer.WriteUShort(peerId); - _serializer.WriteByte(evntMode); + _serializer.WriteByte((byte) eventMode); _serializer.WriteUShort(entityId); _serializer.WriteData(ref payload); var sendData = _serializer.ToArray(); - - switch (targetMode) - { - case RagonTarget.OWNER: - { - Send(ent.OwnerId, sendData, DeliveryType.Reliable); - break; - } - case RagonTarget.EXCEPT_OWNER: - { - _peersCache.Clear(); - foreach (var playerPeerId in _readyPlayers) - if (playerPeerId != ent.OwnerId) - _peersCache.Add(playerPeerId); - - Broadcast(_peersCache.ToArray(), sendData, DeliveryType.Reliable); - break; - } - case RagonTarget.ALL: - { - Broadcast(_readyPlayers, sendData, DeliveryType.Reliable); - break; - } - } - + Broadcast(_readyPlayers, sendData, DeliveryType.Reliable); break; } case RagonOperation.CREATE_ENTITY: { var entityType = _serializer.ReadUShort(); + var eventAuthority = (RagonAuthority) _serializer.ReadByte(); var propertiesCount = _serializer.ReadUShort(); - var entity = new Entity(peerId, entityType, 0, RagonAuthority.ALL, RagonAuthority.ALL, propertiesCount); + + _logger.Trace($"[{peerId}] Create Entity {entityType}"); + + var entity = new Entity(peerId, entityType, 0, eventAuthority, propertiesCount); for (var i = 0; i < propertiesCount; i++) { var propertyType = _serializer.ReadBool(); @@ -369,7 +431,6 @@ namespace Ragon.Core var sendData = _serializer.ToArray(); Broadcast(_readyPlayers, sendData, DeliveryType.Reliable); } - break; } } @@ -381,13 +442,14 @@ namespace Ragon.Core ReplicateProperties(); } - + + // TODO: Move this to specialized class void ReplicateSnapshot(uint peerId) { _serializer.Clear(); _serializer.WriteOperation(RagonOperation.SNAPSHOT); - _serializer.WriteUShort((ushort) _allPlayers.Length); - foreach (var playerPeerId in _allPlayers) + _serializer.WriteUShort((ushort) _readyPlayers.Length); + foreach (var playerPeerId in _readyPlayers) { _serializer.WriteUShort((ushort) playerPeerId); _serializer.WriteString(_players[playerPeerId].Id); @@ -475,6 +537,35 @@ namespace Ragon.Core public IScheduler GetScheduler() => _scheduler; + + // TODO: Move this to Entity Event Manager + public void SendEvent(Entity ent, RagonTarget targetMode, byte[] sendData) + { + switch (targetMode) + { + case RagonTarget.OWNER: + { + Send(ent.OwnerId, sendData, DeliveryType.Reliable); + break; + } + case RagonTarget.EXCEPT_OWNER: + { + _peersCache.Clear(); + foreach (var playerPeerId in _readyPlayers) + if (playerPeerId != ent.OwnerId) + _peersCache.Add(playerPeerId); + + Broadcast(_peersCache.ToArray(), sendData, DeliveryType.Reliable); + break; + } + case RagonTarget.ALL: + { + Broadcast(_readyPlayers, sendData, DeliveryType.Reliable); + break; + } + } + } + public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable) => _gameThread.Server.Send(peerId, rawData, deliveryType);