From fca3302e3fd2fec725199dd488408667d9c1feae Mon Sep 17 00:00:00 2001 From: Fildrance Date: Thu, 24 Sep 2026 18:19:44 +0000 Subject: [PATCH] Fix error spamming in update loop of artifact scanner (#46149) * refactor: make relations etween node scanner and artifact more clear, remove errors on crushing artifact while scanner is attached, add generic `attached entities` collection for artifact to be used for other purposes * Apply xml-doc suggestion from @Tayrtahn Co-authored-by: Tayrtahn * fix: attaching scanner to other artifact now detaches it from original one * refactor: fix whitespaces in SharedXenoArtifactSystem * refactor: fix detaching artifact when scanning same artifact twice --------- Co-authored-by: Tayrtahn --- .../Components/XenoArtifactComponent.cs | 12 +++- .../Artifact/SharedXenoArtifactSystem.cs | 60 +++++++++++++++++++ .../Components/NodeScannerComponent.cs | 1 + .../Equipment/NodeScannerSystem.cs | 49 +++++++++++---- 4 files changed, 109 insertions(+), 13 deletions(-) diff --git a/Content.Shared/Xenoarchaeology/Artifact/Components/XenoArtifactComponent.cs b/Content.Shared/Xenoarchaeology/Artifact/Components/XenoArtifactComponent.cs index 9c87b80a2a..959b3987a9 100644 --- a/Content.Shared/Xenoarchaeology/Artifact/Components/XenoArtifactComponent.cs +++ b/Content.Shared/Xenoarchaeology/Artifact/Components/XenoArtifactComponent.cs @@ -14,7 +14,8 @@ namespace Content.Shared.Xenoarchaeology.Artifact.Components; /// This is used for handling interactions with artifacts as well as /// storing data about artifact node graphs. /// -[RegisterComponent, NetworkedComponent, Access(typeof(SharedXenoArtifactSystem)), AutoGenerateComponentState, AutoGenerateComponentPause] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] +[Access(typeof(SharedXenoArtifactSystem))] public sealed partial class XenoArtifactComponent : Component { public static string NodeContainerId = "node-container"; @@ -192,6 +193,15 @@ public sealed partial class XenoArtifactComponent : Component /// [DataField] public LocId? UnlockFailureMsg = "artifact-unlock-state-end-failure"; + + /// + /// List of currently attached entities - node scanners, etc. + /// + /// + /// TODO: replace with relationship system. + /// + [DataField, AutoNetworkedField] + public HashSet AttachedEntities = new(); } /// diff --git a/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.cs b/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.cs index 2cd02a1d13..deca5e4dee 100644 --- a/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.cs +++ b/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.cs @@ -40,6 +40,17 @@ public abstract partial class SharedXenoArtifactSystem : EntitySystem UpdateUnlock(frameTime); } + /// Clears all attached scanners. + [SubscribeLocalEvent] + private void OnShutdown(Entity ent, ref ComponentRemove shutdown) + { + var removedEvent = new XenoArtifactDestroyedEvent(); + foreach (var entity in ent.Comp.AttachedEntities) + { + RaiseLocalEvent(entity, ref removedEvent); + } + } + /// As all artifacts have to contain nodes - we ensure that they are containers. private void OnStartup(Entity ent, ref ComponentStartup args) { @@ -52,6 +63,51 @@ public abstract partial class SharedXenoArtifactSystem : EntitySystem args.Handled = TryActivateXenoArtifact(ent, ent, null, Transform(ent).Coordinates, false); } + /// + /// Tries to remove an entity from the list of attached entities. + /// This helps with tracking relationship. + /// + /// Artifact entity. + /// Entity to detach. + /// + /// Returns false if is not artifact, + /// or if there is no such entity in list of attached ones. + /// Otherwise, returns true. + /// + public bool TryDetachEntity(Entity ent, EntityUid entityToDetach) + { + if (!Resolve(ent, ref ent.Comp)) + return false; + + var result = ent.Comp.AttachedEntities.Remove(entityToDetach); + if (result) + Dirty(ent); + + return result; + } + + /// + /// Tries to add an entity to list of attached entities. + /// This helps with tracking relationship. + /// + /// Artifact entity. + /// Entity to attach. + /// + /// Returns False if is not artifact, + /// or entity is already added, otherwise true. + /// + public bool TryAttachEntity(Entity ent, EntityUid entityToAttach) + { + if (!Resolve(ent, ref ent.Comp)) + return false; + + var result = ent.Comp.AttachedEntities.Add(entityToAttach); + if (result) + Dirty(ent); + + return result; + } + public void SetSuppressed(Entity ent, bool val) { if (ent.Comp.Suppressed == val) @@ -61,3 +117,7 @@ public abstract partial class SharedXenoArtifactSystem : EntitySystem Dirty(ent); } } + +/// Event of artifact destruction. +[ByRefEvent] +public record struct XenoArtifactDestroyedEvent; diff --git a/Content.Shared/Xenoarchaeology/Equipment/Components/NodeScannerComponent.cs b/Content.Shared/Xenoarchaeology/Equipment/Components/NodeScannerComponent.cs index 72223a366b..1b29ddeab0 100644 --- a/Content.Shared/Xenoarchaeology/Equipment/Components/NodeScannerComponent.cs +++ b/Content.Shared/Xenoarchaeology/Equipment/Components/NodeScannerComponent.cs @@ -28,6 +28,7 @@ public sealed partial class NodeScannerComponent : Component /// Component-marker that node scanner device () is connected to artifact. /// [RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), AutoGenerateComponentPause] +[Access(typeof(NodeScannerSystem))] public sealed partial class NodeScannerConnectedComponent : Component { /// diff --git a/Content.Shared/Xenoarchaeology/Equipment/NodeScannerSystem.cs b/Content.Shared/Xenoarchaeology/Equipment/NodeScannerSystem.cs index 60c027736c..78301b0376 100644 --- a/Content.Shared/Xenoarchaeology/Equipment/NodeScannerSystem.cs +++ b/Content.Shared/Xenoarchaeology/Equipment/NodeScannerSystem.cs @@ -1,6 +1,7 @@ using Content.Shared.Interaction; using Content.Shared.Timing.Systems; using Content.Shared.Verbs; +using Content.Shared.Xenoarchaeology.Artifact; using Content.Shared.Xenoarchaeology.Artifact.Components; using Content.Shared.Xenoarchaeology.Equipment.Components; using Robust.Shared.Timing; @@ -14,13 +15,7 @@ public sealed partial class NodeScannerSystem : EntitySystem [Dependency] private IGameTiming _timing = default!; [Dependency] private SharedUserInterfaceSystem _ui = default!; [Dependency] private SharedTransformSystem _transform = default!; - - /// - public override void Initialize() - { - SubscribeLocalEvent(OnBeforeRangedInteract); - SubscribeLocalEvent>(AddScanVerb); - } + [Dependency] private SharedXenoArtifactSystem _artifact = default!; /// public override void Update(float frameTime) @@ -43,7 +38,27 @@ public sealed partial class NodeScannerSystem : EntitySystem } } - private void OnBeforeRangedInteract(EntityUid uid, NodeScannerComponent component, BeforeRangedInteractEvent args) + /// Disconnect if artifact is destroyed + [SubscribeLocalEvent] + private void OnArtifactRemoved(Entity ent, ref XenoArtifactDestroyedEvent args) + { + RemCompDeferred(ent, ent.Comp); + } + + /// Detach if scanner is disconnected, or destroyed. + [SubscribeLocalEvent] + private void OnScannerRemoved(Entity ent, ref ComponentRemove args) + { + var artifact = ent.Comp.AttachedTo; + if (!TerminatingOrDeleted(artifact)) + { + _artifact.TryDetachEntity((artifact, null), ent); + } + } + + /// Attach scanner if target is fitting. + [SubscribeLocalEvent] + private void OnBeforeRangedInteract(Entity ent, ref BeforeRangedInteractEvent args) { if (args.Handled || !args.CanReach || args.Target is not { } target || !HasComp(target)) return; @@ -52,22 +67,26 @@ public sealed partial class NodeScannerSystem : EntitySystem ? (target, unlockingComponent) : (target, null); - Attach((uid, component), unlockingEnt, args.User); + Attach(ent, unlockingEnt, args.User); args.Handled = true; } - private void AddScanVerb(EntityUid uid, NodeScannerComponent component, GetVerbsEvent args) + /// Add `scan` verb if target is fitting. + [SubscribeLocalEvent] + private void AddScanVerb(Entity ent, ref GetVerbsEvent args) { if (!args.CanAccess) return; - if (!TryComp(args.Target, out var unlockingComponent)) + var target = args.Target; + if (!TryComp(target, out var unlockingComponent)) return; + var user = args.User; var verb = new UtilityVerb { - Act = () => Attach((uid, component), (args.Target, unlockingComponent), args.User), + Act = () => Attach(ent, (target, unlockingComponent), user), Text = Loc.GetString("node-scan-tooltip") }; @@ -84,11 +103,17 @@ public sealed partial class NodeScannerSystem : EntitySystem return; var connected = EnsureComp(device); + EntityUid artifact = unlockingEnt; if (connected.AttachedTo != artifact) { + // Remove connection from previous scanner. + if (connected.AttachedTo.Valid) + _artifact.TryDetachEntity(connected.AttachedTo, device); + connected.AttachedTo = artifact; Dirty(device, connected); + _artifact.TryAttachEntity(artifact, device); } _ui.TryOpenUi((device, null), NodeScannerUiKey.Key, actor, predicted: true);