+++ /dev/null
-using Content.Client.Xenoarchaeology.Ui;
-using Content.Shared.Xenoarchaeology.Equipment;
-using Content.Shared.Xenoarchaeology.Equipment.Components;
-using Robust.Client.GameObjects;
-
-namespace Content.Client.Xenoarchaeology.Equipment;
-
-/// <inheritdoc cref="SharedNodeScannerSystem"/>
-public sealed class NodeScannerSystem : SharedNodeScannerSystem
-{
- [Dependency] private readonly UserInterfaceSystem _ui = default!;
-
- /// <inheritdoc />
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent<NodeScannerComponent, AfterAutoHandleStateEvent>(OnAnalysisConsoleAfterAutoHandleState);
- }
-
- protected override void TryOpenUi(Entity<NodeScannerComponent> device, EntityUid actor)
- {
- _ui.TryOpenUi(device.Owner, NodeScannerUiKey.Key, actor, true);
- }
-
- private void OnAnalysisConsoleAfterAutoHandleState(Entity<NodeScannerComponent> ent, ref AfterAutoHandleStateEvent args)
- {
- if (_ui.TryGetOpenUi<NodeScannerBoundUserInterface>(ent.Owner, NodeScannerUiKey.Key, out var bui))
- bui.Update(ent);
- }
-}
-using Content.Shared.Xenoarchaeology.Equipment.Components;
using Robust.Client.UserInterface;
namespace Content.Client.Xenoarchaeology.Ui;
_scannerDisplay = this.CreateWindow<NodeScannerDisplay>();
_scannerDisplay.SetOwner(Owner);
- _scannerDisplay.OnClose += Close;
- }
-
- /// <summary>
- /// Update UI state based on corresponding component.
- /// </summary>
- public void Update(Entity<NodeScannerComponent> ent)
- {
- _scannerDisplay?.Update(ent);
}
/// <inheritdoc />
using Content.Client.UserInterface.Controls;
+using Content.Shared.NameIdentifier;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
using Content.Shared.Xenoarchaeology.Equipment.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Timing;
namespace Content.Client.Xenoarchaeology.Ui;
public sealed partial class NodeScannerDisplay : FancyWindow
{
[Dependency] private readonly IEntityManager _ent = default!;
+ [Dependency] private readonly IGameTiming _timing= default!;
+
+ private readonly SharedXenoArtifactSystem _artifact;
+ private TimeSpan? _nextUpdate;
+ private EntityUid _owner;
+ private TimeSpan _updateFromAttachedFrequency;
+ private readonly HashSet<string> _triggeredNodeNames = new();
public NodeScannerDisplay()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
+
+ _artifact = _ent.System<SharedXenoArtifactSystem>();
}
/// <summary>
return;
}
- Update((scannerEntityUid, scannerComponent));
+ _updateFromAttachedFrequency = scannerComponent.DisplayDataUpdateInterval;
+ _owner = scannerEntityUid;
+ }
+
+ /// <inheritdoc />
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+
+ if(_nextUpdate != null && _timing.CurTime < _nextUpdate)
+ return;
+
+ _nextUpdate = _timing.CurTime + _updateFromAttachedFrequency;
+
+ if (!_ent.TryGetComponent(_owner, out NodeScannerConnectedComponent? connectedScanner))
+ {
+ Update(false, ArtifactState.None);
+ return;
+ }
+
+ var attachedArtifactEnt = connectedScanner.AttachedTo;
+ if (!_ent.TryGetComponent(attachedArtifactEnt, out XenoArtifactComponent? artifactComponent))
+ return;
+
+ _ent.TryGetComponent(attachedArtifactEnt, out XenoArtifactUnlockingComponent? unlockingComponent);
+
+ _triggeredNodeNames.Clear();
+ ArtifactState artifactState;
+ if (unlockingComponent == null)
+ {
+ var timeToUnlockAvailable = artifactComponent.NextUnlockTime - _timing.CurTime;
+ artifactState = timeToUnlockAvailable > TimeSpan.Zero
+ ? ArtifactState.Cooldown
+ : ArtifactState.Ready;
+ }
+ else
+ {
+ var triggeredIndexes = unlockingComponent.TriggeredNodeIndexes;
+
+ foreach (var triggeredIndex in triggeredIndexes)
+ {
+ var node = _artifact.GetNode((attachedArtifactEnt, artifactComponent), triggeredIndex);
+ var triggeredNodeName = (_ent.GetComponentOrNull<NameIdentifierComponent>(node)?.Identifier ?? 0).ToString("D3");
+ _triggeredNodeNames.Add(triggeredNodeName);
+ }
+
+ artifactState = ArtifactState.Unlocking;
+ }
+
+ Update(true, artifactState, _triggeredNodeNames);
}
/// <summary>
/// Updates labels with scanned artifact data and list of triggered nodes from component.
/// </summary>
- public void Update(Entity<NodeScannerComponent> ent)
+ private void Update(bool isConnected, ArtifactState artifactState, HashSet<string>? triggeredNodeNames = null)
{
- ArtifactStateLabel.Text = GetState(ent);
- var scannedAt = ent.Comp.ScannedAt;
- NodeScannerState.Text = scannedAt > TimeSpan.Zero
- ? Loc.GetString("node-scanner-artifact-scanned-time", ("time", scannedAt.Value.ToString(@"hh\:mm\:ss")))
- : Loc.GetString("node-scanner-artifact-scanned-time-none");
+ ArtifactStateLabel.Text = GetStateText(artifactState);
+ NodeScannerState.Text = isConnected
+ ? Loc.GetString("node-scanner-artifact-connected")
+ : Loc.GetString("node-scanner-artifact-non-connected");
ActiveNodesList.Children.Clear();
- var triggeredNodesSnapshot = ent.Comp.TriggeredNodesSnapshot;
- if (triggeredNodesSnapshot.Count > 0)
+ if (triggeredNodeNames == null)
+ return;
+
+ if (triggeredNodeNames.Count > 0)
{
// show list of triggered nodes instead of 'no data' placeholder
NoActiveNodeDataLabel.Visible = false;
ActiveNodesList.Visible = true;
- foreach (var nodeId in triggeredNodesSnapshot)
+ foreach (var nodeId in triggeredNodeNames)
{
var nodeLabel = new Button
{
}
}
- private string GetState(Entity<NodeScannerComponent> ent)
+ private string GetStateText(ArtifactState state)
{
- return ent.Comp.ArtifactState switch
+ return state switch
{
ArtifactState.None => "\u2800", // placeholder for line to not be squeezed
ArtifactState.Ready => Loc.GetString("node-scanner-artifact-state-ready"),
+++ /dev/null
-using Content.Shared.Xenoarchaeology.Equipment;
-using Content.Shared.Xenoarchaeology.Equipment.Components;
-
-namespace Content.Server.Xenoarchaeology.Equipment.Systems;
-
-/// <inheritdoc cref="SharedNodeScannerSystem"/>
-public sealed class NodeScannerSystem : SharedNodeScannerSystem
-{
- protected override void TryOpenUi(Entity<NodeScannerComponent> device, EntityUid actor)
- {
- // no-op
- }
-}
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Xenoarchaeology.Equipment.Components;
/// <summary>
-/// Component for managing data stored on NodeScanner hand-held device.
-/// Can store snapshot list of currently triggered artifact nodes.
+/// Component for NodeScanner hand-held device settings.
/// </summary>
-[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), Access(typeof(SharedNodeScannerSystem))]
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(NodeScannerSystem))]
public sealed partial class NodeScannerComponent : Component
{
/// <summary>
- /// Identity-names (3-digit codes) of nodes that are triggered on scanned artifact.
+ /// Maximum range for keeping connection to artifact.
/// </summary>
- [DataField, AutoNetworkedField]
- public HashSet<string> TriggeredNodesSnapshot = new();
+ [DataField]
+ public int MaxLinkedRange = 5;
/// <summary>
- /// State of artifact on the moment of scanning.
+ /// Update interval for link info.
/// </summary>
- [DataField, AutoNetworkedField]
- public ArtifactState ArtifactState;
+ [DataField]
+ public TimeSpan DisplayDataUpdateInterval = TimeSpan.FromSeconds(1);
+}
+/// <summary>
+/// Component-marker that node scanner device (<see cref="NodeScannerComponent"/>) is connected to artifact.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), AutoGenerateComponentPause]
+public sealed partial class NodeScannerConnectedComponent : Component
+{
/// <summary>
- /// Time until next unlocking of scanned artifact can be started.
+ /// Xeno artifact entity, to which scanner is attached currently.
+ /// Upon detaching this component should be removed.
/// </summary>
[DataField, AutoNetworkedField]
- public TimeSpan? WaitTime;
+ public EntityUid AttachedTo;
/// <summary>
- /// Moment of gametime, at which last artifact scanning was done.
+ /// Next update tick gametime.
/// </summary>
- [DataField, AutoNetworkedField]
- public TimeSpan? ScannedAt;
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ [AutoPausedField]
+ public TimeSpan NextUpdate = TimeSpan.Zero;
+
+ /// <summary>
+ /// Update interval for link info.
+ /// </summary>
+ [DataField]
+ public TimeSpan LinkUpdateInterval = TimeSpan.FromSeconds(1);
}
/// <summary>
--- /dev/null
+using Content.Shared.Interaction;
+using Content.Shared.Timing;
+using Content.Shared.Verbs;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Equipment.Components;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Xenoarchaeology.Equipment;
+
+/// <summary> Controls behaviour of artifact node scanner device. </summary>
+public sealed class NodeScannerSystem : EntitySystem
+{
+ [Dependency] private readonly UseDelaySystem _useDelay = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+
+ /// <inheritdoc/>
+ public override void Initialize()
+ {
+ SubscribeLocalEvent<NodeScannerComponent, BeforeRangedInteractEvent>(OnBeforeRangedInteract);
+ SubscribeLocalEvent<NodeScannerComponent, GetVerbsEvent<UtilityVerb>>(AddScanVerb);
+ }
+
+ /// <inheritdoc />
+ public override void Update(float frameTime)
+ {
+ var scannerQuery = EntityQueryEnumerator<NodeScannerConnectedComponent, NodeScannerComponent, TransformComponent>();
+ while (scannerQuery.MoveNext(out var uid, out var connected, out var scanner, out var transform))
+ {
+ if (connected.NextUpdate > _timing.CurTime)
+ continue;
+
+ connected.NextUpdate = _timing.CurTime + connected.LinkUpdateInterval;
+
+ var attachedArtifact = connected.AttachedTo;
+ var artifactCoordinates = Transform(attachedArtifact).Coordinates;
+ if (!_transform.InRange(artifactCoordinates, transform.Coordinates, scanner.MaxLinkedRange))
+ {
+ //scanner is too far, disconnect
+ RemCompDeferred(uid, connected);
+ }
+ }
+ }
+
+ private void OnBeforeRangedInteract(EntityUid uid, NodeScannerComponent component, BeforeRangedInteractEvent args)
+ {
+ if (args.Handled || !args.CanReach || args.Target is not { } target)
+ return;
+
+ Entity<XenoArtifactUnlockingComponent?> unlockingEnt = TryComp<XenoArtifactUnlockingComponent>(target, out var unlockingComponent)
+ ? (target, unlockingComponent)
+ : (target, null);
+
+ Attach((uid, component), unlockingEnt, args.User);
+
+ args.Handled = true;
+ }
+
+ private void AddScanVerb(EntityUid uid, NodeScannerComponent component, GetVerbsEvent<UtilityVerb> args)
+ {
+ if (!args.CanAccess)
+ return;
+
+ if (!TryComp<XenoArtifactUnlockingComponent>(args.Target, out var unlockingComponent))
+ return;
+
+ var verb = new UtilityVerb
+ {
+ Act = () => Attach((uid, component), (args.Target, unlockingComponent), args.User),
+ Text = Loc.GetString("node-scan-tooltip")
+ };
+
+ args.Verbs.Add(verb);
+ }
+
+ private void Attach(
+ Entity<NodeScannerComponent> device,
+ Entity<XenoArtifactUnlockingComponent?> unlockingEnt,
+ EntityUid actor
+ )
+ {
+ if (!_timing.IsFirstTimePredicted)
+ return;
+
+ if (TryComp(device, out UseDelayComponent? useDelay)
+ && !_useDelay.TryResetDelay((device, useDelay), true))
+ return;
+
+ var connected = EnsureComp<NodeScannerConnectedComponent>(device);
+ EntityUid artifact = unlockingEnt;
+ if (connected.AttachedTo != artifact)
+ {
+ connected.AttachedTo = artifact;
+ Dirty(device, connected);
+ }
+
+ _ui.TryOpenUi((device, null), NodeScannerUiKey.Key, actor, predicted: true);
+ }
+}
+++ /dev/null
-using Content.Shared.Interaction;
-using Content.Shared.NameIdentifier;
-using Content.Shared.Timing;
-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;
-
-namespace Content.Shared.Xenoarchaeology.Equipment;
-
-/// <summary> Controls behaviour of artifact node scanner device. </summary>
-public abstract class SharedNodeScannerSystem : EntitySystem
-{
- [Dependency] private readonly UseDelaySystem _useDelay = default!;
- [Dependency] private readonly SharedXenoArtifactSystem _artifact = default!;
- [Dependency] private readonly IGameTiming _timing = default!;
-
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<NodeScannerComponent, BeforeRangedInteractEvent>(OnBeforeRangedInteract);
- SubscribeLocalEvent<NodeScannerComponent, GetVerbsEvent<UtilityVerb>>(AddScanVerb);
- }
-
- private void OnBeforeRangedInteract(EntityUid uid, NodeScannerComponent component, BeforeRangedInteractEvent args)
- {
- if (args.Handled || !args.CanReach || args.Target is not { } target)
- return;
-
- Entity<XenoArtifactUnlockingComponent?> unlockingEnt = TryComp<XenoArtifactUnlockingComponent>(target, out var unlockingComponent)
- ? (target, unlockingComponent)
- : (target, null);
-
- TryMakeActiveNodesSnapshot((uid, component), unlockingEnt, args.User);
-
- args.Handled = true;
- }
-
- private void AddScanVerb(EntityUid uid, NodeScannerComponent component, GetVerbsEvent<UtilityVerb> args)
- {
- if (!args.CanAccess)
- return;
-
- if (!TryComp<XenoArtifactUnlockingComponent>(args.Target, out var unlockingComponent))
- return;
-
- var verb = new UtilityVerb
- {
- Act = () =>
- {
- TryMakeActiveNodesSnapshot((uid, component), (args.Target, unlockingComponent), args.User);
- },
- Text = Loc.GetString("node-scan-tooltip")
- };
-
- args.Verbs.Add(verb);
- }
-
- private void TryMakeActiveNodesSnapshot(
- Entity<NodeScannerComponent> device,
- Entity<XenoArtifactUnlockingComponent?> unlockingEnt,
- EntityUid actor
- )
- {
- if (!_timing.IsFirstTimePredicted)
- return;
-
- if (TryComp(device, out UseDelayComponent? useDelay)
- && !_useDelay.TryResetDelay((device, useDelay), true))
- return;
-
- if (!TryComp<XenoArtifactComponent>(unlockingEnt.Owner, out var artifactComponent))
- return;
-
- TryOpenUi(device, actor);
-
- TimeSpan? waitTime = null;
- HashSet<string> triggeredNodeNames;
- ArtifactState artifactState;
- if (unlockingEnt.Comp == null)
- {
- triggeredNodeNames = new HashSet<string>();
- var timeToUnlockAvailable = artifactComponent.NextUnlockTime - _timing.CurTime;
- if (timeToUnlockAvailable > TimeSpan.Zero)
- {
- artifactState = ArtifactState.Cooldown;
- waitTime = timeToUnlockAvailable;
- }
- else
- {
- artifactState = ArtifactState.Ready;
- }
- }
- else
- {
- var triggeredIndexes = unlockingEnt.Comp.TriggeredNodeIndexes;
- triggeredNodeNames = new HashSet<string>(triggeredIndexes.Count);
-
- foreach (var triggeredIndex in triggeredIndexes)
- {
- var node = _artifact.GetNode((unlockingEnt.Owner, artifactComponent), triggeredIndex);
- var triggeredNodeName = (CompOrNull<NameIdentifierComponent>(node)?.Identifier ?? 0).ToString("D3");
- triggeredNodeNames.Add(triggeredNodeName);
- }
-
- artifactState = ArtifactState.Unlocking;
- waitTime = _timing.CurTime - unlockingEnt.Comp.EndTime;
- }
-
- device.Comp.ArtifactState = artifactState;
- device.Comp.WaitTime = waitTime;
- device.Comp.TriggeredNodesSnapshot = triggeredNodeNames;
- device.Comp.ScannedAt = _timing.CurTime;
-
- Dirty(device);
- }
-
- protected abstract void TryOpenUi(Entity<NodeScannerComponent> device, EntityUid actor);
-}
node-scan-no-data = No active node data found
node-scan-display-title = Node scanner
-node-scanner-artifact-scanned-time = Artifact last scanned at {$time}
node-scanner-artifact-state-ready = Artifact is ready for interaction
node-scanner-artifact-state-unlocking = Artifact is resonating with your actions
node-scanner-artifact-state-cooldown = Artifact is resting
-node-scanner-artifact-scanned-time-none = Scan artifact to see current state
+node-scanner-artifact-connected = Scanning artifact
+node-scanner-artifact-non-connected = Artifact not found or out of range