diff --git a/Ragon.Client.Property/Sources/RagonBool.cs b/Ragon.Client.Property/Sources/RagonBool.cs index 2688b07..ba87207 100644 --- a/Ragon.Client.Property/Sources/RagonBool.cs +++ b/Ragon.Client.Property/Sources/RagonBool.cs @@ -17,7 +17,7 @@ using Ragon.Protocol; -namespace Ragon.Client.Simulation +namespace Ragon.Client.Property { [Serializable] public class RagonBool : RagonProperty diff --git a/Ragon.Client.Property/Sources/RagonInt.cs b/Ragon.Client.Property/Sources/RagonInt.cs index 063a5b2..b8498d7 100644 --- a/Ragon.Client.Property/Sources/RagonInt.cs +++ b/Ragon.Client.Property/Sources/RagonInt.cs @@ -17,7 +17,7 @@ using Ragon.Protocol; -namespace Ragon.Client.Simulation +namespace Ragon.Client.Property { [Serializable] public class RagonInt : RagonProperty diff --git a/Ragon.Client.Property/Sources/RagonQuaternion.cs b/Ragon.Client.Property/Sources/RagonQuaternion.cs new file mode 100644 index 0000000..0b208f7 --- /dev/null +++ b/Ragon.Client.Property/Sources/RagonQuaternion.cs @@ -0,0 +1,76 @@ +/* + * Copyright 2023 Eduard Kargin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Numerics; +using Ragon.Client.Compressor; +using Ragon.Protocol; + +namespace Ragon.Client.Property; + +public class RagonQuaternion : RagonProperty +{ + private Quaternion _value; + + public Quaternion Value + { + get => _value; + set + { + _value = value; + + MarkAsChanged(); + } + } + + private readonly FloatCompressor _compressor; + + public RagonQuaternion(bool invokeLocal = false, int priority = 0) : base(priority, invokeLocal) + { + _compressor = new FloatCompressor(-1.0f, 1f, 0.01f); + + SetFixedSize(_compressor.RequiredBits * 4); + } + + public override void Serialize(RagonBuffer buffer) + { + var compressedX = _compressor.Compress(_value.X); + var compressedY = _compressor.Compress(_value.Y); + var compressedZ = _compressor.Compress(_value.Z); + var compressedW = _compressor.Compress(_value.W); + + buffer.Write(compressedX, _compressor.RequiredBits); + buffer.Write(compressedY, _compressor.RequiredBits); + buffer.Write(compressedZ, _compressor.RequiredBits); + buffer.Write(compressedW, _compressor.RequiredBits); + } + + public override void Deserialize(RagonBuffer buffer) + { + var compressedX = buffer.Read(_compressor.RequiredBits); + var compressedY = buffer.Read(_compressor.RequiredBits); + var compressedZ = buffer.Read(_compressor.RequiredBits); + var compressedW = buffer.Read(_compressor.RequiredBits); + + var x = _compressor.Decompress(compressedX); + var y = _compressor.Decompress(compressedY); + var z = _compressor.Decompress(compressedZ); + var w = _compressor.Decompress(compressedW); + + _value = new Quaternion(x, y, z, w); + + InvokeChanged(); + } +} \ No newline at end of file diff --git a/Ragon.Client.Property/Sources/RagonString.cs b/Ragon.Client.Property/Sources/RagonString.cs index 0f7ca94..7733d26 100644 --- a/Ragon.Client.Property/Sources/RagonString.cs +++ b/Ragon.Client.Property/Sources/RagonString.cs @@ -18,7 +18,7 @@ using System.Text; using Ragon.Protocol; -namespace Ragon.Client.Simulation +namespace Ragon.Client.Property { [Serializable] public class RagonString : RagonProperty diff --git a/Ragon.Client.Property/Sources/RagonVector3.cs b/Ragon.Client.Property/Sources/RagonVector3.cs new file mode 100644 index 0000000..750f2b4 --- /dev/null +++ b/Ragon.Client.Property/Sources/RagonVector3.cs @@ -0,0 +1,330 @@ +/* + * Copyright 2023 Eduard Kargin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Numerics; +using Ragon.Client.Compressor; +using Ragon.Protocol; + +namespace Ragon.Client.Property +{ + [Serializable] + public enum RagonAxis + { + XYZ, + XY, + YZ, + XZ, + X, + Y, + Z + } + + [Serializable] + public class RagonVector3 : RagonProperty + { + private Vector3 _value; + + public Vector3 Value + { + get => _value; + set + { + _value = value; + + MarkAsChanged(); + } + } + + private RagonAxis _axis; + private FloatCompressor _compressorX; + private FloatCompressor _compressorY; + private FloatCompressor _compressorZ; + + + public RagonVector3( + RagonAxis axis = RagonAxis.XYZ, + bool invokeLocal = true, + int priority = 0 + ) : base(priority, invokeLocal) + { + _axis = axis; + + var defaultCompressor = new FloatCompressor(-1024.0f, 1024f, 0.01f); + + _compressorX = defaultCompressor; + _compressorY = defaultCompressor; + _compressorZ = defaultCompressor; + + switch (_axis) + { + case RagonAxis.XYZ: + SetFixedSize(_compressorX.RequiredBits + _compressorY.RequiredBits + _compressorZ.RequiredBits); + break; + case RagonAxis.XY: + SetFixedSize(_compressorX.RequiredBits + _compressorY.RequiredBits); + break; + case RagonAxis.XZ: + SetFixedSize(_compressorX.RequiredBits + _compressorZ.RequiredBits); + break; + case RagonAxis.YZ: + SetFixedSize(_compressorY.RequiredBits + _compressorZ.RequiredBits); + break; + case RagonAxis.X: + SetFixedSize(_compressorX.RequiredBits); + break; + case RagonAxis.Y: + SetFixedSize(_compressorY.RequiredBits); + break; + case RagonAxis.Z: + SetFixedSize(_compressorZ.RequiredBits); + break; + } + } + + public RagonVector3( + Vector3 initialValue, + RagonAxis axis = RagonAxis.XYZ, + float min = -1024.0f, + float max = 1024.0f, + float precision = 0.1f, + bool invokeLocal = true, + int priority = 0 + ) : base(priority, invokeLocal) + { + _value = initialValue; + _axis = axis; + + var defaultCompressor = new FloatCompressor(min, max, precision); + + _compressorX = defaultCompressor; + _compressorY = defaultCompressor; + _compressorZ = defaultCompressor; + + switch (_axis) + { + case RagonAxis.XYZ: + SetFixedSize(_compressorX.RequiredBits + _compressorY.RequiredBits + _compressorZ.RequiredBits); + break; + case RagonAxis.XY: + SetFixedSize(_compressorX.RequiredBits + _compressorY.RequiredBits); + break; + case RagonAxis.XZ: + SetFixedSize(_compressorX.RequiredBits + _compressorZ.RequiredBits); + break; + case RagonAxis.YZ: + SetFixedSize(_compressorY.RequiredBits + _compressorZ.RequiredBits); + break; + case RagonAxis.X: + SetFixedSize(_compressorX.RequiredBits); + break; + case RagonAxis.Y: + SetFixedSize(_compressorY.RequiredBits); + break; + case RagonAxis.Z: + SetFixedSize(_compressorZ.RequiredBits); + break; + } + } + + public RagonVector3( + RagonAxis axis = RagonAxis.XYZ, + FloatCompressor compressorX = null, + FloatCompressor compressorY = null, + FloatCompressor compressorZ = null, + bool invokeLocal = true, + int priority = 0 + ) : base(priority, invokeLocal) + { + _axis = axis; + + var defaultCompressor = new FloatCompressor(-1024.0f, 1024f, 0.01f); + + _compressorX = defaultCompressor; + _compressorY = defaultCompressor; + _compressorZ = defaultCompressor; + + if (compressorX != null) + _compressorX = compressorX; + + if (compressorY != null) + _compressorY = compressorY; + + if (compressorZ != null) + _compressorZ = compressorZ; + + switch (_axis) + { + case RagonAxis.XYZ: + SetFixedSize(_compressorX.RequiredBits + _compressorY.RequiredBits + _compressorZ.RequiredBits); + break; + case RagonAxis.XY: + SetFixedSize(_compressorX.RequiredBits + _compressorY.RequiredBits); + break; + case RagonAxis.XZ: + SetFixedSize(_compressorX.RequiredBits + _compressorZ.RequiredBits); + break; + case RagonAxis.YZ: + SetFixedSize(_compressorY.RequiredBits + _compressorZ.RequiredBits); + break; + case RagonAxis.X: + SetFixedSize(_compressorX.RequiredBits); + break; + case RagonAxis.Y: + SetFixedSize(_compressorY.RequiredBits); + break; + case RagonAxis.Z: + SetFixedSize(_compressorZ.RequiredBits); + break; + } + } + + public override void Serialize(RagonBuffer buffer) + { + switch (_axis) + { + case RagonAxis.XYZ: + { + var compressedX = _compressorX.Compress(_value.X); + var compressedY = _compressorY.Compress(_value.Y); + var compressedZ = _compressorZ.Compress(_value.Z); + + buffer.Write(compressedX, _compressorX.RequiredBits); + buffer.Write(compressedY, _compressorY.RequiredBits); + buffer.Write(compressedZ, _compressorZ.RequiredBits); + } + break; + case RagonAxis.XY: + { + var compressedX = _compressorX.Compress(_value.X); + var compressedY = _compressorY.Compress(_value.Y); + + buffer.Write(compressedX, _compressorX.RequiredBits); + buffer.Write(compressedY, _compressorY.RequiredBits); + } + break; + case RagonAxis.XZ: + { + var compressedX = _compressorX.Compress(_value.X); + var compressedZ = _compressorZ.Compress(_value.Z); + + buffer.Write(compressedX, _compressorX.RequiredBits); + buffer.Write(compressedZ, _compressorZ.RequiredBits); + break; + } + + case RagonAxis.YZ: + { + var compressedY = _compressorY.Compress(_value.Y); + var compressedZ = _compressorZ.Compress(_value.Z); + + buffer.Write(compressedY, _compressorY.RequiredBits); + buffer.Write(compressedZ, _compressorZ.RequiredBits); + break; + } + case RagonAxis.X: + { + var compressedX = _compressorX.Compress(_value.X); + + buffer.Write(compressedX, _compressorX.RequiredBits); + break; + } + case RagonAxis.Y: + { + var compressedY = _compressorY.Compress(_value.Y); + + buffer.Write(compressedY, _compressorY.RequiredBits); + break; + } + case RagonAxis.Z: + { + var compressedZ = _compressorZ.Compress(_value.Z); + + buffer.Write(compressedZ, _compressorZ.RequiredBits); + break; + } + } + } + + public override void Deserialize(RagonBuffer buffer) + { + switch (_axis) + { + case RagonAxis.XYZ: + { + var compressedX = buffer.Read(_compressorX.RequiredBits); + var compressedY = buffer.Read(_compressorY.RequiredBits); + var compressedZ = buffer.Read(_compressorZ.RequiredBits); + + _value.X = _compressorX.Decompress(compressedX); + _value.Y = _compressorY.Decompress(compressedY); + _value.Z = _compressorZ.Decompress(compressedZ); + break; + } + case RagonAxis.XY: + { + var compressedX = buffer.Read(_compressorX.RequiredBits); + var compressedY = buffer.Read(_compressorY.RequiredBits); + + _value.X = _compressorX.Decompress(compressedX); + _value.Z = _compressorY.Decompress(compressedY); + break; + } + case RagonAxis.XZ: + { + var compressedX = buffer.Read(_compressorX.RequiredBits); + var compressedZ = buffer.Read(_compressorZ.RequiredBits); + + _value.X = _compressorX.Decompress(compressedX); + _value.Z = _compressorZ.Decompress(compressedZ); + break; + } + case RagonAxis.YZ: + { + var compressedY = buffer.Read(_compressorY.RequiredBits); + var compressedZ = buffer.Read(_compressorZ.RequiredBits); + + _value.Y = _compressorY.Decompress(compressedY); + _value.Z = _compressorZ.Decompress(compressedZ); + break; + } + case RagonAxis.X: + { + var compressedX = buffer.Read(_compressorX.RequiredBits); + + _value.X = _compressorX.Decompress(compressedX); + break; + } + case RagonAxis.Y: + { + var compressedY = buffer.Read(_compressorY.RequiredBits); + + _value.Y = _compressorY.Decompress(compressedY); + break; + } + case RagonAxis.Z: + { + var compressedZ = buffer.Read(_compressorZ.RequiredBits); + + _value.Z = _compressorZ.Decompress(compressedZ); + break; + } + } + + InvokeChanged(); + } + } +} \ No newline at end of file diff --git a/Ragon.Client/Ragon.Client.csproj b/Ragon.Client/Ragon.Client.csproj index c94409a..1e4cf11 100644 --- a/Ragon.Client/Ragon.Client.csproj +++ b/Ragon.Client/Ragon.Client.csproj @@ -12,7 +12,7 @@ true none - /Users/edmand46/RagonProjects/ragon-oss-examples/Assets/Ragon/Plugins/ + /Users/edmand46/RagonProjects/ragon-oss-examples/Assets/Ragon/Plugins/netstandard2.0/ diff --git a/Ragon.Client/Sources/Handler/AuthorizeFailedHandler.cs b/Ragon.Client/Sources/Handler/AuthorizeFailedHandler.cs index 36f0ac4..d378aac 100644 --- a/Ragon.Client/Sources/Handler/AuthorizeFailedHandler.cs +++ b/Ragon.Client/Sources/Handler/AuthorizeFailedHandler.cs @@ -19,7 +19,7 @@ using Ragon.Protocol; namespace Ragon.Client; -internal class AuthorizeFailedHandler: Handler +internal class AuthorizeFailedHandler: IHandler { private readonly RagonListenerList _listenerList; public AuthorizeFailedHandler(RagonListenerList list) diff --git a/Ragon.Client/Sources/Handler/AuthorizeSuccessHandler.cs b/Ragon.Client/Sources/Handler/AuthorizeSuccessHandler.cs index bf88717..e86fd07 100644 --- a/Ragon.Client/Sources/Handler/AuthorizeSuccessHandler.cs +++ b/Ragon.Client/Sources/Handler/AuthorizeSuccessHandler.cs @@ -19,7 +19,7 @@ using Ragon.Protocol; namespace Ragon.Client; -internal class AuthorizeSuccessHandler: Handler +internal class AuthorizeSuccessHandler: IHandler { private readonly RagonListenerList _listenerList; private readonly RagonClient _client; diff --git a/Ragon.Client/Sources/Handler/EntityCreateHandler.cs b/Ragon.Client/Sources/Handler/EntityCreateHandler.cs index 7615e23..c303dd6 100644 --- a/Ragon.Client/Sources/Handler/EntityCreateHandler.cs +++ b/Ragon.Client/Sources/Handler/EntityCreateHandler.cs @@ -18,7 +18,7 @@ using Ragon.Protocol; namespace Ragon.Client; -internal class EntityCreateHandler : Handler +internal class EntityCreateHandler : IHandler { private readonly RagonClient _client; private readonly RagonPlayerCache _playerCache; diff --git a/Ragon.Client/Sources/Handler/EntityEventHandler.cs b/Ragon.Client/Sources/Handler/EntityEventHandler.cs index fb8593d..ecffa61 100644 --- a/Ragon.Client/Sources/Handler/EntityEventHandler.cs +++ b/Ragon.Client/Sources/Handler/EntityEventHandler.cs @@ -18,19 +18,16 @@ using Ragon.Protocol; namespace Ragon.Client; -internal class EntityEventHandler : Handler +internal class EntityEventHandler : IHandler { - private readonly RagonClient _client; private readonly RagonPlayerCache _playerCache; private readonly RagonEntityCache _entityCache; public EntityEventHandler( - RagonClient client, RagonPlayerCache playerCache, RagonEntityCache entityCache ) { - _client = client; _playerCache = playerCache; _entityCache = entityCache; } diff --git a/Ragon.Client/Sources/Handler/EntityOwnershipHandler.cs b/Ragon.Client/Sources/Handler/EntityOwnershipHandler.cs index 6713819..91738a1 100644 --- a/Ragon.Client/Sources/Handler/EntityOwnershipHandler.cs +++ b/Ragon.Client/Sources/Handler/EntityOwnershipHandler.cs @@ -19,7 +19,7 @@ using Ragon.Protocol; namespace Ragon.Client; -internal class EntityOwnershipHandler: Handler +internal class EntityOwnershipHandler: IHandler { private readonly RagonListenerList _listenerList; private readonly RagonPlayerCache _playerCache; diff --git a/Ragon.Client/Sources/Handler/EntityRemoveHandler.cs b/Ragon.Client/Sources/Handler/EntityRemoveHandler.cs index 25abafe..b19f4b0 100644 --- a/Ragon.Client/Sources/Handler/EntityRemoveHandler.cs +++ b/Ragon.Client/Sources/Handler/EntityRemoveHandler.cs @@ -19,7 +19,7 @@ using Ragon.Protocol; namespace Ragon.Client; -internal class EntityRemoveHandler: Handler +internal class EntityRemoveHandler: IHandler { private readonly RagonEntityCache _entityCache; diff --git a/Ragon.Client/Sources/Handler/EntityStateHandler.cs b/Ragon.Client/Sources/Handler/EntityStateHandler.cs index d59b5dc..c8db5a0 100644 --- a/Ragon.Client/Sources/Handler/EntityStateHandler.cs +++ b/Ragon.Client/Sources/Handler/EntityStateHandler.cs @@ -18,7 +18,7 @@ using Ragon.Protocol; namespace Ragon.Client; -internal class StateEntityHandler: Handler +internal class StateEntityHandler: IHandler { private readonly RagonEntityCache _entityCache; diff --git a/Ragon.Client/Sources/Handler/EventRoomHandler.cs b/Ragon.Client/Sources/Handler/EventRoomHandler.cs new file mode 100644 index 0000000..346de17 --- /dev/null +++ b/Ragon.Client/Sources/Handler/EventRoomHandler.cs @@ -0,0 +1,53 @@ +/* + * Copyright 2023 Eduard Kargin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Ragon.Protocol; + +namespace Ragon.Client; + +public class EventRoomHandler: IHandler +{ + private readonly RagonClient _client; + private readonly RagonPlayerCache _playerCache; + + public EventRoomHandler( + RagonClient client, + RagonPlayerCache playerCache + ) + { + _client = client; + _playerCache = playerCache; + } + + public void Handle(RagonBuffer buffer) + { + var eventCode = buffer.ReadUShort(); + var peerId = buffer.ReadUShort(); + var executionMode = (RagonReplicationMode)buffer.ReadByte(); + + var player = _playerCache.GetPlayerByPeer(peerId); + if (player == null) + { + RagonLog.Warn($"Player not found for event {eventCode}"); + return; + } + + if (player.IsLocal && executionMode == RagonReplicationMode.LocalAndServer) + return; + + _client.Room.Event(eventCode, player, buffer); + } +} \ No newline at end of file diff --git a/Ragon.Client/Sources/Handler/Handler.cs b/Ragon.Client/Sources/Handler/IHandler.cs similarity index 96% rename from Ragon.Client/Sources/Handler/Handler.cs rename to Ragon.Client/Sources/Handler/IHandler.cs index ba396d6..4385782 100644 --- a/Ragon.Client/Sources/Handler/Handler.cs +++ b/Ragon.Client/Sources/Handler/IHandler.cs @@ -19,7 +19,7 @@ using Ragon.Protocol; namespace Ragon.Client; -public interface Handler +public interface IHandler { public void Handle(RagonBuffer buffer); } \ No newline at end of file diff --git a/Ragon.Client/Sources/Handler/JoinFailedHandler.cs b/Ragon.Client/Sources/Handler/JoinFailedHandler.cs index 73d3963..023017f 100644 --- a/Ragon.Client/Sources/Handler/JoinFailedHandler.cs +++ b/Ragon.Client/Sources/Handler/JoinFailedHandler.cs @@ -19,7 +19,7 @@ using Ragon.Protocol; namespace Ragon.Client; -internal class JoinFailedHandler: Handler +internal class JoinFailedHandler: IHandler { private readonly RagonListenerList _listenerList; diff --git a/Ragon.Client/Sources/Handler/JoinSuccessHandler.cs b/Ragon.Client/Sources/Handler/JoinSuccessHandler.cs index 41a7aa7..191d022 100644 --- a/Ragon.Client/Sources/Handler/JoinSuccessHandler.cs +++ b/Ragon.Client/Sources/Handler/JoinSuccessHandler.cs @@ -37,7 +37,7 @@ public struct RagonRoomInformation public ushort Max { get; private set; } } -internal class JoinSuccessHandler : Handler +internal class JoinSuccessHandler : IHandler { private readonly RagonListenerList _listenerList; private readonly RagonPlayerCache _playerCache; @@ -46,7 +46,6 @@ internal class JoinSuccessHandler : Handler public JoinSuccessHandler( RagonClient client, - RagonBuffer buffer, RagonListenerList listenerList, RagonPlayerCache playerCache, RagonEntityCache entityCache diff --git a/Ragon.Client/Sources/Handler/LeaveRoomHandler.cs b/Ragon.Client/Sources/Handler/LeaveRoomHandler.cs index bd9e6e2..3c0722e 100644 --- a/Ragon.Client/Sources/Handler/LeaveRoomHandler.cs +++ b/Ragon.Client/Sources/Handler/LeaveRoomHandler.cs @@ -19,7 +19,7 @@ using Ragon.Protocol; namespace Ragon.Client; -internal class LeaveRoomHandler : Handler +internal class LeaveRoomHandler : IHandler { private readonly RagonClient _client; private readonly RagonListenerList _listenerList; diff --git a/Ragon.Client/Sources/Handler/LoadSceneHandler.cs b/Ragon.Client/Sources/Handler/LoadSceneHandler.cs index 2f13925..f12959e 100644 --- a/Ragon.Client/Sources/Handler/LoadSceneHandler.cs +++ b/Ragon.Client/Sources/Handler/LoadSceneHandler.cs @@ -18,7 +18,7 @@ using Ragon.Protocol; namespace Ragon.Client; -internal class SceneLoadHandler: Handler +internal class SceneLoadHandler: IHandler { private readonly RagonClient _client; private readonly RagonListenerList _listenerList; diff --git a/Ragon.Client/Sources/Handler/OwnershipRoomHandler.cs b/Ragon.Client/Sources/Handler/OwnershipRoomHandler.cs index cb35c6f..74d7348 100644 --- a/Ragon.Client/Sources/Handler/OwnershipRoomHandler.cs +++ b/Ragon.Client/Sources/Handler/OwnershipRoomHandler.cs @@ -19,7 +19,7 @@ using Ragon.Protocol; namespace Ragon.Client; -internal class OwnershipRoomHandler: Handler +internal class OwnershipRoomHandler: IHandler { private readonly RagonListenerList _listenerList; private readonly RagonPlayerCache _playerCache; diff --git a/Ragon.Client/Sources/Handler/PlayerJoinHandler.cs b/Ragon.Client/Sources/Handler/PlayerJoinHandler.cs index 3057d37..e0a5aad 100644 --- a/Ragon.Client/Sources/Handler/PlayerJoinHandler.cs +++ b/Ragon.Client/Sources/Handler/PlayerJoinHandler.cs @@ -19,7 +19,7 @@ using Ragon.Protocol; namespace Ragon.Client; -internal class PlayerJoinHandler : Handler +internal class PlayerJoinHandler : IHandler { private RagonPlayerCache _playerCache; private RagonListenerList _listenerList; diff --git a/Ragon.Client/Sources/Handler/PlayerLeftHandler.cs b/Ragon.Client/Sources/Handler/PlayerLeftHandler.cs index f19080e..f0fc04e 100644 --- a/Ragon.Client/Sources/Handler/PlayerLeftHandler.cs +++ b/Ragon.Client/Sources/Handler/PlayerLeftHandler.cs @@ -19,7 +19,7 @@ using Ragon.Protocol; namespace Ragon.Client; -internal class PlayerLeftHandler : Handler +internal class PlayerLeftHandler : IHandler { private RagonPlayerCache _playerCache; private RagonEntityCache _entityCache; diff --git a/Ragon.Client/Sources/Handler/SnapshotHandler.cs b/Ragon.Client/Sources/Handler/SnapshotHandler.cs index aff6245..5ce1a7c 100644 --- a/Ragon.Client/Sources/Handler/SnapshotHandler.cs +++ b/Ragon.Client/Sources/Handler/SnapshotHandler.cs @@ -20,7 +20,7 @@ using Ragon.Protocol; namespace Ragon.Client; -internal class SnapshotHandler : Handler +internal class SnapshotHandler : IHandler { private readonly IRagonEntityListener _entityListener; private readonly RagonClient _client; diff --git a/Ragon.Client/Sources/Handler/TimestampHandler.cs b/Ragon.Client/Sources/Handler/TimestampHandler.cs new file mode 100644 index 0000000..53d40f4 --- /dev/null +++ b/Ragon.Client/Sources/Handler/TimestampHandler.cs @@ -0,0 +1,21 @@ +using Ragon.Protocol; + +namespace Ragon.Client; + +public class TimestampHandler: IHandler +{ + private readonly RagonClient _client; + public TimestampHandler(RagonClient client) + { + _client = client; + } + + public void Handle(RagonBuffer buffer) + { + var timestamp0 = buffer.Read(32); + var timestamp1 = buffer.Read(32); + var value = new DoubleToUInt { Int0 = timestamp0, Int1 = timestamp1 }; + + _client.SetTimestamp(value.Double); + } +} \ No newline at end of file diff --git a/Ragon.Client/Sources/IRagonConnection.cs b/Ragon.Client/Sources/IRagonConnection.cs index 2a1c116..cb5e186 100644 --- a/Ragon.Client/Sources/IRagonConnection.cs +++ b/Ragon.Client/Sources/IRagonConnection.cs @@ -18,5 +18,5 @@ namespace Ragon.Client; public interface IRagonConnection { - + public void Close(); } \ No newline at end of file diff --git a/Ragon.Client/Sources/RagonClient.cs b/Ragon.Client/Sources/RagonClient.cs index 582c2b4..cc407b1 100644 --- a/Ragon.Client/Sources/RagonClient.cs +++ b/Ragon.Client/Sources/RagonClient.cs @@ -24,7 +24,7 @@ namespace Ragon.Client private readonly NetworkStatistics _stats; private IRagonEntityListener _entityListener; private IRagonSceneCollector _sceneCollector; - private Handler[] _handlers; + private IHandler[] _handlers; private RagonBuffer _readBuffer; private RagonBuffer _writeBuffer; private RagonRoom _room; @@ -35,9 +35,11 @@ namespace Ragon.Client private RagonEventCache _eventCache; private RagonStatus _status; + private double _serverTimestamp; private float _replicationRate = 0; private float _replicationTime = 0; + public double ServerTimestamp => _serverTimestamp; public IRagonConnection Connection => _connection; public RagonStatus Status => _status; public RagonSession Session => _session; @@ -46,6 +48,7 @@ namespace Ragon.Client public NetworkStatistics Statistics => _stats; public RagonRoom Room => _room; + internal RagonBuffer Buffer => _writeBuffer; internal INetworkChannel Reliable => _connection.Reliable; internal INetworkChannel Unreliable => _connection.Unreliable; @@ -96,15 +99,15 @@ namespace Ragon.Client _writeBuffer = new RagonBuffer(); _readBuffer = new RagonBuffer(); - _session = new RagonSession(this, _readBuffer); + _session = new RagonSession(this, _writeBuffer); _playerCache = new RagonPlayerCache(); _entityCache = new RagonEntityCache(this, _playerCache, _sceneCollector); - _handlers = new Handler[byte.MaxValue]; + _handlers = new IHandler[byte.MaxValue]; _handlers[(byte)RagonOperation.AUTHORIZED_SUCCESS] = new AuthorizeSuccessHandler(this, listeners); _handlers[(byte)RagonOperation.AUTHORIZED_FAILED] = new AuthorizeFailedHandler(listeners); - _handlers[(byte)RagonOperation.JOIN_SUCCESS] = new JoinSuccessHandler(this, _readBuffer, listeners, _playerCache, _entityCache); + _handlers[(byte)RagonOperation.JOIN_SUCCESS] = new JoinSuccessHandler(this, listeners, _playerCache, _entityCache); _handlers[(byte)RagonOperation.JOIN_FAILED] = new JoinFailedHandler(listeners); _handlers[(byte)RagonOperation.LEAVE_ROOM] = new LeaveRoomHandler(this, listeners, _entityCache); _handlers[(byte)RagonOperation.OWNERSHIP_ROOM_CHANGED] = new OwnershipRoomHandler(listeners, _playerCache, _entityCache); @@ -115,8 +118,10 @@ namespace Ragon.Client _handlers[(byte)RagonOperation.CREATE_ENTITY] = new EntityCreateHandler(this, _playerCache, _entityCache, _entityListener); _handlers[(byte)RagonOperation.REMOVE_ENTITY] = new EntityRemoveHandler(_entityCache); _handlers[(byte)RagonOperation.REPLICATE_ENTITY_STATE] = new StateEntityHandler(_entityCache); - _handlers[(byte)RagonOperation.REPLICATE_ENTITY_EVENT] = new EntityEventHandler(this, _playerCache, _entityCache); + _handlers[(byte)RagonOperation.REPLICATE_ENTITY_EVENT] = new EntityEventHandler(_playerCache, _entityCache); _handlers[(byte)RagonOperation.SNAPSHOT] = new SnapshotHandler(this, listeners, _entityCache, _playerCache, _entityListener); + _handlers[(byte)RagonOperation.REPLICATE_RAW_DATA] = new EventRoomHandler(this, _playerCache); + _handlers[(byte)RagonOperation.TIMESTAMP_SYNCHRONIZATION] = new TimestampHandler(this); var protocolRaw = RagonVersion.Parse(protocol); _connection.Connect(address, port, protocolRaw); @@ -138,8 +143,10 @@ namespace Ragon.Client _replicationTime += dt; if (_replicationTime >= _replicationRate) { - _entityCache.WriteState(_readBuffer); _replicationTime = 0; + _entityCache.WriteState(_writeBuffer); + + SendTimestamp(); } _stats.Update(_connection.BytesSent, _connection.BytesReceived, _connection.Ping, dt); @@ -194,10 +201,29 @@ namespace Ragon.Client _status = status; } + internal void SetTimestamp(double time) + { + _serverTimestamp = time; + } + #endregion #region PRIVATE + private void SendTimestamp() + { + var timestamp = RagonTime.CurrentTimestamp(); + var value = new DoubleToUInt() + { + Double = timestamp, + }; + + _writeBuffer.Clear(); + _writeBuffer.WriteOperation(RagonOperation.TIMESTAMP_SYNCHRONIZATION); + _writeBuffer.Write(value.Int0, 32); + _writeBuffer.Write(value.Int1, 32); + } + private void OnConnected() { RagonLog.Trace("Connected"); @@ -214,7 +240,7 @@ namespace Ragon.Client _status = RagonStatus.DISCONNECTED; } - public void OnData(byte[] data) + private void OnData(byte[] data) { _readBuffer.Clear(); _readBuffer.FromArray(data); diff --git a/Ragon.Client/Sources/RagonRoom.cs b/Ragon.Client/Sources/RagonRoom.cs index ca9fd6b..2532a7f 100644 --- a/Ragon.Client/Sources/RagonRoom.cs +++ b/Ragon.Client/Sources/RagonRoom.cs @@ -14,16 +14,20 @@ * limitations under the License. */ +using Ragon.Protocol; + namespace Ragon.Client { public class RagonRoom { + private delegate void OnEventDelegate(RagonPlayer player, RagonBuffer serializer); + private RagonClient _client; private RagonScene _scene; private RagonEntityCache _entityCache; private RagonPlayerCache _playerCache; private RagonRoomInformation _information; - + public string Id => _information.RoomId; public int MinPlayers => _information.Min; public int MaxPlayers => _information.Max; @@ -33,6 +37,9 @@ namespace Ragon.Client public RagonPlayer Local => _playerCache.Local; public RagonPlayer Owner => _playerCache.Owner; + private readonly Dictionary _events = new Dictionary(); + private readonly Dictionary> _localEvents = new Dictionary>(); + public RagonRoom(RagonClient client, RagonEntityCache entityCache, RagonPlayerCache playerCache, @@ -57,8 +64,40 @@ namespace Ragon.Client _scene.Update(sceneName); } + internal void Event(ushort eventCode, RagonPlayer caller, RagonBuffer buffer) + { + if (_events.TryGetValue(eventCode, out var evnt)) + evnt?.Invoke(caller, buffer); + else + RagonLog.Warn($"Handler event on entity {Id} with eventCode {eventCode} not defined"); + } + + public void OnEvent(Action callback) where TEvent : IRagonEvent, new() + { + var t = new TEvent(); + var eventCode = _client.Event.GetEventCode(t); + + if (_events.ContainsKey(eventCode)) + { + _events.Remove(eventCode); + _localEvents.Remove(eventCode); + + RagonLog.Warn($"Event already {eventCode} subscribed, removed old one!"); + } + + _localEvents.Add(eventCode, (player, eventData) => { callback.Invoke(player, (TEvent)eventData); }); + _events.Add(eventCode, (player, serializer) => + { + t.Deserialize(serializer); + callback.Invoke(player, t); + }); + } + public void LoadScene(string sceneName) => _scene.Load(sceneName); public void SceneLoaded() => _scene.SceneLoaded(); + + public void ReplicateEvent(TEvent evnt, RagonTarget target, RagonReplicationMode mode) where TEvent : IRagonEvent, new() => _scene.ReplicateEvent(evnt, target, mode); + public void ReplicateEvent(TEvent evnt, RagonPlayer target, RagonReplicationMode mode) where TEvent : IRagonEvent, new() => _scene.ReplicateEvent(evnt, target, mode); public void CreateEntity(RagonEntity entity) => CreateEntity(entity, null); public void CreateEntity(RagonEntity entity, RagonPayload payload) => _entityCache.Create(entity, payload); diff --git a/Ragon.Client/Sources/RagonScene.cs b/Ragon.Client/Sources/RagonScene.cs index fdeee30..79f9258 100644 --- a/Ragon.Client/Sources/RagonScene.cs +++ b/Ragon.Client/Sources/RagonScene.cs @@ -67,4 +67,39 @@ public class RagonScene var sendData = buffer.ToArray(); _client.Reliable.Send(sendData); } + + internal void ReplicateEvent(TEvent evnt, RagonTarget target, RagonReplicationMode replicationMode) + where TEvent : IRagonEvent, new() + { + var evntId = _client.Event.GetEventCode(evnt); + var buffer = _client.Buffer; + + buffer.Clear(); + buffer.WriteOperation(RagonOperation.REPLICATE_RAW_DATA); + buffer.WriteUShort(evntId); + buffer.WriteByte((byte)replicationMode); + buffer.WriteByte((byte)target); + + var sendData = buffer.ToArray(); + _client.Reliable.Send(sendData); + } + + internal void ReplicateEvent(TEvent evnt, RagonPlayer target, RagonReplicationMode replicationMode) + where TEvent : IRagonEvent, new() + { + var evntId = _client.Event.GetEventCode(evnt); + var buffer = _client.Buffer; + + buffer.Clear(); + buffer.WriteOperation(RagonOperation.REPLICATE_RAW_DATA); + buffer.WriteUShort(evntId); + buffer.WriteByte((byte)replicationMode); + buffer.WriteByte((byte)RagonTarget.Player); + buffer.WriteUShort(target.PeerId); + + evnt.Serialize(buffer); + + var sendData = buffer.ToArray(); + _client.Reliable.Send(sendData); + } } \ No newline at end of file diff --git a/Ragon.Protocol/Sources/RagonOperation.cs b/Ragon.Protocol/Sources/RagonOperation.cs index a152d1e..32cecf2 100644 --- a/Ragon.Protocol/Sources/RagonOperation.cs +++ b/Ragon.Protocol/Sources/RagonOperation.cs @@ -41,5 +41,7 @@ namespace Ragon.Protocol REPLICATE_ENTITY_EVENT, TRANSFER_ROOM_OWNERSHIP, TRANSFER_ENTITY_OWNERSHIP, + REPLICATE_RAW_DATA, + TIMESTAMP_SYNCHRONIZATION, } } \ No newline at end of file diff --git a/Ragon.Protocol/Sources/RagonUtils.cs b/Ragon.Protocol/Sources/RagonUtils.cs index acc995a..bc30b20 100644 --- a/Ragon.Protocol/Sources/RagonUtils.cs +++ b/Ragon.Protocol/Sources/RagonUtils.cs @@ -16,9 +16,34 @@ using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace Ragon.Protocol { + [StructLayout(LayoutKind.Explicit)] + public struct DoubleToUInt + { + [FieldOffset(0)] + public double Double; + + [FieldOffset(0)] + public uint Int0; + + [FieldOffset(4)] + public uint Int1; + } + + public static class RagonTime { + public static double CurrentTimestamp() + { + var currentTime = System.DateTime.UtcNow.ToUniversalTime().Subtract( + new System.DateTime(1970, 1, 1, 0, 0, 0, System.DateTimeKind.Utc) + ).TotalMilliseconds; + + return currentTime; + } + } + public static class DeBruijn { private static readonly int[] _lookup = new int[32] diff --git a/Ragon.Relay/Program.cs b/Ragon.Relay/Program.cs index 39a26be..ef0a056 100644 --- a/Ragon.Relay/Program.cs +++ b/Ragon.Relay/Program.cs @@ -16,12 +16,12 @@ namespace Ragon.Relay { - class Program + class Program + { + static void Main(string[] args) { - static void Main(string[] args) - { - var relay = new Relay(); - relay.Start(); - } + var relay = new Relay(); + relay.Start(); } + } } \ No newline at end of file diff --git a/Ragon.Server.ENet/Sources/ENetServer.cs b/Ragon.Server.ENet/Sources/ENetServer.cs index c953a3d..1eacbc8 100644 --- a/Ragon.Server.ENet/Sources/ENetServer.cs +++ b/Ragon.Server.ENet/Sources/ENetServer.cs @@ -21,19 +21,19 @@ using Ragon.Server.IO; namespace Ragon.Server.ENet { - public sealed class ENetServer: INetworkServer + public sealed class ENetServer : INetworkServer { public Executor Executor => _executor; - + private readonly Host _host; private readonly ILogger _logger = LogManager.GetCurrentClassLogger(); - + private ENetConnection[] _connections; private INetworkListener _listener; private uint _protocol; - private Event _event; + private global::ENet.Event _event; private Executor _executor; - + public ENetServer() { _host = new Host(); @@ -44,15 +44,15 @@ namespace Ragon.Server.ENet public void Start(INetworkListener listener, NetworkConfiguration configuration) { Library.Initialize(); - + _connections = new ENetConnection[configuration.LimitConnections]; - + _listener = listener; _protocol = configuration.Protocol; - + var address = new Address { - Port = (ushort) configuration.Port, + Port = (ushort)configuration.Port, }; _host.Create(address, _connections.Length, 2, 0, 0, 1024 * 1024); @@ -74,7 +74,7 @@ namespace Ragon.Server.ENet polled = true; } - + switch (_event.Type) { case EventType.None: @@ -90,8 +90,9 @@ namespace Ragon.Server.ENet _event.Peer.DisconnectNow(0); break; } + var connection = new ENetConnection(_event.Peer); - + _connections[_event.Peer.ID] = connection; _listener.OnConnected(connection); break; @@ -110,13 +111,13 @@ namespace Ragon.Server.ENet } case EventType.Receive: { - var peerId = (ushort) _event.Peer.ID; + var peerId = (ushort)_event.Peer.ID; var connection = _connections[peerId]; var dataRaw = new byte[_event.Packet.Length]; - + _event.Packet.CopyTo(dataRaw); _event.Packet.Dispose(); - + _listener.OnData(connection, dataRaw); break; } @@ -124,10 +125,26 @@ namespace Ragon.Server.ENet } } + public void BroadcastReliable(byte[] data) + { + var packet = new Packet(); + packet.Create(data, PacketFlags.Reliable); + + _host.Broadcast(0, ref packet); + } + + public void BroadcastUnreliable(byte[] data) + { + var packet = new Packet(); + packet.Create(data, PacketFlags.None); + + _host.Broadcast(1, ref packet); + } + public void Stop() { _host?.Dispose(); - + Library.Deinitialize(); } diff --git a/Ragon.Server.WebSocketServer/Sources/WebSocketServer.cs b/Ragon.Server.WebSocketServer/Sources/WebSocketServer.cs index d304238..eed20db 100644 --- a/Ragon.Server.WebSocketServer/Sources/WebSocketServer.cs +++ b/Ragon.Server.WebSocketServer/Sources/WebSocketServer.cs @@ -102,6 +102,18 @@ public class WebSocketServer : INetworkServer Flush(); } + public void BroadcastUnreliable(byte[] data) + { + foreach (var activeConnection in _activeConnections) + activeConnection.Unreliable.Send(data); + } + + public void BroadcastReliable(byte[] data) + { + foreach (var activeConnection in _activeConnections) + activeConnection.Reliable.Send(data); + } + public async void Flush() { foreach (var conn in _activeConnections) diff --git a/Ragon.Server/Ragon.Server.csproj b/Ragon.Server/Ragon.Server.csproj index 785c45a..2575c98 100644 --- a/Ragon.Server/Ragon.Server.csproj +++ b/Ragon.Server/Ragon.Server.csproj @@ -25,4 +25,5 @@ + diff --git a/Ragon.Server/Sources/Entity/RagonEntity.cs b/Ragon.Server/Sources/Entity/RagonEntity.cs index 9331821..d3f6ab5 100644 --- a/Ragon.Server/Sources/Entity/RagonEntity.cs +++ b/Ragon.Server/Sources/Entity/RagonEntity.cs @@ -16,6 +16,7 @@ using Ragon.Protocol; +using Ragon.Server.Event; using Ragon.Server.Room; namespace Ragon.Server.Entity; @@ -117,32 +118,37 @@ public class RagonEntity : IRagonEntity { buffer.WriteUShort(Type); buffer.WriteUShort(Id); - + if (StaticId != 0) buffer.WriteUShort(StaticId); - + buffer.WriteUShort(Owner.Connection.Id); buffer.WriteUShort(Payload.Size); - + Payload.Write(buffer); - + _state.Snapshot(buffer); } public void ReplicateEvent( - RagonRoomPlayer caller, + RagonRoomPlayer invoker, RagonEvent evnt, RagonReplicationMode eventMode, RagonRoomPlayer targetPlayer ) { + if (Authority == RagonAuthority.OwnerOnly && invoker.Connection.Id != Owner.Connection.Id) + { + return; + } + var room = Owner.Room; var buffer = room.Writer; - + buffer.Clear(); buffer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT); buffer.WriteUShort(evnt.EventCode); - buffer.WriteUShort(caller.Connection.Id); + buffer.WriteUShort(invoker.Connection.Id); buffer.WriteByte((byte)eventMode); buffer.WriteUShort(Id); @@ -153,21 +159,18 @@ public class RagonEntity : IRagonEntity } public void ReplicateEvent( - RagonRoomPlayer caller, + RagonRoomPlayer invoker, RagonEvent evnt, RagonReplicationMode eventMode, RagonTarget targetMode ) { - if (Authority == RagonAuthority.OwnerOnly && - Owner.Connection.Id != caller.Connection.Id) + if (Authority == RagonAuthority.OwnerOnly && invoker.Connection.Id != Owner.Connection.Id) { return; } - if (eventMode == RagonReplicationMode.Buffered && - targetMode != RagonTarget.Owner && - _bufferedEvents.Count < _limitBufferedEvents) + if (eventMode == RagonReplicationMode.Buffered && targetMode != RagonTarget.Owner && _bufferedEvents.Count < _limitBufferedEvents) { _bufferedEvents.Add(evnt); } @@ -178,7 +181,7 @@ public class RagonEntity : IRagonEntity buffer.Clear(); buffer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT); buffer.WriteUShort(evnt.EventCode); - buffer.WriteUShort(caller.Connection.Id); + buffer.WriteUShort(invoker.Connection.Id); buffer.WriteByte((byte)eventMode); buffer.WriteUShort(Id); @@ -206,7 +209,7 @@ public class RagonEntity : IRagonEntity { foreach (var roomPlayer in room.ReadyPlayersList) { - if (roomPlayer.Connection.Id != caller.Connection.Id) + if (roomPlayer.Connection.Id != invoker.Connection.Id) roomPlayer.Connection.Reliable.Send(sendData); } diff --git a/Ragon.Server/Sources/Entity/RagonEvent.cs b/Ragon.Server/Sources/Event/RagonEvent.cs similarity index 97% rename from Ragon.Server/Sources/Entity/RagonEvent.cs rename to Ragon.Server/Sources/Event/RagonEvent.cs index 22e20ac..ee55e8c 100644 --- a/Ragon.Server/Sources/Entity/RagonEvent.cs +++ b/Ragon.Server/Sources/Event/RagonEvent.cs @@ -17,7 +17,7 @@ using Ragon.Protocol; using Ragon.Server.Room; -namespace Ragon.Server.Entity; +namespace Ragon.Server.Event; public class RagonEvent { diff --git a/Ragon.Server/Sources/Handler/AuthorizationOperation.cs b/Ragon.Server/Sources/Handler/AuthorizationOperation.cs index c03e795..5d00c57 100644 --- a/Ragon.Server/Sources/Handler/AuthorizationOperation.cs +++ b/Ragon.Server/Sources/Handler/AuthorizationOperation.cs @@ -17,7 +17,6 @@ using NLog; using Ragon.Protocol; using Ragon.Server.Lobby; -using Ragon.Server.Plugin; using Ragon.Server.Plugin.Web; diff --git a/Ragon.Server/Sources/Handler/EntityEventOperation.cs b/Ragon.Server/Sources/Handler/EntityEventOperation.cs index 069c9d3..63c51a8 100644 --- a/Ragon.Server/Sources/Handler/EntityEventOperation.cs +++ b/Ragon.Server/Sources/Handler/EntityEventOperation.cs @@ -16,7 +16,7 @@ using NLog; using Ragon.Protocol; -using Ragon.Server.Entity; +using Ragon.Server.Event; namespace Ragon.Server.Handler; diff --git a/Ragon.Server/Sources/Handler/EntityOwnershipOperation.cs b/Ragon.Server/Sources/Handler/EntityOwnershipOperation.cs index df78d2e..7deb29d 100644 --- a/Ragon.Server/Sources/Handler/EntityOwnershipOperation.cs +++ b/Ragon.Server/Sources/Handler/EntityOwnershipOperation.cs @@ -29,7 +29,7 @@ public sealed class EntityOwnershipOperation : IRagonOperation if (!room.Players.TryGetValue(playerPeerId, out var nextOwner)) { - _logger.Error($"Player not found with id {entityId}"); + _logger.Error($"Player not found with id {playerPeerId}"); return; } diff --git a/Ragon.Server/Sources/Handler/TimestampSyncOperation.cs b/Ragon.Server/Sources/Handler/TimestampSyncOperation.cs new file mode 100644 index 0000000..4989f37 --- /dev/null +++ b/Ragon.Server/Sources/Handler/TimestampSyncOperation.cs @@ -0,0 +1,15 @@ +using Ragon.Protocol; + +namespace Ragon.Server.Handler; + +public class TimestampSyncOperation: IRagonOperation +{ + public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) + { + var timestamp0 = reader.Read(32); + var timestamp1 = reader.Read(32); + var value = new DoubleToUInt() { Int0 = timestamp0, Int1 = timestamp1 }; + + context.RoomPlayer?.SetTimestamp(value.Double); + } +} \ No newline at end of file diff --git a/Ragon.Server/Sources/IO/INetworkChannel.cs b/Ragon.Server/Sources/IO/INetworkChannel.cs index f2c8074..d18b1ca 100644 --- a/Ragon.Server/Sources/IO/INetworkChannel.cs +++ b/Ragon.Server/Sources/IO/INetworkChannel.cs @@ -14,6 +14,8 @@ * limitations under the License. */ +using Ragon.Protocol; + namespace Ragon.Server.IO; public interface INetworkChannel diff --git a/Ragon.Server/Sources/IO/INetworkServer.cs b/Ragon.Server/Sources/IO/INetworkServer.cs index 0108e02..91694ea 100644 --- a/Ragon.Server/Sources/IO/INetworkServer.cs +++ b/Ragon.Server/Sources/IO/INetworkServer.cs @@ -21,5 +21,7 @@ public interface INetworkServer public Executor Executor { get; } public void Stop(); public void Update(); + public void BroadcastUnreliable(byte[] data); + public void BroadcastReliable(byte[] data); public void Start(INetworkListener listener, NetworkConfiguration configuration); } \ No newline at end of file diff --git a/Ragon.Server/Sources/RagonServer.cs b/Ragon.Server/Sources/RagonServer.cs index f3e40b4..d306e03 100644 --- a/Ragon.Server/Sources/RagonServer.cs +++ b/Ragon.Server/Sources/RagonServer.cs @@ -87,6 +87,8 @@ public class RagonServer : IRagonServer, INetworkListener _handlers[(byte) RagonOperation.REPLICATE_ENTITY_STATE] = new EntityStateOperation(); _handlers[(byte) RagonOperation.TRANSFER_ROOM_OWNERSHIP] = new EntityOwnershipOperation(); _handlers[(byte) RagonOperation.TRANSFER_ENTITY_OWNERSHIP] = new EntityOwnershipOperation(); + _handlers[(byte)RagonOperation.REPLICATE_RAW_DATA] = new RoomDataOperation(); + _handlers[(byte)RagonOperation.TIMESTAMP_SYNCHRONIZATION] = new TimestampSyncOperation(); _logger.Trace($"Server Tick Rate: {_configuration.ServerTickRate}"); } @@ -98,8 +100,10 @@ public class RagonServer : IRagonServer, INetworkListener { if (_timer.ElapsedMilliseconds > _tickRate) { - _scheduler.Update(_timer.ElapsedMilliseconds / 1000.0f); _timer.Restart(); + _scheduler.Update(_timer.ElapsedMilliseconds / 1000.0f); + + SendTimestamp(); } _executor.Update(); @@ -158,7 +162,7 @@ public class RagonServer : IRagonServer, INetworkListener } else { - _logger.Trace($"Disconnected: {connection.Id}"); + _logger.Trace($"Disconnected without context: {connection.Id}"); } } @@ -190,7 +194,7 @@ public class RagonServer : IRagonServer, INetworkListener _writer.Clear(); _reader.Clear(); _reader.FromArray(data); - + var operation = _reader.ReadByte(); _handlers[operation].Handle(context, _reader, _writer); } @@ -201,6 +205,23 @@ public class RagonServer : IRagonServer, INetworkListener } } + public void SendTimestamp() + { + var timestamp = RagonTime.CurrentTimestamp(); + var value = new DoubleToUInt + { + Double = timestamp, + }; + + _writer.Clear(); + _writer.WriteOperation(RagonOperation.TIMESTAMP_SYNCHRONIZATION); + _writer.Write(value.Int0, 32); + _writer.Write(value.Int1, 32); + + var sendData = _writer.ToArray(); + _server.BroadcastUnreliable(sendData); + } + public IRagonOperation ResolveOperation(RagonOperation operation) { return _handlers[(byte)operation]; diff --git a/Ragon.Server/Sources/Room/RagonRoomPlayer.cs b/Ragon.Server/Sources/Room/RagonRoomPlayer.cs index 5cc2c32..b40637a 100644 --- a/Ragon.Server/Sources/Room/RagonRoomPlayer.cs +++ b/Ragon.Server/Sources/Room/RagonRoomPlayer.cs @@ -25,6 +25,7 @@ public class RagonRoomPlayer public string Id { get; } public string Name { get; } public bool IsLoaded { get; private set; } + public double Timestamp { get; private set; } public RagonRoom Room { get; private set; } public RagonEntityCache Entities { get; private set; } @@ -65,4 +66,9 @@ public class RagonRoomPlayer { IsLoaded = false; } + + internal void SetTimestamp(double time) + { + Timestamp = time; + } } \ No newline at end of file diff --git a/Ragon.Client.Simulation/Program.cs b/Ragon.Simulation/Program.cs similarity index 100% rename from Ragon.Client.Simulation/Program.cs rename to Ragon.Simulation/Program.cs diff --git a/Ragon.Client.Simulation/Ragon.Client.Simulation.csproj b/Ragon.Simulation/Ragon.Simulation.csproj similarity index 78% rename from Ragon.Client.Simulation/Ragon.Client.Simulation.csproj rename to Ragon.Simulation/Ragon.Simulation.csproj index 890cd74..cad77a6 100644 --- a/Ragon.Client.Simulation/Ragon.Client.Simulation.csproj +++ b/Ragon.Simulation/Ragon.Simulation.csproj @@ -6,6 +6,7 @@ enable enable Linux + Ragon.Client.Simulation @@ -15,6 +16,7 @@ + @@ -23,4 +25,8 @@ + + + + diff --git a/Ragon.Simulation/Sources/Client/Client.cs b/Ragon.Simulation/Sources/Client/Client.cs new file mode 100644 index 0000000..3180853 --- /dev/null +++ b/Ragon.Simulation/Sources/Client/Client.cs @@ -0,0 +1,143 @@ +using System.Numerics; +using Raylib_cs; +using static Raylib_cs.Raylib; + +namespace Ragon.Simulation; + +public class Client +{ + public void Start() + { + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [models] example - first person maze"); + + // Define the camera to look into our 3d world + Camera3D camera = new(); + camera.Position = new Vector3(0.2f, 0.4f, 0.2f); + camera.Target = new Vector3(0.0f, 0.0f, 0.0f); + camera.Up = new Vector3(0.0f, 1.0f, 0.0f); + camera.FovY = 45.0f; + camera.Projection = CameraProjection.CAMERA_PERSPECTIVE; + + Image imMap = LoadImage("resources/cubicmap.png"); + Texture2D cubicmap = LoadTextureFromImage(imMap); + Mesh mesh = GenMeshCubicmap(imMap, new Vector3(1.0f, 1.0f, 1.0f)); + Model model = LoadModelFromMesh(mesh); + + // NOTE: By default each cube is mapped to one part of texture atlas + Texture2D texture = LoadTexture("resources/cubicmap_atlas.png"); + + // Set map diffuse texture + Raylib.SetMaterialTexture(ref model, 0, MaterialMapIndex.MATERIAL_MAP_ALBEDO, ref texture); + + // Get map image data to be used for collision detection + Color* mapPixels = LoadImageColors(imMap); + UnloadImage(imMap); + + Vector3 mapPosition = new(-16.0f, 0.0f, -8.0f); + Vector3 playerPosition = camera.Position; + + SetTargetFPS(60); + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) + { + // Update + //---------------------------------------------------------------------------------- + Vector3 oldCamPos = camera.Position; + + UpdateCamera(ref camera, CameraMode.CAMERA_FIRST_PERSON); + + // Check player collision (we simplify to 2D collision detection) + Vector2 playerPos = new(camera.Position.X, camera.Position.Z); + + // Collision radius (player is modelled as a cilinder for collision) + float playerRadius = 0.1f; + + int playerCellX = (int)(playerPos.X - mapPosition.X + 0.5f); + int playerCellY = (int)(playerPos.Y - mapPosition.Z + 0.5f); + + // Out-of-limits security check + if (playerCellX < 0) + { + playerCellX = 0; + } + else if (playerCellX >= cubicmap.Width) + { + playerCellX = cubicmap.Width - 1; + } + + if (playerCellY < 0) + { + playerCellY = 0; + } + else if (playerCellY >= cubicmap.Height) + { + playerCellY = cubicmap.Height - 1; + } + + // Check map collisions using image data and player position + // TODO: Improvement: Just check player surrounding cells for collision + for (int y = 0; y < cubicmap.Height; y++) + { + for (int x = 0; x < cubicmap.Width; x++) + { + Color* mapPixelsData = mapPixels; + + // Collision: Color.white pixel, only check R channel + Rectangle rec = new( + mapPosition.X - 0.5f + x * 1.0f, + mapPosition.Z - 0.5f + y * 1.0f, + 1.0f, + 1.0f + ); + + bool collision = CheckCollisionCircleRec(playerPos, playerRadius, rec); + if ((mapPixelsData[y * cubicmap.Width + x].R == 255) && collision) + { + // Collision detected, reset camera position + camera.Position = oldCamPos; + } + } + } + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + ClearBackground(Color.RAYWHITE); + + // Draw maze map + BeginMode3D(camera); + DrawModel(model, mapPosition, 1.0f, Color.WHITE); + EndMode3D(); + + DrawTextureEx(cubicmap, new Vector2(GetScreenWidth() - cubicmap.Width * 4 - 20, 20), 0.0f, 4.0f, Color.WHITE); + DrawRectangleLines(GetScreenWidth() - cubicmap.Width * 4 - 20, 20, cubicmap.Width * 4, cubicmap.Height * 4, Color.GREEN); + + // Draw player position radar + DrawRectangle(GetScreenWidth() - cubicmap.Width * 4 - 20 + playerCellX * 4, 20 + playerCellY * 4, 4, 4, Color.RED); + + DrawFPS(10, 10); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadImageColors(mapPixels); + + UnloadTexture(cubicmap); + UnloadTexture(texture); + UnloadModel(model); + + CloseWindow(); + //-------------------------------------------------------------------------------------- + } +} \ No newline at end of file diff --git a/Ragon.Client.Simulation/Sources/IO/RagonENetConnection.cs b/Ragon.Simulation/Sources/Client/IO/RagonENetConnection.cs similarity index 100% rename from Ragon.Client.Simulation/Sources/IO/RagonENetConnection.cs rename to Ragon.Simulation/Sources/Client/IO/RagonENetConnection.cs diff --git a/Ragon.Client.Simulation/Sources/IO/RagonENetReliableChannel.cs b/Ragon.Simulation/Sources/Client/IO/RagonENetReliableChannel.cs similarity index 100% rename from Ragon.Client.Simulation/Sources/IO/RagonENetReliableChannel.cs rename to Ragon.Simulation/Sources/Client/IO/RagonENetReliableChannel.cs diff --git a/Ragon.Client.Simulation/Sources/IO/RagonENetUnreliableChannel.cs b/Ragon.Simulation/Sources/Client/IO/RagonENetUnreliableChannel.cs similarity index 100% rename from Ragon.Client.Simulation/Sources/IO/RagonENetUnreliableChannel.cs rename to Ragon.Simulation/Sources/Client/IO/RagonENetUnreliableChannel.cs diff --git a/Ragon.Client.Simulation/Sources/IO/RagonNullConnection.cs b/Ragon.Simulation/Sources/Client/IO/RagonNullConnection.cs similarity index 100% rename from Ragon.Client.Simulation/Sources/IO/RagonNullConnection.cs rename to Ragon.Simulation/Sources/Client/IO/RagonNullConnection.cs diff --git a/Ragon.Client.Simulation/Sources/IO/RagonNullReliableChannel.cs b/Ragon.Simulation/Sources/Client/IO/RagonNullReliableChannel.cs similarity index 100% rename from Ragon.Client.Simulation/Sources/IO/RagonNullReliableChannel.cs rename to Ragon.Simulation/Sources/Client/IO/RagonNullReliableChannel.cs diff --git a/Ragon.Client.Simulation/Sources/IO/RagonNullUnreliableChannel.cs b/Ragon.Simulation/Sources/Client/IO/RagonNullUnreliableChannel.cs similarity index 100% rename from Ragon.Client.Simulation/Sources/IO/RagonNullUnreliableChannel.cs rename to Ragon.Simulation/Sources/Client/IO/RagonNullUnreliableChannel.cs diff --git a/Ragon.Client.Simulation/Sources/Player/PlayerPayload.cs b/Ragon.Simulation/Sources/Client/Player/PlayerPayload.cs similarity index 100% rename from Ragon.Client.Simulation/Sources/Player/PlayerPayload.cs rename to Ragon.Simulation/Sources/Client/Player/PlayerPayload.cs diff --git a/Ragon.Client.Simulation/Sources/Game.cs b/Ragon.Simulation/Sources/Game.cs similarity index 100% rename from Ragon.Client.Simulation/Sources/Game.cs rename to Ragon.Simulation/Sources/Game.cs diff --git a/Ragon.Client.Simulation/Sources/Simulation.cs b/Ragon.Simulation/Sources/Simulation.cs similarity index 89% rename from Ragon.Client.Simulation/Sources/Simulation.cs rename to Ragon.Simulation/Sources/Simulation.cs index db8d223..5d46619 100644 --- a/Ragon.Client.Simulation/Sources/Simulation.cs +++ b/Ragon.Simulation/Sources/Simulation.cs @@ -1,4 +1,7 @@ -namespace Ragon.Client.Simulation; +using Ragon.Client; +using Ragon.Client.Simulation; + +namespace Ragon.Simulation; public class EntityListener : IRagonEntityListener { @@ -24,6 +27,9 @@ public class Simulation { public void Start() { + var client = new Ragon.Simulation.Client(); + client.Start(); + // INetworkConnection protocol = debug ? new RagonNullConnection() : new RagonENetConnection(); // var network = new RagonClient(protocol, new EntityListener(), 30); // var game = new Game(network); diff --git a/Ragon.sln b/Ragon.sln index 80980c2..56958b6 100644 --- a/Ragon.sln +++ b/Ragon.sln @@ -12,7 +12,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Server.ENet", "Ragon. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Client", "Ragon.Client\Ragon.Client.csproj", "{C82D65BF-6D80-4263-ADFE-CB9ED990B6C3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Client.Simulation", "Ragon.Client.Simulation\Ragon.Client.Simulation.csproj", "{0384848D-3B63-4B3A-B15F-A836EBB3E95D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Simulation", "Ragon.Simulation\Ragon.Simulation.csproj", "{0384848D-3B63-4B3A-B15F-A836EBB3E95D}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Client.Property", "Ragon.Client.Property\Ragon.Client.Property.csproj", "{46A60DAB-F854-4BB6-A119-BD4C5B2B0D29}" EndProject