--- /dev/null
+using Content.Shared.Xenoarchaeology.Artifact;
+
+namespace Content.Client.Xenoarchaeology.Artifact;
+
+/// <inheritdoc/>
+public sealed class XenoArtifactSystem : SharedXenoArtifactSystem;
--- /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 />
+public sealed class ArtifactAnalyzerSystem : SharedArtifactAnalyzerSystem
+{
+ [Dependency] private readonly UserInterfaceSystem _ui = default!;
+
+ /// <inheritdoc/>
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<AnalysisConsoleComponent, AfterAutoHandleStateEvent>(OnAnalysisConsoleAfterAutoHandleState);
+ SubscribeLocalEvent<ArtifactAnalyzerComponent, AfterAutoHandleStateEvent>(OnAnalyzerAfterAutoHandleState);
+ }
+
+ private void OnAnalysisConsoleAfterAutoHandleState(Entity<AnalysisConsoleComponent> ent, ref AfterAutoHandleStateEvent args)
+ {
+ UpdateBuiIfCanGetAnalysisConsoleUi(ent);
+ }
+
+ private void OnAnalyzerAfterAutoHandleState(Entity<ArtifactAnalyzerComponent> ent, ref AfterAutoHandleStateEvent args)
+ {
+ if (!TryGetAnalysisConsole(ent, out var analysisConsole))
+ return;
+
+ UpdateBuiIfCanGetAnalysisConsoleUi(analysisConsole.Value);
+ }
+
+ private void UpdateBuiIfCanGetAnalysisConsoleUi(Entity<AnalysisConsoleComponent> analysisConsole)
+ {
+ if (_ui.TryGetOpenUi<AnalysisConsoleBoundUserInterface>(analysisConsole.Owner, ArtifactAnalyzerUiKey.Key, out var bui))
+ bui.Update(analysisConsole);
+ }
+}
public sealed class ArtifactCrusherSystem : SharedArtifactCrusherSystem
{
-}
+}
\ No newline at end of file
--- /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;
-using JetBrains.Annotations;
-using Robust.Client.GameObjects;
+using Content.Shared.Research.Components;
+using Content.Shared.Xenoarchaeology.Equipment.Components;
using Robust.Client.UserInterface;
+using JetBrains.Annotations;
namespace Content.Client.Xenoarchaeology.Ui;
+/// <summary>
+/// BUI for artifact analysis console, proxies server-provided UI updates
+/// (related to device, connected artifact analyzer, and artifact lying on it).
+/// </summary>
[UsedImplicitly]
-public sealed class AnalysisConsoleBoundUserInterface : BoundUserInterface
+public sealed class AnalysisConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
{
[ViewVariables]
private AnalysisConsoleMenu? _consoleMenu;
- public AnalysisConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
- {
- }
-
+ /// <inheritdoc />
protected override void Open()
{
base.Open();
_consoleMenu = this.CreateWindow<AnalysisConsoleMenu>();
+ _consoleMenu.SetOwner(owner);
+
+ _consoleMenu.OnClose += Close;
+ _consoleMenu.OpenCentered();
_consoleMenu.OnServerSelectionButtonPressed += () =>
{
- SendMessage(new AnalysisConsoleServerSelectionMessage());
- };
- _consoleMenu.OnScanButtonPressed += () =>
- {
- SendMessage(new AnalysisConsoleScanButtonPressedMessage());
- };
- _consoleMenu.OnPrintButtonPressed += () =>
- {
- SendMessage(new AnalysisConsolePrintButtonPressedMessage());
+ SendMessage(new ConsoleServerSelectionMessage());
};
_consoleMenu.OnExtractButtonPressed += () =>
{
SendMessage(new AnalysisConsoleExtractButtonPressedMessage());
};
- _consoleMenu.OnUpBiasButtonPressed += () =>
- {
- SendMessage(new AnalysisConsoleBiasButtonPressedMessage(false));
- };
- _consoleMenu.OnDownBiasButtonPressed += () =>
- {
- SendMessage(new AnalysisConsoleBiasButtonPressedMessage(true));
- };
}
- protected override void UpdateState(BoundUserInterfaceState state)
+ /// <summary>
+ /// Update UI state based on corresponding component.
+ /// </summary>
+ public void Update(Entity<AnalysisConsoleComponent> ent)
{
- base.UpdateState(state);
-
- switch (state)
- {
- case AnalysisConsoleUpdateState msg:
- _consoleMenu?.SetButtonsDisabled(msg);
- _consoleMenu?.UpdateInformationDisplay(msg);
- _consoleMenu?.UpdateProgressBar(msg);
- break;
- }
+ _consoleMenu?.Update(ent);
}
+ /// <inheritdoc />
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
-<controls:FancyWindow xmlns="https://spacestation14.io"
+<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
+ xmlns:ui="clr-namespace:Content.Client.Xenoarchaeology.Ui"
Title="{Loc 'analysis-console-menu-title'}"
- MinSize="620 280"
- SetSize="620 280">
+ MinSize="700 350"
+ SetSize="980 550">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
- <BoxContainer Margin="10 10 10 10" MinWidth="150" Orientation="Vertical"
- VerticalExpand="True" SizeFlagsStretchRatio="1">
- <BoxContainer Orientation="Vertical" VerticalExpand="True">
- <Button Name="ServerSelectionButton"
- Text="{Loc 'analysis-console-server-list-button'}"></Button>
- <BoxContainer MinHeight="5"></BoxContainer>
- <Button Name="ScanButton"
- Text="{Loc 'analysis-console-scan-button'}"
- ToolTip="{Loc 'analysis-console-scan-tooltip-info'}">
- </Button>
- <BoxContainer MinHeight="5"></BoxContainer>
- <Button Name="PrintButton"
- Text="{Loc 'analysis-console-print-button'}"
- ToolTip="{Loc 'analysis-console-print-tooltip-info'}">
- </Button>
- <BoxContainer MinHeight="5"></BoxContainer>
- <BoxContainer Orientation="Horizontal">
- <Button Name="UpBiasButton"
- Text="{Loc 'analysis-console-bias-up'}"
- ToolTip="{Loc 'analysis-console-bias-button-info-up'}"
- HorizontalExpand="True"
- StyleClasses="OpenRight">
- </Button>
- <Button Name="DownBiasButton"
- Text="{Loc 'analysis-console-bias-down'}"
- ToolTip="{Loc 'analysis-console-bias-button-info-down'}"
- HorizontalExpand="True"
- StyleClasses="OpenLeft">
- </Button>
+ <BoxContainer Margin="10 10 10 10" MaxWidth="240" SetWidth="240" Orientation="Vertical" HorizontalExpand="False" VerticalExpand="True">
+ <PanelContainer Name="BackPanel" HorizontalAlignment="Center">
+ <PanelContainer.PanelOverride>
+ <gfx:StyleBoxTexture Modulate="#1B1B1E" PatchMarginBottom="10" PatchMarginLeft="10" PatchMarginRight="10" PatchMarginTop="10"/>
+ </PanelContainer.PanelOverride>
+ <BoxContainer HorizontalExpand="True" VerticalExpand="True" MinSize="128 128">
+ <SpriteView Name="ArtifactView" Scale="4 4" HorizontalAlignment="Center" VerticalAlignment="Center" HorizontalExpand="True" VerticalExpand="True"/>
</BoxContainer>
- <BoxContainer MinHeight="15"></BoxContainer>
- <Button Name="ExtractButton"
- Text="{Loc 'analysis-console-extract-button'}"
- ToolTip="{Loc 'analysis-console-extract-button-info'}">
- </Button>
+ </PanelContainer>
+ <customControls:HSeparator StyleClasses="HighDivider" Margin="0 15 0 10"/>
+ <BoxContainer Name="ExtractContainer" Orientation="Vertical" VerticalExpand="True" Visible="False">
+ <PanelContainer HorizontalExpand="True" VerticalExpand="True" RectClipContent="True">
+ <PanelContainer.PanelOverride>
+ <gfx:StyleBoxFlat BackgroundColor="#000000FF" />
+ </PanelContainer.PanelOverride>
+ <BoxContainer Margin="10 10 10 5" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
+ <ScrollContainer HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True">
+ <BoxContainer HorizontalExpand="True" VerticalExpand="True">
+ <RichTextLabel Name="ExtractionResearchLabel" VerticalAlignment="Top" HorizontalAlignment="Left"/>
+ </BoxContainer>
+ </ScrollContainer>
+ <Control MinHeight="5"/>
+ <RichTextLabel Name="ExtractionSumLabel" VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
+ </BoxContainer>
+ </PanelContainer>
</BoxContainer>
- <BoxContainer Orientation="Vertical">
- <Label Name="ProgressLabel"></Label>
- <ProgressBar
- Name="ProgressBar"
- MinValue="0"
- MaxValue="1"
- SetHeight="20">
- </ProgressBar>
+ <BoxContainer Name="NodeViewContainer" Orientation="Vertical" VerticalExpand="True">
+ <ScrollContainer HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True">
+ <BoxContainer Orientation="Vertical" HorizontalExpand="False" VerticalExpand="True">
+ <Label Name="NoneSelectedLabel" Text="{Loc 'analysis-console-no-node'}" HorizontalAlignment="Center" VerticalAlignment="Center" VerticalExpand="True" Visible="False"/>
+ <BoxContainer Name="InfoContainer" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
+ <BoxContainer HorizontalExpand="True">
+ <RichTextLabel Name="IDLabel" HorizontalExpand="True" Text="{Loc 'analysis-console-info-id'}"/>
+ <RichTextLabel Name="IDValueLabel" HorizontalAlignment="Right"/>
+ </BoxContainer>
+ <BoxContainer HorizontalExpand="True">
+ <RichTextLabel Name="ClassLabel" HorizontalExpand="True" Text="{Loc 'analysis-console-info-class'}"/>
+ <RichTextLabel Name="ClassValueLabel" HorizontalAlignment="Right"/>
+ </BoxContainer>
+ <BoxContainer HorizontalExpand="True">
+ <RichTextLabel Name="LockedLabel" HorizontalExpand="True" Text="{Loc 'analysis-console-info-locked'}"/>
+ <RichTextLabel Name="LockedValueLabel" HorizontalAlignment="Right"/>
+ </BoxContainer>
+ <BoxContainer HorizontalExpand="True">
+ <RichTextLabel Name="DurabilityLabel" HorizontalExpand="True" Text="{Loc 'analysis-console-info-durability'}"/>
+ <RichTextLabel Name="DurabilityValueLabel" HorizontalAlignment="Right"/>
+ </BoxContainer>
+ <Control MinHeight="20"/>
+ <RichTextLabel Name="EffectLabel" Text="{Loc 'analysis-console-info-effect'}"/>
+ <RichTextLabel Name="EffectValueLabel" HorizontalExpand="True"/>
+ <RichTextLabel Name="TriggerLabel" Text="{Loc 'analysis-console-info-trigger'}"/>
+ <RichTextLabel Name="TriggerValueLabel" HorizontalExpand="True"/>
+ </BoxContainer>
+ </BoxContainer>
+ </ScrollContainer>
+ <Control MinHeight="5"/>
+ <BoxContainer Orientation="Horizontal" HorizontalExpand="True">
+ <Button Name="ServerButton" Text="{Loc 'analysis-console-server-list-button'}" StyleClasses="OpenRight" HorizontalExpand="True" MinHeight="35"/>
+ <Button Name="ExtractButton" Text="{Loc 'analysis-console-extract-button'}" StyleClasses="OpenLeft" HorizontalExpand="True" MinHeight="35"/>
+ </BoxContainer>
</BoxContainer>
</BoxContainer>
<customControls:VSeparator StyleClasses="LowDivider" />
- <PanelContainer Margin="10 10 10 10" HorizontalExpand="True" SizeFlagsStretchRatio="3">
- <PanelContainer.PanelOverride>
- <gfx:StyleBoxFlat BackgroundColor="#000000FF" />
- </PanelContainer.PanelOverride>
- <BoxContainer Margin="10 10 10 10" Orientation="Horizontal">
- <BoxContainer Orientation="Vertical" HorizontalExpand="True">
- <BoxContainer VerticalExpand="True">
- <RichTextLabel Name="Information"> </RichTextLabel>
- </BoxContainer>
+ <BoxContainer HorizontalExpand="True" VerticalExpand="True">
+ <PanelContainer Margin="10 10 10 10" HorizontalExpand="True" RectClipContent="True">
+ <PanelContainer.PanelOverride>
+ <gfx:StyleBoxFlat BackgroundColor="#000000FF" />
+ </PanelContainer.PanelOverride>
+ <BoxContainer Margin="10 10 10 10" Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
+ <ui:XenoArtifactGraphControl Name="GraphControl" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
+ <Label Name="NoArtiLabel"
+ Text="{Loc 'analysis-console-info-no-artifact'}"
+ HorizontalExpand="True"
+ VerticalExpand="True"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"/>
+ </ui:XenoArtifactGraphControl>
</BoxContainer>
- <BoxContainer VerticalExpand="False" Orientation="Vertical" MaxSize="64 64">
- <SpriteView
- Name="ArtifactDisplay"
- OverrideDirection="South"
- VerticalExpand="False"
- SetSize="64 64"
- MaxSize="64 64"
- Scale="2 2">
- </SpriteView>
- </BoxContainer>
- <BoxContainer VerticalExpand="True"></BoxContainer>
- </BoxContainer>
- </PanelContainer>
+ </PanelContainer>
+ </BoxContainer>
</BoxContainer>
</controls:FancyWindow>
-using Content.Client.Stylesheets;
+using System.Text;
+using Content.Client.Message;
+using Content.Client.Resources;
using Content.Client.UserInterface.Controls;
-using Content.Shared.Xenoarchaeology.Equipment;
-using Microsoft.VisualBasic;
+using Content.Client.Xenoarchaeology.Artifact;
+using Content.Client.Xenoarchaeology.Equipment;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Equipment.Components;
+using Robust.Client.Audio;
using Robust.Client.AutoGenerated;
-using Robust.Client.GameObjects;
+using Robust.Client.Graphics;
+using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Audio;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
[GenerateTypedNameReferences]
public sealed partial class AnalysisConsoleMenu : FancyWindow
{
+ private static readonly TimeSpan ExtractInfoDisplayForDuration = TimeSpan.FromSeconds(3);
+
[Dependency] private readonly IEntityManager _ent = default!;
+ [Dependency] private readonly IResourceCache _resCache = default!;
[Dependency] private readonly IGameTiming _timing = default!;
- public event Action? OnServerSelectionButtonPressed;
- public event Action? OnScanButtonPressed;
- public event Action? OnPrintButtonPressed;
- public event Action? OnExtractButtonPressed;
- public event Action? OnUpBiasButtonPressed;
- public event Action? OnDownBiasButtonPressed;
+ private readonly ArtifactAnalyzerSystem _artifactAnalyzer;
+ private readonly XenoArtifactSystem _xenoArtifact;
+ private readonly AudioSystem _audio;
+ private readonly MetaDataSystem _meta = default!;
- // For rendering the progress bar, updated from BUI state
- private TimeSpan? _startTime;
- private TimeSpan? _totalTime;
- private TimeSpan? _accumulatedRunTime;
+ private Entity<AnalysisConsoleComponent> _owner;
+ private Entity<XenoArtifactNodeComponent>? _currentNode;
- private bool _paused;
+ private TimeSpan? _hideExtractInfoIn;
+ private int _extractionSum;
+
+ public event Action? OnServerSelectionButtonPressed;
+ public event Action? OnExtractButtonPressed;
public AnalysisConsoleMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
- ServerSelectionButton.OnPressed += _ => OnServerSelectionButtonPressed?.Invoke();
- ScanButton.OnPressed += _ => OnScanButtonPressed?.Invoke();
- PrintButton.OnPressed += _ => OnPrintButtonPressed?.Invoke();
- ExtractButton.OnPressed += _ => OnExtractButtonPressed?.Invoke();
- UpBiasButton.OnPressed += _ => OnUpBiasButtonPressed?.Invoke();
- DownBiasButton.OnPressed += _ => OnDownBiasButtonPressed?.Invoke();
+ _xenoArtifact = _ent.System<XenoArtifactSystem>();
+ _artifactAnalyzer = _ent.System<ArtifactAnalyzerSystem>();
+ _audio = _ent.System<AudioSystem>();
+ _meta = _ent.System<MetaDataSystem>();
+
+ if (BackPanel.PanelOverride is StyleBoxTexture tex)
+ tex.Texture = _resCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
- var buttonGroup = new ButtonGroup(false);
- UpBiasButton.Group = buttonGroup;
- DownBiasButton.Group = buttonGroup;
+ GraphControl.OnNodeSelected += node =>
+ {
+ _currentNode = node;
+ SetSelectedNode(node);
+ };
+
+ ServerButton.OnPressed += _ =>
+ {
+ OnServerSelectionButtonPressed?.Invoke();
+ };
+
+ ExtractButton.OnPressed += StartExtract;
}
- protected override void FrameUpdate(FrameEventArgs args)
+ /// <summary>
+ /// Set entity that corresponds analysis console, for which window is opened.
+ /// Closes window if <see cref="AnalysisConsoleComponent"/> is not present on entity.
+ /// </summary>
+ public void SetOwner(EntityUid owner)
{
- base.FrameUpdate(args);
+ if (!_ent.TryGetComponent<AnalysisConsoleComponent>(owner, out var comp))
+ {
+ Close();
+ return;
+ }
+
+ _owner = (owner, comp);
+ Update(_owner);
+ }
- if (_startTime is not { } start || _totalTime is not { } total || _accumulatedRunTime is not { } accumulated)
+ private void StartExtract(BaseButton.ButtonEventArgs obj)
+ {
+ if (!_artifactAnalyzer.TryGetArtifactFromConsole(_owner, out var artifact))
return;
- var remaining = total - accumulated;
- if (!_paused)
+ ExtractContainer.Visible = true;
+ NodeViewContainer.Visible = false;
+
+ _extractionSum = 0;
+ var extractionMessage = new FormattedMessage();
+
+ var nodes = _xenoArtifact.GetAllNodes(artifact.Value);
+
+ var count = 0;
+ foreach (var node in nodes)
{
- // If the analyzer is running, its remaining time is further discounted by the time it's been running for.
- remaining += start - _timing.CurTime;
+ var pointValue = _xenoArtifact.GetResearchValue(node);
+ if (pointValue <= 0)
+ continue;
+
+ count++;
+
+ var nodeId = _xenoArtifact.GetNodeId(node);
+
+ var text = Loc.GetString("analysis-console-extract-value", ("id", nodeId), ("value", pointValue));
+ extractionMessage.AddMarkupOrThrow(text);
+ extractionMessage.PushNewline();
}
- var secsText = Math.Max((int) remaining.TotalSeconds, 0);
- ProgressLabel.Text = Loc.GetString("analysis-console-progress-text",
- ("seconds", secsText));
+ if (count == 0)
+ extractionMessage.AddMarkupOrThrow(Loc.GetString("analysis-console-extract-none"));
+
+ _hideExtractInfoIn = _timing.CurTime + ExtractInfoDisplayForDuration;
+
+ ExtractionResearchLabel.SetMessage(extractionMessage);
+
+ ExtractionSumLabel.SetMarkup(Loc.GetString("analysis-console-extract-sum", ("value", _extractionSum)));
- // 1.0 - div because we want it to tick up not down
- ProgressBar.Value = Math.Clamp(1.0f - (float) remaining.Divide(total), 0.0f, 1.0f);
+ _audio.PlayGlobal(_owner.Comp.ScanFinishedSound, _owner, AudioParams.Default.WithVolume(1f));
+ OnExtractButtonPressed?.Invoke();
}
- public void SetButtonsDisabled(AnalysisConsoleUpdateState state)
+ protected override void FrameUpdate(FrameEventArgs args)
{
- ScanButton.Disabled = !state.CanScan;
- PrintButton.Disabled = !state.CanPrint;
- if (state.IsTraversalDown)
- DownBiasButton.Pressed = true;
- else
- UpBiasButton.Pressed = true;
+ base.FrameUpdate(args);
- ExtractButton.Disabled = false;
- if (!state.ServerConnected)
- {
- ExtractButton.Disabled = true;
- ExtractButton.ToolTip = Loc.GetString("analysis-console-no-server-connected");
- }
- else if (!state.CanScan)
- {
- ExtractButton.Disabled = true;
-
- // CanScan can be false if either there's no analyzer connected or if there's
- // no entity on the scanner. The `Information` text will always tell the user
- // of the former case, but in the latter, it'll only show a message if a scan
- // has never been performed, so add a tooltip to indicate that the artifact
- // is gone.
- if (state.AnalyzerConnected)
- {
- ExtractButton.ToolTip = Loc.GetString("analysis-console-no-artifact-placed");
- }
- else
- {
- ExtractButton.ToolTip = null;
- }
- }
- else if (state.PointAmount <= 0)
- {
- ExtractButton.Disabled = true;
- ExtractButton.ToolTip = Loc.GetString("analysis-console-no-points-to-extract");
- }
+ if (_hideExtractInfoIn == null || _timing.CurTime + _meta.GetPauseTime(_owner) < _hideExtractInfoIn)
+ return;
- if (ExtractButton.Disabled)
- {
- ExtractButton.RemoveStyleClass("ButtonColorGreen");
- }
- else
- {
- ExtractButton.AddStyleClass("ButtonColorGreen");
- ExtractButton.ToolTip = null;
- }
+ ExtractContainer.Visible = false;
+ NodeViewContainer.Visible = true;
+ _hideExtractInfoIn = null;
}
- private void UpdateArtifactIcon(EntityUid? uid)
+
+ public void Update(Entity<AnalysisConsoleComponent> ent)
{
- if (uid == null)
+ _artifactAnalyzer.TryGetArtifactFromConsole(ent, out var arti);
+ ArtifactView.SetEntity(arti);
+ GraphControl.SetArtifact(arti);
+
+ ExtractButton.Disabled = arti == null;
+
+ if (arti == null)
+ NoneSelectedLabel.Visible = false;
+
+ NoArtiLabel.Visible = true;
+ if (!_artifactAnalyzer.TryGetAnalyzer(ent, out _))
+ NoArtiLabel.Text = Loc.GetString("analysis-console-info-no-scanner");
+ else if (arti == null)
+ NoArtiLabel.Text = Loc.GetString("analysis-console-info-no-artifact");
+ else
+ NoArtiLabel.Visible = false;
+
+ if (_currentNode == null
+ || arti == null
+ || !_xenoArtifact.TryGetIndex((arti.Value, arti.Value), _currentNode.Value, out _))
{
- ArtifactDisplay.Visible = false;
- return;
+ SetSelectedNode(null);
}
-
- ArtifactDisplay.Visible = true;
- ArtifactDisplay.SetEntity(uid);
}
- public void UpdateInformationDisplay(AnalysisConsoleUpdateState state)
+ public void SetSelectedNode(Entity<XenoArtifactNodeComponent>? node)
{
- var message = new FormattedMessage();
+ InfoContainer.Visible = node != null;
+ if (!_artifactAnalyzer.TryGetArtifactFromConsole(_owner, out var artifact))
+ return;
- if (state.Scanning)
- {
- if (state.Paused)
- {
- message.AddMarkupOrThrow(Loc.GetString("analysis-console-info-scanner-paused"));
- }
- else
- {
- message.AddMarkupOrThrow(Loc.GetString("analysis-console-info-scanner"));
- }
- Information.SetMessage(message);
- UpdateArtifactIcon(null); //set it to blank
+ NoneSelectedLabel.Visible = node == null;
+
+ if (node == null)
return;
- }
- UpdateArtifactIcon(_ent.GetEntity(state.Artifact));
+ var nodeId = _xenoArtifact.GetNodeId(node.Value);
+ IDValueLabel.SetMarkup(Loc.GetString("analysis-console-info-id-value", ("id", nodeId)));
+
+ // If active, state is 2. else, it is 0 or 1 based on whether it is unlocked, or not.
+ int lockedState;
+ if (_xenoArtifact.IsNodeActive(artifact.Value, node.Value))
+ lockedState = 2;
+ else
+ lockedState = node.Value.Comp.Locked ? 0 : 1;
+
+ LockedValueLabel.SetMarkup(Loc.GetString("analysis-console-info-locked-value", ("state", lockedState)));
- if (state.ScanReport == null)
+ var percent = (float) node.Value.Comp.Durability / node.Value.Comp.MaxDurability;
+ var color = percent switch
+ {
+ >= 0.75f => Color.Lime,
+ >= 0.50f => Color.Yellow,
+ _ => Color.Red
+ };
+ DurabilityValueLabel.SetMarkup(Loc.GetString("analysis-console-info-durability-value",
+ ("color", color),
+ ("current", node.Value.Comp.Durability),
+ ("max", node.Value.Comp.MaxDurability)));
+
+ var hasInfo = _xenoArtifact.HasUnlockedPredecessor(artifact.Value, node.Value);
+
+ EffectValueLabel.SetMarkup(Loc.GetString("analysis-console-info-effect-value",
+ ("state", hasInfo),
+ ("info", _ent.GetComponentOrNull<MetaDataComponent>(node.Value)?.EntityDescription ?? string.Empty)));
+
+ var predecessorNodes = _xenoArtifact.GetPredecessorNodes(artifact.Value.Owner, node.Value);
+ if (!hasInfo)
{
- if (!state.AnalyzerConnected) //no analyzer connected
- message.AddMarkupOrThrow(Loc.GetString("analysis-console-info-no-scanner"));
- else if (!state.CanScan) //no artifact
- message.AddMarkupOrThrow(Loc.GetString("analysis-console-info-no-artifact"));
- else if (state.Artifact == null) //ready to go
- message.AddMarkupOrThrow(Loc.GetString("analysis-console-info-ready"));
+ TriggerValueLabel.SetMarkup(Loc.GetString("analysis-console-info-effect-value", ("state", false)));
}
else
{
- message.AddMessage(state.ScanReport);
- }
-
- Information.SetMessage(message);
- }
+ var triggerStr = new StringBuilder();
+ triggerStr.Append("- ");
+ triggerStr.Append(Loc.GetString(node.Value.Comp.TriggerTip!));
- public void UpdateProgressBar(AnalysisConsoleUpdateState state)
- {
- ProgressBar.Visible = state.Scanning;
- ProgressLabel.Visible = state.Scanning;
+ foreach (var predecessor in predecessorNodes)
+ {
+ triggerStr.AppendLine();
+ triggerStr.Append("- ");
+ triggerStr.Append(Loc.GetString(predecessor.Comp.TriggerTip!));
+ }
+ TriggerValueLabel.SetMarkup(Loc.GetString("analysis-console-info-triggered-value", ("triggers", triggerStr.ToString())));
+ }
- _startTime = state.StartTime;
- _totalTime = state.TotalTime;
- _accumulatedRunTime = state.AccumulatedRunTime;
- _paused = state.Paused;
+ ClassValueLabel.SetMarkup(Loc.GetString("analysis-console-info-class-value",
+ ("class", Loc.GetString($"artifact-node-class-{Math.Min(6, predecessorNodes.Count + 1)}"))));
}
}
--- /dev/null
+using Content.Shared.Xenoarchaeology.Equipment.Components;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.Xenoarchaeology.Ui;
+
+/// <summary>
+/// BUI for hand-held xeno artifact scanner, server-provided UI updates.
+/// </summary>
+public sealed class NodeScannerBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
+{
+ [ViewVariables]
+ private NodeScannerDisplay? _scannerDisplay;
+
+ /// <inheritdoc />
+ protected override void Open()
+ {
+ base.Open();
+
+ _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 />
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (!disposing)
+ return;
+
+ _scannerDisplay?.Dispose();
+ }
+}
--- /dev/null
+<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="47" />
+ </BoxContainer>
+ <controls:StripeBack>
+ <Label Name="ArtifactStateLabel" HorizontalAlignment="Center" StyleClasses="LabelSubText" Margin="4 0 0 4" />
+ </controls:StripeBack>
+ </BoxContainer>
+</controls:FancyWindow>
--- /dev/null
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Xenoarchaeology.Equipment.Components;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Xenoarchaeology.Ui;
+
+[GenerateTypedNameReferences]
+public sealed partial class NodeScannerDisplay : FancyWindow
+{
+ [Dependency] private readonly IEntityManager _ent = default!;
+
+ public NodeScannerDisplay()
+ {
+ RobustXamlLoader.Load(this);
+
+ IoCManager.InjectDependencies(this);
+ }
+
+ /// <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;
+ }
+
+ Update((scannerEntityUid, scannerComponent));
+ }
+
+ /// <summary>
+ /// Updates labels with scanned artifact data and list of triggered nodes from component.
+ /// </summary>
+ public void Update(Entity<NodeScannerComponent> ent)
+ {
+ 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");
+
+ ActiveNodesList.Children.Clear();
+
+ var triggeredNodesSnapshot = ent.Comp.TriggeredNodesSnapshot;
+ if (triggeredNodesSnapshot.Count > 0)
+ {
+ // show list of triggered nodes instead of 'no data' placeholder
+ NoActiveNodeDataLabel.Visible = false;
+ ActiveNodesList.Visible = true;
+
+ foreach (var nodeId in triggeredNodesSnapshot)
+ {
+ 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 GetState(Entity<NodeScannerComponent> ent)
+ {
+ return ent.Comp.ArtifactState 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")
+ };
+ }
+}
--- /dev/null
+<controls:XenoArtifactGraphControl
+ xmlns="https://spacestation14.io"
+ xmlns:controls="clr-namespace:Content.Client.Xenoarchaeology.Ui"
+ HorizontalExpand="True"
+ VerticalExpand="True"
+ MouseFilter="Stop"/>
--- /dev/null
+using System.Linq;
+using System.Numerics;
+using Content.Client.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Robust.Client.AutoGenerated;
+using Robust.Client.Graphics;
+using Robust.Client.ResourceManagement;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Input;
+
+namespace Content.Client.Xenoarchaeology.Ui;
+
+[GenerateTypedNameReferences]
+public sealed partial class XenoArtifactGraphControl : BoxContainer
+{
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+
+ private readonly XenoArtifactSystem _artifactSystem;
+
+ private Entity<XenoArtifactComponent>? _artifact;
+
+ private Entity<XenoArtifactNodeComponent>? _hoveredNode;
+
+ private readonly Font _font;
+
+ public event Action<Entity<XenoArtifactNodeComponent>>? OnNodeSelected;
+
+ private float NodeRadius => 25 * UIScale;
+ private float NodeDiameter => NodeRadius * 2;
+ private float MinYSpacing => NodeDiameter * 0.75f;
+ private float MaxYSpacing => NodeDiameter * 1.5f;
+ private float MinXSpacing => NodeDiameter * 0.33f;
+ private float MaxXSpacing => NodeDiameter * 1f;
+ private float MinXSegmentSpacing => NodeDiameter * 0.5f;
+ private float MaxXSegmentSpacing => NodeDiameter * 3f;
+
+ public XenoArtifactGraphControl()
+ {
+ IoCManager.InjectDependencies(this);
+ RobustXamlLoader.Load(this);
+
+ _artifactSystem = _entityManager.System<XenoArtifactSystem>();
+
+ var fontResource = IoCManager.Resolve<IResourceCache>()
+ .GetResource<FontResource>("/EngineFonts/NotoSans/NotoSansMono-Regular.ttf");
+ _font = new VectorFont(fontResource, 16);
+ }
+
+ public Color LockedNodeColor { get; set; } = Color.FromHex("#777777");
+ public Color ActiveNodeColor { get; set; } = Color.Plum;
+ public Color UnlockedNodeColor { get; set; } = Color.White;
+ public Color HoveredNodeColor { get; set; } = Color.DimGray;
+ public Color UnlockableNodeColor { get; set; } = Color.LightSlateGray;
+
+ public void SetArtifact(Entity<XenoArtifactComponent>? artifact)
+ {
+ _artifact = artifact;
+ }
+
+ protected override void KeyBindDown(GUIBoundKeyEventArgs args)
+ {
+ base.KeyBindDown(args);
+
+ if (args.Handled || args.Function != EngineKeyFunctions.UIClick)
+ return;
+
+ if (_hoveredNode == null)
+ return;
+
+ OnNodeSelected?.Invoke(_hoveredNode.Value);
+ UserInterfaceManager.ClickSound();
+ }
+
+ /// <summary>
+ /// Renders artifact node graph control, consisting of nodes and edges connecting them.
+ /// </summary>
+ protected override void Draw(DrawingHandleScreen handle)
+ {
+ base.Draw(handle);
+
+ _hoveredNode = null;
+ if (_artifact == null)
+ return;
+ var artifact = _artifact.Value;
+
+ var maxDepth = _artifactSystem.GetAllNodes(artifact)
+ .Max(s => s.Comp.Depth);
+ var segments = _artifactSystem.GetSegments(artifact);
+
+ var bottomLeft = Position // the position
+ + new Vector2(0, Size.Y * UIScale) // the scaled height of the control
+ + new Vector2(NodeRadius, -NodeRadius); // offset half a node so we don't render off screen
+
+ var controlHeight = bottomLeft.Y;
+ var controlWidth = Size.X * UIScale - NodeRadius;
+
+ // select y spacing based on max number of nodes we have on Y axis - that is max depth of artifact graph node
+ var ySpacing = 0f;
+ if (maxDepth != 0)
+ ySpacing = Math.Clamp((controlHeight - ((maxDepth + 1) * NodeDiameter)) / maxDepth, MinYSpacing, MaxYSpacing);
+
+ // gets settings for visualizing segments (groups of interconnected nodes - there may be 1 or more per artifact).
+ var segmentWidths = segments.Sum(GetBiggestWidth);
+ var segmentSpacing = Math.Clamp((controlWidth - segmentWidths) / (segments.Count - 1), MinXSegmentSpacing, MaxXSegmentSpacing);
+ var segmentOffset = Math.Max((controlWidth - (segmentWidths) - (segmentSpacing * (segments.Count - 1))) / 2, 0);
+
+ bottomLeft.X += segmentOffset;
+ bottomLeft.Y -= (controlHeight - (ySpacing * maxDepth) - (NodeDiameter * (maxDepth + 1))) / 2;
+
+ var cursor = (UserInterfaceManager.MousePositionScaled.Position * UIScale) - GlobalPixelPosition;
+
+ foreach (var segment in segments)
+ {
+ // For each segment we draw nodes in order of depth. Method returns List of nodes for each depth level.
+ var orderedNodes = _artifactSystem.GetDepthOrderedNodes(segment);
+ foreach (var (_, nodes) in orderedNodes)
+ {
+ for (var i = 0; i < nodes.Count; i++)
+ {
+ // selecting color for node based on its state
+ var node = nodes[i];
+ var color = LockedNodeColor;
+ if (_artifactSystem.IsNodeActive(artifact, node))
+ {
+ color = ActiveNodeColor;
+ }
+ else if (!node.Comp.Locked)
+ {
+ color = UnlockedNodeColor;
+ }
+ else
+ {
+ var directPredecessorNodes = _artifactSystem.GetDirectPredecessorNodes((artifact, artifact), node);
+ if (directPredecessorNodes.Count == 0 || directPredecessorNodes.All(x => !x.Comp.Locked))
+ {
+ color = UnlockableNodeColor;
+ }
+ }
+
+ var pos = GetNodePos(node, ySpacing, segments, ref bottomLeft);
+ var hovered = (cursor - pos).LengthSquared() <= NodeRadius * NodeRadius;
+ if (hovered)
+ {
+ // render hovered node if we have one
+ _hoveredNode = node;
+ handle.DrawCircle(pos, NodeRadius, HoveredNodeColor);
+ }
+
+ // render circle and text with node id inside
+ handle.DrawCircle(pos, NodeRadius, Color.ToSrgb(color), false);
+
+ var text = _artifactSystem.GetNodeId(node);
+ var dimensions = handle.GetDimensions(_font, text, 1);
+ handle.DrawString(_font, pos - new Vector2(dimensions.X / 2, dimensions.Y / 2), text, color);
+ }
+ }
+
+ // draw edges for each segment and each node that have successors
+ foreach (var node in segment)
+ {
+ var fromNode = GetNodePos(node, ySpacing, segments, ref bottomLeft) + new Vector2(0, -NodeRadius);
+ var successorNodes = _artifactSystem.GetDirectSuccessorNodes((artifact, artifact), node);
+ foreach (var successorNode in successorNodes)
+ {
+ var color = node.Comp.Locked
+ ? LockedNodeColor
+ : UnlockedNodeColor;
+
+ var toNode = GetNodePos(successorNode, ySpacing, segments, ref bottomLeft) + new Vector2(0, NodeRadius);
+ handle.DrawLine(fromNode, toNode, color);
+ }
+ }
+
+ bottomLeft.X += GetBiggestWidth(segment) + segmentSpacing;
+ }
+ }
+
+ private Vector2 GetNodePos(Entity<XenoArtifactNodeComponent> node, float ySpacing, List<List<Entity<XenoArtifactNodeComponent>>> segments, ref Vector2 bottomLeft)
+ {
+ var yPos = -(NodeDiameter + ySpacing) * node.Comp.Depth;
+
+ var segment = segments.First(s => s.Contains(node));
+ var depthOrderedNodes = _artifactSystem.GetDepthOrderedNodes(segment);
+ var biggestTier = depthOrderedNodes.Max(s => s.Value.Count);
+ var nodesInLayer = depthOrderedNodes.GetValueOrDefault(node.Comp.Depth)!.Count;
+ var biggestWidth = (NodeDiameter + MinXSpacing) * biggestTier;
+
+ var xSpacing = Math.Clamp((biggestWidth - (NodeDiameter * nodesInLayer)) / (nodesInLayer - 1), MinXSpacing, MaxXSpacing);
+ var layerXOffset = (biggestWidth - (xSpacing * (nodesInLayer - 1)) - (NodeDiameter * nodesInLayer)) / 2;
+
+ // get index of node in current segment's row (row per depth level)
+ var index = depthOrderedNodes.GetValueOrDefault(node.Comp.Depth)!.IndexOf(node);
+
+ var xPos = NodeDiameter * index + (xSpacing * index) + layerXOffset;
+
+ return bottomLeft + new Vector2(xPos, yPos);
+ }
+
+ private float GetBiggestWidth(List<Entity<XenoArtifactNodeComponent>> nodes)
+ {
+ var num = _artifactSystem.GetDepthOrderedNodes(nodes)
+ .Max(p => p.Value.Count);
+ return (NodeDiameter * num) + MinXSpacing * (num - 1);
+ }
+}
+
-using Content.Shared.Xenoarchaeology.XenoArtifacts;
+using Content.Shared.Xenoarchaeology.XenoArtifacts;
using Robust.Client.GameObjects;
namespace Content.Client.Xenoarchaeology.XenoArtifacts;
if (!AppearanceSystem.TryGetData<int>(uid, SharedArtifactsVisuals.SpriteIndex, out var spriteIndex, args.Component))
return;
+ if (!AppearanceSystem.TryGetData<bool>(uid, SharedArtifactsVisuals.IsUnlocking, out var isUnlocking, args.Component))
+ isUnlocking = false;
+
if (!AppearanceSystem.TryGetData<bool>(uid, SharedArtifactsVisuals.IsActivated, out var isActivated, args.Component))
isActivated = false;
var spriteIndexStr = spriteIndex.ToString("D2");
- var spritePrefix = isActivated ? "_on" : "";
+ var spritePrefix = isUnlocking ? "_on" : "";
// layered artifact sprite
- if (args.Sprite.LayerMapTryGet(ArtifactsVisualLayers.Effect, out var layer))
+ if (args.Sprite.LayerMapTryGet(ArtifactsVisualLayers.UnlockingEffect, out var layer))
{
var spriteState = "ano" + spriteIndexStr;
args.Sprite.LayerSetState(ArtifactsVisualLayers.Base, spriteState);
args.Sprite.LayerSetState(layer, spriteState + "_on");
- args.Sprite.LayerSetVisible(layer, isActivated);
+ args.Sprite.LayerSetVisible(layer, isUnlocking);
+
+ if (args.Sprite.LayerMapTryGet(ArtifactsVisualLayers.ActivationEffect, out var activationEffectLayer))
+ {
+ args.Sprite.LayerSetState(activationEffectLayer, "artifact-activation");
+ args.Sprite.LayerSetVisible(activationEffectLayer, isActivated);
+ }
}
// non-layered
else
var spriteState = "ano" + spriteIndexStr + spritePrefix;
args.Sprite.LayerSetState(ArtifactsVisualLayers.Base, spriteState);
}
-
}
}
public enum ArtifactsVisualLayers : byte
{
Base,
- Effect // doesn't have to use this
+ UnlockingEffect, // doesn't have to use this
+ ActivationEffect
}
--- /dev/null
+using System.Linq;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Robust.Shared.GameObjects;
+
+namespace Content.IntegrationTests.Tests;
+
+[TestFixture]
+public sealed class XenoArtifactTest
+{
+ [TestPrototypes]
+ private const string Prototypes = @"
+- type: entity
+ id: TestArtifact
+ parent: BaseXenoArtifact
+ name: artifact
+ components:
+ - type: XenoArtifact
+ isGenerationRequired: false
+ effectsTable: !type:NestedSelector
+ tableId: XenoArtifactEffectsDefaultTable
+
+- type: entity
+ id: TestGenArtifactFlat
+ parent: BaseXenoArtifact
+ name: artifact
+ components:
+ - type: XenoArtifact
+ isGenerationRequired: true
+ nodeCount:
+ min: 2
+ max: 2
+ segmentSize:
+ min: 1
+ max: 1
+ nodesPerSegmentLayer:
+ min: 1
+ max: 1
+ effectsTable: !type:NestedSelector
+ tableId: XenoArtifactEffectsDefaultTable
+
+- type: entity
+ id: TestGenArtifactTall
+ parent: BaseXenoArtifact
+ name: artifact
+ components:
+ - type: XenoArtifact
+ isGenerationRequired: true
+ nodeCount:
+ min: 2
+ max: 2
+ segmentSize:
+ min: 2
+ max: 2
+ nodesPerSegmentLayer:
+ min: 1
+ max: 1
+ effectsTable: !type:NestedSelector
+ tableId: XenoArtifactEffectsDefaultTable
+
+- type: entity
+ id: TestGenArtifactFull
+ name: artifact
+ components:
+ - type: XenoArtifact
+ isGenerationRequired: true
+ nodeCount:
+ min: 6
+ max: 6
+ segmentSize:
+ min: 6
+ max: 6
+ nodesPerSegmentLayer:
+ min: 2
+ max: 2
+ effectsTable: !type:NestedSelector
+ tableId: XenoArtifactEffectsDefaultTable
+
+- type: entity
+ id: TestArtifactNode
+ name: artifact node
+ components:
+ - type: XenoArtifactNode
+ maxDurability: 3
+";
+
+ /// <summary>
+ /// Checks that adding nodes and edges properly adds them into the adjacency matrix
+ /// </summary>
+ [Test]
+ public async Task XenoArtifactAddNodeTest()
+ {
+ await using var pair = await PoolManager.GetServerClient();
+ var server = pair.Server;
+
+ var entManager = server.ResolveDependency<IEntityManager>();
+ var artifactSystem = entManager.System<SharedXenoArtifactSystem>();
+
+ await server.WaitPost(() =>
+ {
+ var artifactUid = entManager.Spawn("TestArtifact");
+ var artifactEnt = (artifactUid, comp: entManager.GetComponent<XenoArtifactComponent>(artifactUid));
+
+ // Create 3 nodes
+ Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node1, false));
+ Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node2, false));
+ Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node3, false));
+
+ Assert.That(artifactSystem.GetAllNodeIndices(artifactEnt).Count(), Is.EqualTo(3));
+
+ // Add connection from 1 -> 2 and 2-> 3
+ artifactSystem.AddEdge(artifactEnt, node1!.Value, node2!.Value, false);
+ artifactSystem.AddEdge(artifactEnt, node2!.Value, node3!.Value, false);
+
+ // Assert that successors and direct successors are counted correctly for node 1.
+ Assert.That(artifactSystem.GetDirectSuccessorNodes(artifactEnt, node1!.Value).Count, Is.EqualTo(1));
+ Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node1!.Value).Count, Is.EqualTo(2));
+ // Assert that we didn't somehow get predecessors on node 1.
+ Assert.That(artifactSystem.GetDirectPredecessorNodes(artifactEnt, node1!.Value), Is.Empty);
+ Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node1!.Value), Is.Empty);
+
+ // Assert that successors and direct successors are counted correctly for node 2.
+ Assert.That(artifactSystem.GetDirectSuccessorNodes(artifactEnt, node2!.Value), Has.Count.EqualTo(1));
+ Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node2!.Value), Has.Count.EqualTo(1));
+ // Assert that predecessors and direct predecessors are counted correctly for node 2.
+ Assert.That(artifactSystem.GetDirectPredecessorNodes(artifactEnt, node2!.Value), Has.Count.EqualTo(1));
+ Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node2!.Value), Has.Count.EqualTo(1));
+
+ // Assert that successors and direct successors are counted correctly for node 3.
+ Assert.That(artifactSystem.GetDirectSuccessorNodes(artifactEnt, node3!.Value), Is.Empty);
+ Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node3!.Value), Is.Empty);
+ // Assert that predecessors and direct predecessors are counted correctly for node 3.
+ Assert.That(artifactSystem.GetDirectPredecessorNodes(artifactEnt, node3!.Value), Has.Count.EqualTo(1));
+ Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node3!.Value), Has.Count.EqualTo(2));
+ });
+ await server.WaitRunTicks(1);
+
+ await pair.CleanReturnAsync();
+ }
+
+ /// <summary>
+ /// Checks to make sure that removing nodes properly cleans up all connections.
+ /// </summary>
+ [Test]
+ public async Task XenoArtifactRemoveNodeTest()
+ {
+ await using var pair = await PoolManager.GetServerClient();
+ var server = pair.Server;
+
+ var entManager = server.ResolveDependency<IEntityManager>();
+ var artifactSystem = entManager.System<SharedXenoArtifactSystem>();
+
+ await server.WaitPost(() =>
+ {
+ var artifactUid = entManager.Spawn("TestArtifact");
+ var artifactEnt = (artifactUid, comp: entManager.GetComponent<XenoArtifactComponent>(artifactUid));
+
+ // Create 3 nodes
+ Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node1, false));
+ Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node2, false));
+ Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node3, false));
+ Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node4, false));
+ Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node5, false));
+
+ Assert.That(artifactSystem.GetAllNodeIndices(artifactEnt).Count(), Is.EqualTo(5));
+
+ // Add connection: 1 -> 2 -> 3 -> 4 -> 5
+ artifactSystem.AddEdge(artifactEnt, node1!.Value, node2!.Value, false);
+ artifactSystem.AddEdge(artifactEnt, node2!.Value, node3!.Value, false);
+ artifactSystem.AddEdge(artifactEnt, node3!.Value, node4!.Value, false);
+ artifactSystem.AddEdge(artifactEnt, node4!.Value, node5!.Value, false);
+
+ // Make sure we have a continuous connection between the two ends of the graph.
+ Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node1.Value), Has.Count.EqualTo(4));
+ Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node5.Value), Has.Count.EqualTo(4));
+
+ // Remove the node and make sure it's no longer in the artifact.
+ Assert.That(artifactSystem.RemoveNode(artifactEnt, node3!.Value, false));
+ Assert.That(artifactSystem.TryGetIndex(artifactEnt, node3!.Value, out _), Is.False, "Node 3 still present in artifact.");
+
+ // Check to make sure that we got rid of all the connections.
+ Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node2!.Value), Is.Empty);
+ Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node4!.Value), Is.Empty);
+ });
+ await server.WaitRunTicks(1);
+
+ await pair.CleanReturnAsync();
+ }
+
+ /// <summary>
+ /// Sets up series of linked nodes and ensures that resizing the adjacency matrix doesn't disturb the connections
+ /// </summary>
+ [Test]
+ public async Task XenoArtifactResizeTest()
+ {
+ await using var pair = await PoolManager.GetServerClient();
+ var server = pair.Server;
+
+ var entManager = server.ResolveDependency<IEntityManager>();
+ var artifactSystem = entManager.System<SharedXenoArtifactSystem>();
+
+ await server.WaitPost(() =>
+ {
+ var artifactUid = entManager.Spawn("TestArtifact");
+ var artifactEnt = (artifactUid, comp: entManager.GetComponent<XenoArtifactComponent>(artifactUid));
+
+ // Create 3 nodes
+ Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node1, false));
+ Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node2, false));
+ Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node3, false));
+
+ // Add connection: 1 -> 2 -> 3
+ artifactSystem.AddEdge(artifactEnt, node1!.Value, node2!.Value, false);
+ artifactSystem.AddEdge(artifactEnt, node2!.Value, node3!.Value, false);
+
+ // Make sure our connection is set up
+ Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node1.Value, node2.Value));
+ Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node2.Value, node3.Value));
+ Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node2.Value, node1.Value), Is.False);
+ Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node3.Value, node2.Value), Is.False);
+ Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node1.Value, node3.Value), Is.False);
+ Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node3.Value, node1.Value), Is.False);
+
+ Assert.That(artifactSystem.GetIndex(artifactEnt, node1!.Value), Is.EqualTo(0));
+ Assert.That(artifactSystem.GetIndex(artifactEnt, node2!.Value), Is.EqualTo(1));
+ Assert.That(artifactSystem.GetIndex(artifactEnt, node3!.Value), Is.EqualTo(2));
+
+ // Add a new node, resizing the original adjacency matrix and array.
+ Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node4));
+
+ // Check that our connections haven't changed.
+ Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node1.Value, node2.Value));
+ Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node2.Value, node3.Value));
+ Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node2.Value, node1.Value), Is.False);
+ Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node3.Value, node2.Value), Is.False);
+ Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node1.Value, node3.Value), Is.False);
+ Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node3.Value, node1.Value), Is.False);
+
+ // Has our array shifted any when we resized?
+ Assert.That(artifactSystem.GetIndex(artifactEnt, node1!.Value), Is.EqualTo(0));
+ Assert.That(artifactSystem.GetIndex(artifactEnt, node2!.Value), Is.EqualTo(1));
+ Assert.That(artifactSystem.GetIndex(artifactEnt, node3!.Value), Is.EqualTo(2));
+
+ // Check that 4 didn't somehow end up with connections
+ Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node4!.Value), Is.Empty);
+ Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node4!.Value), Is.Empty);
+ });
+ await server.WaitRunTicks(1);
+
+ await pair.CleanReturnAsync();
+ }
+
+ /// <summary>
+ /// Checks if removing a node and adding a new node into its place in the adjacency matrix doesn't accidentally retain extra data.
+ /// </summary>
+ [Test]
+ public async Task XenoArtifactReplaceTest()
+ {
+ await using var pair = await PoolManager.GetServerClient();
+ var server = pair.Server;
+
+ var entManager = server.ResolveDependency<IEntityManager>();
+ var artifactSystem = entManager.System<SharedXenoArtifactSystem>();
+
+ await server.WaitPost(() =>
+ {
+ var artifactUid = entManager.Spawn("TestArtifact");
+ var artifactEnt = (artifactUid, comp: entManager.GetComponent<XenoArtifactComponent>(artifactUid));
+
+ // Create 3 nodes
+ Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node1, false));
+ Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node2, false));
+ Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node3, false));
+
+ // Add connection: 1 -> 2 -> 3
+ artifactSystem.AddEdge(artifactEnt, node1!.Value, node2!.Value, false);
+ artifactSystem.AddEdge(artifactEnt, node2!.Value, node3!.Value, false);
+
+ // Make sure our connection is set up
+ Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node1.Value, node2.Value));
+ Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node2.Value, node3.Value));
+
+ // Remove middle node, severing connections
+ artifactSystem.RemoveNode(artifactEnt, node2!.Value, false);
+
+ // Make sure our connection are properly severed.
+ Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node1.Value), Is.Empty);
+ Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node3.Value), Is.Empty);
+
+ // Make sure our matrix is 3x3
+ Assert.That(artifactEnt.Item2.NodeAdjacencyMatrixRows, Is.EqualTo(3));
+ Assert.That(artifactEnt.Item2.NodeAdjacencyMatrixColumns, Is.EqualTo(3));
+
+ Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node4, false));
+
+ // Make sure that adding in a new node didn't add a new slot but instead re-used the middle slot.
+ Assert.That(artifactEnt.Item2.NodeAdjacencyMatrixRows, Is.EqualTo(3));
+ Assert.That(artifactEnt.Item2.NodeAdjacencyMatrixColumns, Is.EqualTo(3));
+
+ // Ensure that all connections are still severed
+ Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node1.Value), Is.Empty);
+ Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node3.Value), Is.Empty);
+ Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node4!.Value), Is.Empty);
+ Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node4!.Value), Is.Empty);
+
+ });
+ await server.WaitRunTicks(1);
+
+ await pair.CleanReturnAsync();
+ }
+
+ /// <summary>
+ /// Checks if the active nodes are properly detected.
+ /// </summary>
+ [Test]
+ public async Task XenoArtifactBuildActiveNodesTest()
+ {
+ await using var pair = await PoolManager.GetServerClient();
+ var server = pair.Server;
+
+ var entManager = server.ResolveDependency<IEntityManager>();
+ var artifactSystem = entManager.System<SharedXenoArtifactSystem>();
+
+ await server.WaitPost(() =>
+ {
+ var artifactUid = entManager.Spawn("TestArtifact");
+ Entity<XenoArtifactComponent> artifactEnt = (artifactUid, entManager.GetComponent<XenoArtifactComponent>(artifactUid));
+
+ Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node1, false));
+ Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node2, false));
+ Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node3, false));
+ Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node4, false));
+ Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node5, false));
+ Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node6, false));
+ Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node7, false));
+ Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node8, false));
+
+ // /----( 6 )
+ // /----[*3 ]-/----( 7 )----( 8 )
+ // /
+ // / /----[*5 ]
+ // [ 1 ]--/----[ 2 ]--/----( 4 )
+ // Diagram of the example generation. Nodes in [brackets] are unlocked, nodes in (braces) are locked
+ // and nodes with an *asterisk are supposed to be active.
+ artifactSystem.AddEdge(artifactEnt, node1!.Value, node2!.Value, false);
+ artifactSystem.AddEdge(artifactEnt, node1!.Value, node3!.Value, false);
+
+ artifactSystem.AddEdge(artifactEnt, node2!.Value, node4!.Value, false);
+ artifactSystem.AddEdge(artifactEnt, node2!.Value, node5!.Value, false);
+
+ artifactSystem.AddEdge(artifactEnt, node3!.Value, node6!.Value, false);
+ artifactSystem.AddEdge(artifactEnt, node3!.Value, node7!.Value, false);
+
+ artifactSystem.AddEdge(artifactEnt, node7!.Value, node8!.Value, false);
+
+ artifactSystem.SetNodeUnlocked(node1!.Value);
+ artifactSystem.SetNodeUnlocked(node2!.Value);
+ artifactSystem.SetNodeUnlocked(node3!.Value);
+ artifactSystem.SetNodeUnlocked(node5!.Value);
+
+ NetEntity[] expectedActiveNodes =
+ [
+ entManager.GetNetEntity(node3!.Value.Owner),
+ entManager.GetNetEntity(node5!.Value.Owner)
+ ];
+ Assert.That(artifactEnt.Comp.CachedActiveNodes, Is.SupersetOf(expectedActiveNodes));
+ Assert.That(artifactEnt.Comp.CachedActiveNodes, Has.Count.EqualTo(expectedActiveNodes.Length));
+
+ });
+ await server.WaitRunTicks(1);
+
+ await pair.CleanReturnAsync();
+ }
+
+ [Test]
+ public async Task XenoArtifactGenerateSegmentsTest()
+ {
+ await using var pair = await PoolManager.GetServerClient();
+ var server = pair.Server;
+
+ var entManager = server.ResolveDependency<IEntityManager>();
+ var artifactSystem = entManager.System<SharedXenoArtifactSystem>();
+
+ await server.WaitPost(() =>
+ {
+ var artifact1Uid = entManager.Spawn("TestGenArtifactFlat");
+ Entity<XenoArtifactComponent> artifact1Ent = (artifact1Uid, entManager.GetComponent<XenoArtifactComponent>(artifact1Uid));
+
+ var segments1 = artifactSystem.GetSegments(artifact1Ent);
+ Assert.That(segments1.Count, Is.EqualTo(2));
+ Assert.That(segments1[0].Count, Is.EqualTo(1));
+ Assert.That(segments1[1].Count, Is.EqualTo(1));
+
+ var artifact2Uid = entManager.Spawn("TestGenArtifactTall");
+ Entity<XenoArtifactComponent> artifact2Ent = (artifact2Uid, entManager.GetComponent<XenoArtifactComponent>(artifact2Uid));
+
+ var segments2 = artifactSystem.GetSegments(artifact2Ent);
+ Assert.That(segments2.Count, Is.EqualTo(1));
+ Assert.That(segments2[0].Count, Is.EqualTo(2));
+
+ var artifact3Uid = entManager.Spawn("TestGenArtifactFull");
+ Entity<XenoArtifactComponent> artifact3Ent = (artifact3Uid, entManager.GetComponent<XenoArtifactComponent>(artifact3Uid));
+
+ var segments3 = artifactSystem.GetSegments(artifact3Ent);
+ Assert.That(segments3.Count, Is.EqualTo(1));
+ Assert.That(segments3.Sum(x => x.Count), Is.EqualTo(6));
+ var nodesDepths = segments3[0].Select(x => x.Comp.Depth).ToArray();
+ Assert.That(nodesDepths.Distinct().Count(), Is.EqualTo(3));
+ var grouped = nodesDepths.ToLookup(x => x);
+ Assert.That(grouped[0].Count(), Is.EqualTo(2));
+ Assert.That(grouped[1].Count(), Is.GreaterThanOrEqualTo(2)); // tree is attempting sometimes to get wider (so it will look like a tree)
+ Assert.That(grouped[2].Count(), Is.LessThanOrEqualTo(2)); // maintain same width or, if we used 3 nodes on previous layer - we only have 1 left!
+
+ });
+ await server.WaitRunTicks(1);
+
+ await pair.CleanReturnAsync();
+ }
+}
using Content.Server.Mind.Commands;
using Content.Server.Prayer;
using Content.Server.Station.Systems;
-using Content.Server.Xenoarchaeology.XenoArtifacts;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
using Content.Shared.Administration;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
[Dependency] private readonly EuiManager _euiManager = default!;
[Dependency] private readonly GameTicker _ticker = default!;
[Dependency] private readonly GhostRoleSystem _ghostRoleSystem = default!;
- [Dependency] private readonly ArtifactSystem _artifactSystem = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly PrayerSystem _prayerSystem = default!;
[Dependency] private readonly MindSystem _mindSystem = default!;
args.Verbs.Add(verb);
}
- // XenoArcheology
- if (_adminManager.IsAdmin(player) && TryComp<ArtifactComponent>(args.Target, out var artifact))
- {
- // make artifact always active (by adding timer trigger)
- args.Verbs.Add(new Verb()
- {
- Text = Loc.GetString("artifact-verb-make-always-active"),
- Category = VerbCategory.Debug,
- Act = () => EntityManager.AddComponent<ArtifactTimerTriggerComponent>(args.Target),
- Disabled = EntityManager.HasComponent<ArtifactTimerTriggerComponent>(args.Target),
- Impact = LogImpact.High
- });
-
- // force to activate artifact ignoring timeout
- args.Verbs.Add(new Verb()
- {
- Text = Loc.GetString("artifact-verb-activate"),
- Category = VerbCategory.Debug,
- Act = () => _artifactSystem.ForceActivateArtifact(args.Target, component: artifact),
- Impact = LogImpact.High
- });
- }
-
// Make Sentient verb
if (_groupController.CanCommand(player, "makesentient") &&
args.User != args.Target &&
Spawn(EmpPulseEffectPrototype, coordinates);
}
+ /// <summary>
+ /// Triggers an EMP pulse at the given location, by first raising an <see cref="EmpAttemptEvent"/>, then a raising <see cref="EmpPulseEvent"/> on all entities in range.
+ /// </summary>
+ /// <param name="coordinates">The location to trigger the EMP pulse at.</param>
+ /// <param name="range">The range of the EMP pulse.</param>
+ /// <param name="energyConsumption">The amount of energy consumed by the EMP pulse.</param>
+ /// <param name="duration">The duration of the EMP effects.</param>
+ public void EmpPulse(EntityCoordinates coordinates, float range, float energyConsumption, float duration)
+ {
+ foreach (var uid in _lookup.GetEntitiesInRange(coordinates, range))
+ {
+ TryEmpEffects(uid, energyConsumption, duration);
+ }
+ Spawn(EmpPulseEffectPrototype, coordinates);
+ }
+
/// <summary>
/// Attempts to apply the effects of an EMP pulse onto an entity by first raising an <see cref="EmpAttemptEvent"/>, followed by raising a <see cref="EmpPulseEvent"/> on it.
/// </summary>
+++ /dev/null
-using Content.Server.Xenoarchaeology.XenoArtifacts;
-using Content.Shared.Chemistry.Reagent;
-using Robust.Shared.Prototypes;
-using Content.Shared.EntityEffects;
-
-namespace Content.Server.EntityEffects.Effects;
-
-public sealed partial class ActivateArtifact : EntityEffect
-{
- public override void Effect(EntityEffectBaseArgs args)
- {
- var artifact = args.EntityManager.EntitySysManager.GetEntitySystem<ArtifactSystem>();
- artifact.TryActivateArtifact(args.TargetEntity, logMissing: false);
- }
-
- protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
- Loc.GetString("reagent-effect-guidebook-activate-artifact", ("chance", Probability));
-}
-using Content.Server.UserInterface;
using Content.Shared.Instruments;
using Robust.Shared.Player;
using ActivatableUIComponent = Content.Shared.UserInterface.ActivatableUIComponent;
_entMan.GetComponentOrNull<ActivatableUIComponent>(Owner)?.CurrentSingleUser
?? _entMan.GetComponentOrNull<ActorComponent>(Owner)?.PlayerSession.AttachedEntity;
}
-
-[RegisterComponent]
-public sealed partial class ActiveInstrumentComponent : Component
-{
-}
using Content.Shared.Popups;
using Robust.Shared.Random;
using System.Text;
+using Content.Shared.Instruments;
using Robust.Shared.Player;
namespace Content.Server.PAI;
-using Content.Server.Radiation.Components;
+using Content.Server.Radiation.Components;
using Content.Shared.Radiation.Components;
using Content.Shared.Radiation.Events;
using Content.Shared.Stacks;
public void IrradiateEntity(EntityUid uid, float radsPerSecond, float time)
{
- var msg = new OnIrradiatedEvent(time, radsPerSecond);
+ var msg = new OnIrradiatedEvent(time, radsPerSecond, uid);
RaiseLocalEvent(uid, msg);
}
using JetBrains.Annotations;
using Robust.Shared.Random;
using System.Linq;
+using Content.Shared.Chemistry.Reaction;
namespace Content.Server.StationEvents.Events;
var quantity = weak ? component.WeakReagentQuantity : component.ReagentQuantity;
solution.AddReagent(reagent, quantity);
- var foamEnt = Spawn("Foam", transform.Coordinates);
+ var foamEnt = Spawn(ChemicalReactionSystem.FoamReaction, transform.Coordinates);
var spreadAmount = weak ? component.WeakSpread : component.Spread;
_smoke.StartSmoke(foamEnt, solution, component.Time, spreadAmount);
Audio.PlayPvs(component.Sound, transform.Coordinates);
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
using Content.Shared.Item;
+using Content.Shared.Xenoarchaeology.Artifact;
using Content.Shared.Xenoarchaeology.XenoArtifacts;
using Robust.Server.GameObjects;
using Robust.Shared.Random;
using Robust.Shared.Timing;
-namespace Content.Server.Xenoarchaeology.XenoArtifacts;
+namespace Content.Server.Xenoarchaeology.Artifact;
public sealed class RandomArtifactSpriteSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
+
SubscribeLocalEvent<RandomArtifactSpriteComponent, MapInitEvent>(OnMapInit);
- SubscribeLocalEvent<RandomArtifactSpriteComponent, ArtifactActivatedEvent>(OnActivated);
+ SubscribeLocalEvent<RandomArtifactSpriteComponent, ArtifactUnlockingStartedEvent>(UnlockingStageStarted);
+ SubscribeLocalEvent<RandomArtifactSpriteComponent, ArtifactUnlockingFinishedEvent>(UnlockingStageFinished);
+ SubscribeLocalEvent<RandomArtifactSpriteComponent, XenoArtifactActivatedEvent>(ArtifactActivated);
}
public override void Update(float frameTime)
_item.SetHeldPrefix(uid, "ano" + randomSprite.ToString("D2")); //set item artifact inhands
}
- private void OnActivated(EntityUid uid, RandomArtifactSpriteComponent component, ArtifactActivatedEvent args)
+ private void UnlockingStageStarted(Entity<RandomArtifactSpriteComponent> ent, ref ArtifactUnlockingStartedEvent args)
+ {
+ _appearance.SetData(ent, SharedArtifactsVisuals.IsUnlocking, true);
+ }
+
+ private void UnlockingStageFinished(Entity<RandomArtifactSpriteComponent> ent, ref ArtifactUnlockingFinishedEvent args)
+ {
+ _appearance.SetData(ent, SharedArtifactsVisuals.IsUnlocking, false);
+ }
+
+ private void ArtifactActivated(Entity<RandomArtifactSpriteComponent> ent, ref XenoArtifactActivatedEvent args)
{
- _appearance.SetData(uid, SharedArtifactsVisuals.IsActivated, true);
- component.ActivationStart = _time.CurTime;
+ _appearance.SetData(ent, SharedArtifactsVisuals.IsActivated, true);
+ ent.Comp.ActivationStart = _time.CurTime;
}
}
--- /dev/null
+namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+
+/// <summary>
+/// This is used for recharging all nearby batteries when activated.
+/// </summary>
+[RegisterComponent, Access(typeof(XAEChargeBatterySystem))]
+public sealed partial class XAEChargeBatteryComponent : Component
+{
+ /// <summary>
+ /// The radius of entities that will be affected.
+ /// </summary>
+ [DataField("radius")]
+ public float Radius = 15f;
+}
--- /dev/null
+using Content.Shared.Atmos;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+
+/// <summary>
+/// XenoArtifact effect that creates gas in atmosphere.
+/// </summary>
+[RegisterComponent, Access(typeof(XAECreateGasSystem))]
+public sealed partial class XAECreateGasComponent : Component
+{
+ /// <summary>
+ /// The gases and how many moles will be created of each.
+ /// </summary>
+ [DataField]
+ public Dictionary<Gas, float> Gases = new();
+}
--- /dev/null
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Destructible.Thresholds;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+
+/// <summary>
+/// This is used for an artifact that creates a puddle of
+/// random chemicals upon being triggered.
+/// </summary>
+[RegisterComponent, Access(typeof(XAECreatePuddleSystem))]
+public sealed partial class XAECreatePuddleComponent : Component
+{
+ /// <summary>
+ /// The solution where all the chemicals are stored.
+ /// </summary>
+ [DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
+ public Solution ChemicalSolution = default!;
+
+ /// <summary>
+ /// The different chemicals that can be spawned by this effect.
+ /// </summary>
+ [DataField]
+ public List<ProtoId<ReagentPrototype>> PossibleChemicals = new();
+
+ /// <summary>
+ /// The number of chemicals in the puddle.
+ /// </summary>
+ [DataField]
+ public MinMax ChemAmount = new MinMax(1, 3);
+
+ /// <summary>
+ /// List of reagents selected for this node. Selected ones are chosen on first activation
+ /// and are picked from <see cref="PossibleChemicals"/> and is calculated separately for each node.
+ /// </summary>
+ [DataField]
+ public List<ProtoId<ReagentPrototype>>? SelectedChemicals;
+
+ /// <summary>
+ /// Marker, if entity where this component is placed should have description replaced with selected chemicals
+ /// on component init.
+ /// </summary>
+ [DataField]
+ public bool ReplaceDescription;
+}
--- /dev/null
+namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+
+/// <summary>
+/// Effect of EMP on activation.
+/// </summary>
+[RegisterComponent, Access(typeof(XAEEmpInAreaSystem))]
+public sealed partial class XAEEmpInAreaComponent : Component
+{
+ /// <summary>
+ /// Range of EMP effect.
+ /// </summary>
+ [DataField]
+ public float Range = 4f;
+
+ /// <summary>
+ /// Energy to be consumed from energy containers.
+ /// </summary>
+ [DataField]
+ public float EnergyConsumption = 1000000;
+
+ /// <summary>
+ /// Duration (in seconds) for which devices going to be disabled.
+ /// </summary>
+ [DataField]
+ public float DisableDuration = 60f;
+}
--- /dev/null
+using Content.Shared.Chemistry.Reagent;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+
+/// <summary>
+/// Generates foam from the artifact when activated.
+/// </summary>
+[RegisterComponent, Access(typeof(XAEFoamSystem))]
+public sealed partial class XAEFoamComponent : Component
+{
+ /// <summary>
+ /// The list of reagents that will randomly be picked from
+ /// to choose the foam reagent.
+ /// </summary>
+ [DataField(required: true)]
+ public List<ProtoId<ReagentPrototype>> Reagents = new();
+
+ /// <summary>
+ /// The foam reagent.
+ /// </summary>
+ [DataField]
+ public string? SelectedReagent;
+
+ /// <summary>
+ /// How long does the foam last?
+ /// </summary>
+ [DataField]
+ public float Duration = 10f;
+
+ /// <summary>
+ /// How much reagent is in the foam?
+ /// </summary>
+ [DataField]
+ public float ReagentAmount = 100f;
+
+ /// <summary>
+ /// Minimum radius of foam spawned.
+ /// </summary>
+ [DataField]
+ public int MinFoamAmount = 15;
+
+ /// <summary>
+ /// Maximum radius of foam spawned.
+ /// </summary>
+ [DataField]
+ public int MaxFoamAmount = 20;
+
+ /// <summary>
+ /// Marker, if entity where this component is placed should have description replaced with selected chemicals
+ /// on component init.
+ /// </summary>
+ [DataField]
+ public bool ReplaceDescription;
+}
+
--- /dev/null
+using Content.Shared.Destructible.Thresholds;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+
+/// <summary>
+/// Artifact that ignites surrounding entities when triggered.
+/// </summary>
+[RegisterComponent, Access(typeof(XAEIgniteSystem))]
+public sealed partial class XAEIgniteComponent : Component
+{
+ /// <summary>
+ /// Range, inside which all entities going be set on fire.
+ /// </summary>
+ [DataField]
+ public float Range = 2f;
+
+ /// <summary>
+ /// Amount of fire stacks to apply
+ /// </summary>
+ [DataField]
+ public MinMax FireStack = new(2, 5);
+}
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
/// <summary>
/// Flickers all the lights within a certain radius.
/// </summary>
-[RegisterComponent]
-public sealed partial class LightFlickerArtifactComponent : Component
+[RegisterComponent, Access(typeof(XAELightFlickerSystem))]
+public sealed partial class XAELightFlickerComponent : Component
{
/// <summary>
- /// Lights within this radius will be flickered on activation
+ /// Lights within this radius will be flickered on activation.
/// </summary>
- [DataField("radius")]
+ [DataField]
public float Radius = 4;
/// <summary>
- /// The chance that the light will flicker
+ /// The chance that the light will flicker.
/// </summary>
- [DataField("flickerChance")]
+ [DataField]
public float FlickerChance = 0.75f;
}
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-using Robust.Shared.Audio;
using Content.Shared.Polymorph;
+using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
/// <summary>
-/// Artifact polymorphs surrounding entities when triggered.
+/// Artifact polymorphs entities when triggered.
/// </summary>
-[RegisterComponent]
-[Access(typeof(PolyOthersArtifactSystem))]
-public sealed partial class PolyOthersArtifactComponent : Component
+[RegisterComponent, Access(typeof(XAEPolymorphSystem))]
+public sealed partial class XAEPolymorphComponent : Component
{
/// <summary>
/// The polymorph effect to trigger.
public ProtoId<PolymorphPrototype> PolymorphPrototypeName = "ArtifactMonkey";
/// <summary>
- /// range of the effect.
+ /// Range of the effect.
/// </summary>
[DataField]
public float Range = 2f;
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
/// <summary>
/// Harmless artifact that broadcast "thoughts" to players nearby.
/// Thoughts are shown as popups and unique for each player.
/// </summary>
-[RegisterComponent]
-public sealed partial class TelepathicArtifactComponent : Component
+[RegisterComponent, Access(typeof(XAETelepathicSystem))]
+public sealed partial class XAETelepathicComponent : Component
{
/// <summary>
/// Loc string ids of telepathic messages.
using Content.Shared.Atmos;
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
/// <summary>
/// Change atmospherics temperature until it reach target.
/// </summary>
-[RegisterComponent]
-public sealed partial class TemperatureArtifactComponent : Component
+[RegisterComponent, Access(typeof(XAETemperatureSystem))]
+public sealed partial class XAETemperatureComponent : Component
{
[DataField("targetTemp"), ViewVariables(VVAccess.ReadWrite)]
public float TargetTemperature = Atmospherics.T0C;
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
/// <summary>
/// Throws all nearby entities backwards.
/// Also pries nearby tiles.
/// </summary>
-[RegisterComponent]
-public sealed partial class ThrowArtifactComponent : Component
+[RegisterComponent, Access(typeof(XAEThrowThingsAroundSystem))]
+public sealed partial class XAEThrowThingsAroundComponent : Component
{
/// <summary>
/// How close do you have to be to get yeeted?
--- /dev/null
+using Content.Shared.Explosion.Components.OnTrigger;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+
+/// <summary>
+/// Activates 'trigger' for <see cref="ExplodeOnTriggerComponent"/>.
+/// </summary>
+[RegisterComponent, Access(typeof(XAETriggerExplosivesSystem))]
+public sealed partial class XAETriggerExplosivesComponent : Component;
--- /dev/null
+using Content.Server.Power.Components;
+using Content.Server.Power.EntitySystems;
+using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.XAE;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact activation effect that is fully charging batteries in certain range.
+/// </summary>
+public sealed class XAEChargeBatterySystem : BaseXAESystem<XAEChargeBatteryComponent>
+{
+ [Dependency] private readonly BatterySystem _battery = default!;
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+
+ /// <summary> Pre-allocated and re-used collection.</summary>
+ private readonly HashSet<Entity<BatteryComponent>> _batteryEntities = new();
+
+ /// <inheritdoc />
+ protected override void OnActivated(Entity<XAEChargeBatteryComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+ {
+ var chargeBatteryComponent = ent.Comp;
+ _batteryEntities.Clear();
+ _lookup.GetEntitiesInRange(args.Coordinates, chargeBatteryComponent.Radius, _batteryEntities);
+ foreach (var battery in _batteryEntities)
+ {
+ _battery.SetCharge(battery, battery.Comp.MaxCharge, battery);
+ }
+ }
+}
--- /dev/null
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+using Content.Shared.Atmos;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.XAE;
+using Robust.Server.GameObjects;
+using Robust.Shared.Collections;
+using Robust.Shared.Map.Components;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact effect that creates certain atmospheric gas on artifact tile / adjacent tiles.
+/// </summary>
+public sealed class XAECreateGasSystem : BaseXAESystem<XAECreateGasComponent>
+{
+ [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
+ [Dependency] private readonly TransformSystem _transform = default!;
+ [Dependency] private readonly MapSystem _map = default!;
+
+ protected override void OnActivated(Entity<XAECreateGasComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+ {
+ var grid = _transform.GetGrid(args.Coordinates);
+ var map = _transform.GetMap(args.Coordinates);
+ if (map == null || !TryComp<MapGridComponent>(grid, out var gridComp))
+ return;
+
+ var tile = _map.LocalToTile(grid.Value, gridComp, args.Coordinates);
+
+ var mixtures = new ValueList<GasMixture>();
+ if (_atmosphere.GetTileMixture(grid.Value, map.Value, tile, excite: true) is { } localMixture)
+ mixtures.Add(localMixture);
+
+ if (_atmosphere.GetAdjacentTileMixtures(grid.Value, tile, excite: true) is var adjacentTileMixtures)
+ {
+ while (adjacentTileMixtures.MoveNext(out var adjacentMixture))
+ {
+ mixtures.Add(adjacentMixture);
+ }
+ }
+
+ foreach (var (gas, moles) in ent.Comp.Gases)
+ {
+ var molesPerMixture = moles / mixtures.Count;
+
+ foreach (var mixture in mixtures)
+ {
+ mixture.AdjustMoles(gas, molesPerMixture);
+ }
+ }
+ }
+}
--- /dev/null
+using Content.Server.Fluids.EntitySystems;
+using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.XAE;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact effect that creates puddle of chemical reagents under artifact.
+/// </summary>
+public sealed class XAECreatePuddleSystem: BaseXAESystem<XAECreatePuddleComponent>
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly PuddleSystem _puddle = default!;
+ [Dependency] private readonly MetaDataSystem _metaData= default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager= default!;
+
+ /// <inheritdoc />
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<XAECreatePuddleComponent, MapInitEvent>(OnInit);
+ }
+
+ private void OnInit(EntityUid uid, XAECreatePuddleComponent component, MapInitEvent _)
+ {
+ if (component.PossibleChemicals == null || component.PossibleChemicals.Count == 0)
+ return;
+
+ if (component.SelectedChemicals == null)
+ {
+ var chemicalList = new List<ProtoId<ReagentPrototype>>();
+ var chemAmount = component.ChemAmount.Next(_random);
+ for (var i = 0; i < chemAmount; i++)
+ {
+ var chemProto = _random.Pick(component.PossibleChemicals);
+ chemicalList.Add(chemProto);
+ }
+
+ component.SelectedChemicals = chemicalList;
+ }
+
+ if (component.ReplaceDescription)
+ {
+ var reagentNames = new HashSet<string>();
+ foreach (var chemProtoId in component.SelectedChemicals)
+ {
+ var reagent = _prototypeManager.Index(chemProtoId);
+ reagentNames.Add(reagent.LocalizedName);
+ }
+
+ var reagentNamesStr = string.Join(", ", reagentNames);
+ var newEntityDescription = Loc.GetString("xenoarch-effect-puddle", ("reagent", reagentNamesStr));
+ _metaData.SetEntityDescription(uid, newEntityDescription);
+ }
+ }
+
+ /// <inheritdoc />
+ protected override void OnActivated(Entity<XAECreatePuddleComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+ {
+ var component = ent.Comp;
+ if (component.SelectedChemicals == null)
+ return;
+
+ var amountPerChem = component.ChemicalSolution.MaxVolume / component.SelectedChemicals.Count;
+ foreach (var reagent in component.SelectedChemicals)
+ {
+ component.ChemicalSolution.AddReagent(reagent, amountPerChem);
+ }
+
+ _puddle.TrySpillAt(ent, component.ChemicalSolution, out _);
+ }
+}
--- /dev/null
+using Content.Server.Emp;
+using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.XAE;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact effect that creates EMP on use.
+/// </summary>
+public sealed class XAEEmpInAreaSystem : BaseXAESystem<XAEEmpInAreaComponent>
+{
+ [Dependency] private readonly EmpSystem _emp = default!;
+
+ /// <inheritdoc />
+ protected override void OnActivated(Entity<XAEEmpInAreaComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+ {
+ _emp.EmpPulse(args.Coordinates, ent.Comp.Range, ent.Comp.EnergyConsumption, ent.Comp.DisableDuration);
+ }
+}
--- /dev/null
+using Content.Server.Fluids.EntitySystems;
+using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.Reaction;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.XAE;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact effect that starts Foam chemical reaction with random-ish reagents inside.
+/// </summary>
+public sealed class XAEFoamSystem : BaseXAESystem<XAEFoamComponent>
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SmokeSystem _smoke = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager= default!;
+ [Dependency] private readonly MetaDataSystem _metaData = default!;
+
+ /// <inheritdoc />
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<XAEFoamComponent, MapInitEvent>(OnMapInit);
+ }
+
+ private void OnMapInit(EntityUid uid, XAEFoamComponent component, MapInitEvent args)
+ {
+ if (component.SelectedReagent != null)
+ return;
+
+ if (component.Reagents.Count == 0)
+ return;
+
+ component.SelectedReagent = _random.Pick(component.Reagents);
+
+ if (component.ReplaceDescription)
+ {
+ var reagent = _prototypeManager.Index<ReagentPrototype>(component.SelectedReagent);
+ var newEntityDescription = Loc.GetString("xenoarch-effect-foam", ("reagent", reagent.LocalizedName));
+ _metaData.SetEntityDescription(uid, newEntityDescription);
+ }
+ }
+
+ /// <inheritdoc />
+ protected override void OnActivated(Entity<XAEFoamComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+ {
+ var component = ent.Comp;
+ if (component.SelectedReagent == null)
+ return;
+
+ var sol = new Solution();
+ var range = (int)MathF.Round(MathHelper.Lerp(component.MinFoamAmount, component.MaxFoamAmount, _random.NextFloat(0, 1f)));
+ sol.AddReagent(component.SelectedReagent, component.ReagentAmount);
+ var foamEnt = Spawn(ChemicalReactionSystem.FoamReaction, args.Coordinates);
+ var spreadAmount = range * 4;
+ _smoke.StartSmoke(foamEnt, sol, component.Duration, spreadAmount);
+ }
+}
--- /dev/null
+using Content.Server.Atmos.Components;
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.XAE;
+using Robust.Shared.Random;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact activation effect that ignites any flammable entity in range.
+/// </summary>
+public sealed class XAEIgniteSystem : BaseXAESystem<XAEIgniteComponent>
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+ [Dependency] private readonly FlammableSystem _flammable = default!;
+
+ private EntityQuery<FlammableComponent> _flammables;
+
+ /// <summary> Pre-allocated and re-used collection.</summary>
+ private readonly HashSet<EntityUid> _entities = new();
+
+ /// <inheritdoc />
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _flammables = GetEntityQuery<FlammableComponent>();
+ }
+
+ /// <inheritdoc />
+ protected override void OnActivated(Entity<XAEIgniteComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+ {
+ var component = ent.Comp;
+ _entities.Clear();
+ _lookup.GetEntitiesInRange(ent.Owner, component.Range, _entities);
+ foreach (var target in _entities)
+ {
+ if (!_flammables.TryGetComponent(target, out var fl))
+ continue;
+
+ fl.FireStacks += component.FireStack.Next(_random);
+ _flammable.Ignite(target, ent.Owner, fl);
+ }
+ }
+}
--- /dev/null
+using Content.Server.Ghost;
+using Content.Server.Light.Components;
+using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.XAE;
+using Robust.Shared.Random;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact activation effect that flickers light on and off.
+/// </summary>
+public sealed class XAELightFlickerSystem : BaseXAESystem<XAELightFlickerComponent>
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+ [Dependency] private readonly GhostSystem _ghost = default!;
+
+ private EntityQuery<PoweredLightComponent> _lights;
+
+ /// <summary> Pre-allocated and re-used collection.</summary>
+ private readonly HashSet<EntityUid> _entities = new();
+
+ /// <inheritdoc />
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _lights = GetEntityQuery<PoweredLightComponent>();
+ }
+
+ /// <inheritdoc />
+ protected override void OnActivated(Entity<XAELightFlickerComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+ {
+ _entities.Clear();
+ _lookup.GetEntitiesInRange(ent.Owner, ent.Comp.Radius, _entities, LookupFlags.StaticSundries);
+ foreach (var light in _entities)
+ {
+ if (!_lights.HasComponent(light))
+ continue;
+
+ if (!_random.Prob(ent.Comp.FlickerChance))
+ continue;
+
+ //todo: extract effect from ghost system, update power system accordingly
+ _ghost.DoGhostBooEvent(light);
+ }
+ }
+}
--- /dev/null
+using Content.Server.Polymorph.Systems;
+using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+using Content.Shared.Humanoid;
+using Content.Shared.Mobs.Systems;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.XAE;
+using Robust.Shared.Audio.Systems;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact activation effect that is polymorphing all humanoid entities in range.
+/// </summary>
+public sealed class XAEPolymorphSystem : BaseXAESystem<XAEPolymorphComponent>
+{
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+ [Dependency] private readonly MobStateSystem _mob = default!;
+ [Dependency] private readonly PolymorphSystem _poly = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+
+ /// <summary> Pre-allocated and re-used collection.</summary>
+ private readonly HashSet<Entity<HumanoidAppearanceComponent>> _humanoids = new();
+
+ /// <inheritdoc />
+ protected override void OnActivated(Entity<XAEPolymorphComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+ {
+ _humanoids.Clear();
+ _lookup.GetEntitiesInRange(args.Coordinates, ent.Comp.Range, _humanoids);
+ foreach (var comp in _humanoids)
+ {
+ var target = comp.Owner;
+ if (!_mob.IsAlive(target))
+ continue;
+
+ _poly.PolymorphEntity(target, ent.Comp.PolymorphPrototypeName);
+ _audio.PlayPvs(ent.Comp.PolySound, ent);
+ }
+ }
+}
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
+using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
using Content.Shared.Popups;
-using Robust.Server.GameObjects;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.XAE;
using Robust.Shared.Player;
using Robust.Shared.Random;
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
+namespace Content.Server.Xenoarchaeology.Artifact.XAE;
-public sealed class TelepathicArtifactSystem : EntitySystem
+/// <summary>
+/// System for xeno artifact activation effect that sends sublime telepathic messages.
+/// </summary>
+public sealed class XAETelepathicSystem : BaseXAESystem<XAETelepathicComponent>
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<TelepathicArtifactComponent, ArtifactActivatedEvent>(OnActivate);
- }
+ /// <summary> Pre-allocated and re-used collection.</summary>
+ private readonly HashSet<EntityUid> _entities = new();
- private void OnActivate(EntityUid uid, TelepathicArtifactComponent component, ArtifactActivatedEvent args)
+ /// <inheritdoc />
+ protected override void OnActivated(Entity<XAETelepathicComponent> ent, ref XenoArtifactNodeActivatedEvent args)
{
+ var component = ent.Comp;
// try to find victims nearby
- var victims = _lookup.GetEntitiesInRange(uid, component.Range);
- foreach (var victimUid in victims)
+ _entities.Clear();
+ _lookup.GetEntitiesInRange(ent, component.Range, _entities);
+ foreach (var victimUid in _entities)
{
- if (!EntityManager.HasComponent<ActorComponent>(victimUid))
+ if (!HasComp<ActorComponent>(victimUid))
continue;
// roll if msg should be usual or drastic
--- /dev/null
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+using Content.Shared.Atmos;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.XAE;
+using Robust.Server.GameObjects;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact effect that changes atmospheric temperature on adjacent tiles.
+/// </summary>
+public sealed class XAETemperatureSystem : BaseXAESystem<XAETemperatureComponent>
+{
+ [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
+ [Dependency] private readonly TransformSystem _transformSystem = default!;
+
+ /// <inheritdoc />
+ protected override void OnActivated(Entity<XAETemperatureComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+ {
+ var component = ent.Comp;
+ var transform = Transform(ent);
+
+ var center = _atmosphereSystem.GetContainingMixture(ent.Owner, false, true);
+ if (center == null)
+ return;
+
+ UpdateTileTemperature(component, center);
+
+ if (component.AffectAdjacentTiles && transform.GridUid != null)
+ {
+ var position = _transformSystem.GetGridOrMapTilePosition(ent, transform);
+ var enumerator = _atmosphereSystem.GetAdjacentTileMixtures(transform.GridUid.Value, position, excite: true);
+
+ while (enumerator.MoveNext(out var mixture))
+ {
+ UpdateTileTemperature(component, mixture);
+ }
+ }
+ }
+
+ private void UpdateTileTemperature(XAETemperatureComponent component, GasMixture environment)
+ {
+ var dif = component.TargetTemperature - environment.Temperature;
+ var absDif = Math.Abs(dif);
+ var step = Math.Min(absDif, component.SpawnTemperature);
+ environment.Temperature += dif > 0 ? step : -step;
+ }
+}
--- /dev/null
+using System.Numerics;
+using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+using Content.Shared.Maps;
+using Content.Shared.Physics;
+using Content.Shared.Throwing;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.XAE;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Random;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact activation effect that pries tiles and throws stuff around.
+/// </summary>
+public sealed class XAEThrowThingsAroundSystem : BaseXAESystem<XAEThrowThingsAroundComponent>
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+ [Dependency] private readonly ThrowingSystem _throwing = default!;
+ [Dependency] private readonly TileSystem _tile = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+ [Dependency] private readonly SharedMapSystem _map = default!;
+
+ private EntityQuery<PhysicsComponent> _physQuery;
+
+ /// <summary> Pre-allocated and re-used collection.</summary>
+ private readonly HashSet<EntityUid> _entities = new();
+
+ /// <inheritdoc />
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _physQuery = GetEntityQuery<PhysicsComponent>();
+ }
+
+ /// <inheritdoc />
+ protected override void OnActivated(Entity<XAEThrowThingsAroundComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+ {
+ var component = ent.Comp;
+ var xform = Transform(ent);
+ if (TryComp<MapGridComponent>(xform.GridUid, out var grid))
+ {
+ var areaForTilesPry = new Circle(_transform.GetWorldPosition(xform), component.Range);
+ var tiles = _map.GetTilesIntersecting(xform.GridUid.Value, grid, areaForTilesPry, true);
+
+ foreach (var tile in tiles)
+ {
+ if (!_random.Prob(component.TilePryChance))
+ continue;
+
+ _tile.PryTile(tile);
+ }
+ }
+
+ _entities.Clear();
+ _lookup.GetEntitiesInRange(ent, component.Range, _entities, LookupFlags.Dynamic | LookupFlags.Sundries);
+ foreach (var entity in _entities)
+ {
+ if (_physQuery.TryGetComponent(entity, out var phys)
+ && (phys.CollisionMask & (int)CollisionGroup.GhostImpassable) != 0)
+ continue;
+
+ var tempXform = Transform(entity);
+
+ var foo = _transform.GetWorldPosition(tempXform) - _transform.GetWorldPosition(xform);
+ _throwing.TryThrow(entity, foo * 2, component.ThrowStrength, ent, 0);
+ }
+ }
+}
--- /dev/null
+using Content.Server.Explosion.EntitySystems;
+using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+using Content.Shared.Explosion.Components;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.XAE;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact effect of triggering explosion.
+/// </summary>
+public sealed class XAETriggerExplosivesSystem : BaseXAESystem<XAETriggerExplosivesComponent>
+{
+ [Dependency] private readonly ExplosionSystem _explosion = default!;
+
+ /// <inheritdoc />
+ protected override void OnActivated(Entity<XAETriggerExplosivesComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+ {
+ if(!TryComp<ExplosiveComponent>(ent, out var explosiveComp))
+ return;
+
+ _explosion.TriggerExplosive(ent, explosiveComp);
+ }
+}
--- /dev/null
+using Content.Shared.Atmos;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used for an artifact that is activated by having a certain amount of gas around it.
+/// </summary>
+[RegisterComponent, Access(typeof(XATGasSystem))]
+public sealed partial class XATGasComponent : Component
+{
+ /// <summary>
+ /// The gas that is related to trigger.
+ /// </summary>
+ [DataField]
+ public Gas TargetGas;
+
+ /// <summary>
+ /// The amount of gas needed.
+ /// </summary>
+ [DataField]
+ public float Moles = Atmospherics.MolesCellStandard * 0.1f;
+
+ /// <summary>
+ /// Marker, if mentioned gas should be present in entity tile for trigger to activate, or it should not.
+ /// </summary>
+ [DataField]
+ public bool ShouldBePresent = true;
+}
--- /dev/null
+namespace Content.Server.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// Component for triggering node on getting activated by powerful magnets.
+/// </summary>
+[RegisterComponent, Access(typeof(XATMagnetSystem))]
+public sealed partial class XATMagnetComponent : Component
+{
+ /// <summary>
+ /// How close to the magnet do you have to be?
+ /// </summary>
+ [DataField]
+ public float MagnetRange = 40f;
+
+ /// <summary>
+ /// How close do active magboots have to be?
+ /// This is smaller because they are weaker magnets
+ /// </summary>
+ [DataField]
+ public float MagbootsRange = 2f;
+}
--- /dev/null
+namespace Content.Server.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used for an artifact that activates when above or below a certain pressure.
+/// </summary>
+[RegisterComponent, Access(typeof(XATPressureSystem))]
+public sealed partial class XATPressureComponent : Component
+{
+ /// <summary>
+ /// The lower-end pressure threshold. Is not considered when null.
+ /// </summary>
+ [DataField]
+ public float? MinPressureThreshold;
+
+ /// <summary>
+ /// The higher-end pressure threshold. Is not considered when null.
+ /// </summary>
+ [DataField]
+ public float? MaxPressureThreshold;
+}
--- /dev/null
+namespace Content.Server.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used for an artifact that is activated by having a certain temperature near it.
+/// </summary>
+[RegisterComponent, Access(typeof(XATTemperatureSystem))]
+public sealed partial class XATTemperatureComponent : Component
+{
+ /// <summary>
+ /// Threshold temperature for trigger activation.
+ /// </summary>
+ [DataField]
+ public float TargetTemperature;
+
+ /// <summary>
+ /// Marker, if temp needs to be above or below the target.
+ /// </summary>
+ [DataField]
+ public bool TriggerOnHigherTemp = true;
+}
--- /dev/null
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Xenoarchaeology.Artifact.XAT.Components;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// System for xeno artifact trigger, which gets activated from some gas being on the same time as artifact with certain concentration.
+/// </summary>
+public sealed class XATGasSystem : BaseQueryUpdateXATSystem<XATGasComponent>
+{
+ [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
+
+ protected override void UpdateXAT(Entity<XenoArtifactComponent> artifact, Entity<XATGasComponent, XenoArtifactNodeComponent> node, float frameTime)
+ {
+ var xform = Transform(artifact);
+
+ if (_atmosphere.GetTileMixture((artifact, xform)) is not { } mixture)
+ return;
+
+ var gasTrigger = node.Comp1;
+ var moles = mixture.GetMoles(gasTrigger.TargetGas);
+
+ if (gasTrigger.ShouldBePresent)
+ {
+ if (moles >= gasTrigger.Moles)
+ Trigger(artifact, node);
+ }
+ else
+ {
+ if (moles <= gasTrigger.Moles)
+ Trigger(artifact, node);
+ }
+ }
+}
--- /dev/null
+using Content.Server.Salvage;
+using Content.Server.Xenoarchaeology.Artifact.XAT.Components;
+using Content.Shared.Clothing;
+using Content.Shared.Item.ItemToggle.Components;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// System for checking if magnets-related xeno artifact node should be triggered.
+/// Works with magboots and salvage magnet, salvage magnet triggers only upon pulsing on activation.
+/// </summary>
+public sealed class XATMagnetSystem : BaseQueryUpdateXATSystem<XATMagnetComponent>
+{
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+
+ /// <summary> Pre-allocated and re-used collection.</summary>
+ private HashSet<Entity<MagbootsComponent>> _magbootEntities = new();
+
+ /// <inheritdoc/>
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<SalvageMagnetActivatedEvent>(OnMagnetActivated);
+ }
+
+ /// <inheritdoc />
+ protected override void UpdateXAT(Entity<XenoArtifactComponent> artifact, Entity<XATMagnetComponent, XenoArtifactNodeComponent> node, float frameTime)
+ {
+ var coords = Transform(artifact.Owner).Coordinates;
+
+ _magbootEntities.Clear();
+ _lookup.GetEntitiesInRange(coords, node.Comp1.MagbootsRange, _magbootEntities);
+ foreach (var ent in _magbootEntities)
+ {
+ if(!TryComp<ItemToggleComponent>(ent, out var itemToggle) || !itemToggle.Activated)
+ continue;
+
+ Trigger(artifact, node);
+ break;
+ }
+ }
+
+ private void OnMagnetActivated(ref SalvageMagnetActivatedEvent args)
+ {
+ var magnetCoordinates = Transform(args.Magnet).Coordinates;
+
+ var query = EntityQueryEnumerator<XATMagnetComponent, XenoArtifactNodeComponent>();
+ while (query.MoveNext(out var uid, out var comp, out var node))
+ {
+ if (node.Attached == null)
+ continue;
+
+ var artifact = _xenoArtifactQuery.Get(GetEntity(node.Attached.Value));
+
+ if (!CanTrigger(artifact, (uid, node)))
+ continue;
+
+ var artifactCoordinates = Transform(artifact).Coordinates;
+ if (_transform.InRange(magnetCoordinates, artifactCoordinates, comp.MagnetRange))
+ Trigger(artifact, (uid, comp, node));
+ }
+ }
+}
--- /dev/null
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Xenoarchaeology.Artifact.XAT.Components;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// System for checking if pressure-related xeno artifact node should be triggered.
+/// </summary>
+public sealed class XATPressureSystem : BaseQueryUpdateXATSystem<XATPressureComponent>
+{
+ [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
+
+ /// <inheritdoc />
+ protected override void UpdateXAT(Entity<XenoArtifactComponent> artifact, Entity<XATPressureComponent, XenoArtifactNodeComponent> node, float frameTime)
+ {
+ var xform = Transform(artifact);
+
+ if (_atmosphere.GetTileMixture((artifact, xform)) is not { } mixture)
+ return;
+
+ var pressure = mixture.Pressure;
+ if (pressure >= node.Comp1.MaxPressureThreshold || pressure <= node.Comp1.MinPressureThreshold)
+ {
+ Trigger(artifact, node);
+ }
+ }
+}
--- /dev/null
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Xenoarchaeology.Artifact.XAT.Components;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// System for checking if temperature-related xeno artifact node should be triggered.
+/// </summary>
+public sealed class XATTemperatureSystem : BaseQueryUpdateXATSystem<XATTemperatureComponent>
+{
+ [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
+
+ /// <inheritdoc />
+ protected override void UpdateXAT(Entity<XenoArtifactComponent> artifact, Entity<XATTemperatureComponent, XenoArtifactNodeComponent> node, float frameTime)
+ {
+ var xform = Transform(artifact);
+
+ if (_atmosphere.GetTileMixture((artifact, xform)) is not { } mixture)
+ return;
+
+ var curTemp = mixture.Temperature;
+
+ var temperatureTriggerComponent = node.Comp1;
+ if (temperatureTriggerComponent.TriggerOnHigherTemp)
+ {
+ if (curTemp >= temperatureTriggerComponent.TargetTemperature)
+ Trigger(artifact, node);
+ }
+ else
+ {
+ if (curTemp <= temperatureTriggerComponent.TargetTemperature)
+ Trigger(artifact, node);
+ }
+ }
+}
--- /dev/null
+using System.Text;
+using Content.Server.Administration;
+using Content.Shared.Administration;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Toolshed;
+
+namespace Content.Server.Xenoarchaeology.Artifact;
+
+/// <summary>
+/// Toolshed commands for manipulating xeno artifact.
+/// </summary>
+[ToolshedCommand, AdminCommand(AdminFlags.Debug)]
+public sealed class XenoArtifactCommand : ToolshedCommand
+{
+ [ValidatePrototypeId<EntityPrototype>]
+ public const string ArtifactPrototype = "BaseXenoArtifact";
+
+ /// <summary> List existing artifacts. </summary>
+ [CommandImplementation("list")]
+ public IEnumerable<EntityUid> List()
+ {
+ var query = EntityManager.EntityQueryEnumerator<XenoArtifactComponent>();
+ while (query.MoveNext(out var uid, out _))
+ {
+ yield return uid;
+ }
+ }
+
+ /// <summary>
+ /// Output matrix of artifact nodes and how they are connected.
+ /// </summary>
+ [CommandImplementation("printMatrix")]
+ public string PrintMatrix([PipedArgument] EntityUid artifactEntitUid)
+ {
+ var comp = EntityManager.GetComponent<XenoArtifactComponent>(artifactEntitUid);
+
+ var nodeCount = comp.NodeVertices.Length;
+
+ var sb = new StringBuilder("\n |");
+ for (var i = 0; i < nodeCount; i++)
+ {
+ sb.Append($" {i:D2}|");
+ }
+
+ AddHorizontalFiller(sb);
+
+ for (var i = 0; i < nodeCount; i++)
+ {
+ sb.Append($"\n{i:D2}|");
+ for (var j = 0; j < nodeCount; j++)
+ {
+ var value = comp.NodeAdjacencyMatrix[i][j]
+ ? "X"
+ : " ";
+ sb.Append($" {value} |");
+ }
+ AddHorizontalFiller(sb);
+ }
+
+ return sb.ToString();
+
+ void AddHorizontalFiller(StringBuilder builder)
+ {
+ builder.AppendLine();
+ builder.Append("--+");
+ for (var i = 0; i < nodeCount; i++)
+ {
+ builder.Append($"---+");
+ }
+ }
+ }
+
+ /// <summary> Output total research points artifact contains. </summary>
+ [CommandImplementation("totalResearch")]
+ public int TotalResearch([PipedArgument] EntityUid artifactEntityUid)
+ {
+ var artiSys = EntityManager.System<XenoArtifactSystem>();
+ var comp = EntityManager.GetComponent<XenoArtifactComponent>(artifactEntityUid);
+
+ var sum = 0;
+
+ var nodes = artiSys.GetAllNodes((artifactEntityUid, comp));
+ foreach (var node in nodes)
+ {
+ sum += node.Comp.ResearchValue;
+ }
+
+ return sum;
+ }
+
+ /// <summary>
+ /// Spawns a bunch of artifacts and gets average total research points they can yield.
+ /// </summary>
+ [CommandImplementation("averageResearch")]
+ public float AverageResearch()
+ {
+ const int n = 100;
+ var sum = 0;
+
+ for (var i = 0; i < n; i++)
+ {
+ var ent = Spawn(ArtifactPrototype, MapCoordinates.Nullspace);
+ sum += TotalResearch(ent);
+ Del(ent);
+ }
+
+ return (float) sum / n;
+ }
+
+ /// <summary> Unlocks all nodes of artifact. </summary>
+ [CommandImplementation("unlockAllNodes")]
+ public void UnlockAllNodes([PipedArgument] EntityUid artifactEntityUid)
+ {
+ var artiSys = EntityManager.System<XenoArtifactSystem>();
+ var comp = EntityManager.GetComponent<XenoArtifactComponent>(artifactEntityUid);
+
+ var nodes = artiSys.GetAllNodes((artifactEntityUid, comp));
+ foreach (var node in nodes)
+ {
+ artiSys.SetNodeUnlocked((node, node.Comp));
+ }
+ }
+}
--- /dev/null
+using System.Linq;
+using Content.Shared.Random.Helpers;
+using Content.Shared.Whitelist;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.Prototypes;
+using Robust.Shared.Random;
+
+namespace Content.Server.Xenoarchaeology.Artifact;
+
+public sealed partial class XenoArtifactSystem
+{
+ [Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!;
+
+ private void GenerateArtifactStructure(Entity<XenoArtifactComponent> ent)
+ {
+ var nodeCount = ent.Comp.NodeCount.Next(RobustRandom);
+ var triggerPool = CreateTriggerPool(ent, nodeCount);
+ // trigger pool could be smaller, then requested node count
+ nodeCount = triggerPool.Count;
+ ResizeNodeGraph(ent, nodeCount);
+ while (nodeCount > 0)
+ {
+ GenerateArtifactSegment(ent, triggerPool, ref nodeCount);
+ }
+
+ RebuildXenoArtifactMetaData((ent, ent));
+ }
+
+ /// <summary>
+ /// Creates pool from all node triggers that current artifact can support.
+ /// As artifact cannot re-use triggers, pool will be growing smaller
+ /// and smaller with each node generated.
+ /// </summary>
+ /// <param name="ent">Artifact for which pool should be created.</param>
+ /// <param name="size">
+ /// Max size of pool. Resulting pool is not guaranteed to be exactly as large, but it will 100% won't be bigger.
+ /// </param>
+ private List<XenoArchTriggerPrototype> CreateTriggerPool(Entity<XenoArtifactComponent> ent, int size)
+ {
+ var triggerPool = new List<XenoArchTriggerPrototype>(size);
+ var weightsProto = PrototypeManager.Index(ent.Comp.TriggerWeights);
+ var weightsByTriggersLeft = new Dictionary<string, float>(weightsProto.Weights);
+
+ while (triggerPool.Count < size)
+ {
+ // OOPS! We ran out of triggers.
+ if (weightsByTriggersLeft.Count == 0)
+ {
+ Log.Error($"Insufficient triggers for generating {ToPrettyString(ent)}! Needed {size} but had {triggerPool.Count}");
+ return triggerPool;
+ }
+
+ var triggerId = RobustRandom.Pick(weightsByTriggersLeft);
+ weightsByTriggersLeft.Remove(triggerId);
+ var trigger = PrototypeManager.Index<XenoArchTriggerPrototype>(triggerId);
+ if (_entityWhitelist.IsWhitelistFail(trigger.Whitelist, ent))
+ continue;
+
+ triggerPool.Add(trigger);
+ }
+
+ return triggerPool;
+ }
+
+ /// <summary>
+ /// Generates segment of artifact - isolated graph, nodes inside which are interconnected.
+ /// As size of segment is randomized - it is subtracted from node count.
+ /// </summary>
+ private void GenerateArtifactSegment(
+ Entity<XenoArtifactComponent> ent,
+ List<XenoArchTriggerPrototype> triggerPool,
+ ref int nodeCount
+ )
+ {
+ var segmentSize = GetArtifactSegmentSize(ent, nodeCount);
+ nodeCount -= segmentSize;
+ var populatedNodes = PopulateArtifactSegmentRecursive(ent, triggerPool, ref segmentSize);
+
+ var segments = GetSegmentsFromNodes(ent, populatedNodes).ToList();
+
+ // We didn't connect all of our nodes: do extra work to make sure there's a connection.
+ if (segments.Count > 1)
+ {
+ var parent = segments.MaxBy(s => s.Count)!;
+ var minP = parent.Min(n => n.Comp.Depth);
+ var maxP = parent.Max(n => n.Comp.Depth);
+
+ segments.Remove(parent);
+ foreach (var segment in segments)
+ {
+ // calculate the range of the depth of the nodes in the segment
+ var minS = segment.Min(n => n.Comp.Depth);
+ var maxS = segment.Max(n => n.Comp.Depth);
+
+ // Figure out the range of depths that allows for a connection between these two.
+ // The range is essentially the lower values + 1 on each side.
+ var min = Math.Max(minS, minP) - 1;
+ var max = Math.Min(maxS, maxP) + 1;
+
+ // how the fuck did you do this? you don't even deserve to get a parent. fuck you.
+ if (min > max || min == max)
+ continue;
+
+ var node1Options = segment.Where(n => n.Comp.Depth >= min && n.Comp.Depth <= max)
+ .ToList();
+ if (node1Options.Count == 0)
+ {
+ continue;
+ }
+
+ var node1 = RobustRandom.Pick(node1Options);
+ var node1Depth = node1.Comp.Depth;
+
+ var node2Options = parent.Where(n => n.Comp.Depth >= node1Depth - 1 && n.Comp.Depth <= node1Depth + 1 && n.Comp.Depth != node1Depth)
+ .ToList();
+ if (node2Options.Count == 0)
+ {
+ continue;
+ }
+
+ var node2 = RobustRandom.Pick(node2Options);
+
+ if (node1.Comp.Depth < node2.Comp.Depth)
+ {
+ AddEdge((ent, ent.Comp), node1, node2, false);
+ }
+ else
+ {
+ AddEdge((ent, ent.Comp), node2, node1, false);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Recursively populate layers of artifact segment - isolated graph, nodes inside which are interconnected.
+ /// Each next iteration is going to have more chances to have more nodes (so it goes 'from top to bottom' of
+ /// the tree, creating its peak nodes first, and then making layers with more and more branches).
+ /// </summary>
+ private List<Entity<XenoArtifactNodeComponent>> PopulateArtifactSegmentRecursive(
+ Entity<XenoArtifactComponent> ent,
+ List<XenoArchTriggerPrototype> triggerPool,
+ ref int segmentSize,
+ int iteration = 0
+ )
+ {
+ if (segmentSize == 0)
+ return new();
+
+ // Try and get larger as we create more layers. Prevents excessive layers.
+ var mod = RobustRandom.Next((int) (iteration / 1.5f), iteration + 1);
+
+ var layerMin = Math.Min(ent.Comp.NodesPerSegmentLayer.Min + mod, segmentSize);
+ var layerMax = Math.Min(ent.Comp.NodesPerSegmentLayer.Max + mod, segmentSize);
+
+ // Default to one node if we had shenanigans and ended up with weird layer counts.
+ var nodeCount = 1;
+ if (layerMax >= layerMin)
+ nodeCount = RobustRandom.Next(layerMin, layerMax + 1); // account for non-inclusive max
+
+ segmentSize -= nodeCount;
+ var nodes = new List<Entity<XenoArtifactNodeComponent>>();
+ for (var i = 0; i < nodeCount; i++)
+ {
+ var trigger = RobustRandom.PickAndTake(triggerPool);
+ nodes.Add(CreateNode(ent, trigger, iteration));
+ }
+
+ var successors = PopulateArtifactSegmentRecursive(
+ ent,
+ triggerPool,
+ ref segmentSize,
+ iteration: iteration + 1
+ );
+
+ if (successors.Count == 0)
+ return nodes;
+
+ foreach (var successor in successors)
+ {
+ var node = RobustRandom.Pick(nodes);
+ AddEdge((ent, ent), node, successor, dirty: false);
+ }
+
+ // randomly add in some extra edges for variance.
+ var scatterCount = ent.Comp.ScatterPerLayer.Next(RobustRandom);
+ for (var i = 0; i < scatterCount; i++)
+ {
+ var node = RobustRandom.Pick(nodes);
+ var successor = RobustRandom.Pick(successors);
+ AddEdge((ent, ent), node, successor, dirty: false);
+ }
+
+ return nodes;
+ }
+
+ /// <summary>
+ /// Rolls segment size, based on amount of nodes left and XenoArtifactComponent settings.
+ /// </summary>
+ private int GetArtifactSegmentSize(Entity<XenoArtifactComponent> ent, int nodeCount)
+ {
+ // Make sure we can't generate a single segment artifact.
+ // We always want to have at least 2 segments. For variety.
+ var segmentMin = ent.Comp.SegmentSize.Min;
+ var segmentMax = Math.Min(ent.Comp.SegmentSize.Max, Math.Max(nodeCount / 2, segmentMin));
+
+ var segmentSize = RobustRandom.Next(segmentMin, segmentMax + 1); // account for non-inclusive max
+ var remainder = nodeCount - segmentSize;
+
+ // If our next segment is going to be undersized, then we just absorb it into this segment.
+ if (remainder < ent.Comp.SegmentSize.Min)
+ segmentSize += remainder;
+
+ // Sanity check to make sure we don't exceed the node count. (it shouldn't happen prior anyway but oh well)
+ segmentSize = Math.Min(nodeCount, segmentSize);
+
+ return segmentSize;
+ }
+}
--- /dev/null
+using Content.Server.Cargo.Systems;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+
+namespace Content.Server.Xenoarchaeology.Artifact;
+
+/// <inheritdoc cref="SharedXenoArtifactSystem"/>
+public sealed partial class XenoArtifactSystem : SharedXenoArtifactSystem
+{
+ /// <inheritdoc/>
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<XenoArtifactComponent, MapInitEvent>(OnArtifactMapInit);
+ SubscribeLocalEvent<XenoArtifactComponent, PriceCalculationEvent>(OnCalculatePrice);
+ }
+
+ private void OnArtifactMapInit(Entity<XenoArtifactComponent> ent, ref MapInitEvent args)
+ {
+ if (ent.Comp.IsGenerationRequired)
+ GenerateArtifactStructure(ent);
+ }
+
+ private void OnCalculatePrice(Entity<XenoArtifactComponent> ent, ref PriceCalculationEvent args)
+ {
+ foreach (var node in GetAllNodes(ent))
+ {
+ if (node.Comp.Locked)
+ continue;
+
+ args.Price += node.Comp.ResearchValue * ent.Comp.PriceMultiplier;
+ }
+ }
+}
--- /dev/null
+using Content.Server.Administration;
+using Content.Shared.Administration;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Robust.Shared.Console;
+
+namespace Content.Server.Xenoarchaeology.Artifact;
+
+/// <summary> Command for unlocking specific node of xeno artifact. </summary>
+[AdminCommand(AdminFlags.Debug)]
+public sealed class XenoArtifactUnlockNodeCommand : LocalizedCommands
+{
+ [Dependency] private readonly EntityManager _entities = default!;
+
+ /// <inheritdoc />
+ public override string Command => "unlocknode";
+
+ /// <inheritdoc />
+ public override string Description => Loc.GetString("cmd-unlocknode-desc");
+
+ /// <inheritdoc />
+ public override string Help => Loc.GetString("cmd-unlocknode-help");
+
+ /// <inheritdoc />
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ if (args.Length != 2)
+ {
+ shell.WriteError(Loc.GetString("cmd-parse-failure-unlocknode-arg-num"));
+ return;
+ }
+
+ if (!NetEntity.TryParse(args[1], out var netNode))
+ {
+ shell.WriteError(Loc.GetString("cmd-parse-failure-unlocknode-invalid-entity"));
+ return;
+ }
+
+ if (!_entities.TryGetEntity(netNode, out var entityUid))
+ {
+ shell.WriteError(Loc.GetString("cmd-parse-failure-unlocknode-invalid-entity"));
+ return;
+ }
+ _entities.System<XenoArtifactSystem>()
+ .SetNodeUnlocked(entityUid.Value);
+ }
+
+ /// <inheritdoc />
+ public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
+ {
+ if (args.Length == 1)
+ {
+ var query = _entities.EntityQueryEnumerator<XenoArtifactComponent>();
+ var completionOptions = new List<CompletionOption>();
+ while (query.MoveNext(out var uid, out _))
+ {
+ completionOptions.Add(new CompletionOption(uid.ToString()));
+ }
+
+ return CompletionResult.FromHintOptions(completionOptions, "<artifact uid>");
+ }
+
+ if (args.Length == 2 &&
+ NetEntity.TryParse(args[0], out var netEnt) &&
+ _entities.TryGetEntity(netEnt, out var artifactUid) &&
+ _entities.TryGetComponent<XenoArtifactComponent>(artifactUid, out var comp))
+ {
+ var artifactSystem = _entities.System<XenoArtifactSystem>();
+
+ var result = new List<CompletionOption>();
+ foreach (var node in artifactSystem.GetAllNodes((artifactUid.Value, comp)))
+ {
+ var metaData = _entities.MetaQuery.Comp(artifactUid.Value);
+ var entityUidStr = _entities.GetNetEntity(node)
+ .ToString();
+ var completionOption = new CompletionOption(entityUidStr, metaData.EntityName);
+ result.Add(completionOption);
+ }
+
+ return CompletionResult.FromHintOptions(result, "<node uid>");
+ }
+
+ return CompletionResult.Empty;
+ }
+}
--- /dev/null
+using Content.Server.Research.Systems;
+using Content.Server.Xenoarchaeology.Artifact;
+using Content.Shared.Popups;
+using Content.Shared.Xenoarchaeology.Equipment;
+using Content.Shared.Xenoarchaeology.Equipment.Components;
+using Robust.Shared.Audio.Systems;
+
+namespace Content.Server.Xenoarchaeology.Equipment;
+
+/// <inheritdoc />
+public sealed class ArtifactAnalyzerSystem : SharedArtifactAnalyzerSystem
+{
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly ResearchSystem _research = default!;
+ [Dependency] private readonly XenoArtifactSystem _xenoArtifact = default!;
+
+ /// <inheritdoc/>
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsoleExtractButtonPressedMessage>(OnExtractButtonPressed);
+ }
+
+ private void OnExtractButtonPressed(Entity<AnalysisConsoleComponent> ent, ref AnalysisConsoleExtractButtonPressedMessage args)
+ {
+ if (!TryGetArtifactFromConsole(ent, out var artifact))
+ return;
+
+ if (!_research.TryGetClientServer(ent, out var server, out var serverComponent))
+ return;
+
+ var sumResearch = 0;
+ foreach (var node in _xenoArtifact.GetAllNodes(artifact.Value))
+ {
+ var research = _xenoArtifact.GetResearchValue(node);
+ _xenoArtifact.SetConsumedResearchValue(node, node.Comp.ConsumedResearchValue + research);
+ sumResearch += research;
+ }
+
+ if (sumResearch == 0)
+ return;
+
+ _research.ModifyServerPoints(server.Value, sumResearch, serverComponent);
+ _audio.PlayPvs(ent.Comp.ExtractSound, artifact.Value);
+ _popup.PopupEntity(Loc.GetString("analyzer-artifact-extract-popup"), artifact.Value, PopupType.Large);
+ }
+}
+
+++ /dev/null
-using Robust.Shared.Serialization.TypeSerializers.Implementations;
-
-namespace Content.Server.Xenoarchaeology.Equipment.Components;
-
-/// <summary>
-/// Activecomp used for tracking artifact analyzers that are currently
-/// in the process of scanning an artifact.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ActiveArtifactAnalyzerComponent : Component
-{
- /// <summary>
- /// When did the scanning start or last resume?
- /// </summary>
- [DataField("startTime", customTypeSerializer: typeof(TimespanSerializer))]
- public TimeSpan StartTime;
-
- /// <summary>
- /// When pausing, this will store the duration the scan has already been running for.
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- public TimeSpan AccumulatedRunTime;
-
- /// <summary>
- /// Is analysis paused?
- /// It could be when the Artifact Analyzer has no power, for example.
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- public bool AnalysisPaused = false;
-
- /// <summary>
- /// What is being scanned?
- /// </summary>
- [DataField]
- public EntityUid Artifact;
-}
+++ /dev/null
-using Robust.Shared.Audio;
-
-namespace Content.Server.Xenoarchaeology.Equipment.Components;
-
-/// <summary>
-/// This is used for tracking artifacts that are currently
-/// being scanned by <see cref="ActiveArtifactAnalyzerComponent"/>
-/// </summary>
-[RegisterComponent]
-public sealed partial class ActiveScannedArtifactComponent : Component
-{
- /// <summary>
- /// The scanner that is scanning this artifact
- /// </summary>
- [ViewVariables]
- public EntityUid Scanner;
-
- /// <summary>
- /// The sound that plays when the scan fails
- /// </summary>
- public readonly SoundSpecifier ScanFailureSound = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg");
-}
+++ /dev/null
-using Content.Shared.DeviceLinking;
-using Robust.Shared.Audio;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Server.Xenoarchaeology.Equipment.Components;
-
-/// <summary>
-/// The console that is used for artifact analysis
-/// </summary>
-[RegisterComponent]
-public sealed partial class AnalysisConsoleComponent : Component
-{
- /// <summary>
- /// The analyzer entity the console is linked.
- /// Can be null if not linked.
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- public EntityUid? AnalyzerEntity;
-
- /// <summary>
- /// The machine linking port for the analyzer
- /// </summary>
- [DataField("linkingPort", customTypeSerializer: typeof(PrototypeIdSerializer<SourcePortPrototype>))]
- public string LinkingPort = "ArtifactAnalyzerSender";
-
- /// <summary>
- /// The sound played when an artifact has points extracted.
- /// </summary>
- [DataField("extractSound")]
- public SoundSpecifier ExtractSound = new SoundPathSpecifier("/Audio/Effects/radpulse11.ogg");
-
- /// <summary>
- /// The entity spawned by a report.
- /// </summary>
- [DataField("reportEntityId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
- public string ReportEntityId = "Paper";
-}
+++ /dev/null
-using Content.Server.Xenoarchaeology.XenoArtifacts;
-using Content.Shared.Construction.Prototypes;
-using Robust.Shared.Audio;
-using Robust.Shared.Serialization.TypeSerializers.Implementations;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Server.Xenoarchaeology.Equipment.Components;
-
-/// <summary>
-/// A machine that is combined and linked to the <see cref="AnalysisConsoleComponent"/>
-/// in order to analyze artifacts and extract points.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactAnalyzerComponent : Component
-{
- /// <summary>
- /// How long it takes to analyze an artifact
- /// </summary>
- [DataField("analysisDuration", customTypeSerializer: typeof(TimespanSerializer))]
- public TimeSpan AnalysisDuration = TimeSpan.FromSeconds(30);
-
- /// <summary>
- /// The corresponding console entity.
- /// Can be null if not linked.
- /// </summary>
- [ViewVariables]
- public EntityUid? Console;
-
- [ViewVariables(VVAccess.ReadWrite)]
- public bool ReadyToPrint = false;
-
- [DataField("scanFinishedSound")]
- public SoundSpecifier ScanFinishedSound = new SoundPathSpecifier("/Audio/Machines/scan_finish.ogg");
-
- #region Analysis Data
- [DataField]
- public EntityUid? LastAnalyzedArtifact;
-
- [ViewVariables]
- public ArtifactNode? LastAnalyzedNode;
-
- [ViewVariables(VVAccess.ReadWrite)]
- public int? LastAnalyzerPointValue;
- #endregion
-}
+++ /dev/null
-namespace Content.Server.Xenoarchaeology.Equipment.Components;
-
-/// <summary>
-/// This is used for artifacts that are biased to move
-/// in a particular direction via the <see cref="TraversalDistorterComponent"/>
-/// </summary>
-[RegisterComponent]
-public sealed partial class BiasedArtifactComponent : Component
-{
- [ViewVariables]
- public EntityUid Provider;
-}
+++ /dev/null
-namespace Content.Server.Xenoarchaeology.Equipment.Components;
-
-[RegisterComponent]
-public sealed partial class NodeScannerComponent : Component
-{
-
-}
+++ /dev/null
-namespace Content.Server.Xenoarchaeology.Equipment.Components;
-
-/// <summary>
-/// Suppress artifact activation, when entity is placed inside this container.
-/// </summary>
-[RegisterComponent]
-public sealed partial class SuppressArtifactContainerComponent : Component
-{
-
-}
+++ /dev/null
-namespace Content.Server.Xenoarchaeology.Equipment.Components;
-
-/// <summary>
-/// This is used for a machine that biases
-/// an artifact placed on it to move up/down
-/// </summary>
-[RegisterComponent]
-public sealed partial class TraversalDistorterComponent : Component
-{
- [ViewVariables(VVAccess.ReadWrite)]
- public BiasDirection BiasDirection = BiasDirection.Up;
-
- public TimeSpan NextActivation = default!;
- public TimeSpan ActivationDelay = TimeSpan.FromSeconds(1);
-}
-
-public enum BiasDirection : byte
-{
- Up, //Towards depth 0
- Down, //Away from depth 0
-}
+++ /dev/null
-using System.Linq;
-using Content.Server.Power.Components;
-using Content.Server.Research.Systems;
-using Content.Shared.UserInterface;
-using Content.Server.Xenoarchaeology.Equipment.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Audio;
-using Content.Shared.DeviceLinking;
-using Content.Shared.DeviceLinking.Events;
-using Content.Shared.Paper;
-using Content.Shared.Placeable;
-using Content.Shared.Popups;
-using Content.Shared.Power;
-using Content.Shared.Power.EntitySystems;
-using Content.Shared.Research.Components;
-using Content.Shared.Xenoarchaeology.Equipment;
-using Content.Shared.Xenoarchaeology.XenoArtifacts;
-using JetBrains.Annotations;
-using Robust.Server.GameObjects;
-using Robust.Shared.Audio;
-using Robust.Shared.Audio.Systems;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Timing;
-using Robust.Shared.Utility;
-
-namespace Content.Server.Xenoarchaeology.Equipment.Systems;
-
-/// <summary>
-/// This system is used for managing the artifact analyzer as well as the analysis console.
-/// It also hanadles scanning and ui updates for both systems.
-/// </summary>
-public sealed class ArtifactAnalyzerSystem : EntitySystem
-{
- [Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly IPrototypeManager _prototype = default!;
- [Dependency] private readonly ArtifactSystem _artifact = default!;
- [Dependency] private readonly MetaDataSystem _metaSystem = default!;
- [Dependency] private readonly PaperSystem _paper = default!;
- [Dependency] private readonly ResearchSystem _research = default!;
- [Dependency] private readonly SharedAmbientSoundSystem _ambientSound = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
- [Dependency] private readonly SharedPopupSystem _popup = default!;
- [Dependency] private readonly SharedPowerReceiverSystem _receiver = default!;
- [Dependency] private readonly TraversalDistorterSystem _traversalDistorter = default!;
- [Dependency] private readonly UserInterfaceSystem _ui = default!;
-
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<ActiveScannedArtifactComponent, ArtifactActivatedEvent>(OnArtifactActivated);
-
- SubscribeLocalEvent<ActiveArtifactAnalyzerComponent, ComponentStartup>(OnAnalyzeStart);
- SubscribeLocalEvent<ActiveArtifactAnalyzerComponent, ComponentShutdown>(OnAnalyzeEnd);
- SubscribeLocalEvent<ActiveArtifactAnalyzerComponent, PowerChangedEvent>(OnPowerChanged);
-
- SubscribeLocalEvent<ArtifactAnalyzerComponent, ItemPlacedEvent>(OnItemPlaced);
- SubscribeLocalEvent<ArtifactAnalyzerComponent, ItemRemovedEvent>(OnItemRemoved);
-
- SubscribeLocalEvent<ArtifactAnalyzerComponent, MapInitEvent>(OnMapInit);
- SubscribeLocalEvent<AnalysisConsoleComponent, NewLinkEvent>(OnNewLink);
- SubscribeLocalEvent<AnalysisConsoleComponent, PortDisconnectedEvent>(OnPortDisconnected);
-
- SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsoleServerSelectionMessage>(OnServerSelectionMessage);
- SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsoleScanButtonPressedMessage>(OnScanButton);
- SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsolePrintButtonPressedMessage>(OnPrintButton);
- SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsoleExtractButtonPressedMessage>(OnExtractButton);
- SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsoleBiasButtonPressedMessage>(OnBiasButton);
-
- SubscribeLocalEvent<AnalysisConsoleComponent, ResearchClientServerSelectedMessage>((e, c, _) => UpdateUserInterface(e, c),
- after: new[] { typeof(ResearchSystem) });
- SubscribeLocalEvent<AnalysisConsoleComponent, ResearchClientServerDeselectedMessage>((e, c, _) => UpdateUserInterface(e, c),
- after: new[] { typeof(ResearchSystem) });
- SubscribeLocalEvent<AnalysisConsoleComponent, BeforeActivatableUIOpenEvent>((e, c, _) => UpdateUserInterface(e, c));
- }
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- var query = EntityQueryEnumerator<ActiveArtifactAnalyzerComponent, ArtifactAnalyzerComponent>();
- while (query.MoveNext(out var uid, out var active, out var scan))
- {
- if (active.AnalysisPaused)
- continue;
-
- if (_timing.CurTime - active.StartTime < scan.AnalysisDuration - active.AccumulatedRunTime)
- continue;
-
- FinishScan(uid, scan, active);
- }
- }
-
- /// <summary>
- /// Resets the current scan on the artifact analyzer
- /// </summary>
- /// <param name="uid">The analyzer being reset</param>
- /// <param name="component"></param>
- [PublicAPI]
- public void ResetAnalyzer(EntityUid uid, ArtifactAnalyzerComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- component.LastAnalyzedArtifact = null;
- component.ReadyToPrint = false;
- UpdateAnalyzerInformation(uid, component);
- }
-
- /// <summary>
- /// Goes through the current entities on
- /// the analyzer and returns a valid artifact
- /// </summary>
- /// <param name="uid"></param>
- /// <param name="placer"></param>
- /// <returns></returns>
- private EntityUid? GetArtifactForAnalysis(EntityUid? uid, ItemPlacerComponent? placer = null)
- {
- if (uid == null || !Resolve(uid.Value, ref placer))
- return null;
-
- return placer.PlacedEntities.FirstOrNull();
- }
-
- /// <summary>
- /// Updates the current scan information based on
- /// the last artifact that was scanned.
- /// </summary>
- /// <param name="uid"></param>
- /// <param name="component"></param>
- private void UpdateAnalyzerInformation(EntityUid uid, ArtifactAnalyzerComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- if (component.LastAnalyzedArtifact == null)
- {
- component.LastAnalyzerPointValue = null;
- component.LastAnalyzedNode = null;
- }
- else if (TryComp<ArtifactComponent>(component.LastAnalyzedArtifact, out var artifact))
- {
- var lastNode = artifact.CurrentNodeId == null
- ? null
- : (ArtifactNode?) _artifact.GetNodeFromId(artifact.CurrentNodeId.Value, artifact).Clone();
- component.LastAnalyzedNode = lastNode;
- component.LastAnalyzerPointValue = _artifact.GetResearchPointValue(component.LastAnalyzedArtifact.Value, artifact);
- }
- }
-
- private void OnMapInit(EntityUid uid, ArtifactAnalyzerComponent component, MapInitEvent args)
- {
- if (!TryComp<DeviceLinkSinkComponent>(uid, out var sink))
- return;
-
- foreach (var source in sink.LinkedSources)
- {
- if (!TryComp<AnalysisConsoleComponent>(source, out var analysis))
- continue;
- component.Console = source;
- analysis.AnalyzerEntity = uid;
- return;
- }
- }
-
- private void OnNewLink(EntityUid uid, AnalysisConsoleComponent component, NewLinkEvent args)
- {
- if (!TryComp<ArtifactAnalyzerComponent>(args.Sink, out var analyzer))
- return;
-
- component.AnalyzerEntity = args.Sink;
- analyzer.Console = uid;
-
- UpdateUserInterface(uid, component);
- }
-
- private void OnPortDisconnected(EntityUid uid, AnalysisConsoleComponent component, PortDisconnectedEvent args)
- {
- if (args.Port == component.LinkingPort && component.AnalyzerEntity != null)
- {
- if (TryComp<ArtifactAnalyzerComponent>(component.AnalyzerEntity, out var analyzezr))
- analyzezr.Console = null;
- component.AnalyzerEntity = null;
- }
-
- UpdateUserInterface(uid, component);
- }
-
- private void UpdateUserInterface(EntityUid uid, AnalysisConsoleComponent? component = null)
- {
- if (!Resolve(uid, ref component, false))
- return;
-
- EntityUid? artifact = null;
- FormattedMessage? msg = null;
- TimeSpan? totalTime = null;
- var canScan = false;
- var canPrint = false;
- var points = 0;
-
- if (TryComp<ArtifactAnalyzerComponent>(component.AnalyzerEntity, out var analyzer))
- {
- artifact = analyzer.LastAnalyzedArtifact;
- msg = GetArtifactScanMessage(analyzer);
- totalTime = analyzer.AnalysisDuration;
- if (TryComp<ItemPlacerComponent>(component.AnalyzerEntity, out var placer))
- canScan = placer.PlacedEntities.Any();
- canPrint = analyzer.ReadyToPrint;
-
- // the artifact that's actually on the scanner right now.
- if (GetArtifactForAnalysis(component.AnalyzerEntity, placer) is { } current)
- points = _artifact.GetResearchPointValue(current);
- }
-
- var analyzerConnected = component.AnalyzerEntity != null;
- var serverConnected = TryComp<ResearchClientComponent>(uid, out var client) && client.ConnectedToServer;
-
- var scanning = TryComp<ActiveArtifactAnalyzerComponent>(component.AnalyzerEntity, out var active);
- var paused = active != null ? active.AnalysisPaused : false;
-
- var biasDirection = BiasDirection.Up;
-
- if (TryComp<TraversalDistorterComponent>(component.AnalyzerEntity, out var trav))
- biasDirection = trav.BiasDirection;
-
- var state = new AnalysisConsoleUpdateState(GetNetEntity(artifact), analyzerConnected, serverConnected,
- canScan, canPrint, msg, scanning, paused, active?.StartTime, active?.AccumulatedRunTime, totalTime, points, biasDirection == BiasDirection.Down);
-
- _ui.SetUiState(uid, ArtifactAnalzyerUiKey.Key, state);
- }
-
- /// <summary>
- /// opens the server selection menu.
- /// </summary>
- /// <param name="uid"></param>
- /// <param name="component"></param>
- /// <param name="args"></param>
- private void OnServerSelectionMessage(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleServerSelectionMessage args)
- {
- _ui.OpenUi(uid, ResearchClientUiKey.Key, args.Actor);
- }
-
- /// <summary>
- /// Starts scanning the artifact.
- /// </summary>
- /// <param name="uid"></param>
- /// <param name="component"></param>
- /// <param name="args"></param>
- private void OnScanButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleScanButtonPressedMessage args)
- {
- if (component.AnalyzerEntity == null)
- return;
-
- if (HasComp<ActiveArtifactAnalyzerComponent>(component.AnalyzerEntity))
- return;
-
- var ent = GetArtifactForAnalysis(component.AnalyzerEntity);
- if (ent == null)
- return;
-
- var activeComp = EnsureComp<ActiveArtifactAnalyzerComponent>(component.AnalyzerEntity.Value);
- activeComp.StartTime = _timing.CurTime;
- activeComp.AccumulatedRunTime = TimeSpan.Zero;
- activeComp.Artifact = ent.Value;
-
- if (TryComp<ApcPowerReceiverComponent>(component.AnalyzerEntity.Value, out var powa))
- activeComp.AnalysisPaused = !powa.Powered;
-
- var activeArtifact = EnsureComp<ActiveScannedArtifactComponent>(ent.Value);
- activeArtifact.Scanner = component.AnalyzerEntity.Value;
- UpdateUserInterface(uid, component);
- }
-
- private void OnPrintButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsolePrintButtonPressedMessage args)
- {
- if (component.AnalyzerEntity == null)
- return;
-
- if (!TryComp<ArtifactAnalyzerComponent>(component.AnalyzerEntity, out var analyzer) ||
- analyzer.LastAnalyzedNode == null ||
- analyzer.LastAnalyzerPointValue == null ||
- !analyzer.ReadyToPrint)
- {
- return;
- }
- analyzer.ReadyToPrint = false;
-
- var report = Spawn(component.ReportEntityId, Transform(uid).Coordinates);
- _metaSystem.SetEntityName(report, Loc.GetString("analysis-report-title", ("id", analyzer.LastAnalyzedNode.Id)));
-
- var msg = GetArtifactScanMessage(analyzer);
- if (msg == null)
- return;
-
- _popup.PopupEntity(Loc.GetString("analysis-console-print-popup"), uid);
- if (TryComp<PaperComponent>(report, out var paperComp))
- _paper.SetContent((report, paperComp), msg.ToMarkup());
- UpdateUserInterface(uid, component);
- }
-
- private FormattedMessage? GetArtifactScanMessage(ArtifactAnalyzerComponent component)
- {
- var msg = new FormattedMessage();
- if (component.LastAnalyzedNode == null)
- return null;
-
- var n = component.LastAnalyzedNode;
-
- msg.AddMarkupOrThrow(Loc.GetString("analysis-console-info-id", ("id", n.Id)));
- msg.PushNewline();
- msg.AddMarkupOrThrow(Loc.GetString("analysis-console-info-depth", ("depth", n.Depth)));
- msg.PushNewline();
-
- var activated = n.Triggered
- ? "analysis-console-info-triggered-true"
- : "analysis-console-info-triggered-false";
- msg.AddMarkupOrThrow(Loc.GetString(activated));
- msg.PushNewline();
-
- msg.PushNewline();
- var needSecondNewline = false;
-
- var triggerProto = _prototype.Index<ArtifactTriggerPrototype>(n.Trigger);
- if (triggerProto.TriggerHint != null)
- {
- msg.AddMarkupOrThrow(Loc.GetString("analysis-console-info-trigger",
- ("trigger", Loc.GetString(triggerProto.TriggerHint))) + "\n");
- needSecondNewline = true;
- }
-
- var effectproto = _prototype.Index<ArtifactEffectPrototype>(n.Effect);
- if (effectproto.EffectHint != null)
- {
- msg.AddMarkupOrThrow(Loc.GetString("analysis-console-info-effect",
- ("effect", Loc.GetString(effectproto.EffectHint))) + "\n");
- needSecondNewline = true;
- }
-
- if (needSecondNewline)
- msg.PushNewline();
-
- msg.AddMarkupOrThrow(Loc.GetString("analysis-console-info-edges", ("edges", n.Edges.Count)));
- msg.PushNewline();
-
- if (component.LastAnalyzerPointValue != null)
- msg.AddMarkupOrThrow(Loc.GetString("analysis-console-info-value", ("value", component.LastAnalyzerPointValue)));
-
- return msg;
- }
-
- /// <summary>
- /// Extracts points from the artifact and updates the server points
- /// </summary>
- /// <param name="uid"></param>
- /// <param name="component"></param>
- /// <param name="args"></param>
- private void OnExtractButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleExtractButtonPressedMessage args)
- {
- if (component.AnalyzerEntity == null)
- return;
-
- if (!_research.TryGetClientServer(uid, out var server, out var serverComponent))
- return;
-
- var artifact = GetArtifactForAnalysis(component.AnalyzerEntity);
- if (artifact == null)
- return;
-
- var pointValue = _artifact.GetResearchPointValue(artifact.Value);
-
- // no new nodes triggered so nothing to add
- if (pointValue == 0)
- return;
-
- _research.ModifyServerPoints(server.Value, pointValue, serverComponent);
- _artifact.AdjustConsumedPoints(artifact.Value, pointValue);
-
- _audio.PlayPvs(component.ExtractSound, component.AnalyzerEntity.Value, AudioParams.Default.WithVolume(2f));
-
- _popup.PopupEntity(Loc.GetString("analyzer-artifact-extract-popup"),
- component.AnalyzerEntity.Value, PopupType.Large);
-
- UpdateUserInterface(uid, component);
- }
-
- private void OnBiasButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleBiasButtonPressedMessage args)
- {
- if (component.AnalyzerEntity == null)
- return;
-
- if (!TryComp<TraversalDistorterComponent>(component.AnalyzerEntity, out var trav))
- return;
-
- if (!_traversalDistorter.SetState(component.AnalyzerEntity.Value, trav, args.IsDown))
- return;
-
- UpdateUserInterface(uid, component);
- }
-
- /// <summary>
- /// Cancels scans if the artifact changes nodes (is activated) during the scan.
- /// </summary>
- private void OnArtifactActivated(EntityUid uid, ActiveScannedArtifactComponent component, ArtifactActivatedEvent args)
- {
- CancelScan(uid);
- }
-
- /// <summary>
- /// Stops the current scan
- /// </summary>
- [PublicAPI]
- public void CancelScan(EntityUid artifact, ActiveScannedArtifactComponent? component = null, ArtifactAnalyzerComponent? analyzer = null)
- {
- if (!Resolve(artifact, ref component, false))
- return;
-
- if (!Resolve(component.Scanner, ref analyzer))
- return;
-
- _audio.PlayPvs(component.ScanFailureSound, component.Scanner, AudioParams.Default.WithVolume(3f));
-
- RemComp<ActiveArtifactAnalyzerComponent>(component.Scanner);
- if (analyzer.Console != null)
- UpdateUserInterface(analyzer.Console.Value);
-
- RemCompDeferred(artifact, component);
- }
-
- /// <summary>
- /// Finishes the current scan.
- /// </summary>
- [PublicAPI]
- public void FinishScan(EntityUid uid, ArtifactAnalyzerComponent? component = null, ActiveArtifactAnalyzerComponent? active = null)
- {
- if (!Resolve(uid, ref component, ref active))
- return;
-
- component.ReadyToPrint = true;
- _audio.PlayPvs(component.ScanFinishedSound, uid);
- component.LastAnalyzedArtifact = active.Artifact;
- UpdateAnalyzerInformation(uid, component);
-
- RemComp<ActiveScannedArtifactComponent>(active.Artifact);
- RemComp(uid, active);
- if (component.Console != null)
- UpdateUserInterface(component.Console.Value);
- }
-
- [PublicAPI]
- public void PauseScan(EntityUid uid, ArtifactAnalyzerComponent? component = null, ActiveArtifactAnalyzerComponent? active = null)
- {
- if (!Resolve(uid, ref component, ref active) || active.AnalysisPaused)
- return;
-
- active.AnalysisPaused = true;
- // As we pause, we store what was already completed.
- active.AccumulatedRunTime = (_timing.CurTime - active.StartTime) + active.AccumulatedRunTime;
-
- if (Exists(component.Console))
- UpdateUserInterface(component.Console.Value);
- }
-
- [PublicAPI]
- public void ResumeScan(EntityUid uid, ArtifactAnalyzerComponent? component = null, ActiveArtifactAnalyzerComponent? active = null)
- {
- if (!Resolve(uid, ref component, ref active) || !active.AnalysisPaused)
- return;
-
- active.StartTime = _timing.CurTime;
- active.AnalysisPaused = false;
-
- if (Exists(component.Console))
- UpdateUserInterface(component.Console.Value);
- }
-
- private void OnItemPlaced(EntityUid uid, ArtifactAnalyzerComponent component, ref ItemPlacedEvent args)
- {
- if (component.Console != null && Exists(component.Console))
- UpdateUserInterface(component.Console.Value);
- }
-
- private void OnItemRemoved(EntityUid uid, ArtifactAnalyzerComponent component, ref ItemRemovedEvent args)
- {
- // Scanners shouldn't give permanent remove vision to an artifact, and the scanned artifact doesn't have any
- // component to track analyzers that have scanned it for removal if the artifact gets deleted.
- // So we always clear this on removal.
- component.LastAnalyzedArtifact = null;
-
- // cancel the scan if the artifact moves off the analyzer
- CancelScan(args.OtherEntity);
- if (Exists(component.Console))
- UpdateUserInterface(component.Console.Value);
- }
-
- private void OnAnalyzeStart(EntityUid uid, ActiveArtifactAnalyzerComponent component, ComponentStartup args)
- {
- _receiver.SetNeedsPower(uid, true);
- _ambientSound.SetAmbience(uid, true);
- }
-
- private void OnAnalyzeEnd(EntityUid uid, ActiveArtifactAnalyzerComponent component, ComponentShutdown args)
- {
- _receiver.SetNeedsPower(uid, false);
- _ambientSound.SetAmbience(uid, false);
- }
-
- private void OnPowerChanged(EntityUid uid, ActiveArtifactAnalyzerComponent active, ref PowerChangedEvent args)
- {
- if (!args.Powered)
- {
- PauseScan(uid, null, active);
- }
- else
- {
- ResumeScan(uid, null, active);
- }
- }
-}
-
using Content.Server.Body.Systems;
using Content.Server.Popups;
-using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Server.Stack;
using Content.Server.Storage.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts;
using Content.Shared.Body.Components;
using Content.Shared.Damage;
using Content.Shared.Power;
using Content.Shared.Verbs;
using Content.Shared.Whitelist;
using Content.Shared.Xenoarchaeology.Equipment;
+using Content.Shared.Xenoarchaeology.Equipment.Components;
using Robust.Shared.Collections;
using Robust.Shared.Random;
using Robust.Shared.Timing;
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly ArtifactSystem _artifact = default!;
[Dependency] private readonly BodySystem _body = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly StackSystem _stack = default!;
{
ContainerSystem.Insert((stack, null, null, null), crusher.OutputContainer);
}
- _artifact.ForceActivateArtifact(contained);
}
if (!TryComp<BodyComponent>(contained, out var body))
-using Content.Server.Popups;
-using Content.Server.Xenoarchaeology.Equipment.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts;
-using Content.Shared.Interaction;
-using Content.Shared.Timing;
-using Content.Shared.Verbs;
+using Content.Shared.Xenoarchaeology.Equipment;
+using Content.Shared.Xenoarchaeology.Equipment.Components;
namespace Content.Server.Xenoarchaeology.Equipment.Systems;
-public sealed class NodeScannerSystem : EntitySystem
+/// <inheritdoc cref="SharedNodeScannerSystem"/>
+public sealed class NodeScannerSystem : SharedNodeScannerSystem
{
- [Dependency] private readonly UseDelaySystem _useDelay = default!;
- [Dependency] private readonly PopupSystem _popupSystem = 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;
-
- if (!TryComp<ArtifactComponent>(target, out var artifact) || artifact.CurrentNodeId == null)
- return;
-
- CreatePopup(uid, target, artifact);
- args.Handled = true;
- }
-
- private void AddScanVerb(EntityUid uid, NodeScannerComponent component, GetVerbsEvent<UtilityVerb> args)
- {
- if (!args.CanAccess)
- return;
-
- if (!TryComp<ArtifactComponent>(args.Target, out var artifact) || artifact.CurrentNodeId == null)
- return;
-
- var verb = new UtilityVerb()
- {
- Act = () =>
- {
- CreatePopup(uid, args.Target, artifact);
- },
- Text = Loc.GetString("node-scan-tooltip")
- };
-
- args.Verbs.Add(verb);
- }
-
- private void CreatePopup(EntityUid uid, EntityUid target, ArtifactComponent artifact)
+ protected override void TryOpenUi(Entity<NodeScannerComponent> device, EntityUid actor)
{
- if (TryComp(uid, out UseDelayComponent? useDelay)
- && !_useDelay.TryResetDelay((uid, useDelay), true))
- return;
-
- _popupSystem.PopupEntity(Loc.GetString("node-scan-popup",
- ("id", $"{artifact.CurrentNodeId}")), target);
+ // no-op
}
}
+++ /dev/null
-using Content.Server.Popups;
-using Content.Server.Power.EntitySystems;
-using Content.Server.Xenoarchaeology.Equipment.Components;
-using Content.Shared.Examine;
-using Content.Shared.Interaction;
-using Content.Shared.Placeable;
-using Robust.Shared.Timing;
-
-namespace Content.Server.Xenoarchaeology.Equipment.Systems;
-
-public sealed class TraversalDistorterSystem : EntitySystem
-{
- [Dependency] private readonly IGameTiming _timing = default!;
-
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<TraversalDistorterComponent, MapInitEvent>(OnInit);
- SubscribeLocalEvent<TraversalDistorterComponent, ExaminedEvent>(OnExamine);
-
- SubscribeLocalEvent<TraversalDistorterComponent, ItemPlacedEvent>(OnItemPlaced);
- SubscribeLocalEvent<TraversalDistorterComponent, ItemRemovedEvent>(OnItemRemoved);
- }
-
- private void OnInit(EntityUid uid, TraversalDistorterComponent component, MapInitEvent args)
- {
- component.NextActivation = _timing.CurTime;
- }
-
- /// <summary>
- /// Switches the state of the traversal distorter between up and down.
- /// </summary>
- /// <param name="uid">The distorter's entity</param>
- /// <param name="component">The component on the entity</param>
- /// <returns>If the distorter changed state</returns>
- public bool SetState(EntityUid uid, TraversalDistorterComponent component, bool isDown)
- {
- if (!this.IsPowered(uid, EntityManager))
- return false;
-
- if (_timing.CurTime < component.NextActivation)
- return false;
-
- component.NextActivation = _timing.CurTime + component.ActivationDelay;
-
- component.BiasDirection = isDown ? BiasDirection.Down : BiasDirection.Up;
-
- return true;
- }
-
- private void OnExamine(EntityUid uid, TraversalDistorterComponent component, ExaminedEvent args)
- {
- string examine = string.Empty;
- switch (component.BiasDirection)
- {
- case BiasDirection.Up:
- examine = Loc.GetString("traversal-distorter-desc-up");
- break;
- case BiasDirection.Down:
- examine = Loc.GetString("traversal-distorter-desc-down");
- break;
- }
-
- args.PushMarkup(examine);
- }
-
- private void OnItemPlaced(EntityUid uid, TraversalDistorterComponent component, ref ItemPlacedEvent args)
- {
- var bias = EnsureComp<BiasedArtifactComponent>(args.OtherEntity);
- bias.Provider = uid;
- }
-
- private void OnItemRemoved(EntityUid uid, TraversalDistorterComponent component, ref ItemRemovedEvent args)
- {
- var otherEnt = args.OtherEntity;
- if (TryComp<BiasedArtifactComponent>(otherEnt, out var bias) && bias.Provider == uid)
- RemComp(otherEnt, bias);
- }
-}
+++ /dev/null
-using Content.Shared.Xenoarchaeology.XenoArtifacts;
-using Robust.Shared.Audio;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts;
-
-[RegisterComponent, Access(typeof(ArtifactSystem))]
-public sealed partial class ArtifactComponent : Component
-{
- /// <summary>
- /// Every node contained in the tree
- /// </summary>
- [DataField("nodeTree"), ViewVariables]
- public List<ArtifactNode> NodeTree = new();
-
- /// <summary>
- /// The current node the artifact is on.
- /// </summary>
- [DataField("currentNodeId"), ViewVariables]
- public int? CurrentNodeId;
-
- #region Node Tree Gen
- /// <summary>
- /// Minimum number of nodes to generate, inclusive
- /// </summary>
- [DataField("nodesMin")]
- public int NodesMin = 3;
-
- /// <summary>
- /// Maximum number of nodes to generate, exclusive
- /// </summary>
- [DataField("nodesMax")]
- public int NodesMax = 9;
- #endregion
-
- /// <summary>
- /// Cooldown time between artifact activations (in seconds).
- /// </summary>
- [DataField("timer"), ViewVariables(VVAccess.ReadWrite)]
- public TimeSpan CooldownTime = TimeSpan.FromSeconds(5);
-
- /// <summary>
- /// Is this artifact under some suppression device?
- /// f true, will ignore all trigger activations attempts.
- /// </summary>
- [DataField("isSuppressed"), ViewVariables(VVAccess.ReadWrite)]
- public bool IsSuppressed;
-
- /// <summary>
- /// The last time the artifact was activated.
- /// </summary>
- [DataField("lastActivationTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
- public TimeSpan LastActivationTime;
-
- /// <summary>
- /// A multiplier applied to the calculated point value
- /// to determine the monetary value of the artifact
- /// </summary>
- [DataField("priceMultiplier"), ViewVariables(VVAccess.ReadWrite)]
- public float PriceMultiplier = 0.05f;
-
- /// <summary>
- /// The base amount of research points for each artifact node.
- /// </summary>
- [DataField("pointsPerNode"), ViewVariables(VVAccess.ReadWrite)]
- public int PointsPerNode = 6500;
-
- /// <summary>
- /// Research points which have been "consumed" from the theoretical max value of the artifact.
- /// </summary>
- [DataField("consumedPoints"), ViewVariables(VVAccess.ReadWrite)]
- public int ConsumedPoints;
-
- /// <summary>
- /// A multiplier that is raised to the power of the average depth of a node.
- /// Used for calculating the research point value of an artifact node.
- /// </summary>
- [DataField("pointDangerMultiplier"), ViewVariables(VVAccess.ReadWrite)]
- public float PointDangerMultiplier = 1.35f;
-
- /// <summary>
- /// The sound that plays when an artifact is activated
- /// </summary>
- [DataField("activationSound")]
- public SoundSpecifier ActivationSound = new SoundCollectionSpecifier("ArtifactActivation")
- {
- Params = new()
- {
- Variation = 0.1f,
- Volume = 3f
- }
- };
-
- [DataField("activateActionEntity")] public EntityUid? ActivateActionEntity;
-}
-
-/// <summary>
-/// A single "node" of an artifact that contains various data about it.
-/// </summary>
-[DataDefinition]
-public sealed partial class ArtifactNode : ICloneable
-{
- /// <summary>
- /// A numeric id corresponding to each node.
- /// </summary>
- [DataField("id"), ViewVariables]
- public int Id;
-
- /// <summary>
- /// how "deep" into the node tree. used for generation and price/value calculations
- /// </summary>
- [DataField("depth"), ViewVariables]
- public int Depth;
-
- /// <summary>
- /// A list of surrounding nodes. Used for tree traversal
- /// </summary>
- [DataField("edges"), ViewVariables]
- public HashSet<int> Edges = new();
-
- /// <summary>
- /// Whether or not the node has been entered
- /// </summary>
- [DataField("discovered"), ViewVariables(VVAccess.ReadWrite)]
- public bool Discovered;
-
- /// <summary>
- /// The trigger for the node
- /// </summary>
- [DataField("trigger", customTypeSerializer: typeof(PrototypeIdSerializer<ArtifactTriggerPrototype>), required: true), ViewVariables]
- public string Trigger = default!;
-
- /// <summary>
- /// Whether or not the node has been triggered
- /// </summary>
- [DataField("triggered"), ViewVariables(VVAccess.ReadWrite)]
- public bool Triggered;
-
- /// <summary>
- /// The effect when the node is activated
- /// </summary>
- [DataField("effect", customTypeSerializer: typeof(PrototypeIdSerializer<ArtifactEffectPrototype>), required: true), ViewVariables]
- public string Effect = default!;
-
- /// <summary>
- /// Used for storing cumulative information about nodes
- /// </summary>
- [DataField("nodeData"), ViewVariables]
- public Dictionary<string, object> NodeData = new();
-
- public object Clone()
- {
- return new ArtifactNode
- {
- Id = Id,
- Depth = Depth,
- Edges = Edges,
- Discovered = Discovered,
- Trigger = Trigger,
- Triggered = Triggered,
- Effect = Effect,
- NodeData = NodeData
- };
- }
-}
+++ /dev/null
-using Content.Server.Actions;
-using Content.Server.Popups;
-using Content.Shared.Xenoarchaeology.XenoArtifacts;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts;
-
-public partial class ArtifactSystem
-{
- [Dependency] private readonly ActionsSystem _actions = default!;
- [Dependency] private readonly PopupSystem _popup = default!;
-
- [ValidatePrototypeId<EntityPrototype>] private const string ArtifactActivateActionId = "ActionArtifactActivate";
-
- /// <summary>
- /// Used to add the artifact activation action (hehe), which lets sentient artifacts activate themselves,
- /// either through admemery or the sentience effect.
- /// </summary>
- public void InitializeActions()
- {
- SubscribeLocalEvent<ArtifactComponent, MapInitEvent>(OnMapInit);
- SubscribeLocalEvent<ArtifactComponent, ComponentRemove>(OnRemove);
-
- SubscribeLocalEvent<ArtifactComponent, ArtifactSelfActivateEvent>(OnSelfActivate);
- }
-
- private void OnMapInit(EntityUid uid, ArtifactComponent component, MapInitEvent args)
- {
- RandomizeArtifact(uid, component);
- _actions.AddAction(uid, ref component.ActivateActionEntity, ArtifactActivateActionId);
- }
-
- private void OnRemove(EntityUid uid, ArtifactComponent component, ComponentRemove args)
- {
- _actions.RemoveAction(uid, component.ActivateActionEntity);
- }
-
- private void OnSelfActivate(EntityUid uid, ArtifactComponent component, ArtifactSelfActivateEvent args)
- {
- if (component.CurrentNodeId == null)
- return;
-
- var curNode = GetNodeFromId(component.CurrentNodeId.Value, component).Id;
- _popup.PopupEntity(Loc.GetString("activate-artifact-popup-self", ("node", curNode)), uid, uid);
- TryActivateArtifact(uid, uid, component);
-
- args.Handled = true;
- }
-}
+++ /dev/null
-using System.Linq;
-using Content.Server.Administration;
-using Content.Shared.Administration;
-using Robust.Shared.Console;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts;
-
-public partial class ArtifactSystem
-{
- [Dependency] private readonly IConsoleHost _conHost = default!;
-
- public void InitializeCommands()
- {
- _conHost.RegisterCommand("forceartifactnode", "Forces an artifact to traverse to a given node", "forceartifacteffect <uid> <node ID>",
- ForceArtifactNode,
- ForceArtifactNodeCompletions);
-
- _conHost.RegisterCommand("getartifactmaxvalue", "Reports the maximum research point value for a given artifact", "forceartifacteffect <uid>",
- GetArtifactMaxValue);
- }
-
- [AdminCommand(AdminFlags.Fun)]
- private void ForceArtifactNode(IConsoleShell shell, string argstr, string[] args)
- {
- if (args.Length != 2)
- {
- shell.WriteError("Argument length must be 2");
- return;
- }
-
- if (!NetEntity.TryParse(args[0], out var uidNet) || !TryGetEntity(uidNet, out var uid) || !int.TryParse(args[1], out var id))
- return;
-
- if (!TryComp<ArtifactComponent>(uid, out var artifact))
- return;
-
- if (artifact.NodeTree.FirstOrDefault(n => n.Id == id) is { } node)
- {
- EnterNode(uid.Value, ref node);
- }
- }
-
- private CompletionResult ForceArtifactNodeCompletions(IConsoleShell shell, string[] args)
- {
- if (args.Length == 2 && NetEntity.TryParse(args[0], out var uidNet) && TryGetEntity(uidNet, out var uid))
- {
- if (TryComp<ArtifactComponent>(uid, out var artifact))
- {
- return CompletionResult.FromHintOptions(artifact.NodeTree.Select(s => s.Id.ToString()), "<node id>");
- }
- }
-
- return CompletionResult.Empty;
- }
-
- [AdminCommand(AdminFlags.Debug)]
- private void GetArtifactMaxValue(IConsoleShell shell, string argstr, string[] args)
- {
- if (args.Length != 1)
- shell.WriteError("Argument length must be 1");
-
- if (!NetEntity.TryParse(args[0], out var uidNet) || !TryGetEntity(uidNet, out var uid))
- return;
-
- if (!TryComp<ArtifactComponent>(uid, out var artifact))
- return;
-
- var pointSum = GetResearchPointValue(uid.Value, artifact, true);
- shell.WriteLine($"Max point value for {ToPrettyString(uid.Value)} with {artifact.NodeTree.Count} nodes: {pointSum}");
- }
-}
+++ /dev/null
-using System.Linq;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Whitelist;
-using Content.Shared.Xenoarchaeology.XenoArtifacts;
-using JetBrains.Annotations;
-using Robust.Shared.Random;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts;
-
-public sealed partial class ArtifactSystem
-{
- [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
-
- private const int MaxEdgesPerNode = 4;
-
- private readonly HashSet<int> _usedNodeIds = new();
-
- /// <summary>
- /// Generate an Artifact tree with fully developed nodes.
- /// </summary>
- /// <param name="artifact"></param>
- /// <param name="allNodes"></param>
- /// <param name="nodesToCreate">The amount of nodes it has.</param>
- private void GenerateArtifactNodeTree(EntityUid artifact, List<ArtifactNode> allNodes, int nodesToCreate)
- {
- if (nodesToCreate < 1)
- {
- Log.Error($"nodesToCreate {nodesToCreate} is less than 1. Aborting artifact tree generation.");
- return;
- }
-
- _usedNodeIds.Clear();
-
- var uninitializedNodes = new List<ArtifactNode> { new(){ Id = GetValidNodeId() } };
- var createdNodes = 1;
-
- while (uninitializedNodes.Count > 0)
- {
- var node = uninitializedNodes[0];
- uninitializedNodes.Remove(node);
-
- node.Trigger = GetRandomTrigger(artifact, ref node);
- node.Effect = GetRandomEffect(artifact, ref node);
-
- var maxChildren = _random.Next(1, MaxEdgesPerNode - 1);
-
- for (var i = 0; i < maxChildren; i++)
- {
- if (nodesToCreate <= createdNodes)
- {
- break;
- }
-
- var child = new ArtifactNode {Id = GetValidNodeId(), Depth = node.Depth + 1};
- node.Edges.Add(child.Id);
- child.Edges.Add(node.Id);
-
- uninitializedNodes.Add(child);
- createdNodes++;
- }
-
- allNodes.Add(node);
- }
- }
-
- private int GetValidNodeId()
- {
- var id = _random.Next(100, 1000);
- while (_usedNodeIds.Contains(id))
- {
- id = _random.Next(100, 1000);
- }
-
- _usedNodeIds.Add(id);
-
- return id;
- }
-
- //yeah these two functions are near duplicates but i don't
- //want to implement an interface or abstract parent
-
- private string GetRandomTrigger(EntityUid artifact, ref ArtifactNode node)
- {
- var allTriggers = _prototype.EnumeratePrototypes<ArtifactTriggerPrototype>()
- .Where(x => _whitelistSystem.IsWhitelistPassOrNull(x.Whitelist, artifact) &&
- _whitelistSystem.IsBlacklistFailOrNull(x.Blacklist, artifact)).ToList();
- var validDepth = allTriggers.Select(x => x.TargetDepth).Distinct().ToList();
-
- var weights = GetDepthWeights(validDepth, node.Depth);
- var selectedRandomTargetDepth = GetRandomTargetDepth(weights);
- var targetTriggers = allTriggers
- .Where(x => x.TargetDepth == selectedRandomTargetDepth).ToList();
-
- return _random.Pick(targetTriggers).ID;
- }
-
- private string GetRandomEffect(EntityUid artifact, ref ArtifactNode node)
- {
- var allEffects = _prototype.EnumeratePrototypes<ArtifactEffectPrototype>()
- .Where(x => _whitelistSystem.IsWhitelistPassOrNull(x.Whitelist, artifact) &&
- _whitelistSystem.IsBlacklistFailOrNull(x.Blacklist, artifact)).ToList();
- var validDepth = allEffects.Select(x => x.TargetDepth).Distinct().ToList();
-
- var weights = GetDepthWeights(validDepth, node.Depth);
- var selectedRandomTargetDepth = GetRandomTargetDepth(weights);
- var targetEffects = allEffects
- .Where(x => x.TargetDepth == selectedRandomTargetDepth).ToList();
-
- return _random.Pick(targetEffects).ID;
- }
-
- /// <remarks>
- /// The goal is that the depth that is closest to targetDepth has the highest chance of appearing.
- /// The issue is that we also want some variance, so levels that are +/- 1 should also have a
- /// decent shot of appearing. This function should probably get some tweaking at some point.
- /// </remarks>
- private Dictionary<int, float> GetDepthWeights(IEnumerable<int> depths, int targetDepth)
- {
- // this function is just a normal distribution with a
- // mean of target depth and standard deviation of 0.75
- var weights = new Dictionary<int, float>();
- foreach (var d in depths)
- {
- var w = 10f / (0.75f * MathF.Sqrt(2 * MathF.PI)) * MathF.Pow(MathF.E, -MathF.Pow((d - targetDepth) / 0.75f, 2));
- weights.Add(d, w);
- }
- return weights;
- }
-
- /// <summary>
- /// Uses a weighted random system to get a random depth.
- /// </summary>
- private int GetRandomTargetDepth(Dictionary<int, float> weights)
- {
- var sum = weights.Values.Sum();
- var accumulated = 0f;
-
- var rand = _random.NextFloat() * sum;
-
- foreach (var (key, weight) in weights)
- {
- accumulated += weight;
-
- if (accumulated >= rand)
- {
- return key;
- }
- }
-
- return _random.Pick(weights.Keys); //shouldn't happen
- }
-
- /// <summary>
- /// Enter a node: attach the relevant components
- /// </summary>
- private void EnterNode(EntityUid uid, ref ArtifactNode node, ArtifactComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- if (component.CurrentNodeId != null)
- {
- ExitNode(uid, component);
- }
-
- component.CurrentNodeId = node.Id;
-
- var trigger = _prototype.Index<ArtifactTriggerPrototype>(node.Trigger);
- var effect = _prototype.Index<ArtifactEffectPrototype>(node.Effect);
-
- var allComponents = effect.Components.Concat(effect.PermanentComponents).Concat(trigger.Components);
- foreach (var (name, entry) in allComponents)
- {
- var reg = _componentFactory.GetRegistration(name);
-
- if (node.Discovered && EntityManager.HasComponent(uid, reg.Type))
- {
- // Don't re-add permanent components unless this is the first time you've entered this node
- if (effect.PermanentComponents.ContainsKey(name))
- continue;
-
- EntityManager.RemoveComponent(uid, reg.Type);
- }
-
- var comp = (Component)_componentFactory.GetComponent(reg);
-
- var temp = (object)comp;
- _serialization.CopyTo(entry.Component, ref temp);
- EntityManager.RemoveComponent(uid, temp!.GetType());
- EntityManager.AddComponent(uid, (Component)temp!);
- }
-
- node.Discovered = true;
- RaiseLocalEvent(uid, new ArtifactNodeEnteredEvent(component.CurrentNodeId.Value));
- }
-
- /// <summary>
- /// Exit a node: remove the relevant components.
- /// </summary>
- private void ExitNode(EntityUid uid, ArtifactComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- if (component.CurrentNodeId == null)
- return;
- var currentNode = GetNodeFromId(component.CurrentNodeId.Value, component);
-
- var trigger = _prototype.Index<ArtifactTriggerPrototype>(currentNode.Trigger);
- var effect = _prototype.Index<ArtifactEffectPrototype>(currentNode.Effect);
-
- var entityPrototype = MetaData(uid).EntityPrototype;
- var toRemove = effect.Components.Keys.Concat(trigger.Components.Keys).ToList();
-
- foreach (var name in toRemove)
- {
- // if the entity prototype contained the component originally
- if (entityPrototype?.Components.TryGetComponent(name, out var entry) ?? false)
- {
- var comp = (Component)_componentFactory.GetComponent(name);
- var temp = (object)comp;
- _serialization.CopyTo(entry, ref temp);
- EntityManager.RemoveComponent(uid, temp!.GetType());
- EntityManager.AddComponent(uid, (Component)temp);
- continue;
- }
-
- EntityManager.RemoveComponentDeferred(uid, _componentFactory.GetRegistration(name).Type);
- }
- component.CurrentNodeId = null;
- }
-
- [PublicAPI]
- public ArtifactNode GetNodeFromId(int id, ArtifactComponent component)
- {
- return component.NodeTree.First(x => x.Id == id);
- }
-
- [PublicAPI]
- public ArtifactNode GetNodeFromId(int id, IEnumerable<ArtifactNode> nodes)
- {
- return nodes.First(x => x.Id == id);
- }
-}
+++ /dev/null
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using Content.Server.Cargo.Systems;
-using Content.Server.GameTicking;
-using Content.Server.Power.EntitySystems;
-using Content.Server.Xenoarchaeology.Equipment.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-using Content.Shared.CCVar;
-using Content.Shared.Xenoarchaeology.XenoArtifacts;
-using JetBrains.Annotations;
-using Robust.Shared.Audio;
-using Robust.Shared.Audio.Systems;
-using Robust.Shared.Configuration;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-using Robust.Shared.Serialization.Manager;
-using Robust.Shared.Timing;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts;
-
-public sealed partial class ArtifactSystem : EntitySystem
-{
- [Dependency] private readonly IComponentFactory _componentFactory = default!;
- [Dependency] private readonly IGameTiming _gameTiming = default!;
- [Dependency] private readonly IPrototypeManager _prototype = default!;
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly ISerializationManager _serialization = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent<ArtifactComponent, PriceCalculationEvent>(GetPrice);
-
- InitializeCommands();
- InitializeActions();
- }
-
- /// <summary>
- /// Calculates the price of an artifact based on
- /// how many nodes have been unlocked/triggered
- /// </summary>
- /// <remarks>
- /// General balancing (for fully unlocked artifacts):
- /// Simple (1-2 Nodes): 1-2K
- /// Medium (5-8 Nodes): 6-7K
- /// Complex (7-12 Nodes): 10-11K
- /// </remarks>
- private void GetPrice(EntityUid uid, ArtifactComponent component, ref PriceCalculationEvent args)
- {
- args.Price += (GetResearchPointValue(uid, component) + component.ConsumedPoints) * component.PriceMultiplier;
- }
-
- /// <summary>
- /// Calculates how many research points the artifact is worth
- /// </summary>
- /// <remarks>
- /// General balancing (for fully unlocked artifacts):
- /// Simple (1-2 Nodes): ~10K
- /// Medium (5-8 Nodes): ~30-40K
- /// Complex (7-12 Nodes): ~60-80K
- ///
- /// Simple artifacts should be enough to unlock a few techs.
- /// Medium should get you partway through a tree.
- /// Complex should get you through a full tree and then some.
- /// </remarks>
- public int GetResearchPointValue(EntityUid uid, ArtifactComponent? component = null, bool getMaxPrice = false)
- {
- if (!Resolve(uid, ref component))
- return 0;
-
- var sumValue = component.NodeTree.Sum(n => GetNodePointValue(n, component, getMaxPrice));
- var fullyExploredBonus = component.NodeTree.All(x => x.Triggered) || getMaxPrice ? 1.25f : 1;
-
- return (int) (sumValue * fullyExploredBonus) - component.ConsumedPoints;
- }
-
- /// <summary>
- /// Adjusts how many points on the artifact have been consumed
- /// </summary>
- public void AdjustConsumedPoints(EntityUid uid, int amount, ArtifactComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- component.ConsumedPoints += amount;
- }
-
- /// <summary>
- /// Sets whether or not the artifact is suppressed,
- /// preventing it from activating
- /// </summary>
- public void SetIsSuppressed(EntityUid uid, bool suppressed, ArtifactComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- component.IsSuppressed = suppressed;
- }
-
- /// <summary>
- /// Gets the point value for an individual node
- /// </summary>
- private float GetNodePointValue(ArtifactNode node, ArtifactComponent component, bool getMaxPrice = false)
- {
- var valueDeduction = 1f;
- if (!getMaxPrice)
- {
- if (!node.Discovered)
- return 0;
-
- valueDeduction = !node.Triggered ? 0.25f : 1;
- }
-
- var triggerProto = _prototype.Index<ArtifactTriggerPrototype>(node.Trigger);
- var effectProto = _prototype.Index<ArtifactEffectPrototype>(node.Effect);
-
- var nodeDanger = (node.Depth + effectProto.TargetDepth + triggerProto.TargetDepth) / 3;
- return component.PointsPerNode * MathF.Pow(component.PointDangerMultiplier, nodeDanger) * valueDeduction;
- }
-
- /// <summary>
- /// Randomize a given artifact.
- /// </summary>
- [PublicAPI]
- public void RandomizeArtifact(EntityUid uid, ArtifactComponent component)
- {
- var nodeAmount = _random.Next(component.NodesMin, component.NodesMax);
-
- GenerateArtifactNodeTree(uid, component.NodeTree, nodeAmount);
- var firstNode = GetRootNode(component.NodeTree);
- EnterNode(uid, ref firstNode, component);
- }
-
- /// <summary>
- /// Tries to activate the artifact
- /// </summary>
- /// <param name="uid"></param>
- /// <param name="user"></param>
- /// <param name="component"></param>
- /// <param name="logMissing">Set this to false if you don't know if the entity is an artifact.</param>
- /// <returns></returns>
- public bool TryActivateArtifact(EntityUid uid, EntityUid? user = null, ArtifactComponent? component = null, bool logMissing = true)
- {
- if (!Resolve(uid, ref component, logMissing))
- return false;
-
- // check if artifact is under suppression field
- if (component.IsSuppressed)
- return false;
-
- // check if artifact isn't under cooldown
- var timeDif = _gameTiming.CurTime - component.LastActivationTime;
- if (timeDif < component.CooldownTime)
- return false;
-
- ForceActivateArtifact(uid, user, component);
- return true;
- }
-
- /// <summary>
- /// Forces an artifact to activate
- /// </summary>
- /// <param name="uid"></param>
- /// <param name="user"></param>
- /// <param name="component"></param>
- public void ForceActivateArtifact(EntityUid uid, EntityUid? user = null, ArtifactComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
- if (component.CurrentNodeId == null)
- return;
-
- _audio.PlayPvs(component.ActivationSound, uid);
- component.LastActivationTime = _gameTiming.CurTime;
-
- var ev = new ArtifactActivatedEvent
- {
- Activator = user
- };
- RaiseLocalEvent(uid, ev, true);
-
- var currentNode = GetNodeFromId(component.CurrentNodeId.Value, component);
-
- currentNode.Triggered = true;
- if (currentNode.Edges.Count == 0)
- return;
-
- var newNode = GetNewNode(uid, component);
- if (newNode == null)
- return;
-
- EnterNode(uid, ref newNode, component);
- }
-
- private ArtifactNode? GetNewNode(EntityUid uid, ArtifactComponent component)
- {
- if (component.CurrentNodeId == null)
- return null;
-
- var currentNode = GetNodeFromId(component.CurrentNodeId.Value, component);
-
- var allNodes = currentNode.Edges;
- Log.Debug($"our node: {currentNode.Id}");
- Log.Debug($"other nodes: {string.Join(", ", allNodes)}");
-
- if (TryComp<BiasedArtifactComponent>(uid, out var bias) &&
- TryComp<TraversalDistorterComponent>(bias.Provider, out var trav) &&
- this.IsPowered(bias.Provider, EntityManager))
- {
- switch (trav.BiasDirection)
- {
- case BiasDirection.Up:
- var upNodes = allNodes.Where(x => GetNodeFromId(x, component).Depth < currentNode.Depth).ToHashSet();
- if (upNodes.Count != 0)
- allNodes = upNodes;
- break;
- case BiasDirection.Down:
- var downNodes = allNodes.Where(x => GetNodeFromId(x, component).Depth > currentNode.Depth).ToHashSet();
- if (downNodes.Count != 0)
- allNodes = downNodes;
- break;
- }
- }
-
- var undiscoveredNodes = allNodes.Where(x => !GetNodeFromId(x, component).Discovered).ToList();
- Log.Debug($"Undiscovered nodes: {string.Join(", ", undiscoveredNodes)}");
- var newNode = _random.Pick(allNodes);
-
- if (undiscoveredNodes.Count != 0 && _random.Prob(0.75f))
- {
- newNode = _random.Pick(undiscoveredNodes);
- }
-
- Log.Debug($"Going to node {newNode}");
-
- return GetNodeFromId(newNode, component);
- }
-
- /// <summary>
- /// Try and get a data object from a node
- /// </summary>
- /// <param name="uid">The entity you're getting the data from</param>
- /// <param name="key">The data's key</param>
- /// <param name="data">The data you are trying to get.</param>
- /// <param name="component"></param>
- /// <typeparam name="T"></typeparam>
- /// <returns></returns>
- public bool TryGetNodeData<T>(EntityUid uid, string key, [NotNullWhen(true)] out T? data, ArtifactComponent? component = null)
- {
- data = default;
-
- if (!Resolve(uid, ref component))
- return false;
-
- if (component.CurrentNodeId == null)
- return false;
- var currentNode = GetNodeFromId(component.CurrentNodeId.Value, component);
-
- if (currentNode.NodeData.TryGetValue(key, out var dat) && dat is T value)
- {
- data = value;
- return true;
- }
-
- return false;
- }
-
- /// <summary>
- /// Sets the node data to a certain value
- /// </summary>
- /// <param name="uid">The artifact</param>
- /// <param name="key">The key being set</param>
- /// <param name="value">The value it's being set to</param>
- /// <param name="component"></param>
- public void SetNodeData(EntityUid uid, string key, object value, ArtifactComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- if (component.CurrentNodeId == null)
- return;
- var currentNode = GetNodeFromId(component.CurrentNodeId.Value, component);
-
- currentNode.NodeData[key] = value;
- }
-
- /// <summary>
- /// Gets the base node (depth 0) of an artifact's node graph
- /// </summary>
- /// <param name="allNodes"></param>
- /// <returns></returns>
- public ArtifactNode GetRootNode(List<ArtifactNode> allNodes)
- {
- return allNodes.First(n => n.Depth == 0);
- }
-}
+++ /dev/null
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-/// <summary>
-/// This is used for recharging all nearby batteries when activated
-/// </summary>
-[RegisterComponent]
-public sealed partial class ChargeBatteryArtifactComponent : Component
-{
- /// <summary>
- /// The radius of entities that will be affected
- /// </summary>
- [DataField("radius")]
- public float Radius = 15f;
-}
+++ /dev/null
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-using Content.Shared.Chemistry.Components;
-using Content.Shared.Chemistry.Reagent;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-/// <summary>
-/// This is used for an artifact that creates a puddle of
-/// random chemicals upon being triggered.
-/// </summary>
-[RegisterComponent, Access(typeof(ChemicalPuddleArtifactSystem))]
-public sealed partial class ChemicalPuddleArtifactComponent : Component
-{
- /// <summary>
- /// The solution where all the chemicals are stored
- /// </summary>
- [DataField("chemicalSolution", required: true), ViewVariables(VVAccess.ReadWrite)]
- public Solution ChemicalSolution = default!;
-
- /// <summary>
- /// The different chemicals that can be spawned by this effect
- /// </summary>
- [DataField("possibleChemicals", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<ReagentPrototype>))]
- public List<string> PossibleChemicals = default!;
-
- /// <summary>
- /// The number of chemicals in the puddle
- /// </summary>
- [DataField("chemAmount")]
- public int ChemAmount = 3;
-}
+++ /dev/null
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-/// <summary>
-/// Artifact that EMP
-/// </summary>
-[RegisterComponent]
-[Access(typeof(EmpArtifactSystem))]
-public sealed partial class EmpArtifactComponent : Component
-{
- [DataField("range"), ViewVariables(VVAccess.ReadWrite)]
- public float Range = 4f;
-
- [DataField("energyConsumption"), ViewVariables(VVAccess.ReadWrite)]
- public float EnergyConsumption = 1000000;
-
- [DataField("disableDuration"), ViewVariables(VVAccess.ReadWrite)]
- public float DisableDuration = 60f;
-}
+++ /dev/null
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-using Content.Shared.Chemistry.Reagent;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-/// <summary>
-/// Generates foam from the artifact when activated
-/// </summary>
-[RegisterComponent, Access(typeof(FoamArtifactSystem))]
-public sealed partial class FoamArtifactComponent : Component
-{
- /// <summary>
- /// The list of reagents that will randomly be picked from
- /// to choose the foam reagent
- /// </summary>
- [DataField("reagents", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<ReagentPrototype>))]
- public List<string> Reagents = new();
-
- /// <summary>
- /// The foam reagent
- /// </summary>
- [DataField("selectedReagent"), ViewVariables(VVAccess.ReadWrite)]
- public string? SelectedReagent;
-
- /// <summary>
- /// How long does the foam last?
- /// </summary>
- [DataField("duration"), ViewVariables(VVAccess.ReadWrite)]
- public float Duration = 10;
-
- /// <summary>
- /// How much reagent is in the foam?
- /// </summary>
- [DataField("reagentAmount"), ViewVariables(VVAccess.ReadWrite)]
- public float ReagentAmount = 100;
-
- /// <summary>
- /// Minimum radius of foam spawned
- /// </summary>
- [DataField("minFoamAmount"), ViewVariables(VVAccess.ReadWrite)]
- public int MinFoamAmount = 15;
-
- /// <summary>
- /// Maximum radius of foam spawned
- /// </summary>
- [DataField("maxFoamAmount"), ViewVariables(VVAccess.ReadWrite)]
- public int MaxFoamAmount = 20;
-}
+++ /dev/null
-using Content.Shared.Atmos;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-/// <summary>
-/// Spawn a random gas with random temperature when artifact activated.
-/// </summary>
-[RegisterComponent]
-public sealed partial class GasArtifactComponent : Component
-{
- /// <summary>
- /// Gas that will be spawned when artifact activated.
- /// If null it will be picked on startup from <see cref="PossibleGases"/>.
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("spawnGas")]
- public Gas? SpawnGas;
-
- /// <summary>
- /// List of possible activation gases to pick on startup.
- /// </summary>
- [DataField("possibleGas")]
- public List<Gas> PossibleGases = new()
- {
- Gas.Oxygen,
- Gas.Plasma,
- Gas.Nitrogen,
- Gas.CarbonDioxide,
- Gas.Tritium,
- Gas.Ammonia,
- Gas.NitrousOxide,
- Gas.Frezon
- };
-
- /// <summary>
- /// Temperature of spawned gas. If null it will be picked on startup from range from
- /// <see cref="MinRandomTemperature"/> to <see cref="MaxRandomTemperature"/>.
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("spawnTemperature")]
- public float? SpawnTemperature;
-
- [DataField("minRandomTemp")]
- public float MinRandomTemperature = 100;
-
- [DataField("maxRandomTemp")]
- public float MaxRandomTemperature = 400;
-
- /// <summary>
- /// Max allowed external atmospheric pressure.
- /// Artifact will stop spawn gas.
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("maxExternalPressure")]
- public float MaxExternalPressure = Atmospherics.GasMinerDefaultMaxExternalPressure;
-
- /// <summary>
- /// Moles of gas to spawn each time when artifact activated.
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("spawnAmount")]
- public float SpawnAmount = Atmospherics.MolesCellStandard * 3;
-}
+++ /dev/null
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-/// <summary>
-/// Artifact that ignites surrounding entities when triggered.
-/// </summary>
-[RegisterComponent]
-public sealed partial class IgniteArtifactComponent : Component
-{
- [DataField("range")]
- public float Range = 2f;
-
- [DataField("minFireStack")]
- public int MinFireStack = 2;
-
- [DataField("maxFireStack")]
- public int MaxFireStack = 5;
-}
+++ /dev/null
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-/// <summary>
-/// This is used for using the "knock" spell when the artifact is activated
-/// </summary>
-[RegisterComponent]
-public sealed partial class KnockArtifactComponent : Component
-{
- /// <summary>
- /// The range of the spell
- /// </summary>
- [DataField("knockRange")]
- public float KnockRange = 4f;
-}
+++ /dev/null
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-/// <summary>
-/// Removes the masks/layers of hard fixtures from the artifact when added, allowing it to pass through walls
-/// and such.
-/// </summary>
-[RegisterComponent]
-public sealed partial class PhasingArtifactComponent : Component
-{
-}
+++ /dev/null
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-/// <summary>
-/// When activated artifact will spawn an pair portals. First - right in artifact, Second - at random point of station.
-/// </summary>
-[RegisterComponent, Access(typeof(PortalArtifactSystem))]
-public sealed partial class PortalArtifactComponent : Component
-{
- [DataField]
- public EntProtoId PortalProto = "PortalArtifact";
-}
+++ /dev/null
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-[RegisterComponent]
-public sealed partial class RandomInstrumentArtifactComponent : Component
-{
-
-}
+++ /dev/null
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-/// <summary>
-/// When activated, will shuffle the position of all players
-/// within a certain radius.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ShuffleArtifactComponent : Component
-{
- [DataField("radius")]
- public float Radius = 7.5f;
-}
+++ /dev/null
-using Content.Shared.Storage;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-/// <summary>
-/// When activated artifact will spawn an entity from prototype.
-/// It could be an angry mob or some random item.
-/// </summary>
-[RegisterComponent]
-public sealed partial class SpawnArtifactComponent : Component
-{
- [DataField("spawns")]
- public List<EntitySpawnEntry>? Spawns;
-
- /// <summary>
- /// The range around the artifact that it will spawn the entity
- /// </summary>
- [DataField("range")]
- public float Range = 0.5f;
-
- /// <summary>
- /// The maximum number of times the spawn will occur
- /// </summary>
- [DataField("maxSpawns")]
- public int MaxSpawns = 10;
-}
+++ /dev/null
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-/// <summary>
-/// This is used for an artifact that triggers when activated.
-/// </summary>
-[RegisterComponent]
-public sealed partial class TriggerArtifactComponent : Component
-{
-
-}
+++ /dev/null
-using Content.Server.Power.Components;
-using Content.Server.Power.EntitySystems;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Robust.Server.GameObjects;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-/// <summary>
-/// This handles <see cref="ChargeBatteryArtifactComponent"/>
-/// </summary>
-public sealed class ChargeBatteryArtifactSystem : EntitySystem
-{
- [Dependency] private readonly BatterySystem _battery = default!;
- [Dependency] private readonly EntityLookupSystem _lookup = default!;
- [Dependency] private readonly TransformSystem _transform = default!;
-
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<ChargeBatteryArtifactComponent, ArtifactActivatedEvent>(OnActivated);
- }
-
- private void OnActivated(EntityUid uid, ChargeBatteryArtifactComponent component, ArtifactActivatedEvent args)
- {
- foreach (var battery in _lookup.GetEntitiesInRange<BatteryComponent>(_transform.GetMapCoordinates(uid), component.Radius))
- {
- _battery.SetCharge(battery, battery.Comp.MaxCharge, battery);
- }
- }
-}
+++ /dev/null
-using Content.Server.Fluids.EntitySystems;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Robust.Shared.Random;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-/// <summary>
-/// This handles <see cref="ChemicalPuddleArtifactComponent"/>
-/// </summary>
-public sealed class ChemicalPuddleArtifactSystem : EntitySystem
-{
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly ArtifactSystem _artifact = default!;
- [Dependency] private readonly PuddleSystem _puddle = default!;
-
- /// <summary>
- /// The key for the node data entry containing
- /// the chemicals that the puddle is made of.
- /// </summary>
- public const string NodeDataChemicalList = "nodeDataChemicalList";
-
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<ChemicalPuddleArtifactComponent, ArtifactActivatedEvent>(OnActivated);
- }
-
- private void OnActivated(EntityUid uid, ChemicalPuddleArtifactComponent component, ArtifactActivatedEvent args)
- {
- if (!TryComp<ArtifactComponent>(uid, out var artifact))
- return;
-
- if (!_artifact.TryGetNodeData(uid, NodeDataChemicalList, out List<string>? chemicalList, artifact))
- {
- chemicalList = new();
- for (var i = 0; i < component.ChemAmount; i++)
- {
- var chemProto = _random.Pick(component.PossibleChemicals);
- chemicalList.Add(chemProto);
- }
-
- _artifact.SetNodeData(uid, NodeDataChemicalList, chemicalList, artifact);
- }
-
- var amountPerChem = component.ChemicalSolution.MaxVolume / component.ChemAmount;
- foreach (var reagent in chemicalList)
- {
- component.ChemicalSolution.AddReagent(reagent, amountPerChem);
- }
-
- _puddle.TrySpillAt(uid, component.ChemicalSolution, out _);
- }
-}
+++ /dev/null
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Damage;
-using Content.Shared.Whitelist;
-using Robust.Shared.Random;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class BreakWindowArtifactSystem : EntitySystem
-{
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly EntityLookupSystem _lookup = default!;
- [Dependency] private readonly DamageableSystem _damageable = default!;
- [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
-
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<DamageNearbyArtifactComponent, ArtifactActivatedEvent>(OnActivated);
- }
-
- private void OnActivated(EntityUid uid, DamageNearbyArtifactComponent component, ArtifactActivatedEvent args)
- {
- var ents = _lookup.GetEntitiesInRange(uid, component.Radius);
- if (args.Activator != null)
- ents.Add(args.Activator.Value);
- foreach (var ent in ents)
- {
- if (_whitelistSystem.IsWhitelistFail(component.Whitelist, ent))
- continue;
-
- if (!_random.Prob(component.DamageChance))
- return;
-
- _damageable.TryChangeDamage(ent, component.Damage, component.IgnoreResistances);
- }
- }
-}
+++ /dev/null
-using Content.Server.Emp;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Robust.Server.GameObjects;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class EmpArtifactSystem : EntitySystem
-{
- [Dependency] private readonly EmpSystem _emp = default!;
- [Dependency] private readonly TransformSystem _transform = default!;
-
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<EmpArtifactComponent, ArtifactActivatedEvent>(OnActivate);
- }
-
- private void OnActivate(EntityUid uid, EmpArtifactComponent component, ArtifactActivatedEvent args)
- {
- _emp.EmpPulse(_transform.GetMapCoordinates(uid), component.Range, component.EnergyConsumption, component.DisableDuration);
- }
-}
+++ /dev/null
-using System.Linq;
-using Content.Server.Fluids.EntitySystems;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Chemistry.Components;
-using Robust.Shared.Random;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class FoamArtifactSystem : EntitySystem
-{
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly SmokeSystem _smoke = default!;
-
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<FoamArtifactComponent, ArtifactNodeEnteredEvent>(OnNodeEntered);
- SubscribeLocalEvent<FoamArtifactComponent, ArtifactActivatedEvent>(OnActivated);
- }
-
- private void OnNodeEntered(EntityUid uid, FoamArtifactComponent component, ArtifactNodeEnteredEvent args)
- {
- if (!component.Reagents.Any())
- return;
-
- component.SelectedReagent = component.Reagents[args.RandomSeed % component.Reagents.Count];
- }
-
- private void OnActivated(EntityUid uid, FoamArtifactComponent component, ArtifactActivatedEvent args)
- {
- if (component.SelectedReagent == null)
- return;
-
- var sol = new Solution();
- var xform = Transform(uid);
- var range = (int) MathF.Round(MathHelper.Lerp(component.MinFoamAmount, component.MaxFoamAmount, _random.NextFloat(0, 1f)));
- sol.AddReagent(component.SelectedReagent, component.ReagentAmount);
- var foamEnt = Spawn("Foam", xform.Coordinates);
- var spreadAmount = range * 4;
- _smoke.StartSmoke(foamEnt, sol, component.Duration, spreadAmount);
- }
-}
+++ /dev/null
-using Content.Server.Atmos;
-using Content.Server.Atmos.EntitySystems;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Atmos;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class GasArtifactSystem : EntitySystem
-{
- [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
-
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<GasArtifactComponent, ArtifactNodeEnteredEvent>(OnNodeEntered);
- SubscribeLocalEvent<GasArtifactComponent, ArtifactActivatedEvent>(OnActivate);
- }
-
- private void OnNodeEntered(EntityUid uid, GasArtifactComponent component, ArtifactNodeEnteredEvent args)
- {
- if (component.SpawnGas == null && component.PossibleGases.Count != 0)
- {
- var gas = component.PossibleGases[args.RandomSeed % component.PossibleGases.Count];
- component.SpawnGas = gas;
- }
-
- if (component.SpawnTemperature == null)
- {
- var temp = args.RandomSeed % component.MaxRandomTemperature - component.MinRandomTemperature +
- component.MinRandomTemperature;
- component.SpawnTemperature = temp;
- }
- }
-
- private void OnActivate(EntityUid uid, GasArtifactComponent component, ArtifactActivatedEvent args)
- {
- if (component.SpawnGas == null || component.SpawnTemperature == null)
- return;
-
- var environment = _atmosphereSystem.GetContainingMixture(uid, false, true);
- if (environment == null)
- return;
-
- if (environment.Pressure >= component.MaxExternalPressure)
- return;
-
- var merger = new GasMixture(1) { Temperature = component.SpawnTemperature.Value };
- merger.SetMoles(component.SpawnGas.Value, component.SpawnAmount);
-
- _atmosphereSystem.Merge(environment, merger);
- }
-}
+++ /dev/null
-using System.Linq;
-using Content.Server.Atmos.Components;
-using Content.Server.Atmos.EntitySystems;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Robust.Shared.Random;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class IgniteArtifactSystem : EntitySystem
-{
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly EntityLookupSystem _lookup = default!;
- [Dependency] private readonly FlammableSystem _flammable = default!;
-
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<IgniteArtifactComponent, ArtifactActivatedEvent>(OnActivate);
- }
-
- private void OnActivate(EntityUid uid, IgniteArtifactComponent component, ArtifactActivatedEvent args)
- {
- var flammable = GetEntityQuery<FlammableComponent>();
- foreach (var target in _lookup.GetEntitiesInRange(uid, component.Range))
- {
- if (!flammable.TryGetComponent(target, out var fl))
- continue;
- fl.FireStacks += _random.Next(component.MinFireStack, component.MaxFireStack);
- _flammable.Ignite(target, uid, fl);
- }
- }
-}
+++ /dev/null
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Magic.Events;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class KnockArtifactSystem : EntitySystem
-{
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<KnockArtifactComponent, ArtifactActivatedEvent>(OnActivated);
- }
-
- private void OnActivated(EntityUid uid, KnockArtifactComponent component, ArtifactActivatedEvent args)
- {
- var ev = new KnockSpellEvent
- {
- Performer = uid,
- Range = component.KnockRange
- };
- RaiseLocalEvent(ev);
- }
-}
+++ /dev/null
-using Content.Server.Ghost;
-using Content.Server.Light.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Robust.Shared.Random;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-/// <summary>
-/// This handles...
-/// </summary>
-public sealed class LightFlickerArtifactSystem : EntitySystem
-{
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly EntityLookupSystem _lookup = default!;
- [Dependency] private readonly GhostSystem _ghost = default!;
-
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<LightFlickerArtifactComponent, ArtifactActivatedEvent>(OnActivated);
- }
-
- private void OnActivated(EntityUid uid, LightFlickerArtifactComponent component, ArtifactActivatedEvent args)
- {
- var lights = GetEntityQuery<PoweredLightComponent>();
- foreach (var light in _lookup.GetEntitiesInRange(uid, component.Radius, LookupFlags.StaticSundries ))
- {
- if (!lights.HasComponent(light))
- continue;
-
- if (!_random.Prob(component.FlickerChance))
- continue;
-
- _ghost.DoGhostBooEvent(light);
- }
- }
-}
+++ /dev/null
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Physics;
-using Robust.Shared.Physics;
-using Robust.Shared.Physics.Components;
-using Robust.Shared.Physics.Dynamics;
-using Robust.Shared.Physics.Systems;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-/// <summary>
-/// Handles allowing activated artifacts to phase through walls.
-/// </summary>
-public sealed class PhasingArtifactSystem : EntitySystem
-{
- [Dependency] private readonly SharedPhysicsSystem _physics = default!;
-
- /// <inheritdoc/>
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent<PhasingArtifactComponent, ArtifactActivatedEvent>(OnActivate);
- }
-
- private void OnActivate(EntityUid uid, PhasingArtifactComponent component, ArtifactActivatedEvent args)
- {
- if (!TryComp<FixturesComponent>(uid, out var fixtures))
- return;
-
- foreach (var fixture in fixtures.Fixtures.Values)
- {
- _physics.SetHard(uid, fixture, false, fixtures);
- }
- }
-}
+++ /dev/null
-using Content.Server.Polymorph.Systems;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Humanoid;
-using Content.Shared.Mobs.Systems;
-using Robust.Shared.Audio.Systems;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class PolyOthersArtifactSystem : EntitySystem
-{
- [Dependency] private readonly EntityLookupSystem _lookup = default!;
- [Dependency] private readonly MobStateSystem _mob = default!;
- [Dependency] private readonly PolymorphSystem _poly = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
-
- /// <summary>
- /// On effect trigger polymorphs targets in range.
- /// </summary>
- public override void Initialize()
- {
- SubscribeLocalEvent<PolyOthersArtifactComponent, ArtifactActivatedEvent>(OnActivate);
- }
-
- /// <summary>
- /// Provided target is alive and is not a zombie, polymorphs the target.
- /// </summary>
- private void OnActivate(Entity<PolyOthersArtifactComponent> ent, ref ArtifactActivatedEvent args)
- {
- var xform = Transform(ent);
- var humanoids = new HashSet<Entity<HumanoidAppearanceComponent>>();
- _lookup.GetEntitiesInRange(xform.Coordinates, ent.Comp.Range, humanoids);
-
- foreach (var comp in humanoids)
- {
- var target = comp.Owner;
- if (_mob.IsAlive(target))
- {
- _poly.PolymorphEntity(target, ent.Comp.PolymorphPrototypeName);
- _audio.PlayPvs(ent.Comp.PolySound, ent);
- }
- }
- }
-}
+++ /dev/null
-using Content.Server.Instruments;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Shared.Instruments;
-using Robust.Shared.Random;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class RandomInstrumentArtifactSystem : EntitySystem
-{
- [Dependency] private readonly InstrumentSystem _instrument = default!;
- [Dependency] private readonly IRobustRandom _random = default!;
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<RandomInstrumentArtifactComponent, ComponentStartup>(OnStartup);
- }
-
- private void OnStartup(EntityUid uid, RandomInstrumentArtifactComponent component, ComponentStartup args)
- {
- var instrument = EnsureComp<InstrumentComponent>(uid);
- _instrument.SetInstrumentProgram(uid, instrument, (byte) _random.Next(0, 127), 0);
- }
-}
+++ /dev/null
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Popups;
-using Robust.Shared.Player;
-using Robust.Shared.Random;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class RandomTeleportArtifactSystem : EntitySystem
-{
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly SharedPopupSystem _popup = default!;
- [Dependency] private readonly SharedTransformSystem _xform = default!;
-
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<RandomTeleportArtifactComponent, ArtifactActivatedEvent>(OnActivate);
- }
-
- private void OnActivate(EntityUid uid, RandomTeleportArtifactComponent component, ArtifactActivatedEvent args)
- {
- var xform = Transform(uid);
- _popup.PopupCoordinates(Loc.GetString("blink-artifact-popup"), xform.Coordinates, PopupType.Medium);
-
- _xform.SetCoordinates(uid, xform, xform.Coordinates.Offset(_random.NextVector2(component.MinRange, component.MaxRange)));
- }
-}
+++ /dev/null
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Mobs.Components;
-using Robust.Shared.Map;
-using Robust.Shared.Random;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class ShuffleArtifactSystem : EntitySystem
-{
- [Dependency] private readonly EntityLookupSystem _lookup = default!;
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly SharedTransformSystem _xform = default!;
-
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<ShuffleArtifactComponent, ArtifactActivatedEvent>(OnActivated);
- }
-
- private void OnActivated(EntityUid uid, ShuffleArtifactComponent component, ArtifactActivatedEvent args)
- {
- var mobState = GetEntityQuery<MobStateComponent>();
-
- List<Entity<TransformComponent>> toShuffle = new();
-
- foreach (var ent in _lookup.GetEntitiesInRange(uid, component.Radius, LookupFlags.Dynamic | LookupFlags.Sundries))
- {
- if (!mobState.HasComponent(ent))
- continue;
-
- var xform = Transform(ent);
-
- toShuffle.Add((ent, xform));
- }
-
- _random.Shuffle(toShuffle);
-
- while (toShuffle.Count > 1)
- {
- var ent1 = _random.PickAndTake(toShuffle);
- var ent2 = _random.PickAndTake(toShuffle);
- _xform.SwapPositions((ent1, ent1), (ent2, ent2));
- }
- }
-}
+++ /dev/null
-using System.Numerics;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Storage;
-using Robust.Server.GameObjects;
-using Robust.Shared.Random;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class SpawnArtifactSystem : EntitySystem
-{
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly ArtifactSystem _artifact = default!;
- [Dependency] private readonly TransformSystem _transform = default!;
-
- public const string NodeDataSpawnAmount = "nodeDataSpawnAmount";
-
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<SpawnArtifactComponent, ArtifactActivatedEvent>(OnActivate);
- }
-
- private void OnActivate(EntityUid uid, SpawnArtifactComponent component, ArtifactActivatedEvent args)
- {
- if (!_artifact.TryGetNodeData(uid, NodeDataSpawnAmount, out int amount))
- amount = 0;
-
- if (amount >= component.MaxSpawns)
- return;
-
- if (component.Spawns is not {} spawns)
- return;
-
- var artifactCord = _transform.GetMapCoordinates(uid);
- foreach (var spawn in EntitySpawnCollection.GetSpawns(spawns, _random))
- {
- var dx = _random.NextFloat(-component.Range, component.Range);
- var dy = _random.NextFloat(-component.Range, component.Range);
- var spawnCord = artifactCord.Offset(new Vector2(dx, dy));
- var ent = Spawn(spawn, spawnCord);
- _transform.AttachToGridOrMap(ent);
- }
- _artifact.SetNodeData(uid, NodeDataSpawnAmount, amount + 1);
- }
-}
+++ /dev/null
-using Content.Server.Atmos;
-using Content.Server.Atmos.EntitySystems;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Atmos;
-using Robust.Server.GameObjects;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class TemperatureArtifactSystem : EntitySystem
-{
- [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
- [Dependency] private readonly TransformSystem _transformSystem = default!;
-
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<TemperatureArtifactComponent, ArtifactActivatedEvent>(OnActivate);
- }
-
- private void OnActivate(EntityUid uid, TemperatureArtifactComponent component, ArtifactActivatedEvent args)
- {
- var transform = Transform(uid);
-
- var center = _atmosphereSystem.GetContainingMixture(uid, false, true);
- if (center == null)
- return;
- UpdateTileTemperature(component, center);
-
- if (component.AffectAdjacentTiles && transform.GridUid != null)
- {
- var enumerator = _atmosphereSystem.GetAdjacentTileMixtures(transform.GridUid.Value,
- _transformSystem.GetGridOrMapTilePosition(uid, transform), excite: true);
-
- while (enumerator.MoveNext(out var mixture))
- {
- UpdateTileTemperature(component, mixture);
- }
- }
- }
-
- private void UpdateTileTemperature(TemperatureArtifactComponent component, GasMixture environment)
- {
- var dif = component.TargetTemperature - environment.Temperature;
- var absDif = Math.Abs(dif);
- var step = Math.Min(absDif, component.SpawnTemperature);
- environment.Temperature += dif > 0 ? step : -step;
- }
-}
+++ /dev/null
-using System.Numerics;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Maps;
-using Content.Shared.Physics;
-using Content.Shared.Throwing;
-using Robust.Shared.Map.Components;
-using Robust.Shared.Physics.Components;
-using Robust.Shared.Random;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class ThrowArtifactSystem : EntitySystem
-{
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly EntityLookupSystem _lookup = default!;
- [Dependency] private readonly ThrowingSystem _throwing = default!;
- [Dependency] private readonly TileSystem _tile = default!;
- [Dependency] private readonly SharedTransformSystem _transform = default!;
- [Dependency] private readonly SharedMapSystem _mapSystem = default!;
-
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<ThrowArtifactComponent, ArtifactActivatedEvent>(OnActivated);
- }
-
- private void OnActivated(EntityUid uid, ThrowArtifactComponent component, ArtifactActivatedEvent args)
- {
- var xform = Transform(uid);
- if (TryComp<MapGridComponent>(xform.GridUid, out var grid))
- {
- var tiles = _mapSystem.GetTilesIntersecting(
- xform.GridUid.Value,
- grid,
- Box2.CenteredAround(_transform.GetWorldPosition(xform), new Vector2(component.Range * 2, component.Range)));
-
- foreach (var tile in tiles)
- {
- if (!_random.Prob(component.TilePryChance))
- continue;
-
- _tile.PryTile(tile);
- }
- }
-
- var lookup = _lookup.GetEntitiesInRange(uid, component.Range, LookupFlags.Dynamic | LookupFlags.Sundries);
- var physQuery = GetEntityQuery<PhysicsComponent>();
- foreach (var ent in lookup)
- {
- if (physQuery.TryGetComponent(ent, out var phys)
- && (phys.CollisionMask & (int) CollisionGroup.GhostImpassable) != 0)
- continue;
-
- var tempXform = Transform(ent);
-
- var foo = _transform.GetMapCoordinates(ent, xform: tempXform).Position - _transform.GetMapCoordinates(uid, xform: xform).Position;
- _throwing.TryThrow(ent, foo*2, component.ThrowStrength, uid, 0);
- }
- }
-}
+++ /dev/null
-using Content.Server.Explosion.EntitySystems;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-/// <summary>
-/// This handles <see cref="TriggerArtifactComponent"/>
-/// </summary>
-public sealed class TriggerArtifactSystem : EntitySystem
-{
- [Dependency] private readonly TriggerSystem _trigger = default!;
-
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<TriggerArtifactComponent, ArtifactActivatedEvent>(OnArtifactActivated);
- }
-
- private void OnArtifactActivated(EntityUid uid, TriggerArtifactComponent component, ArtifactActivatedEvent args)
- {
- _trigger.Trigger(uid, args.Activator);
- }
-}
+++ /dev/null
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-
-/// <summary>
-/// Invokes when artifact was successfully activated.
-/// Used to start attached effects.
-/// </summary>
-public sealed class ArtifactActivatedEvent : EntityEventArgs
-{
- /// <summary>
- /// Entity that activate this artifact.
- /// Usually player, but can also be another object.
- /// </summary>
- public EntityUid? Activator;
-}
-
-/// <summary>
-/// Force to randomize artifact triggers.
-/// </summary>
-public sealed class ArtifactNodeEnteredEvent : EntityEventArgs
-{
- /// <summary>
- /// An entity-specific seed that can be used to
- /// generate random values.
- /// </summary>
- public readonly int RandomSeed;
-
- public ArtifactNodeEnteredEvent(int randomSeed)
- {
- RandomSeed = randomSeed;
- }
-}
+++ /dev/null
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-/// Triggers when an artifact is anchored
-/// </summary>
-/// <remarks>
-/// Not every trigger can be a winner
-/// </remarks>
-[RegisterComponent]
-public sealed partial class ArtifactAnchorTriggerComponent : Component
-{
-
-}
+++ /dev/null
-using Content.Shared.Damage.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-/// Triggers when a certain threshold of damage of certain types is reached
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactDamageTriggerComponent : Component
-{
- /// <summary>
- /// What damage types are accumulated for the trigger?
- /// </summary>
- [DataField("damageTypes", customTypeSerializer: typeof(PrototypeIdListSerializer<DamageTypePrototype>))]
- public List<string>? DamageTypes;
-
- /// <summary>
- /// What threshold has to be reached before it is activated?
- /// </summary>
- [DataField("damageThreshold", required: true)]
- public float DamageThreshold;
-
- /// <summary>
- /// How much damage has been accumulated on the artifact so far
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- public float AccumulatedDamage = 0;
-}
+++ /dev/null
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-/// Triggers when a nearby entity dies
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactDeathTriggerComponent : Component
-{
- /// <summary>
- /// How close to the death the artifact has to be for it to trigger.
- /// </summary>
- [DataField("range")]
- public float Range = 15f;
-}
+++ /dev/null
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-/// Activate artifact when it contacted with an electricity source.
-/// It could be connected MV cables, stun baton or multi tool.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactElectricityTriggerComponent : Component
-{
- /// <summary>
- /// How much power should artifact receive to operate.
- /// </summary>
- [DataField("minPower")]
- public float MinPower = 400;
-}
+++ /dev/null
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-/// Triggers when the artifact is examined.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactExamineTriggerComponent : Component
-{
-
-}
+++ /dev/null
-using Content.Shared.Atmos;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-/// Activates artifact when it surrounded by certain gas.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactGasTriggerComponent : Component
-{
- /// <summary>
- /// List of possible activation gases to pick on startup.
- /// </summary>
- [DataField("possibleGas")]
- public List<Gas> PossibleGases = new()
- {
- Gas.Oxygen,
- Gas.Plasma,
- Gas.Nitrogen,
- Gas.CarbonDioxide,
- Gas.Ammonia,
- Gas.NitrousOxide
- };
-
- /// <summary>
- /// Gas id that will activate artifact.
- /// </summary>
- [DataField("gas")]
- [ViewVariables(VVAccess.ReadWrite)]
- public Gas? ActivationGas;
-
- /// <summary>
- /// How many moles of gas should be present in room to activate artifact.
- /// </summary>
- [DataField("moles")]
- [ViewVariables(VVAccess.ReadWrite)]
- public float ActivationMoles = Atmospherics.MolesCellStandard * 0.1f;
-}
+++ /dev/null
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-// TODO: This should probably be generalized for cold temperature too,
-// but right now there is no sane way to make a freezer.
-
-/// <summary>
-/// Triggers artifact if its in hot environment or
-/// has contacted with a hot object (lit welder, lighter, etc).
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactHeatTriggerComponent : Component
-{
- /// <summary>
- /// Minimal surrounding gas temperature to trigger artifact.
- /// Around 100 degrees celsius by default.
- /// Doesn't affect hot items temperature.
- /// </summary>
- [DataField("activationTemperature")]
- [ViewVariables(VVAccess.ReadWrite)]
- public float ActivationTemperature = 373;
-
- /// <summary>
- /// Should artifact be activated by hot items (welders, lighter, etc)?
- /// </summary>
- [DataField("activateHot")]
- [ViewVariables(VVAccess.ReadWrite)]
- public bool ActivateHotItems = true;
-}
+++ /dev/null
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-/// Activate artifact by touching, attacking or pulling it.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactInteractionTriggerComponent : Component
-{
- /// <summary>
- /// Should artifact be activated just by touching with empty hand?
- /// </summary>
- [DataField("emptyHandActivation")]
- [ViewVariables(VVAccess.ReadWrite)]
- public bool EmptyHandActivation = true;
-
- /// <summary>
- /// Should artifact be activated by melee attacking?
- /// </summary>
- [DataField("attackActivation")]
- [ViewVariables(VVAccess.ReadWrite)]
- public bool AttackActivation = true;
-
- /// <summary>
- /// Should artifact be activated by starting pulling it?
- /// </summary>
- [DataField("pullActivation")]
- [ViewVariables(VVAccess.ReadWrite)]
- public bool PullActivation = true;
-}
+++ /dev/null
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-/// Triggers when the artifact lands after being thrown.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactLandTriggerComponent : Component
-{
-}
+++ /dev/null
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-/// Triggers when the salvage magnet is activated
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactMagnetTriggerComponent : Component
-{
- /// <summary>
- /// how close to the magnet do you have to be?
- /// </summary>
- [DataField("range")]
- public float Range = 40f;
-
- /// <summary>
- /// How close do active magboots have to be?
- /// This is smaller because they are weaker magnets
- /// </summary>
- [DataField("magbootRange")]
- public float MagbootRange = 2f;
-}
+++ /dev/null
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-/// Triggers when an item artifact is microwaved.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactMicrowaveTriggerComponent : Component
-{
-}
+++ /dev/null
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-/// Triggers when an instrument is played nearby
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactMusicTriggerComponent : Component
-{
- /// <summary>
- /// how close does the artifact have to be to the instrument to activate
- /// </summary>
- [DataField("range")]
- public float Range = 5;
-}
+++ /dev/null
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-/// Triggers when a certain pressure threshold is hit
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactPressureTriggerComponent : Component
-{
- /// <summary>
- /// The lower-end pressure threshold
- /// </summary>
- [DataField("minPressureThreshold")]
- public float? MinPressureThreshold;
-
- /// <summary>
- /// The higher-end pressure threshold
- /// </summary>
- [DataField("maxPressureThreshold")]
- public float? MaxPressureThreshold;
-}
+++ /dev/null
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-/// Will try to activate artifact periodically.
-/// Doesn't used for random artifacts, can be spawned by admins.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactTimerTriggerComponent : Component
-{
- /// <summary>
- /// Time between artifact activation attempts.
- /// </summary>
- [DataField("rate")]
- [ViewVariables(VVAccess.ReadWrite)]
- public TimeSpan ActivationRate = TimeSpan.FromSeconds(5.0f);
-
- /// <summary>
- /// Last time when artifact was activated.
- /// </summary>
- public TimeSpan LastActivation;
-}
+++ /dev/null
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-public sealed class ArtifactAnchorTriggerSystem : EntitySystem
-{
- [Dependency] private readonly ArtifactSystem _artifact = default!;
-
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<ArtifactAnchorTriggerComponent, AnchorStateChangedEvent>(OnAnchorStateChanged);
- }
-
- private void OnAnchorStateChanged(EntityUid uid, ArtifactAnchorTriggerComponent component, ref AnchorStateChangedEvent args)
- {
- if (args.Detaching)
- return;
-
- _artifact.TryActivateArtifact(uid);
- }
-}
+++ /dev/null
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-using Content.Shared.Damage;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-public sealed class ArtifactDamageTriggerSystem : EntitySystem
-{
- [Dependency] private readonly ArtifactSystem _artifact = default!;
-
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<ArtifactDamageTriggerComponent, DamageChangedEvent>(OnDamageChanged);
- }
-
- private void OnDamageChanged(EntityUid uid, ArtifactDamageTriggerComponent component, DamageChangedEvent args)
- {
- if (!args.DamageIncreased)
- return;
-
- if (args.DamageDelta == null)
- return;
-
- foreach (var (type, amount) in args.DamageDelta.DamageDict)
- {
- if (component.DamageTypes != null && !component.DamageTypes.Contains(type))
- continue;
-
- component.AccumulatedDamage += (float) amount;
- }
-
- if (component.AccumulatedDamage >= component.DamageThreshold)
- _artifact.TryActivateArtifact(uid, args.Origin);
- }
-}
+++ /dev/null
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-using Content.Shared.Mobs;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-public sealed class ArtifactDeathTriggerSystem : EntitySystem
-{
- [Dependency] private readonly ArtifactSystem _artifact = default!;
-
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<MobStateChangedEvent>(OnMobStateChanged);
- }
-
- private void OnMobStateChanged(MobStateChangedEvent ev)
- {
- if (ev.NewMobState != MobState.Dead)
- return;
-
- var deathXform = Transform(ev.Target);
-
- var toActivate = new List<Entity<ArtifactDeathTriggerComponent>>();
- var query = EntityQueryEnumerator<ArtifactDeathTriggerComponent, TransformComponent>();
- while (query.MoveNext(out var uid, out var trigger, out var xform))
- {
- if (!deathXform.Coordinates.TryDistance(EntityManager, xform.Coordinates, out var distance))
- continue;
-
- if (distance > trigger.Range)
- continue;
-
- toActivate.Add((uid, trigger));
- }
-
- foreach (var a in toActivate)
- {
- _artifact.TryActivateArtifact(a);
- }
- }
-}
+++ /dev/null
-using Content.Server.Power.Components;
-using Content.Server.Power.Events;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-using Content.Shared.Interaction;
-using Content.Shared.Tools.Components;
-using Content.Shared.Tools.Systems;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-public sealed class ArtifactElectricityTriggerSystem : EntitySystem
-{
- [Dependency] private readonly ArtifactSystem _artifactSystem = default!;
- [Dependency] private readonly SharedToolSystem _toolSystem = default!;
-
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<ArtifactElectricityTriggerComponent, InteractUsingEvent>(OnInteractUsing);
- SubscribeLocalEvent<ArtifactElectricityTriggerComponent, PowerPulseEvent>(OnPowerPulse);
- }
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- List<Entity<ArtifactComponent>> toUpdate = new();
- var query = EntityQueryEnumerator<ArtifactElectricityTriggerComponent, PowerConsumerComponent, ArtifactComponent>();
- while (query.MoveNext(out var uid, out var trigger, out var power, out var artifact))
- {
- if (power.ReceivedPower <= trigger.MinPower)
- continue;
-
- toUpdate.Add((uid, artifact));
- }
-
- foreach (var a in toUpdate)
- {
- _artifactSystem.TryActivateArtifact(a, null, a);
- }
- }
-
- private void OnInteractUsing(EntityUid uid, ArtifactElectricityTriggerComponent component, InteractUsingEvent args)
- {
- if (args.Handled)
- return;
-
- if (!_toolSystem.HasQuality(args.Used, SharedToolSystem.PulseQuality))
- return;
-
- args.Handled = _artifactSystem.TryActivateArtifact(uid, args.User);
- }
-
- private void OnPowerPulse(EntityUid uid, ArtifactElectricityTriggerComponent component, PowerPulseEvent args)
- {
- _artifactSystem.TryActivateArtifact(uid, args.User);
- }
-}
+++ /dev/null
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-using Content.Shared.Examine;
-using Content.Shared.Ghost;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-public sealed class ArtifactExamineTriggerSystem : EntitySystem
-{
- [Dependency] private readonly ArtifactSystem _artifact = default!;
-
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<ArtifactExamineTriggerComponent, ExaminedEvent>(OnExamine);
- }
-
- private void OnExamine(EntityUid uid, ArtifactExamineTriggerComponent component, ExaminedEvent args)
- {
- // Prevent ghosts from activating this trigger unless they have CanGhostInteract
- if (TryComp<GhostComponent>(args.Examiner, out var ghost) && !ghost.CanGhostInteract)
- return;
-
- _artifact.TryActivateArtifact(uid);
- }
-}
+++ /dev/null
-using Content.Server.Atmos.EntitySystems;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-public sealed class ArtifactGasTriggerSystem : EntitySystem
-{
- [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
- [Dependency] private readonly ArtifactSystem _artifactSystem = default!;
-
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<ArtifactGasTriggerComponent, ArtifactNodeEnteredEvent>(OnRandomizeTrigger);
- }
-
- private void OnRandomizeTrigger(EntityUid uid, ArtifactGasTriggerComponent component, ArtifactNodeEnteredEvent args)
- {
- if (component.ActivationGas != null)
- return;
-
- var gas = component.PossibleGases[args.RandomSeed % component.PossibleGases.Count];
- component.ActivationGas = gas;
- }
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- List<Entity<ArtifactComponent>> toUpdate = new();
- var query = EntityQueryEnumerator<ArtifactGasTriggerComponent, ArtifactComponent, TransformComponent>();
- while (query.MoveNext(out var uid, out var trigger, out var artifact, out var transform))
- {
- if (trigger.ActivationGas == null)
- continue;
-
- var environment = _atmosphereSystem.GetTileMixture((uid, transform));
- if (environment == null)
- continue;
-
- // check if outside there is enough moles to activate artifact
- var moles = environment.GetMoles(trigger.ActivationGas.Value);
- if (moles < trigger.ActivationMoles)
- continue;
-
- toUpdate.Add((uid, artifact));
- }
-
- foreach (var a in toUpdate)
- {
- _artifactSystem.TryActivateArtifact(a, null, a);
- }
- }
-}
+++ /dev/null
-using Content.Server.Atmos.EntitySystems;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-using Content.Shared.Interaction;
-using Content.Shared.Temperature;
-using Content.Shared.Weapons.Melee.Events;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-public sealed class ArtifactHeatTriggerSystem : EntitySystem
-{
- [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
- [Dependency] private readonly ArtifactSystem _artifactSystem = default!;
-
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<ArtifactHeatTriggerComponent, AttackedEvent>(OnAttacked);
- SubscribeLocalEvent<ArtifactHeatTriggerComponent, InteractUsingEvent>(OnUsing);
- }
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- List<Entity<ArtifactComponent>> toUpdate = new();
- var query = EntityQueryEnumerator<ArtifactHeatTriggerComponent, TransformComponent, ArtifactComponent>();
- while (query.MoveNext(out var uid, out var trigger, out var transform, out var artifact))
- {
- var environment = _atmosphereSystem.GetTileMixture((uid, transform));
- if (environment == null)
- continue;
-
- if (environment.Temperature < trigger.ActivationTemperature)
- continue;
-
- toUpdate.Add((uid, artifact));
- }
-
- foreach (var a in toUpdate)
- {
- _artifactSystem.TryActivateArtifact(a, null, a);
- }
- }
-
- private void OnAttacked(EntityUid uid, ArtifactHeatTriggerComponent component, AttackedEvent args)
- {
- if (!component.ActivateHotItems || !CheckHot(args.Used))
- return;
- _artifactSystem.TryActivateArtifact(uid, args.User);
- }
-
- private void OnUsing(EntityUid uid, ArtifactHeatTriggerComponent component, InteractUsingEvent args)
- {
- if (args.Handled)
- return;
-
- if (!component.ActivateHotItems || !CheckHot(args.Used))
- return;
- args.Handled = _artifactSystem.TryActivateArtifact(uid, args.User);
- }
-
- private bool CheckHot(EntityUid usedUid)
- {
- var hotEvent = new IsHotEvent();
- RaiseLocalEvent(usedUid, hotEvent);
- return hotEvent.IsHot;
- }
-}
+++ /dev/null
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-using Content.Shared.Interaction;
-using Content.Shared.Movement.Pulling.Events;
-using Content.Shared.Weapons.Melee.Events;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-public sealed class ArtifactInteractionTriggerSystem : EntitySystem
-{
- [Dependency] private readonly ArtifactSystem _artifactSystem = default!;
-
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<ArtifactInteractionTriggerComponent, PullStartedMessage>(OnPull);
- SubscribeLocalEvent<ArtifactInteractionTriggerComponent, AttackedEvent>(OnAttack);
- SubscribeLocalEvent<ArtifactInteractionTriggerComponent, InteractHandEvent>(OnInteract);
- }
-
- private void OnPull(EntityUid uid, ArtifactInteractionTriggerComponent component, PullStartedMessage args)
- {
- if (!component.PullActivation)
- return;
-
- _artifactSystem.TryActivateArtifact(uid, args.PullerUid);
- }
-
- private void OnAttack(EntityUid uid, ArtifactInteractionTriggerComponent component, AttackedEvent args)
- {
- if (!component.AttackActivation)
- return;
-
- _artifactSystem.TryActivateArtifact(uid, args.User);
- }
-
- private void OnInteract(EntityUid uid, ArtifactInteractionTriggerComponent component, InteractHandEvent args)
- {
- if (args.Handled)
- return;
-
- if (!component.EmptyHandActivation)
- return;
-
- args.Handled = _artifactSystem.TryActivateArtifact(uid, args.User);
- }
-}
+++ /dev/null
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-using Content.Shared.Throwing;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-public sealed class ArtifactLandSystem : EntitySystem
-{
- [Dependency] private readonly ArtifactSystem _artifact = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent<ArtifactLandTriggerComponent, LandEvent>(OnLand);
- }
-
- private void OnLand(EntityUid uid, ArtifactLandTriggerComponent component, ref LandEvent args)
- {
- _artifact.TryActivateArtifact(uid, args.User);
- }
-}
+++ /dev/null
-using System.Linq;
-using Content.Server.Salvage;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-using Content.Shared.Clothing;
-using Content.Shared.Item.ItemToggle.Components;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-/// <summary>
-/// This handles artifacts that are activated by magnets, both salvage and magboots.
-/// </summary>
-public sealed class ArtifactMagnetTriggerSystem : EntitySystem
-{
- [Dependency] private readonly ArtifactSystem _artifact = default!;
-
- private readonly List<EntityUid> _toActivate = new();
-
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<SalvageMagnetActivatedEvent>(OnMagnetActivated);
- }
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- if (!EntityQuery<ArtifactMagnetTriggerComponent>().Any())
- return;
-
- _toActivate.Clear();
-
- //assume that there's more magboots than artifacts
- var query = EntityQueryEnumerator<MagbootsComponent, TransformComponent, ItemToggleComponent>();
- while (query.MoveNext(out _, out var magboot, out var magXform, out var toggle))
- {
- if (!toggle.Activated)
- continue;
-
- var artiQuery = EntityQueryEnumerator<ArtifactMagnetTriggerComponent, TransformComponent>();
- while (artiQuery.MoveNext(out var artifactUid, out var trigger, out var xform))
- {
- if (!magXform.Coordinates.TryDistance(EntityManager, xform.Coordinates, out var distance))
- continue;
-
- if (distance > trigger.MagbootRange)
- continue;
-
- _toActivate.Add(artifactUid);
- }
- }
-
- foreach (var a in _toActivate)
- {
- _artifact.TryActivateArtifact(a);
- }
- }
-
- private void OnMagnetActivated(ref SalvageMagnetActivatedEvent ev)
- {
- var magXform = Transform(ev.Magnet);
-
- var query = EntityQueryEnumerator<ArtifactMagnetTriggerComponent, TransformComponent>();
- while (query.MoveNext(out var uid, out var artifact, out var xform))
- {
- if (!magXform.Coordinates.TryDistance(EntityManager, xform.Coordinates, out var distance))
- continue;
-
- if (distance > artifact.Range)
- continue;
-
- _toActivate.Add(uid);
- }
-
- foreach (var a in _toActivate)
- {
- _artifact.TryActivateArtifact(a);
- }
- }
-}
+++ /dev/null
-using Content.Server.Kitchen.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-public sealed class ArtifactMicrowaveTriggerSystem : EntitySystem
-{
- [Dependency] private readonly ArtifactSystem _artifact = default!;
-
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<ArtifactMicrowaveTriggerComponent, BeingMicrowavedEvent>(OnMicrowaved);
- }
-
- private void OnMicrowaved(EntityUid uid, ArtifactMicrowaveTriggerComponent component, BeingMicrowavedEvent args)
- {
- _artifact.TryActivateArtifact(uid);
- }
-}
+++ /dev/null
-using System.Linq;
-using Content.Server.Instruments;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-/// <summary>
-/// This handles activating an artifact when music is playing nearby
-/// </summary>
-public sealed class ArtifactMusicTriggerSystem : EntitySystem
-{
- [Dependency] private readonly ArtifactSystem _artifact = default!;
-
- private readonly List<Entity<ArtifactMusicTriggerComponent, TransformComponent>> _artifacts = new();
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- _artifacts.Clear();
- var artifactQuery = EntityQueryEnumerator<ArtifactMusicTriggerComponent, TransformComponent>();
- while (artifactQuery.MoveNext(out var uid, out var trigger, out var xform))
- {
- _artifacts.Add((uid, trigger, xform));
- }
-
- if (!_artifacts.Any())
- return;
-
- List<EntityUid> toActivate = new();
- var query = EntityQueryEnumerator<ActiveInstrumentComponent, TransformComponent>();
-
- //assume that there's more instruments than artifacts
- while (query.MoveNext(out _, out var instXform))
- {
- foreach (var (uid, trigger, xform) in _artifacts)
- {
- if (!instXform.Coordinates.TryDistance(EntityManager, xform.Coordinates, out var distance))
- continue;
-
- if (distance > trigger.Range)
- continue;
-
- toActivate.Add(uid);
- }
- }
-
- foreach (var a in toActivate)
- {
- _artifact.TryActivateArtifact(a);
- }
- }
-}
+++ /dev/null
-using Content.Server.Atmos.EntitySystems;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-/// <summary>
-/// This handles activation upon certain pressure thresholds.
-/// </summary>
-public sealed class ArtifactPressureTriggerSystem : EntitySystem
-{
- [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
- [Dependency] private readonly ArtifactSystem _artifactSystem = default!;
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- List<Entity<ArtifactComponent>> toUpdate = new();
- var query = EntityQueryEnumerator<ArtifactPressureTriggerComponent, ArtifactComponent, TransformComponent>();
- while (query.MoveNext(out var uid, out var trigger, out var artifact, out var transform))
- {
- var environment = _atmosphereSystem.GetTileMixture((uid, transform));
- if (environment == null)
- continue;
-
- var pressure = environment.Pressure;
- if (pressure >= trigger.MaxPressureThreshold || pressure <= trigger.MinPressureThreshold)
- toUpdate.Add((uid, artifact));
- }
-
- foreach (var a in toUpdate)
- {
- _artifactSystem.TryActivateArtifact(a, null, a);
- }
- }
-}
+++ /dev/null
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-using Robust.Shared.Timing;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-public sealed class ArtifactTimerTriggerSystem : EntitySystem
-{
- [Dependency] private readonly IGameTiming _time = default!;
- [Dependency] private readonly ArtifactSystem _artifactSystem = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent<ArtifactTimerTriggerComponent, ComponentStartup>(OnStartup);
- }
-
- private void OnStartup(EntityUid uid, ArtifactTimerTriggerComponent component, ComponentStartup args)
- {
- component.LastActivation = _time.CurTime;
- }
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- List<Entity<ArtifactComponent>> toUpdate = new();
- var query = EntityQueryEnumerator<ArtifactTimerTriggerComponent, ArtifactComponent>();
- while (query.MoveNext(out var uid, out var trigger, out var artifact))
- {
- var timeDif = _time.CurTime - trigger.LastActivation;
- if (timeDif <= trigger.ActivationRate)
- continue;
-
- toUpdate.Add((uid, artifact));
- trigger.LastActivation = _time.CurTime;
- }
-
- foreach (var a in toUpdate)
- {
- _artifactSystem.TryActivateArtifact(a, null, a);
- }
- }
-}
/// Logs related to botany, such as planting and harvesting crops
/// </summary>
Botany = 100,
+ /// <summary>
+ /// Artifact node got activated.
+ /// </summary>
+ ArtifactNode = 101
}
{
public sealed class ChemicalReactionSystem : EntitySystem
{
+ /// <summary>
+ /// Foam reaction protoId.
+ /// </summary>
+ public static readonly ProtoId<ReactionPrototype> FoamReaction = "Foam";
+
/// <summary>
/// The maximum number of reactions that may occur when a solution is changed.
/// </summary>
if (!TryComp(uid, out ReactiveComponent? reactive))
return;
+ // custom event for bypassing reactivecomponent stuff
+ var ev = new ReactionEntityEvent(method, proto, reagentQuantity, source);
+ RaiseLocalEvent(uid, ref ev);
+
// If we have a source solution, use the reagent quantity we have left. Otherwise, use the reaction volume specified.
var args = new EntityEffectReagentArgs(uid, EntityManager, null, source, source?.GetReagentQuantity(reagentQuantity.Reagent) ?? reagentQuantity.Quantity, proto, method, 1f);
Injection,
Ingestion,
}
+
+[ByRefEvent]
+public readonly record struct ReactionEntityEvent(
+ ReactionMethod Method,
+ ReagentPrototype Reagent,
+ ReagentQuantity ReagentQuantity,
+ Solution? Source
+);
damage.DamageDict.Add(typeId, damageValue);
}
- TryChangeDamage(uid, damage, interruptsDoAfters: false);
+ TryChangeDamage(uid, damage, interruptsDoAfters: false, origin: args.Origin);
}
private void OnRejuvenate(EntityUid uid, DamageableComponent component, RejuvenateEvent args)
public BitArray FilteredChannels { get; set; } = new(RobustMidiEvent.MaxChannels, true);
}
+/// <summary>
+/// Component that indicates that musical instrument was activated (ui opened).
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ActiveInstrumentComponent : Component;
+
[Serializable, NetSerializable]
public sealed class InstrumentComponentState : ComponentState
{
-namespace Content.Shared.Radiation.Events;
+namespace Content.Shared.Radiation.Events;
/// <summary>
/// Raised on entity when it was irradiated
/// by some radiation source.
/// </summary>
-public readonly record struct OnIrradiatedEvent(float FrameTime, float RadsPerSecond)
+public readonly record struct OnIrradiatedEvent(float FrameTime, float RadsPerSecond, EntityUid Origin)
{
public readonly float FrameTime = FrameTime;
public readonly float RadsPerSecond = RadsPerSecond;
+ public readonly EntityUid Origin = Origin;
+
public float TotalRads => RadsPerSecond * FrameTime;
}
--- /dev/null
+using Content.Shared.Destructible.Thresholds;
+using Content.Shared.EntityTable.EntitySelectors;
+using Content.Shared.Xenoarchaeology.Artifact.Prototypes;
+using Robust.Shared.Audio;
+using Robust.Shared.Containers;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.Components;
+
+/// <summary>
+/// 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]
+public sealed partial class XenoArtifactComponent : Component
+{
+ public static string NodeContainerId = "node-container";
+
+ /// <summary>
+ /// Marker, if nodes graph should be generated for artifact.
+ /// </summary>
+ [DataField]
+ public bool IsGenerationRequired = true;
+
+ /// <summary>
+ /// Container for artifact graph node entities.
+ /// </summary>
+ [ViewVariables]
+ public Container NodeContainer = default!;
+
+ /// <summary>
+ /// The nodes in this artifact that are currently "active."
+ /// This is cached and updated when nodes are removed, added, or unlocked.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public List<NetEntity> CachedActiveNodes = new();
+
+ /// <summary>
+ /// Cache of interconnected node chunks - segments.
+ /// This is cached and updated when nodes are removed, added, or unlocked.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public List<List<NetEntity>> CachedSegments = new();
+
+ /// <summary>
+ /// Marker, if true - node activations should not happen.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool Suppressed;
+
+ /// <summary>
+ /// A multiplier applied to the calculated point value
+ /// to determine the monetary value of the artifact.
+ /// </summary>
+ [DataField]
+ public float PriceMultiplier = 0.10f;
+
+ #region Unlocking
+ /// <summary>
+ /// How long does the unlocking state last by default.
+ /// </summary>
+ [DataField]
+ public TimeSpan UnlockStateDuration = TimeSpan.FromSeconds(6);
+
+ /// <summary>
+ /// By how much unlocking state should be prolonged for each node that was unlocked.
+ /// </summary>
+ [DataField]
+ public TimeSpan UnlockStateIncrementPerNode = TimeSpan.FromSeconds(5);
+
+ /// <summary>
+ /// Minimum waiting time between unlock states.
+ /// </summary>
+ [DataField]
+ public TimeSpan UnlockStateRefractory = TimeSpan.FromSeconds(10);
+
+ /// <summary>
+ /// When next unlock session can be triggered.
+ /// </summary>
+ [DataField, AutoPausedField]
+ public TimeSpan NextUnlockTime;
+ #endregion
+
+ // NOTE: you should not be accessing any of these values directly. Use the methods in SharedXenoArtifactSystem.Graph
+ #region Graph
+ /// <summary>
+ /// List of all nodes currently on this artifact.
+ /// Indexes are used as a lookup table for <see cref="NodeAdjacencyMatrix"/>.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public NetEntity?[] NodeVertices = [];
+
+ /// <summary>
+ /// Adjacency matrix that stores connections between this artifact's nodes.
+ /// A value of "true" denotes an directed edge from node1 to node2, where the location of the vertex is (node1, node2)
+ /// A value of "false" denotes no edge.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public List<List<bool>> NodeAdjacencyMatrix = new();
+
+ public int NodeAdjacencyMatrixRows => NodeAdjacencyMatrix.Count;
+ public int NodeAdjacencyMatrixColumns => NodeAdjacencyMatrix.TryGetValue(0, out var value) ? value.Count : 0;
+ #endregion
+
+ #region GenerationInfo
+
+ /// <summary>
+ /// The total number of nodes that make up this artifact.
+ /// </summary>
+ [DataField]
+ public MinMax NodeCount = new(10, 16);
+
+ /// <summary>
+ /// The amount of nodes that go in each segment.
+ /// A segment is an interconnected series of nodes.
+ /// </summary>
+ [DataField]
+ public MinMax SegmentSize = new(5, 8);
+
+ /// <summary>
+ /// For each "layer" in a segment (set of nodes with equal depth), how many will we generate?
+ /// </summary>
+ [DataField]
+ public MinMax NodesPerSegmentLayer = new(1, 3);
+
+ /// <summary>
+ /// How man nodes can be randomly added on top of usual distribution (per layer).
+ /// </summary>
+ [DataField]
+ public MinMax ScatterPerLayer = new(0, 2);
+
+ /// <summary>
+ /// Effects that can be used during this artifact generation.
+ /// </summary>
+ [DataField]
+ public EntityTableSelector EffectsTable = new NestedSelector
+ {
+ TableId = "XenoArtifactEffectsDefaultTable"
+ };
+
+ /// <summary>
+ /// Triggers that can be used during this artefact generation.
+ /// </summary>
+ [DataField]
+ public ProtoId<WeightedRandomXenoArchTriggerPrototype> TriggerWeights = "DefaultTriggers";
+ #endregion
+
+ /// <summary>
+ /// Sound effect to be played when artifact node is force-activated.
+ /// </summary>
+ [DataField]
+ public SoundSpecifier? ForceActivationSoundSpecifier = new SoundCollectionSpecifier("ArtifactForceActivation")
+ {
+ Params = new()
+ {
+ Variation = 0.1f
+ }
+ };
+}
--- /dev/null
+using Content.Shared.Destructible.Thresholds;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.Components;
+
+/// <summary>
+/// Stores metadata about a particular artifact node
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedXenoArtifactSystem)), AutoGenerateComponentState]
+public sealed partial class XenoArtifactNodeComponent : Component
+{
+ /// <summary>
+ /// Depth within the graph generation.
+ /// Used for sorting.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public int Depth;
+
+ /// <summary>
+ /// Denotes whether an artifact node has been activated at least once (through the required triggers).
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool Locked = true;
+
+ /// <summary>
+ /// List of trigger descriptions that this node require for activation.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public LocId? TriggerTip;
+
+ /// <summary>
+ /// The entity whose graph this node is a part of.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public NetEntity? Attached;
+
+ #region Durability
+ /// <summary>
+ /// Marker, is durability of node degraded or not.
+ /// </summary>
+ public bool Degraded => Durability <= 0;
+
+ /// <summary>
+ /// The amount of generic activations a node has left before becoming fully degraded and useless.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public int Durability;
+
+ /// <summary>
+ /// The maximum amount of times a node can be generically activated before becoming useless
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public int MaxDurability = 5;
+
+ /// <summary>
+ /// The variance from MaxDurability present when a node is created.
+ /// </summary>
+ [DataField]
+ public MinMax MaxDurabilityCanDecreaseBy = new(0, 2);
+ #endregion
+
+ #region Research
+ /// <summary>
+ /// The amount of points a node is worth with no scaling
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public float BasePointValue = 5000;
+
+ /// <summary>
+ /// Amount of points available currently for extracting.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public int ResearchValue;
+
+ /// <summary>
+ /// Amount of points already extracted from node.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public int ConsumedResearchValue;
+ #endregion
+}
--- /dev/null
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.Components;
+
+/// <summary>
+/// This is used for tracking the nodes which have been triggered during a particular unlocking state.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class XenoArtifactUnlockingComponent : Component
+{
+ /// <summary>
+ /// Indexes corresponding to all of the nodes that have been triggered
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public HashSet<int> TriggeredNodeIndexes = new();
+
+ /// <summary>
+ /// The time at which the unlocking state ends.
+ /// </summary>
+ [DataField, AutoNetworkedField, AutoPausedField]
+ public TimeSpan EndTime;
+
+ /// <summary>
+ /// The sound that plays when an artifact finishes unlocking successfully (with node unlocked).
+ /// </summary>
+ [DataField]
+ public SoundSpecifier UnlockActivationSuccessfulSound = new SoundCollectionSpecifier("ArtifactUnlockingActivationSuccess")
+ {
+ Params = new()
+ {
+ Variation = 0.1f,
+ Volume = 3f
+ }
+ };
+
+ /// <summary>
+ /// The sound that plays when artifact finishes unlocking non-successfully.
+ /// </summary>
+ [DataField]
+ public SoundSpecifier? UnlockActivationFailedSound = new SoundCollectionSpecifier("ArtifactUnlockActivationFailure")
+ {
+ Params = new()
+ {
+ Variation = 0.1f
+ }
+ };
+}
--- /dev/null
+using Content.Shared.Random;
+using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.Prototypes;
+
+/// <summary> Proto for xeno artifact triggers - markers, which event could trigger node to unlock it. </summary>
+[Prototype]
+public sealed partial class XenoArchTriggerPrototype : IPrototype
+{
+ /// <inheritdoc/>
+ [IdDataField]
+ public string ID { get; } = default!;
+
+ /// <summary>
+ /// Tip for user on how to activate this trigger.
+ /// </summary>
+ [DataField]
+ public LocId Tip;
+
+ /// <summary>
+ /// Whitelist, describing for which subtype of artifacts this trigger could be used.
+ /// </summary>
+ [DataField]
+ public EntityWhitelist? Whitelist;
+
+ /// <summary>
+ /// List of components that represent ways to trigger node.
+ /// </summary>
+ [DataField]
+ public ComponentRegistry Components = new();
+}
+
+/// <summary>
+/// Container for list of xeno artifact triggers and their respective weights to be used in case randomly rolling trigger is required.
+/// </summary>
+[Prototype]
+public sealed partial class WeightedRandomXenoArchTriggerPrototype : IWeightedRandomPrototype
+{
+ [IdDataField]
+ public string ID { get; private set; } = default!;
+
+ [DataField(customTypeSerializer: typeof(PrototypeIdDictionarySerializer<float, XenoArchTriggerPrototype>))]
+ public Dictionary<string, float> Weights { get; private set; } = new();
+}
--- /dev/null
+using System.Diagnostics.CodeAnalysis;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Xenoarchaeology.Artifact;
+
+/// <summary>
+/// User-friendly API for viewing and modifying the complex graph relationship in XenoArtifacts
+/// </summary>
+public abstract partial class SharedXenoArtifactSystem
+{
+ /// <summary>
+ /// Gets the index, corresponding to a given node, throwing if the node is not present.
+ /// </summary>
+ public int GetIndex(Entity<XenoArtifactComponent> ent, EntityUid node)
+ {
+ if (TryGetIndex((ent, ent), node, out var index))
+ {
+ return index.Value;
+ }
+
+ throw new ArgumentException($"node {ToPrettyString(node)} is not present in {ToPrettyString(ent)}");
+ }
+
+ /// <summary>
+ /// Tries to get index inside nodes collection, corresponding to a given node EntityUid.
+ /// </summary>
+ public bool TryGetIndex(Entity<XenoArtifactComponent?> ent, EntityUid node, [NotNullWhen(true)] out int? index)
+ {
+ index = null;
+ if (!Resolve(ent, ref ent.Comp))
+ return false;
+
+ for (var i = 0; i < ent.Comp.NodeVertices.Length; i++)
+ {
+ if (!TryGetNode(ent, i, out var iNode))
+ continue;
+
+ if (node != iNode.Value.Owner)
+ continue;
+
+ index = i;
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Gets node entity with node component from artifact by index of node inside artifact nodes collection.
+ /// </summary>
+ /// <exception cref="ArgumentException">Throws if requested index doesn't exist on artifact. </exception>
+ public Entity<XenoArtifactNodeComponent> GetNode(Entity<XenoArtifactComponent> ent, int index)
+ {
+ if (ent.Comp.NodeVertices[index] is { } netUid && GetEntity(netUid) is var uid)
+ return (uid, XenoArtifactNode(uid));
+
+ throw new ArgumentException($"index {index} does not correspond to an existing node in {ToPrettyString(ent)}");
+ }
+
+ /// <summary>
+ /// Tries to get node entity with node component from artifact by index of node inside artifact nodes collection.
+ /// </summary>
+ public bool TryGetNode(Entity<XenoArtifactComponent?> ent, int index, [NotNullWhen(true)] out Entity<XenoArtifactNodeComponent>? node)
+ {
+ node = null;
+ if (!Resolve(ent, ref ent.Comp))
+ return false;
+
+ if (index < 0 || index >= ent.Comp.NodeVertices.Length)
+ return false;
+
+ if (ent.Comp.NodeVertices[index] is { } netUid && GetEntity(netUid) is var uid)
+ node = (uid, XenoArtifactNode(uid));
+
+ return node != null;
+ }
+
+ /// <summary>
+ /// Gets the index of the first empty spot in the NodeVertices array.
+ /// If there is none, resizes both arrays and returns the new index.
+ /// </summary>
+ public int GetFreeNodeIndex(Entity<XenoArtifactComponent> ent)
+ {
+ var length = ent.Comp.NodeVertices.Length;
+ for (var i = 0; i < length; i++)
+ {
+ if (ent.Comp.NodeVertices[i] == null)
+ return i;
+ }
+
+ ResizeNodeGraph(ent, length + 1);
+ return length;
+ }
+
+ /// <summary>
+ /// Extracts node entities from artifact container
+ /// (uses pre-cached <see cref="XenoArtifactComponent.NodeVertices"/> and mapping from NetEntity).
+ /// </summary>
+ public IEnumerable<Entity<XenoArtifactNodeComponent>> GetAllNodes(Entity<XenoArtifactComponent> ent)
+ {
+ foreach (var netNode in ent.Comp.NodeVertices)
+ {
+ if (TryGetEntity(netNode, out var node))
+ yield return (node.Value, XenoArtifactNode(node.Value));
+ }
+ }
+
+ /// <summary>
+ /// Extracts enumeration of all indices that artifact node container have.
+ /// </summary>
+ public IEnumerable<int> GetAllNodeIndices(Entity<XenoArtifactComponent> ent)
+ {
+ for (var i = 0; i < ent.Comp.NodeVertices.Length; i++)
+ {
+ if (ent.Comp.NodeVertices[i] is not null)
+ yield return i;
+ }
+ }
+
+ /// <summary>
+ /// Adds edge between artifact nodes - <see cref="from"/> and <see cref="to"/>
+ /// </summary>
+ /// <param name="ent">Artifact entity that contains 'from' and 'to' node entities.</param>
+ /// <param name="from">Node from which we need to draw edge. </param>
+ /// <param name="to">Node to which we need to draw edge. </param>
+ /// <param name="dirty">
+ /// Marker, if we need to recalculate caches and mark related components dirty to update on client side.
+ /// Should be disabled for initial graph creation to not recalculate cache on each node/edge.
+ /// </param>
+ /// <returns>True if adding edge was successful, false otherwise.</returns>
+ public bool AddEdge(Entity<XenoArtifactComponent?> ent, EntityUid from, EntityUid to, bool dirty = true)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return false;
+
+ if (!TryGetIndex(ent, from, out var fromIdx) ||
+ !TryGetIndex(ent, to, out var toIdx))
+ return false;
+
+ return AddEdge(ent, fromIdx.Value, toIdx.Value, dirty: dirty);
+ }
+
+ /// <summary>
+ /// Adds edge between artifact nodes by indices inside node container - <see cref="fromIdx"/> and <see cref="toIdx"/>
+ /// </summary>
+ /// <param name="ent">Artifact entity that contains 'from' and 'to' node entities.</param>
+ /// <param name="fromIdx">Node index inside artifact node container, from which we need to draw edge. </param>
+ /// <param name="toIdx">Node index inside artifact node container, to which we need to draw edge. </param>
+ /// <param name="dirty">
+ /// Marker, if we need to recalculate caches and mark related components dirty to update on client side.
+ /// Should be disabled for initial graph creation to not recalculate cache on each node/edge.
+ /// </param>
+ /// <returns>True if adding edge was successful, false otherwise.</returns>
+ public bool AddEdge(Entity<XenoArtifactComponent?> ent, int fromIdx, int toIdx, bool dirty = true)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return false;
+
+ DebugTools.Assert(fromIdx >= 0 && fromIdx < ent.Comp.NodeVertices.Length, $"fromIdx is out of bounds for fromIdx {fromIdx}");
+ DebugTools.Assert(toIdx >= 0 && toIdx < ent.Comp.NodeVertices.Length, $"toIdx is out of bounds for toIdx {toIdx}");
+
+ if (ent.Comp.NodeAdjacencyMatrix[fromIdx][toIdx])
+ return false; //Edge already exists
+
+ // TODO: add a safety check to prohibit cyclic paths.
+
+ ent.Comp.NodeAdjacencyMatrix[fromIdx][toIdx] = true;
+ if (dirty)
+ {
+ RebuildXenoArtifactMetaData(ent);
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Removes edge between artifact nodes.
+ /// </summary>
+ /// <param name="ent">Artifact entity that contains 'from' and 'to' node entities.</param>
+ /// <param name="from">Entity of node from which edge to remove is connected.</param>
+ /// <param name="to">Entity of node to which edge to remove is connected.</param>
+ /// <param name="dirty">
+ /// Marker, if we need to recalculate caches and mark related components dirty to update on client side.
+ /// Should be disabled for initial graph creation to not recalculate cache on each node/edge.
+ /// </param>
+ /// <returns>True if removed edge was successfully, false otherwise.</returns>
+ public bool RemoveEdge(Entity<XenoArtifactComponent?> ent, EntityUid from, EntityUid to, bool dirty = true)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return false;
+
+ if (!TryGetIndex(ent, from, out var fromIdx) ||
+ !TryGetIndex(ent, to, out var toIdx))
+ return false;
+
+ return RemoveEdge(ent, fromIdx.Value, toIdx.Value, dirty);
+ }
+
+ /// <summary>
+ /// Removes edge between artifact nodes.
+ /// </summary>
+ /// <param name="ent">Artifact entity that contains 'from' and 'to' node entities.</param>
+ /// <param name="fromIdx"> First node index inside artifact node container, from which we need to remove connecting edge. </param>
+ /// <param name="toIdx"> Other node index inside artifact node container, from which we need to remove connecting edge. </param>
+ /// <param name="dirty">
+ /// Marker, if we need to recalculate caches and mark related components dirty to update on client side.
+ /// Should be disabled for initial graph creation to not recalculate cache on each node/edge.
+ /// </param>
+ /// <returns>True if removed edge was successfully, false otherwise.</returns>
+ public bool RemoveEdge(Entity<XenoArtifactComponent?> ent, int fromIdx, int toIdx, bool dirty = true)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return false;
+
+ DebugTools.Assert(fromIdx >= 0 && fromIdx < ent.Comp.NodeVertices.Length, $"fromIdx is out of bounds for fromIdx {fromIdx}");
+ DebugTools.Assert(toIdx >= 0 && toIdx < ent.Comp.NodeVertices.Length, $"toIdx is out of bounds for toIdx {toIdx}");
+
+ if (!ent.Comp.NodeAdjacencyMatrix[fromIdx][toIdx])
+ return false; //Edge doesn't exist
+
+ ent.Comp.NodeAdjacencyMatrix[fromIdx][toIdx] = false;
+
+ if (dirty)
+ {
+ RebuildXenoArtifactMetaData(ent);
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Creates node entity (spawns) and adds node into artifact node container.
+ /// </summary>
+ /// <param name="ent">Artifact entity, to container of which node should be added.</param>
+ /// <param name="entProtoId">EntProtoId of node to be added.</param>
+ /// <param name="node">Created node or null.</param>
+ /// <param name="dirty">
+ /// Marker, if we need to recalculate caches and mark related components dirty to update on client side.
+ /// Should be disabled for initial graph creation to not recalculate cache on each node/edge.
+ /// </param>
+ /// <returns>True if node creation and adding was successful, false otherwise.</returns>
+ public bool AddNode(
+ Entity<XenoArtifactComponent?> ent,
+ EntProtoId entProtoId,
+ [NotNullWhen(true)] out Entity<XenoArtifactNodeComponent>? node,
+ bool dirty = true
+ )
+ {
+ node = null;
+ if (!Resolve(ent, ref ent.Comp))
+ return false;
+
+ var uid = Spawn(entProtoId);
+ node = (uid, XenoArtifactNode(uid));
+ return AddNode(ent, (node.Value, node.Value.Comp), dirty: dirty);
+ }
+
+ /// <summary>
+ /// Adds node entity to artifact node container.
+ /// </summary>
+ /// <param name="ent">Artifact entity, to container of which node should be added.</param>
+ /// <param name="node">Node entity to add.</param>
+ /// <param name="dirty">
+ /// Marker, if we need to recalculate caches and mark related components dirty to update on client side.
+ /// Should be disabled for initial graph creation to not recalculate cache on each node/edge.
+ /// </param>
+ /// <returns>True if node adding was successful, false otherwise.</returns>
+ public bool AddNode(Entity<XenoArtifactComponent?> ent, Entity<XenoArtifactNodeComponent?> node, bool dirty = true)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return false;
+
+ node.Comp ??= XenoArtifactNode(node);
+ node.Comp.Attached = GetNetEntity(ent);
+
+ var nodeIdx = GetFreeNodeIndex((ent, ent.Comp));
+ _container.Insert(node.Owner, ent.Comp.NodeContainer);
+ ent.Comp.NodeVertices[nodeIdx] = GetNetEntity(node);
+
+ Dirty(node);
+ if (dirty)
+ {
+ RebuildXenoArtifactMetaData(ent);
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Removes artifact node from artifact node container.
+ /// </summary>
+ /// <param name="ent">Artifact from container of which node should be removed</param>
+ /// <param name="node">Node entity to be removed.</param>
+ /// <param name="dirty">
+ /// Marker, if we need to recalculate caches and mark related components dirty to update on client side.
+ /// Should be disabled for initial graph creation to not recalculate cache on each node/edge.
+ /// </param>
+ /// <returns>True if node was removed successfully, false otherwise.</returns>
+ public bool RemoveNode(Entity<XenoArtifactComponent?> ent, Entity<XenoArtifactNodeComponent?> node, bool dirty = true)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return false;
+
+ node.Comp ??= XenoArtifactNode(node);
+
+ if (!TryGetIndex(ent, node, out var idx))
+ return false; // node isn't attached to this entity.
+
+ RemoveAllNodeEdges(ent, idx.Value, dirty: false);
+
+ _container.Remove(node.Owner, ent.Comp.NodeContainer);
+ node.Comp.Attached = null;
+ ent.Comp.NodeVertices[idx.Value] = null;
+ if (dirty)
+ {
+ RebuildXenoArtifactMetaData(ent);
+ }
+
+ Dirty(node);
+
+ return true;
+ }
+
+ /// <summary>
+ /// Remove edges, connected to passed artifact node.
+ /// </summary>
+ /// <param name="ent">Entity of artifact, in node container of which node resides.</param>
+ /// <param name="nodeIdx">Index of node (inside node container), for which all edges should be removed.</param>
+ /// <param name="dirty">
+ /// Marker, if we need to recalculate caches and mark related components dirty to update on client side.
+ /// Should be disabled for initial graph creation to not recalculate cache on each node/edge.
+ /// </param>
+ public void RemoveAllNodeEdges(Entity<XenoArtifactComponent?> ent, int nodeIdx, bool dirty = true)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return;
+
+ var predecessors = GetDirectPredecessorNodes(ent, nodeIdx);
+ foreach (var p in predecessors)
+ {
+ RemoveEdge(ent, p, nodeIdx, dirty: false);
+ }
+
+ var successors = GetDirectSuccessorNodes(ent, nodeIdx);
+ foreach (var s in successors)
+ {
+ RemoveEdge(ent, nodeIdx, s, dirty: false);
+ }
+
+ if (dirty)
+ {
+ RebuildXenoArtifactMetaData(ent);
+ }
+ }
+
+ /// <summary>
+ /// Gets set of node entities, that are direct predecessors to passed node entity.
+ /// </summary>
+ /// <remarks>
+ /// Direct predecessors are nodes, which are connected by edges directly to target node,
+ /// and are on outgoing ('FROM') side of edge connection.
+ /// </remarks>
+ public HashSet<Entity<XenoArtifactNodeComponent>> GetDirectPredecessorNodes(Entity<XenoArtifactComponent?> ent, EntityUid node)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return new();
+
+ if (!TryGetIndex(ent, node, out var index))
+ return new();
+
+ var indices = GetDirectPredecessorNodes(ent, index.Value);
+ var output = new HashSet<Entity<XenoArtifactNodeComponent>>();
+ foreach (var i in indices)
+ {
+ if (TryGetNode(ent, i, out var predecessor))
+ output.Add(predecessor.Value);
+ }
+
+ return output;
+ }
+
+ /// <summary>
+ /// Gets set of node indices (in artifact node container) which are direct predecessors to node with passed node index.
+ /// </summary>
+ /// <remarks>
+ /// Direct predecessors are nodes, which are connected by edges directly to target node,
+ /// and are on outgoing ('FROM') side of edge connection.
+ /// </remarks>
+ public HashSet<int> GetDirectPredecessorNodes(Entity<XenoArtifactComponent?> ent, int nodeIdx)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return new();
+
+ DebugTools.Assert(nodeIdx >= 0 && nodeIdx < ent.Comp.NodeVertices.Length, $"node index {nodeIdx} is out of bounds!");
+
+ var indices = new HashSet<int>();
+ for (var i = 0; i < ent.Comp.NodeAdjacencyMatrixRows; i++)
+ {
+ if (ent.Comp.NodeAdjacencyMatrix[i][nodeIdx])
+ indices.Add(i);
+ }
+
+ return indices;
+ }
+
+ /// <summary>
+ /// Gets set of node entities, that are direct successors to passed node entity.
+ /// </summary>
+ /// <remarks>
+ /// Direct successors are nodes, which are connected by edges
+ /// directly to target node, and are on incoming ('TO') side of edge connection.
+ /// </remarks>
+ public HashSet<Entity<XenoArtifactNodeComponent>> GetDirectSuccessorNodes(Entity<XenoArtifactComponent?> ent, EntityUid node)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return new();
+
+ if (!TryGetIndex(ent, node, out var index))
+ return new();
+
+ var indices = GetDirectSuccessorNodes(ent, index.Value);
+ var output = new HashSet<Entity<XenoArtifactNodeComponent>>();
+ foreach (var i in indices)
+ {
+ if (TryGetNode(ent, i, out var successor))
+ output.Add(successor.Value);
+ }
+
+ return output;
+ }
+
+ /// <summary>
+ /// Gets set of node indices (in artifact node container) which are direct successors to node with passed node index.
+ /// </summary>
+ /// <remarks>
+ /// Direct successors are nodes, which are connected by edges
+ /// directly to target node, and are on incoming ('TO') side of edge connection.
+ /// </remarks>
+ public HashSet<int> GetDirectSuccessorNodes(Entity<XenoArtifactComponent?> ent, int nodeIdx)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return new();
+ DebugTools.Assert(nodeIdx >= 0 && nodeIdx < ent.Comp.NodeVertices.Length, "node index is out of bounds!");
+
+ var indices = new HashSet<int>();
+ for (var i = 0; i < ent.Comp.NodeAdjacencyMatrixColumns; i++)
+ {
+ if (ent.Comp.NodeAdjacencyMatrix[nodeIdx][i])
+ indices.Add(i);
+ }
+
+ return indices;
+ }
+
+ /// <summary>
+ /// Gets set of node entities, that are predecessors to passed node entity.
+ /// </summary>
+ /// <remarks>
+ /// Predecessors are nodes, which are connected by edges directly to target node on 'FROM' side of edge,
+ /// or connected to such node on 'FROM' side of edge, etc recursively.
+ /// </remarks>
+ public HashSet<Entity<XenoArtifactNodeComponent>> GetPredecessorNodes(Entity<XenoArtifactComponent?> ent, Entity<XenoArtifactNodeComponent> node)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return new();
+
+ var predecessors = GetPredecessorNodes(ent, GetIndex((ent, ent.Comp), node));
+ var output = new HashSet<Entity<XenoArtifactNodeComponent>>();
+ foreach (var p in predecessors)
+ {
+ output.Add(GetNode((ent, ent.Comp), p));
+ }
+
+ return output;
+ }
+
+ /// <summary>
+ /// Gets set of node indices inside artifact node container, that are predecessors to entity with passed node index.
+ /// </summary>
+ /// <remarks>
+ /// Predecessors are nodes, which are connected by edges directly to target node on 'FROM' side of edge,
+ /// or connected to such node on 'FROM' side of edge, etc recursively.
+ /// </remarks>
+ public HashSet<int> GetPredecessorNodes(Entity<XenoArtifactComponent?> ent, int nodeIdx)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return new();
+
+ var predecessors = GetDirectPredecessorNodes(ent, nodeIdx);
+ if (predecessors.Count == 0)
+ return new();
+
+ var output = new HashSet<int>();
+ foreach (var p in predecessors)
+ {
+ output.Add(p);
+ var recursivePredecessors = GetPredecessorNodes(ent, p);
+ foreach (var rp in recursivePredecessors)
+ {
+ output.Add(rp);
+ }
+ }
+
+ return output;
+ }
+
+ /// <summary>
+ /// Gets set of node entities, that are successors to passed node entity.
+ /// </summary>
+ /// <remarks>
+ /// Successors are nodes, which are connected by edges directly to target node on 'TO' side of edge,
+ /// or connected to such node on 'TO' side of edge, etc recursively.
+ /// </remarks>
+ public HashSet<Entity<XenoArtifactNodeComponent>> GetSuccessorNodes(Entity<XenoArtifactComponent?> ent, Entity<XenoArtifactNodeComponent> node)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return new();
+
+ var successors = GetSuccessorNodes(ent, GetIndex((ent, ent.Comp), node));
+ var output = new HashSet<Entity<XenoArtifactNodeComponent>>();
+ foreach (var s in successors)
+ {
+ output.Add(GetNode((ent, ent.Comp), s));
+ }
+
+ return output;
+ }
+
+ /// <summary>
+ /// Gets set of node indices inside artifact node container, that are successors to entity with passed node index.
+ /// </summary>
+ /// <remarks>
+ /// Successors are nodes, which are connected by edges directly to target node on 'TO' side of edge,
+ /// or connected to such node on 'TO' side of edge, etc recursively.
+ /// </remarks>
+ public HashSet<int> GetSuccessorNodes(Entity<XenoArtifactComponent?> ent, int nodeIdx)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return new();
+
+ var successors = GetDirectSuccessorNodes(ent, nodeIdx);
+ if (successors.Count == 0)
+ return new();
+
+ var output = new HashSet<int>();
+ foreach (var s in successors)
+ {
+ output.Add(s);
+ var recursiveSuccessors = GetSuccessorNodes(ent, s);
+ foreach (var rs in recursiveSuccessors)
+ {
+ output.Add(rs);
+ }
+ }
+
+ return output;
+ }
+
+ /// <summary>
+ /// Determines, if there is an edge (directed link) FROM one node TO other in passed artifact.
+ /// </summary>
+ /// <param name="ent">Artifact, inside which node container nodes are.</param>
+ /// <param name="from">Node FROM which existence of edge should be checked.</param>
+ /// <param name="to">Node TO which existance of edge should be checked.</param>
+ public bool NodeHasEdge(
+ Entity<XenoArtifactComponent?> ent,
+ Entity<XenoArtifactNodeComponent?> from,
+ Entity<XenoArtifactNodeComponent?> to
+ )
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return new();
+
+ var fromIdx = GetIndex((ent, ent.Comp), from);
+ var toIdx = GetIndex((ent, ent.Comp), to);
+
+ return ent.Comp.NodeAdjacencyMatrix[fromIdx][toIdx];
+ }
+
+ /// <summary>
+ /// Resizes the adjacency matrix and vertices array to <paramref name="newSize"/>,
+ /// or at least what it WOULD do if i wasn't forced to use shitty lists.
+ /// </summary>
+ protected void ResizeNodeGraph(Entity<XenoArtifactComponent> ent, int newSize)
+ {
+ Array.Resize(ref ent.Comp.NodeVertices, newSize);
+
+ while (ent.Comp.NodeAdjacencyMatrix.Count < newSize)
+ {
+ ent.Comp.NodeAdjacencyMatrix.Add(new());
+ }
+
+ foreach (var row in ent.Comp.NodeAdjacencyMatrix)
+ {
+ while (row.Count < newSize)
+ {
+ row.Add(false);
+ }
+ }
+
+ Dirty(ent);
+ }
+
+ /// <summary> Removes unlocking state from artifact. </summary>
+ private void CancelUnlockingOnGraphStructureChange(Entity<XenoArtifactComponent> ent)
+ {
+ if (!TryComp<XenoArtifactUnlockingComponent>(ent, out var unlockingComponent))
+ return;
+
+ Entity<XenoArtifactUnlockingComponent, XenoArtifactComponent> artifactEnt = (ent, unlockingComponent, ent.Comp);
+ CancelUnlockingState(artifactEnt);
+ }
+}
--- /dev/null
+using System.Linq;
+using Content.Shared.EntityTable;
+using Content.Shared.NameIdentifier;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.Prototypes;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Xenoarchaeology.Artifact;
+
+public abstract partial class SharedXenoArtifactSystem
+{
+ [Dependency] private readonly EntityTableSystem _entityTable = default!;
+
+ private EntityQuery<XenoArtifactComponent> _xenoArtifactQuery;
+ private EntityQuery<XenoArtifactNodeComponent> _nodeQuery;
+
+ private void InitializeNode()
+ {
+ SubscribeLocalEvent<XenoArtifactNodeComponent, MapInitEvent>(OnNodeMapInit);
+
+ _xenoArtifactQuery = GetEntityQuery<XenoArtifactComponent>();
+ _nodeQuery = GetEntityQuery<XenoArtifactNodeComponent>();
+ }
+
+ /// <summary>
+ /// Initializes artifact node on its creation (by setting durability).
+ /// </summary>
+ private void OnNodeMapInit(Entity<XenoArtifactNodeComponent> ent, ref MapInitEvent args)
+ {
+ XenoArtifactNodeComponent nodeComponent = ent;
+ nodeComponent.MaxDurability -= nodeComponent.MaxDurabilityCanDecreaseBy.Next(RobustRandom);
+ SetNodeDurability((ent, ent), nodeComponent.MaxDurability);
+ }
+
+ /// <summary> Gets node component by node entity uid. </summary>
+ public XenoArtifactNodeComponent XenoArtifactNode(EntityUid uid)
+ {
+ return _nodeQuery.Get(uid);
+ }
+
+ public void SetNodeUnlocked(Entity<XenoArtifactNodeComponent?> ent)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return;
+
+ if (ent.Comp.Attached is not { } netArtifact)
+ return;
+
+ var artifact = GetEntity(netArtifact);
+ if (!TryComp<XenoArtifactComponent>(artifact, out var artifactComponent))
+ return;
+
+ SetNodeUnlocked((artifact, artifactComponent), (ent, ent.Comp));
+ }
+
+ public void SetNodeUnlocked(Entity<XenoArtifactComponent> artifact, Entity<XenoArtifactNodeComponent> node)
+ {
+ if (!node.Comp.Locked)
+ return;
+
+ node.Comp.Locked = false;
+ RebuildCachedActiveNodes((artifact, artifact));
+ Dirty(node);
+ }
+
+ /// <summary>
+ /// Adds to the node's durability by the specified value. To reduce, provide negative value.
+ /// </summary>
+ public void AdjustNodeDurability(Entity<XenoArtifactNodeComponent?> ent, int durabilityDelta)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return;
+
+ SetNodeDurability(ent, ent.Comp.Durability + durabilityDelta);
+ }
+
+ /// <summary>
+ /// Sets a node's durability to the specified value. HIGHLY recommended to not be less than 0.
+ /// </summary>
+ public void SetNodeDurability(Entity<XenoArtifactNodeComponent?> ent, int durability)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return;
+
+ ent.Comp.Durability = Math.Clamp(durability, 0, ent.Comp.MaxDurability);
+ UpdateNodeResearchValue((ent, ent.Comp));
+ Dirty(ent);
+ }
+
+ /// <summary>
+ /// Creates artifact node entity, attaching trigger and marking depth level for future use.
+ /// </summary>
+ public Entity<XenoArtifactNodeComponent> CreateNode(Entity<XenoArtifactComponent> ent, ProtoId<XenoArchTriggerPrototype> trigger, int depth = 0)
+ {
+ var triggerProto = PrototypeManager.Index(trigger);
+ return CreateNode(ent, triggerProto, depth);
+ }
+
+ /// <summary>
+ /// Creates artifact node entity, attaching trigger and marking depth level for future use.
+ /// </summary>
+ public Entity<XenoArtifactNodeComponent> CreateNode(Entity<XenoArtifactComponent> ent, XenoArchTriggerPrototype trigger, int depth = 0)
+ {
+ var entProtoId = _entityTable.GetSpawns(ent.Comp.EffectsTable)
+ .First();
+
+ AddNode((ent, ent), entProtoId, out var nodeEnt, dirty: false);
+ DebugTools.Assert(nodeEnt.HasValue, "Failed to create node on artifact.");
+
+ var nodeComponent = nodeEnt.Value.Comp;
+ nodeComponent.Depth = depth;
+ nodeComponent.TriggerTip = trigger.Tip;
+ EntityManager.AddComponents(nodeEnt.Value, trigger.Components);
+
+ Dirty(nodeEnt.Value);
+ return nodeEnt.Value;
+ }
+
+ /// <summary> Checks if all predecessor nodes are marked as 'unlocked'. </summary>
+ public bool HasUnlockedPredecessor(Entity<XenoArtifactComponent> ent, EntityUid node)
+ {
+ var predecessors = GetDirectPredecessorNodes((ent, ent), node);
+ if (predecessors.Count == 0)
+ {
+ return true;
+ }
+
+ foreach (var predecessor in predecessors)
+ {
+ if (predecessor.Comp.Locked)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /// <summary> Checks if node was marked as 'active'. Active nodes are invoked on artifact use (if durability is greater than zero). </summary>
+ public bool IsNodeActive(Entity<XenoArtifactComponent> ent, EntityUid node)
+ {
+ return ent.Comp.CachedActiveNodes.Contains(GetNetEntity(node));
+ }
+
+ /// <summary>
+ /// Gets list of 'active' nodes. Active nodes are invoked on artifact use (if durability is greater than zero).
+ /// </summary>
+ public List<Entity<XenoArtifactNodeComponent>> GetActiveNodes(Entity<XenoArtifactComponent> ent)
+ {
+ return ent.Comp.CachedActiveNodes
+ .Select(activeNode => _nodeQuery.Get(GetEntity(activeNode)))
+ .ToList();
+ }
+
+ /// <summary>
+ /// Gets amount of research points that can be extracted from node.
+ /// We can only extract "what's left" - its base value, reduced by already consumed value.
+ /// Every drained durability brings more points to be extracted.
+ /// </summary>
+ public int GetResearchValue(Entity<XenoArtifactNodeComponent> ent)
+ {
+ if (ent.Comp.Locked)
+ return 0;
+
+ return ent.Comp.ResearchValue - ent.Comp.ConsumedResearchValue;
+ }
+
+ /// <summary>
+ /// Sets amount of points already extracted from node.
+ /// </summary>
+ public void SetConsumedResearchValue(Entity<XenoArtifactNodeComponent> ent, int value)
+ {
+ ent.Comp.ConsumedResearchValue = value;
+ Dirty(ent);
+ }
+
+ /// <summary>
+ /// Converts node entity uid to its display name (which is Identifier from <see cref="NameIdentifierComponent"/>.
+ /// </summary>
+ public string GetNodeId(EntityUid uid)
+ {
+ return (CompOrNull<NameIdentifierComponent>(uid)?.Identifier ?? 0).ToString("D3");
+ }
+
+ /// <summary>
+ /// Gets two-dimensional array in a form of nested lists, which holds artifact nodes, grouped by segments.
+ /// Segments are groups of interconnected nodes, there might be one or more segments in non-empty artifact.
+ /// </summary>
+ public List<List<Entity<XenoArtifactNodeComponent>>> GetSegments(Entity<XenoArtifactComponent> ent)
+ {
+ var output = new List<List<Entity<XenoArtifactNodeComponent>>>();
+
+ foreach (var segment in ent.Comp.CachedSegments)
+ {
+ var outSegment = new List<Entity<XenoArtifactNodeComponent>>();
+ foreach (var netNode in segment)
+ {
+ var node = GetEntity(netNode);
+ outSegment.Add((node, XenoArtifactNode(node)));
+ }
+
+ output.Add(outSegment);
+ }
+
+ return output;
+ }
+
+ /// <summary>
+ /// Gets list of nodes, grouped by depth level. Depth level count starts from 0.
+ /// Only 0 depth nodes have no incoming edges - as only they are starting nodes.
+ /// </summary>
+ public Dictionary<int, List<Entity<XenoArtifactNodeComponent>>> GetDepthOrderedNodes(IEnumerable<Entity<XenoArtifactNodeComponent>> nodes)
+ {
+ var nodesByDepth = new Dictionary<int, List<Entity<XenoArtifactNodeComponent>>>();
+
+ foreach (var node in nodes)
+ {
+ if (!nodesByDepth.TryGetValue(node.Comp.Depth, out var depthList))
+ {
+ depthList = new List<Entity<XenoArtifactNodeComponent>>();
+ nodesByDepth.Add(node.Comp.Depth, depthList);
+ }
+
+ depthList.Add(node);
+ }
+
+ return nodesByDepth;
+ }
+
+ /// <summary>
+ /// Rebuilds all the data, associated with nodes in an artifact, updating caches.
+ /// </summary>
+ public void RebuildXenoArtifactMetaData(Entity<XenoArtifactComponent?> artifact)
+ {
+ if (!Resolve(artifact, ref artifact.Comp))
+ return;
+
+ RebuildCachedActiveNodes(artifact);
+ RebuildCachedSegments(artifact);
+ foreach (var node in GetAllNodes((artifact, artifact.Comp)))
+ {
+ RebuildNodeMetaData(node);
+ }
+
+ CancelUnlockingOnGraphStructureChange((artifact, artifact.Comp));
+ }
+
+ public void RebuildNodeMetaData(Entity<XenoArtifactNodeComponent> node)
+ {
+ UpdateNodeResearchValue(node);
+ }
+
+ /// <summary>
+ /// Clears all cached active nodes and rebuilds the list using the current node state.
+ /// Active nodes have the following property:
+ /// - Are unlocked themselves
+ /// - All successors are also unlocked
+ /// </summary>
+ /// <remarks>
+ /// You could technically modify this to have a per-node method that only checks direct predecessors
+ /// and then does recursive updates for all successors, but I don't think the optimization is necessary right now.
+ /// </remarks>
+ public void RebuildCachedActiveNodes(Entity<XenoArtifactComponent?> ent)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return;
+
+ ent.Comp.CachedActiveNodes.Clear();
+ var allNodes = GetAllNodes((ent, ent.Comp));
+ foreach (var node in allNodes)
+ {
+ // Locked nodes cannot be active.
+ if (node.Comp.Locked)
+ continue;
+
+ var successors = GetDirectSuccessorNodes(ent, node);
+
+ // If this node has no successors, then we don't need to bother with this extra logic.
+ if (successors.Count != 0)
+ {
+ // Checks for any of the direct successors being unlocked.
+ var successorIsUnlocked = false;
+ foreach (var sNode in successors)
+ {
+ if (sNode.Comp.Locked)
+ continue;
+
+ successorIsUnlocked = true;
+ break;
+ }
+
+ // Active nodes must be at the end of the path.
+ if (successorIsUnlocked)
+ continue;
+ }
+
+ var netEntity = GetNetEntity(node);
+ ent.Comp.CachedActiveNodes.Add(netEntity);
+ }
+
+ Dirty(ent);
+ }
+
+ public void RebuildCachedSegments(Entity<XenoArtifactComponent?> ent)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return;
+
+ ent.Comp.CachedSegments.Clear();
+
+ var entities = GetAllNodes((ent, ent.Comp))
+ .ToList();
+ var segments = GetSegmentsFromNodes((ent, ent.Comp), entities);
+ var netEntities = segments.Select(
+ s => s.Select(n => GetNetEntity(n))
+ .ToList()
+ );
+ ent.Comp.CachedSegments.AddRange(netEntities);
+
+ Dirty(ent);
+ }
+
+ /// <summary>
+ /// Gets two-dimensional array (as lists inside enumeration) that contains artifact nodes, grouped by segment.
+ /// </summary>
+ public IEnumerable<List<Entity<XenoArtifactNodeComponent>>> GetSegmentsFromNodes(Entity<XenoArtifactComponent> ent, List<Entity<XenoArtifactNodeComponent>> nodes)
+ {
+ var outSegments = new List<List<Entity<XenoArtifactNodeComponent>>>();
+ foreach (var node in nodes)
+ {
+ var segment = new List<Entity<XenoArtifactNodeComponent>>();
+ GetSegmentNodesRecursive(ent, node, segment, outSegments);
+
+ if (segment.Count == 0)
+ continue;
+
+ outSegments.Add(segment);
+ }
+
+ return outSegments;
+ }
+
+ /// <summary>
+ /// Fills nodes into segments by recursively walking through collections of predecessors and successors.
+ /// </summary>
+ private void GetSegmentNodesRecursive(
+ Entity<XenoArtifactComponent> ent,
+ Entity<XenoArtifactNodeComponent> node,
+ List<Entity<XenoArtifactNodeComponent>> segment,
+ List<List<Entity<XenoArtifactNodeComponent>>> otherSegments
+ )
+ {
+ if (otherSegments.Any(s => s.Contains(node)))
+ return;
+
+ if (segment.Contains(node))
+ return;
+
+ segment.Add(node);
+
+ var predecessors = GetDirectPredecessorNodes((ent, ent), node);
+ foreach (var p in predecessors)
+ {
+ GetSegmentNodesRecursive(ent, p, segment, otherSegments);
+ }
+
+ var successors = GetDirectSuccessorNodes((ent, ent), node);
+ foreach (var s in successors)
+ {
+ GetSegmentNodesRecursive(ent, s, segment, otherSegments);
+ }
+ }
+
+ /// <summary>
+ /// Sets node research point amount that can be extracted.
+ /// Used up durability increases amount to be extracted.
+ /// </summary>
+ public void UpdateNodeResearchValue(Entity<XenoArtifactNodeComponent> node)
+ {
+ XenoArtifactNodeComponent nodeComponent = node;
+ if (nodeComponent.Attached == null)
+ {
+ nodeComponent.ResearchValue = 0;
+ return;
+ }
+
+ var artifact = _xenoArtifactQuery.Get(GetEntity(nodeComponent.Attached.Value));
+
+ var nonactiveNodes = GetActiveNodes(artifact);
+ var durabilityEffect = MathF.Pow((float)nodeComponent.Durability / nodeComponent.MaxDurability, 2);
+ var durabilityMultiplier = nonactiveNodes.Contains(node)
+ ? 1f - durabilityEffect
+ : 1f + durabilityEffect;
+
+ var predecessorNodes = GetPredecessorNodes((artifact, artifact), node);
+ nodeComponent.ResearchValue = (int)(Math.Pow(1.25, predecessorNodes.Count) * nodeComponent.BasePointValue * durabilityMultiplier);
+ }
+}
--- /dev/null
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Robust.Shared.Audio;
+using Robust.Shared.Audio.Systems;
+
+namespace Content.Shared.Xenoarchaeology.Artifact;
+
+public abstract partial class SharedXenoArtifactSystem
+{
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+
+ private EntityQuery<XenoArtifactUnlockingComponent> _unlockingQuery;
+
+ private void InitializeUnlock()
+ {
+ _unlockingQuery = GetEntityQuery<XenoArtifactUnlockingComponent>();
+
+ SubscribeLocalEvent<XenoArtifactUnlockingComponent, MapInitEvent>(OnUnlockingStarted);
+ }
+
+ /// <summary> Finish unlocking phase when the time is up. </summary>
+ private void UpdateUnlock(float _)
+ {
+ var query = EntityQueryEnumerator<XenoArtifactUnlockingComponent, XenoArtifactComponent>();
+ while (query.MoveNext(out var uid, out var unlock, out var comp))
+ {
+ if (_timing.CurTime < unlock.EndTime)
+ continue;
+
+ FinishUnlockingState((uid, unlock, comp));
+ }
+ }
+
+ /// <summary>
+ /// Checks if node can be unlocked.
+ /// Only those nodes, that have no predecessors, or have all
+ /// predecessors unlocked can be unlocked themselves.
+ /// Artifact being suppressed also prevents unlocking.
+ /// </summary>
+ public bool CanUnlockNode(Entity<XenoArtifactNodeComponent?> ent)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return false;
+
+ var artifact = GetEntity(ent.Comp.Attached);
+ if (!TryComp<XenoArtifactComponent>(artifact, out var artiComp))
+ return false;
+
+ if (artiComp.Suppressed)
+ return false;
+
+ if (!HasUnlockedPredecessor((artifact.Value, artiComp), ent)
+ // unlocked final nodes should not listen for unlocking
+ || (!ent.Comp.Locked && GetSuccessorNodes((artifact.Value, artiComp), (ent.Owner, ent.Comp)).Count == 0)
+ )
+ return false;
+
+ return true;
+ }
+
+ /// <summary>
+ /// Finishes unlocking phase, removing related component, and sums up what nodes were triggered,
+ /// that could be unlocked. Marks such nodes as unlocked, and pushes their node activation event.
+ /// </summary>
+ public void FinishUnlockingState(Entity<XenoArtifactUnlockingComponent, XenoArtifactComponent> ent)
+ {
+ string unlockAttemptResultMsg;
+ XenoArtifactComponent artifactComponent = ent;
+ XenoArtifactUnlockingComponent unlockingComponent = ent;
+
+ SoundSpecifier? soundEffect;
+ if (TryGetNodeFromUnlockState(ent, out var node))
+ {
+ SetNodeUnlocked((ent, artifactComponent), node.Value);
+ unlockAttemptResultMsg = "artifact-unlock-state-end-success";
+
+ // as an experiment - unlocking node doesn't activate it, activation is left for player to decide.
+ // var activated = ActivateNode((ent, artifactComponent), node.Value, null, null, Transform(ent).Coordinates, false);
+ // if (activated)
+ soundEffect = unlockingComponent.UnlockActivationSuccessfulSound;
+ }
+ else
+ {
+ unlockAttemptResultMsg = "artifact-unlock-state-end-failure";
+ soundEffect = unlockingComponent.UnlockActivationFailedSound;
+ }
+
+ if (_net.IsServer)
+ {
+ _popup.PopupEntity(Loc.GetString(unlockAttemptResultMsg), ent);
+ _audio.PlayPvs(soundEffect, ent.Owner);
+ }
+
+ RemComp(ent, unlockingComponent);
+ RiseUnlockingFinished(ent, node);
+ artifactComponent.NextUnlockTime = _timing.CurTime + artifactComponent.UnlockStateRefractory;
+ }
+
+ public void CancelUnlockingState(Entity<XenoArtifactUnlockingComponent, XenoArtifactComponent> ent)
+ {
+ RemComp(ent, ent.Comp1);
+ RiseUnlockingFinished(ent, null);
+ }
+
+ /// <summary>
+ /// Gets first locked node that can be unlocked (it is locked and all predecessor are unlocked).
+ /// </summary>
+ public bool TryGetNodeFromUnlockState(
+ Entity<XenoArtifactUnlockingComponent, XenoArtifactComponent> ent,
+ [NotNullWhen(true)] out Entity<XenoArtifactNodeComponent>? node
+ )
+ {
+ node = null;
+
+ var artifactUnlockingComponent = ent.Comp1;
+ foreach (var nodeIndex in artifactUnlockingComponent.TriggeredNodeIndexes)
+ {
+ var artifactComponent = ent.Comp2;
+ var curNode = GetNode((ent, artifactComponent), nodeIndex);
+ if (!curNode.Comp.Locked || !CanUnlockNode((curNode, curNode)))
+ continue;
+
+ var requiredIndices = GetPredecessorNodes((ent, artifactComponent), nodeIndex);
+ requiredIndices.Add(nodeIndex);
+
+ // Make sure the two sets are identical
+ if (requiredIndices.Count != artifactUnlockingComponent.TriggeredNodeIndexes.Count
+ || !artifactUnlockingComponent.TriggeredNodeIndexes.All(requiredIndices.Contains))
+ continue;
+
+ node = curNode;
+ return true;
+ }
+
+ return node != null;
+ }
+
+ private void OnUnlockingStarted(Entity<XenoArtifactUnlockingComponent> ent, ref MapInitEvent args)
+ {
+ var unlockingStartedEvent = new ArtifactUnlockingStartedEvent();
+ RaiseLocalEvent(ent.Owner, ref unlockingStartedEvent);
+ }
+
+ private void RiseUnlockingFinished(
+ Entity<XenoArtifactUnlockingComponent, XenoArtifactComponent> ent,
+ Entity<XenoArtifactNodeComponent>? node
+ )
+ {
+ var unlockingFinishedEvent = new ArtifactUnlockingFinishedEvent(node);
+ RaiseLocalEvent(ent.Owner, ref unlockingFinishedEvent);
+ }
+
+}
+
+/// <summary>
+/// Event for starting artifact unlocking stage.
+/// </summary>
+[ByRefEvent]
+public record struct ArtifactUnlockingStartedEvent;
+
+/// <summary>
+/// Event for finishing artifact unlocking stage.
+/// </summary>
+/// <param name="UnlockedNode">Node which were unlocked. Null if stage was finished without new unlocks.</param>
+[ByRefEvent]
+public record struct ArtifactUnlockingFinishedEvent(EntityUid? UnlockedNode);
--- /dev/null
+using Content.Shared.Administration.Logs;
+using Content.Shared.Database;
+using Content.Shared.Interaction;
+using Content.Shared.Interaction.Events;
+using Content.Shared.Timing;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Robust.Shared.Map;
+
+namespace Content.Shared.Xenoarchaeology.Artifact;
+
+public abstract partial class SharedXenoArtifactSystem
+{
+ [Dependency] private readonly UseDelaySystem _useDelay = default!;
+ [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+
+ private void InitializeXAE()
+ {
+ SubscribeLocalEvent<XenoArtifactComponent, UseInHandEvent>(OnUseInHand);
+ SubscribeLocalEvent<XenoArtifactComponent, AfterInteractEvent>(OnAfterInteract);
+ SubscribeLocalEvent<XenoArtifactComponent, ActivateInWorldEvent>(OnActivateInWorld);
+ }
+
+ private void OnUseInHand(Entity<XenoArtifactComponent> ent, ref UseInHandEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ args.Handled = TryActivateXenoArtifact(ent, args.User, args.User, Transform(args.User).Coordinates);
+ }
+
+ private void OnAfterInteract(Entity<XenoArtifactComponent> ent, ref AfterInteractEvent args)
+ {
+ if (args.Handled || !args.CanReach)
+ return;
+
+ args.Handled = TryActivateXenoArtifact(ent, args.User, args.Target, args.ClickLocation);
+ }
+
+ private void OnActivateInWorld(Entity<XenoArtifactComponent> ent, ref ActivateInWorldEvent args)
+ {
+ if (args.Handled || !args.Complex)
+ return;
+
+ args.Handled = TryActivateXenoArtifact(ent, args.User, args.Target, Transform(args.Target).Coordinates);
+ }
+
+ /// <summary>
+ /// Attempts to activate artifact nodes. 'active' are nodes that are marked as 'unlocked' and have no other successors, marked as 'unlocked'.
+ /// </summary>
+ /// <param name="artifact">Artifact entity, for which attempt to activate was made.</param>
+ /// <param name="user">Character that attempted to activate artifact.</param>
+ /// <param name="target">Target, on which artifact activation attempt was used (for hand-held artifact - it can be 'clicked' over someone).</param>
+ /// <param name="coordinates">Coordinates of <paramref name="target"/> entity.</param>
+ /// <returns>True, if activation was successful, false otherwise.</returns>
+ public bool TryActivateXenoArtifact(
+ Entity<XenoArtifactComponent> artifact,
+ EntityUid? user,
+ EntityUid? target,
+ EntityCoordinates coordinates
+ )
+ {
+ XenoArtifactComponent xenoArtifactComponent = artifact;
+ if (xenoArtifactComponent.Suppressed)
+ return false;
+
+ if (TryComp<UseDelayComponent>(artifact, out var delay) && !_useDelay.TryResetDelay((artifact, delay), true))
+ return false;
+
+ var success = false;
+ foreach (var node in GetActiveNodes(artifact))
+ {
+ success |= ActivateNode(artifact, node, user, target, coordinates);
+ }
+
+ if (!success)
+ {
+ _popup.PopupClient(Loc.GetString("artifact-activation-fail"), artifact, user);
+ return false;
+ }
+
+ // we raised event for each node activation,
+ // now we raise event for artifact itself. For animations and stuff.
+ var ev = new XenoArtifactActivatedEvent(
+ artifact,
+ user,
+ target,
+ coordinates
+ );
+ RaiseLocalEvent(artifact, ref ev);
+
+ if (user.HasValue)
+ _audio.PlayPredicted(xenoArtifactComponent.ForceActivationSoundSpecifier, artifact, user);
+ else
+ _audio.PlayPvs(xenoArtifactComponent.ForceActivationSoundSpecifier, artifact);
+
+ return true;
+ }
+
+ /// <summary>
+ /// Pushes node activation event and updates durability for activated node.
+ /// </summary>
+ /// <param name="artifact">Artifact entity, for which attempt to activate was made.</param>
+ /// <param name="node">Node entity, effect of which should be activated.</param>
+ /// <param name="user">Character that attempted to activate artifact.</param>
+ /// <param name="target">Target, on which artifact activation attempt was used (for hand-held artifact - it can be 'clicked' over someone).</param>
+ /// <param name="coordinates">Coordinates of <paramref name="target"/> entity.</param>
+ /// <param name="consumeDurability">Marker, if node durability should be adjusted as a result of activation.</param>
+ /// <returns>True, if activation was successful, false otherwise.</returns>
+ public bool ActivateNode(
+ Entity<XenoArtifactComponent> artifact,
+ Entity<XenoArtifactNodeComponent> node,
+ EntityUid? user,
+ EntityUid? target,
+ EntityCoordinates coordinates,
+ bool consumeDurability = true
+ )
+ {
+ if (node.Comp.Degraded)
+ return false;
+
+ _adminLogger.Add(
+ LogType.ArtifactNode,
+ LogImpact.Low,
+ $"{ToPrettyString(artifact.Owner)} node {ToPrettyString(node)} got activated at {coordinates}"
+ );
+ if (consumeDurability)
+ {
+ AdjustNodeDurability((node, node.Comp), -1);
+ }
+
+ var ev = new XenoArtifactNodeActivatedEvent(artifact, node, user, target, coordinates);
+ RaiseLocalEvent(node, ref ev);
+ return true;
+ }
+}
+
+/// <summary>
+/// Event of node activation. Should lead to node effect being activated.
+/// </summary>
+/// <param name="Artifact">Artifact entity, for which attempt to activate was made.</param>
+/// <param name="Node">Node entity, effect of which should be activated.</param>
+/// <param name="User">Character that attempted to activate artifact.</param>
+/// <param name="Target">Target, on which artifact activation attempt was used (for hand-held artifact - it can be 'clicked' over someone).</param>
+/// <param name="Coordinates">Coordinates of <paramref name="Target"/> entity.</param>
+[ByRefEvent]
+public readonly record struct XenoArtifactNodeActivatedEvent(
+ Entity<XenoArtifactComponent> Artifact,
+ Entity<XenoArtifactNodeComponent> Node,
+ EntityUid? User,
+ EntityUid? Target,
+ EntityCoordinates Coordinates
+);
+
+[ByRefEvent]
+public readonly record struct XenoArtifactActivatedEvent(
+ Entity<XenoArtifactComponent> Artifact,
+ EntityUid? User,
+ EntityUid? Target,
+ EntityCoordinates Coordinates
+);
--- /dev/null
+using System.Linq;
+using Content.Shared.Chemistry;
+using Content.Shared.Damage;
+using Content.Shared.Examine;
+using Content.Shared.Interaction;
+using Content.Shared.Movement.Pulling.Events;
+using Content.Shared.Throwing;
+using Content.Shared.Weapons.Melee.Events;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+namespace Content.Shared.Xenoarchaeology.Artifact;
+
+public abstract partial class SharedXenoArtifactSystem
+{
+ private void InitializeXAT()
+ {
+ XATRelayLocalEvent<DamageChangedEvent>();
+ XATRelayLocalEvent<InteractUsingEvent>();
+ XATRelayLocalEvent<PullStartedMessage>();
+ XATRelayLocalEvent<AttackedEvent>();
+ XATRelayLocalEvent<XATToolUseDoAfterEvent>();
+ XATRelayLocalEvent<InteractHandEvent>();
+ XATRelayLocalEvent<ReactionEntityEvent>();
+ XATRelayLocalEvent<LandEvent>();
+
+ // special case this one because we need to order the messages
+ SubscribeLocalEvent<XenoArtifactComponent, ExaminedEvent>(OnExamined);
+ }
+
+ /// <summary> Relays artifact events for artifact nodes. </summary>
+ protected void XATRelayLocalEvent<T>() where T : notnull
+ {
+ SubscribeLocalEvent<XenoArtifactComponent, T>(RelayEventToNodes);
+ }
+
+ private void OnExamined(Entity<XenoArtifactComponent> ent, ref ExaminedEvent args)
+ {
+ using (args.PushGroup(nameof(XenoArtifactComponent)))
+ {
+ RelayEventToNodes(ent, ref args);
+ }
+ }
+
+ protected void RelayEventToNodes<T>(Entity<XenoArtifactComponent> ent, ref T args) where T : notnull
+ {
+ var ev = new XenoArchNodeRelayedEvent<T>(ent, args);
+
+ var nodes = GetAllNodes(ent);
+ foreach (var node in nodes)
+ {
+ RaiseLocalEvent(node, ref ev);
+ }
+ }
+
+ /// <summary>
+ /// Attempts to shift artifact into unlocking state, in which it is going to listen to interactions, that could trigger nodes.
+ /// </summary>
+ public void TriggerXenoArtifact(Entity<XenoArtifactComponent> ent, Entity<XenoArtifactNodeComponent> node)
+ {
+ // limits spontaneous chain activations, also prevents spamming every triggering tool to activate nodes
+ // without real knowledge about triggers
+ if (_timing.CurTime < ent.Comp.NextUnlockTime)
+ return;
+
+ var index = GetIndex(ent, node);
+
+ if (!_unlockingQuery.TryGetComponent(ent, out var unlockingComp))
+ {
+ unlockingComp = EnsureComp<XenoArtifactUnlockingComponent>(ent);
+ unlockingComp.EndTime = _timing.CurTime + ent.Comp.UnlockStateDuration;
+ Log.Debug($"{ToPrettyString(ent)} entered unlocking state");
+
+ if (_net.IsServer)
+ _popup.PopupEntity(Loc.GetString("artifact-unlock-state-begin"), ent);
+ }
+ else
+ {
+ 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)
+ )
+ )
+ // we add time on each new trigger, if it is not going to fail us
+ unlockingComp.EndTime += ent.Comp.UnlockStateIncrementPerNode;
+ }
+
+ if (unlockingComp.TriggeredNodeIndexes.Add(index))
+ {
+ Dirty(ent, unlockingComp);
+ }
+ }
+}
+
+/// <summary>
+/// Event wrapper for XenoArch Trigger events.
+/// </summary>
+[ByRefEvent]
+public record struct XenoArchNodeRelayedEvent<TEvent>(Entity<XenoArtifactComponent> Artifact, TEvent Args)
+{
+ /// <summary>
+ /// Original event.
+ /// </summary>
+ public TEvent Args = Args;
+
+ /// <summary>
+ /// Artifact entity, that received original event.
+ /// </summary>
+ public Entity<XenoArtifactComponent> Artifact = Artifact;
+}
--- /dev/null
+using Content.Shared.Popups;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Robust.Shared.Containers;
+using Robust.Shared.Network;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Xenoarchaeology.Artifact;
+
+/// <summary>
+/// Handles all logic for generating and facilitating interactions with XenoArtifacts
+/// </summary>
+public abstract partial class SharedXenoArtifactSystem : EntitySystem
+{
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly INetManager _net = default!;
+ [Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
+ [Dependency] protected readonly IRobustRandom RobustRandom = default!;
+ [Dependency] private readonly SharedContainerSystem _container = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+
+ /// <inheritdoc/>
+ public override void Initialize()
+ {
+ SubscribeLocalEvent<XenoArtifactComponent, ComponentStartup>(OnStartup);
+
+ InitializeNode();
+ InitializeUnlock();
+ InitializeXAT();
+ InitializeXAE();
+ }
+
+ /// <inheritdoc />
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ UpdateUnlock(frameTime);
+ }
+
+ /// <summary> As all artifacts have to contain nodes - we ensure that they are containers. </summary>
+ private void OnStartup(Entity<XenoArtifactComponent> ent, ref ComponentStartup args)
+ {
+ ent.Comp.NodeContainer = _container.EnsureContainer<Container>(ent, XenoArtifactComponent.NodeContainerId);
+ }
+
+ public void SetSuppressed(Entity<XenoArtifactComponent> ent, bool val)
+ {
+ if (ent.Comp.Suppressed == val)
+ return;
+
+ ent.Comp.Suppressed = val;
+ Dirty(ent);
+ }
+}
--- /dev/null
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// Base class for
+/// </summary>
+/// <typeparam name="T"></typeparam>
+public abstract class BaseXAESystem<T> : EntitySystem where T : Component
+{
+ /// <inheritdoc/>
+ public override void Initialize()
+ {
+ SubscribeLocalEvent<T, XenoArtifactNodeActivatedEvent>(OnActivated);
+ }
+
+ /// <summary>
+ /// Handler for node activation.
+ /// </summary>
+ /// <param name="ent">Entity (node) that got activated.</param>
+ /// <param name="args">Activation event (containing artifact and other useful info).</param>
+ protected abstract void OnActivated(Entity<T> ent, ref XenoArtifactNodeActivatedEvent args);
+}
--- /dev/null
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
+
+/// <summary>
+/// Applies components when effect is activated.
+/// </summary>
+[RegisterComponent, Access(typeof(XAEApplyComponentsSystem))]
+public sealed partial class XAEApplyComponentsComponent : Component
+{
+ /// <summary>
+ /// Components that are permanently added to an entity when the effect's node is entered.
+ /// </summary>
+ [DataField]
+ public ComponentRegistry Components = new();
+
+ /// <summary>
+ /// Does adding components need to be done only on first activation.
+ /// </summary>
+ [DataField]
+ public bool ApplyIfAlreadyHave { get; set; }
+
+ /// <summary>
+ /// Does component need to be restored when activated 2nd or more times.
+ /// </summary>
+ [DataField]
+ public bool RefreshOnReactivate { get; set; }
+}
-using Content.Shared.Damage;
+using Content.Shared.Damage;
using Content.Shared.Whitelist;
+using Robust.Shared.GameStates;
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
/// <summary>
/// When activated, damages nearby entities.
/// </summary>
-[RegisterComponent]
-public sealed partial class DamageNearbyArtifactComponent : Component
+[RegisterComponent, Access(typeof(XAEDamageInAreaSystem)), NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class XAEDamageInAreaComponent : Component
{
/// <summary>
/// The radius of entities that will be affected
/// </summary>
- [DataField("radius")]
+ [DataField, AutoNetworkedField]
public float Radius = 3f;
/// <summary>
/// A whitelist for filtering certain damage.
/// </summary>
- /// <remarks>
- /// TODO: The component portion, since it uses an array, does not work currently.
- /// </remarks>
- [DataField("whitelist")]
+ [DataField, AutoNetworkedField]
public EntityWhitelist? Whitelist;
/// <summary>
/// The damage that is applied
/// </summary>
- [DataField("damage", required: true)]
+ [DataField(required: true), AutoNetworkedField]
public DamageSpecifier Damage = default!;
/// <summary>
/// The chance that damage is applied to each individual entity
/// </summary>
- [DataField("damageChance")]
+ [DataField, AutoNetworkedField]
public float DamageChance = 1f;
/// <summary>
/// Whether or not this should ignore resistances for the damage
/// </summary>
- [DataField("ignoreResistances")]
+ [DataField, AutoNetworkedField]
public bool IgnoreResistances;
}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
+
+/// <summary>
+/// This is used for using the "knock" spell when the artifact is activated
+/// </summary>
+[RegisterComponent, Access(typeof(XAEKnockSystem)), NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class XAEKnockComponent : Component
+{
+ /// <summary>
+ /// The range of the spell
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public float KnockRange = 4f;
+}
--- /dev/null
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
+
+/// <summary>
+/// When activated artifact will spawn a pair of portals. First - right in artifact, Second - at random point of station.
+/// </summary>
+[RegisterComponent, Access(typeof(XAEPortalSystem)), NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class XAEPortalComponent : Component
+{
+ /// <summary>
+ /// Entity that should be spawned as portal.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public EntProtoId PortalProto = "PortalArtifact";
+}
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
/// <summary>
/// When activated, will teleport the artifact
/// to a random position within a certain radius
/// </summary>
-[RegisterComponent]
-public sealed partial class RandomTeleportArtifactComponent : Component
+[RegisterComponent, Access(typeof(XAERandomTeleportInvokerSystem)), NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class XAERandomTeleportInvokerComponent : Component
{
/// <summary>
/// The max distance that the artifact will teleport.
/// </summary>
- [DataField("maxRange")]
+ [DataField, AutoNetworkedField]
public float MaxRange = 15f;
/// <summary>
/// The min distance that the artifact will teleport.
/// </summary>
- [DataField("minRange")]
+ [DataField, AutoNetworkedField]
public float MinRange = 6f;
}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
+
+/// <summary>
+/// Removes the masks/layers of hard fixtures from the artifact when added, allowing it to pass through walls
+/// and such.
+/// </summary>
+[RegisterComponent, Access(typeof(XAERemoveCollisionSystem)), NetworkedComponent]
+public sealed partial class XAERemoveCollisionComponent : Component;
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
+
+/// <summary>
+/// When activated, will shuffle the position of all players
+/// within a certain radius.
+/// </summary>
+[RegisterComponent, Access(typeof(XAEShuffleSystem)), NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class XAEShuffleComponent : Component
+{
+ /// <summary>
+ /// Radius, within which mobs would be switched.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public float Radius = 7.5f;
+}
--- /dev/null
+using Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for applying component-registry when artifact effect is activated.
+/// </summary>
+public sealed class XAEApplyComponentsSystem : BaseXAESystem<XAEApplyComponentsComponent>
+{
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+ /// <inheritdoc />
+ protected override void OnActivated(Entity<XAEApplyComponentsComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+ {
+ if (!_timing.IsFirstTimePredicted)
+ return;
+
+ var artifact = args.Artifact;
+
+ foreach (var registry in ent.Comp.Components)
+ {
+ var componentType = registry.Value.Component.GetType();
+ if (!ent.Comp.ApplyIfAlreadyHave && EntityManager.HasComponent(artifact, componentType))
+ {
+ continue;
+ }
+
+ if (ent.Comp.RefreshOnReactivate)
+ {
+ EntityManager.RemoveComponent(artifact, componentType);
+ }
+
+ var clone = EntityManager.ComponentFactory.GetComponent(registry.Value);
+ EntityManager.AddComponent(artifact, clone);
+ }
+ }
+}
--- /dev/null
+using Content.Shared.Damage;
+using Content.Shared.Whitelist;
+using Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact effect that damages entities from whitelist in area.
+/// </summary>
+public sealed class XAEDamageInAreaSystem : BaseXAESystem<XAEDamageInAreaComponent>
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+ [Dependency] private readonly DamageableSystem _damageable = default!;
+ [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+ /// <summary> Pre-allocated and re-used collection.</summary>
+ private readonly HashSet<EntityUid> _entitiesInRange = new();
+
+ /// <inheritdoc />
+ protected override void OnActivated(Entity<XAEDamageInAreaComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+ {
+ if (!_timing.IsFirstTimePredicted)
+ return;
+
+ var damageInAreaComponent = ent.Comp;
+ _entitiesInRange.Clear();
+ _lookup.GetEntitiesInRange(ent.Owner, damageInAreaComponent.Radius, _entitiesInRange);
+ foreach (var entityInRange in _entitiesInRange)
+ {
+ if (!_random.Prob(damageInAreaComponent.DamageChance))
+ continue;
+
+ if (_whitelistSystem.IsWhitelistFail(damageInAreaComponent.Whitelist, entityInRange))
+ continue;
+
+ _damageable.TryChangeDamage(entityInRange, damageInAreaComponent.Damage, damageInAreaComponent.IgnoreResistances);
+ }
+ }
+}
--- /dev/null
+using Content.Shared.Magic.Events;
+using Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact effect that opens doors in some area around.
+/// </summary>
+public sealed class XAEKnockSystem : BaseXAESystem<XAEKnockComponent>
+{
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+ /// <inheritdoc />
+ protected override void OnActivated(Entity<XAEKnockComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+ {
+ if (!_timing.IsFirstTimePredicted)
+ return;
+
+ var ev = new KnockSpellEvent
+ {
+ Performer = ent.Owner,
+ Range = ent.Comp.KnockRange
+ };
+ RaiseLocalEvent(ev);
+ }
+}
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
using Content.Shared.Mind.Components;
using Content.Shared.Mobs.Components;
using Content.Shared.Teleportation.Systems;
+using Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
using Robust.Shared.Collections;
using Robust.Shared.Containers;
using Robust.Shared.Random;
+using Robust.Shared.Timing;
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE;
-public sealed class PortalArtifactSystem : EntitySystem
+/// <summary>
+/// System for xeno artifact effect that creates temporary portal between places on station.
+/// </summary>
+public sealed class XAEPortalSystem : BaseXAESystem<XAEPortalComponent>
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly LinkedEntitySystem _link = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
- public override void Initialize()
+ /// <inheritdoc />
+ protected override void OnActivated(Entity<XAEPortalComponent> ent, ref XenoArtifactNodeActivatedEvent args)
{
- base.Initialize();
- SubscribeLocalEvent<PortalArtifactComponent, ArtifactActivatedEvent>(OnActivate);
- }
+ if (!_timing.IsFirstTimePredicted)
+ return;
- private void OnActivate(Entity<PortalArtifactComponent> artifact, ref ArtifactActivatedEvent args)
- {
- var map = Transform(artifact).MapID;
+ var map = Transform(ent).MapID;
var validMinds = new ValueList<EntityUid>();
var mindQuery = EntityQueryEnumerator<MindContainerComponent, MobStateComponent, TransformComponent, MetaDataComponent>();
while (mindQuery.MoveNext(out var uid, out var mc, out _, out var xform, out var meta))
validMinds.Add(uid);
}
}
- //this would only be 0 if there were a station full of AIs and no one else, in that case just stop this function
+ // this would only be 0 if there were a station full of AIs and no one else, in that case just stop this function
if (validMinds.Count == 0)
return;
- var firstPortal = Spawn(artifact.Comp.PortalProto, _transform.GetMapCoordinates(artifact));
+ var offset = _random.NextVector2(2, 3);
+ var originWithOffset = args.Coordinates.Offset(offset);
+ var firstPortal = Spawn(ent.Comp.PortalProto, originWithOffset);
var target = _random.Pick(validMinds);
- var secondPortal = Spawn(artifact.Comp.PortalProto, _transform.GetMapCoordinates(target));
+ var secondPortal = Spawn(ent.Comp.PortalProto, _transform.GetMapCoordinates(target));
- //Manual position swapping, because the portal that opens doesn't trigger a collision, and doesn't teleport targets the first time.
- _transform.SwapPositions(target, artifact.Owner);
+ // Manual position swapping, because the portal that opens doesn't trigger a collision, and doesn't teleport targets the first time.
+ _transform.SwapPositions(target, ent.Owner);
_link.TryLink(firstPortal, secondPortal, true);
}
--- /dev/null
+using Content.Shared.Popups;
+using Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE;
+
+public sealed class XAERandomTeleportInvokerSystem : BaseXAESystem<XAERandomTeleportInvokerComponent>
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly SharedTransformSystem _xform = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+ /// <inheritdoc />
+ protected override void OnActivated(Entity<XAERandomTeleportInvokerComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+ {
+ if (!_timing.IsFirstTimePredicted)
+ return;
+ // todo: teleport person who activated artifact with artifact itself
+ var component = ent.Comp;
+
+ var xform = Transform(ent.Owner);
+ _popup.PopupCoordinates(Loc.GetString("blink-artifact-popup"), xform.Coordinates, PopupType.Medium);
+
+ var offsetTo = _random.NextVector2(component.MinRange, component.MaxRange);
+ _xform.SetCoordinates(ent.Owner, xform, xform.Coordinates.Offset(offsetTo));
+ }
+}
--- /dev/null
+using Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
+using Robust.Shared.Physics;
+using Robust.Shared.Physics.Systems;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact effect that make artifact pass through other objects.
+/// </summary>
+public sealed class XAERemoveCollisionSystem : BaseXAESystem<XAERemoveCollisionComponent>
+{
+ [Dependency] private readonly SharedPhysicsSystem _physics = default!;
+
+ /// <inheritdoc />
+ protected override void OnActivated(Entity<XAERemoveCollisionComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+ {
+ if (!TryComp<FixturesComponent>(ent.Owner, out var fixtures))
+ return;
+
+ foreach (var fixture in fixtures.Fixtures.Values)
+ {
+ _physics.SetHard(ent.Owner, fixture, false, fixtures);
+ }
+ }
+}
--- /dev/null
+using Content.Shared.Mobs.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System that handles mob entities spacial shuffling effect.
+/// </summary>
+public sealed class XAEShuffleSystem : BaseXAESystem<XAEShuffleComponent>
+{
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedTransformSystem _xform = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+ private EntityQuery<MobStateComponent> _mobState;
+
+ /// <summary> Pre-allocated and re-used collection.</summary>
+ private readonly HashSet<EntityUid> _entities= new();
+
+ /// <inheritdoc />
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _mobState = GetEntityQuery<MobStateComponent>();
+ }
+
+ /// <inheritdoc />
+ protected override void OnActivated(Entity<XAEShuffleComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+ {
+ if(!_timing.IsFirstTimePredicted)
+ return;
+
+ List<Entity<TransformComponent>> toShuffle = new();
+ _entities.Clear();
+ _lookup.GetEntitiesInRange(ent.Owner, ent.Comp.Radius, _entities, LookupFlags.Dynamic | LookupFlags.Sundries);
+ foreach (var entity in _entities)
+ {
+ if (!_mobState.HasComponent(entity))
+ continue;
+
+ var xform = Transform(entity);
+
+ toShuffle.Add((entity, xform));
+ }
+
+ _random.Shuffle(toShuffle);
+
+ while (toShuffle.Count > 1)
+ {
+ var ent1 = _random.PickAndTake(toShuffle);
+ var ent2 = _random.PickAndTake(toShuffle);
+ _xform.SwapPositions((ent1, ent1), (ent2, ent2));
+ }
+ }
+}
--- /dev/null
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// Base type for xeno artifact trigger systems, that are relied on updating loop.
+/// </summary>
+/// <typeparam name="T">Type of XAT component that system will work with.</typeparam>
+public abstract class BaseQueryUpdateXATSystem<T> : BaseXATSystem<T> where T : Component
+{
+ protected EntityQuery<XenoArtifactComponent> _xenoArtifactQuery;
+
+ /// <inheritdoc />
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _xenoArtifactQuery = GetEntityQuery<XenoArtifactComponent>();
+ }
+
+ /// <inheritdoc />
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ // TODO: add a way to defer triggering artifacts to the end of the Update loop
+
+ var query = EntityQueryEnumerator<T, XenoArtifactNodeComponent>();
+ while (query.MoveNext(out var uid, out var comp, out var node))
+ {
+ if (node.Attached == null)
+ continue;
+
+ var artifact = _xenoArtifactQuery.Get(GetEntity(node.Attached.Value));
+
+ if (!CanTrigger(artifact, (uid, node)))
+ continue;
+
+ UpdateXAT(artifact, (uid, comp, node), frameTime);
+ }
+ }
+
+ /// <summary>
+ /// Handles update logic that is related to trigger component.
+ /// </summary>
+ protected abstract void UpdateXAT(
+ Entity<XenoArtifactComponent> artifact,
+ Entity<T, XenoArtifactNodeComponent> node,
+ float frameTime
+ );
+}
--- /dev/null
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// Base type for xeno artifact trigger systems. Each system should work with 1 trigger mechanics.
+/// </summary>
+/// <typeparam name="T">Type of XAT component that system will work with.</typeparam>
+public abstract class BaseXATSystem<T> : EntitySystem where T : Component
+{
+ [Dependency] protected readonly IGameTiming Timing = default!;
+ [Dependency] protected readonly SharedXenoArtifactSystem XenoArtifact = default!;
+
+ private EntityQuery<XenoArtifactUnlockingComponent> _unlockingQuery;
+
+ /// <inheritdoc/>
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _unlockingQuery = GetEntityQuery<XenoArtifactUnlockingComponent>();
+ }
+
+ /// <summary>
+ /// Subscribes to event occurring on artifact (and by relaying - on node).
+ /// </summary>
+ /// <typeparam name="TEvent">Type of event to sub for.</typeparam>
+ /// <param name="eventHandler">Delegate that handles event.</param>
+ protected void XATSubscribeDirectEvent<TEvent>(XATEventHandler<TEvent> eventHandler) where TEvent : notnull
+ {
+ SubscribeLocalEvent<T, XenoArchNodeRelayedEvent<TEvent>>((uid, component, args) =>
+ {
+ var nodeComp = Comp<XenoArtifactNodeComponent>(uid);
+
+ if (!CanTrigger(args.Artifact, (uid, nodeComp)))
+ return;
+
+ var node = new Entity<T, XenoArtifactNodeComponent>(uid, component, nodeComp);
+ eventHandler.Invoke(args.Artifact, node, ref args.Args);
+ });
+ }
+
+ /// <summary>
+ /// Checks if node can be triggered.
+ /// </summary>
+ /// <param name="artifact">Artifact entity.</param>
+ /// <param name="node">Node from <see cref="artifact"/>.</param>
+ protected bool CanTrigger(Entity<XenoArtifactComponent> artifact, Entity<XenoArtifactNodeComponent> node)
+ {
+ if (Timing.CurTime < artifact.Comp.NextUnlockTime)
+ return false;
+
+ if (_unlockingQuery.TryComp(artifact, out var unlocking) &&
+ unlocking.TriggeredNodeIndexes.Contains(XenoArtifact.GetIndex(artifact, node)))
+ return false;
+
+ if (!XenoArtifact.CanUnlockNode((node, node)))
+ return false;
+
+ return true;
+ }
+
+ /// <summary>
+ /// Triggers node. Triggered nodes participate in node unlocking.
+ /// </summary>
+ protected void Trigger(Entity<XenoArtifactComponent> artifact, Entity<T, XenoArtifactNodeComponent> node)
+ {
+ if (!Timing.IsFirstTimePredicted)
+ return;
+
+ Log.Debug($"Activated trigger {typeof(T).Name} on node {ToPrettyString(node)} for {ToPrettyString(artifact)}");
+ XenoArtifact.TriggerXenoArtifact(artifact, (node.Owner, node.Comp2));
+ }
+
+ /// <summary>
+ /// Delegate for handling relayed artifact trigger events.
+ /// </summary>
+ /// <typeparam name="TEvent">Event type to be handled.</typeparam>
+ /// <param name="artifact">Artifact, on which event occurred.</param>
+ /// <param name="node">Node which for which event were relayed.</param>
+ /// <param name="args">Event data.</param>
+ protected delegate void XATEventHandler<TEvent>(
+ Entity<XenoArtifactComponent> artifact,
+ Entity<T, XenoArtifactNodeComponent> node,
+ ref TEvent args
+ ) where TEvent : notnull;
+}
--- /dev/null
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used a XAT that activates when an entity fulfilling the given whitelist is nearby the artifact.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(XATCompNearbyComponent)), AutoGenerateComponentState]
+public sealed partial class XATCompNearbyComponent : Component
+{
+ /// <summary>
+ /// Component name that is required to activate trigger.
+ /// Is spelled without 'Component' suffix.
+ /// </summary>
+ [DataField(customTypeSerializer: typeof(ComponentNameSerializer)), AutoNetworkedField]
+ public string RequireComponentWithName = "Item";
+
+ /// <summary>
+ /// Radius, in which trigger going to search for entity with component.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public float Radius = 5;
+
+ /// <summary>
+ /// Required entities count.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public int Count = 1;
+}
--- /dev/null
+using Content.Shared.Damage;
+using Content.Shared.Damage.Prototypes;
+using Content.Shared.FixedPoint;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used for an artifact that is activated after a certain amount of damage is dealt.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(XATDamageThresholdReachedSystem))]
+public sealed partial class XATDamageThresholdReachedComponent : Component
+{
+ /// <summary>
+ /// Damage, accumulated by artifact so far. Is cleared on node activation.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public DamageSpecifier AccumulatedDamage = new();
+
+ /// <summary>
+ /// Damage that is required to activate trigger, grouped by damage type.
+ /// Only one damage type is required, amount of damage must exceed set limit.
+ /// <see cref="GroupsNeeded"/> is not required to activate trigger if this
+ /// requirement is satisfied.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public Dictionary<ProtoId<DamageTypePrototype>, FixedPoint2> TypesNeeded = new();
+
+ /// <summary>
+ /// Damage that is required to activate trigger, grouped by damage group.
+ /// Only one damage type is required, amount of damage must exceed set limit.
+ /// <see cref="TypesNeeded"/> is not required to activate trigger if this
+ /// requirement is satisfied.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public Dictionary<ProtoId<DamageGroupPrototype>, FixedPoint2> GroupsNeeded = new();
+}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used for a xenoarch trigger that activates when something dies nearby.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(XATDeathSystem)), AutoGenerateComponentState]
+public sealed partial class XATDeathComponent : Component
+{
+ /// <summary>
+ /// Range within which artifact going to listen to death event.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public float Range = 15;
+}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used for an artifact node that puts examine text on the artifact itself. Useful for flavor
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedXenoArtifactSystem)), AutoGenerateComponentState]
+public sealed partial class XATExaminableTextComponent : Component
+{
+ /// <summary> Text to display. </summary>
+ [DataField(required: true), AutoNetworkedField]
+ public LocId ExamineText;
+}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used for an artifact that is activated when someone examines it.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(XATExamineSystem))]
+public sealed partial class XATExamineComponent : Component;
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used for a xenoarch trigger that activates after any type of physical interaction.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(XATInteractionSystem))]
+public sealed partial class XATInteractionComponent : Component;
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used for an artifact trigger that activates when a thrown item lands on the ground.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(XATItemLandSystem))]
+public sealed partial class XATItemLandComponent : Component;
--- /dev/null
+using Content.Shared.Chemistry;
+using Content.Shared.Chemistry.Reaction;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.FixedPoint;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used for a xenoarch trigger that activates when a reaction occurs on the artifact.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(XATReactiveSystem)), AutoGenerateComponentState]
+public sealed partial class XATReactiveComponent : Component
+{
+ [DataField, AutoNetworkedField]
+ public List<ReactionMethod> ReactionMethods = new() { ReactionMethod.Touch };
+
+ /// <summary>
+ /// Reagents that are required in quantity <see cref="MinQuantity"/> to activate trigger.
+ /// If any of them are present in required amount - activation will be triggered.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public HashSet<ProtoId<ReagentPrototype>> Reagents = new();
+
+ /// <summary>
+ /// ReagentGroups that are required in quantity <see cref="MinQuantity"/> to activate trigger.
+ /// If any of them are present in required amount - activation will be triggered.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public HashSet<ProtoId<ReactiveGroupPrototype>> ReactiveGroups = new();
+
+ /// <summary>
+ /// Min amount of reagent to trigger.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public FixedPoint2 MinQuantity = 5f;
+}
--- /dev/null
+using Content.Shared.Destructible.Thresholds;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used for a xenoarch trigger that self-activates at a regular interval
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(XATTimerSystem)), AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class XATTimerComponent : Component
+{
+ /// <summary>
+ /// Next time timer going to activate.
+ /// </summary>
+ [DataField, AutoNetworkedField, AutoPausedField]
+ public TimeSpan NextActivation;
+
+ /// <summary>
+ /// Delay between activations.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public MinMax PossibleDelayInSeconds;
+}
--- /dev/null
+using Content.Shared.DoAfter;
+using Content.Shared.Tools;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used for a xenoarch trigger that is activated by a tool being used on it.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(XATToolUseSystem)), AutoGenerateComponentState]
+public sealed partial class XATToolUseComponent : Component
+{
+ /// <summary>
+ /// Tool to be used.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public ProtoId<ToolQualityPrototype> RequiredTool;
+
+ /// <summary>
+ /// Time that using tool on artifact will take.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public float Delay = 3;
+
+ /// <summary>
+ /// Amount of fuel using tool will take (for devices such as Welding tool).
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public float Fuel;
+}
+
+/// <summary> Do after that will be used if proper tool was used on artifact with <see cref="XATToolUseComponent"/>. </summary>
+[Serializable, NetSerializable]
+public sealed partial class XATToolUseDoAfterEvent : DoAfterEvent
+{
+ public NetEntity Node;
+
+ public XATToolUseDoAfterEvent(NetEntity node)
+ {
+ Node = node;
+ }
+
+ public override DoAfterEvent Clone()
+ {
+ return this;
+ }
+}
--- /dev/null
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// System for xeno artifact trigger that requires some entity/entities with certain component on them nearby.
+/// </summary>
+public sealed class XATCompNearbySystem : BaseQueryUpdateXATSystem<XATCompNearbyComponent>
+{
+ [Dependency] private readonly EntityLookupSystem _entityLookup = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+
+ /// <summary> Pre-allocated and re-used collection.</summary>
+ private readonly HashSet<Entity<IComponent>> _entities = new();
+
+ /// <inheritdoc />
+ protected override void UpdateXAT(
+ Entity<XenoArtifactComponent> artifact,
+ Entity<XATCompNearbyComponent, XenoArtifactNodeComponent> node,
+ float frameTime
+ )
+ {
+ var compNearbyComponent = node.Comp1;
+
+ var pos = _transform.GetMapCoordinates(artifact);
+ var comp = EntityManager.ComponentFactory.GetRegistration(compNearbyComponent.RequireComponentWithName);
+
+ _entities.Clear();
+ _entityLookup.GetEntitiesInRange(comp.Type, pos, compNearbyComponent.Radius, _entities);
+ if (_entities.Count >= compNearbyComponent.Count)
+ Trigger(artifact, node);
+ }
+}
--- /dev/null
+using Content.Shared.Damage;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// System for xeno artifact trigger that requires certain damage to be applied to artifact within a timeframe.
+/// </summary>
+public sealed class XATDamageThresholdReachedSystem : BaseXATSystem<XATDamageThresholdReachedComponent>
+{
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+
+ /// <inheritdoc/>
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ XATSubscribeDirectEvent<DamageChangedEvent>(OnDamageChanged);
+ }
+
+ private void OnDamageChanged(Entity<XenoArtifactComponent> artifact, Entity<XATDamageThresholdReachedComponent, XenoArtifactNodeComponent> node, ref DamageChangedEvent args)
+ {
+ if (!args.DamageIncreased || args.DamageDelta == null || args.Origin == artifact.Owner)
+ return;
+
+ var damageTriggerComponent = node.Comp1;
+ if (Timing.IsFirstTimePredicted)
+ damageTriggerComponent.AccumulatedDamage += args.DamageDelta;
+
+ foreach (var (type, needed) in damageTriggerComponent.TypesNeeded)
+ {
+ if (damageTriggerComponent.AccumulatedDamage.DamageDict.GetValueOrDefault(type) >= needed)
+ {
+ InvokeTrigger(artifact, node);
+ return; // intentional. Do not continue checks
+ }
+ }
+
+ foreach (var (group, needed) in damageTriggerComponent.GroupsNeeded)
+ {
+ var damageGroupPrototype = _prototype.Index(group);
+ if (!damageTriggerComponent.AccumulatedDamage.TryGetDamageInGroup(damageGroupPrototype, out var damage))
+ continue;
+
+ if (damage >= needed)
+ {
+ InvokeTrigger(artifact, node);
+ return; // intentional. Do not continue checks
+ }
+ }
+ }
+
+ private void InvokeTrigger(
+ Entity<XenoArtifactComponent> artifact,
+ Entity<XATDamageThresholdReachedComponent, XenoArtifactNodeComponent> node
+ )
+ {
+ var damageTriggerComponent = node.Comp1;
+ damageTriggerComponent.AccumulatedDamage.DamageDict.Clear();
+ Dirty(node, damageTriggerComponent);
+ Trigger(artifact, node);
+ }
+}
--- /dev/null
+using Content.Shared.Mobs;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// System for xeno artifact trigger that requires death of some mob near artifact.
+/// </summary>
+public sealed class XATDeathSystem : BaseXATSystem<XATDeathComponent>
+{
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+
+ private EntityQuery<XenoArtifactComponent> _xenoArtifactQuery;
+
+ /// <inheritdoc/>
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _xenoArtifactQuery = GetEntityQuery<XenoArtifactComponent>();
+
+ SubscribeLocalEvent<MobStateChangedEvent>(OnMobStateChanged);
+ }
+
+ private void OnMobStateChanged(MobStateChangedEvent args)
+ {
+ if (args.NewMobState != MobState.Dead)
+ return;
+
+ var targetCoords = Transform(args.Target).Coordinates;
+
+ var query = EntityQueryEnumerator<XATDeathComponent, XenoArtifactNodeComponent>();
+ while (query.MoveNext(out var uid, out var comp, out var node))
+ {
+ if (node.Attached == null)
+ continue;
+
+ var artifact = _xenoArtifactQuery.Get(GetEntity(node.Attached.Value));
+
+ if (!CanTrigger(artifact, (uid, node)))
+ continue;
+
+ var artifactCoords = Transform(artifact).Coordinates;
+ if (_transform.InRange(targetCoords, artifactCoords, comp.Range))
+ Trigger(artifact, (uid, comp, node));
+ }
+ }
+}
--- /dev/null
+using Content.Shared.Examine;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+/// <remarks>
+/// System for marking xeno artifact with certain text.
+/// </remarks>
+/// <remarks> Not actually a trigger but nice and easy to use. </remarks>
+public sealed class XATExaminableTextSystem : BaseXATSystem<XATExaminableTextComponent>
+{
+ /// <inheritdoc/>
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ XATSubscribeDirectEvent<ExaminedEvent>(OnExamined);
+ }
+
+ private void OnExamined(Entity<XenoArtifactComponent> artifact, Entity<XATExaminableTextComponent, XenoArtifactNodeComponent> node, ref ExaminedEvent args)
+ {
+ if (!args.IsInDetailsRange)
+ return;
+
+ args.PushMarkup(Loc.GetString(node.Comp1.ExamineText));
+ }
+}
--- /dev/null
+using Content.Shared.Examine;
+using Content.Shared.Ghost;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// System for xeno artifact trigger that requires player to examine details of artifact.
+/// </summary>
+public sealed class XATExamineSystem : BaseXATSystem<XATExamineComponent>
+{
+ /// <inheritdoc/>
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ XATSubscribeDirectEvent<ExaminedEvent>(OnExamine);
+ }
+
+ private void OnExamine(Entity<XenoArtifactComponent> artifact, Entity<XATExamineComponent, XenoArtifactNodeComponent> node, ref ExaminedEvent args)
+ {
+ if (!args.IsInDetailsRange)
+ return;
+
+ if (HasComp<GhostComponent>(args.Examiner))
+ return;
+
+ Trigger(artifact, node);
+ args.PushMarkup(Loc.GetString("artifact-examine-trigger-desc"));
+ }
+}
--- /dev/null
+using Content.Shared.Interaction;
+using Content.Shared.Movement.Pulling.Events;
+using Content.Shared.Weapons.Melee.Events;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// System for xeno artifact trigger that requires some way of 'using' (with default action) an artifact entity.
+/// </summary>
+public sealed class XATInteractionSystem : BaseXATSystem<XATInteractionComponent>
+{
+ /// <inheritdoc/>
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ XATSubscribeDirectEvent<PullStartedMessage>(OnPullStart);
+ XATSubscribeDirectEvent<AttackedEvent>(OnAttacked);
+ XATSubscribeDirectEvent<InteractHandEvent>(OnInteractHand);
+ }
+
+ private void OnPullStart(Entity<XenoArtifactComponent> artifact, Entity<XATInteractionComponent, XenoArtifactNodeComponent> node, ref PullStartedMessage args)
+ {
+ Trigger(artifact, node);
+ }
+
+ private void OnAttacked(Entity<XenoArtifactComponent> artifact, Entity<XATInteractionComponent, XenoArtifactNodeComponent> node, ref AttackedEvent args)
+ {
+ Trigger(artifact, node);
+ }
+
+ private void OnInteractHand(Entity<XenoArtifactComponent> artifact, Entity<XATInteractionComponent, XenoArtifactNodeComponent> node, ref InteractHandEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ args.Handled = true;
+ Trigger(artifact, node);
+ }
+}
--- /dev/null
+using Content.Shared.Throwing;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// System for xeno artifact trigger that requires hand-held artifact to be thrown (and land).
+/// </summary>
+public sealed class XATItemLandSystem : BaseXATSystem<XATItemLandComponent>
+{
+ /// <inheritdoc/>
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ XATSubscribeDirectEvent<LandEvent>(OnLand);
+ }
+
+ private void OnLand(Entity<XenoArtifactComponent> artifact, Entity<XATItemLandComponent, XenoArtifactNodeComponent> node, ref LandEvent args)
+ {
+ Trigger(artifact, node);
+ }
+}
--- /dev/null
+using Content.Shared.Chemistry;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// System for xeno artifact trigger that requires some chemical reagent.
+/// </summary>
+public sealed class XATReactiveSystem : BaseXATSystem<XATReactiveComponent>
+{
+ /// <inheritdoc/>
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ XATSubscribeDirectEvent<ReactionEntityEvent>(OnReaction);
+ }
+
+ private void OnReaction(Entity<XenoArtifactComponent> artifact, Entity<XATReactiveComponent, XenoArtifactNodeComponent> node, ref ReactionEntityEvent args)
+ {
+ var reactiveTriggerComponent = node.Comp1;
+ if (!reactiveTriggerComponent.ReactionMethods.Contains(args.Method))
+ return;
+
+ if (args.ReagentQuantity.Quantity < reactiveTriggerComponent.MinQuantity)
+ return;
+
+ if (!reactiveTriggerComponent.Reagents.Contains(args.Reagent.ID))
+ return;
+
+ if (reactiveTriggerComponent.ReactiveGroups?.Count > 0 && !ReagentHaveReactiveGroup(args, reactiveTriggerComponent))
+ return;
+
+ Trigger(artifact, node);
+ }
+
+ private static bool ReagentHaveReactiveGroup(ReactionEntityEvent args, XATReactiveComponent reactiveTriggerComponent)
+ {
+ var reactiveReagentEffectEntries = args.Reagent.ReactiveEffects;
+ if (reactiveReagentEffectEntries == null)
+ {
+ return false;
+ }
+
+ var reactiveGroups = reactiveTriggerComponent.ReactiveGroups;
+ foreach(var reactiveGroup in reactiveGroups)
+ {
+ if (reactiveReagentEffectEntries.TryGetValue(reactiveGroup, out var effectEntry)
+ && effectEntry.Methods?.Contains(args.Method) == true)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
--- /dev/null
+using Content.Shared.Examine;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+using Robust.Shared.Random;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// System for xeno artifact trigger that activates from time to time on schedule.
+/// </summary>
+public sealed class XATTimerSystem : BaseQueryUpdateXATSystem<XATTimerComponent>
+{
+ [Dependency] private readonly IRobustRandom _robustRandom = default!;
+
+ /// <inheritdoc />
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<XATTimerComponent, MapInitEvent>(OnMapInit);
+ XATSubscribeDirectEvent<ExaminedEvent>(OnExamine);
+ }
+
+ // We handle the timer resetting here because we need to keep it updated even if the node isn't able to unlock.
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var timerQuery = EntityQueryEnumerator<XATTimerComponent>();
+ while (timerQuery.MoveNext(out var uid, out var timer))
+ {
+ if (Timing.CurTime < timer.NextActivation)
+ continue;
+ timer.NextActivation += GetNextDelay(timer);
+ Dirty(uid, timer);
+ }
+ }
+
+ /// <inheritdoc />
+ protected override void UpdateXAT(Entity<XenoArtifactComponent> artifact, Entity<XATTimerComponent, XenoArtifactNodeComponent> node, float frameTime)
+ {
+ if (Timing.CurTime > node.Comp1.NextActivation)
+ Trigger(artifact, node);
+ }
+
+ private void OnMapInit(Entity<XATTimerComponent> ent, ref MapInitEvent args)
+ {
+ var delay = GetNextDelay(ent);
+ ent.Comp.NextActivation = Timing.CurTime + delay;
+ Dirty(ent);
+ }
+
+ private void OnExamine(Entity<XenoArtifactComponent> artifact, Entity<XATTimerComponent, XenoArtifactNodeComponent> node, ref ExaminedEvent args)
+ {
+ if (!args.IsInDetailsRange)
+ return;
+
+ args.PushMarkup(
+ Loc.GetString("xenoarch-trigger-examine-timer",
+ ("time", MathF.Ceiling((float) (node.Comp1.NextActivation - Timing.CurTime).TotalSeconds)))
+ );
+ }
+
+ private TimeSpan GetNextDelay(XATTimerComponent comp)
+ {
+ return TimeSpan.FromSeconds(comp.PossibleDelayInSeconds.Next(_robustRandom));
+ }
+}
--- /dev/null
+using Content.Shared.Interaction;
+using Content.Shared.Tools.Components;
+using Content.Shared.Tools.Systems;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// This handles <see cref="XATToolUseComponent"/>
+/// </summary>
+public sealed class XATToolUseSystem : BaseXATSystem<XATToolUseComponent>
+{
+ [Dependency] private readonly SharedToolSystem _tool = default!;
+
+ /// <inheritdoc/>
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ XATSubscribeDirectEvent<InteractUsingEvent>(OnInteractUsing);
+ XATSubscribeDirectEvent<XATToolUseDoAfterEvent>(OnToolUseComplete);
+ }
+
+ private void OnToolUseComplete(Entity<XenoArtifactComponent> artifact, Entity<XATToolUseComponent, XenoArtifactNodeComponent> node, ref XATToolUseDoAfterEvent args)
+ {
+ if (args.Cancelled)
+ return;
+
+ if (GetEntity(args.Node) != node.Owner)
+ return;
+
+ Trigger(artifact, node);
+ args.Handled = true;
+ }
+
+ private void OnInteractUsing(Entity<XenoArtifactComponent> artifact, Entity<XATToolUseComponent, XenoArtifactNodeComponent> node, ref InteractUsingEvent args)
+ {
+ if (!TryComp<ToolComponent>(args.Used, out var tool))
+ return;
+
+ var toolUseTriggerComponent = node.Comp1;
+ args.Handled = _tool.UseTool(args.Used,
+ args.User,
+ artifact,
+ toolUseTriggerComponent.Delay,
+ toolUseTriggerComponent.RequiredTool,
+ new XATToolUseDoAfterEvent(GetNetEntity(node)),
+ fuel: toolUseTriggerComponent.Fuel,
+ tool);
+ }
+}
--- /dev/null
+using Content.Shared.DeviceLinking;
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Xenoarchaeology.Equipment.Components;
+
+/// <summary>
+/// The console that is used for artifact analysis
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
+public sealed partial class AnalysisConsoleComponent : Component
+{
+ /// <summary>
+ /// The analyzer entity the console is linked.
+ /// Can be null if not linked.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public NetEntity? AnalyzerEntity;
+
+ [DataField]
+ public SoundSpecifier? ScanFinishedSound = new SoundPathSpecifier("/Audio/Machines/scan_finish.ogg");
+
+ /// <summary>
+ /// The sound played when an artifact has points extracted.
+ /// </summary>
+ [DataField]
+ public SoundSpecifier? ExtractSound = new SoundPathSpecifier("/Audio/Effects/radpulse11.ogg")
+ {
+ Params = new AudioParams
+ {
+ Volume = 4,
+ }
+ };
+
+ /// <summary>
+ /// The machine linking port for the analyzer
+ /// </summary>
+ [DataField]
+ public ProtoId<SourcePortPrototype> LinkingPort = "ArtifactAnalyzerSender";
+}
+
+[Serializable, NetSerializable]
+public enum ArtifactAnalyzerUiKey : byte
+{
+ Key
+}
+
+[Serializable, NetSerializable]
+public sealed class AnalysisConsoleExtractButtonPressedMessage : BoundUserInterfaceMessage;
+
--- /dev/null
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Equipment.Components;
+
+/// <summary>
+/// A machine that is combined and linked to the <see cref="AnalysisConsoleComponent"/>
+/// in order to analyze artifacts and extract points.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
+public sealed partial class ArtifactAnalyzerComponent : Component
+{
+ /// <summary>
+ /// How long it takes to analyze an artifact
+ /// </summary>
+ [DataField]
+ public TimeSpan AnalysisDuration = TimeSpan.FromSeconds(30);
+
+ /// <summary>
+ /// The current artifact placed on this analyzer.
+ /// Can be null if none are present.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public EntityUid? CurrentArtifact;
+
+ /// <summary>
+ /// The corresponding console entity.
+ /// Can be null if not linked.
+ /// </summary>
+ [ViewVariables, AutoNetworkedField]
+ public EntityUid? Console;
+
+ /// <summary>
+ /// Marker, if artifact graph data is ready for printing.
+ /// </summary>
+ [ViewVariables(VVAccess.ReadWrite)]
+ public bool ReadyToPrint = false;
+}
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-namespace Content.Shared.Xenoarchaeology.Equipment;
+namespace Content.Shared.Xenoarchaeology.Equipment.Components;
/// <summary>
/// This is an entity storage that, when activated, crushes the artifact inside of it and gives artifact fragments.
--- /dev/null
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
+
+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.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), Access(typeof(SharedNodeScannerSystem))]
+public sealed partial class NodeScannerComponent : Component
+{
+ /// <summary>
+ /// Identity-names (3-digit codes) of nodes that are triggered on scanned artifact.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public HashSet<string> TriggeredNodesSnapshot = new();
+
+ /// <summary>
+ /// State of artifact on the moment of scanning.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public ArtifactState ArtifactState;
+
+ /// <summary>
+ /// Time until next unlocking of scanned artifact can be started.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public TimeSpan? WaitTime;
+
+ /// <summary>
+ /// Moment of gametime, at which last artifact scanning was done.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public TimeSpan? ScannedAt;
+}
+
+/// <summary>
+/// Displayable to player artifact states.
+/// </summary>
+[Serializable, NetSerializable]
+public enum ArtifactState
+{
+ /// <summary> Unused default. </summary>
+ None,
+ /// <summary> Artifact is ready to start unlocking. </summary>
+ Ready,
+ /// <summary> Artifact is in unlocking state, listening to any additional trigger. </summary>
+ Unlocking,
+ /// <summary> Artifact unlocking is on cooldown, nodes could not be triggered. </summary>
+ Cooldown
+}
+
+[Serializable, NetSerializable]
+public enum NodeScannerUiKey : byte
+{
+ Key
+}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Equipment.Components;
+
+/// <summary>
+/// Suppress artifact activation, when entity is placed inside this container.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(SuppressArtifactContainerSystem))]
+public sealed partial class SuppressArtifactContainerComponent : Component;
+++ /dev/null
-using Robust.Shared.Serialization;
-using Robust.Shared.Utility;
-
-namespace Content.Shared.Xenoarchaeology.Equipment;
-
-[Serializable, NetSerializable]
-public enum ArtifactAnalzyerUiKey : byte
-{
- Key
-}
-
-[Serializable, NetSerializable]
-public sealed class AnalysisConsoleServerSelectionMessage : BoundUserInterfaceMessage
-{
-}
-
-[Serializable, NetSerializable]
-public sealed class AnalysisConsoleScanButtonPressedMessage : BoundUserInterfaceMessage
-{
-}
-
-[Serializable, NetSerializable]
-public sealed class AnalysisConsolePrintButtonPressedMessage : BoundUserInterfaceMessage
-{
-}
-
-[Serializable, NetSerializable]
-public sealed class AnalysisConsoleExtractButtonPressedMessage : BoundUserInterfaceMessage
-{
-}
-
-[Serializable, NetSerializable]
-public sealed class AnalysisConsoleBiasButtonPressedMessage(bool isDown) : BoundUserInterfaceMessage
-{
- public bool IsDown = isDown;
-}
-
-[Serializable, NetSerializable]
-public sealed class AnalysisConsoleUpdateState(
- NetEntity? artifact,
- bool analyzerConnected,
- bool serverConnected,
- bool canScan,
- bool canPrint,
- FormattedMessage? scanReport,
- bool scanning,
- bool paused,
- TimeSpan? startTime,
- TimeSpan? accumulatedRunTime,
- TimeSpan? totalTime,
- int pointAmount,
- bool isTraversalDown
-)
- : BoundUserInterfaceState
-{
- public NetEntity? Artifact = artifact;
- public bool AnalyzerConnected = analyzerConnected;
- public bool ServerConnected = serverConnected;
- public bool CanScan = canScan;
- public bool CanPrint = canPrint;
- public FormattedMessage? ScanReport = scanReport;
- public bool Scanning = scanning;
- public bool Paused = paused;
- public TimeSpan? StartTime = startTime;
- public TimeSpan? AccumulatedRunTime = accumulatedRunTime;
- public TimeSpan? TotalTime = totalTime;
- public int PointAmount = pointAmount;
- public bool IsTraversalDown = isTraversalDown;
-}
--- /dev/null
+using System.Diagnostics.CodeAnalysis;
+using Content.Shared.DeviceLinking;
+using Content.Shared.DeviceLinking.Events;
+using Content.Shared.Placeable;
+using Content.Shared.Power.EntitySystems;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Equipment.Components;
+
+namespace Content.Shared.Xenoarchaeology.Equipment;
+
+/// <summary>
+/// This system is used for managing the artifact analyzer as well as the analysis console.
+/// It also handles scanning and ui updates for both systems.
+/// </summary>
+public abstract class SharedArtifactAnalyzerSystem : EntitySystem
+{
+ [Dependency] private readonly SharedPowerReceiverSystem _powerReceiver = default!;
+
+ /// <inheritdoc/>
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<ArtifactAnalyzerComponent, ItemPlacedEvent>(OnItemPlaced);
+ SubscribeLocalEvent<ArtifactAnalyzerComponent, ItemRemovedEvent>(OnItemRemoved);
+ SubscribeLocalEvent<ArtifactAnalyzerComponent, MapInitEvent>(OnMapInit);
+
+ SubscribeLocalEvent<AnalysisConsoleComponent, NewLinkEvent>(OnNewLink);
+ SubscribeLocalEvent<AnalysisConsoleComponent, PortDisconnectedEvent>(OnPortDisconnected);
+ }
+
+ private void OnItemPlaced(Entity<ArtifactAnalyzerComponent> ent, ref ItemPlacedEvent args)
+ {
+ ent.Comp.CurrentArtifact = args.OtherEntity;
+ Dirty(ent);
+ }
+
+ private void OnItemRemoved(Entity<ArtifactAnalyzerComponent> ent, ref ItemRemovedEvent args)
+ {
+ if (args.OtherEntity != ent.Comp.CurrentArtifact)
+ return;
+
+ ent.Comp.CurrentArtifact = null;
+ Dirty(ent);
+ }
+
+ private void OnMapInit(Entity<ArtifactAnalyzerComponent> ent, ref MapInitEvent args)
+ {
+ if (!TryComp<DeviceLinkSinkComponent>(ent, out var sink))
+ return;
+
+ foreach (var source in sink.LinkedSources)
+ {
+ if (!TryComp<AnalysisConsoleComponent>(source, out var analysis))
+ continue;
+
+ analysis.AnalyzerEntity = GetNetEntity(ent);
+ ent.Comp.Console = source;
+ Dirty(source, analysis);
+ Dirty(ent);
+ break;
+ }
+ }
+
+ private void OnNewLink(Entity<AnalysisConsoleComponent> ent, ref NewLinkEvent args)
+ {
+ if (!TryComp<ArtifactAnalyzerComponent>(args.Sink, out var analyzer))
+ return;
+
+ ent.Comp.AnalyzerEntity = GetNetEntity(args.Sink);
+ analyzer.Console = ent;
+ Dirty(args.Sink, analyzer);
+ Dirty(ent);
+ }
+
+ private void OnPortDisconnected(Entity<AnalysisConsoleComponent> ent, ref PortDisconnectedEvent args)
+ {
+ var analyzerNetEntity = ent.Comp.AnalyzerEntity;
+ if (args.Port != ent.Comp.LinkingPort || analyzerNetEntity == null)
+ return;
+
+ var analyzerEntityUid = GetEntity(analyzerNetEntity);
+ if (TryComp<ArtifactAnalyzerComponent>(analyzerEntityUid, out var analyzer))
+ {
+ analyzer.Console = null;
+ Dirty(analyzerEntityUid.Value, analyzer);
+ }
+
+ ent.Comp.AnalyzerEntity = null;
+ Dirty(ent);
+ }
+
+ public bool TryGetAnalyzer(Entity<AnalysisConsoleComponent> ent, [NotNullWhen(true)] out Entity<ArtifactAnalyzerComponent>? analyzer)
+ {
+ analyzer = null;
+
+ var consoleEnt = ent.Owner;
+ if (!_powerReceiver.IsPowered(consoleEnt))
+ return false;
+
+ var analyzerUid = GetEntity(ent.Comp.AnalyzerEntity);
+ if (!TryComp<ArtifactAnalyzerComponent>(analyzerUid, out var analyzerComp))
+ return false;
+
+ if (!_powerReceiver.IsPowered(analyzerUid.Value))
+ return false;
+
+ analyzer = (analyzerUid.Value, analyzerComp);
+ return true;
+ }
+
+ public bool TryGetArtifactFromConsole(Entity<AnalysisConsoleComponent> ent, [NotNullWhen(true)] out Entity<XenoArtifactComponent>? artifact)
+ {
+ artifact = null;
+
+ if (!TryGetAnalyzer(ent, out var analyzer))
+ return false;
+
+ if (!TryComp<XenoArtifactComponent>(analyzer.Value.Comp.CurrentArtifact, out var comp))
+ return false;
+
+ artifact = (analyzer.Value.Comp.CurrentArtifact.Value, comp);
+ return true;
+ }
+
+ public bool TryGetAnalysisConsole(Entity<ArtifactAnalyzerComponent> ent, [NotNullWhen(true)] out Entity<AnalysisConsoleComponent>? analysisConsole)
+ {
+ analysisConsole = null;
+
+ if (!TryComp<AnalysisConsoleComponent>(ent.Comp.Console, out var consoleComp))
+ return false;
+
+ analysisConsole = (ent.Comp.Console.Value, consoleComp);
+ return true;
+ }
+}
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Content.Shared.Emag.Systems;
+using Content.Shared.Xenoarchaeology.Equipment.Components;
namespace Content.Shared.Xenoarchaeology.Equipment;
--- /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);
+}
-using Content.Server.Xenoarchaeology.Equipment.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Equipment.Components;
using Robust.Shared.Containers;
-namespace Content.Server.Xenoarchaeology.Equipment.Systems;
+namespace Content.Shared.Xenoarchaeology.Equipment;
public sealed class SuppressArtifactContainerSystem : EntitySystem
{
- [Dependency] private readonly ArtifactSystem _artifact = default!;
+ [Dependency] private readonly SharedXenoArtifactSystem _xenoArtifact = default!;
public override void Initialize()
{
private void OnInserted(EntityUid uid, SuppressArtifactContainerComponent component, EntInsertedIntoContainerMessage args)
{
- if (!TryComp<ArtifactComponent>(args.Entity, out var artifact))
+ if (!TryComp<XenoArtifactComponent>(args.Entity, out var artifact))
return;
- _artifact.SetIsSuppressed(args.Entity, true, artifact);
+ _xenoArtifact.SetSuppressed((args.Entity, artifact), true);
}
private void OnRemoved(EntityUid uid, SuppressArtifactContainerComponent component, EntRemovedFromContainerMessage args)
{
- if (!TryComp<ArtifactComponent>(args.Entity, out var artifact))
+ if (!TryComp<XenoArtifactComponent>(args.Entity, out var artifact))
return;
- _artifact.SetIsSuppressed(args.Entity, false, artifact);
+ _xenoArtifact.SetSuppressed((args.Entity, artifact), false);
}
}
+++ /dev/null
-using Content.Shared.Item;
-using Content.Shared.Whitelist;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Xenoarchaeology.XenoArtifacts;
-
-/// <summary>
-/// This is a prototype for...
-/// </summary>
-[Prototype]
-[DataDefinition]
-public sealed partial class ArtifactEffectPrototype : IPrototype
-{
- /// <inheritdoc/>
- [IdDataField]
- public string ID { get; private set; } = default!;
-
- /// <summary>
- /// Components that are added to the artifact when the specfic effect is active.
- /// These are removed after the node is exited and the effect is changed.
- /// </summary>
- [DataField("components", serverOnly: true)]
- public ComponentRegistry Components = new();
-
- /// <summary>
- /// Components that are permanently added to an entity when the effect's node is entered.
- /// </summary>
- [DataField("permanentComponents")]
- public ComponentRegistry PermanentComponents = new();
-
- //TODO: make this a list so we can have multiple target depths
- [DataField("targetDepth")]
- public int TargetDepth = 0;
-
- [DataField("effectHint")]
- public string? EffectHint;
-
- [DataField("whitelist")]
- public EntityWhitelist? Whitelist;
-
- [DataField("blacklist")]
- public EntityWhitelist? Blacklist;
-}
+++ /dev/null
-using Content.Shared.Item;
-using Content.Shared.Whitelist;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Xenoarchaeology.XenoArtifacts;
-
-/// <summary>
-/// This is a prototype for...
-/// </summary>
-[Prototype]
-[DataDefinition]
-public sealed partial class ArtifactTriggerPrototype : IPrototype
-{
- /// <inheritdoc/>
- [IdDataField]
- public string ID { get; private set; } = default!;
-
- [DataField("components", serverOnly: true)]
- public ComponentRegistry Components = new();
-
- [DataField("targetDepth")]
- public int TargetDepth = 0;
-
- [DataField("triggerHint")]
- public string? TriggerHint;
-
- [DataField("whitelist")]
- public EntityWhitelist? Whitelist;
-
- [DataField("blacklist")]
- public EntityWhitelist? Blacklist;
-}
-namespace Content.Shared.Xenoarchaeology.XenoArtifacts;
+namespace Content.Shared.Xenoarchaeology.XenoArtifacts;
[RegisterComponent]
public sealed partial class RandomArtifactSpriteComponent : Component
public int MaxSprite = 14;
[DataField("activationTime")]
- public double ActivationTime = 2.0;
+ public double ActivationTime = 0.4;
public TimeSpan? ActivationStart;
}
-using Content.Shared.Actions;
using Robust.Shared.Serialization;
namespace Content.Shared.Xenoarchaeology.XenoArtifacts;
public enum SharedArtifactsVisuals : byte
{
SpriteIndex,
- IsActivated
-}
-
-/// <summary>
-/// Raised as an instant action event when a sentient artifact activates itself using an action.
-/// </summary>
-public sealed partial class ArtifactSelfActivateEvent : InstantActionEvent
-{
+ IsActivated,
+ IsUnlocking
}
license: "CC-BY-4.0"
copyright: "Created by Garuda1982, split into individual files and converted to OGG and Mono by EmoGarbage404 (github)"
source: "https://freesound.org/people/Garuda1982/sounds/560310/"
+- files: ["artifact-activation-fail1.ogg"]
+ license: "CC-BY-NC-3.0"
+ copyright: "Created by GabrieleKkj, split and converted by fildrance"
+ source: "https://freesound.org/people/GabrieleKkj/sounds/264370/"
+- files: ["artifact-force-activated1.ogg"]
+ license: "CC0-1.0"
+ copyright: "Created by brunoboselli, split and converted by fildrance"
+ source: "https://freesound.org/people/brunoboselli/sounds/457294/"
Adds an access log to this entity. Do note that this bypasses the log's default limit and pause check.
command-description-stationevent-simulate =
Simulates N number of rounds in which events will occur and prints the occurrences of every event after.
+command-description-xenoartifact-list =
+ List all EntityUids of spawned artifacts.
+command-description-xenoartifact-printMatrix =
+ Prints out matrix that displays all edges between nodes.
+command-description-xenoartifact-totalResearch =
+ Gets all research points that can be extracted from artifact currently.
+command-description-xenoartifact-averageResearch =
+ Calculates amount of research points average generated xeno artifact will output when fully activated.
+command-description-xenoartifact-unlockAllNodes =
+ Unlocks all nodes of artifact.
} {NATURALFIXED($time, 3)} {MANY("second", $time)} of {LOC($key)}
}
-reagent-effect-guidebook-activate-artifact =
- { $chance ->
- [1] Attempts
- *[other] attempt
- } to activate an artifact
-
reagent-effect-guidebook-set-solution-temperature-effect =
{ $chance ->
[1] Sets
-analysis-console-menu-title = analysis console
-analysis-console-server-list-button = Server List
-analysis-console-scan-button = Scan
-analysis-console-scan-tooltip-info = Scan artifacts to learn information about their structure.
-analysis-console-print-button = Print
-analysis-console-print-tooltip-info = Print out the current information about the artifact.
-analysis-console-extract-button = Extract
-analysis-console-extract-button-info = Extract points from an artifact based on the newly explored nodes.
-analysis-console-bias-up = Up
-analysis-console-bias-down = Down
-analysis-console-bias-button-info-up = Toggles the bias an artifact has in moving between its nodes. Up heads toward zero depth.
-analysis-console-bias-button-info-down = Toggles the bias an artifact has in moving between its nodes. Down heads toward ever-higher depths.
+analysis-console-menu-title = Broad-Spectrum Mark 3 Analysis Console
+analysis-console-server-list-button = Server
+analysis-console-extract-button = Extract points
analysis-console-info-no-scanner = No analyzer connected! Please connect one using a multitool.
-analysis-console-info-no-artifact = No artifact present! Place one on the pad then scan for information.
+analysis-console-info-no-artifact = No artifact present! Place one on the pad to view node information.
analysis-console-info-ready = Systems operational. Ready to scan.
-analysis-console-info-id = NODE_ID: {$id}
-analysis-console-info-depth = DEPTH: {$depth}
-analysis-console-info-triggered-true = ACTIVATED: TRUE
-analysis-console-info-triggered-false = ACTIVATED: FALSE
-analysis-console-info-effect = REACTION: {$effect}
-analysis-console-info-trigger = STIMULUS: {$trigger}
-analysis-console-info-edges = EDGES: {$edges}
-analysis-console-info-value = UNEXTRACTED_VALUE: {$value}
-
+analysis-console-no-node = Select node to view
+analysis-console-info-id = [font="Monospace" size=11]ID:[/font]
+analysis-console-info-id-value = [font="Monospace" size=11][color=yellow]{$id}[/color][/font]
+analysis-console-info-class = [font="Monospace" size=11]Class:[/font]
+analysis-console-info-class-value = [font="Monospace" size=11]{$class}[/font]
+analysis-console-info-locked = [font="Monospace" size=11]Status:[/font]
+analysis-console-info-locked-value = [font="Monospace" size=11][color={ $state ->
+ [0] red]Locked
+ [1] lime]Unlocked
+ *[2] plum]Active
+}[/color][/font]
+analysis-console-info-durability = [font="Monospace" size=11]Durability:[/font]
+analysis-console-info-durability-value = [font="Monospace" size=11][color={$color}]{$current}/{$max}[/color][/font]
+analysis-console-info-effect = [font="Monospace" size=11]Effect:[/font]
+analysis-console-info-effect-value = [font="Monospace" size=11][color=gray]{ $state ->
+ [true] {$info}
+ *[false] Unlock nodes to gain info
+}[/color][/font]
+analysis-console-info-trigger = [font="Monospace" size=11]Triggers:[/font]
+analysis-console-info-triggered-value = [font="Monospace" size=11][color=gray]{$triggers}[/color][/font]
analysis-console-info-scanner = Scanning...
analysis-console-info-scanner-paused = Paused.
analysis-console-progress-text = {$seconds ->
[one] T-{$seconds} second
*[other] T-{$seconds} seconds
}
-analysis-console-no-server-connected = Cannot extract. No server connected.
-analysis-console-no-artifact-placed = No artifact on scanner.
-analysis-console-no-points-to-extract = No points to extract.
-analyzer-artifact-component-upgrade-analysis = analysis duration
+analysis-console-extract-value = [font="Monospace" size=11][color=orange]Node {$id} (+{$value})[/color][/font]
+analysis-console-extract-none = [font="Monospace" size=11][color=orange] No unlocked nodes have any points left to extract [/color][/font]
+analysis-console-extract-sum = [font="Monospace" size=11][color=orange]Total Research: {$value}[/color][/font]
-analysis-console-print-popup = The console printed out a report.
analyzer-artifact-extract-popup = Energy shimmers on the artifact's surface!
-
-analysis-report-title = Artifact Report: Node {$id}
-### Verbs
+### Commands
+cmd-unlocknode-desc = Unlocks a node on a given artifact
+cmd-unlocknode-help = unlocknode <artifact uid> <node uid>
+cmd-parse-failure-unlocknode-arg-num = Incorrect number of args
+cmd-parse-failure-unlocknode-invalid-entity = Provided netEntity is not valid node
+### Verbs
artifact-verb-make-always-active = Make artifact always active
artifact-verb-activate = Activate artifact
+
+### Unlocking
+artifact-unlock-state-begin = It begins to shift in strange ways...
+artifact-unlock-state-end-success = It slows down, visibly changed.
+artifact-unlock-state-end-failure = It slows down before uneventfully stopping.
+
+### Activation
+artifact-activation-fail = Nothing happens...
+
+### Misc.
+artifact-examine-trigger-desc = [color=gray][italic]Am I on your mind?[/italic][/color]
+
+artifact-node-class-1 = [color=#ff2bb1]Hylic[/color]
+artifact-node-class-2 = [color=#ff8b2b]Psychic[/color]
+artifact-node-class-3 = [color=#a9ff38]Pneumatic[/color]
+artifact-node-class-4 = [color=#2bfff8]Archon[/color]
+artifact-node-class-5 = [color=#7883ff]Luminary[/color]
+artifact-node-class-6 = [color=#be78ff]Demiurge[/color]
artifact-trigger-hint-examine = Examination
artifact-trigger-hint-medical = Therapeutic chemicals
+xenoarch-trigger-tip-music = Harmonical sound vibrations
+xenoarch-trigger-tip-heat = High temperature gas
+xenoarch-trigger-tip-cold = Low temperature gas
+xenoarch-trigger-tip-no-oxygen = Oxygen-free environment
+xenoarch-trigger-tip-water = Water
+xenoarch-trigger-tip-co2 = Carbon dioxide
+xenoarch-trigger-tip-plasma = Non-solid plasma
+xenoarch-trigger-tip-tritium = Tritium
+xenoarch-trigger-tip-ammonia = Ammonia
+xenoarch-trigger-tip-n2o = Nitrous oxide
+xenoarch-trigger-tip-frezon = Frezon
+xenoarch-trigger-tip-radiation = Radiation
+xenoarch-trigger-tip-brute-damage = Physical damage
+xenoarch-trigger-tip-interaction = Physical interaction
+xenoarch-trigger-tip-wrenching = Tightening
+xenoarch-trigger-tip-prying = Prying
+xenoarch-trigger-tip-screwing = Screwing
+xenoarch-trigger-tip-pulsing = Pulsing
+xenoarch-trigger-tip-pressure-low = Low pressure
+xenoarch-trigger-tip-pressure-high = High pressure
+xenoarch-trigger-tip-examine = Close inspection
+xenoarch-trigger-tip-timer = Regular self-activation
+xenoarch-trigger-tip-blood = Blood
+xenoarch-trigger-tip-throw = Being thrown
+xenoarch-trigger-tip-death = Death
+xenoarch-trigger-tip-magnet = Magnetic waves
+
+### Description hints
+xenoarch-trigger-examine-wrenching = There's a loose bit spinning around.
+xenoarch-trigger-examine-prying = There's a panel coming up from the surface.
+xenoarch-trigger-examine-screwing = There's a raised section with a small inset on it.
+xenoarch-trigger-examine-pulsing = An exposed diode pokes out of the artifact's surface.
+xenoarch-trigger-examine-timer = Carvings and scratches cover the surface... You can just barely make out a number: [italic]{$time}[/italic]
+
+### Effects hints
+xenoarch-effect-puddle = Produces puddle of following reagents: {$reagent}
+xenoarch-effect-foam = Produces foam of following reagents: {$reagent}
-node-scan-popup = The node ID is {$id}
-node-scan-tooltip = Scan artifact
+node-scan-tooltip = Scan active nodes
+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
components:
- type: Transform
parent: 766
- - type: Artifact
- isSuppressed: True
+ - type: XenoArtifact
- type: Physics
canCollide: False
- type: InsideEntityStorage
components:
- type: Transform
parent: 768
- - type: Artifact
- isSuppressed: True
+ - type: XenoArtifact
- type: Physics
canCollide: False
- type: InsideEntityStorage
rot: 1.5707963267948966 rad
pos: -5.5242987,-12.5
parent: 1
- - type: BiasedArtifact
- uid: 789
components:
- type: Transform
rot: 1.5707963267948966 rad
pos: 6.506952,-12.5
parent: 1
- - type: BiasedArtifact
- proto: VendingMachineMedical
entities:
- uid: 230
useDelay: 30
event: !type:VendingMachineSelfDispenseEvent
-- type: entity
- id: ActionArtifactActivate
- name: Activate Artifact
- description: Immediately activates your current artifact node.
- components:
- - type: InstantAction
- icon:
- sprite: Objects/Specific/Xenoarchaeology/xeno_artifacts.rsi
- state: ano01
- useDelay: 60
- event: !type:ArtifactSelfActivateEvent
-
- type: entity
id: ActionToggleBlock
name: Block
amount: 1
whitelist:
components:
- - Artifact
+ - XenoArtifact
- type: cargoBounty
id: BountyBaseballBat
capacity: 1
whitelist:
components:
- - Artifact
+ - XenoArtifact
- type: Weldable
- type: SuppressArtifactContainer
- type: RadiationBlockingContainer
- type: entity
- parent: BaseItem
+ parent: [BaseItem, BaseXenoArtifact]
id: BaseXenoArtifactItem
- name: alien artifact
- description: A strange handheld alien device.
+ name: artifact
+ description: A strange artifact from time unknown. Looks like a good time. Fits in hand perfectly.
abstract: true
+ noSpawn: true
components:
+ # Visual
- type: Sprite
sprite: Objects/Specific/Xenoarchaeology/item_artifacts.rsi
layers:
- state: ano01
map: [ "enum.ArtifactsVisualLayers.Base" ]
- state: ano01_on
- map: [ "enum.ArtifactsVisualLayers.Effect" ]
+ map: [ "enum.ArtifactsVisualLayers.UnlockingEffect" ]
+ visible: false
+ - state: artifact-activation
+ sprite: Objects/Specific/Xenoarchaeology/xeno_artifacts.rsi
+ map: [ "enum.ArtifactsVisualLayers.ActivationEffect" ]
visible: false
- - type: Damageable
- type: Physics
bodyType: Dynamic
- type: CollisionWake
enabled: false
- type: InteractionOutline
- - type: Reactive
- groups:
- Acidic: [Touch]
- type: Fixtures
fixtures:
fix1:
- Opaque
restitution: 0.3 # fite me
friction: 0.2
- - type: Artifact
- type: RandomArtifactSprite
maxSprite: 11
activationTime: 2.4
- type: RandomSprite
available:
- - enum.ArtifactsVisualLayers.Effect:
+ - enum.ArtifactsVisualLayers.UnlockingEffect:
ano01_on: Rainbow
- - type: UserInterface #needs to be here for certain effects
- interfaces:
- enum.StorageUiKey.Key:
- type: StorageBoundUserInterface
- enum.TransferAmountUiKey.Key:
- type: TransferAmountBoundUserInterface
- enum.InstrumentUiKey.Key:
- type: InstrumentBoundUserInterface
- enum.IntercomUiKey.Key:
- type: IntercomBoundUserInterface
- - type: Appearance
+ # gameplay interactions
- type: Item
size: Normal
sprite: Objects/Specific/Xenoarchaeology/item_artifacts.rsi
heldPrefix: ano01
- - type: Actions
- type: Construction
graph: Artifact
node: done
- - type: GuideHelp
- guides:
- - Xenoarchaeology
+ - type: XenoArtifact
+ effectsTable: !type:GroupSelector
+ children:
+ - !type:NestedSelector
+ tableId: XenoArtifactEffectsDefaultTable
+ weight: 54
+ - !type:NestedSelector
+ tableId: XenoArtifactEffectsHandheldOnlyTable
+ weight: 2
- type: entity
parent: BaseXenoArtifactItem
id: SimpleXenoArtifactItem
- suffix: Simple
+ suffix: hand-sized Simple
components:
- - type: Artifact
- nodesMin: 2
- nodesMax: 5
+ - type: XenoArtifact
+ nodeCount:
+ min: 2
+ max: 5
- type: entity
parent: BaseXenoArtifactItem
id: MediumXenoArtifactItem
- suffix: Medium
+ suffix: hand-sized Medium
components:
- - type: Artifact
- nodesMin: 5
- nodesMax: 9
+ - type: XenoArtifact
+ nodeCount:
+ min: 5
+ max: 9
- type: entity
parent: BaseXenoArtifactItem
id: ComplexXenoArtifactItem
- suffix: Complex
+ suffix: hand-sized Complex
components:
- - type: Artifact
- nodesMin: 9
- nodesMax: 13
+ - type: XenoArtifact
+ nodeCount:
+ min: 9
+ max: 13
# this exists for crafting item artifacts so that the final result can be simple, medium, or complex.
- type: entity
parent: BaseXenoArtifactItem
id: VariedXenoArtifactItem
- suffix: Varied
+ suffix: hand-sized Varied
components:
- - type: Artifact
- nodesMin: 2
- nodesMax: 13
+ - type: XenoArtifact
+ nodeCount:
+ min: 2
+ max: 13
- type: entity
id: ArtifactFragment
sprite: Objects/Specific/Xenoarchaeology/node_scanner.rsi
- type: NodeScanner
- type: UseDelay
- delay: 3
+ delay: 1
- type: GuideHelp
guides:
- ArtifactReports
+ - type: ActivatableUI
+ key: enum.NodeScannerUiKey.Key
+ singleUser: true
+ - type: UserInterface #needs to be here for certain effects
+ interfaces:
+ enum.NodeScannerUiKey.Key:
+ type: NodeScannerBoundUserInterface
+++ /dev/null
-- type: entity
- parent: BaseStructureDynamic
- id: BaseXenoArtifact
- name: alien artifact
- description: A strange alien device.
- abstract: true
- components:
- - type: Sprite
- drawdepth: SmallObjects
- sprite: Objects/Specific/Xenoarchaeology/xeno_artifacts.rsi
- noRot: true
- layers:
- - state: ano30
- map: [ "enum.ArtifactsVisualLayers.Base" ]
- - state: ano30_on
- map: [ "enum.ArtifactsVisualLayers.Effect" ]
- visible: false
- - type: Damageable
- - type: Physics
- bodyType: Dynamic
- - type: Transform
- noRot: true
- - type: UserInterface #needs to be here for certain effects
- interfaces:
- enum.StorageUiKey.Key:
- type: StorageBoundUserInterface
- enum.TransferAmountUiKey.Key:
- type: TransferAmountBoundUserInterface
- enum.InstrumentUiKey.Key:
- type: InstrumentBoundUserInterface
- enum.IntercomUiKey.Key:
- type: IntercomBoundUserInterface
- - type: Reactive
- groups:
- Acidic: [Touch]
- - type: Fixtures
- fixtures:
- fix1:
- shape:
- !type:PhysShapeCircle
- radius: 0.45
- density: 75
- layer: # doesn't collide with artifact storage
- - Opaque
- mask:
- - MachineMask
- - type: InteractionOutline
- - type: Artifact
- - type: RandomArtifactSprite
- maxSprite: 36
- - type: RandomSprite
- available:
- - enum.ArtifactsVisualLayers.Effect:
- ano01_on: Rainbow
- - type: Appearance
- - type: Actions
- - type: GuideHelp
- guides:
- - Xenoarchaeology
- - type: StealTarget
- stealGroup: XenoArtifact
- - type: ContainerContainer
- containers:
- storagebase: !type:Container # needed for the EffectStorage artifactEffect
- ents: [ ]
- revolver-ammo: !type:Container # needed for the EffectBigIron artifactEffect
-
-- type: entity
- parent: BaseXenoArtifact
- id: SimpleXenoArtifact
- suffix: Simple
- components:
- - type: Artifact
- nodesMin: 2
- nodesMax: 5
-
-- type: entity
- parent: BaseXenoArtifact
- id: MediumXenoArtifact
- suffix: Medium
- components:
- - type: Artifact
- nodesMin: 5
- nodesMax: 9
-
-- type: entity
- parent: BaseXenoArtifact
- id: ComplexXenoArtifact
- suffix: Complex
- components:
- - type: Artifact
- nodesMin: 9
- nodesMax: 13
-
--- /dev/null
+- type: entity
+ parent: [BaseStructureDynamic, BaseXenoArtifact]
+ id: BaseXenoArtifactStructure
+ name: artifact
+ abstract: true
+ noSpawn: true
+ components:
+ # Visual
+ - type: Sprite
+ drawdepth: SmallObjects
+ sprite: Objects/Specific/Xenoarchaeology/xeno_artifacts.rsi
+ noRot: true
+ layers:
+ - state: ano30
+ map: [ "enum.ArtifactsVisualLayers.Base" ]
+ - state: ano30_on
+ map: [ "enum.ArtifactsVisualLayers.UnlockingEffect" ]
+ visible: false
+ - state: artifact-activation
+ map: [ "enum.ArtifactsVisualLayers.ActivationEffect" ]
+ visible: false
+ - type: RandomArtifactSprite
+ maxSprite: 36
+ - type: RandomSprite
+ available:
+ - enum.ArtifactsVisualLayers.UnlockingEffect:
+ ano01_on: Rainbow
+ - type: Transform
+ noRot: true
+ - type: Fixtures
+ fixtures:
+ fix1:
+ shape:
+ !type:PhysShapeCircle
+ radius: 0.45
+ density: 75
+ layer: # doesn't collide with artifact storage
+ - Opaque
+ mask:
+ - MachineMask
+
+- type: entity
+ parent: BaseXenoArtifactStructure
+ id: SimpleXenoArtifact
+ suffix: Simple
+ components:
+ - type: XenoArtifact
+ nodeCount:
+ min: 2
+ max: 5
+
+- type: entity
+ parent: BaseXenoArtifactStructure
+ id: MediumXenoArtifact
+ suffix: Medium
+ components:
+ - type: XenoArtifact
+ nodeCount:
+ min: 5
+ max: 9
+
+- type: entity
+ parent: BaseXenoArtifactStructure
+ id: ComplexXenoArtifact
+ suffix: Complex
+ components:
+ - type: XenoArtifact
+ nodeCount:
+ min: 9
+ max: 13
+
--- /dev/null
+- type: entity
+ id: BaseXenoArtifact
+ name: artifact
+ description: A strange artifact from time unknown. Looks like a good time.
+ abstract: true
+ noSpawn: true
+ components:
+ # Visual
+ - type: Appearance
+ - type: InteractionOutline
+ - type: UserInterface #needs to be here for certain effects
+ interfaces:
+ enum.StorageUiKey.Key:
+ type: StorageBoundUserInterface
+ enum.TransferAmountUiKey.Key:
+ type: TransferAmountBoundUserInterface
+ enum.InstrumentUiKey.Key:
+ type: InstrumentBoundUserInterface
+ enum.IntercomUiKey.Key:
+ type: IntercomBoundUserInterface
+ - type: GuideHelp
+ guides:
+ - Xenoarchaeology
+ # gameplay interactions
+ - type: XenoArtifact
+ effectsTable: !type:NestedSelector
+ tableId: XenoArtifactEffectsDefaultTable
+ - type: Damageable
+ - type: Actions
+ - type: Physics
+ bodyType: Dynamic
+ - type: UseDelay
+ - type: StealTarget
+ stealGroup: XenoArtifact
+ - type: ContainerContainer
+ containers:
+ node-container: !type:Container
+ showEnts: False
+ occludes: True
+ ents: []
+ # These components are needed for certain triggers to work.
+ - type: RadiationReceiver
+ - type: Reactive
state: generic_panel_open
- type: ResearchClient
- type: AnalysisConsole
- reportEntityId: PaperArtifactAnalyzer
- type: DeviceList
- type: DeviceNetwork
deviceNetId: Wired
ports:
- ArtifactAnalyzerSender
- type: ActivatableUI
- key: enum.ArtifactAnalzyerUiKey.Key
+ key: enum.ArtifactAnalyzerUiKey.Key
- type: UserInterface
interfaces:
- enum.ArtifactAnalzyerUiKey.Key:
+ enum.ArtifactAnalyzerUiKey.Key:
type: AnalysisConsoleBoundUserInterface
enum.ResearchClientUiKey.Key:
type: ResearchClientBoundUserInterface
powerLoad: 12000
needsPower: false #only turns on when scanning
- type: ArtifactAnalyzer
- - type: TraversalDistorter
- type: ItemPlacer
whitelist:
components:
- - Artifact
+ - XenoArtifact
- type: DeviceNetwork
deviceNetId: Wired
receiveFrequencyId: BasicDevice
- type: ArtifactCrusher
crushingWhitelist:
components:
- - Artifact
+ - XenoArtifact
crushingDamage:
types:
Blunt: 10
capacity: 1
whitelist:
components:
- - Artifact
+ - XenoArtifact
tags:
- CanPilot # People
- VimPilot # Pets
damage:
types:
Caustic: 2
- reactiveEffects:
- Acidic:
- methods: [ Touch ]
- effects:
- - !type:ActivateArtifact
- conditions:
- - !type:ReagentThreshold
- min: 5
- type: reagent
id: Benzene
- type: soundCollection
- id: ArtifactActivation
+ id: ArtifactUnlockingActivationSuccess
files:
- /Audio/Items/Artifact/artifact1.ogg
- /Audio/Items/Artifact/artifact2.ogg
- /Audio/Items/Artifact/artifact5.ogg
- /Audio/Items/Artifact/artifact6.ogg
- /Audio/Items/Artifact/artifact7.ogg
+
+- type: soundCollection
+ id: ArtifactUnlockActivationFailure
+ files:
+ - /Audio/Items/Artifact/artifact-activation-fail1.ogg
+
+- type: soundCollection
+ id: ArtifactForceActivation
+ files:
+ - /Audio/Items/Artifact/artifact-force-activated1.ogg
+++ /dev/null
-- type: artifactEffect
- id: EffectBadFeeling
- targetDepth: 0
- effectHint: artifact-effect-hint-mental
- components:
- - type: TelepathicArtifact
- messages:
- - badfeeling-artifact-1
- - badfeeling-artifact-2
- - badfeeling-artifact-3
- - badfeeling-artifact-4
- - badfeeling-artifact-5
- - badfeeling-artifact-6
- - badfeeling-artifact-7
- - badfeeling-artifact-8
- - badfeeling-artifact-9
- - badfeeling-artifact-10
- - badfeeling-artifact-11
- - badfeeling-artifact-12
- - badfeeling-artifact-13
- - badfeeling-artifact-14
- - badfeeling-artifact-15
- drastic:
- - badfeeling-artifact-drastic-1
- - badfeeling-artifact-drastic-2
- - badfeeling-artifact-drastic-3
- - badfeeling-artifact-drastic-4
- - badfeeling-artifact-drastic-5
- - badfeeling-artifact-drastic-6
-
-- type: artifactEffect
- id: EffectGoodFeeling
- targetDepth: 0
- effectHint: artifact-effect-hint-mental
- components:
- - type: TelepathicArtifact
- messages:
- - goodfeeling-artifact-1
- - goodfeeling-artifact-2
- - goodfeeling-artifact-3
- - goodfeeling-artifact-4
- - goodfeeling-artifact-5
- - goodfeeling-artifact-6
- - goodfeeling-artifact-7
- - goodfeeling-artifact-8
- - goodfeeling-artifact-9
- - goodfeeling-artifact-10
- - goodfeeling-artifact-11
- - goodfeeling-artifact-12
- - goodfeeling-artifact-13
- - goodfeeling-artifact-14
- drastic:
- - goodfeeling-artifact-drastic-1
- - goodfeeling-artifact-drastic-2
- - goodfeeling-artifact-drastic-3
- - goodfeeling-artifact-drastic-4
- - goodfeeling-artifact-drastic-5
- - goodfeeling-artifact-drastic-6
-
-- type: artifactEffect
- id: EffectJunkSpawn
- targetDepth: 0
- effectHint: artifact-effect-hint-creation
- components:
- - type: SpawnArtifact
- maxSpawns: 10
- spawns:
- - id: FoodPacketSyndiTrash
- prob: 0.1
- orGroup: Trash
- - id: FoodPacketSemkiTrash
- prob: 0.1
- orGroup: Trash
- - id: FoodPacketBoritosTrash
- prob: 0.1
- orGroup: Trash
- - id: FoodPacketCheesieTrash
- prob: 0.1
- orGroup: Trash
- - id: FoodPacketChipsTrash
- prob: 0.1
- orGroup: Trash
- - id: FoodPacketChocolateTrash
- prob: 0.1
- orGroup: Trash
- - id: FoodPacketEnergyTrash
- prob: 0.1
- orGroup: Trash
- - id: FoodPacketPopcornTrash
- prob: 0.1
- orGroup: Trash
- - id: FoodPacketRaisinsTrash
- prob: 0.1
- orGroup: Trash
- - id: ToySpawner
- prob: 0.1
- orGroup: Trash
-
-- type: artifactEffect
- id: EffectLightFlicker
- targetDepth: 0
- effectHint: artifact-effect-hint-electrical-interference
- components:
- - type: LightFlickerArtifact
-
-- type: artifactEffect
- id: EffectPointLight
- targetDepth: 0
- components:
- - type: PointLight
- radius: 8
- energy: 10
- color: "#daa3fd"
- - type: TriggerArtifact
- - type: FlashOnTrigger
- range: 8
-
-- type: artifactEffect #bornana
- id: EffectBananaSpawn
- targetDepth: 0
- effectHint: artifact-effect-hint-creation
- components:
- - type: SpawnArtifact
- maxSpawns: 20
- spawns:
- - id: FoodBanana
- amount: 3
- maxAmount: 6
- - type: ChemicalPuddleArtifact
- chemicalSolution:
- maxVol: 100
- canReact: false
- possibleChemicals:
- - Potassium
-
-- type: artifactEffect
- id: EffectFloraSpawn
- targetDepth: 1
- effectHint: artifact-effect-hint-creation
- components:
- - type: SpawnArtifact
- maxSpawns: 3
- spawns:
- - id: RandomFloraTree
-
-- type: artifactEffect
- id: EffectThrow
- targetDepth: 0
- effectHint: artifact-effect-hint-environment
- components:
- - type: ThrowArtifact
-
-- type: artifactEffect
- id: EffectChemicalPuddle
- targetDepth: 0
- effectHint: artifact-effect-hint-biochemical
- components:
- - type: ChemicalPuddleArtifact
- chemicalSolution:
- maxVol: 500
- canReact: false
- possibleChemicals:
- - Aluminium
- - Carbon
- - Chlorine
- - Copper
- - Ethanol
- - Fluorine
- - Sugar
- - Hydrogen
- - Iodine
- - Iron
- - Lithium
- - Mercury
- - Nitrogen
- - Oxygen
- - Phosphorus
- - Potassium
- - Radium
- - Silicon
- - Sodium
- - Water
- - Sulfur
-
-- type: artifactEffect
- id: EffectCold
- targetDepth: 1
- effectHint: artifact-effect-hint-consumption
- components:
- - type: TemperatureArtifact
- targetTemp: 50
-
-- type: artifactEffect
- id: EffectHeat
- targetDepth: 1
- effectHint: artifact-effect-hint-release
- components:
- - type: TemperatureArtifact
- targetTemp: 500
-
-- type: artifactEffect
- id: EffectFoamMild
- targetDepth: 1
- effectHint: artifact-effect-hint-biochemical
- components:
- - type: FoamArtifact
- reagents:
- - Oxygen
- - Plasma
- - Blood
- - SpaceCleaner
- - Nutriment
- - SpaceLube
- - Ethanol
- - Mercury
- - VentCrud
- - WeldingFuel
- - JuiceThatMakesYouWeh
-
-- type: artifactEffect
- id: EffectInstrumentSpawn
- targetDepth: 1
- effectHint: artifact-effect-hint-creation
- components:
- - type: SpawnArtifact
- maxSpawns: 5
- spawns:
- - id: RandomInstruments
-
-- type: artifactEffect
- id: EffectMonkeySpawn
- targetDepth: 1
- effectHint: artifact-effect-hint-creation
- components:
- - type: SpawnArtifact
- spawns:
- - id: MobMonkey
- orGroup: monkey
- prob: 0.95
- - id: MobGorilla #harambe
- orGroup: monkey
- prob: 0.05
-
-- type: artifactEffect
- id: EffectChargeBatteries
- targetDepth: 1
- effectHint: artifact-effect-hint-release
- components:
- - type: ChargeBatteryArtifact
- - type: TelepathicArtifact
- messages:
- - charge-artifact-popup
-
-- type: artifactEffect
- id: EffectRadiate
- targetDepth: 1
- effectHint: artifact-effect-hint-release
- components:
- - type: RadiationSource
- intensity: 1
- slope: 0.3
-
-- type: artifactEffect
- id: EffectKnock
- targetDepth: 1
- effectHint: artifact-effect-hint-electrical-interference
- components:
- - type: KnockArtifact
-
-- type: artifactEffect
- id: EffectMagnet
- targetDepth: 1
- effectHint: artifact-effect-hint-magnet
- components:
- - type: GravityWell
- maxRange: 3
- baseRadialAcceleration: 1
- baseTangentialAcceleration: 3
-
-- type: artifactEffect
- id: EffectAntiMagnet
- targetDepth: 1
- effectHint: artifact-effect-hint-magnet
- components:
- - type: GravityWell
- maxRange: 3
- baseRadialAcceleration: -1
- baseTangentialAcceleration: -3
-
-- type: artifactEffect
- id: EffectInvisibility
- targetDepth: 2
- effectHint: artifact-effect-hint-visual
- components:
- - type: Stealth
- hadOutline: true
- - type: StealthOnMove
- passiveVisibilityRate: -0.10
- movementVisibilityRate: 0.10
-
-- type: artifactEffect
- id: EffectExplosionScary
- targetDepth: 2
- effectHint: artifact-effect-hint-environment
- components:
- - type: TriggerArtifact
- - type: ExplodeOnTrigger
- - type: Explosive
- deleteAfterExplosion: false
- explosionType: Radioactive
- totalIntensity: 300
- intensitySlope: 2
- maxIntensity: 1.5
- canCreateVacuum: false
-
-- type: artifactEffect
- id: EffectRareMaterialSpawn
- targetDepth: 2
- effectHint: artifact-effect-hint-creation
- components:
- - type: SpawnArtifact
- spawns:
- - id: SilverOre1
- prob: 0.3
- maxAmount: 3
- - id: PlasmaOre1
- prob: 0.3
- maxAmount: 3
- - id: GoldOre1
- prob: 0.3
- maxAmount: 3
- - id: UraniumOre1
- prob: 0.3
- maxAmount: 3
-
-- type: artifactEffect
- id: EffectAngryCarpSpawn
- targetDepth: 2
- effectHint: artifact-effect-hint-creation
- components:
- - type: SpawnArtifact
- maxSpawns: 5
- spawns:
- - id: MobCarpHolo
- orGroup: carp
- - id: MobCarpMagic
- orGroup: carp
-
-- type: artifactEffect
- id: EffectFaunaSpawn
- targetDepth: 2
- effectHint: artifact-effect-hint-creation
- components:
- - type: SpawnArtifact
- maxSpawns: 5
- spawns:
- - id: MobAdultSlimesYellowAngry
- orGroup: fauna
- - id: MobAngryBee
- orGroup: fauna
- - id: MobBearSpace
- orGroup: fauna
- - id: MobBee
- orGroup: fauna
- maxAmount: 5
- - id: MobCat
- orGroup: fauna
- maxAmount: 2
- - id: MobCatKitten
- orGroup: fauna
- maxAmount: 2
- - id: MobCorgiPuppy
- orGroup: fauna
- maxAmount: 2
- - id: MobFox
- orGroup: fauna
- maxAmount: 1
- - id: MobGoat
- orGroup: fauna
- maxAmount: 3
- - id: MobKangaroo
- orGroup: fauna
- maxAmount: 1
- - id: MobKangarooSpace
- orGroup: fauna
- - id: MobMothroach
- orGroup: fauna
- maxAmount: 2
- - id: MobMonkeySyndicateAgent #so lucky
- orGroup: fauna
- maxAmount: 1
- prob: 0.03
- - id: MobMouse
- orGroup: fauna
- - id: MobParrot
- orGroup: fauna
- maxAmount: 1
- - id: MobPenguin
- orGroup: fauna
- maxAmount: 2
- - id: MobPig
- orGroup: fauna
- maxAmount: 1
- - id: MobPurpleSnake
- orGroup: fauna
- - id: MobSpiderSpace
- orGroup: fauna
- - id: MobTick
- orGroup: fauna
- - id: MobXenoRavager
- orGroup: fauna
-
-- type: artifactEffect
- id: EffectCashSpawn
- targetDepth: 2
- effectHint: artifact-effect-hint-creation
- components:
- - type: SpawnArtifact
- maxSpawns: 10
- spawns:
- - id: SpaceCash10
- maxAmount: 5
- prob: 0.75
- - id: SpaceCash100
- maxAmount: 2
- prob: 0.5
- - id: SpaceCash500
- prob: 0.25
- - id: SpaceCash1000
- prob: 0.1
-
-- type: artifactEffect
- id: EffectShatterWindows
- targetDepth: 2
- effectHint: artifact-effect-hint-environment
- components:
- - type: DamageNearbyArtifact
- damageChance: 0.75
- whitelist:
- tags:
- - Window
- damage:
- types:
- Structural: 200
-
-- type: artifactEffect
- id: EffectGas
- targetDepth: 2
- effectHint: artifact-effect-hint-environment
- components:
- - type: GasArtifact
- possibleGas:
- - CarbonDioxide
- - Plasma
- - Tritium
- - Ammonia
- - NitrousOxide
- - Frezon
-
-- type: artifactEffect
- id: EffectBlink
- targetDepth: 2
- effectHint: artifact-effect-hint-displacement
- components:
- - type: RandomTeleportArtifact
-
-- type: artifactEffect
- id: EffectFoamGood
- targetDepth: 2
- effectHint: artifact-effect-hint-biochemical
- components:
- - type: FoamArtifact
- reagents:
- - Dermaline
- - Arithrazine
- - Bicaridine
- - Inaprovaline
- - Kelotane
- - Dexalin
- - Omnizine
-
-- type: artifactEffect
- id: EffectChemicalPuddleRare
- targetDepth: 2
- effectHint: artifact-effect-hint-biochemical
- components:
- - type: ChemicalPuddleArtifact
- chemicalSolution:
- maxVol: 500
- canReact: false
- possibleChemicals:
- - Dermaline
- - Arithrazine
- - Bicaridine
- - Inaprovaline
- - Kelotane
- - Dexalin
- - Omnizine
- - Napalm
- - Toxin
- - Epinephrine
- - Cognizine
- - Ultravasculine
- - Desoxyephedrine
- - Pax
- - Siderlac
-
-- type: artifactEffect
- id: EffectEmp
- targetDepth: 2
- effectHint: artifact-effect-hint-electrical-interference
- components:
- - type: EmpArtifact
-
-- type: artifactEffect
- id: EffectPolyMonkey
- targetDepth: 2
- effectHint: artifact-effect-hint-polymorph
- components:
- - type: PolyOthersArtifact
-
-- type: artifactEffect
- id: EffectPolyLizard
- targetDepth: 2
- effectHint: artifact-effect-hint-polymorph
- components:
- - type: PolyOthersArtifact
- polymorphPrototypeName: ArtifactLizard
-
-- type: artifactEffect
- id: EffectPolyLuminous
- targetDepth: 3
- effectHint: artifact-effect-hint-polymorph
- components:
- - type: PolyOthersArtifact
- polymorphPrototypeName: ArtifactLuminous
-
-- type: artifactEffect
- id: EffectHealAll
- targetDepth: 3
- effectHint: artifact-effect-hint-environment
- components:
- - type: DamageNearbyArtifact
- damageChance: 1
- radius: 8
- whitelist:
- components:
- - MobState
- damage:
- groups:
- Brute: -300
- Burn: -300
-
-- type: artifactEffect
- id: EffectRadiateStrong
- targetDepth: 3
- effectHint: artifact-effect-hint-release
- components:
- - type: RadiationSource
- intensity: 2
- slope: 0.3
-
-- type: artifactEffect
- id: EffectMaterialSpawn
- targetDepth: 3
- effectHint: artifact-effect-hint-creation
- components:
- - type: SpawnArtifact
- maxSpawns: 5
- spawns:
- - id: SheetSteel
- orGroup: materials
- - id: SheetGlass
- orGroup: materials
- - id: SheetPlastic
- orGroup: materials
-
-- type: artifactEffect
- id: EffectShuffle
- targetDepth: 3
- effectHint: artifact-effect-hint-displacement
- components:
- - type: ShuffleArtifact
- - type: TelepathicArtifact
- range: 7.5
- messages:
- - shuffle-artifact-popup
-
-- type: artifactEffect
- id: EffectFoamDangerous
- targetDepth: 3
- effectHint: artifact-effect-hint-biochemical
- components:
- - type: FoamArtifact
- minFoamAmount: 20
- maxFoamAmount: 30
- reagents:
- - Tritium
- - Plasma
- - SulfuricAcid
- - SpaceDrugs
- - Nocturine
- - MuteToxin
- - Napalm
- - CarpoToxin
- - ChloralHydrate
- - Mold
- - Amatoxin
-
-- type: artifactEffect
- id: EffectIgnite
- targetDepth: 3
- effectHint: artifact-effect-hint-release
- components:
- - type: IgniteArtifact
- range: 7
- minFireStack: 3
- maxFireStack: 6
-
-- type: artifactEffect
- id: EffectMitosis
- targetDepth: 3
- effectHint: artifact-effect-hint-creation
- components:
- - type: SpawnArtifact
- maxSpawns: 1
- spawns:
- - id: RandomArtifactSpawner
-
-- type: artifactEffect
- id: EffectAnomaly
- targetDepth: 3
- effectHint: artifact-effect-hint-creation
- components:
- - type: SpawnArtifact
- maxSpawns: 1
- spawns:
- - id: RandomAnomalySpawner
-
-- type: artifactEffect
- id: EffectBoom
- targetDepth: 3
- effectHint: artifact-effect-hint-environment
- components:
- - type: TriggerArtifact
- - type: ExplodeOnTrigger
- - type: Explosive
- deleteAfterExplosion: false
- explosionType: Default
- totalIntensity: 500
- intensitySlope: 2.5
- maxIntensity: 50
-
-- type: artifactEffect
- id: EffectPortal
- targetDepth: 3
- effectHint: artifact-effect-hint-displacement
- components:
- - type: PortalArtifact
-
-- type: artifactEffect
- id: EffectSingulo
- targetDepth: 10
- effectHint: artifact-effect-hint-destruction
- components:
- - type: SpawnArtifact
- maxSpawns: 1
- spawns:
- - id: Singularity
-
-- type: artifactEffect
- id: EffectTesla
- targetDepth: 10
- effectHint: artifact-effect-hint-destruction
- components:
- - type: SpawnArtifact
- maxSpawns: 1
- spawns:
- - id: TeslaEnergyBall
+++ /dev/null
-# Utility effects permanently modify the entity in some way when triggered, and they generally make it 'useful' for some purpose,
-# like turning the artifact into a tool, or gun, or whatever.
-- type: artifactEffect
- id: EffectIntercom
- targetDepth: 2
- effectHint: artifact-effect-hint-communication
- permanentComponents:
- - type: RadioMicrophone
- powerRequired: false
- toggleOnInteract: false
- listenRange: 3
- - type: Speech
- - type: RadioSpeaker
- toggleOnInteract: false
- - type: ActivatableUI
- key: enum.IntercomUiKey.Key
- - type: Intercom
- requiresPower: false
- supportedChannels:
- - Common
- - CentCom
- - Command
- - Engineering
- - Medical
- - Science
- - Security
- - Service
- - Supply
-
-- type: artifactEffect
- id: EffectRandomInstrument
- targetDepth: 2
- effectHint: artifact-effect-hint-mental
- permanentComponents:
- - type: Instrument
- - type: ActivatableUI
- singleUser: true
- verbText: verb-instrument-openui
- key: enum.InstrumentUiKey.Key
- - type: RandomInstrumentArtifact
-
-- type: artifactEffect
- id: EffectStorage
- targetDepth: 2
- effectHint: artifact-effect-hint-storage
- whitelist:
- components:
- - Item # it doesnt necessarily have to be restricted from structures, but i think it'll be better that way
- permanentComponents:
- - type: Item
- size: Huge
- - type: Storage
- maxItemSize: Huge
- grid:
- - 0,0,10,5
-
-- type: artifactEffect
- id: EffectPhasing
- targetDepth: 2
- effectHint: artifact-effect-hint-phasing
- permanentComponents:
- - type: PhasingArtifact
-
-- type: artifactEffect
- id: EffectWandering
- targetDepth: 2
- effectHint: artifact-effect-hint-displacement
- blacklist:
- components:
- - Item # item artifacts can't be anchored, so wanderers can't really be scanned properly
- permanentComponents:
- - type: RandomWalk
- minSpeed: 12
- maxSpeed: 20
- minStepCooldown: 1
- maxStepCooldown: 3
-
-- type: artifactEffect
- id: EffectSolutionStorage
- targetDepth: 2
- effectHint: artifact-effect-hint-storage
- whitelist:
- components:
- - Item
- permanentComponents:
- - type: SolutionContainerManager
- solutions:
- beaker:
- maxVol: 150
- - type: FitsInDispenser
- solution: beaker
- - type: RefillableSolution
- solution: beaker
- - type: DrainableSolution
- solution: beaker
- - type: ExaminableSolution
- solution: beaker
- - type: DrawableSolution
- solution: beaker
- - type: InjectableSolution
- solution: beaker
- - type: SolutionTransfer
- canChangeTransferAmount: true
- - type: Drink
- solution: beaker
-
-- type: artifactEffect
- id: EffectSpeedUp
- targetDepth: 2
- effectHint: artifact-effect-hint-displacement
- whitelist:
- components:
- - Item
- permanentComponents:
- - type: HeldSpeedModifier
- walkModifier: 1.2
- sprintModifier: 1.3
-
-- type: artifactEffect
- id: EffectDrill
- targetDepth: 3
- effectHint: artifact-effect-hint-drill
- whitelist:
- components:
- - Item
- permanentComponents:
- - type: UseDelay
- - type: MeleeWeapon
- damage:
- types:
- Piercing: 18
- Blunt: 4
- soundHit:
- path: /Audio/Weapons/bladeslice.ogg
- - type: Sharp
-
-- type: artifactEffect
- id: EffectPowerGen20K
- targetDepth: 3
- effectHint: artifact-effect-hint-release
- blacklist:
- components:
- - Item
- permanentComponents:
- - type: PowerSupplier
- supplyRate: 20000
- - type: NodeContainer
- examinable: true
- nodes:
- output_hv:
- !type:CableDeviceNode
- nodeGroupID: HVPower
-
-- type: artifactEffect
- id: EffectBigIron
- targetDepth: 3
- effectHint: artifact-effect-hint-gun
- whitelist:
- components:
- - Item
- permanentComponents:
- - type: RevolverAmmoProvider
- whitelist:
- tags:
- - CartridgeMagnum
- - SpeedLoaderMagnum
- proto: CartridgeMagnum
- capacity: 7
- chambers: [ True, True, True, True, True, True, True ]
- ammoSlots: [ null, null, null, null, null, null, null ]
- soundEject:
- path: /Audio/Weapons/Guns/MagOut/revolver_magout.ogg
- soundInsert:
- path: /Audio/Weapons/Guns/MagIn/revolver_magin.ogg
- - type: Gun
- selectedMode: SemiAuto
- fireRate: 2
- availableModes:
- - SemiAuto
- - FullAuto # no alien revolver in buildings
- soundGunshot:
- path: /Audio/Weapons/Guns/Gunshots/revolver.ogg
-
-- type: artifactEffect
- id: EffectSentience
- targetDepth: 3
- effectHint: artifact-effect-hint-sentience
- permanentComponents:
- - type: GhostRole
- allowMovement: true
- allowSpeech: true
- makeSentient: true
- name: ghost-role-information-artifact-name
- description: ghost-role-information-artifact-description
- rules: ghost-role-information-freeagent-rules
- mindRoles:
- - MindRoleGhostRoleFreeAgent
- raffle:
- settings: default
- - type: GhostTakeoverAvailable
- - type: MovementSpeedModifier
- baseWalkSpeed: 0.25
- baseSprintSpeed: 0.5
-
-- type: artifactEffect
- id: EffectMultitool
- targetDepth: 3
- effectHint: artifact-effect-hint-multitool
- whitelist:
- components:
- - Item
- permanentComponents:
- - type: UserInterface
- interfaces:
- enum.SignalLinkerUiKey.Key:
- type: SignalPortSelectorBoundUserInterface
- - type: ToolTileCompatible
- - type: Tool
- qualities:
- - Screwing
- speedModifier: 2 # Very powerful multitool to balance out the desire to sell or scrap for points
- useSound: /Audio/Items/drill_use.ogg
- - type: Tag
- tags:
- - Multitool
- - type: MultipleTool
- statusShowBehavior: true
- entries:
- - behavior: Screwing
- useSound:
- path: /Audio/Items/drill_use.ogg
- changeSound:
- path: /Audio/Items/change_drill.ogg
- - behavior: Prying
- useSound:
- path: /Audio/Items/jaws_pry.ogg
- changeSound:
- path: /Audio/Items/change_drill.ogg
- - behavior: Anchoring
- useSound:
- path: /Audio/Items/ratchet.ogg
- changeSound:
- path: /Audio/Items/change_drill.ogg
- - behavior: Cutting
- useSound:
- path: /Audio/Items/jaws_cut.ogg
- changeSound:
- path: /Audio/Items/change_drill.ogg
- - behavior: Pulsing
- changeSound:
- path: /Audio/Items/change_drill.ogg
+++ /dev/null
-- type: artifactTrigger
- id: TriggerInteraction
- targetDepth: 0
- triggerHint: artifact-trigger-hint-physical
- components:
- - type: ArtifactInteractionTrigger
-
-- type: artifactTrigger
- id: TriggerTimer
- targetDepth: 0
- components:
- - type: ArtifactTimerTrigger
-
-- type: artifactTrigger
- id: TriggerExamine
- targetDepth: 0
- triggerHint: artifact-trigger-hint-examine
- components:
- - type: ArtifactExamineTrigger
-
-- type: artifactTrigger
- id: TriggerAnchor
- targetDepth: 0
- triggerHint: artifact-trigger-hint-tool
- blacklist:
- components:
- - Item
- components:
- - type: ArtifactAnchorTrigger
-
-- type: artifactTrigger
- id: TriggerElectricity
- targetDepth: 0
- triggerHint: artifact-trigger-hint-electricity
- blacklist:
- components:
- - Item
- components:
- - type: ArtifactElectricityTrigger
- - type: PowerConsumer
- voltage: Medium
- drawRate: 500
- - type: Electrified
- requirePower: true
- noWindowInTile: true
- highVoltageNode: high
- mediumVoltageNode: medium
- lowVoltageNode: low
- - type: NodeContainer
- nodes:
- medium:
- !type:CableDeviceNode
- nodeGroupID: MVPower
- # sadly, HVPower and Apc cables doesn't work right now
-
-- type: artifactTrigger
- id: TriggerMusic
- targetDepth: 1
- triggerHint: artifact-trigger-hint-music
- components:
- - type: ArtifactMusicTrigger
-
-- type: artifactTrigger
- id: TriggerBruteDamage
- targetDepth: 1
- triggerHint: artifact-trigger-hint-physical
- components:
- - type: ArtifactDamageTrigger
- damageTypes:
- - Blunt
- - Slash
- - Piercing
- damageThreshold: 50
-
-- type: artifactTrigger
- id: TriggerItemLanded
- targetDepth: 1
- triggerHint: artifact-trigger-hint-land
- whitelist:
- components:
- - Item
- components:
- - type: ArtifactLandTrigger
-
-- type: artifactTrigger
- id: TriggerHeat
- targetDepth: 1
- triggerHint: artifact-trigger-hint-heat
- components:
- - type: ArtifactHeatTrigger
-
-- type: artifactTrigger
- id: TriggerWater
- targetDepth: 1
- triggerHint: artifact-trigger-hint-water
- components:
- - type: Reactive
- groups:
- Acidic: [ Touch ]
- reactions:
- - reagents: [ Water ]
- methods: [ Touch ]
- effects:
- - !type:ActivateArtifact
-
-- type: artifactTrigger
- id: TriggerBlood
- targetDepth: 1
- triggerHint: artifact-trigger-hint-blood
- components:
- - type: Reactive
- groups:
- Acidic: [ Touch ]
- reactions:
- - reagents: [ Blood, CopperBlood, InsectBlood, Slime, AmmoniaBlood, ZombieBlood ]
- methods: [ Touch ]
- effects:
- - !type:ActivateArtifact
-
-- type: artifactTrigger
- id: TriggerMedical
- targetDepth: 2
- triggerHint: artifact-trigger-hint-medical
- components:
- - type: Reactive
- groups:
- Acidic: [ Touch ]
- reactions:
- - reagents: [ Dylovene, Diphenhydramine, Arithrazine, Bicaridine, Dermaline, Dexalin, DexalinPlus, Tricordrazine, Leporazine, Bruizine, Lacerinol, Puncturase, Pyrazine, Insuzine, Kelotane, Hyronalin, Inaprovaline, Epinephrine ]
- methods: [ Touch ]
- effects:
- - !type:ActivateArtifact
-
-- type: artifactTrigger
- id: TriggerGas
- targetDepth: 2
- triggerHint: artifact-trigger-hint-regular-gases
- components:
- - type: ArtifactGasTrigger
- possibleGas:
- - Oxygen
- - Nitrogen
- - CarbonDioxide
-
-- type: artifactTrigger
- id: TriggerDeath
- targetDepth: 2
- triggerHint: artifact-trigger-hint-death
- components:
- - type: ArtifactDeathTrigger
-
-- type: artifactTrigger
- id: TriggerMagnet
- targetDepth: 2
- triggerHint: artifact-trigger-hint-magnet
- components:
- - type: ArtifactMagnetTrigger
-
-- type: artifactTrigger
- id: TriggerLowPressure
- targetDepth: 2
- triggerHint: artifact-trigger-hint-pressure
- components:
- - type: ArtifactPressureTrigger
- minPressureThreshold: 50
-
-- type: artifactTrigger
- id: TriggerHighDamage
- targetDepth: 3
- triggerHint: artifact-trigger-hint-physical
- components:
- - type: ArtifactDamageTrigger
- damageThreshold: 500 #make it go boom or w/e
-
-- type: artifactTrigger
- id: TriggerRadiation
- targetDepth: 3
- triggerHint: artifact-trigger-hint-radiation
- components:
- - type: ArtifactMicrowaveTrigger
- - type: ArtifactDamageTrigger
- damageTypes:
- - Radiation
- damageThreshold: 50
- - type: RadiationReceiver
-
-- type: artifactTrigger
- id: TriggerHighPressure
- targetDepth: 3
- triggerHint: artifact-trigger-hint-pressure
- components:
- - type: ArtifactPressureTrigger
- maxPressureThreshold: 385
-
-- type: artifactTrigger
- id: TriggerPlasma
- targetDepth: 3
- triggerHint: artifact-trigger-hint-plasma
- components:
- - type: ArtifactGasTrigger
- possibleGas:
- - Plasma
-
-#don't add in new targetdepth values until you have a few
-#or else it will skew heavily towards a few options.
--- /dev/null
+- type: entityTable
+ id: XenoArtifactEffectsDefaultTable
+ table: !type:GroupSelector
+ children:
+ # hijacks use key, prevents from using artifact, sadly
+ #- id: XenoArtifactEffectUniversalIntercom
+ # weight: 10.0
+ - id: XenoArtifactSolutionStorage
+ weight: 10.0
+ - id: XenoArtifactPhasing
+ weight: 2.0
+ - id: XenoArtifactWandering
+ weight: 4.0
+ - id: XenoArtifactSpeedUp
+ weight: 4.0
+ - id: XenoArtifactGhost
+ weight: 2.0
+ - id: XenoArtifactEffectBadFeeling
+ weight: 10.0
+ - id: XenoArtifactEffectGoodFeeling
+ weight: 10.0
+ - id: XenoArtifactEffectJunkSpawn
+ weight: 10.0
+ - id: XenoArtifactEffectLightFlicker
+ weight: 10.0
+ - id: XenoArtifactPotassiumWave
+ weight: 7.0
+ - id: XenoArtifactFloraSpawn
+ weight: 10.0
+ - id: XenoArtifactChemicalPuddle
+ weight: 10.0
+ - id: XenoArtifactThrowThingsAround
+ weight: 10.0
+ - id: XenoArtifactColdWave
+ weight: 10.0
+ - id: XenoArtifactHeatWave
+ weight: 4.0
+ - id: XenoArtifactFoamMild
+ weight: 8.0
+ - id: XenoArtifactRandomInstrumentSpawn
+ weight: 10.0
+ - id: XenoArtifactMonkeySpawn
+ weight: 10.0
+ - id: XenoArtifactRadioactive
+ weight: 8.0
+ - id: XenoArtifactChargeBattery
+ weight: 10.0
+ - id: XenoArtifactKnock
+ weight: 4.0
+ - id: XenoArtifactMagnet
+ weight: 2.0
+ - id: XenoArtifactMagnetNegative
+ weight: 2.0
+ - id: XenoArtifactStealth
+ weight: 1.0
+ - id: XenoArtifactRareMaterialSpawnSilver
+ weight: 1.8 # amount is laughable
+ - id: XenoArtifactRareMaterialSpawnPlasma
+ weight: 2.0 # amount is laughable
+ - id: XenoArtifactRareMaterialSpawnGold
+ weight: 1.8 # amount is laughable
+ - id: XenoArtifactRareMaterialSpawnUranium
+ weight: 1.0 # amount is laughable
+ - id: XenoArtifactAngryCarpSpawn
+ weight: 4.0
+ - id: XenoArtifactFaunaSpawn
+ weight: 10.0
+ - id: XenoArtifactCashSpawn
+ weight: 10.0
+ - id: XenoArtifactShatterWindows
+ weight: 8.0
+ - id: XenoArtifactFoamGood
+ weight: 4.0
+ - id: XenoArtifactFoamDangerous
+ weight: 2.0
+ - id: XenoArtifactPuddleRare
+ weight: 2.0
+ - id: XenoArtifactAnomalySpawn
+ weight: 10.0
+ - id: XenoArtifactIgnite
+ weight: 2.0
+ - id: XenoArtifactTeleport
+ weight: 2.0
+ - id: XenoArtifactEmp
+ weight: 2.0
+ - id: XenoArtifactPolyMonkey
+ weight: 2.0
+ - id: XenoArtifactPolyLuminous
+ weight: 2.0
+ - id: XenoArtifactPolyLizard
+ weight: 2.0
+ - id: XenoArtifactRadioactiveStrong
+ weight: 3.0
+ - id: XenoArtifactMaterialSpawnGlass
+ weight: 3.3
+ - id: XenoArtifactMaterialSpawnSteel
+ weight: 3.3
+ - id: XenoArtifactMaterialSpawnPlastic
+ weight: 3.3
+ - id: XenoArtifactPortal
+ weight: 2.0
+ - id: XenoArtifactArtifactSpawn
+ weight: 0.5
+ - id: XenoArtifactShuffle
+ weight: 3.0
+ - id: XenoArtifactHealAll
+ weight: 1.0
+ #- id: XenoArtifactTesla
+ # weight: 10.0
+ #- id: XenoArtifactSingularity
+ # weight: 10.0
+ - id: XenoArtifactExplosionScary
+ weight: 1.0
+ - id: XenoArtifactBoom
+ weight: 5.0
+ - id: XenoArtifactEffectCreationGasPlasma
+ weight: 2.0
+ - id: XenoArtifactEffectCreationGasTritium
+ weight: 2.0
+ - id: XenoArtifactEffectCreationGasAmmonia
+ weight: 3.0
+ - id: XenoArtifactEffectCreationGasFrezon
+ weight: 1.0
+ - id: XenoArtifactEffectCreationGasNitrousOxide
+ weight: 4.0
+ - id: XenoArtifactEffectCreationGasCarbonDioxide
+ weight: 4.0
+
+- type: entityTable
+ id: XenoArtifactEffectsHandheldOnlyTable
+ table: !type:GroupSelector
+ children:
+ #- id: XenoArtifactBecomeRandomInstrument
+ # weight 10.0 # removed until we have value-based system
+ #- id: XenoArtifactGun
+ # weight 4.0 #it conflicts with default interaction - it should activate artifact nodes
+ - id: XenoArtifactOmnitool
+ weight: 10.0
+ - id: XenoArtifactDrill
+ weight: 10.0
+
+- type: entity
+ id: BaseXenoArtifactEffect
+ name: effect
+ description: Unknown
+ categories: [ HideSpawnMenu ]
+ abstract: true
+ components:
+ - type: XenoArtifactNode
+ - type: NameIdentifier
+ group: XenoArtifactNode
+
+- type: entity
+ id: BaseOneTimeXenoArtifactEffect
+ parent: BaseXenoArtifactEffect
+ name: one-time-effect
+ description: Unknown
+ categories: [ HideSpawnMenu ]
+ abstract: true
+ components:
+ - type: XenoArtifactNode
+ maxDurability: 1
+ maxDurabilityCanDecreaseBy:
+ min: 0
+ max: 0
+ - type: NameIdentifier
+ group: XenoArtifactNode
+
+- type: entity
+ id: XenoArtifactEffectUniversalIntercom
+ parent: BaseOneTimeXenoArtifactEffect
+ description: Obtains ability of long-distance communication device
+ components:
+ - type: XAEApplyComponents
+ components:
+ - type: RadioMicrophone
+ powerRequired: false
+ toggleOnInteract: false
+ listenRange: 3
+ - type: Speech
+ - type: RadioSpeaker
+ toggleOnInteract: false
+ - type: ActivatableUI
+ key: enum.IntercomUiKey.Key
+ - type: Intercom
+ requiresPower: false
+ supportedChannels:
+ - Common
+ - CentCom
+ - Command
+ - Engineering
+ - Medical
+ - Science
+ - Security
+ - Service
+ - Supply
+
+- type: entity
+ id: XenoArtifactBecomeRandomInstrument
+ parent: BaseOneTimeXenoArtifactEffect
+ description: Obtains ability of musical instrument
+ components:
+ - type: XAEApplyComponents
+ components:
+ - type: Instrument
+ - type: ActivatableUI
+ singleUser: true
+ verbText: verb-instrument-openui
+ key: enum.InstrumentUiKey.Key
+
+- type: entity
+ id: XenoArtifactStorage
+ parent: BaseOneTimeXenoArtifactEffect
+ description: Obtains ability of hidden storage
+ components:
+ - type: XAEApplyComponents
+ components:
+ - type: Item
+ size: Huge
+ - type: Storage
+ maxItemSize: Huge
+ grid:
+ - 0,0,10,5
+
+- type: entity
+ id: XenoArtifactPhasing
+ parent: BaseOneTimeXenoArtifactEffect
+ description: Becomes phased
+ components:
+ - type: XAERemoveCollision
+
+- type: entity
+ id: XenoArtifactWandering
+ parent: BaseOneTimeXenoArtifactEffect
+ description: Starts to move sporadically
+ components:
+ - type: XAEApplyComponents
+ components:
+ - type: RandomWalk
+ minSpeed: 12
+ maxSpeed: 20
+ minStepCooldown: 1
+ maxStepCooldown: 3
+
+- type: entity
+ id: XenoArtifactSolutionStorage
+ parent: BaseOneTimeXenoArtifactEffect
+ description: Obtains ability of container for chemical solutions
+ components:
+ - type: XAEApplyComponents
+ components:
+ - type: SolutionContainerManager
+ solutions:
+ beaker:
+ maxVol: 150
+ - type: FitsInDispenser
+ solution: beaker
+ - type: RefillableSolution
+ solution: beaker
+ - type: DrainableSolution
+ solution: beaker
+ - type: ExaminableSolution
+ solution: beaker
+ - type: DrawableSolution
+ solution: beaker
+ - type: InjectableSolution
+ solution: beaker
+ - type: SolutionTransfer
+ canChangeTransferAmount: true
+ - type: Drink
+ solution: beaker
+
+- type: entity
+ id: XenoArtifactSpeedUp
+ parent: BaseOneTimeXenoArtifactEffect
+ description: Improves holder movement speed
+ components:
+ - type: XAEApplyComponents
+ components:
+ - type: HeldSpeedModifier
+ walkModifier: 1.2
+ sprintModifier: 1.3
+
+- type: entity
+ id: XenoArtifactDrill
+ parent: BaseOneTimeXenoArtifactEffect
+ description: Obtains ability of drill
+ components:
+ - type: XAEApplyComponents
+ components:
+ - type: MeleeWeapon
+ damage:
+ types:
+ Piercing: 18
+ Blunt: 4
+ soundHit:
+ path: /Audio/Weapons/bladeslice.ogg
+ - type: Sharp
+
+- type: entity
+ id: XenoArtifactGenerateEnergy
+ parent: BaseOneTimeXenoArtifactEffect # todo - increment power, but only once per node
+ description: Produces power
+ components:
+ - type: XAEApplyComponents
+ components:
+ - type: PowerSupplier
+ supplyRate: 20000
+ - type: NodeContainer
+ examinable: true
+ nodes:
+ output_hv:
+ !type:CableDeviceNode
+ nodeGroupID: HVPower
+
+- type: entity
+ id: XenoArtifactGun
+ parent: BaseOneTimeXenoArtifactEffect
+ description: Obtains ability of firearm
+ components:
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: RevolverAmmoProvider
+ whitelist:
+ tags:
+ - CartridgeMagnum
+ - SpeedLoaderMagnum
+ proto: CartridgeMagnum
+ capacity: 7
+ chambers: [ True, True, True, True, True, True, True ]
+ ammoSlots: [ null, null, null, null, null, null, null ]
+ soundEject:
+ path: /Audio/Weapons/Guns/MagOut/revolver_magout.ogg
+ soundInsert:
+ path: /Audio/Weapons/Guns/MagIn/revolver_magin.ogg
+ - type: Gun
+ selectedMode: SemiAuto
+ fireRate: 2
+ availableModes:
+ - SemiAuto
+ - FullAuto # no alien revolver in buildings
+ soundGunshot:
+ path: /Audio/Weapons/Guns/Gunshots/revolver.ogg
+
+- type: entity
+ id: XenoArtifactGhost
+ parent: BaseOneTimeXenoArtifactEffect
+ description: Becomes sentient
+ components:
+ - type: XAEApplyComponents
+ components:
+ - type: GhostRole
+ allowMovement: true
+ allowSpeech: true
+ makeSentient: true
+ name: ghost-role-information-artifact-name
+ description: ghost-role-information-artifact-description
+ rules: ghost-role-information-freeagent-rules
+ raffle:
+ settings: default
+ mindRoles:
+ - MindRoleGhostRoleFreeAgent
+ - type: GhostTakeoverAvailable
+ - type: MovementSpeedModifier
+ baseWalkSpeed: 0.25
+ baseSprintSpeed: 0.5
+
+- type: entity
+ id: XenoArtifactOmnitool
+ parent: BaseOneTimeXenoArtifactEffect
+ description: Obtains ability of omnitool
+ components:
+ - type: XAEApplyComponents
+ components:
+ - type: UserInterface
+ interfaces:
+ enum.SignalLinkerUiKey.Key:
+ type: SignalPortSelectorBoundUserInterface
+ - type: ToolTileCompatible
+ - type: Tool
+ qualities:
+ - Screwing
+ speedModifier: 2 # Very powerful multitool to balance out the desire to sell or scrap for points
+ useSound: /Audio/Items/drill_use.ogg
+ - type: Tag
+ tags:
+ - Multitool
+ - type: MultipleTool
+ statusShowBehavior: true
+ entries:
+ - behavior: Screwing
+ useSound:
+ path: /Audio/Items/drill_use.ogg
+ changeSound:
+ path: /Audio/Items/change_drill.ogg
+ - behavior: Prying
+ useSound:
+ path: /Audio/Items/jaws_pry.ogg
+ changeSound:
+ path: /Audio/Items/change_drill.ogg
+ - behavior: Anchoring
+ useSound:
+ path: /Audio/Items/ratchet.ogg
+ changeSound:
+ path: /Audio/Items/change_drill.ogg
+ - behavior: Cutting
+ useSound:
+ path: /Audio/Items/jaws_cut.ogg
+ changeSound:
+ path: /Audio/Items/change_drill.ogg
+ - behavior: Pulsing
+ changeSound:
+ path: /Audio/Items/change_drill.ogg
+
+- type: entity
+ id: XenoArtifactEffectBadFeeling
+ parent: BaseXenoArtifactEffect
+ description: Broadcasts sublime message
+ components:
+ - type: XAETelepathic
+ messages:
+ - badfeeling-artifact-1
+ - badfeeling-artifact-2
+ - badfeeling-artifact-3
+ - badfeeling-artifact-4
+ - badfeeling-artifact-5
+ - badfeeling-artifact-6
+ - badfeeling-artifact-7
+ - badfeeling-artifact-8
+ - badfeeling-artifact-9
+ - badfeeling-artifact-10
+ - badfeeling-artifact-11
+ - badfeeling-artifact-12
+ - badfeeling-artifact-13
+ - badfeeling-artifact-14
+ - badfeeling-artifact-15
+ drastic:
+ - badfeeling-artifact-drastic-1
+ - badfeeling-artifact-drastic-2
+ - badfeeling-artifact-drastic-3
+ - badfeeling-artifact-drastic-4
+ - badfeeling-artifact-drastic-5
+ - badfeeling-artifact-drastic-6
+
+- type: entity
+ id: XenoArtifactEffectGoodFeeling
+ parent: BaseXenoArtifactEffect
+ description: Broadcasts sublime message
+ components:
+ - type: XAETelepathic
+ messages:
+ - goodfeeling-artifact-1
+ - goodfeeling-artifact-2
+ - goodfeeling-artifact-3
+ - goodfeeling-artifact-4
+ - goodfeeling-artifact-5
+ - goodfeeling-artifact-6
+ - goodfeeling-artifact-7
+ - goodfeeling-artifact-8
+ - goodfeeling-artifact-9
+ - goodfeeling-artifact-10
+ - goodfeeling-artifact-11
+ - goodfeeling-artifact-12
+ - goodfeeling-artifact-13
+ - goodfeeling-artifact-14
+ drastic:
+ - goodfeeling-artifact-drastic-1
+ - goodfeeling-artifact-drastic-2
+ - goodfeeling-artifact-drastic-3
+ - goodfeeling-artifact-drastic-4
+ - goodfeeling-artifact-drastic-5
+ - goodfeeling-artifact-drastic-6
+
+- type: entity
+ id: XenoArtifactEffectJunkSpawn
+ parent: BaseXenoArtifactEffect
+ description: Create recyclable junk
+ components:
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: EntityTableSpawner
+ deleteSpawnerAfterSpawn: false
+ table: !type:GroupSelector
+ rolls: !type:RangeNumberSelector
+ range: 1, 4
+ children:
+ - !type:NestedSelector
+ tableId: GenericTrashItems
+ weight: 35
+ - !type:AllSelector
+ weight: 1
+ children:
+ - id: ToySpawner
+
+- type: entity
+ id: XenoArtifactEffectLightFlicker
+ parent: BaseXenoArtifactEffect
+ description: Minor electromagnetic interference
+ components:
+ - type: XAELightFlicker
+
+- type: entity
+ id: XenoArtifactPotassiumWave
+ parent: BaseXenoArtifactEffect
+ description: Produces potassium
+ components:
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: EntityTableSpawner
+ deleteSpawnerAfterSpawn: false
+ table: !type:AllSelector
+ children:
+ - id: FoodBanana
+ rolls: !type:ConstantNumberSelector
+ value: 6
+ prob: 0.5
+ - type: XAECreatePuddle
+ chemAmount:
+ min: 1
+ max: 1
+ chemicalSolution:
+ maxVol: 100
+ canReact: false
+ possibleChemicals:
+ - Potassium
+
+- type: entity
+ id: XenoArtifactFloraSpawn
+ parent: BaseXenoArtifactEffect
+ description: Produces flora
+ components:
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: EntityTableSpawner
+ deleteSpawnerAfterSpawn: false
+ table: !type:AllSelector
+ children:
+ - id: RandomFloraTree
+
+- type: entity
+ id: XenoArtifactChemicalPuddle
+ parent: BaseXenoArtifactEffect
+ description: Produces puddle of chemical mixture # todo: make description say what exact chemical is produced, maybe add mixes into possible chemicals
+ components:
+ - type: XAECreatePuddle
+ chemAmount:
+ min: 1
+ max: 3
+ replaceDescription: true
+ chemicalSolution:
+ maxVol: 500
+ canReact: false
+ possibleChemicals:
+ - Aluminium
+ - Carbon
+ - Chlorine
+ - Copper
+ - Ethanol
+ - Fluorine
+ - Sugar
+ - Hydrogen
+ - Iodine
+ - Iron
+ - Lithium
+ - Mercury
+ - Nitrogen
+ - Oxygen
+ - Phosphorus
+ - Potassium
+ - Radium
+ - Silicon
+ - Sodium
+ - Water
+ - Sulfur
+
+- type: entity
+ id: XenoArtifactThrowThingsAround
+ parent: BaseXenoArtifactEffect
+ description: Minor implosion
+ components:
+ - type: XAEThrowThingsAround
+
+- type: entity
+ id: XenoArtifactColdWave
+ parent: BaseXenoArtifactEffect
+ description: Cools down surrounding gas
+ components:
+ - type: XAETemperature
+ targetTemp: 50
+
+- type: entity
+ id: XenoArtifactHeatWave
+ parent: BaseXenoArtifactEffect
+ description: Heats up surrounding gas greatly
+ components:
+ - type: XAETemperature
+ targetTemp: 500
+
+- type: entity
+ id: XenoArtifactFoamMild
+ parent: BaseXenoArtifactEffect
+ description: Produces chemical foam # todo: separate in 1 for each chemical for description? actually sounds like a very good idea
+ components:
+ - type: XAEFoam
+ replaceDescription: true
+ reagents:
+ - Oxygen
+ - Plasma
+ - Blood
+ - SpaceCleaner
+ - Nutriment
+ - SpaceLube
+ - Ethanol
+ - Mercury
+ - VentCrud
+ - WeldingFuel
+ - JuiceThatMakesYouWeh
+
+- type: entity
+ id: XenoArtifactRandomInstrumentSpawn
+ parent: BaseXenoArtifactEffect
+ description: Creates musical instrument
+ components:
+ - type: XenoArtifactNode
+ maxDurability: 2
+ maxDurabilityCanDecreaseBy:
+ min: 0
+ max: 1
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: EntityTableSpawner
+ deleteSpawnerAfterSpawn: false
+ table: !type:AllSelector
+ children:
+ - id: RandomInstruments
+
+- type: entity
+ id: XenoArtifactMonkeySpawn
+ parent: BaseXenoArtifactEffect
+ description: Creates primate
+ components:
+ - type: XenoArtifactNode
+ maxDurability: 3
+ maxDurabilityCanDecreaseBy:
+ min: 0
+ max: 2
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: EntityTableSpawner
+ deleteSpawnerAfterSpawn: false
+ table: !type:GroupSelector
+ children:
+ - id: MobMonkey
+ weight: 95.0
+ - id: MobGorilla
+ weight: 5.0
+
+- type: entity
+ id: XenoArtifactRadioactive
+ parent: BaseOneTimeXenoArtifactEffect
+ description: Becomes mildly radioactive
+ components:
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: RadiationSource
+ intensity: 1
+ slope: 0.3
+
+- type: entity
+ id: XenoArtifactChargeBattery
+ parent: BaseXenoArtifactEffect
+ description: Charges up batteries
+ components:
+ - type: XAEChargeBattery
+ - type: XAETelepathic
+ messages:
+ - charge-artifact-popup
+
+- type: entity
+ id: XenoArtifactKnock
+ parent: BaseXenoArtifactEffect
+ description: Mild electromagnetic interference
+ components:
+ - type: XAEKnock
+ - type: XAELightFlicker
+
+- type: entity
+ id: XenoArtifactMagnet
+ parent: BaseOneTimeXenoArtifactEffect
+ description: Create small gravity well
+ components:
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: GravityWell
+ maxRange: 3
+ baseRadialAcceleration: 1
+ baseTangentialAcceleration: 3
+
+- type: entity
+ id: XenoArtifactMagnetNegative
+ parent: BaseOneTimeXenoArtifactEffect
+ description: Create small gravity well
+ components:
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: GravityWell
+ maxRange: 3
+ baseRadialAcceleration: -1
+ baseTangentialAcceleration: -3
+
+- type: entity
+ id: XenoArtifactStealth
+ parent: BaseOneTimeXenoArtifactEffect
+ description: Create light interference
+ components:
+ - type: XAEApplyComponents
+ components:
+ - type: Stealth
+ hadOutline: true
+ - type: StealthOnMove
+ passiveVisibilityRate: -0.10
+ movementVisibilityRate: 0.10
+
+- type: entity
+ id: XenoArtifactRareMaterialSpawn
+ parent: BaseXenoArtifactEffect # todo: splice into different well-named effects, amounts should reflect how rare material is
+ description: Create rare materials
+ components:
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: EntityTableSpawner
+ deleteSpawnerAfterSpawn: false
+ table: !type:AllSelector
+ children:
+ - id: SilverOre1
+ rolls: !type:ConstantNumberSelector
+ value: 6
+ prob: 0.3
+ - id: PlasmaOre1
+ rolls: !type:ConstantNumberSelector
+ value: 6
+ prob: 0.3
+ - id: GoldOre1
+ rolls: !type:ConstantNumberSelector
+ value: 6
+ prob: 0.3
+ - id: UraniumOre1
+ rolls: !type:ConstantNumberSelector
+ value: 6
+ prob: 0.3
+
+- type: entity
+ id: XenoArtifactRareMaterialSpawnSilver
+ parent: BaseXenoArtifactEffect
+ description: Create rare materials
+ components:
+ - type: XenoArtifactNode
+ maxDurability: 4
+ maxDurabilityCanDecreaseBy:
+ min: 0
+ max: 2
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: EntityTableSpawner
+ deleteSpawnerAfterSpawn: false
+ table: !type:AllSelector
+ children:
+ - id: SilverOre1
+ rolls: !type:ConstantNumberSelector
+ value: 6
+ prob: 0.3
+
+- type: entity
+ id: XenoArtifactRareMaterialSpawnPlasma
+ parent: BaseXenoArtifactEffect
+ description: Create plasma
+ components:
+ - type: XenoArtifactNode
+ maxDurability: 4
+ maxDurabilityCanDecreaseBy:
+ min: 0
+ max: 2
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: EntityTableSpawner
+ deleteSpawnerAfterSpawn: false
+ table: !type:AllSelector
+ children:
+ - id: PlasmaOre1
+ rolls: !type:ConstantNumberSelector
+ value: 6
+ prob: 0.3
+
+- type: entity
+ id: XenoArtifactRareMaterialSpawnGold
+ parent: BaseXenoArtifactEffect
+ description: Create gold
+ components:
+ - type: XenoArtifactNode
+ maxDurability: 3
+ maxDurabilityCanDecreaseBy:
+ min: 0
+ max: 1
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: EntityTableSpawner
+ deleteSpawnerAfterSpawn: false
+ table: !type:AllSelector
+ children:
+ - id: GoldOre1
+ rolls: !type:ConstantNumberSelector
+ value: 6
+ prob: 0.3
+
+- type: entity
+ id: XenoArtifactRareMaterialSpawnUranium
+ parent: BaseXenoArtifactEffect
+ description: Create uranium
+ components:
+ - type: XenoArtifactNode
+ maxDurability: 4
+ maxDurabilityCanDecreaseBy:
+ min: 0
+ max: 2
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: EntityTableSpawner
+ deleteSpawnerAfterSpawn: false
+ table: !type:AllSelector
+ children:
+ - id: UraniumOre1
+ rolls: !type:ConstantNumberSelector
+ value: 3
+ prob: 0.3
+
+- type: entity
+ id: XenoArtifactAngryCarpSpawn
+ parent: BaseXenoArtifactEffect
+ description: Create hostile fish
+ components:
+ - type: XenoArtifactNode
+ maxDurability: 3
+ maxDurabilityCanDecreaseBy:
+ min: 0
+ max: 2
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: EntityTableSpawner
+ deleteSpawnerAfterSpawn: false
+ table: !type:GroupSelector
+ children:
+ - id: MobCarpMagic
+ weight: 1.0
+ - id: MobCarpHolo
+ weight: 1.0
+
+- type: entity
+ id: XenoArtifactFaunaSpawn
+ parent: BaseXenoArtifactEffect
+ description: Create friendly fauna
+ components:
+ - type: XenoArtifactNode
+ maxDurability: 4
+ maxDurabilityCanDecreaseBy:
+ min: 0
+ max: 3
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: EntityTableSpawner
+ deleteSpawnerAfterSpawn: false
+ table: !type:GroupSelector
+ children:
+ - id: MobAdultSlimesYellowAngry
+ - id: MobAngryBee
+ - id: MobBearSpace
+ - id: MobXenoRavager
+ - id: MobTick
+ - id: MobSpiderSpace
+ - id: MobPurpleSnake
+ - id: MobMouse
+ - id: MobKangarooSpace
+ - id: MobPig
+ - id: MobParrot
+ - id: MobKangaroo
+ - id: MobFox
+ - id: MobPenguin
+ amount: !type:RangeNumberSelector
+ range: 1, 2
+ - id: MobMothroach
+ amount: !type:RangeNumberSelector
+ range: 1, 2
+ - id: MobCorgiPuppy
+ amount: !type:RangeNumberSelector
+ range: 1, 2
+ - id: MobCatKitten
+ amount: !type:RangeNumberSelector
+ range: 1, 2
+ - id: MobCat
+ amount: !type:RangeNumberSelector
+ range: 1, 2
+ - id: MobBee
+ amount: !type:RangeNumberSelector
+ range: 2, 5
+ - id: MobGoat
+ amount: !type:RangeNumberSelector
+ range: 1, 3
+ - id: MobMonkeySyndicateAgent #so lucky
+ prob: 0.03
+
+- type: entity
+ id: XenoArtifactCashSpawn
+ parent: BaseXenoArtifactEffect
+ description: Create money
+ components:
+ - type: XenoArtifactNode
+ maxDurability: 2
+ maxDurabilityCanDecreaseBy:
+ min: 0
+ max: 1
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: EntityTableSpawner
+ deleteSpawnerAfterSpawn: false
+ table: !type:GroupSelector
+ rolls: !type:RangeNumberSelector
+ range: 2, 4
+ children:
+ - id: SpaceCash10
+ weight: 0.75
+ - id: SpaceCash100
+ weight: 0.5
+ - id: SpaceCash500
+ weight: 0.25
+ - id: SpaceCash1000
+ weight: 0.1
+
+- type: entity
+ id: XenoArtifactShatterWindows
+ parent: BaseXenoArtifactEffect
+ description: Break windows
+ components:
+ - type: XenoArtifactNode
+ maxDurability: 3
+ maxDurabilityCanDecreaseBy:
+ min: 0
+ max: 2
+ - type: XAEDamageInArea
+ damageChance: 0.75
+ whitelist:
+ tags:
+ - Window
+ damage:
+ types:
+ Structural: 200
+
+- type: entity
+ id: XenoArtifactFoamGood
+ parent: BaseXenoArtifactEffect
+ description: Creates wave of helpful foam
+ components:
+ - type: XenoArtifactNode
+ maxDurability: 7
+ maxDurabilityCanDecreaseBy:
+ min: 0
+ max: 5
+ - type: XAEFoam
+ replaceDescription: true
+ reagents:
+ - Dermaline
+ - Arithrazine
+ - Bicaridine
+ - Inaprovaline
+ - Kelotane
+ - Dexalin
+ - Omnizine
+
+- type: entity
+ id: XenoArtifactFoamDangerous
+ parent: BaseXenoArtifactEffect
+ description: Creates wave of harmful foam
+ components:
+ - type: XAEFoam
+ minFoamAmount: 20
+ maxFoamAmount: 30
+ replaceDescription: true
+ reagents:
+ - Tritium
+ - Plasma
+ - SulfuricAcid
+ - SpaceDrugs
+ - Nocturine
+ - MuteToxin
+ - Napalm
+ - CarpoToxin
+ - ChloralHydrate
+ - Mold
+ - Amatoxin
+
+- type: entity
+ id: XenoArtifactPuddleRare
+ parent: BaseXenoArtifactEffect
+ description: Creates puddle of helpful chemicals
+ components:
+ - type: XAECreatePuddle
+ chemAmount:
+ min: 1
+ max: 3
+ replaceDescription: true
+ chemicalSolution:
+ maxVol: 500
+ canReact: false
+ possibleChemicals:
+ - Dermaline
+ - Arithrazine
+ - Bicaridine
+ - Inaprovaline
+ - Kelotane
+ - Dexalin
+ - Omnizine
+ - Napalm
+ - Toxin
+ - Epinephrine
+ - Cognizine
+ - Ultravasculine
+ - Desoxyephedrine
+ - Pax
+ - Siderlac
+
+- type: entity
+ id: XenoArtifactAnomalySpawn
+ parent: BaseXenoArtifactEffect
+ description: Creates anomaly
+ components:
+ - type: XenoArtifactNode
+ maxDurability: 2
+ maxDurabilityCanDecreaseBy:
+ min: 0
+ max: 1
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: EntityTableSpawner
+ deleteSpawnerAfterSpawn: false
+ table: !type:AllSelector
+ children:
+ - id: RandomAnomalySpawner
+
+- type: entity
+ id: XenoArtifactIgnite
+ parent: BaseXenoArtifactEffect
+ description: Pyrokinesis
+ components:
+ - type: XAEIgnite
+ range: 7
+ fireStack:
+ min: 3
+ max: 6
+
+- type: entity
+ id: XenoArtifactTeleport
+ parent: BaseXenoArtifactEffect
+ description: Teleportation
+ components:
+ - type: XAERandomTeleportInvoker
+
+- type: entity
+ id: XenoArtifactEmp
+ parent: BaseXenoArtifactEffect
+ description: Dangerous electromagnetic interference
+ components:
+ - type: XenoArtifactNode
+ maxDurability: 5
+ maxDurabilityCanDecreaseBy:
+ min: 0
+ max: 3
+ - type: XAEEmpInArea
+
+- type: entity
+ id: XenoArtifactPolyMonkey
+ parent: BaseXenoArtifactEffect
+ description: Temporarily reshape flesh to fur
+ components:
+ - type: XAEPolymorph
+
+- type: entity
+ id: XenoArtifactPolyLizard
+ parent: BaseXenoArtifactEffect
+ description: Temporarily reshape flesh to scale
+ components:
+ - type: XAEPolymorph
+ polymorphPrototypeName: ArtifactLizard
+
+- type: entity
+ id: XenoArtifactPolyLuminous
+ parent: BaseXenoArtifactEffect
+ description: Temporarily reshape flesh to light
+ components:
+ - type: XAEPolymorph
+ polymorphPrototypeName: ArtifactLuminous
+
+- type: entity
+ id: XenoArtifactRadioactiveStrong
+ parent: BaseOneTimeXenoArtifactEffect
+ description: Becomes highly radioactive
+ components:
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: RadiationSource
+ intensity: 2
+ slope: 0.3
+
+- type: entity
+ id: XenoArtifactMaterialSpawnGlass
+ parent: BaseXenoArtifactEffect
+ description: Create glass
+ components:
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: EntityTableSpawner
+ deleteSpawnerAfterSpawn: false
+ table: !type:GroupSelector
+ children:
+ - id: SheetGlass
+
+- type: entity
+ id: XenoArtifactMaterialSpawnSteel
+ parent: BaseXenoArtifactEffect
+ description: Create steel
+ components:
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: EntityTableSpawner
+ deleteSpawnerAfterSpawn: false
+ table: !type:GroupSelector
+ children:
+ - id: SheetSteel
+
+- type: entity
+ id: XenoArtifactMaterialSpawnPlastic
+ parent: BaseXenoArtifactEffect
+ description: Create plastic
+ components:
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: EntityTableSpawner
+ deleteSpawnerAfterSpawn: false
+ table: !type:GroupSelector
+ children:
+ - id: SheetPlastic
+
+- type: entity
+ id: XenoArtifactPortal
+ parent: BaseXenoArtifactEffect
+ description: Create short-living bluespace portal
+ components:
+ - type: XAEPortal
+
+- type: entity
+ id: XenoArtifactArtifactSpawn
+ parent: BaseXenoArtifactEffect
+ description: Create artifact
+ components:
+ - type: XenoArtifactNode
+ maxDurability: 2
+ maxDurabilityCanDecreaseBy:
+ min: 0
+ max: 1
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: EntityTableSpawner
+ deleteSpawnerAfterSpawn: false
+ table: !type:AllSelector
+ children:
+ - id: RandomArtifactSpawner
+
+- type: entity
+ id: XenoArtifactShuffle
+ parent: BaseXenoArtifactEffect
+ description: Switch places of sentient beings #not ALL beings, but oh well...
+ components:
+ - type: XAEShuffle
+ - type: XAETelepathic
+ range: 7.5
+ messages:
+ - shuffle-artifact-popup
+
+- type: entity
+ id: XenoArtifactHealAll
+ parent: BaseXenoArtifactEffect
+ description: Miraclous healing
+ components:
+ - type: XAEDamageInArea
+ damageChance: 1
+ radius: 8
+ whitelist:
+ components:
+ - MobState
+ damage:
+ groups:
+ Brute: -300
+ Burn: -300
+
+- type: entity
+ id: XenoArtifactTesla
+ parent: BaseOneTimeXenoArtifactEffect
+ description: Mass destruction
+ components:
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: EntityTableSpawner
+ deleteSpawnerAfterSpawn: false
+ table: !type:AllSelector
+ children:
+ - id: Singularity
+
+- type: entity
+ id: XenoArtifactSingularity
+ parent: BaseOneTimeXenoArtifactEffect
+ description: Imminent doom
+ components:
+ - type: XAEApplyComponents
+ applyIfAlreadyHave: true
+ refreshOnReactivate: true
+ components:
+ - type: EntityTableSpawner
+ deleteSpawnerAfterSpawn: false
+ table: !type:AllSelector
+ children:
+ - id: TeslaEnergyBall
+
+- type: entity
+ id: XenoArtifactExplosionScary
+ parent: BaseOneTimeXenoArtifactEffect
+ description: Small scale high-speed nuclear reaction
+ components:
+ - type: XAETriggerExplosives
+ - type: Explosive
+ deleteAfterExplosion: false
+ explosionType: Radioactive
+ totalIntensity: 300
+ intensitySlope: 2
+ maxIntensity: 1.5
+ canCreateVacuum: false
+ repeatable: true
+
+- type: entity
+ id: XenoArtifactBoom
+ parent: BaseOneTimeXenoArtifactEffect
+ description: Explosion
+ components:
+ - type: XAETriggerExplosives
+ - type: Explosive
+ deleteAfterExplosion: false
+ explosionType: Default
+ totalIntensity: 500
+ intensitySlope: 2.5
+ maxIntensity: 50
+ repeatable: true
+
+- type: entity
+ id: XenoArtifactEffectCreationGasPlasma
+ parent: BaseXenoArtifactEffect
+ description: Expels plasma
+ components:
+ - type: XAECreateGas
+ gases:
+ Plasma: 300
+
+- type: entity
+ id: XenoArtifactEffectCreationGasTritium
+ parent: BaseXenoArtifactEffect
+ description: Expels tritium
+ components:
+ - type: XAECreateGas
+ gases:
+ Tritium: 300
+
+- type: entity
+ id: XenoArtifactEffectCreationGasAmmonia
+ parent: BaseXenoArtifactEffect
+ description: Expels ammonia
+ components:
+ - type: XAECreateGas
+ gases:
+ Ammonia: 300
+
+- type: entity
+ id: XenoArtifactEffectCreationGasFrezon
+ parent: BaseXenoArtifactEffect
+ description: Expels frezon
+ components:
+ - type: XAECreateGas
+ gases:
+ Frezon: 300
+
+- type: entity
+ id: XenoArtifactEffectCreationGasNitrousOxide
+ parent: BaseXenoArtifactEffect
+ description: Expels nitrous oxide
+ components:
+ - type: XAECreateGas
+ gases:
+ NitrousOxide: 300
+
+- type: entity
+ id: XenoArtifactEffectCreationGasCarbonDioxide
+ parent: BaseXenoArtifactEffect
+ description: Expels carbon dioxide
+ components:
+ - type: XAECreateGas
+ gases:
+ CarbonDioxide: 300
--- /dev/null
+- type: weightedRandomXenoArchTrigger
+ id: DefaultTriggers
+ weights:
+ TriggerMusic: 1
+ TriggerHeat: 1
+ TriggerCold: 0.5
+ TriggerNoOxygen: 1
+ TriggerWater: 1
+ TriggerCO2: 0.5
+ TriggerPlasma: 0.5
+ TriggerN2O: 0.5
+ TriggerTritium: 0.1
+ TriggerAmmonia: 0.1
+ TriggerFrezon: 0.1
+ TriggerRadiation: 1
+ TriggerPressureHigh: 0.5
+ TriggerPressureLow: 1
+ TriggerExamine: 1
+ TriggerBruteDamage: 1
+ #TriggerInteraction: 1 #this one interferes with activating artifact AND brute dmg!
+ TriggerWrenching: 0.5
+ TriggerPrying: 0.5
+ TriggerScrewing: 0.5
+ TriggerPulsing: 0.5
+ TriggerTimer: 0.25
+ TriggerBlood: 1
+ TriggerThrow: 1
+ TriggerDeath: 1
+ TriggerMagnet: 1
+
+- type: xenoArchTrigger
+ id: TriggerMusic
+ tip: xenoarch-trigger-tip-music
+ components:
+ - type: XATCompNearby
+ requireComponentWithName: ActiveInstrument
+ radius: 2
+
+- type: xenoArchTrigger
+ id: TriggerHeat
+ tip: xenoarch-trigger-tip-heat
+ components:
+ - type: XATTemperature
+ targetTemperature: 373
+ triggerOnHigherTemp: true
+ - type: XATDamageThresholdReached
+ typesNeeded:
+ Heat: 20
+# This kinda trivializes the difficulty.
+# - type: XATToolUse
+# requiredTool: Welding
+# delay: 5
+# fuel: 10
+
+- type: xenoArchTrigger
+ id: TriggerCold
+ tip: xenoarch-trigger-tip-cold
+ components:
+ - type: XATTemperature
+ targetTemperature: 255
+ triggerOnHigherTemp: false
+ - type: XATDamageThresholdReached
+ typesNeeded:
+ Cold: 20
+
+- type: xenoArchTrigger
+ id: TriggerNoOxygen
+ tip: xenoarch-trigger-tip-no-oxygen
+ components:
+ - type: XATGas
+ targetGas: Oxygen
+ moles: 10
+ shouldBePresent: false
+
+- type: xenoArchTrigger
+ id: TriggerWater
+ tip: xenoarch-trigger-tip-water
+ components:
+ - type: XATGas
+ targetGas: WaterVapor
+ - type: XATReactive
+ reagents:
+ - Water
+
+- type: xenoArchTrigger
+ id: TriggerCO2
+ tip: xenoarch-trigger-tip-co2
+ components:
+ - type: XATGas
+ targetGas: CarbonDioxide
+ - type: XATReactive
+ reagents:
+ - CarbonDioxide
+
+- type: xenoArchTrigger
+ id: TriggerPlasma
+ tip: xenoarch-trigger-tip-plasma
+ components:
+ - type: XATGas
+ targetGas: Plasma
+ - type: XATReactive
+ reagents:
+ - Plasma
+
+- type: xenoArchTrigger
+ id: TriggerTritium
+ tip: xenoarch-trigger-tip-tritium
+ components:
+ - type: XATGas
+ targetGas: Tritium
+ - type: XATReactive
+ reagents:
+ - Tritium
+
+- type: xenoArchTrigger
+ id: TriggerAmmonia
+ tip: xenoarch-trigger-tip-ammonia
+ components:
+ - type: XATGas
+ targetGas: Ammonia
+ - type: XATReactive
+ reagents:
+ - Ammonia
+
+- type: xenoArchTrigger
+ id: TriggerN2O
+ tip: xenoarch-trigger-tip-n2o
+ components:
+ - type: XATGas
+ targetGas: NitrousOxide
+ - type: XATReactive
+ reagents:
+ - NitrousOxide
+
+- type: xenoArchTrigger
+ id: TriggerFrezon
+ tip: xenoarch-trigger-tip-frezon
+ components:
+ - type: XATGas
+ targetGas: Frezon
+ - type: XATReactive
+ reagents:
+ - Frezon
+
+- type: xenoArchTrigger
+ id: TriggerRadiation
+ tip: xenoarch-trigger-tip-radiation
+ components:
+ - type: XATDamageThresholdReached
+ typesNeeded:
+ Radiation: 20
+ # TODO: legendary microwave trigger
+
+- type: xenoArchTrigger
+ id: TriggerPressureHigh
+ tip: xenoarch-trigger-tip-pressure-high
+ components:
+ - type: XATPressure
+ maxPressureThreshold: 385
+
+- type: xenoArchTrigger
+ id: TriggerPressureLow
+ tip: xenoarch-trigger-tip-pressure-low
+ components:
+ - type: XATPressure
+ minPressureThreshold: 50
+
+- type: xenoArchTrigger
+ id: TriggerExamine
+ tip: xenoarch-trigger-tip-examine
+ components:
+ - type: XATExamine
+
+- type: xenoArchTrigger
+ id: TriggerBruteDamage
+ tip: xenoarch-trigger-tip-brute-damage
+ components:
+ - type: XATDamageThresholdReached
+ groupsNeeded:
+ Brute: 20
+
+- type: xenoArchTrigger
+ id: TriggerInteraction
+ tip: xenoarch-trigger-tip-interaction
+ components:
+ - type: XATInteraction
+
+- type: xenoArchTrigger
+ id: TriggerWrenching
+ tip: xenoarch-trigger-tip-wrenching
+ components:
+ - type: XATToolUse
+ requiredTool: Anchoring
+ - type: XATExaminableText
+ examineText: xenoarch-trigger-examine-wrenching
+
+- type: xenoArchTrigger
+ id: TriggerPrying
+ tip: xenoarch-trigger-tip-prying
+ components:
+ - type: XATToolUse
+ requiredTool: Prying
+ - type: XATExaminableText
+ examineText: xenoarch-trigger-examine-prying
+
+- type: xenoArchTrigger
+ id: TriggerScrewing
+ tip: xenoarch-trigger-tip-screwing
+ components:
+ - type: XATToolUse
+ requiredTool: Screwing
+ - type: XATExaminableText
+ examineText: xenoarch-trigger-examine-screwing
+
+- type: xenoArchTrigger
+ id: TriggerPulsing
+ tip: xenoarch-trigger-tip-pulsing
+ components:
+ - type: XATToolUse
+ requiredTool: Pulsing
+ - type: XATExaminableText
+ examineText: xenoarch-trigger-examine-pulsing
+
+- type: xenoArchTrigger
+ id: TriggerTimer
+ tip: xenoarch-trigger-tip-timer
+ components:
+ - type: XATTimer
+ possibleDelayInSeconds:
+ min: 80
+ max: 120
+
+- type: xenoArchTrigger
+ id: TriggerBlood
+ tip: xenoarch-trigger-tip-blood
+ components:
+ - type: XATReactive
+ reagents:
+ - Blood
+ - CopperBlood
+ - InsectBlood
+ - Slime
+ - AmmoniaBlood
+ - ZombieBlood
+
+- type: xenoArchTrigger
+ id: TriggerThrow
+ tip: xenoarch-trigger-tip-throw
+ whitelist:
+ components:
+ - Item
+ components:
+ - type: XATItemLand
+
+- type: xenoArchTrigger
+ id: TriggerDeath
+ tip: xenoarch-trigger-tip-death
+ components:
+ - type: XATDeath
+
+- type: xenoArchTrigger
+ id: TriggerMagnet
+ tip: xenoarch-trigger-tip-magnet
+ components:
+ - type: XATMagnet
minValue: 1000
maxValue: 9999
+- type: nameIdentifierGroup
+ id: XenoArtifactNode
+ minValue: 0
+ maxValue: 999
+
# Used to suffix a number to any mob to identify player controlled mob griefing.
- type: nameIdentifierGroup
id: GenericNumber
0.10599999
]
]
+ },
+ {
+ "name": "artifact-activation",
+ "delays": [
+ [
+ 0.100,
+ 0.100,
+ 0.100,
+ 0.100,
+ 0.100,
+ 0.100,
+ 0.100,
+ 0.100
+ ]
+ ]
}
]
}