using System; 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; 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 Vector3 SectorOffset { get; protected set; } protected bool reassigning = false; public GameObject(Sector sector, Vector3 localCoordinates) { UUID = Guid.NewGuid(); CurrentSector = sector; LocalCoordinates = localCoordinates; Velocity = new(0, 0, 1); AngularVelocity = Vector3.Zero; Rotation = Vector3.Zero; } public void ApplyVelocity(double delta) { SetCoordinates(LocalCoordinates + Velocity * delta); Rotation += AngularVelocity * delta; } public bool IsInCurrentSector() { return Helpers.IsInsideArea(GameManager.Generator.GetSectorSize() / 2, LocalCoordinates); } public void UpdateSector() { if (!GameManager.GameUniverse.IsInside(CurrentSector.Coordinates, LocalCoordinates)) { return; } Vector3 sectorSize = GameManager.Generator.GetSectorSize() / 2; bool? x = null; bool? y = null; bool? z = null; if (LocalCoordinates.X > sectorSize.X) x = true; else if (LocalCoordinates.X < -sectorSize.X) x = false; if (LocalCoordinates.Y > sectorSize.Y) y = true; else if (LocalCoordinates.Y < -sectorSize.Y) y = false; if (LocalCoordinates.Z > sectorSize.Z) z = true; else if (LocalCoordinates.Z < -sectorSize.Z) z = false; Sector sector = GameManager.GameUniverse.GetNeighbouringSector(CurrentSector, x, y, z); if (sector != null) { reassigning = true; sector.AssignObject(this); } } public virtual void AssignSector(Sector sector) { Vector3 sectorOffset = GetSectorOffset(sector); CurrentSector = sector; LocalCoordinates += sectorOffset; UpdateSectorOffsetToMainPlayer(); reassigning = false; } public Vector3 GetSectorOffset(Sector sector) { Vector3I relative = CurrentSector.Coordinates - sector.Coordinates; return relative * GameManager.Generator.GetSectorSize(); } public void UpdateSectorOffset(Sector sector) { SectorOffset = GetSectorOffset(sector); } public void UpdateSectorOffsetToMainPlayer() { UpdateSectorOffset(GameManager.Instance.GetCurrentSector()); } public void SetCoordinates(Vector3 localCoordinates) { LocalCoordinates = localCoordinates; UpdateNodePosition(); } public double GetDistanceToObject(GameObject gameObject) { Sector sector = gameObject.CurrentSector; Vector3 sectorOffset = GetSectorOffset(sector); Vector3 position1 = LocalCoordinates; Vector3 position2 = gameObject.LocalCoordinates - sectorOffset; return Helpers.GetDistance(position1, position2); } public virtual void UpdateNodePosition() { } public virtual void Simulate(double delta) { ApplyVelocity(delta); if (!reassigning && !IsInCurrentSector()) { UpdateSector(); } } public virtual Node3D Instantiate(Sector sector) { PackedScene modulePrefab = ResourceLoader.Load("res://prefabs/gameObjects/node.tscn"); Node3D instance = modulePrefab.Instantiate(); instance.Name = $"GameObject-{UUID}"; 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(); } }