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}";
}
}
}