using System; using JetBrains.Annotations; using Robust.Shared.IoC; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Timing; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; namespace Robust.Shared.GameObjects; /// /// Network identifier for entities; used by client and server to refer to the same entity where their local may differ. /// [Serializable, NetSerializable, CopyByRef] public readonly struct NetEntity : IEquatable, IComparable, ISpanFormattable { public readonly int Id; public const int ClientEntity = 2 << 29; /* * Differed to EntityUid to be more consistent with Arch. */ /// /// An Invalid entity UID you can compare against. /// public static readonly NetEntity Invalid = new(0); /// /// The first entity UID the entityManager should use when the manager is initialized. /// public static readonly NetEntity First = new(1); /// /// Creates an instance of this structure, with the given network ID. /// public NetEntity(int id) { Id = id; } public bool Valid => IsValid(); /// /// Creates a network entity UID by parsing a string number. /// public static NetEntity Parse(ReadOnlySpan uid) { if (uid.Length == 0) throw new FormatException($"An empty string is not a valid NetEntity"); // 'c' prefix for client-side entities if (uid[0] != 'c') return new NetEntity(int.Parse(uid)); if (uid.Length == 1) throw new FormatException($"'c' is not a valid NetEntity"); var id = int.Parse(uid[1..]); return new NetEntity(id | ClientEntity); } public static bool TryParse(ReadOnlySpan uid, out NetEntity entity) { entity = Invalid; int id; if (uid.Length == 0) return false; // 'c' prefix for client-side entities if (uid[0] != 'c') { if (!int.TryParse(uid, out id)) return false; entity = new NetEntity(id); return true; } if (uid.Length == 1) return false; if (!int.TryParse(uid[1..], out id)) return false; entity = new NetEntity(id | ClientEntity); return true; } /// /// Checks if the ID value is valid. Does not check if it identifies /// a valid Entity. /// [Pure] public bool IsValid() { return Id > 0; } /// public bool Equals(NetEntity other) { return Id == other.Id; } /// public override bool Equals(object? obj) { if (ReferenceEquals(null, obj)) return false; return obj is NetEntity id && Equals(id); } /// public override int GetHashCode() { return Id; } /// /// Check for equality by value between two objects. /// public static bool operator ==(NetEntity a, NetEntity b) { return a.Id == b.Id; } /// /// Check for inequality by value between two objects. /// public static bool operator !=(NetEntity a, NetEntity b) { return !(a == b); } /// /// Explicit conversion of EntityId to int. This should only be used in special /// cases like serialization. Do NOT use this in content. /// public static explicit operator int(NetEntity self) { return self.Id; } /// public override string ToString() { if (IsClientSide()) return $"c{Id & ~ClientEntity}"; return Id.ToString(); } public string ToString(string? format, IFormatProvider? formatProvider) { return ToString(); } public bool TryFormat( Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) { if (IsClientSide()) { return FormatHelpers.TryFormatInto( destination, out charsWritten, $"c{Id & ~ClientEntity}"); } return Id.TryFormat(destination, out charsWritten); } /// public int CompareTo(NetEntity other) { return Id.CompareTo(other.Id); } public bool IsClientSide() => (Id & ClientEntity) == ClientEntity; #region ViewVariables [ViewVariables] private string Representation { get { var entManager = IoCManager.Resolve(); return entManager.ToPrettyString(entManager.GetEntity(this)); } } [ViewVariables(VVAccess.ReadWrite)] private string Name { get => MetaData?.EntityName ?? string.Empty; set { if (MetaData is { } metaData) { var entManager = IoCManager.Resolve(); entManager.System().SetEntityName(entManager.GetEntity(this), value, metaData); } } } [ViewVariables(VVAccess.ReadWrite)] private string Description { get => MetaData?.EntityDescription ?? string.Empty; set { if (MetaData is { } metaData) { var entManager = IoCManager.Resolve(); entManager.System().SetEntityDescription(entManager.GetEntity(this), value, metaData); } } } [ViewVariables] private EntityPrototype? Prototype => MetaData?.EntityPrototype; [ViewVariables] private GameTick LastModifiedTick => MetaData?.EntityLastModifiedTick ?? GameTick.Zero; [ViewVariables] private bool Paused => MetaData?.EntityPaused ?? false; [ViewVariables] private EntityLifeStage LifeStage => MetaData?.EntityLifeStage ?? EntityLifeStage.Deleted; [ViewVariables] private MetaDataComponent? MetaData { get { var entManager = IoCManager.Resolve(); return entManager.GetComponentOrNull(entManager.GetEntity(this)); } } [ViewVariables] private TransformComponent? Transform { get { var entManager = IoCManager.Resolve(); return entManager.GetComponentOrNull(entManager.GetEntity(this)); } } [ViewVariables] private EntityUid _uid { get { return IoCManager.Resolve().GetEntity(this); } } [ViewVariables] private NetEntity _netId => this; #endregion }