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 <tayrtahn@gmail.com>

* 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 <tayrtahn@gmail.com>
This commit is contained in:
Fildrance 2026-09-24 18:19:44 +00:00 • committed by GitHub
parent 458a3af6c1
commit fca3302e3f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 109 additions and 13 deletions

View file

@ -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.
/// </summary>
[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
/// </summary>
[DataField]
public LocId? UnlockFailureMsg = "artifact-unlock-state-end-failure";
/// <summary>
/// List of currently attached entities - node scanners, etc.
/// </summary>
/// <remarks>
/// TODO: replace with relationship system.
/// </remarks>
[DataField, AutoNetworkedField]
public HashSet<EntityUid> AttachedEntities = new();
}
/// <summary>

View file

@ -40,6 +40,17 @@ public abstract partial class SharedXenoArtifactSystem : EntitySystem
UpdateUnlock(frameTime);
}
/// <summary> Clears all attached scanners. </summary>
[SubscribeLocalEvent]
private void OnShutdown(Entity<XenoArtifactComponent> ent, ref ComponentRemove shutdown)
{
var removedEvent = new XenoArtifactDestroyedEvent();
foreach (var entity in ent.Comp.AttachedEntities)
{
RaiseLocalEvent(entity, ref removedEvent);
}
}
/// <summary> As all artifacts have to contain nodes - we ensure that they are containers. </summary>
private void OnStartup(Entity<XenoArtifactComponent> ent, ref ComponentStartup args)
{
@ -52,6 +63,51 @@ public abstract partial class SharedXenoArtifactSystem : EntitySystem
args.Handled = TryActivateXenoArtifact(ent, ent, null, Transform(ent).Coordinates, false);
}
/// <summary>
/// Tries to remove an entity from the list of attached entities.
/// This helps with tracking relationship.
/// </summary>
/// <param name="ent">Artifact entity.</param>
/// <param name="entityToDetach">Entity to detach.</param>
/// <returns>
/// Returns false if <paramref name="ent"/> is not artifact,
/// or if there is no such entity in list of attached ones.
/// Otherwise, returns true.
/// </returns>
public bool TryDetachEntity(Entity<XenoArtifactComponent?> ent, EntityUid entityToDetach)
{
if (!Resolve(ent, ref ent.Comp))
return false;
var result = ent.Comp.AttachedEntities.Remove(entityToDetach);
if (result)
Dirty(ent);
return result;
}
/// <summary>
/// Tries to add an entity to list of attached entities.
/// This helps with tracking relationship.
/// </summary>
/// <param name="ent">Artifact entity.</param>
/// <param name="entityToAttach">Entity to attach.</param>
/// <returns>
/// Returns False if <paramref name="ent"/> is not artifact,
/// or entity is already added, otherwise true.
/// </returns>
public bool TryAttachEntity(Entity<XenoArtifactComponent?> 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<XenoArtifactComponent> ent, bool val)
{
if (ent.Comp.Suppressed == val)
@ -61,3 +117,7 @@ public abstract partial class SharedXenoArtifactSystem : EntitySystem
Dirty(ent);
}
}
/// <summary> Event of artifact destruction. </summary>
[ByRefEvent]
public record struct XenoArtifactDestroyedEvent;

View file

@ -28,6 +28,7 @@ public sealed partial class NodeScannerComponent : Component
/// Component-marker that node scanner device (<see cref="NodeScannerComponent"/>) is connected to artifact.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), AutoGenerateComponentPause]
[Access(typeof(NodeScannerSystem))]
public sealed partial class NodeScannerConnectedComponent : Component
{
/// <summary>

View file

@ -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!;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<NodeScannerComponent, BeforeRangedInteractEvent>(OnBeforeRangedInteract);
SubscribeLocalEvent<NodeScannerComponent, GetVerbsEvent<UtilityVerb>>(AddScanVerb);
}
[Dependency] private SharedXenoArtifactSystem _artifact = default!;
/// <inheritdoc />
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)
/// <summary> Disconnect if artifact is destroyed </summary>
[SubscribeLocalEvent]
private void OnArtifactRemoved(Entity<NodeScannerConnectedComponent> ent, ref XenoArtifactDestroyedEvent args)
{
RemCompDeferred(ent, ent.Comp);
}
/// <summary> Detach if scanner is disconnected, or destroyed. </summary>
[SubscribeLocalEvent]
private void OnScannerRemoved(Entity<NodeScannerConnectedComponent> ent, ref ComponentRemove args)
{
var artifact = ent.Comp.AttachedTo;
if (!TerminatingOrDeleted(artifact))
{
_artifact.TryDetachEntity((artifact, null), ent);
}
}
/// <summary> Attach scanner if target is fitting. </summary>
[SubscribeLocalEvent]
private void OnBeforeRangedInteract(Entity<NodeScannerComponent> ent, ref BeforeRangedInteractEvent args)
{
if (args.Handled || !args.CanReach || args.Target is not { } target || !HasComp<XenoArtifactComponent>(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<UtilityVerb> args)
/// <summary> Add `scan` verb if target is fitting. </summary>
[SubscribeLocalEvent]
private void AddScanVerb(Entity<NodeScannerComponent> ent, ref GetVerbsEvent<UtilityVerb> args)
{
if (!args.CanAccess)
return;
if (!TryComp<XenoArtifactUnlockingComponent>(args.Target, out var unlockingComponent))
var target = args.Target;
if (!TryComp<XenoArtifactUnlockingComponent>(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<NodeScannerConnectedComponent>(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);