xenoarch: node scanner UI overhaul (#4814)
* xenoarch: node scanner UI overhaul * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.Unlock.cs Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com> Signed-off-by: Charlie Morley <cmorley191@gmail.com> * Update Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.Unlock.cs Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com> Signed-off-by: Charlie Morley <cmorley191@gmail.com> --------- Signed-off-by: Charlie Morley <cmorley191@gmail.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
This commit is contained in:
parent
e7afcda1a8
commit
c9ea4aeb77
|
|
@ -1,3 +1,4 @@
|
|||
using Content.Client._DV.Xenoarchaeology.Ui;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Xenoarchaeology.Ui;
|
||||
|
|
@ -7,15 +8,19 @@ namespace Content.Client.Xenoarchaeology.Ui;
|
|||
/// </summary>
|
||||
public sealed class NodeScannerBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
|
||||
{
|
||||
// DeltaV - start of node scanner overhaul
|
||||
[ViewVariables]
|
||||
private NodeScannerDisplay? _scannerDisplay;
|
||||
private DVNodeScannerDisplay? _scannerDisplay;
|
||||
// DeltaV - end of node scanner overhaul
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_scannerDisplay = this.CreateWindow<NodeScannerDisplay>();
|
||||
// DeltaV - start of node scanner overhaul
|
||||
_scannerDisplay = this.CreateWindow<DVNodeScannerDisplay>();
|
||||
// DeltaV - end of node scanner overhaul
|
||||
_scannerDisplay.SetOwner(Owner);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'node-scan-display-title'}"
|
||||
MinSize="305 180"
|
||||
SetSize="305 180"
|
||||
Resizable="False"
|
||||
>
|
||||
<BoxContainer Orientation="Vertical" >
|
||||
<controls:StripeBack>
|
||||
<Label Name="NodeScannerState" HorizontalAlignment="Center" StyleClasses="LabelSubText" Margin="4 0 0 4" />
|
||||
</controls:StripeBack>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Name="NoActiveNodeDataLabel" Text="{Loc 'node-scan-no-data'}" Margin="45 25 0 0" MinHeight="47" />
|
||||
<GridContainer Name="ActiveNodesList" Columns="4" Rows="2" Visible="True" MinHeight="72" />
|
||||
</BoxContainer>
|
||||
<controls:StripeBack>
|
||||
<Label Name="ArtifactStateLabel" HorizontalAlignment="Center" StyleClasses="LabelSubText" Margin="4 0 0 4" />
|
||||
</controls:StripeBack>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
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<string> _triggeredNodeNames = new();
|
||||
|
||||
public NodeScannerDisplay()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_artifact = _ent.System<SharedXenoArtifactSystem>();
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
_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>
|
||||
private void Update(bool isConnected, ArtifactState artifactState, HashSet<string>? 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"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'node-scan-display-title'}"
|
||||
MinSize="350 400"
|
||||
SetSize="350 400"
|
||||
Resizable="False"
|
||||
>
|
||||
<BoxContainer Orientation="Vertical" >
|
||||
<controls:StripeBack>
|
||||
<Label Name="NodeScannerState" HorizontalAlignment="Center" StyleClasses="LabelSubText" Margin="4 0 0 4" />
|
||||
</controls:StripeBack>
|
||||
<BoxContainer Name="ProgressBarContainer" Orientation="Horizontal" Margin="0 5 0 0" HorizontalExpand="True" Visible="False" >
|
||||
<Label Name="ProgressBarLabel" Text="{Loc 'node-scan-timer'}" Margin="20 0 0 0" />
|
||||
<ProgressBar
|
||||
Name="ProgressBar"
|
||||
MinValue="0"
|
||||
MaxValue="1"
|
||||
SetHeight="20"
|
||||
Margin="10 0 20 0"
|
||||
HorizontalExpand="True" >
|
||||
</ProgressBar>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" VerticalExpand="True" >
|
||||
<Label Name="NoActiveNodeDataLabel" Text="{Loc 'node-scan-no-data'}" Margin="15 15 0 0" MinHeight="47" HorizontalExpand="True" HorizontalAlignment="Center" />
|
||||
<BoxContainer Name="ActiveNodesList" Orientation="Vertical" HorizontalExpand="True" Visible="False" MinHeight="72" />
|
||||
</BoxContainer>
|
||||
<PanelContainer Name="SystemWarningPanel"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalExpand="True"
|
||||
Margin="10 0 10 5"
|
||||
Visible="False" >
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" >
|
||||
<RichTextLabel Name="SystemWarningLabel" Margin="5 8 5 0" HorizontalAlignment="Center" />
|
||||
<RichTextLabel Name="SystemWarningSublabel" Margin="5 3 5 8" HorizontalAlignment="Center" />
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -91,6 +91,31 @@ public sealed partial class XenoArtifactComponent : Component
|
|||
/// </summary>
|
||||
[DataField, AutoPausedField]
|
||||
public TimeSpan NextUnlockTime;
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - The end time of the last-completed XenoArtifactUnlockingComponent phase.
|
||||
/// If null, no unlocking phase has occurred yet, and the other LastUnlocking... fields are also invalid.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public TimeSpan? LastUnlockingEndTime = null;
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - The set of related nodes that were triggered during the last completed XenoArtifactUnlockingComponent phase.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public HashSet<int> LastUnlockingTriggeredNodeIndexesRelated = new();
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - The list of nodes that were triggered during the last completed XenoArtifactUnlockingComponent phase.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public List<int> LastUnlockingTriggeredNodeIndexesOrdered = new();
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Whether the last completed unlocking phase resulted in a newly-unlocked node.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool LastUnlockingSuccessful = false;
|
||||
#endregion
|
||||
|
||||
// NOTE: you should not be accessing any of these values directly. Use the methods in SharedXenoArtifactSystem.Graph
|
||||
|
|
|
|||
|
|
@ -15,6 +15,21 @@ public sealed partial class XenoArtifactUnlockingComponent : Component
|
|||
[DataField, AutoNetworkedField]
|
||||
public HashSet<int> TriggeredNodeIndexes = new();
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Ordered list of triggered nodes.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public List<int> TriggeredNodeIndexesOrdered = new();
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - A subset of TriggeredNodeIndexes whose elements are "related" to eachother.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See definition of relatedness in GetRelatedNodes() of SharedXenoArtifactSystem.DV.cs
|
||||
/// </remarks
|
||||
[DataField, AutoNetworkedField]
|
||||
public HashSet<int> TriggeredNodeIndexesRelated = new();
|
||||
|
||||
/// <summary>
|
||||
/// The time at which the unlocking state ends.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -82,11 +82,13 @@ public abstract partial class SharedXenoArtifactSystem
|
|||
// var activated = ActivateNode((ent, artifactComponent), node.Value, null, null, Transform(ent).Coordinates, false);
|
||||
// if (activated)
|
||||
soundEffect = unlockingComponent.UnlockActivationSuccessfulSound;
|
||||
artifactComponent.LastUnlockingSuccessful = true; // DeltaV - node scanner overhaul
|
||||
}
|
||||
else
|
||||
{
|
||||
unlockAttemptResultMsg = "artifact-unlock-state-end-failure";
|
||||
soundEffect = unlockingComponent.UnlockActivationFailedSound;
|
||||
artifactComponent.LastUnlockingSuccessful = false; // DeltaV - node scanner overhaul
|
||||
}
|
||||
|
||||
if (_net.IsServer)
|
||||
|
|
@ -95,6 +97,21 @@ public abstract partial class SharedXenoArtifactSystem
|
|||
_audio.PlayPvs(soundEffect, ent.Owner);
|
||||
}
|
||||
|
||||
// DeltaV - start of node scanner overhaul
|
||||
artifactComponent.LastUnlockingTriggeredNodeIndexesOrdered.Clear();
|
||||
artifactComponent.LastUnlockingTriggeredNodeIndexesRelated.Clear();
|
||||
|
||||
artifactComponent.LastUnlockingTriggeredNodeIndexesOrdered
|
||||
.AddRange(unlockingComponent.TriggeredNodeIndexesOrdered);
|
||||
// one day we'll get a HashSet.AddRange
|
||||
foreach (var i in unlockingComponent.TriggeredNodeIndexesRelated)
|
||||
artifactComponent.LastUnlockingTriggeredNodeIndexesRelated.Add(i);
|
||||
|
||||
artifactComponent.LastUnlockingEndTime = unlockingComponent.EndTime;
|
||||
|
||||
Dirty(ent, artifactComponent);
|
||||
// DeltaV - end of node scanner overhaul
|
||||
|
||||
RemComp(ent, unlockingComponent);
|
||||
RaiseUnlockingFinished(ent, node);
|
||||
artifactComponent.NextUnlockTime = _timing.CurTime + artifactComponent.UnlockStateRefractory;
|
||||
|
|
|
|||
|
|
@ -63,6 +63,15 @@ public abstract partial class SharedXenoArtifactSystem
|
|||
if (!force && _timing.CurTime < ent.Comp.NextUnlockTime)
|
||||
return;
|
||||
|
||||
// DeltaV - start of node scanner overhaul
|
||||
(Entity<XenoArtifactNodeComponent> node, int index)? parsedNode =
|
||||
(node == null)
|
||||
? null
|
||||
: (node.Value, GetIndex(ent, node.Value));
|
||||
|
||||
bool partOfRelatedTriggersSet = true;
|
||||
// DeltaV - end of node scanner overhaul
|
||||
|
||||
if (!_unlockingQuery.TryGetComponent(ent, out var unlockingComp))
|
||||
{
|
||||
unlockingComp = EnsureComp<XenoArtifactUnlockingComponent>(ent);
|
||||
|
|
@ -73,24 +82,37 @@ public abstract partial class SharedXenoArtifactSystem
|
|||
_popup.PopupEntity(Loc.GetString("artifact-unlock-state-begin"), ent);
|
||||
Dirty(ent);
|
||||
}
|
||||
else if (node != null)
|
||||
else if (parsedNode != null)
|
||||
{
|
||||
var index = GetIndex(ent, node.Value);
|
||||
// DeltaV - start of node scanner overhaul
|
||||
|
||||
var predecessorNodeIndices = GetPredecessorNodes((ent, ent), index);
|
||||
var successorNodeIndices = GetSuccessorNodes((ent, ent), index);
|
||||
if (unlockingComp.TriggeredNodeIndexes.Count == 0
|
||||
|| unlockingComp.TriggeredNodeIndexes.All(
|
||||
x => predecessorNodeIndices.Contains(x) || successorNodeIndices.Contains(x)
|
||||
)
|
||||
)
|
||||
var relatedNodeIndices = GetRelatedNodes((ent, ent), parsedNode.Value.index);
|
||||
partOfRelatedTriggersSet = unlockingComp.TriggeredNodeIndexesRelated.All(x => relatedNodeIndices.Contains(x));
|
||||
|
||||
// Checking for trigger "relatedness" is a much more accurate measurement
|
||||
// of "is this locking phase going to fail" than upstream's predecessor/successor check.
|
||||
// Upstream's version of this check had edge-cases where time would not add, even though the
|
||||
// unlocking phase ends up succeeding.
|
||||
// See definition of GetRelatedNodes() for details on the concept of "relatedness".
|
||||
if (
|
||||
unlockingComp.TriggeredNodeIndexes.Count == unlockingComp.TriggeredNodeIndexesRelated.Count
|
||||
&& partOfRelatedTriggersSet
|
||||
)
|
||||
// we add time on each new trigger, if it is not going to fail us
|
||||
unlockingComp.EndTime += ent.Comp.UnlockStateIncrementPerNode;
|
||||
|
||||
// DeltaV - end of node scanner overhaul
|
||||
}
|
||||
|
||||
if (node != null && unlockingComp.TriggeredNodeIndexes.Add(GetIndex(ent, node.Value)))
|
||||
if (parsedNode != null && unlockingComp.TriggeredNodeIndexes.Add(parsedNode.Value.index))
|
||||
{
|
||||
// DeltaV - start of faster unlock effect
|
||||
// DeltaV - start of changes
|
||||
// node scanner overhaul:
|
||||
unlockingComp.TriggeredNodeIndexesOrdered.Add(parsedNode.Value.index);
|
||||
if (partOfRelatedTriggersSet)
|
||||
unlockingComp.TriggeredNodeIndexesRelated.Add(parsedNode.Value.index);
|
||||
|
||||
// faster unlock effect:
|
||||
if (
|
||||
ent.Comp.UnlockCompleteDuration is {} completeDuration
|
||||
&& TryGetNodeFromUnlockState((ent.Owner, unlockingComp, ent.Comp), out var unlockingNode)
|
||||
|
|
@ -98,7 +120,7 @@ public abstract partial class SharedXenoArtifactSystem
|
|||
{
|
||||
unlockingComp.EndTime = _timing.CurTime + completeDuration;
|
||||
}
|
||||
// DeltaV - end of faster unlock effect
|
||||
// DeltaV - end of changes
|
||||
|
||||
Dirty(ent, unlockingComp);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
using System.Linq;
|
||||
using Content.Shared.Xenoarchaeology.Artifact.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Xenoarchaeology.Artifact;
|
||||
|
||||
public abstract partial class SharedXenoArtifactSystem
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Returns the set of nodes that are "related" to the node with the passed node index.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Related nodes are those that might be triggered together with this node, in order to unlock some node.
|
||||
/// Triggering nodes in unrelated parts of the graph (e.g. different segments, or diverging branches)
|
||||
/// causes the unlocking phase to fail. (see TryGetNodeFromUnlockState)
|
||||
/// </remarks>
|
||||
public HashSet<int> GetRelatedNodes(Entity<XenoArtifactComponent?> ent, int nodeIdx)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return new();
|
||||
|
||||
var related = GetRelatedNodes(ent, GetNode((ent, ent.Comp), nodeIdx));
|
||||
var output = new HashSet<int>();
|
||||
foreach (var r in related)
|
||||
{
|
||||
output.Add(GetIndex((ent, ent.Comp), r));
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns set of node entities, that are "related" to passed node entity.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Related nodes are those that might be triggered together with this node, in order to unlock some node.
|
||||
/// Triggering nodes in unrelated parts of the graph (e.g. different segments, or diverging branches)
|
||||
/// causes the unlocking phase to fail. (see TryGetNodeFromUnlockState)
|
||||
/// </remarks>
|
||||
public HashSet<Entity<XenoArtifactNodeComponent>> GetRelatedNodes(Entity<XenoArtifactComponent?> ent, Entity<XenoArtifactNodeComponent> node)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return new();
|
||||
|
||||
var potentialUnlockTargetNodes = GetSuccessorNodes(ent, node);
|
||||
potentialUnlockTargetNodes.Add(node);
|
||||
|
||||
var output = new HashSet<Entity<XenoArtifactNodeComponent>>();
|
||||
foreach (var t in potentialUnlockTargetNodes)
|
||||
{
|
||||
var tPredecessors = GetPredecessorNodes(ent, t);
|
||||
// nodes can only be unlocked if all their predecessors are unlocked
|
||||
if (tPredecessors.All(p => !p.Comp.Locked))
|
||||
{
|
||||
output.Add(t);
|
||||
foreach (var p in tPredecessors)
|
||||
{
|
||||
output.Add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
node-scan-timer = Signal stability:
|
||||
|
||||
node-scan-warning-unlock-complete = [color=white][font size=13][bold]NEW HARMONICS DETECTED[/bold][/font][/color]
|
||||
node-scan-warning-unlock-complete-subtext = [color=white][font size=11][/font]See analysis console[/color]
|
||||
node-scan-warning-partial-complete = [color=white][font size=13][bold]SIGNAL LOST[/bold][/font][/color]
|
||||
node-scan-warning-partial-complete-subtext = [color=white][font size=11]Cause: incomplete trigger framework[/font][/color]
|
||||
node-scan-warning-failure-imminent = [color=white][font size=13][bold]DIVERGING NODES DETECTED[/bold][/font][/color]
|
||||
node-scan-warning-failure-imminent-subtext = [color=white][font size=11]Signal collapse imminent[/font][/color]
|
||||
node-scan-warning-failure-complete = [color=white][font size=13][bold]SIGNAL LOST[/bold][/font][/color]
|
||||
node-scan-warning-failure-complete-subtext = [color=white][font size=11]Cause: dissonant harmonics[/font][/color]
|
||||
Loading…
Reference in New Issue