Delta-v/Content.Client/_DV/Xenoarchaeology/Ui/DVNodeScannerDisplay.xaml.cs

251 lines
9.9 KiB
C#

using System.Linq;
using System.Numerics;
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.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Client._DV.Xenoarchaeology.Ui;
/// <summary>
/// DeltaV replacement for NodeScannerDisplay, the UI of the artifact node scanner.
/// Displays extra information about the artifact unlocking phase.
/// </summary>
[GenerateTypedNameReferences]
public sealed partial class DVNodeScannerDisplay : FancyWindow
{
[Dependency] private readonly IEntityManager _ent = default!;
[Dependency] private readonly IGameTiming _timing= default!;
private readonly SharedXenoArtifactSystem _artifact;
private readonly SpriteSystem _spriteSystem;
private Texture? _blipTexture;
private EntityUid _owner;
private float animatedTimeRemaining = 0;
private float animatedTimeRemainingVelocity = 0;
public DVNodeScannerDisplay()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_artifact = _ent.System<SharedXenoArtifactSystem>();
_spriteSystem = _ent.System<SpriteSystem>();
}
/// <summary>
/// Sets entity that represents hand-held xeno artifact node scanner for which window is opened.
/// Closes window if <see cref="NodeScannerComponent"/> is not present on entity.
/// </summary>
public void SetOwner(EntityUid scannerEntityUid)
{
if (!_ent.TryGetComponent<NodeScannerComponent>(scannerEntityUid, out var scannerComponent))
{
Close();
return;
}
_owner = scannerEntityUid;
_blipTexture = _spriteSystem.Frame0(new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")));
}
/// <inheritdoc />
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
// reset
NoActiveNodeDataLabel.Visible = true;
NodeScannerState.Text = Loc.GetString("node-scanner-artifact-non-connected");
ProgressBarContainer.Visible = false;
ActiveNodesList.Visible = false;
SystemWarningPanel.Visible = false;
if (!_ent.TryGetComponent(_owner, out NodeScannerConnectedComponent? connectedScanner))
return;
var attachedArtifactEnt = connectedScanner.AttachedTo;
if (!_ent.TryGetComponent(attachedArtifactEnt, out XenoArtifactComponent? artifactComponent))
return;
_ent.TryGetComponent(attachedArtifactEnt, out XenoArtifactUnlockingComponent? unlockingComponent);
// header banner
NodeScannerState.Text =
unlockingComponent != null
? Loc.GetString("node-scanner-artifact-state-unlocking")
: _timing.CurTime < artifactComponent.NextUnlockTime
? Loc.GetString("node-scanner-artifact-state-cooldown")
: Loc.GetString("node-scanner-artifact-state-ready");
// time remaining progress bar
var unlockTimeRemaining =
unlockingComponent != null
? MathF.Max(0f, (float)(unlockingComponent.EndTime - _timing.CurTime).TotalSeconds)
: 0f;
// damped-oscillator-style system for animating sudden jumps in time remaining
animatedTimeRemainingVelocity += 0.001f * (unlockTimeRemaining - animatedTimeRemaining) - 0.1f * animatedTimeRemainingVelocity;
animatedTimeRemaining = MathF.Max(0f, animatedTimeRemaining + animatedTimeRemainingVelocity);
// sigmoid-style function to make sure progress bar never goes over 100%
ProgressBar.Value = 2f / (1f + MathF.Exp(-0.08f * animatedTimeRemaining)) - 1f;
// node data and warning panel
(List<int> triggeredIndexesOrdered, HashSet<int> triggeredIndexesRelated)? unlockingDataToShow = null;
if (unlockingComponent != null)
{
unlockingDataToShow = (
unlockingComponent.TriggeredNodeIndexesOrdered,
unlockingComponent.TriggeredNodeIndexesRelated
);
}
else if (
artifactComponent.LastUnlockingEndTime != null
&& _timing.CurTime - artifactComponent.LastUnlockingEndTime < TimeSpan.FromSeconds(60)
)
{
unlockingDataToShow = (
artifactComponent.LastUnlockingTriggeredNodeIndexesOrdered,
artifactComponent.LastUnlockingTriggeredNodeIndexesRelated
);
}
if (
unlockingDataToShow is (List<int> triggeredIndexesOrdered, HashSet<int> triggeredIndexesRelated)
&& triggeredIndexesOrdered.Count > 0
)
{
// show triggered node data instead of 'no data' placeholder
NoActiveNodeDataLabel.Visible = false;
ProgressBarContainer.Visible = true;
ActiveNodesList.Visible = true;
// node list
ActiveNodesList.Children.Clear();
foreach (var triggeredIndex in triggeredIndexesOrdered)
{
var node = _artifact.GetNode((attachedArtifactEnt, artifactComponent), triggeredIndex);
var nodeControl = new Button
{
Margin = new Thickness(15, 5, 15, 0),
MaxHeight = 40,
Disabled = true,
HorizontalExpand = true
};
ActiveNodesList.AddChild(nodeControl);
var mainContainer = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
HorizontalExpand = true,
};
nodeControl.AddChild(mainContainer);
// mimics suitCoordsIndicator in CrewMonitoringWindow
var relatednessIndicator = new TextureRect()
{
Texture = _blipTexture,
TextureScale = new Vector2(0.25f, 0.25f),
Modulate = triggeredIndexesRelated.Contains(triggeredIndex) ? Color.LimeGreen : Color.DarkRed,
HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Center,
};
mainContainer.AddChild(relatednessIndicator);
var triggerTip = Loc.GetString(node.Comp.TriggerTip!);
var triggeredNodeName = (_ent.GetComponentOrNull<NameIdentifierComponent>(node)?.Identifier ?? 0).ToString("D3");
var descriptionLabel = new Label()
{
Text = $"{triggerTip} ({triggeredNodeName})",
HorizontalExpand = true,
ClipText = true,
Margin = new Thickness(8, 0, 0, 0),
};
mainContainer.AddChild(descriptionLabel);
}
// warning panel (mimics that of PowerMonitoringWindow)
(Color background, Color border, string message, string subtext)? warningDataToShow = null;
if (unlockingComponent == null)
{
if (artifactComponent.LastUnlockingSuccessful)
{
warningDataToShow = (
background: Color.Green,
border: Color.DarkGreen,
message: Loc.GetString("node-scan-warning-unlock-complete"),
subtext: Loc.GetString("node-scan-warning-unlock-complete-subtext")
);
}
else if (triggeredIndexesOrdered.Count == triggeredIndexesRelated.Count)
{
warningDataToShow = (
background: Color.Orange,
border: Color.DarkOrange,
message: Loc.GetString("node-scan-warning-partial-complete"),
subtext: Loc.GetString("node-scan-warning-partial-complete-subtext")
);
}
else
{
warningDataToShow = (
background: Color.Red,
border: Color.DarkRed,
message: Loc.GetString("node-scan-warning-failure-complete"),
subtext: Loc.GetString("node-scan-warning-failure-complete-subtext")
);
}
}
else if (triggeredIndexesOrdered.Count != triggeredIndexesRelated.Count)
{
warningDataToShow = (
background: Color.Red,
border: Color.DarkRed,
message: Loc.GetString("node-scan-warning-failure-imminent"),
subtext: Loc.GetString("node-scan-warning-failure-imminent-subtext")
);
}
if (warningDataToShow is (Color background, Color border, string message, string subtext))
{
SystemWarningPanel.Visible = true;
SystemWarningPanel.PanelOverride = new StyleBoxFlat
{
BackgroundColor = background,
BorderColor = border,
BorderThickness = new Thickness(2),
};
// warning panel pulses during unlocking phase
var lit = unlockingComponent != null && _timing.RealTime.TotalSeconds % 1f > 0.5f;
SystemWarningPanel.Modulate = lit ? Color.White : new Color(178, 178, 178);
SystemWarningLabel.SetMessage(FormattedMessage.FromMarkupOrThrow(message));
SystemWarningSublabel.SetMessage(FormattedMessage.FromMarkupOrThrow(subtext));
}
}
}
}