diff --git a/global.json b/global.json deleted file mode 100644 index 3c0982c..0000000 --- a/global.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "sdk": { - "version": "8.0.123", - "rollForward": "disable" - } -} diff --git a/scripts/GameManager.cs b/scripts/GameManager.cs index 20d2c05..491646d 100644 --- a/scripts/GameManager.cs +++ b/scripts/GameManager.cs @@ -11,7 +11,6 @@ public partial class GameManager : Node3D [Export] public QueueManager QueueManager { get; private set; } [Export] public Node3D SpaceRoot { get; private set; } - [Export] public double closeDistance = 50; [Export] public double closeTickInterval = 1; [Export] public double farTickInterval = 10; [Export] public int maxFarThreads = 16; @@ -22,7 +21,6 @@ public partial class GameManager : Node3D public static Universe GameUniverse { get; private set; } public Player MainPlayer { get; private set; } public Dictionary Players { get; private set; } = []; - public Dictionary> PlayerSpawnedObjects { get; private set; } = []; private double closeTickTimer = 0; private double farTickTimer = 0; @@ -31,7 +29,7 @@ public partial class GameManager : Node3D public bool simulatingFar = false; public bool playerReady = false; - private readonly Dictionary localSpawnedObjects = []; + private readonly Dictionary spawnedObjects = []; private readonly ConcurrentQueue spawnQueue = []; public override void _EnterTree() @@ -53,6 +51,7 @@ public partial class GameManager : Node3D private void OnPlayerReady() { playerReady = true; + SpawnClose(); } public override void _ExitTree() @@ -72,12 +71,11 @@ public partial class GameManager : Node3D if (closeTickTimer >= closeTickInterval && !simulatingClose) { SimulateClose(closeTickTimer); - SyncClose(); closeTickTimer = 0; } farTickTimer += delta; - if (farTickTimer >= farTickInterval && !simulatingFar && QueueManager.SectorReassignQueue.IsEmpty) + if (farTickTimer >= farTickInterval && !simulatingFar) { double taskFarTickTimer = farTickTimer; Task.Run(() => @@ -104,131 +102,123 @@ public partial class GameManager : Node3D { simulatingClose = true; - HashSet objectsToSimulate = []; + List sectorsClose = GetCurrentSector().GetNeighbouringSectors(); + + List tasks = []; + sectorsClose.ForEach(sector => + { + Task simulateTask = Task.Run(() => + { + sector.Simulate(delta); + }); + + tasks.Add(simulateTask); + }); + Task.WaitAll([.. tasks]); foreach (KeyValuePair player in Players) { - Character character = player.Value.PlayerData; - - List playerSectors = character.CurrentSector.GetNeighbouringSectors(); + List playerSectors = player.Value.PlayerData.CurrentSector.GetNeighbouringSectors(); playerSectors.ForEach(sector => { - sector.GameObjects.ForEach(gameObject => + if (player.Key != 1) { - double distance = character.GetDistanceToObject(gameObject); - if (distance <= closeDistance) - { - objectsToSimulate.Add(gameObject); - } - }); + sector.NetworkSync(player.Key); + } }); } - foreach (GameObject gameObject in objectsToSimulate) - { - gameObject.Simulate(delta); - } - simulatingClose = false; } - private void SyncClose() - { - foreach (KeyValuePair player in Players) - { - bool isHostPlayer = player.Key == 1; - long playerKey = player.Key; - Character character = player.Value.PlayerData; - - PlayerSpawnedObjects.TryGetValue(playerKey, out HashSet playerSpawnedObjects); - playerSpawnedObjects ??= []; - HashSet playerNewSpawnedObjects = []; - - List playerSectors = character.CurrentSector.GetNeighbouringSectors(); - - // Spawn/Send object - playerSectors.ForEach(sector => - { - sector.GameObjects.ForEach(gameObject => - { - double distance = character.GetDistanceToObject(gameObject); - if (distance <= closeDistance) - { - if (isHostPlayer) - Spawn(gameObject); - else - gameObject.NetworkWrite(playerKey, !playerSpawnedObjects.Contains(gameObject)); - - playerNewSpawnedObjects.Add(gameObject); - } - }); - }); - - // Despawn object - foreach (GameObject gameObject in playerSpawnedObjects) - { - if (!playerNewSpawnedObjects.Contains(gameObject)) - { - if (isHostPlayer) - Despawn(gameObject); - else - RPCNode.Instance.RpcId(playerKey, nameof(RPCNode.RpcDespawnGameObject), gameObject.UUID.ToString()); - } - } - - PlayerSpawnedObjects[playerKey] = playerNewSpawnedObjects; - } - } - private void SimulateFar(double delta) { simulatingFar = true; + List tasks = []; + Sector[,,] sectors = GameUniverse.Sectors; - HashSet allSpawnedObjects = []; - foreach (HashSet item in PlayerSpawnedObjects.Values) + int sizeX = sectors.GetLength(0); + int countX = Mathf.Clamp(sizeX / maxFarThreads, 1, int.MaxValue); + + for (int x = 0; x < sizeX; x += countX) { - foreach (GameObject gameObject in item) + double taskDelta = delta; + int taskStartX = x; + int taskCountX = countX; + + Task clusteredTask = Task.Run(() => { - allSpawnedObjects.Add(gameObject); - } + SimulateFarClustered(taskDelta, taskStartX, taskCountX); + }); + + tasks.Add(clusteredTask); } - int sizeX = sectors.GetLength(0); - - double taskDelta = delta; - Parallel.For(0, sizeX, x => - { - SimulateFarClustered(allSpawnedObjects, taskDelta, x); - }); + Task.WaitAll([.. tasks]); simulatingFar = false; } - private static void SimulateFarClustered(HashSet ignoreObjects, double delta, int x) + private void SimulateFarClustered(double delta, int startX, int countX) { + Vector3I currentCoordinates = GetCurrentSector().Coordinates; + Sector[,,] sectors = GameUniverse.Sectors; + int sizeX = sectors.GetLength(0); int sizeY = sectors.GetLength(1); int sizeZ = sectors.GetLength(2); - for (int y = 0; y < sizeY; y++) + int startSkipX = Mathf.Clamp(currentCoordinates.X - 1, 0, sizeX); + int startSkipY = Mathf.Clamp(currentCoordinates.Y - 1, 0, sizeY); + int startSkipZ = Mathf.Clamp(currentCoordinates.Z - 1, 0, sizeZ); + + int endSkipX = Mathf.Clamp(currentCoordinates.X + 1, 0, sizeX); + int endSkipY = Mathf.Clamp(currentCoordinates.Y + 1, 0, sizeY); + int endSkipZ = Mathf.Clamp(currentCoordinates.Z + 1, 0, sizeZ); + + int endX = Mathf.Clamp(startX + countX, 0, sizeX); + + for (int x = startX; x < endX; x++) { - for (int z = 0; z < sizeZ; z++) + for (int y = 0; y < sizeY; y++) { - try + Thread.Sleep(5); + for (int z = 0; z < sizeZ; z++) { - sectors[x, y, z].Simulate(delta, ignoreObjects); - } - catch (Exception e) - { - QueueManager.LogQueue.Enqueue("EXCEPTION: " + e.Message); + if (Helpers.IsBetweenInclusive(x, startSkipX, endSkipX)) + { + if (Helpers.IsBetweenInclusive(y, startSkipY, endSkipY)) + { + if (Helpers.IsBetweenInclusive(z, startSkipZ, endSkipZ)) + { + continue; + } + } + } + + try + { + sectors[x, y, z].Simulate(delta); + } + catch (Exception e) + { + QueueManager.LogQueue.Enqueue("EXCEPTION: " + e.Message); + } } } } } + private void SpawnClose() + { + List neighbours = GetCurrentSector().GetNeighbouringSectors(); + + neighbours.ForEach(sector => sector.SpawnObjects()); + } + public Player SpawnPlayer(Character character, long networkId, bool isMainPlayer = false) { Player player = character.InstantiatePlayer(); @@ -248,10 +238,8 @@ public partial class GameManager : Node3D return player; } - public void DespawnPlayer(Player player, long id) + public void DespawnPlayer(Player player) { - Players.Remove(id); - SpaceRoot.CallDeferred("remove_child", player); player.CallDeferred("queue_free"); } @@ -269,43 +257,31 @@ public partial class GameManager : Node3D { return; } - if (localSpawnedObjects.ContainsKey(gameObject)) + if (spawnedObjects.ContainsKey(gameObject)) { return; } Node3D instance = gameObject.Instantiate(GetCurrentSector()); - localSpawnedObjects.Add(gameObject, instance); + spawnedObjects.Add(gameObject, instance); SpaceRoot.CallDeferred("add_child", instance); } public void Despawn(GameObject gameObject) { - if (!localSpawnedObjects.ContainsKey(gameObject)) + if (!spawnedObjects.ContainsKey(gameObject)) { return; } - Node3D nodeToDespawn = localSpawnedObjects.GetValueOrDefault(gameObject); - localSpawnedObjects.Remove(gameObject); + Node3D nodeToDespawn = spawnedObjects.GetValueOrDefault(gameObject); + spawnedObjects.Remove(gameObject); SpaceRoot.CallDeferred("remove_child", nodeToDespawn); nodeToDespawn.CallDeferred("queue_free"); } - public void Despawn(Guid uuid) - { - foreach (GameObject gameObject in localSpawnedObjects.Keys) - { - if (gameObject.UUID == uuid) - { - Despawn(gameObject); - break; - } - } - } - public void ApplyOrigin() { Sector current = GetCurrentSector(); @@ -316,14 +292,24 @@ public partial class GameManager : Node3D player.Value.PlayerData.UpdateNodePosition(); } - foreach (KeyValuePair spawned in localSpawnedObjects) + List nearby = current.GetNeighbouringSectors(); + foreach (KeyValuePair spawned in spawnedObjects) { GameObject gameObject = spawned.Key; Node3D node = spawned.Value; - gameObject.UpdateSectorOffset(current); - node._Process(0); + if (!nearby.Contains(gameObject.CurrentSector)) + { + Despawn(gameObject); + } + else + { + gameObject.UpdateSectorOffset(current); + node._Process(0); + } } + + nearby.ForEach(sector => sector.SpawnObjects()); } public void SendUniverseToClient(long id) diff --git a/scripts/GameObject.cs b/scripts/GameObject.cs index 472f24d..8ad4333 100644 --- a/scripts/GameObject.cs +++ b/scripts/GameObject.cs @@ -1,295 +1,332 @@ using System; +using System.Collections.Generic; using Godot; public abstract class GameObject { - [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; + [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 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 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 _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 _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 _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; - } - } - } + protected Vector3 _angularVelocity; + public Vector3 AngularVelocity + { + get => _angularVelocity; + protected set + { + if (_angularVelocity != value) + { + _angularVelocity = value; + DirtyBits |= DirtyFlags.AngularVelocity; + } + } + } - public Vector3 SectorOffset { get; protected set; } + public Vector3Dec GlobalCoordinates { get; protected set; } + public Vector3 SectorOffset { get; protected set; } - protected bool reassigning = false; + protected bool reassigning = false; - public GameObject(Sector sector, Vector3 localCoordinates) - { - UUID = Guid.NewGuid(); + public GameObject(Sector sector, Vector3 localCoordinates) + { + UUID = Guid.NewGuid(); - CurrentSector = sector; - LocalCoordinates = localCoordinates; + CurrentSector = sector; + LocalCoordinates = localCoordinates; - Velocity = new(0, 0, 1); - AngularVelocity = Vector3.Zero; - Rotation = Vector3.Zero; - } + Velocity = new(0, 0, 1); + AngularVelocity = Vector3.Zero; + Rotation = Vector3.Zero; - public void ApplyVelocity(double delta) - { - SetCoordinates(LocalCoordinates + Velocity * delta); - Rotation += AngularVelocity * delta; - } + GlobalCoordinates = CalculateGlobalCoordinates(sector.GlobalCenterCoordinates, localCoordinates); + } - public bool IsInCurrentSector() - { - return Helpers.IsInsideArea(GameManager.Generator.GetSectorSize() / 2, LocalCoordinates); - } + public GameObject(Vector3Dec coordinates) + { + GlobalCoordinates = coordinates; + UpdateSector(); + } - public void UpdateSector() - { - if (!GameManager.GameUniverse.IsInside(CurrentSector.Coordinates, LocalCoordinates)) - { - return; - } + public GameObject(decimal x, decimal y, decimal z) + { + GlobalCoordinates = new(x, y, z); + UpdateSector(); + } - Vector3 sectorSize = GameManager.Generator.GetSectorSize() / 2; + public Vector3Dec CalculateGlobalCoordinates(Vector3Dec sectorCenter, Vector3 local) + { + return new + ( + sectorCenter.X + (decimal)local.X, + sectorCenter.Y + (decimal)local.Y, + sectorCenter.Z + (decimal)local.Z + ); + } - bool? x = null; - bool? y = null; - bool? z = null; + public void ApplyVelocity(double delta) + { + SetCoordinatesFromLocal(LocalCoordinates + Velocity * delta); + Rotation += AngularVelocity * delta; + } - if (LocalCoordinates.X > sectorSize.X) x = true; - else if (LocalCoordinates.X < -sectorSize.X) x = false; + public bool IsInCurrentSector() + { + return Helpers.IsInsideArea(CurrentSector.Size / 2, LocalCoordinates); + } - if (LocalCoordinates.Y > sectorSize.Y) y = true; - else if (LocalCoordinates.Y < -sectorSize.Y) y = false; + public void UpdateSector() + { + List neighbours = CurrentSector.GetNeighbouringSectors(); + foreach (Sector sector in neighbours) + { + if (sector.IsObjectInSector(this)) + { + reassigning = true; + sector.AssignObject(this); + return; + } + } - if (LocalCoordinates.Z > sectorSize.Z) z = true; - else if (LocalCoordinates.Z < -sectorSize.Z) z = false; + if (!GameManager.GameUniverse.IsInside(CurrentSector.Coordinates, LocalCoordinates)) + { + return; + } - Sector sector = GameManager.GameUniverse.GetNeighbouringSector(CurrentSector, x, y, z); - if (sector != null) - { - reassigning = true; - sector.AssignObject(this); - } - } + foreach (Sector sector in GameManager.GameUniverse.Sectors) + { + if (sector.IsObjectInSector(this)) + { + reassigning = true; + sector.AssignObject(this); + return; + } + } + } - public virtual void AssignSector(Sector sector) - { - Vector3 sectorOffset = GetSectorOffset(sector); + public virtual void AssignSector(Sector sector) + { + CurrentSector = sector; + ResetLocalCoordinates(); - CurrentSector = sector; - LocalCoordinates += sectorOffset; + UpdateSectorOffsetToMainPlayer(); - UpdateSectorOffsetToMainPlayer(); + List neighbours = GameManager.Instance.GetCurrentSector().GetNeighbouringSectors(); - reassigning = false; - } + if (neighbours.Contains(sector)) + { + GameManager.Instance.Spawn(this); + } + else + { + GameManager.Instance.Despawn(this); + } - public Vector3 GetSectorOffset(Sector sector) - { - Vector3I relative = CurrentSector.Coordinates - sector.Coordinates; - return relative * GameManager.Generator.GetSectorSize(); - } + reassigning = false; + } - public void UpdateSectorOffset(Sector sector) - { - SectorOffset = GetSectorOffset(sector); - } + public Vector3 GetSectorOffset(Sector sector) + { + Vector3Dec relative = CurrentSector.GlobalCenterCoordinates - sector.GlobalCenterCoordinates; + return relative.ToVector3(); + } - public void UpdateSectorOffsetToMainPlayer() - { - UpdateSectorOffset(GameManager.Instance.GetCurrentSector()); - } + public void UpdateSectorOffset(Sector sector) + { + SectorOffset = GetSectorOffset(sector); + } - public void SetCoordinates(Vector3 localCoordinates) - { - LocalCoordinates = localCoordinates; + public void UpdateSectorOffsetToMainPlayer() + { + UpdateSectorOffset(GameManager.Instance.GetCurrentSector()); + } - UpdateNodePosition(); - } + public void SetCoordinatesFromGlobal(Vector3Dec globalCoordinates) + { + GlobalCoordinates = globalCoordinates; + LocalCoordinates = (globalCoordinates - CurrentSector.GlobalCenterCoordinates).ToVector3(); - public double GetDistanceToObject(GameObject gameObject) - { - Sector sector = gameObject.CurrentSector; - Vector3 sectorOffset = GetSectorOffset(sector); + UpdateNodePosition(); + } - Vector3 position1 = LocalCoordinates; - Vector3 position2 = gameObject.LocalCoordinates - sectorOffset; + public void SetCoordinatesFromLocal(Vector3 localCoordinates) + { + LocalCoordinates = localCoordinates; + GlobalCoordinates = Vector3Dec.FromVector3(localCoordinates) + CurrentSector.GlobalCenterCoordinates; - return Helpers.GetDistance(position1, position2); - } + UpdateNodePosition(); + } - public virtual void UpdateNodePosition() { } + public void ResetLocalCoordinates() + { + SetCoordinatesFromGlobal(GlobalCoordinates); + } - public virtual void Simulate(double delta) - { - ApplyVelocity(delta); + public virtual void UpdateNodePosition() { } - if (!reassigning && !IsInCurrentSector()) - { - UpdateSector(); - } - } + public virtual void Simulate(double delta) + { + ApplyVelocity(delta); - public virtual Node3D Instantiate(Sector sector) - { - PackedScene modulePrefab = ResourceLoader.Load("res://prefabs/gameObjects/node.tscn"); - Node3D instance = modulePrefab.Instantiate(); + if (!reassigning && !IsInCurrentSector()) + { + UpdateSector(); + } + } - instance.Name = $"GameObject-{UUID}"; + public virtual Node3D Instantiate(Sector sector) + { + PackedScene modulePrefab = ResourceLoader.Load("res://prefabs/gameObjects/node.tscn"); + Node3D instance = modulePrefab.Instantiate(); - return instance; - } + instance.Name = $"GameObject-{UUID}"; - public virtual Godot.Collections.Dictionary NetworkWrite(long id, bool full) - { - Godot.Collections.Dictionary gameObjectData = []; + return instance; + } - if (full) - gameObjectData.Add("type", GetType().ToString()); + public virtual Godot.Collections.Dictionary NetworkWrite(long id, bool full) + { + Godot.Collections.Dictionary gameObjectData = []; - if (DirtyBits.HasFlag(DirtyFlags.Sector) || full) - gameObjectData.Add("sectorCoordinates", _currentSectorCoordinates); + if (full) + gameObjectData.Add("type", GetType().ToString()); - if (DirtyBits.HasFlag(DirtyFlags.LocalCoordinates) || full) - gameObjectData.Add("localCoordinates", _localCoordinates); + if (DirtyBits.HasFlag(DirtyFlags.Sector) || full) + gameObjectData.Add("sectorCoordinates", _currentSectorCoordinates); - if (DirtyBits.HasFlag(DirtyFlags.Rotation) || full) - gameObjectData.Add("rotation", _rotation); + if (DirtyBits.HasFlag(DirtyFlags.LocalCoordinates) || full) + gameObjectData.Add("localCoordinates", _localCoordinates); - if (DirtyBits.HasFlag(DirtyFlags.Velocity) || full) - gameObjectData.Add("velocity", _velocity); + if (DirtyBits.HasFlag(DirtyFlags.Rotation) || full) + gameObjectData.Add("rotation", _rotation); - if (DirtyBits.HasFlag(DirtyFlags.AngularVelocity) || full) - gameObjectData.Add("angularVelocity", _angularVelocity); + if (DirtyBits.HasFlag(DirtyFlags.Velocity) || full) + gameObjectData.Add("velocity", _velocity); - if (gameObjectData.Count > 0) - { - gameObjectData.Add("uuid", UUID.ToString()); - return gameObjectData; - } + if (DirtyBits.HasFlag(DirtyFlags.AngularVelocity) || full) + gameObjectData.Add("angularVelocity", _angularVelocity); - return null; - } + if (gameObjectData.Count > 0) + { + gameObjectData.Add("uuid", UUID.ToString()); + return gameObjectData; + } - 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); + return null; + } - AssignSector(newSector); - } + 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); - if (gameObjectData.TryGetValue("localCoordinates", out var localCoordinatesData)) - LocalCoordinates = (Vector3)localCoordinatesData; + AssignSector(newSector); + } - if (gameObjectData.TryGetValue("rotation", out var rotationData)) - Rotation = (Vector3)rotationData; + if (gameObjectData.TryGetValue("localCoordinates", out var localCoordinatesData)) + LocalCoordinates = (Vector3)localCoordinatesData; - if (gameObjectData.TryGetValue("velocity", out var velocityData)) - Velocity = (Vector3)velocityData; + if (gameObjectData.TryGetValue("rotation", out var rotationData)) + Rotation = (Vector3)rotationData; - if (gameObjectData.TryGetValue("angularVelocity", out var angularVelocityData)) - AngularVelocity = (Vector3)angularVelocityData; - } + if (gameObjectData.TryGetValue("velocity", out var velocityData)) + Velocity = (Vector3)velocityData; - public override bool Equals(object obj) - { - GameObject gameObj = (GameObject)obj; - return gameObj.UUID == UUID; - } + if (gameObjectData.TryGetValue("angularVelocity", out var angularVelocityData)) + AngularVelocity = (Vector3)angularVelocityData; + } - public override int GetHashCode() - { - return UUID.GetHashCode(); - } + 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 642c8c2..7ed6327 100644 --- a/scripts/GameObjects/Character.cs +++ b/scripts/GameObjects/Character.cs @@ -8,14 +8,9 @@ public class Character(Sector sector, Vector3 localCoordinates) : GameObject(sec public override void AssignSector(Sector sector) { - Vector3 sectorOffset = GetSectorOffset(sector); - CurrentSector = sector; - if (IsMainPlayer()) - { - LocalCoordinates += sectorOffset; - } + ResetLocalCoordinates(); UpdateSectorOffsetToMainPlayer(); UpdateNodePosition(); @@ -53,6 +48,13 @@ public class Character(Sector sector, Vector3 localCoordinates) : GameObject(sec public override void UpdateNodePosition() { - player.GlobalPosition = LocalCoordinates + SectorOffset; + if (IsMainPlayer()) + { + player.GlobalPosition = LocalCoordinates; + } + else + { + player.GlobalPosition = LocalCoordinates + SectorOffset; + } } } diff --git a/scripts/GameObjects/Star.cs b/scripts/GameObjects/Star.cs index c90b9f5..337e436 100644 --- a/scripts/GameObjects/Star.cs +++ b/scripts/GameObjects/Star.cs @@ -25,7 +25,7 @@ public class Star(Sector sector, Vector3 localCoordinates) : GameObject(sector, if (gameObjectData != null) { - QueueManager.NetworkSyncQueue.Enqueue((id, full, gameObjectData)); + QueueManager.NetworkSyncQueue.Enqueue((id, gameObjectData)); } DirtyBits = DirtyFlags.None; diff --git a/scripts/Generator/IGenerator.cs b/scripts/Generator/IGenerator.cs index 6ebc27b..796d4ea 100644 --- a/scripts/Generator/IGenerator.cs +++ b/scripts/Generator/IGenerator.cs @@ -7,12 +7,12 @@ public interface IGenerator public Universe InitializeEmptyUniverse(Vector3I universeSize, Vector3 sectorSize); public Universe GenerateUniverse(); - public Sector GenerateSector(Vector3I coordinates, RandomNumberGenerator rng); + public Sector GenerateSector(Vector3I coordinates); public Star GenerateStar(Sector sector, Vector3 localCoordinates); - public Star GenerateStar(Sector sector, RandomNumberGenerator rng); + public Star GenerateStar(Sector sector); public Vessel GenerateShip(Sector sector, Vector3 localCoordinates); - public Vessel GenerateShip(Sector sector, RandomNumberGenerator rng); + public Vessel GenerateShip(Sector sector); public Vessel GenerateStation(Sector sector, Vector3 localCoordinates); - public Vessel GenerateStation(Sector sector, RandomNumberGenerator rng); + public Vessel GenerateStation(Sector sector); } diff --git a/scripts/Generator/TestGenerator.cs b/scripts/Generator/TestGenerator.cs index 1ffe68d..9565a43 100644 --- a/scripts/Generator/TestGenerator.cs +++ b/scripts/Generator/TestGenerator.cs @@ -1,16 +1,21 @@ +using System.Collections.Generic; using System.Threading.Tasks; using Godot; public class TestGenerator : IGenerator { - public readonly static Vector3I UNIVERSE_SIZE = new(10, 10, 10); - public readonly static Vector3 SECTOR_SIZE = new(50, 50, 50); - public readonly static int STARS_PER_SECTOR = 1; - public readonly static int STARS_PER_SECTOR_VARIANCE = 0; - public readonly static int SHIPS_PER_SECTOR = 0; - public readonly static int SHIPS_PER_SECTOR_VARIANCE = 0; - public readonly static int STATIONS_PER_SECTOR = 0; - public readonly static int STATIONS_PER_SECTOR_VARIANCE = 0; + public static Vector3I UNIVERSE_SIZE = new(10, 10, 10); + public static Vector3 SECTOR_SIZE = new(50, 50, 50); + public static int STARS_PER_SECTOR = 1; + public static int STARS_PER_SECTOR_VARIANCE = 0; + public static int SHIPS_PER_SECTOR = 0; + public static int SHIPS_PER_SECTOR_VARIANCE = 0; + public static int STATIONS_PER_SECTOR = 0; + public static int STATIONS_PER_SECTOR_VARIANCE = 0; + + private readonly int threads = 16; + + private RandomNumberGenerator rng; public Vector3I GetUniverseSize() { @@ -22,7 +27,7 @@ public class TestGenerator : IGenerator return SECTOR_SIZE; } - public static Vector3I GetSectorOffset(Vector3I universeSize) + public Vector3I GetSectorOffset(Vector3I universeSize) { return new( (int)Mathf.Floor(universeSize.X / 2), @@ -31,7 +36,7 @@ public class TestGenerator : IGenerator ); } - public static Vector3I GetSectorOffset() + public Vector3I GetSectorOffset() { return GetSectorOffset(UNIVERSE_SIZE); } @@ -47,7 +52,9 @@ public class TestGenerator : IGenerator for (int z = 0; z < universeSize.Z; z++) { universe.Sectors[x, y, z] = new Sector( - new(x, y, z) + new(x, y, z), + GetSectorOffset(universeSize), + sectorSize ); } } @@ -58,33 +65,50 @@ public class TestGenerator : IGenerator public Universe GenerateUniverse() { - Universe universe = new(UNIVERSE_SIZE); + rng = new(); - Parallel.For(0, UNIVERSE_SIZE.X, x => + List tasks = []; + + Universe universe = new(UNIVERSE_SIZE); + int countX = Mathf.Clamp(UNIVERSE_SIZE.X / threads, 1, int.MaxValue); + + for (int x = 0; x < UNIVERSE_SIZE.X; x += countX) { - GenerateClustered(universe, x); - }); + int taskStartX = x; + int taskCountX = countX; + + Task clusteredTask = Task.Run(() => + { + GenerateClustered(universe, taskStartX, taskCountX); + }); + + tasks.Add(clusteredTask); + } + + Task.WaitAll([.. tasks]); return universe; } - private void GenerateClustered(Universe universe, int x) + private void GenerateClustered(Universe universe, int startX, int countX) { - RandomNumberGenerator rng = new(); - rng.Randomize(); + int endX = Mathf.Clamp(startX + countX, 0, UNIVERSE_SIZE.X); - for (int y = 0; y < UNIVERSE_SIZE.Y; y++) + for (int x = startX; x < endX; x++) { - for (int z = 0; z < UNIVERSE_SIZE.Z; z++) + for (int y = 0; y < UNIVERSE_SIZE.Y; y++) { - universe.Sectors[x, y, z] = GenerateSector(new(x, y, z), rng); + for (int z = 0; z < UNIVERSE_SIZE.Z; z++) + { + universe.Sectors[x, y, z] = GenerateSector(new(x, y, z)); + } } } } - public Sector GenerateSector(Vector3I coordinates, RandomNumberGenerator rng) + public Sector GenerateSector(Vector3I coordinates) { - Sector sector = new(coordinates); + Sector sector = new(coordinates, GetSectorOffset(), SECTOR_SIZE); int starCount = rng.RandiRange( STARS_PER_SECTOR - STARS_PER_SECTOR_VARIANCE, @@ -96,7 +120,7 @@ public class TestGenerator : IGenerator { if (coordinates.X == 5 && coordinates.Y == 5 && coordinates.Z == 5) { - Vector3 localCoordinates = GenerateLocalCoordinates(rng); + Vector3 localCoordinates = GenerateLocalCoordinates(); Star star = GenerateStar(sector, localCoordinates); sector.GameObjects.Add(star); @@ -111,7 +135,7 @@ public class TestGenerator : IGenerator for (int i = 0; i < shipCount; i++) { - Vector3 localCoordinates = GenerateLocalCoordinates(rng); + Vector3 localCoordinates = GenerateLocalCoordinates(); Vessel ship = GenerateShip(sector, localCoordinates); sector.GameObjects.Add(ship); @@ -125,7 +149,7 @@ public class TestGenerator : IGenerator for (int i = 0; i < stationCount; i++) { - Vector3 localCoordinates = GenerateLocalCoordinates(rng); + Vector3 localCoordinates = GenerateLocalCoordinates(); Vessel station = GenerateStation(sector, localCoordinates); sector.GameObjects.Add(station); @@ -139,9 +163,9 @@ public class TestGenerator : IGenerator return new Star(sector, localCoordinates); } - public Star GenerateStar(Sector sector, RandomNumberGenerator rng) + public Star GenerateStar(Sector sector) { - return GenerateStar(sector, GenerateLocalCoordinates(rng)); + return GenerateStar(sector, GenerateLocalCoordinates()); } public Vessel GenerateShip(Sector sector, Vector3 localCoordinates) @@ -149,9 +173,9 @@ public class TestGenerator : IGenerator return new Vessel(sector, localCoordinates); } - public Vessel GenerateShip(Sector sector, RandomNumberGenerator rng) + public Vessel GenerateShip(Sector sector) { - return GenerateShip(sector, GenerateLocalCoordinates(rng)); + return GenerateShip(sector, GenerateLocalCoordinates()); } public Vessel GenerateStation(Sector sector, Vector3 localCoordinates) @@ -159,12 +183,12 @@ public class TestGenerator : IGenerator return new Vessel(sector, localCoordinates); } - public Vessel GenerateStation(Sector sector, RandomNumberGenerator rng) + public Vessel GenerateStation(Sector sector) { - return GenerateStation(sector, GenerateLocalCoordinates(rng)); + return GenerateStation(sector, GenerateLocalCoordinates()); } - public static Vector3 GenerateLocalCoordinates(RandomNumberGenerator rng) + public Vector3 GenerateLocalCoordinates() { double x = (rng.Randf() - 0.5) * SECTOR_SIZE.X; double y = (rng.Randf() - 0.5) * SECTOR_SIZE.Y; diff --git a/scripts/Helpers.cs b/scripts/Helpers.cs index 27375b9..3b1add7 100644 --- a/scripts/Helpers.cs +++ b/scripts/Helpers.cs @@ -1,4 +1,3 @@ -using System; using Godot; public static class Helpers @@ -28,12 +27,18 @@ public static class Helpers return true; } - public static double GetDistance(Vector3 position1, Vector3 position2) + public static bool IsInsideGlobalArea(Vector3Dec areaStart, Vector3Dec areaEnd, Vector3Dec coordinates) { - double diffX = Math.Abs(position1.X - position2.X); - double diffY = Math.Abs(position1.Y - position2.Y); - double diffZ = Math.Abs(position1.Z - position2.Z); + if (coordinates.X >= areaEnd.X || coordinates.Y >= areaEnd.Y || coordinates.Z >= areaEnd.Z) + { + return false; + } - return Math.Sqrt(diffX * diffX + diffY * diffY + diffZ * diffZ); + if (coordinates.X < areaStart.X || coordinates.Y < areaStart.Y || coordinates.Z < areaStart.Z) + { + return false; + } + + return true; } } diff --git a/scripts/NetworkManager.cs b/scripts/NetworkManager.cs index 08bbcc5..328a324 100644 --- a/scripts/NetworkManager.cs +++ b/scripts/NetworkManager.cs @@ -73,7 +73,7 @@ public partial class NetworkManager : Node public void OnPlayerDisconnected(long id) { Player player = GetNode($"/root/Game/Space/Player-{id}"); - GameManager.Instance.DespawnPlayer(player, id); + GameManager.Instance.DespawnPlayer(player); } private async Task SpawnNetPlayer(long id) diff --git a/scripts/Player.cs b/scripts/Player.cs index 441b8b7..955046c 100644 --- a/scripts/Player.cs +++ b/scripts/Player.cs @@ -51,7 +51,7 @@ public partial class Player : CharacterBody3D rpc.Rpc(nameof(rpc.RpcSyncPlayer), GetMultiplayerAuthority(), GlobalPosition, PlayerData.CurrentSector.Coordinates); } - PlayerData.SetCoordinates(GlobalPosition); + PlayerData.SetCoordinatesFromLocal(GlobalPosition); } PlayerData.Simulate(delta); diff --git a/scripts/QueueManager.cs b/scripts/QueueManager.cs index f33a2cb..4eb5cec 100644 --- a/scripts/QueueManager.cs +++ b/scripts/QueueManager.cs @@ -4,13 +4,13 @@ using Godot; public partial class QueueManager : Node { - public static readonly ConcurrentQueue LogQueue = new(); - public static readonly ConcurrentQueue ActionQueue = new(); + public static ConcurrentQueue LogQueue = new(); + public static ConcurrentQueue ActionQueue = new(); - public static readonly ConcurrentQueue<(Sector, GameObject)> SectorReassignQueue = new(); - public static readonly ConcurrentQueue<(long, bool, Godot.Collections.Dictionary)> NetworkSyncQueue = new(); + public static ConcurrentQueue<(Sector, GameObject)> SectorReassignQueue = new(); + public static ConcurrentQueue<(long, Godot.Collections.Dictionary)> NetworkSyncQueue = new(); - private readonly int sectorReassignQueueRateLimit = 5000; + private readonly int sectorReassignQueueRateLimit = 500; private readonly int networkSyncQueueRateLimit = 10; public override void _Process(double delta) @@ -51,12 +51,9 @@ public partial class QueueManager : Node && NetworkSyncQueue.TryDequeue(out var item) ) { - var (clientId, full, gameObjectData) = item; + var (clientId, gameObjectData) = item; - if (full) - RPCNode.Instance.RpcId(clientId, nameof(RPCNode.RpcSpawnGameObject), gameObjectData); - else - RPCNode.Instance.RpcId(clientId, nameof(RPCNode.RpcSyncGameObject), gameObjectData); + RPCNode.Instance.RpcId(clientId, nameof(RPCNode.RpcSyncGameObject), gameObjectData); } } } diff --git a/scripts/RPCNode.cs b/scripts/RPCNode.cs index 7a00d2e..93c659a 100644 --- a/scripts/RPCNode.cs +++ b/scripts/RPCNode.cs @@ -6,6 +6,8 @@ public partial class RPCNode : Node { public static RPCNode Instance { get; private set; } + private readonly HashSet requestedFullGameObjects = []; + public override void _EnterTree() { Instance = this; @@ -16,31 +18,6 @@ public partial class RPCNode : Node Instance = null; } - [Rpc(MultiplayerApi.RpcMode.Authority)] - public void RpcDespawnGameObject(string uuidData) - { - GD.Print("DESPAWNING: " + uuidData); - - if (!GameManager.Instance.playerReady) - { - return; - } - - Guid uuid = Guid.Parse(uuidData); - - List sectors = GameManager.Instance.GetCurrentSector().GetNeighbouringSectors(); - foreach (Sector sector in sectors) - { - bool removed = sector.RemoveObjectById(uuid); - if (removed) - { - break; - } - } - - GameManager.Instance.Despawn(uuid); - } - [Rpc(MultiplayerApi.RpcMode.Authority)] public void RpcSyncGameObject(Godot.Collections.Dictionary gameObjectData) { @@ -66,22 +43,28 @@ public partial class RPCNode : Node return; } } + + if (!TrySyncFullGameObject(gameObjectData)) + { + if (requestedFullGameObjects.Contains(uuid)) + { + return; + } + requestedFullGameObjects.Add(uuid); + RpcId(1, nameof(RequestFullGameObject), NetworkManager.Instance.LocalNetId, (string)uuidData); + } } - - [Rpc(MultiplayerApi.RpcMode.Authority)] - public void RpcSpawnGameObject(Godot.Collections.Dictionary gameObjectData) + public bool TrySyncFullGameObject(Godot.Collections.Dictionary gameObjectData) { - GD.Print("SPAWNING: " + gameObjectData); - if (!gameObjectData.TryGetValue("type", out var typeData)) - return; + return false; if (!gameObjectData.TryGetValue("sectorCoordinates", out var sectorCoordinatesData)) - return; + return false; if (!gameObjectData.TryGetValue("localCoordinates", out var localCoordinatesData)) - return; + return false; if (!gameObjectData.TryGetValue("uuid", out var uuidData)) - return; + return false; string type = (string)typeData; Vector3I sectorCoordinates = (Vector3I)sectorCoordinatesData; @@ -90,7 +73,7 @@ public partial class RPCNode : Node Sector sector = GameManager.GameUniverse.GetSector(sectorCoordinates); if (sector == null) - return; + return false; GameObject gameObject; switch (type) @@ -100,15 +83,38 @@ public partial class RPCNode : Node break; default: - return; + return false; } gameObject.UUID = uuid; gameObject.NetworkRead(gameObjectData); + requestedFullGameObjects.Remove(uuid); sector.AssignObject(gameObject); - GameManager.Instance.Spawn(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)] @@ -132,7 +138,8 @@ public partial class RPCNode : Node return; } - playerData.SetCoordinates(position); + Vector3Dec newGlobal = playerData.CalculateGlobalCoordinates(sector.GlobalCenterCoordinates, position); + playerData.SetCoordinatesFromGlobal(newGlobal); if (playerData.CurrentSector.Coordinates != sectorCoordinates) { diff --git a/scripts/Sector.cs b/scripts/Sector.cs index 3f14bca..8c801e0 100644 --- a/scripts/Sector.cs +++ b/scripts/Sector.cs @@ -5,22 +5,45 @@ using Godot; public class Sector { public Vector3I Coordinates; + public Vector3Dec GlobalStartCoordinates; + public Vector3Dec GlobalCenterCoordinates; + public Vector3Dec GlobalEndCoordinates; + public Vector3 Size; public FastUniqueList GameObjects = new(); - public Sector(Vector3I coordinates) + public Sector(Vector3I coordinates, Vector3I offset, Vector3 size) { Coordinates = coordinates; + + Vector3Dec sizeDec = Vector3Dec.FromVector3(size); + + decimal startX = (coordinates.X - offset.X) * sizeDec.X; + decimal startY = (coordinates.Y - offset.Y) * sizeDec.Y; + decimal startZ = (coordinates.Z - offset.Z) * sizeDec.Z; + + GlobalStartCoordinates = new(startX, startY, startZ); + + GlobalCenterCoordinates = new( + startX + sizeDec.X / 2, + startY + sizeDec.Y / 2, + startZ + sizeDec.Z / 2 + ); + + GlobalEndCoordinates = new( + startX + sizeDec.X, + startY + sizeDec.Y, + startZ + sizeDec.Z + ); + + Size = size; } - public void Simulate(double delta, HashSet ignoreObjects = null) + public void Simulate(double delta) { GameObjects.ForEach(gameObject => { - if (ignoreObjects == null || !ignoreObjects.Contains(gameObject)) - { - gameObject.Simulate(delta); - } + gameObject.Simulate(delta); }); } @@ -32,6 +55,11 @@ public class Sector }); } + public bool IsObjectInSector(GameObject gameObject) + { + return Helpers.IsInsideGlobalArea(GlobalStartCoordinates, GlobalEndCoordinates, gameObject.GlobalCoordinates); + } + public void AssignObject(GameObject gameObject) { QueueManager.SectorReassignQueue.Enqueue((this, gameObject)); @@ -55,20 +83,6 @@ public class Sector return null; } - public bool RemoveObjectById(Guid id) - { - foreach (GameObject gameObject in GameObjects) - { - if (gameObject.UUID.Equals(id)) - { - GameObjects.Remove(gameObject); - return true; - } - } - - return false; - } - public List GetNeighbouringSectors() { List neighbours = []; @@ -100,15 +114,4 @@ public class Sector return neighbours; } - - public override bool Equals(object obj) - { - Sector sector = (Sector)obj; - return sector.Coordinates == Coordinates; - } - - public override int GetHashCode() - { - return Coordinates.GetHashCode(); - } } diff --git a/scripts/Universe.cs b/scripts/Universe.cs index 0194a82..f75ba77 100644 --- a/scripts/Universe.cs +++ b/scripts/Universe.cs @@ -28,29 +28,10 @@ public class Universe return Sectors[x, y, z]; } - public Sector GetNeighbouringSector(Sector sector, bool? x, bool? y, bool? z) - { - Vector3I coordinates = sector.Coordinates; - - if (x == true) coordinates.X++; - else if (x == false) coordinates.X--; - - if (y == true) coordinates.Y++; - else if (y == false) coordinates.Y--; - - if (z == true) coordinates.Z++; - else if (z == false) coordinates.Z--; - - coordinates.X = Math.Clamp(coordinates.X, 0, Size.X); - coordinates.Y = Math.Clamp(coordinates.Y, 0, Size.Y); - coordinates.Z = Math.Clamp(coordinates.Z, 0, Size.Z); - - return GetSector(coordinates); - } - public bool IsInside(Vector3I sectorCoordinates, Vector3 localCoordinates) { Vector3 sectorSize = GameManager.Generator.GetSectorSize() / 2; + Vector3I universeSize = GameManager.GameUniverse.Size; if ( sectorCoordinates.X == 0 && localCoordinates.X < -sectorSize.X || @@ -62,9 +43,9 @@ public class Universe } if ( - sectorCoordinates.X == Size.X - 1 && localCoordinates.X >= sectorSize.X || - sectorCoordinates.Y == Size.Y - 1 && localCoordinates.Y >= sectorSize.Y || - sectorCoordinates.Z == Size.Z - 1 && localCoordinates.Z >= sectorSize.Z + sectorCoordinates.X == universeSize.X - 1 && localCoordinates.X >= sectorSize.X || + sectorCoordinates.Y == universeSize.Y - 1 && localCoordinates.Y >= sectorSize.Y || + sectorCoordinates.Z == universeSize.Z - 1 && localCoordinates.Z >= sectorSize.Z ) { return false; diff --git a/scripts/Vector3Dec.cs b/scripts/Vector3Dec.cs new file mode 100644 index 0000000..032a058 --- /dev/null +++ b/scripts/Vector3Dec.cs @@ -0,0 +1,106 @@ +using System; +using Godot; + +public readonly struct Vector3Dec(decimal x, decimal y, decimal z) +{ + public static Vector3Dec Zero { get; } = new(0, 0, 0); + + public decimal X { get; } = x; + public decimal Y { get; } = y; + public decimal Z { get; } = z; + + public Vector3 ToVector3() + { + return new((double)X, (double)Y, (double)Z); + } + + public static Vector3Dec FromVector3(Vector3 vec) + { + return new((decimal)vec.X, (decimal)vec.Y, (decimal)vec.Z); + } + + public static Vector3Dec operator +(Vector3Dec vec1, Vector3Dec vec2) + { + return new(vec1.X + vec2.X, vec1.Y + vec2.Y, vec1.Z + vec2.Z); + } + + public static Vector3Dec operator -(Vector3Dec vec1, Vector3Dec vec2) + { + return new(vec1.X - vec2.X, vec1.Y - vec2.Y, vec1.Z - vec2.Z); + } + + public static Vector3Dec operator *(Vector3Dec vec1, Vector3Dec vec2) + { + return new(vec1.X * vec2.X, vec1.Y * vec2.Y, vec1.Z * vec2.Z); + } + + public static Vector3Dec operator /(Vector3Dec vec1, Vector3Dec vec2) + { + return new(vec1.X / vec2.X, vec1.Y / vec2.Y, vec1.Z / vec2.Z); + } + + public static Vector3Dec operator +(Vector3Dec vec, decimal value) + { + return new(vec.X + value, vec.Y + value, vec.Z + value); + } + + public static Vector3Dec operator +(decimal value, Vector3Dec vec) + { + return vec + value; + } + + public static Vector3Dec operator -(Vector3Dec vec, decimal value) + { + return new(vec.X - value, vec.Y - value, vec.Z - value); + } + + public static Vector3Dec operator -(decimal value, Vector3Dec vec) + { + return vec - value; + } + + public static Vector3Dec operator *(Vector3Dec vec, decimal value) + { + return new(vec.X * value, vec.Y * value, vec.Z * value); + } + + public static Vector3Dec operator *(decimal value, Vector3Dec vec) + { + return vec * value; + } + + public static Vector3Dec operator /(Vector3Dec vec, decimal value) + { + return new(vec.X / value, vec.Y / value, vec.Z / value); + } + + public static Vector3Dec operator /(decimal value, Vector3Dec vec) + { + return vec / value; + } + + public static Vector3Dec operator -(Vector3Dec vec) + { + return new(-vec.X, -vec.Y, -vec.Z); + } + + public static bool operator ==(Vector3Dec vec1, Vector3Dec vec2) + { + return vec1.X == vec2.X && vec1.Y == vec2.Y && vec1.Z == vec2.Z; + } + + public static bool operator !=(Vector3Dec vec1, Vector3Dec vec2) + { + return !(vec1 == vec2); + } + + public override bool Equals(object obj) + { + return obj != null && obj is Vector3Dec vec && this == vec; + } + + public override int GetHashCode() + { + return HashCode.Combine(X, Y, Z); + } +} diff --git a/scripts/Vector3Dec.cs.uid b/scripts/Vector3Dec.cs.uid new file mode 100644 index 0000000..1668d18 --- /dev/null +++ b/scripts/Vector3Dec.cs.uid @@ -0,0 +1 @@ +uid://def7cpbdn6gjm