using System; using System.Numerics; using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Maths; using Robust.Shared.Serialization; using Robust.Shared.Utility; namespace Robust.Shared.Map { /// /// A set of coordinates relative to another entity. /// [PublicAPI] public readonly struct EntityCoordinates : IEquatable, ISpanFormattable { 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; public EntityCoordinates() { EntityId = EntityUid.Invalid; Position = Vector2.Zero; } /// /// 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. /// [Obsolete("Use ToMap() with TransformSystem overload")] public MapCoordinates ToMap(IEntityManager entityManager) { return ToMap(entityManager, entityManager.System()); } /// /// Transforms this set of coordinates from the entity's local space to the map space. /// /// Entity Manager containing the entity Id. /// Shared transform system for doing calculations. public MapCoordinates ToMap(IEntityManager entityManager, SharedTransformSystem transformSystem) { if(!IsValid(entityManager)) return MapCoordinates.Nullspace; var transform = entityManager.GetComponent(EntityId); var worldPos = transformSystem.GetWorldMatrix(transform).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. /// [Obsolete("Use ToMapPos() with TransformSystem overload")] public Vector2 ToMapPos(IEntityManager entityManager) { return ToMap(entityManager).Position; } /// /// Transform this set of coordinates from the entity's local space to the map space. /// /// Entity Manager containing the entity Id. /// Shared transform system for doing calculations. public Vector2 ToMapPos(IEntityManager entityManager, SharedTransformSystem transformSystem) { return ToMap(entityManager, transformSystem).Position; } /// /// Creates EntityCoordinates given an entity and some MapCoordinates. /// /// If is not on the same map as the . [Obsolete("Use FromMap() with TransformSystem overload")] public static EntityCoordinates FromMap(EntityUid entity, MapCoordinates coordinates, IEntityManager? entMan = null) { IoCManager.Resolve(ref entMan); return FromMap(entity, coordinates, entMan.System(), entMan); } /// /// 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, SharedTransformSystem transformSystem, 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 = transformSystem.GetInvWorldMatrix(transform).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 . [Obsolete("Use overload with other parameter order.")] public static EntityCoordinates FromMap(IEntityManager entityManager, EntityUid entityUid, MapCoordinates coordinates) { return FromMap(entityUid, coordinates, entityManager); } /// /// 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. /// [Obsolete("Use overload with TransformSystem")] public Vector2i ToVector2i(IEntityManager entityManager, IMapManager mapManager) { return ToVector2i(entityManager, mapManager, entityManager.System()); } /// /// Converts this set of coordinates to Vector2i. /// public Vector2i ToVector2i( IEntityManager entityManager, IMapManager mapManager, SharedTransformSystem transformSystem) { if(!IsValid(entityManager)) return new Vector2i(); var mapSystem = entityManager.System(); var gridIdOpt = GetGridUid(entityManager); if (gridIdOpt is { } gridId && gridId.IsValid()) { var grid = mapManager.GetGrid(gridId); return mapSystem.GetTileRef(gridId, grid, this).GridIndices; } var vec = ToMapPos(entityManager, transformSystem); return new Vector2i((int)MathF.Floor(vec.X), (int)MathF.Floor(vec.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); return WithEntityId(entity, entMan.System(), entMan); } /// /// 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, SharedTransformSystem transformSystem, IEntityManager? entMan = null) { IoCManager.Resolve(ref entMan); var mapPos = ToMap(entMan, transformSystem); if(!IsValid(entMan) || entMan.GetComponent(entity).MapID != mapPos.MapId) return new EntityCoordinates(entity, Vector2.Zero); var localPos = transformSystem.GetInvWorldMatrix(entity).Transform(mapPos.Position); return new EntityCoordinates(entity, localPos); } /// /// Returns the Grid EntityUid these coordinates are on. /// If none of the ancestors are a grid, returns null instead. /// /// /// Grid EntityUid this entity is on or null public EntityUid? GetGridUid(IEntityManager entityManager) { return !IsValid(entityManager) ? null : entityManager.GetComponent(EntityId).GridUid; } /// /// 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; } /// /// Returns the Map Id these coordinates are on. /// If the relative entity is not valid, returns null instead. /// /// /// Map Id these coordinates are on or null public EntityUid? GetMapUid(IEntityManager entityManager) { return !IsValid(entityManager) ? null : entityManager.GetComponent(EntityId).MapUid; } /// /// 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. [Obsolete("Use overload with TransformSystem")] public bool InRange(IEntityManager entityManager, EntityCoordinates otherCoordinates, float range) { return InRange(entityManager, entityManager.System(), otherCoordinates, range); } /// /// 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, SharedTransformSystem transformSystem, 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, transformSystem); var otherMapCoordinates = otherCoordinates.ToMap(entityManager, transformSystem); 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) { return TryDistance( entityManager, entityManager.System(), otherCoordinates, out distance); } /// /// Tries to calculate the distance between two sets of coordinates. /// /// True if it was possible to calculate the distance public bool TryDistance( IEntityManager entityManager, SharedTransformSystem transformSystem, 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, transformSystem); var otherMapCoordinates = otherCoordinates.ToMap(entityManager, transformSystem); 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}"; } public string ToString(string? format, IFormatProvider? formatProvider) => ToString(); public bool TryFormat( Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) { return FormatHelpers.TryFormatInto( destination, out charsWritten, $"EntId={EntityId}, X={Position.X:N2}, Y={Position.Y:N2}"); } } }