using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Shared.Teleportation.Components; namespace Content.Shared.Teleportation.Systems; /// /// Handles symmetrically linking two entities together, and removing links properly. /// This does not do anything on its own (outside of deleting entities that have 0 links, if that option is true) /// Systems can do whatever they please with the linked entities, such as . /// public sealed partial class LinkedEntitySystem : EntitySystem { [Dependency] private SharedAppearanceSystem _appearance = default!; /// public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnLinkShutdown); } private void OnLinkShutdown(EntityUid uid, LinkedEntityComponent component, ComponentShutdown args) { // Remove any links to this entity when deleted. foreach (var ent in component.LinkedEntities.ToArray()) { if (!Deleted(ent) && LifeStage(ent) < EntityLifeStage.Terminating && TryComp(ent, out var link)) { TryUnlink(uid, ent, component, link); } } } #region Public API /// /// Links two entities together. Does not require the existence of on either /// already. Linking is symmetrical, so order doesn't matter. /// /// The first entity to link /// The second entity to link /// Whether both entities should now delete once their links are removed /// Whether linking was successful (e.g. they weren't already linked) public bool TryLink(EntityUid first, EntityUid second, bool deleteOnEmptyLinks=false) { var firstLink = EnsureComp(first); var secondLink = EnsureComp(second); firstLink.DeleteOnEmptyLinks = deleteOnEmptyLinks; secondLink.DeleteOnEmptyLinks = deleteOnEmptyLinks; _appearance.SetData(first, LinkedEntityVisuals.HasAnyLinks, true); _appearance.SetData(second, LinkedEntityVisuals.HasAnyLinks, true); Dirty(first, firstLink); Dirty(second, secondLink); if (firstLink.LinkedEntities.Add(second) && secondLink.LinkedEntities.Add(first)) { var firstEv = new LinkedEntityChangedEvent(firstLink.LinkedEntities); RaiseLocalEvent(first, ref firstEv); var secondEv = new LinkedEntityChangedEvent(secondLink.LinkedEntities); RaiseLocalEvent(second, ref secondEv); return true; } return false; } /// /// Does a one-way link from source to target. /// /// Whether both entities should now delete once their links are removed public bool OneWayLink(EntityUid source, EntityUid target, bool deleteOnEmptyLinks=false) { var firstLink = EnsureComp(source); firstLink.DeleteOnEmptyLinks = deleteOnEmptyLinks; _appearance.SetData(source, LinkedEntityVisuals.HasAnyLinks, true); Dirty(source, firstLink); if (firstLink.LinkedEntities.Add(target)) { var ev = new LinkedEntityChangedEvent(firstLink.LinkedEntities); RaiseLocalEvent(source, ref ev); return true; } return false; } /// /// Unlinks two entities. Deletes either entity if /// was true and its links are now empty. Symmetrical, so order doesn't matter. /// /// The first entity to unlink /// The second entity to unlink /// Resolve comp /// Resolve comp /// Whether unlinking was successful (e.g. they both were actually linked to one another) public bool TryUnlink(EntityUid first, EntityUid second, LinkedEntityComponent? firstLink = null, LinkedEntityComponent? secondLink = null) { if (!Resolve(first, ref firstLink)) return false; if (!Resolve(second, ref secondLink)) return false; var success = firstLink.LinkedEntities.Remove(second) && secondLink.LinkedEntities.Remove(first); _appearance.SetData(first, LinkedEntityVisuals.HasAnyLinks, firstLink.LinkedEntities.Any()); _appearance.SetData(second, LinkedEntityVisuals.HasAnyLinks, secondLink.LinkedEntities.Any()); Dirty(first, firstLink); Dirty(second, secondLink); var firstEv = new LinkedEntityChangedEvent(firstLink.LinkedEntities); RaiseLocalEvent(first, ref firstEv); var secondEv = new LinkedEntityChangedEvent(secondLink.LinkedEntities); RaiseLocalEvent(second, ref secondEv); if (firstLink.LinkedEntities.Count == 0 && firstLink.DeleteOnEmptyLinks) QueueDel(first); if (secondLink.LinkedEntities.Count == 0 && secondLink.DeleteOnEmptyLinks) QueueDel(second); return success; } /// /// Get the first entity this entity is linked to. /// If multiple are linked only the first one is picked. /// public bool GetLink(EntityUid uid, [NotNullWhen(true)] out EntityUid? dest, LinkedEntityComponent? comp = null) { dest = null; if (!Resolve(uid, ref comp, false)) return false; var first = comp.LinkedEntities.FirstOrDefault(); if (first != default) { dest = first; return true; } return false; } #endregion } [ByRefEvent] public readonly record struct LinkedEntityChangedEvent(HashSet NewLinks);