diff --git a/SlipeServer.Console/Logic/ServerTestLogic.cs b/SlipeServer.Console/Logic/ServerTestLogic.cs index fbb4c753..60f1c4bd 100644 --- a/SlipeServer.Console/Logic/ServerTestLogic.cs +++ b/SlipeServer.Console/Logic/ServerTestLogic.cs @@ -547,7 +547,7 @@ void Player_Disconnected(Player sender, PlayerQuitEventArgs e) }; this.commandService.AddCommand("increment").Triggered += (source, args) - => args.Player.GetAndIncrementTimeContext(); + => args.Player.TimeContext.GetAndIncrement(); this.commandService.AddCommand("resendmodpackets").Triggered += (source, args) => args.Player.ResendModPackets(); diff --git a/SlipeServer.Packets/Definitions/Entities/AddEntityPacket.cs b/SlipeServer.Packets/Definitions/Entities/AddEntityPacket.cs index 2bb00d72..8baff05f 100644 --- a/SlipeServer.Packets/Definitions/Entities/AddEntityPacket.cs +++ b/SlipeServer.Packets/Definitions/Entities/AddEntityPacket.cs @@ -1,6 +1,7 @@ using SlipeServer.Packets.Builder; using SlipeServer.Packets.Definitions.Entities.Structs; using SlipeServer.Packets.Enums; +using SlipeServer.Packets.Reader; using SlipeServer.Packets.Structs; using System; using System.Drawing; @@ -18,6 +19,8 @@ public class AddEntityPacket : Packet private readonly PacketBuilder builder; private uint entityCount; + public bool HasAnyEntity => entityCount > 0; + public AddEntityPacket() { this.builder = new PacketBuilder(); @@ -785,7 +788,14 @@ public void AddWater( public override void Read(byte[] bytes) { - throw new NotSupportedException(); + var reader = new PacketReader(bytes); + this.entityCount = reader.GetCompressedUInt32(); + + for (int i = 0; i < entityCount; i++) + { + + } + ; } public override byte[] Write() diff --git a/SlipeServer.Packets/Definitions/Join/PlayerListPacket.cs b/SlipeServer.Packets/Definitions/Join/PlayerListPacket.cs index 0664bbdb..235e5eea 100644 --- a/SlipeServer.Packets/Definitions/Join/PlayerListPacket.cs +++ b/SlipeServer.Packets/Definitions/Join/PlayerListPacket.cs @@ -1,27 +1,58 @@ using SlipeServer.Packets.Builder; using SlipeServer.Packets.Enums; +using SlipeServer.Packets.Reader; using SlipeServer.Packets.Structs; using System; +using System.Collections.Generic; using System.Drawing; using System.Numerics; namespace SlipeServer.Packets.Definitions.Join; +public record PacketPlayer(ElementId playerId, + byte timeContext, + string nickname, + ushort bitsreamVersion, + uint buildNumber, + + bool isDead, + bool isInVehicle, + bool hasJetpack, + bool isNametagShowing, + bool isHeadless, + bool isFrozen, + + string nametagText, + Color? nametagColor, + byte moveAnimation, + + ushort model, + ElementId? teamId, + + ElementId? vehicleId, + byte? seat, + + Vector3? position, + float? rotation, + + ushort dimension, + byte fightingStyle, + byte alpha, + byte interior, + + byte[] weapons); + public class PlayerListPacket : Packet { public override PacketId PacketId => PacketId.PACKET_ID_PLAYER_LIST; public override PacketReliability Reliability => PacketReliability.ReliableSequenced; public override PacketPriority Priority => PacketPriority.High; + private PacketBuilder builder = new(); + public List? Players { get; private set; } - private readonly PacketBuilder builder; - - public PlayerListPacket(bool showInChat) - { - this.builder = new PacketBuilder(); - - this.builder.Write(showInChat); - } + public bool ShowInChat { get; set; } + public PlayerListPacket() { } public void AddPlayer( ElementId playerId, @@ -129,11 +160,21 @@ byte[] weapons public override void Read(byte[] bytes) { - throw new NotSupportedException(); + this.Players = new(); + var reader = new PacketReader(bytes); + this.ShowInChat = reader.GetBit(); + while (!reader.IsFinishedReading) + { + var playerId = reader.GetElementId(); + var timeContext = reader.GetByte(); + var nickname = reader.GetStringWithByteAsLength(); + ; + + } } public override byte[] Write() { - return this.builder.Build(); + return this.builder!.Build(); } } diff --git a/SlipeServer.Packets/Definitions/Lua/ElementRpc/Element/SetElementPositionRpcPacket.cs b/SlipeServer.Packets/Definitions/Lua/ElementRpc/Element/SetElementPositionRpcPacket.cs index 4f315bcd..c8f28ece 100644 --- a/SlipeServer.Packets/Definitions/Lua/ElementRpc/Element/SetElementPositionRpcPacket.cs +++ b/SlipeServer.Packets/Definitions/Lua/ElementRpc/Element/SetElementPositionRpcPacket.cs @@ -1,5 +1,6 @@ using SlipeServer.Packets.Builder; using SlipeServer.Packets.Enums; +using SlipeServer.Packets.Reader; using SlipeServer.Packets.Structs; using System; using System.Numerics; @@ -15,7 +16,7 @@ public class SetElementPositionRpcPacket : Packet public ElementId ElementId { get; set; } public byte TimeContext { get; set; } public Vector3 Position { get; set; } - public bool IsWarp { get; } + public bool IsWarp { get; set; } public SetElementPositionRpcPacket() { @@ -32,7 +33,16 @@ public SetElementPositionRpcPacket(ElementId elementId, byte timeContext, Vector public override void Read(byte[] bytes) { - throw new NotSupportedException(); + var reader = new PacketReader(bytes); + + var rpcFunction = (ElementRpcFunction)reader.GetByte(); + if (rpcFunction != ElementRpcFunction.SET_ELEMENT_POSITION) + throw new InvalidOperationException($"Invalid rpcFunction, expected SET_ELEMENT_POSITION, got: {rpcFunction}"); + + this.ElementId = reader.GetElementId(); + this.Position = reader.GetVector3(); + this.TimeContext = reader.GetByte(); + this.IsWarp = reader.IsFinishedReading ? true : false; } public override byte[] Write() diff --git a/SlipeServer.Packets/Definitions/Player/ChangeNicknamePacket.cs b/SlipeServer.Packets/Definitions/Player/ChangeNicknamePacket.cs index e2e24bf4..aafe34a8 100644 --- a/SlipeServer.Packets/Definitions/Player/ChangeNicknamePacket.cs +++ b/SlipeServer.Packets/Definitions/Player/ChangeNicknamePacket.cs @@ -1,5 +1,6 @@ using SlipeServer.Packets.Builder; using SlipeServer.Packets.Enums; +using SlipeServer.Packets.Reader; using SlipeServer.Packets.Structs; using System; @@ -14,18 +15,11 @@ public class ChangeNicknamePacket : Packet public ElementId PlayerId { get; set; } public string Name { get; set; } - public ChangeNicknamePacket( - ElementId playerId, - string name - ) - { - this.PlayerId = playerId; - this.Name = name; - } - public override void Read(byte[] bytes) { - throw new NotSupportedException(); + var reader = new PacketReader(bytes); + this.PlayerId = reader.GetElementId(); + this.Name = reader.GetStringCharacters(reader.RemainingBytes); } public override byte[] Write() diff --git a/SlipeServer.Packets/Definitions/Rpc/RpcPacket.cs b/SlipeServer.Packets/Definitions/Rpc/RpcPacket.cs index 08ae4235..c47ade49 100644 --- a/SlipeServer.Packets/Definitions/Rpc/RpcPacket.cs +++ b/SlipeServer.Packets/Definitions/Rpc/RpcPacket.cs @@ -1,4 +1,5 @@ -using SlipeServer.Packets.Enums; +using SlipeServer.Packets.Builder; +using SlipeServer.Packets.Enums; using SlipeServer.Packets.Reader; using System; @@ -10,7 +11,7 @@ public class RpcPacket : Packet public override PacketReliability Reliability => PacketReliability.ReliableSequenced; public override PacketPriority Priority => PacketPriority.High; - public RpcFunctions FunctionId { get; private set; } + public RpcFunctions FunctionId { get; set; } public PacketReader Reader { get; private set; } @@ -28,6 +29,10 @@ public override void Read(byte[] bytes) public override byte[] Write() { - throw new NotSupportedException(); + var builder = new PacketBuilder(); + + builder.Write((byte)this.FunctionId); + + return builder.Build(); } } diff --git a/SlipeServer.Packets/Definitions/Sync/PlayerPureSyncPacket.cs b/SlipeServer.Packets/Definitions/Sync/PlayerPureSyncPacket.cs index e3716e5a..8c61f243 100644 --- a/SlipeServer.Packets/Definitions/Sync/PlayerPureSyncPacket.cs +++ b/SlipeServer.Packets/Definitions/Sync/PlayerPureSyncPacket.cs @@ -6,6 +6,7 @@ using SlipeServer.Packets.Reader; using SlipeServer.Packets.Constants; using SlipeServer.Packets.Structs; +using System.Reflection.PortableExecutable; namespace SlipeServer.Packets.Definitions.Sync; @@ -113,9 +114,9 @@ public override byte[] Write() { var builder = new PacketBuilder(); - builder.Write(this.PlayerId); + //builder.Write(this.PlayerId); builder.Write(this.TimeContext); - builder.WriteCompressed(this.Latency); + //builder.WriteCompressed(this.Latency); this.KeySync.Write(builder); this.SyncFlags.Write(builder); @@ -135,7 +136,9 @@ public override byte[] Write() builder.WritePlayerHealth(this.Health); builder.WritePlayerArmor(this.Armor); - builder.WriteFloatFromBits(this.CameraRotation, 12, -MathF.PI, MathF.PI, true, false); + builder.WriteFloatFromBits(this.CameraRotation, 12, -MathF.PI, MathF.PI, false, true); + this.CameraOrientation = new CameraOrientationStructure(this.Position); + this.CameraOrientation.Write(builder); if (this.SyncFlags.HasAWeapon) { diff --git a/SlipeServer.Packets/Reader/PacketReader.cs b/SlipeServer.Packets/Reader/PacketReader.cs index b61710e0..e87a3c8e 100644 --- a/SlipeServer.Packets/Reader/PacketReader.cs +++ b/SlipeServer.Packets/Reader/PacketReader.cs @@ -1,6 +1,7 @@ using SlipeServer.Packets.Structs; using System; using System.Linq; +using System.Numerics; using System.Text; namespace SlipeServer.Packets.Reader; @@ -14,6 +15,7 @@ public class PacketReader public int Counter { get; private set; } public int Size { get; private set; } public bool IsFinishedReading => this.Counter == this.Size; + public int RemainingBytes => (this.Size - this.Counter) / 8; public PacketReader(byte[] data) { @@ -145,7 +147,13 @@ public string GetString() int length = GetUint16(); return GetStringCharacters(length); } - + + public string GetStringWithByteAsLength() + { + var length = GetByte(); + return GetStringCharacters(length); + } + public ElementId GetElementId() { var id = BitConverter.ToUInt32(GetBytesCapped(17).Concat(new byte[] { 0 }).ToArray(), 0); diff --git a/SlipeServer.Packets/SyncStructures/CameraOrientationStructure.cs b/SlipeServer.Packets/SyncStructures/CameraOrientationStructure.cs index a57748c4..59654ec9 100644 --- a/SlipeServer.Packets/SyncStructures/CameraOrientationStructure.cs +++ b/SlipeServer.Packets/SyncStructures/CameraOrientationStructure.cs @@ -60,6 +60,14 @@ public void Read(PacketReader reader) public void Write(PacketBuilder builder) { - + builder.WriteFloatFromBits(this.CameraForward.Z, 8, -MathF.PI, MathF.PI, false); + builder.WriteFloatFromBits(this.CameraForward.X, 8, -MathF.PI, MathF.PI, false); + builder.Write(true); + builder.WriteCapped(3, 2); + uint bitCount = bitCountLookup[3]; + float range = MathF.Pow(2, bitCount); + builder.WriteFloatFromBits(this.CameraForward.X, (int)bitCount, -range, range, false); + builder.WriteFloatFromBits(this.CameraForward.Y, (int)bitCount, -range, range, false); + builder.WriteFloatFromBits(this.CameraForward.Z, (int)bitCount, -range, range, false); } } diff --git a/SlipeServer.Server.TestTools/TestPacketQueueHandler.cs b/SlipeServer.Server.TestTools/TestPacketQueueHandler.cs new file mode 100644 index 00000000..94edc8cf --- /dev/null +++ b/SlipeServer.Server.TestTools/TestPacketQueueHandler.cs @@ -0,0 +1,54 @@ +using SlipeServer.Packets; +using SlipeServer.Server.PacketHandling.Handlers.QueueHandlers; +using SlipeServer.Server.PacketHandling.Handlers; +using SlipeServer.Server.PacketHandling; +using System; + +namespace SlipeServer.Server.TestTools; + +public class TestPacketQueueHandlerDispatcher +{ + public event Action? Flushed; + + public void Flush() + { + Flushed?.Invoke(); + } +} + +public class TestPacketQueueHandler : BasePacketQueueHandler where T : Packet +{ + private readonly TestPacketQueueHandlerDispatcher testPacketQueueHandlerDispatcher; + private readonly IPacketHandler packetHandler; + + public TestPacketQueueHandler(TestPacketQueueHandlerDispatcher testPacketQueueHandlerDispatcher, IPacketHandler packetHandler) + { + this.testPacketQueueHandlerDispatcher = testPacketQueueHandlerDispatcher; + this.packetHandler = packetHandler; + + testPacketQueueHandlerDispatcher.Flushed += HandleFlushed; + } + + private void HandleFlushed() + { + while (this.packetQueue.TryDequeue(out var queueEntry)) + { + try + { + ClientContext.Current = queueEntry.Client; + this.packetHandler.HandlePacket(queueEntry.Client, queueEntry.Packet); + TriggerPacketHandled(queueEntry.Packet); + } + finally + { + ClientContext.Current = null; + } + } + } + + public override void Dispose() + { + testPacketQueueHandlerDispatcher.Flushed -= HandleFlushed; + base.Dispose(); + } +} diff --git a/SlipeServer.Server.TestTools/TestingClient.cs b/SlipeServer.Server.TestTools/TestingClient.cs index fbbacfb7..3788a8ca 100644 --- a/SlipeServer.Server.TestTools/TestingClient.cs +++ b/SlipeServer.Server.TestTools/TestingClient.cs @@ -1,6 +1,14 @@ using SlipeServer.Net.Wrappers; +using SlipeServer.Packets; +using SlipeServer.Packets.Definitions.Join; +using SlipeServer.Packets.Definitions.Lua.ElementRpc; +using SlipeServer.Packets.Definitions.Lua.ElementRpc.Element; +using SlipeServer.Packets.Definitions.Player; +using SlipeServer.Packets.Enums; using SlipeServer.Server.Clients; using SlipeServer.Server.Elements; +using System; +using System.Net.Sockets; namespace SlipeServer.Server.TestTools; @@ -8,10 +16,54 @@ public class TestingClient : Client { public ulong Address { get; private set; } + public event Action PacketSent; public TestingClient(ulong address, INetWrapper netWrapper, Player player) : base(address, netWrapper, player) + { this.Address = address; this.ConnectionState = Enums.ClientConnectionState.Joined; } + + public override void SendPacket(Packet packet) + { + base.SendPacket(packet); + PacketSent?.Invoke(this, packet); + } + + public override void SendPacket(PacketId packetId, byte[] data, PacketPriority priority = PacketPriority.Medium, PacketReliability reliability = PacketReliability.Unreliable) + { + base.SendPacket(packetId, data, priority, reliability); + Packet? packet = null; + switch (packetId) + { + case PacketId.PACKET_ID_LUA_ELEMENT_RPC: + var elementRpcFunction = (ElementRpcFunction)data[0]; + switch (elementRpcFunction) + { + case ElementRpcFunction.SET_ELEMENT_POSITION: + packet = new SetElementPositionRpcPacket(); + break; + } + break; + case PacketId.PACKET_ID_ENTITY_ADD: + packet = new AddEntityPacket(); + break; + case PacketId.PACKET_ID_PLAYER_LIST: + packet = new PlayerListPacket(); + break; + case PacketId.PACKET_ID_PLAYER_CHANGE_NICK: + packet = new ChangeNicknamePacket(); + break; + default: + throw new NotImplementedException(packetId.ToString()); + + } + + if(packet != null) + { + packet.Read(data); + PacketSent?.Invoke(this, packet); + } + } } diff --git a/SlipeServer.Server.TestTools/TestingPlayer.cs b/SlipeServer.Server.TestTools/TestingPlayer.cs index 131632dd..2c91089f 100644 --- a/SlipeServer.Server.TestTools/TestingPlayer.cs +++ b/SlipeServer.Server.TestTools/TestingPlayer.cs @@ -1,6 +1,19 @@ using Moq; using SlipeServer.Net.Wrappers; +using SlipeServer.Packets; +using SlipeServer.Packets.Definitions.Join; +using SlipeServer.Packets.Definitions.Lua.ElementRpc.Element; +using SlipeServer.Packets.Definitions.Player; +using SlipeServer.Packets.Definitions.Sync; +using SlipeServer.Packets.Rpc; +using SlipeServer.Packets.Structs; +using SlipeServer.Server.ElementCollections; using SlipeServer.Server.Elements; +using SlipeServer.Server.Elements.Events; +using SlipeServer.Server.PacketHandling.Handlers.Rpc; +using System; +using System.Collections.Generic; +using System.Numerics; namespace SlipeServer.Server.TestTools; @@ -14,3 +27,156 @@ public static Player CreateStandalone() return player; } } + +public enum EventSide +{ + Client, + Server +} + +public class ClientElementCollection +{ + private readonly object @lock = new(); + private readonly Dictionary elements = []; + + public void Add(ClientElement clientElement) + { + lock (@lock) + { + this.elements.TryAdd(clientElement.ElementId, clientElement); + } + } + + public void Remove(ClientElement clientElement) + { + lock (@lock) + { + this.elements.Remove(clientElement.ElementId); + } + } + + public ClientElement Get(ElementId elementId) + { + lock (@lock) + { + if (this.elements.TryGetValue(elementId, out var clientElement)) + return clientElement; + throw new KeyNotFoundException($"Element of id {elementId} not found."); + } + } +} + +public class ClientElement +{ + public TimeContext TimeContext { get; } = new(); + public ElementId ElementId { get; } + + + public event Action? PositionChanged; + public Vector3 Position { get; private set; } + public event Action? NameChanged; + public string Name { get; private set; } + + public ClientElement(ElementId elementId) + { + this.ElementId = elementId; + } + + public void SetPosition(Vector3 position, EventSide eventSide = EventSide.Client) + { + this.Position = position; + this.PositionChanged?.Invoke(this, position, eventSide); + } + + public void SetName(string name, EventSide eventSide = EventSide.Client) + { + this.Name = name; + this.NameChanged?.Invoke(this, name, eventSide); + } +} + +public class ClientPlayer : ClientElement where TPlayer : Player +{ + public ClientElementCollection ElementCollection { get; } = new(); + + public TestingServer TestingServer { get; } + public TPlayer ServerPlayer { get; } + public TestingClient Client { get; } + + public ClientPlayer(TestingServer testingServer, TPlayer testingPlayer) : base(testingPlayer.Id) + { + this.TestingServer = testingServer; + this.ServerPlayer = testingPlayer; + this.Client = testingPlayer.Client as TestingClient; + this.Client.PacketSent += HandlePacketSent; + this.ServerPlayer.Disconnected += HandleDisconnected; + + this.ElementCollection.Add(this); + this.Client.SendPacket(new RpcPacket + { + FunctionId = Packets.Enums.RpcFunctions.INITIAL_DATA_STREAM + }); + } + + private void HandleDisconnected(Player sender, PlayerQuitEventArgs e) + { + if (sender.Client is TestingClient testingClient) + testingClient.PacketSent -= HandlePacketSent; + } + + private void HandlePacketSent(TestingClient client, Packet packet) + { + switch (packet) + { + case RpcPacket rpcPacket: + this.TestingServer.GetRequiredService().HandlePacket(client, rpcPacket); + break; + case SetElementPositionRpcPacket setElementPositionRpcPacket: + { + var element = this.ElementCollection.Get(setElementPositionRpcPacket.ElementId); + element.SetPosition(setElementPositionRpcPacket.Position, EventSide.Server); + } + break; + case AddEntityPacket addEntityPacket: + + break; + case PlayerListPacket playerListPacket: + ; + break; + case SetSyncSettingsPacket setSyncSettingsPacket: + + break; + case SetSyncIntervalPacket setSyncIntervalPacket: + + break; + case PlayerPureSyncPacket playerPureSyncPacket: + + break; + case ReturnSyncPacket returnSyncPacket: + + break; + case ChangeNicknamePacket changeNicknamePacket: + { + var element = this.ElementCollection.Get(changeNicknamePacket.PlayerId); + element.SetName(changeNicknamePacket.Name, EventSide.Server); + } + + break; + default: + throw new NotImplementedException(packet.ToString()); + } + } + + /// + /// This is reimplementation of this function: void CNetAPI::DoPulse() + /// + public void SynchronizeWithServer() + { + var packet = new PlayerPureSyncPacket + { + Position = this.Position, + }; + + this.Client.SendPacket(packet); + } +} diff --git a/SlipeServer.Server.TestTools/TestingServer.cs b/SlipeServer.Server.TestTools/TestingServer.cs index 8aaa4dc1..00c649b0 100644 --- a/SlipeServer.Server.TestTools/TestingServer.cs +++ b/SlipeServer.Server.TestTools/TestingServer.cs @@ -11,12 +11,16 @@ using SlipeServer.Packets.Lua.Event; using SlipeServer.Server.Clients; using SlipeServer.Server.Elements; +using SlipeServer.Server.PacketHandling.Handlers.QueueHandlers; +using SlipeServer.Server.PacketHandling.Handlers; using SlipeServer.Server.Resources.Serving; using SlipeServer.Server.ServerBuilders; using System; using System.Collections.Generic; using System.Linq; using ILogger = Microsoft.Extensions.Logging.ILogger; +using System.Diagnostics.Tracing; +using SlipeServer.Server.PacketHandling.Handlers.Rpc; namespace SlipeServer.Server.TestTools; @@ -76,6 +80,7 @@ private void SetupSendPacketMocks() this.NetWrapperMock.Setup(x => x.SendPacket(It.IsAny(), It.IsAny(), It.IsAny())) .Callback((ulong address, ushort version, Packet packet) => { + var data = packet.Write(); this.sendPacketCalls.Add(new SendPacketCall() { Address = address, @@ -85,6 +90,7 @@ private void SetupSendPacketMocks() Priority = packet.Priority, Reliability = packet.Reliability }); + this.EnqueueIncomingPacket(this.NetWrapperMock.Object, address, packet.PacketId, data, null); }); this.NetWrapperMock.Setup(x => x.SendPacket( @@ -111,11 +117,18 @@ private void SetupSendPacketMocks() public static void ConfigureOverrides(IServiceCollection services) { var httpServerMock = new Mock(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(httpServerMock.Object); services.AddLogging(); services.AddSingleton(x => x.GetRequiredService>()); } + public void FlushPacketQueueHandler() + { + GetRequiredService().Flush(); + } + public TPlayer AddFakePlayer() { var address = ++this.binaryAddressCounter; @@ -196,6 +209,12 @@ public void VerifyLuaEventTriggered(string eventName, TPlayer to, Element source public uint GenerateBinaryAddress() => ++this.binaryAddressCounter; + public ClientPlayer CreateClientPlayer(TPlayer player) + { + var clientPlayer = new ClientPlayer(this, player); + this.FlushPacketQueueHandler(); + return clientPlayer; + } /// /// Starts the networking interfaces, allowing clients to connect and packets to be sent out to clients. @@ -212,6 +231,11 @@ public override void Start() this.IsRunning = true; } + public override void RegisterPacketHandler(params object[] parameters) + { + RegisterPacketHandler, TPacketHandler>(); + } + public override void Stop() { Stopped?.Invoke(); diff --git a/SlipeServer.Server.Tests/Integration/ClientPlayerTests.cs b/SlipeServer.Server.Tests/Integration/ClientPlayerTests.cs new file mode 100644 index 00000000..7594d401 --- /dev/null +++ b/SlipeServer.Server.Tests/Integration/ClientPlayerTests.cs @@ -0,0 +1,37 @@ +using FluentAssertions; +using FluentAssertions.Execution; +using SlipeServer.Server.TestTools; +using System.Numerics; +using Xunit; + +namespace SlipeServer.Server.Tests.Integration; + +public class ClientPlayerTests +{ + [Fact] + public void SettingGettingPositionShouldWork() + { + var server = new TestingServer(); + server.AddDefaultPacketHandlers(); + var player1 = server.AddFakePlayer(); + player1.Name = "player1"; + var clientPlayer1 = server.CreateClientPlayer(player1); + var player2 = server.AddFakePlayer(); + player2.Name = "player2"; + var clientPlayer2 = server.CreateClientPlayer(player2); + + using var monitor = clientPlayer1.Monitor(); + player1.Position = new Vector3(3, 3, 3); + var wasClientPosition = player1.Position; + clientPlayer1.SetPosition(new Vector3(5, 0, 0)); + + clientPlayer1.SynchronizeWithServer(); + server.FlushPacketQueueHandler(); + + using var _ = new AssertionScope(); + + wasClientPosition.Should().Be(new Vector3(3, 3, 3)); + player1.Position.Should().Be(new Vector3(5, 0, 0)); + clientPlayer2.ElementCollection.Get(player1.Id).Position.Should().Be(new Vector3(5, 0, 0)); + } +} diff --git a/SlipeServer.Server.Tests/Integration/HostingTests.cs b/SlipeServer.Server.Tests/Integration/HostingTests.cs index 42bea129..59a8207f 100644 --- a/SlipeServer.Server.Tests/Integration/HostingTests.cs +++ b/SlipeServer.Server.Tests/Integration/HostingTests.cs @@ -5,10 +5,7 @@ using System.Threading.Tasks; using System.Threading; using FluentAssertions; -using SlipeServer.Hosting; using System.Net.Http; -using Moq; -using Microsoft.Extensions.Logging; using SlipeServer.Server.Elements; using System; using System.Linq; diff --git a/SlipeServer.Server.Tests/Unit/Elements/ElementTests.cs b/SlipeServer.Server.Tests/Unit/Elements/ElementTests.cs index 3872d9ba..415712bf 100644 --- a/SlipeServer.Server.Tests/Unit/Elements/ElementTests.cs +++ b/SlipeServer.Server.Tests/Unit/Elements/ElementTests.cs @@ -13,7 +13,7 @@ public void GetAndIncrementTimeContext_ReturnsNewTimeContext() var element = new Element(); var context = element.TimeContext; - var incrementContext = element.GetAndIncrementTimeContext(); + var incrementContext = element.TimeContext.GetAndIncrement(); context.Should().NotBe(incrementContext); } @@ -25,7 +25,7 @@ public void GetAndIncrementTimeContext_WrapsAroundSkippingZero() for (int i = 0; i < 255; i++) { - element.GetAndIncrementTimeContext(); + element.TimeContext.GetAndIncrement(); } var context = element.TimeContext; diff --git a/SlipeServer.Server/Clients/Client.cs b/SlipeServer.Server/Clients/Client.cs index df2dcccb..791365e6 100644 --- a/SlipeServer.Server/Clients/Client.cs +++ b/SlipeServer.Server/Clients/Client.cs @@ -92,7 +92,7 @@ public Client(ulong binaryAddress, INetWrapper netWrapper, TPlayer player) /// Sends a single packet to the client /// /// - public void SendPacket(Packet packet) + public virtual void SendPacket(Packet packet) { if (!CanSendPacket(packet.PacketId)) return; @@ -106,7 +106,7 @@ public void SendPacket(Packet packet) /// Sends a single packet to the client /// /// - public void SendPacket(PacketId packetId, byte[] data, PacketPriority priority = PacketPriority.Medium, PacketReliability reliability = PacketReliability.Unreliable) + public virtual void SendPacket(PacketId packetId, byte[] data, PacketPriority priority = PacketPriority.Medium, PacketReliability reliability = PacketReliability.Unreliable) { if (!CanSendPacket(packetId)) return; diff --git a/SlipeServer.Server/ElementConcepts/Camera.cs b/SlipeServer.Server/ElementConcepts/Camera.cs index 11238223..f617501c 100644 --- a/SlipeServer.Server/ElementConcepts/Camera.cs +++ b/SlipeServer.Server/ElementConcepts/Camera.cs @@ -87,6 +87,6 @@ public void SetMatrix(Vector3 position, Vector3 lookAt, float roll = 0, float fo this.target = null; this.Position = position; this.LookAt = lookAt; - this.player.Client.SendPacket(new SetCameraMatrixPacket(position, lookAt, roll, fov, this.player.GetAndIncrementTimeContext())); + this.player.Client.SendPacket(new SetCameraMatrixPacket(position, lookAt, roll, fov, this.player.TimeContext.GetAndIncrement())); } } diff --git a/SlipeServer.Server/Elements/Element.cs b/SlipeServer.Server/Elements/Element.cs index 6ea36479..6e840249 100644 --- a/SlipeServer.Server/Elements/Element.cs +++ b/SlipeServer.Server/Elements/Element.cs @@ -75,12 +75,7 @@ public ElementId Id } } - private readonly object timeContextLock = new(); - /// - /// The time sync context, this is a value used to verify whether synchronisation packets are to be applied or ignored. - /// - public byte TimeContext { get; private set; } - + public TimeContext TimeContext { get; } private string name = ""; /// @@ -386,7 +381,7 @@ public Element() this.associatedPlayers = []; this.subscribers = []; this.attachedElements = []; - this.TimeContext = 1; + this.TimeContext = new(); this.ElementData = []; this.ElementDataSubscriptions = new(); @@ -425,22 +420,6 @@ public void RemoveSubscriber(Player player) player.UnsubscribeFrom(this); } - /// - /// Returns a new time context, to be used when sync updates sent prior to this moment are meant to be invaldiated. - /// - /// The new time context - public byte GetAndIncrementTimeContext() - { - lock (this.timeContextLock) - { - if (++this.TimeContext == 0) - { - this.TimeContext++; - } - return this.TimeContext; - } - } - /// /// Indicates whether a specific time context is valid to update this element /// diff --git a/SlipeServer.Server/Elements/TimeContext.cs b/SlipeServer.Server/Elements/TimeContext.cs new file mode 100644 index 00000000..830c79a9 --- /dev/null +++ b/SlipeServer.Server/Elements/TimeContext.cs @@ -0,0 +1,48 @@ +namespace SlipeServer.Server.Elements; + +public struct TimeContext +{ + private readonly object @lock; + private byte current; + + public TimeContext() + { + this.current = 1; + this.@lock = new(); + } + + /// + /// The current time context value, used to verify whether synchronization packets are to be applied or ignored. + /// + public byte Current + { + get + { + lock (this.@lock) + { + return this.current; + } + } + } + + /// + /// Generates and returns a new time context. This can be used to invalidate synchronization updates sent prior to this moment. + /// + /// The new time context value. + public byte GetAndIncrement() + { + lock (this.@lock) + { + if (++this.current == 0) + { + this.current++; + } + return this.current; + } + } + + public static implicit operator byte(TimeContext manager) + { + return manager.Current; + } +} diff --git a/SlipeServer.Server/Extensions/Relaying/PedPropertyRelayingExtensions.cs b/SlipeServer.Server/Extensions/Relaying/PedPropertyRelayingExtensions.cs index 72122681..9fab18fd 100644 --- a/SlipeServer.Server/Extensions/Relaying/PedPropertyRelayingExtensions.cs +++ b/SlipeServer.Server/Extensions/Relaying/PedPropertyRelayingExtensions.cs @@ -39,7 +39,7 @@ public static void AddPedRelayers(this Ped ped) private static void RelayPedWasted(Ped sender, PedWastedEventArgs e) { var packet = new PedWastedPacket( - e.Source.Id, e.Killer?.Id ?? ElementId.Zero, (byte)e.WeaponType, (byte)e.BodyPart, e.Ammo, false, sender.GetAndIncrementTimeContext(), e.AnimationGroup, e.AnimationId + e.Source.Id, e.Killer?.Id ?? ElementId.Zero, (byte)e.WeaponType, (byte)e.BodyPart, e.Ammo, false, sender.TimeContext.GetAndIncrement(), e.AnimationGroup, e.AnimationId ) { Ammo = e.Ammo diff --git a/SlipeServer.Server/Extensions/Relaying/VehiclePropertyRelayingExtensions.cs b/SlipeServer.Server/Extensions/Relaying/VehiclePropertyRelayingExtensions.cs index fe7588ef..219b1310 100644 --- a/SlipeServer.Server/Extensions/Relaying/VehiclePropertyRelayingExtensions.cs +++ b/SlipeServer.Server/Extensions/Relaying/VehiclePropertyRelayingExtensions.cs @@ -262,7 +262,7 @@ private static void RelayAreSirensOn(Vehicle sender, ElementChangedEventArgs args) { if (!args.IsSync) - sender.RelayChange(new SetElementHealthRpcPacket(sender.Id, sender.GetAndIncrementTimeContext(), args.NewValue)); + sender.RelayChange(new SetElementHealthRpcPacket(sender.Id, sender.TimeContext.GetAndIncrement(), args.NewValue)); } private static void RelayRespawn(Element sender, VehicleRespawnEventArgs args) @@ -270,7 +270,7 @@ private static void RelayRespawn(Element sender, VehicleRespawnEventArgs args) sender.RelayChange(new VehicleSpawnPacket(new VehicleSpawnInfo[] { new VehicleSpawnInfo { ElementId = args.Vehicle.Id, - TimeContext = args.Vehicle.GetAndIncrementTimeContext(), + TimeContext = args.Vehicle.TimeContext.GetAndIncrement(), VehicleId = args.Vehicle.Model, Position = args.Vehicle.RespawnPosition, Rotation = args.Vehicle.RespawnRotation, @@ -286,7 +286,7 @@ private static void RelayEnter(Element sender, VehicleEnteredEventsArgs eventArg eventArgs.Ped.Id, eventArgs.Vehicle.Id, eventArgs.Seat, - eventArgs.Ped.GetAndIncrementTimeContext() + eventArgs.Ped.TimeContext.GetAndIncrement() )); } } @@ -297,7 +297,7 @@ private static void RelayeLeft(Element sender, VehicleLeftEventArgs eventArgs) { sender.RelayChange(new RemoveFromVehiclePacket( eventArgs.Ped.Id, - eventArgs.Ped.GetAndIncrementTimeContext() + eventArgs.Ped.TimeContext.GetAndIncrement() )); } } diff --git a/SlipeServer.Server/MtaServer.cs b/SlipeServer.Server/MtaServer.cs index 99f7552d..b5d8987c 100644 --- a/SlipeServer.Server/MtaServer.cs +++ b/SlipeServer.Server/MtaServer.cs @@ -301,7 +301,7 @@ public void RegisterPacketHandler( /// /// /// - public void RegisterPacketHandler(params object[] parameters) + public virtual void RegisterPacketHandler(params object[] parameters) where TPacket : Packet, new() where TPacketHandler : IPacketHandler { @@ -557,7 +557,7 @@ public void RegisterNetWrapper(INetWrapper netWrapper) this.clients[netWrapper] = new(); } - private void EnqueueIncomingPacket(INetWrapper netWrapper, ulong binaryAddress, PacketId packetId, byte[] data, uint? ping) + protected void EnqueueIncomingPacket(INetWrapper netWrapper, ulong binaryAddress, PacketId packetId, byte[] data, uint? ping) { if (!this.clients[netWrapper].ContainsKey(binaryAddress)) { diff --git a/SlipeServer.Server/PacketHandling/Factories/ElementPacketFactory.cs b/SlipeServer.Server/PacketHandling/Factories/ElementPacketFactory.cs index e3f813a2..ed0ffdf2 100644 --- a/SlipeServer.Server/PacketHandling/Factories/ElementPacketFactory.cs +++ b/SlipeServer.Server/PacketHandling/Factories/ElementPacketFactory.cs @@ -11,15 +11,15 @@ public static class ElementPacketFactory { public static SetElementPositionRpcPacket CreateSetPositionPacket(Element element, Vector3 position, bool isWarp = false) { - return new SetElementPositionRpcPacket(element.Id, element.GetAndIncrementTimeContext(), position, isWarp); + return new SetElementPositionRpcPacket(element.Id, element.TimeContext.GetAndIncrement(), position, isWarp); } public static Packet CreateSetRotationPacket(Element element, Vector3 rotation) { return element switch { - Vehicle => new SetVehicleRotationRpcPacket(element.Id, element.GetAndIncrementTimeContext(), rotation), - Ped => new SetPedRotationRpcPacket(element.Id, element.GetAndIncrementTimeContext(), rotation.Z / (180 / MathF.PI), true), + Vehicle => new SetVehicleRotationRpcPacket(element.Id, element.TimeContext.GetAndIncrement(), rotation), + Ped => new SetPedRotationRpcPacket(element.Id, element.TimeContext.GetAndIncrement(), rotation.Z / (180 / MathF.PI), true), WorldObject => new SetObjectRotationRpcPacket(element.Id, rotation), _ => throw new NotImplementedException($"Can not create set rotation packet for {element.GetType()}"), }; @@ -27,12 +27,12 @@ public static Packet CreateSetRotationPacket(Element element, Vector3 rotation) public static FixVehicleRpcPacket CreateFixVehiclePacket(Element element) { - return new FixVehicleRpcPacket(element.Id, element.GetAndIncrementTimeContext()); + return new FixVehicleRpcPacket(element.Id, element.TimeContext.GetAndIncrement()); } public static SetElementHealthRpcPacket CreateSetHealthPacket(Element element, float health) { - return new SetElementHealthRpcPacket(element.Id, element.GetAndIncrementTimeContext(), health); + return new SetElementHealthRpcPacket(element.Id, element.TimeContext.GetAndIncrement(), health); } public static SetElementAlphaRpcPacket CreateSetAlphaPacket(Element element, byte alpha) diff --git a/SlipeServer.Server/PacketHandling/Factories/PedPacketFactory.cs b/SlipeServer.Server/PacketHandling/Factories/PedPacketFactory.cs index e21ea8b8..c984ec34 100644 --- a/SlipeServer.Server/PacketHandling/Factories/PedPacketFactory.cs +++ b/SlipeServer.Server/PacketHandling/Factories/PedPacketFactory.cs @@ -20,12 +20,12 @@ public static SetElementModelRpcPacket CreateSetModelPacket(Ped ped) public static SetElementHealthRpcPacket CreateSetHealthPacket(Ped ped) { - return new SetElementHealthRpcPacket(ped.Id, ped.GetAndIncrementTimeContext(), ped.Health); + return new SetElementHealthRpcPacket(ped.Id, ped.TimeContext.GetAndIncrement(), ped.Health); } public static SetPedArmourRpcPacket CreateSetArmourPacket(Ped ped) { - return new SetPedArmourRpcPacket(ped.Id, ped.GetAndIncrementTimeContext(), ped.Armor); + return new SetPedArmourRpcPacket(ped.Id, ped.TimeContext.GetAndIncrement(), ped.Armor); } public static GivePedJetpackRpcPacket CreateGiveJetpack(Ped ped) diff --git a/SlipeServer.Server/PacketHandling/Factories/PlayerPacketFactory.cs b/SlipeServer.Server/PacketHandling/Factories/PlayerPacketFactory.cs index 753f61e4..14f46f75 100644 --- a/SlipeServer.Server/PacketHandling/Factories/PlayerPacketFactory.cs +++ b/SlipeServer.Server/PacketHandling/Factories/PlayerPacketFactory.cs @@ -22,7 +22,10 @@ public static HudComponentVisiblePacket CreateShowHudComponentPacket(HudComponen public static PlayerListPacket CreatePlayerListPacket(Player[] players, bool showInChat = false) { - var packet = new PlayerListPacket(showInChat); + var packet = new PlayerListPacket + { + ShowInChat = showInChat + }; foreach (var player in players) { @@ -125,7 +128,7 @@ public static PlayerQuitPacket CreateQuitPacket(Player player, QuitReason reason public static SpawnPlayerPacket CreateSpawnPacket(Player player) { - return new SpawnPlayerPacket(player.Id, 0, player.Position, player.PedRotation, player.Model, ElementId.Zero, player.Interior, player.Dimension, player.GetAndIncrementTimeContext()); + return new SpawnPlayerPacket(player.Id, 0, player.Position, player.PedRotation, player.Model, ElementId.Zero, player.Interior, player.Dimension, player.TimeContext.GetAndIncrement()); } public static PlayerWastedPacket CreateWastedPacket( @@ -134,12 +137,16 @@ public static PlayerWastedPacket CreateWastedPacket( ) { return new PlayerWastedPacket(player.Id, killer?.Id ?? ElementId.Zero, (byte)weaponType, (byte)bodyPart, isStealth, - player.GetAndIncrementTimeContext(), animationGroup, animationId); + player.TimeContext.GetAndIncrement(), animationGroup, animationId); } public static ChangeNicknamePacket CreateNicknameChangePacket(Player player) { - return new ChangeNicknamePacket(player.Id, player.Name); + return new ChangeNicknamePacket + { + PlayerId = player.Id, + Name = player.Name + }; } public static UpdateInfoPacket CreateUpdateInfoPacket(Version version, bool mandatory = true) diff --git a/SlipeServer.Server/PacketHandling/Factories/VehiclePacketFactory.cs b/SlipeServer.Server/PacketHandling/Factories/VehiclePacketFactory.cs index afe348f1..b47d1b2a 100644 --- a/SlipeServer.Server/PacketHandling/Factories/VehiclePacketFactory.cs +++ b/SlipeServer.Server/PacketHandling/Factories/VehiclePacketFactory.cs @@ -107,11 +107,11 @@ public static DestroyAllVehiclesRpcPacket CreateDestroyAllPacket() public static FixVehicleRpcPacket CreateFixVehiclePacket(Vehicle vehicle) { - return new FixVehicleRpcPacket(vehicle.Id, vehicle.GetAndIncrementTimeContext()); + return new FixVehicleRpcPacket(vehicle.Id, vehicle.TimeContext.GetAndIncrement()); } public static VehicleBlownRpcPacket CreateBlownVehiclePacket(Vehicle vehicle, bool createExplosion) { - return new VehicleBlownRpcPacket(vehicle.Id, vehicle.GetAndIncrementTimeContext(), createExplosion); + return new VehicleBlownRpcPacket(vehicle.Id, vehicle.TimeContext.GetAndIncrement(), createExplosion); } }