From 605e43273ea8f4481859102e8a3faa54ded7320a5d78ebff85da27e319413a1e Mon Sep 17 00:00:00 2001 From: Aslan Date: Mon, 2 Feb 2026 12:17:04 -0500 Subject: [PATCH] Implement RPC Node --- prefabs/gameObjects/star.tscn | 4 +- scenes/game.tscn | 3 +- scripts/GameManager.cs | 170 +++++++---------------- scripts/GameObject.cs | 190 ++++++++++++++++++++++++-- scripts/GameObjects/Character.cs | 22 ++- scripts/GameObjects/Nodes/StarNode.cs | 30 +++- scripts/GameObjects/Star.cs | 20 ++- scripts/Generator/TestGenerator.cs | 15 +- scripts/NetworkManager.cs | 17 +-- scripts/Player.cs | 49 ++----- scripts/QueueManager.cs | 20 ++- scripts/RPCNode.cs | 166 ++++++++++++++++++++++ scripts/Sector.cs | 24 +++- 13 files changed, 524 insertions(+), 206 deletions(-) diff --git a/prefabs/gameObjects/star.tscn b/prefabs/gameObjects/star.tscn index abed28f..1d993b8 100644 --- a/prefabs/gameObjects/star.tscn +++ b/prefabs/gameObjects/star.tscn @@ -2,7 +2,7 @@ [ext_resource type="Script" uid="uid://cbt6p1bhh4o8q" path="res://scripts/GameObjects/Nodes/StarNode.cs" id="1_o2f8k"] -[sub_resource type="SphereMesh" id="SphereMesh_o2f8k"] +[sub_resource type="SphereMesh" id="SphereMesh_2pyv3"] radius = 5.0 height = 10.0 @@ -13,7 +13,7 @@ radius = 5.0 script = ExtResource("1_o2f8k") [node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=1423841842] -mesh = SubResource("SphereMesh_o2f8k") +mesh = SubResource("SphereMesh_2pyv3") [node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1898956937] shape = SubResource("SphereShape3D_o2f8k") diff --git a/scenes/game.tscn b/scenes/game.tscn index 75fb9b1..605adfd 100644 --- a/scenes/game.tscn +++ b/scenes/game.tscn @@ -55,9 +55,8 @@ GameMenu = NodePath("../GameMenu") [node name="QueueManager" type="Node" parent="." unique_id=355148200] script = ExtResource("4_iywne") -[node name="NetworkManager" type="Node" parent="." unique_id=1765485895 node_paths=PackedStringArray("RPCNode")] +[node name="NetworkManager" type="Node" parent="." unique_id=1765485895] script = ExtResource("5_p57ef") -RPCNode = NodePath("../RPC") [node name="RPC" type="Node" parent="." unique_id=498537245] script = ExtResource("6_u5sy4") diff --git a/scripts/GameManager.cs b/scripts/GameManager.cs index a83eaef..491646d 100644 --- a/scripts/GameManager.cs +++ b/scripts/GameManager.cs @@ -16,25 +16,29 @@ public partial class GameManager : Node3D [Export] public int maxFarThreads = 16; public static bool Loading { get; private set; } = true; - public static GameManager Singleton { get; private set; } + public static GameManager Instance { get; private set; } public static IGenerator Generator { get; private set; } public static Universe GameUniverse { get; private set; } public Player MainPlayer { get; private set; } - public List Players { get; private set; } = []; + public Dictionary Players { get; private set; } = []; private double closeTickTimer = 0; private double farTickTimer = 0; - public bool simulating = false; + public bool simulatingClose = false; + public bool simulatingFar = false; public bool playerReady = false; private readonly Dictionary spawnedObjects = []; private readonly ConcurrentQueue spawnQueue = []; + public override void _EnterTree() + { + Instance = this; + } + public override void _Ready() { - Singleton = this; - Generator = new TestGenerator(); if (Global.IsGameHost) @@ -53,7 +57,7 @@ public partial class GameManager : Node3D public override void _ExitTree() { Loading = true; - Singleton = null; + Instance = null; } public override void _Process(double delta) @@ -64,20 +68,14 @@ public partial class GameManager : Node3D } closeTickTimer += delta; - farTickTimer += delta; - - if (closeTickTimer >= closeTickInterval) + if (closeTickTimer >= closeTickInterval && !simulatingClose) { SimulateClose(closeTickTimer); closeTickTimer = 0; } - if (simulating) - { - farTickTimer = 0; - } - - if (farTickTimer >= farTickInterval) + farTickTimer += delta; + if (farTickTimer >= farTickInterval && !simulatingFar) { double taskFarTickTimer = farTickTimer; Task.Run(() => @@ -94,22 +92,48 @@ public partial class GameManager : Node3D return MainPlayer.PlayerData.CurrentSector; } + public Player GetPlayer(long id) + { + Players.TryGetValue(id, out Player player); + return player; + } + private void SimulateClose(double delta) { - List neighbours = GetCurrentSector().GetNeighbouringSectors(); + simulatingClose = true; - neighbours.ForEach(sector => + List sectorsClose = GetCurrentSector().GetNeighbouringSectors(); + + List tasks = []; + sectorsClose.ForEach(sector => { - Task.Run(() => + Task simulateTask = Task.Run(() => { sector.Simulate(delta); }); + + tasks.Add(simulateTask); }); + Task.WaitAll([.. tasks]); + + foreach (KeyValuePair player in Players) + { + List playerSectors = player.Value.PlayerData.CurrentSector.GetNeighbouringSectors(); + playerSectors.ForEach(sector => + { + if (player.Key != 1) + { + sector.NetworkSync(player.Key); + } + }); + } + + simulatingClose = false; } private void SimulateFar(double delta) { - simulating = true; + simulatingFar = true; List tasks = []; @@ -134,7 +158,7 @@ public partial class GameManager : Node3D Task.WaitAll([.. tasks]); - simulating = false; + simulatingFar = false; } private void SimulateFarClustered(double delta, int startX, int countX) @@ -195,7 +219,7 @@ public partial class GameManager : Node3D neighbours.ForEach(sector => sector.SpawnObjects()); } - public Player SpawnPlayer(Character character, bool isMainPlayer = false) + public Player SpawnPlayer(Character character, long networkId, bool isMainPlayer = false) { Player player = character.InstantiatePlayer(); player.GameMenu = GameMenu; @@ -206,7 +230,7 @@ public partial class GameManager : Node3D OnPlayerReady(); } - Players.Add(player); + Players.Add(networkId, player); character.UpdateSector(); @@ -262,10 +286,10 @@ public partial class GameManager : Node3D { Sector current = GetCurrentSector(); - foreach (Player player in Players) + foreach (KeyValuePair player in Players) { - player.PlayerData.UpdateSectorOffset(current); - player.PlayerData.UpdateNodePosition(); + player.Value.PlayerData.UpdateSectorOffset(current); + player.Value.PlayerData.UpdateNodePosition(); } List nearby = current.GetNeighbouringSectors(); @@ -281,20 +305,18 @@ public partial class GameManager : Node3D else { gameObject.UpdateSectorOffset(current); - node.GlobalPosition = gameObject.LocalCoordinates + gameObject.SectorOffset; + node._Process(0); } } nearby.ForEach(sector => sector.SpawnObjects()); } - // These should be in RPCNode and should be queued public void SendUniverseToClient(long id) { RpcId(id, nameof(RpcDownloadUniverse), Generator.GetUniverseSize(), Generator.GetSectorSize()); } - // These should be in RPCNode and should be queued [Rpc(MultiplayerApi.RpcMode.Authority)] public void RpcDownloadUniverse(Vector3I universeSize, Vector3 sectorSize) { @@ -302,96 +324,4 @@ public partial class GameManager : Node3D Loading = false; } - - // These should be in RPCNode and should be queued - [Rpc(MultiplayerApi.RpcMode.AnyPeer)] - public void RpcSendNearbySectors(Vector3I coordinates, long id = -1) - { - Sector sector = GameUniverse.GetSector(coordinates); - if (sector == null) - { - return; - } - - List sectors = sector.GetNeighbouringSectors(); - sectors.ForEach(sector => SendSectorToClients(sector, id == -1 ? null : id)); - } - - // These should be in RPCNode and should be queued - public void SendSectorToClients(Sector sector, long? id = null) - { - foreach (GameObject gameObject in sector.GameObjects) - { - if (gameObject is Character) - { - continue; - } - - Godot.Collections.Dictionary gameObjectData = new() { - { "type", gameObject.GetType().Name }, - { "sectorCoordinates", sector.Coordinates }, - { "coordinates", gameObject.LocalCoordinates }, - { "uuid", gameObject.UUID.ToString() }, - }; - - if (id.HasValue) - { - RpcId(id.Value, nameof(RpcDownloadGameObject), gameObjectData); - } - else - { - Rpc(nameof(RpcDownloadGameObject), gameObjectData); - } - } - } - - // These should be in RPCNode and should be queued - [Rpc(MultiplayerApi.RpcMode.Authority)] - public void RpcDownloadGameObject(Godot.Collections.Dictionary gameObjectData) - { - if (!gameObjectData.TryGetValue("type", out var typeData)) - { - return; - } - if (!gameObjectData.TryGetValue("sectorCoordinates", out var sectorCoordinatesData)) - { - return; - } - if (!gameObjectData.TryGetValue("coordinates", out var coordinatesData)) - { - return; - } - if (!gameObjectData.TryGetValue("uuid", out var uuidData)) - { - return; - } - - string type = (string)typeData; - Vector3I sectorCoordinates = (Vector3I)sectorCoordinatesData; - Vector3 coordinates = (Vector3)coordinatesData; - Guid uuid = Guid.Parse((string)uuidData); - - Sector sector = GameUniverse.GetSector(sectorCoordinates); - if (sector == null) - { - return; - } - - GameObject gameObject; - switch (type) - { - case "Star": - gameObject = new Star(sector, coordinates); - break; - - default: - return; - } - - gameObject.UUID = uuid; - - Spawn(gameObject); - - sector.AssignObject(gameObject); - } } diff --git a/scripts/GameObject.cs b/scripts/GameObject.cs index a689a96..8ad4333 100644 --- a/scripts/GameObject.cs +++ b/scripts/GameObject.cs @@ -4,13 +4,107 @@ using Godot; public abstract class GameObject { - public Guid UUID { get; set; } + [Flags] + public enum DirtyFlags : ulong + { + None = 0, + UUID = 1UL << 0, + Sector = 1UL << 1, + LocalCoordinates = 1UL << 2, + Rotation = 1UL << 3, + Velocity = 1UL << 4, + AngularVelocity = 1UL << 5 + } + public DirtyFlags DirtyBits { get; protected set; } = DirtyFlags.None; + + protected Guid _uuid; + public Guid UUID + { + get => _uuid; + set + { + if (_uuid != value) + { + _uuid = value; + DirtyBits |= DirtyFlags.UUID; + } + } + } + + protected Vector3I _currentSectorCoordinates; + protected Sector _currentSector; + public Sector CurrentSector + { + get => _currentSector; + protected set + { + if (_currentSector != value) + { + _currentSector = value; + _currentSectorCoordinates = value.Coordinates; + DirtyBits |= DirtyFlags.Sector; + } + } + } + + protected Vector3 _localCoordinates; + public Vector3 LocalCoordinates + { + get => _localCoordinates; + protected set + { + if (_localCoordinates != value) + { + _localCoordinates = value; + DirtyBits |= DirtyFlags.LocalCoordinates; + } + } + } + + protected Vector3 _rotation; + public Vector3 Rotation + { + get => _rotation; + protected set + { + if (_rotation != value) + { + _rotation = value; + DirtyBits |= DirtyFlags.Rotation; + } + } + } + + protected Vector3 _velocity; + public Vector3 Velocity + { + get => _velocity; + protected set + { + if (_velocity != value) + { + _velocity = value; + DirtyBits |= DirtyFlags.Velocity; + } + } + } + + protected Vector3 _angularVelocity; + public Vector3 AngularVelocity + { + get => _angularVelocity; + protected set + { + if (_angularVelocity != value) + { + _angularVelocity = value; + DirtyBits |= DirtyFlags.AngularVelocity; + } + } + } - public Sector CurrentSector { get; protected set; } - public Vector3 LocalCoordinates { get; protected set; } public Vector3Dec GlobalCoordinates { get; protected set; } - - public Vector3 SectorOffset { get; set; } + public Vector3 SectorOffset { get; protected set; } protected bool reassigning = false; @@ -21,6 +115,10 @@ public abstract class GameObject CurrentSector = sector; LocalCoordinates = localCoordinates; + Velocity = new(0, 0, 1); + AngularVelocity = Vector3.Zero; + Rotation = Vector3.Zero; + GlobalCoordinates = CalculateGlobalCoordinates(sector.GlobalCenterCoordinates, localCoordinates); } @@ -46,6 +144,12 @@ public abstract class GameObject ); } + public void ApplyVelocity(double delta) + { + SetCoordinatesFromLocal(LocalCoordinates + Velocity * delta); + Rotation += AngularVelocity * delta; + } + public bool IsInCurrentSector() { return Helpers.IsInsideArea(CurrentSector.Size / 2, LocalCoordinates); @@ -87,15 +191,15 @@ public abstract class GameObject UpdateSectorOffsetToMainPlayer(); - List neighbours = GameManager.Singleton.GetCurrentSector().GetNeighbouringSectors(); + List neighbours = GameManager.Instance.GetCurrentSector().GetNeighbouringSectors(); if (neighbours.Contains(sector)) { - GameManager.Singleton.Spawn(this); + GameManager.Instance.Spawn(this); } else { - GameManager.Singleton.Despawn(this); + GameManager.Instance.Despawn(this); } reassigning = false; @@ -114,7 +218,7 @@ public abstract class GameObject public void UpdateSectorOffsetToMainPlayer() { - UpdateSectorOffset(GameManager.Singleton.GetCurrentSector()); + UpdateSectorOffset(GameManager.Instance.GetCurrentSector()); } public void SetCoordinatesFromGlobal(Vector3Dec globalCoordinates) @@ -142,6 +246,8 @@ public abstract class GameObject public virtual void Simulate(double delta) { + ApplyVelocity(delta); + if (!reassigning && !IsInCurrentSector()) { UpdateSector(); @@ -157,4 +263,70 @@ public abstract class GameObject return instance; } + + public virtual Godot.Collections.Dictionary NetworkWrite(long id, bool full) + { + Godot.Collections.Dictionary gameObjectData = []; + + if (full) + gameObjectData.Add("type", GetType().ToString()); + + if (DirtyBits.HasFlag(DirtyFlags.Sector) || full) + gameObjectData.Add("sectorCoordinates", _currentSectorCoordinates); + + if (DirtyBits.HasFlag(DirtyFlags.LocalCoordinates) || full) + gameObjectData.Add("localCoordinates", _localCoordinates); + + if (DirtyBits.HasFlag(DirtyFlags.Rotation) || full) + gameObjectData.Add("rotation", _rotation); + + if (DirtyBits.HasFlag(DirtyFlags.Velocity) || full) + gameObjectData.Add("velocity", _velocity); + + if (DirtyBits.HasFlag(DirtyFlags.AngularVelocity) || full) + gameObjectData.Add("angularVelocity", _angularVelocity); + + if (gameObjectData.Count > 0) + { + gameObjectData.Add("uuid", UUID.ToString()); + return gameObjectData; + } + + return null; + } + + public virtual void NetworkRead(Godot.Collections.Dictionary gameObjectData) + { + if (gameObjectData.TryGetValue("sectorCoordinates", out var sectorCoordinatesData)) + { + Sector newSector = GameManager.GameUniverse.GetSector((Vector3I)sectorCoordinatesData); + CurrentSector.GameObjects.Remove(this); + newSector.GameObjects.Add(this); + + AssignSector(newSector); + } + + if (gameObjectData.TryGetValue("localCoordinates", out var localCoordinatesData)) + LocalCoordinates = (Vector3)localCoordinatesData; + + if (gameObjectData.TryGetValue("rotation", out var rotationData)) + Rotation = (Vector3)rotationData; + + if (gameObjectData.TryGetValue("velocity", out var velocityData)) + Velocity = (Vector3)velocityData; + + if (gameObjectData.TryGetValue("angularVelocity", out var angularVelocityData)) + AngularVelocity = (Vector3)angularVelocityData; + } + + public override bool Equals(object obj) + { + GameObject gameObj = (GameObject)obj; + return gameObj.UUID == UUID; + } + + public override int GetHashCode() + { + return UUID.GetHashCode(); + } } diff --git a/scripts/GameObjects/Character.cs b/scripts/GameObjects/Character.cs index 2b7ceef..7ed6327 100644 --- a/scripts/GameObjects/Character.cs +++ b/scripts/GameObjects/Character.cs @@ -16,17 +16,7 @@ public class Character(Sector sector, Vector3 localCoordinates) : GameObject(sec if (IsMainPlayer()) { - GameManager.Singleton.ApplyOrigin(); - - if (!Global.IsGameHost) - { - GameManager.Singleton.RpcId( - 1, - nameof(GameManager.Singleton.RpcSendNearbySectors), - CurrentSector.Coordinates, - NetworkManager.Singleton.LocalNetId - ); - } + GameManager.Instance.ApplyOrigin(); } reassigning = false; @@ -34,7 +24,7 @@ public class Character(Sector sector, Vector3 localCoordinates) : GameObject(sec public bool IsMainPlayer() { - return player == GameManager.Singleton.MainPlayer; + return player == GameManager.Instance.MainPlayer; } public Player InstantiatePlayer() @@ -48,6 +38,14 @@ public class Character(Sector sector, Vector3 localCoordinates) : GameObject(sec return instance; } + public override void Simulate(double delta) + { + if (!reassigning && !IsInCurrentSector()) + { + UpdateSector(); + } + } + public override void UpdateNodePosition() { if (IsMainPlayer()) diff --git a/scripts/GameObjects/Nodes/StarNode.cs b/scripts/GameObjects/Nodes/StarNode.cs index e48ccf7..c7e08d3 100644 --- a/scripts/GameObjects/Nodes/StarNode.cs +++ b/scripts/GameObjects/Nodes/StarNode.cs @@ -4,13 +4,41 @@ public partial class StarNode : StaticBody3D { public Star StarData { get; set; } + private Vector3 lastFrameCoordinates = Vector3.Zero; + private Vector3 lastFrameRotation = Vector3.Zero; + + private Vector3 velocityOffset = Vector3.Zero; + private Vector3 angularVelocityOffset = Vector3.Zero; + public override void _Ready() { GlobalPosition = StarData.LocalCoordinates + StarData.SectorOffset; + GlobalRotation = StarData.Rotation; } public override void _Process(double delta) { - GlobalPosition = StarData.LocalCoordinates + StarData.SectorOffset; + if (StarData.LocalCoordinates == lastFrameCoordinates) + { + velocityOffset += StarData.Velocity * delta; + } + else + { + lastFrameCoordinates = StarData.LocalCoordinates; + velocityOffset = Vector3.Zero; + } + + if (StarData.Rotation == lastFrameRotation) + { + angularVelocityOffset += StarData.AngularVelocity * delta; + } + else + { + lastFrameRotation = StarData.Rotation; + angularVelocityOffset = Vector3.Zero; + } + + GlobalPosition = StarData.LocalCoordinates + StarData.SectorOffset + velocityOffset; + GlobalRotation = StarData.Rotation + angularVelocityOffset; } } diff --git a/scripts/GameObjects/Star.cs b/scripts/GameObjects/Star.cs index 16a7914..337e436 100644 --- a/scripts/GameObjects/Star.cs +++ b/scripts/GameObjects/Star.cs @@ -5,8 +5,6 @@ public class Star(Sector sector, Vector3 localCoordinates) : GameObject(sector, public override void Simulate(double delta) { base.Simulate(delta); - - //SetCoordinatesFromGlobal(new(GlobalCoordinates.X, GlobalCoordinates.Y, GlobalCoordinates.Z + 1 * (decimal)delta)); } public override Node3D Instantiate(Sector sector) @@ -20,4 +18,22 @@ public class Star(Sector sector, Vector3 localCoordinates) : GameObject(sector, return instance; } + + public override Godot.Collections.Dictionary NetworkWrite(long id, bool full) + { + Godot.Collections.Dictionary gameObjectData = base.NetworkWrite(id, full); + + if (gameObjectData != null) + { + QueueManager.NetworkSyncQueue.Enqueue((id, gameObjectData)); + } + DirtyBits = DirtyFlags.None; + + return gameObjectData; + } + + public override void NetworkRead(Godot.Collections.Dictionary gameObjectData) + { + base.NetworkRead(gameObjectData); + } } diff --git a/scripts/Generator/TestGenerator.cs b/scripts/Generator/TestGenerator.cs index 8a54986..9565a43 100644 --- a/scripts/Generator/TestGenerator.cs +++ b/scripts/Generator/TestGenerator.cs @@ -30,9 +30,9 @@ public class TestGenerator : IGenerator public Vector3I GetSectorOffset(Vector3I universeSize) { return new( - (int)Mathf.Floor(UNIVERSE_SIZE.X / 2), - (int)Mathf.Floor(UNIVERSE_SIZE.X / 2), - (int)Mathf.Floor(UNIVERSE_SIZE.X / 2) + (int)Mathf.Floor(universeSize.X / 2), + (int)Mathf.Floor(universeSize.X / 2), + (int)Mathf.Floor(universeSize.X / 2) ); } @@ -118,10 +118,13 @@ public class TestGenerator : IGenerator for (int i = 0; i < starCount; i++) { - Vector3 localCoordinates = GenerateLocalCoordinates(); - Star star = GenerateStar(sector, localCoordinates); + if (coordinates.X == 5 && coordinates.Y == 5 && coordinates.Z == 5) + { + Vector3 localCoordinates = GenerateLocalCoordinates(); + Star star = GenerateStar(sector, localCoordinates); - sector.GameObjects.Add(star); + sector.GameObjects.Add(star); + } } int shipCount = rng.RandiRange( diff --git a/scripts/NetworkManager.cs b/scripts/NetworkManager.cs index 7ab4303..328a324 100644 --- a/scripts/NetworkManager.cs +++ b/scripts/NetworkManager.cs @@ -3,15 +3,13 @@ using Godot; public partial class NetworkManager : Node { - [Export] public RPCNode RPCNode { get; private set; } - - public static NetworkManager Singleton { get; private set; } + public static NetworkManager Instance { get; private set; } public int LocalNetId { get; private set; } = -1; public override void _Ready() { - Singleton = this; + Instance = this; if (Global.IsGameHost) { @@ -66,7 +64,7 @@ public partial class NetworkManager : Node { if (Global.IsGameHost) { - GameManager.Singleton.SendUniverseToClient(id); + GameManager.Instance.SendUniverseToClient(id); } _ = SpawnNetPlayer(id); @@ -75,7 +73,7 @@ public partial class NetworkManager : Node public void OnPlayerDisconnected(long id) { Player player = GetNode($"/root/Game/Space/Player-{id}"); - GameManager.Singleton.DespawnPlayer(player); + GameManager.Instance.DespawnPlayer(player); } private async Task SpawnNetPlayer(long id) @@ -88,14 +86,9 @@ public partial class NetworkManager : Node bool isMainPlayer = LocalNetId == id; Character character = new(GameManager.GameUniverse.Sectors[5, 5, 5], new(0, 0, 0)); - Player player = GameManager.Singleton.SpawnPlayer(character, isMainPlayer); + Player player = GameManager.Instance.SpawnPlayer(character, id, isMainPlayer); player.Name = $"Player-{id}"; player.SetMultiplayerAuthority((int)id); - - if (isMainPlayer && !Global.IsGameHost) - { - GameManager.Singleton.RpcId(1, nameof(GameManager.Singleton.RpcSendNearbySectors), character.CurrentSector.Coordinates, id); - } } } diff --git a/scripts/Player.cs b/scripts/Player.cs index 121c6c6..955046c 100644 --- a/scripts/Player.cs +++ b/scripts/Player.cs @@ -10,11 +10,11 @@ public partial class Player : CharacterBody3D [Export] public double NetworkPhysicsSyncInterval = 0.05; public Control GameMenu { get; set; } - public Character PlayerData { get; set; } - private Vector3 gravityVelocity = Vector3.Zero; - private Vector3 movementVelocity = Vector3.Zero; + public Vector3 GravityVelocity { get; set; } = Vector3.Zero; + public Vector3 MovementVelocity { get; set; } = Vector3.Zero; + private GravityReceiver gravityReceiver; private double cameraPitch = 0; private Camera3D camera; @@ -35,18 +35,20 @@ public partial class Player : CharacterBody3D { if (IsMultiplayerAuthority()) { + RPCNode rpc = RPCNode.Instance; + networkPhysicsSyncCounter += delta; if (networkPhysicsSyncCounter >= NetworkPhysicsSyncInterval) { networkPhysicsSyncCounter = 0; - Rpc(nameof(RpcSyncPhysics), movementVelocity, gravityVelocity, GlobalRotation); + rpc.Rpc(nameof(rpc.RpcSyncPlayerPhysics), GetMultiplayerAuthority(), MovementVelocity, GravityVelocity, GlobalRotation); } networkSyncCounter += delta; if (networkSyncCounter >= NetworkSyncInterval) { networkSyncCounter = 0; - Rpc(nameof(RpcSync), GlobalPosition, PlayerData.CurrentSector.Coordinates); + rpc.Rpc(nameof(rpc.RpcSyncPlayer), GetMultiplayerAuthority(), GlobalPosition, PlayerData.CurrentSector.Coordinates); } PlayerData.SetCoordinatesFromLocal(GlobalPosition); @@ -57,8 +59,8 @@ public partial class Player : CharacterBody3D public override void _PhysicsProcess(double delta) { - Vector3 newGravityVelocity = gravityVelocity; - Vector3 newMovementVelocity = movementVelocity; + Vector3 newGravityVelocity = GravityVelocity; + Vector3 newMovementVelocity = MovementVelocity; if (!GameMenu.Visible && IsMultiplayerAuthority()) { @@ -76,8 +78,8 @@ public partial class Player : CharacterBody3D } } - gravityVelocity = newGravityVelocity; - movementVelocity = newMovementVelocity; + GravityVelocity = newGravityVelocity; + MovementVelocity = newMovementVelocity; Velocity = newGravityVelocity + newMovementVelocity; MoveAndSlide(); @@ -237,33 +239,4 @@ public partial class Player : CharacterBody3D RotateObjectLocal(Vector3.Forward, rotateAmountZ); } - - [Rpc(MultiplayerApi.RpcMode.AnyPeer)] - public void RpcSync(Vector3 position, Vector3I sectorCoordinates) - { - Sector sector = GameManager.GameUniverse.GetSector(sectorCoordinates); - if (sector == null) - { - return; - } - - Vector3Dec newGlobal = PlayerData.CalculateGlobalCoordinates(sector.GlobalCenterCoordinates, position); - PlayerData.SetCoordinatesFromGlobal(newGlobal); - - if (PlayerData.CurrentSector.Coordinates != sectorCoordinates) - { - sector.AssignObject(PlayerData); - } - - GlobalPosition = position + PlayerData.SectorOffset; - } - - [Rpc(MultiplayerApi.RpcMode.AnyPeer)] - public void RpcSyncPhysics(Vector3 _movementVelocity, Vector3 _gravityVelocity, Vector3 rotation) - { - movementVelocity = _movementVelocity; - gravityVelocity = _gravityVelocity; - - GlobalRotation = rotation; - } } diff --git a/scripts/QueueManager.cs b/scripts/QueueManager.cs index fdbb2da..4eb5cec 100644 --- a/scripts/QueueManager.cs +++ b/scripts/QueueManager.cs @@ -8,11 +8,18 @@ public partial class QueueManager : Node public static ConcurrentQueue ActionQueue = new(); public static ConcurrentQueue<(Sector, GameObject)> SectorReassignQueue = new(); + public static ConcurrentQueue<(long, Godot.Collections.Dictionary)> NetworkSyncQueue = new(); private readonly int sectorReassignQueueRateLimit = 500; + private readonly int networkSyncQueueRateLimit = 10; public override void _Process(double delta) { + if (!GameManager.Instance.playerReady) + { + return; + } + while (LogQueue.TryDequeue(out string text)) { GD.Print(text); @@ -25,7 +32,7 @@ public partial class QueueManager : Node int sectorReassignQueueProcessed = 0; while ( - !GameManager.Singleton.simulating + !GameManager.Instance.simulatingFar && sectorReassignQueueProcessed++ < sectorReassignQueueRateLimit && SectorReassignQueue.TryDequeue(out var item) ) @@ -37,5 +44,16 @@ public partial class QueueManager : Node gameObject.AssignSector(sector); } + + int networkSyncQueueProcessed = 0; + while ( + networkSyncQueueProcessed++ < networkSyncQueueRateLimit + && NetworkSyncQueue.TryDequeue(out var item) + ) + { + var (clientId, gameObjectData) = item; + + RPCNode.Instance.RpcId(clientId, nameof(RPCNode.RpcSyncGameObject), gameObjectData); + } } } diff --git a/scripts/RPCNode.cs b/scripts/RPCNode.cs index 5397ef2..93c659a 100644 --- a/scripts/RPCNode.cs +++ b/scripts/RPCNode.cs @@ -1,5 +1,171 @@ +using System; +using System.Collections.Generic; using Godot; public partial class RPCNode : Node { + public static RPCNode Instance { get; private set; } + + private readonly HashSet requestedFullGameObjects = []; + + public override void _EnterTree() + { + Instance = this; + } + + public override void _ExitTree() + { + Instance = null; + } + + [Rpc(MultiplayerApi.RpcMode.Authority)] + public void RpcSyncGameObject(Godot.Collections.Dictionary gameObjectData) + { + GD.Print("READING: " + gameObjectData); + + if (!GameManager.Instance.playerReady) + { + return; + } + + if (!gameObjectData.TryGetValue("uuid", out var uuidData)) + return; + + Guid uuid = Guid.Parse((string)uuidData); + + List sectors = GameManager.Instance.GetCurrentSector().GetNeighbouringSectors(); + foreach (Sector sector in sectors) + { + GameObject gameObject = sector.GetObjectById(uuid); + if (gameObject != null) + { + gameObject.NetworkRead(gameObjectData); + return; + } + } + + if (!TrySyncFullGameObject(gameObjectData)) + { + if (requestedFullGameObjects.Contains(uuid)) + { + return; + } + requestedFullGameObjects.Add(uuid); + RpcId(1, nameof(RequestFullGameObject), NetworkManager.Instance.LocalNetId, (string)uuidData); + } + } + + public bool TrySyncFullGameObject(Godot.Collections.Dictionary gameObjectData) + { + if (!gameObjectData.TryGetValue("type", out var typeData)) + return false; + if (!gameObjectData.TryGetValue("sectorCoordinates", out var sectorCoordinatesData)) + return false; + if (!gameObjectData.TryGetValue("localCoordinates", out var localCoordinatesData)) + return false; + if (!gameObjectData.TryGetValue("uuid", out var uuidData)) + return false; + + string type = (string)typeData; + Vector3I sectorCoordinates = (Vector3I)sectorCoordinatesData; + Vector3 localCoordinates = (Vector3)localCoordinatesData; + Guid uuid = Guid.Parse((string)uuidData); + + Sector sector = GameManager.GameUniverse.GetSector(sectorCoordinates); + if (sector == null) + return false; + + GameObject gameObject; + switch (type) + { + case "Star": + gameObject = new Star(sector, localCoordinates); + break; + + default: + return false; + } + + gameObject.UUID = uuid; + gameObject.NetworkRead(gameObjectData); + requestedFullGameObjects.Remove(uuid); + + sector.AssignObject(gameObject); + + return true; + } + + [Rpc(MultiplayerApi.RpcMode.AnyPeer)] + public void RequestFullGameObject(long id, string uuidString) + { + if (!Global.IsGameHost) + { + return; + } + + Guid uuid = Guid.Parse(uuidString); + + List sectors = GameManager.Instance.GetPlayer(id).PlayerData.CurrentSector.GetNeighbouringSectors(); + foreach (Sector sector in sectors) + { + GameObject gameObject = sector.GetObjectById(uuid); + if (gameObject != null) + { + gameObject.NetworkWrite(id, true); + return; + } + } + } + + [Rpc(MultiplayerApi.RpcMode.AnyPeer)] + public void RpcSyncPlayer(long id, Vector3 position, Vector3I sectorCoordinates) + { + if (!GameManager.Instance.playerReady) + { + return; + } + + GameManager.Instance.Players.TryGetValue(id, out Player player); + if (player == null || !player.IsInsideTree()) + { + return; + } + Character playerData = player.PlayerData; + + Sector sector = GameManager.GameUniverse.GetSector(sectorCoordinates); + if (sector == null) + { + return; + } + + Vector3Dec newGlobal = playerData.CalculateGlobalCoordinates(sector.GlobalCenterCoordinates, position); + playerData.SetCoordinatesFromGlobal(newGlobal); + + if (playerData.CurrentSector.Coordinates != sectorCoordinates) + { + sector.AssignObject(playerData); + } + + player.GlobalPosition = position + playerData.SectorOffset; + } + + [Rpc(MultiplayerApi.RpcMode.AnyPeer)] + public void RpcSyncPlayerPhysics(long id, Vector3 _movementVelocity, Vector3 _gravityVelocity, Vector3 rotation) + { + if (!GameManager.Instance.playerReady) + { + return; + } + + GameManager.Instance.Players.TryGetValue(id, out Player player); + if (player == null || !player.IsInsideTree()) + { + return; + } + + player.MovementVelocity = _movementVelocity; + player.GravityVelocity = _gravityVelocity; + + player.GlobalRotation = rotation; + } } diff --git a/scripts/Sector.cs b/scripts/Sector.cs index 077d196..8c801e0 100644 --- a/scripts/Sector.cs +++ b/scripts/Sector.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Godot; @@ -46,6 +47,14 @@ public class Sector }); } + public void NetworkSync(long id) + { + GameObjects.ForEach(gameObject => + { + gameObject.NetworkWrite(id, false); + }); + } + public bool IsObjectInSector(GameObject gameObject) { return Helpers.IsInsideGlobalArea(GlobalStartCoordinates, GlobalEndCoordinates, gameObject.GlobalCoordinates); @@ -58,7 +67,20 @@ public class Sector public void SpawnObjects() { - GameObjects.ForEach(GameManager.Singleton.Spawn); + GameObjects.ForEach(GameManager.Instance.Spawn); + } + + public GameObject GetObjectById(Guid id) + { + foreach (GameObject gameObject in GameObjects) + { + if (gameObject.UUID.Equals(id)) + { + return gameObject; + } + } + + return null; } public List GetNeighbouringSectors() -- 2.47.3