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 class LinkedEntitySystem : EntitySystem { [Dependency] private readonly 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); return firstLink.LinkedEntities.Add(second) && secondLink.LinkedEntities.Add(first); } /// /// 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); return firstLink.LinkedEntities.Add(target); } /// /// 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(firstLink); Dirty(secondLink); 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 }