using System; using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Maths; using Robust.Shared.Serialization; namespace Robust.Shared.Map { /// /// A set of coordinates relative to another entity. /// [PublicAPI] [Serializable, NetSerializable] public readonly struct EntityCoordinates : IEquatable { public static readonly EntityCoordinates Invalid = new(EntityUid.Invalid, Vector2.Zero); /// /// ID of the entity that this position is relative to. /// public readonly EntityUid EntityId; /// /// Position in the entity's local space. /// public readonly Vector2 Position; /// /// Location of the X axis local to the entity. /// public float X => Position.X; /// /// Location of the Y axis local to the entity. /// public float Y => Position.Y; /// /// Constructs a new instance of . /// /// ID of the entity that this position is relative to. /// Position in the entity's local space. public EntityCoordinates(EntityUid entityId, Vector2 position) { EntityId = entityId; Position = position; } public EntityCoordinates(EntityUid entityId, float x, float y) { EntityId = entityId; Position = new Vector2(x, y); } /// /// Verifies that this set of coordinates can be currently resolved to a location. /// /// Entity Manager containing the entity Id. /// if this set of coordinates can be currently resolved to a location, otherwise . public bool IsValid(IEntityManager entityManager) { if (!EntityId.IsValid() || !entityManager.EntityExists(EntityId)) return false; if (!float.IsFinite(Position.X) || !float.IsFinite(Position.Y)) return false; return true; } /// /// Transforms this set of coordinates from the entity's local space to the map space. /// /// Entity Manager containing the entity Id. /// public MapCoordinates ToMap(IEntityManager entityManager) { if(!IsValid(entityManager)) return MapCoordinates.Nullspace; var transform = entityManager.GetComponent(EntityId); var worldPos = transform.WorldMatrix.Transform(Position); return new MapCoordinates(worldPos, transform.MapID); } /// /// Transform this set of coordinates from the entity's local space to the map space. /// /// Entity Manager containing the entity Id. /// public Vector2 ToMapPos(IEntityManager entityManager) { return ToMap(entityManager).Position; } /// /// Creates EntityCoordinates given an entity and some MapCoordinates. /// /// /// /// /// If is not on the same map as the . public static EntityCoordinates FromMap(EntityUid entity, MapCoordinates coordinates, IEntityManager? entMan = null) { IoCManager.Resolve(ref entMan); var transform = entMan.GetComponent(entity); if(transform.MapID != coordinates.MapId) throw new InvalidOperationException("Entity is not on the same map!"); var localPos = transform.InvWorldMatrix.Transform(coordinates.Position); return new EntityCoordinates(entity, localPos); } /// /// Creates EntityCoordinates given an entity Uid and some MapCoordinates. /// /// Entity Manager containing the entity Id. /// /// /// /// If is not on the same map as the . public static EntityCoordinates FromMap(IEntityManager entityManager, EntityUid entityUid, MapCoordinates coordinates) { return FromMap(entityUid, coordinates); } /// /// Creates a set of EntityCoordinates given some MapCoordinates. /// /// /// public static EntityCoordinates FromMap(IMapManager mapManager, MapCoordinates coordinates) { var mapId = coordinates.MapId; var mapEntity = mapManager.GetMapEntityId(mapId); return new EntityCoordinates(mapEntity, coordinates.Position); } /// /// Converts this set of coordinates to Vector2i. /// /// /// /// public Vector2i ToVector2i(IEntityManager entityManager, IMapManager mapManager) { if(!IsValid(entityManager)) return new Vector2i(); var gridId = GetGridId(entityManager); if (gridId != GridId.Invalid) { return mapManager.GetGrid(gridId).GetTileRef(this).GridIndices; } var (x, y) = ToMapPos(entityManager); return new Vector2i((int)Math.Floor(x), (int)Math.Floor(y)); } /// /// Returns an new set of EntityCoordinates with the same /// but on a different position. /// /// The position the new EntityCoordinates will be in /// A new set of EntityCoordinates with the specified position and same as this one. public EntityCoordinates WithPosition(Vector2 newPosition) { return new(EntityId, newPosition); } /// /// Returns a new set of EntityCoordinates local to a new entity. /// /// The Entity Manager holding this entity /// The entity that the new coordinates will be local to /// A new set of EntityCoordinates local to a new entity. public EntityCoordinates WithEntityId(IEntityManager entityManager, EntityUid entityId) { if(!entityManager.EntityExists(entityId)) return new EntityCoordinates(entityId, Vector2.Zero); return WithEntityId(entityId); } /// /// Returns a new set of EntityCoordinates local to a new entity. /// /// The entity that the new coordinates will be local to /// A new set of EntityCoordinates local to a new entity. public EntityCoordinates WithEntityId(EntityUid entity, IEntityManager? entMan = null) { IoCManager.Resolve(ref entMan); var mapPos = ToMap(entMan); if(!IsValid(entMan) || entMan.GetComponent(entity).MapID != mapPos.MapId) return new EntityCoordinates(entity, Vector2.Zero); var localPos = entMan.GetComponent(entity).InvWorldMatrix.Transform(mapPos.Position); return new EntityCoordinates(entity, localPos); } /// /// Returns the Grid Id these coordinates are on. /// If none of the ancestors are a grid, returns grid instead. /// /// /// Grid Id this entity is on or public GridId GetGridId(IEntityManager entityManager) { return !IsValid(entityManager) ? GridId.Invalid : entityManager.GetComponent(EntityId).GridID; } /// /// Returns the Map Id these coordinates are on. /// If the relative entity is not valid, returns instead. /// /// /// Map Id these coordinates are on or public MapId GetMapId(IEntityManager entityManager) { return !IsValid(entityManager) ? MapId.Nullspace : entityManager.GetComponent(EntityId).MapID; } /// /// Offsets the position by a given vector. This happens in local space. /// /// The vector to offset by local to the entity. /// Newly offset coordinates. public EntityCoordinates Offset(Vector2 position) { return new(EntityId, Position + position); } /// /// Compares two sets of coordinates to see if they are in range of each other. /// /// Entity Manager containing the two entity Ids. /// Other set of coordinates to use. /// maximum distance between the two sets of coordinates. /// True if the two points are within a given range. public bool InRange(IEntityManager entityManager, EntityCoordinates otherCoordinates, float range) { if (!IsValid(entityManager) || !otherCoordinates.IsValid(entityManager)) return false; if (EntityId == otherCoordinates.EntityId) return (otherCoordinates.Position - Position).LengthSquared < range * range; var mapCoordinates = ToMap(entityManager); var otherMapCoordinates = otherCoordinates.ToMap(entityManager); return mapCoordinates.InRange(otherMapCoordinates, range); } /// /// Tries to calculate the distance between two sets of coordinates. /// /// /// /// /// True if it was possible to calculate the distance public bool TryDistance(IEntityManager entityManager, EntityCoordinates otherCoordinates, out float distance) { distance = 0f; if (!IsValid(entityManager) || !otherCoordinates.IsValid(entityManager)) return false; if (EntityId == otherCoordinates.EntityId) { distance = (Position - otherCoordinates.Position).Length; return true; } var mapCoordinates = ToMap(entityManager); var otherMapCoordinates = otherCoordinates.ToMap(entityManager); if (mapCoordinates.MapId != otherMapCoordinates.MapId) return false; distance = (mapCoordinates.Position - otherMapCoordinates.Position).Length; return true; } #region IEquatable /// public bool Equals(EntityCoordinates other) { return EntityId.Equals(other.EntityId) && Position.Equals(other.Position); } /// public override bool Equals(object? obj) { return obj is EntityCoordinates other && Equals(other); } /// public override int GetHashCode() { return HashCode.Combine(EntityId, Position); } /// /// Check for equality by value between two objects. /// public static bool operator ==(EntityCoordinates left, EntityCoordinates right) { return left.Equals(right); } /// /// Check for inequality by value between two objects. /// public static bool operator !=(EntityCoordinates left, EntityCoordinates right) { return !left.Equals(right); } #endregion #region Operators /// /// Returns the sum for both coordinates but only if they have the same relative entity. /// /// Thrown when the relative entities aren't the same public static EntityCoordinates operator +(EntityCoordinates left, EntityCoordinates right) { if(left.EntityId != right.EntityId) throw new ArgumentException("Can't sum EntityCoordinates with different relative entities."); return new EntityCoordinates(left.EntityId, left.Position + right.Position); } /// /// Returns the difference for both coordinates but only if they have the same relative entity. /// /// Thrown when the relative entities aren't the same public static EntityCoordinates operator -(EntityCoordinates left, EntityCoordinates right) { if(left.EntityId != right.EntityId) throw new ArgumentException("Can't subtract EntityCoordinates with different relative entities."); return new EntityCoordinates(left.EntityId, left.Position - right.Position); } /// /// Returns the multiplication of both coordinates but only if they have the same relative entity. /// /// When the relative entities aren't the same public static EntityCoordinates operator *(EntityCoordinates left, EntityCoordinates right) { if(left.EntityId != right.EntityId) throw new ArgumentException("Can't multiply EntityCoordinates with different relative entities."); return new EntityCoordinates(left.EntityId, left.Position * right.Position); } /// /// Scales the coordinates by a given factor. /// /// When the relative entities aren't the same public static EntityCoordinates operator *(EntityCoordinates left, float right) { return new(left.EntityId, left.Position * right); } /// /// Scales the coordinates by a given factor. /// /// When the relative entities aren't the same public static EntityCoordinates operator *(EntityCoordinates left, int right) { return new(left.EntityId, left.Position * right); } #endregion /// /// Deconstructs the object into it's fields. /// /// ID of the entity that this position is relative to. /// Position in the entity's local space. public void Deconstruct(out EntityUid entId, out Vector2 localPos) { entId = EntityId; localPos = Position; } /// public override string ToString() { return $"EntId={EntityId}, X={Position.X:N2}, Y={Position.Y:N2}"; } } }