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; [GenerateTypedNameReferences] 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 _triggeredNodeNames = new(); public NodeScannerDisplay() { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); _artifact = _ent.System(); } /// /// Sets entity that represents hand-held xeno artifact node scanner for which window is opened. /// Closes window if is not present on entity. /// public void SetOwner(EntityUid scannerEntityUid) { if (!_ent.TryGetComponent(scannerEntityUid, out var scannerComponent)) { Close(); return; } _updateFromAttachedFrequency = scannerComponent.DisplayDataUpdateInterval; _owner = scannerEntityUid; } /// 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(node)?.Identifier ?? 0).ToString("D3"); _triggeredNodeNames.Add(triggeredNodeName); } artifactState = ArtifactState.Unlocking; } Update(true, artifactState, _triggeredNodeNames); } /// /// Updates labels with scanned artifact data and list of triggered nodes from component. /// private void Update(bool isConnected, ArtifactState artifactState, HashSet? triggeredNodeNames = null) { ArtifactStateLabel.Text = GetStateText(artifactState); NodeScannerState.Text = isConnected ? Loc.GetString("node-scanner-artifact-connected") : Loc.GetString("node-scanner-artifact-non-connected"); ActiveNodesList.Children.Clear(); 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 triggeredNodeNames) { var nodeLabel = new Button { Text = nodeId, Margin = new Thickness(15, 5, 0, 0), MaxHeight = 40, Disabled = true }; ActiveNodesList.Children.Add(nodeLabel); } } else { // clear list of activated nodes (done previously), show 'no data' placeholder NoActiveNodeDataLabel.Visible = true; ActiveNodesList.Visible = false; } } private string GetStateText(ArtifactState state) { return state switch { ArtifactState.None => "\u2800", // placeholder for line to not be squeezed ArtifactState.Ready => Loc.GetString("node-scanner-artifact-state-ready"), ArtifactState.Unlocking => Loc.GetString("node-scanner-artifact-state-unlocking"), ArtifactState.Cooldown => Loc.GetString("node-scanner-artifact-state-cooldown"), _ => throw new ArgumentException("Invalid state"), }; } }