using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using NLog; using Ragon.Common; namespace Ragon.Core { public class GameRoom : IGameRoom { public int PlayersMin { get; private set; } public int PlayersMax { get; private set; } public int PlayersCount => _players.Count; public int EntitiesCount => _entities.Count; public string Id { get; private set; } public string Map { get; private set; } private ILogger _logger = LogManager.GetCurrentClassLogger(); private Dictionary _players = new(); private Dictionary _entities = new(); private uint _owner; private readonly IScheduler _scheduler; private readonly IGameThread _gameThread; private readonly PluginBase _plugin; private readonly RagonSerializer _serializer = new(512); // Cache private uint[] _readyPlayers = Array.Empty(); private uint[] _allPlayers = Array.Empty(); private Entity[] _entitiesAll = Array.Empty(); private HashSet _entitiesDirtySet = new HashSet(); private List _entitiesDirty = new List(); private List _peersCache = new List(); public GameRoom(IGameThread gameThread, PluginBase pluginBase, string roomId, string map, int min, int max) { _gameThread = gameThread; _plugin = pluginBase; _scheduler = new Scheduler(); Map = map; PlayersMin = min; PlayersMax = max; Id = roomId; _plugin.Attach(this); } public void Joined(Player player, ReadOnlySpan payload) { if (_players.Count == 0) { _owner = player.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(); Broadcast(_readyPlayers, sendData, DeliveryType.Reliable); } _players.Add(player.PeerId, player); _allPlayers = _players.Select(p => p.Key).ToArray(); { _serializer.Clear(); _serializer.WriteOperation(RagonOperation.JOIN_SUCCESS); _serializer.WriteString(Id); _serializer.WriteString(player.Id); _serializer.WriteString(GetOwner().Id); _serializer.WriteUShort((ushort) PlayersMin); _serializer.WriteUShort((ushort) PlayersMax); var sendData = _serializer.ToArray(); Send(player.PeerId, sendData, DeliveryType.Reliable); } { _serializer.Clear(); _serializer.WriteOperation(RagonOperation.LOAD_SCENE); _serializer.WriteString(Map); var sendData = _serializer.ToArray(); Send(player.PeerId, sendData, DeliveryType.Reliable); } } public void Leave(uint peerId) { if (_players.Remove(peerId, out var player)) { _allPlayers = _players.Select(p => p.Key).ToArray(); _readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray(); { _plugin.OnPlayerLeaved(player); _serializer.Clear(); _serializer.WriteOperation(RagonOperation.PLAYER_LEAVED); _serializer.WriteString(player.Id); _serializer.WriteUShort((ushort) player.EntitiesIds.Count); foreach (var entityId in player.EntitiesIds) { _serializer.WriteInt(entityId); _entities.Remove(entityId); } var sendData = _serializer.ToArray(); Broadcast(_readyPlayers, sendData); } if (_allPlayers.Length > 0) { var nextOwnerId = _allPlayers[0]; _owner = nextOwnerId; var nextOwner = _players[nextOwnerId]; _serializer.Clear(); _serializer.WriteOperation(RagonOperation.OWNERSHIP_CHANGED); _serializer.WriteString(nextOwner.Id); var sendData = _serializer.ToArray(); Broadcast(_readyPlayers, sendData); } _entitiesAll = _entities.Values.ToArray(); } } public void ProcessEvent(ushort peerId, RagonOperation operation, ReadOnlySpan payloadRawData) { _serializer.Clear(); _serializer.FromSpan(ref payloadRawData); switch (operation) { case RagonOperation.REPLICATE_ENTITY_STATE: { var entitiesCount = _serializer.ReadUShort(); for (var entityIndex = 0; entityIndex < entitiesCount; entityIndex++) { var entityId = _serializer.ReadUShort(); if (_entities.TryGetValue(entityId, out var ent)) { if (ent.OwnerId != peerId) { _logger.Warn($"Not owner can't change properties of object {entityId}"); return; } for (var i = 0; i < ent.Properties.Length; i++) { if (_serializer.ReadBool()) { var property = ent.Properties[i]; if (!property.IsFixed) property.Size = _serializer.ReadUShort(); var propertyPayload = _serializer.ReadData(property.Size); property.Write(ref propertyPayload); } } if (_entitiesDirtySet.Add(ent)) _entitiesDirty.Add(ent); } else { _logger.Error($"Entity with Id {entityId} not found, replication interrupted"); break; } } break; } case RagonOperation.REPLICATE_ENTITY_EVENT: { var evntId = _serializer.ReadUShort(); var evntMode = _serializer.ReadByte(); var targetMode = (RagonTarget) _serializer.ReadByte(); var entityId = _serializer.ReadUShort(); if (!_entities.TryGetValue(entityId, out var ent)) return; if (ent.Authority == RagonAuthority.OWNER_ONLY && ent.OwnerId != peerId) 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)) return; _serializer.Clear(); _serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT); _serializer.WriteUShort(evntId); _serializer.WriteUShort((ushort) peerId); _serializer.WriteByte(evntMode); _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; } } break; } case RagonOperation.LOAD_SCENE: { var sceneName = _serializer.ReadString(); _readyPlayers = Array.Empty(); _entitiesAll = Array.Empty(); _entities.Clear(); _serializer.Clear(); _serializer.WriteOperation(RagonOperation.LOAD_SCENE); _serializer.WriteString(sceneName); var sendData = _serializer.ToArray(); Broadcast(_allPlayers, sendData, DeliveryType.Reliable); break; } case RagonOperation.REPLICATE_EVENT: { var evntId = _serializer.ReadUShort(); var evntMode = _serializer.ReadByte(); var targetMode = (RagonTarget) _serializer.ReadByte(); Span payloadRaw = stackalloc byte[_serializer.Size]; var payloadData = _serializer.ReadData(_serializer.Size); payloadData.CopyTo(payloadRaw); ReadOnlySpan payload = payloadRaw; if (_plugin.InternalHandle(peerId, evntId, ref payload)) return; _serializer.Clear(); _serializer.WriteOperation(RagonOperation.REPLICATE_EVENT); _serializer.WriteUShort((ushort) peerId); _serializer.WriteByte(evntMode); _serializer.WriteUShort(evntId); _serializer.WriteData(ref payload); var sendData = _serializer.ToArray(); switch (targetMode) { case RagonTarget.OWNER: { Send(_owner, sendData, DeliveryType.Reliable); break; } case RagonTarget.EXCEPT_OWNER: { _peersCache.Clear(); foreach (var playerPeerId in _readyPlayers) if (playerPeerId != _owner) _peersCache.Add(playerPeerId); Broadcast(_peersCache.ToArray(), sendData, DeliveryType.Reliable); break; } case RagonTarget.ALL: { Broadcast(_readyPlayers, sendData, DeliveryType.Reliable); break; } } break; } case RagonOperation.CREATE_SCENE_ENTITY: { var entityType = _serializer.ReadUShort(); var staticId = _serializer.ReadUShort(); var propertiesCount = _serializer.ReadUShort(); var entity = new Entity(peerId, entityType, staticId, RagonAuthority.ALL, RagonAuthority.OWNER_ONLY, propertiesCount); for (var i = 0; i < propertiesCount; i++) { var propertyType = _serializer.ReadBool(); var propertySize = _serializer.ReadUShort(); entity.Properties[i] = new EntityProperty(propertySize, propertyType); } { var entityPayload = _serializer.ReadData(_serializer.Size); entity.Payload = entityPayload.ToArray(); } var player = _players[peerId]; player.Entities.Add(entity); player.EntitiesIds.Add(entity.EntityId); var ownerId = (ushort) peerId; _entities.Add(entity.EntityId, entity); _entitiesAll = _entities.Values.ToArray(); _plugin.OnEntityCreated(player, entity); _serializer.Clear(); _serializer.WriteOperation(RagonOperation.CREATE_SCENE_ENTITY); _serializer.WriteUShort(entityType); _serializer.WriteUShort(entity.EntityId); _serializer.WriteUShort(staticId); _serializer.WriteUShort(ownerId); { ReadOnlySpan entityPayload = entity.Payload.AsSpan(); _serializer.WriteData(ref entityPayload); } var sendData = _serializer.ToArray(); Broadcast(_readyPlayers, sendData, DeliveryType.Reliable); break; } case RagonOperation.CREATE_ENTITY: { var entityType = _serializer.ReadUShort(); var propertiesCount = _serializer.ReadUShort(); var entity = new Entity(peerId, entityType, 0, RagonAuthority.ALL, RagonAuthority.ALL, propertiesCount); // _logger.Trace("Created entity with properties: " + propertiesCount); for (var i = 0; i < propertiesCount; i++) { var propertyType = _serializer.ReadBool(); var propertySize = _serializer.ReadUShort(); entity.Properties[i] = new EntityProperty(propertySize, propertyType); // _logger.Trace($"Property: {i} Size: {propertySize} IsFixed: {propertyType}"); } { var entityPayload = _serializer.ReadData(_serializer.Size); entity.Payload = entityPayload.ToArray(); } var player = _players[peerId]; player.Entities.Add(entity); player.EntitiesIds.Add(entity.EntityId); var ownerId = (ushort) peerId; _entities.Add(entity.EntityId, entity); _entitiesAll = _entities.Values.ToArray(); _plugin.OnEntityCreated(player, entity); _serializer.Clear(); _serializer.WriteOperation(RagonOperation.CREATE_ENTITY); _serializer.WriteUShort(entityType); _serializer.WriteUShort(entity.EntityId); _serializer.WriteUShort(ownerId); { ReadOnlySpan entityPayload = entity.Payload.AsSpan(); _serializer.WriteData(ref entityPayload); } var sendData = _serializer.ToArray(); Broadcast(_readyPlayers, sendData, DeliveryType.Reliable); break; } case RagonOperation.DESTROY_ENTITY: { var entityId = _serializer.ReadInt(); if (_entities.TryGetValue(entityId, out var entity)) { if (entity.Authority == RagonAuthority.OWNER_ONLY && entity.OwnerId != peerId) return; var player = _players[peerId]; var destroyPayload = _serializer.ReadData(_serializer.Size); player.Entities.Remove(entity); player.EntitiesIds.Remove(entity.EntityId); _entities.Remove(entityId); _entitiesAll = _entities.Values.ToArray(); _plugin.OnEntityDestroyed(player, entity); _serializer.Clear(); _serializer.WriteOperation(RagonOperation.DESTROY_ENTITY); _serializer.WriteInt(entityId); _serializer.WriteData(ref destroyPayload); var sendData = _serializer.ToArray(); Broadcast(_readyPlayers, sendData, DeliveryType.Reliable); } break; } case RagonOperation.SCENE_IS_LOADED: { _serializer.Clear(); _serializer.WriteOperation(RagonOperation.SNAPSHOT); _serializer.WriteUShort((ushort) _allPlayers.Length); foreach (var playerPeerId in _allPlayers) { _serializer.WriteString(_players[playerPeerId].Id); _serializer.WriteUShort((ushort) playerPeerId); _serializer.WriteString(_players[playerPeerId].PlayerName); } var dynamicEntities = _entitiesAll.Where(e => e.StaticId == 0).ToArray(); _serializer.WriteUShort((ushort) dynamicEntities.Length); foreach (var entity in dynamicEntities) { ReadOnlySpan payload = entity.Payload.AsSpan(); _serializer.WriteUShort(entity.EntityType); _serializer.WriteUShort(entity.EntityId); _serializer.WriteUShort((ushort) entity.OwnerId); _serializer.WriteUShort((ushort) payload.Length); _serializer.WriteData(ref payload); } var staticCount = _entitiesAll.Where(e => e.StaticId != 0).ToArray(); _serializer.WriteUShort((ushort) staticCount.Length); foreach (var entity in staticCount) { ReadOnlySpan payload = entity.Payload.AsSpan(); _serializer.WriteUShort(entity.EntityType); _serializer.WriteUShort(entity.EntityId); _serializer.WriteUShort(entity.StaticId); _serializer.WriteUShort(entity.OwnerId); _serializer.WriteUShort((ushort) payload.Length); _serializer.WriteData(ref payload); } var sendData = _serializer.ToArray(); Send(peerId, sendData, DeliveryType.Reliable); _players[peerId].IsLoaded = true; _readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray(); _plugin.OnPlayerJoined(_players[peerId]); break; } } } public void Tick(float deltaTime) { _scheduler.Tick(deltaTime); if (_entitiesDirty.Count > 0) { _serializer.Clear(); _serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_STATE); _serializer.WriteUShort((ushort) _entitiesDirty.Count); for (var entityIndex = 0; entityIndex < _entitiesDirty.Count; entityIndex++) { var entity = _entitiesDirty[entityIndex]; _serializer.WriteUShort(entity.EntityId); for (int propertyIndex = 0; propertyIndex < entity.Properties.Length; propertyIndex++) { var property = entity.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); } } } _entitiesDirty.Clear(); _entitiesDirtySet.Clear(); var sendData = _serializer.ToArray(); Broadcast(_readyPlayers, sendData); } } public void Start() { _plugin.OnStart(); } public void Stop() { foreach (var peerId in _allPlayers) _gameThread.Server.Disconnect(peerId, 0); _plugin.OnStop(); _plugin.Detach(); } public Player GetPlayerById(uint peerId) => _players[peerId]; public Entity GetEntityById(int entityId) => _entities[entityId]; public Player GetOwner() => _players[_owner]; public IDispatcher GetThreadDispatcher() => _gameThread.ThreadDispatcher; public IScheduler GetScheduler() => _scheduler; public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable) { _gameThread.Server.Send(peerId, rawData, deliveryType); } public void Broadcast(uint[] peersIds, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable) { _gameThread.Server.Broadcast(peersIds, rawData, deliveryType); } public void Broadcast(byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable) { _gameThread.Server.Broadcast(_allPlayers, rawData, deliveryType); } } }