3mo xeno archeology (first phase) (#33370)
* DAG Adjacency Matrix & Tests * Fix sandbox type errors * First pass on procgen * Procgen adjustments * Networking * Cruft and god and beauty and analysis console * convert to data types that dont make me want to kill myself * starting work on console UI * drawing nodes n shit * damn that ui FUCKS * XAT * Add a bunch of basic triggers * Fix trigger gen * Add node info into the analysis console UI * Add node unlocking * more trigger cuz thats pretty cool * final triggers + incorporate gnostic faith * some ui changes, mostly * Fix orphaned procgen segments * its not random dipshit * yeah... this one will make pjb happy.... * we call it a day for the UI * imagine... shared power code... * extraction WIP but we gotta sidequest momentarily * oh hey would you look at that its the actual functionality * distrotrased * Small departure for randomness. * ok yep yep indeed that is an effect very cool. * thanos snap oldcode * fuck it we ball * feat: node scanner now displays triggered nodes. Removed unused old artifact systems and related code (most of it). xml-doc and minor fixups. * refactor: most of preparations, cleanup and groundwork. also segment-related tests * feature: all basic effects returning * feat: finished effects lits, created weight lists for struct and handheld artifacts, fixed throw trigger and music ApplyComponent artifact effects not working * feat: prevent non-first-time-predicted calls in shared artifact effect systems * fix: remove gun effect from artifact effects - as it interferes with 'activate artefact' action * fix: foam reagent selection, neat ApplyComponents art effect scenarios, handheld art is RadiationReceiver again * fix: moved spawn/ pry&throw effect systems back to server part of code - entity duplication bugs were not quite fun * refactor: fix protos * refactor: fix linter * fix: fix old artifact component names in yml * fix: no more throwing error on artifact spawn with empty XAEFoamComponent.Reagents * fix: removed old component usage in maps * fix: remove more deleted components from map * fix: ContainerContainer is now part of initial artifact entity, it won't be affecting UninitializedSaveTest * refactor: fix tests, add loc description to toolshed commands * Changed node scanner to tell the whole story about current artifact state * refactor: remove excessive get of EntityCoordinates in XAE systems, removed Value access in NodeScannerDisplay * fix: turned off TriggerInteraction, removed XAESpawn usage and system, EmpSystem now can use EntityCoordinates, * fix: moved SharedXenoArtifactSystem.CancelUnlockingOnGraphStructureChange into RebuildXenoArtifactMetaData to lessen code coupling * fix: XenoArtifactEffectJunkSpawn moved invalid rolls declaration * refactor: set default value for XenoArtifactComponent.EffectsTable for tests * fix: now explosions XAE can be activated for effect * refactor: added some usedelay so artifactuse would'nt be spammed * refactor: artifact-related hints improvements * fix: artifact no longer spawns fauna into itself * refactor: xml-doc and minor refactoring * refactor: xml-doc for Xeno Artifact systems, renaming of questionable XAT systems * map for playtest, TODO REVERT THIS * fix: magboots trigger art from a mile * refactor: bind artifact animation to unlocking state * feat: radiation dmg now have reference to source (and artifacts won't irradiate themselves) * fix: random artifact node durability now is rolled for max and not current value * refactor: gas effects are more rare, hand-held artifact effects are filtered properly now, rad dmg trigger now requires only 20 dmg for activation * feat: animations and sound effects for artifact force-use and failed finish of unlocking phase * use only 1 file with art use animation * refactor: minor artifact dmg triggers tuning * feat: now nodes that CAN be unlocked are displayed with specific color in console. * feat: now unlocking stage time is dynamic and it depends on amount of triggers player activated correctly. Failed one stops incrementing * feat: now non-active unlocked nodes return more points if durability was not wasted * feat: now puddle/foam effects change description of node * fix: fix test failure * refactor: renamed phasing effect, fixed failing test for elkridge * minor balance changes * refactor: split rare materials into separate effects * feat: unlocked nodes without successor wont listen to unlocks, node unlock is not activating node * fix: removed OnIrradiatedEvent duplicate c-tor * revert changes of reach for playtest * revert last row empty line removal on reach.yml * fix: fix PVS bug, born from attempt to relay event to art nodes that were not synced yet to the client * fix: fix elkridge for tests (again) * refactor: xml-doc, more stuff predicted, allocation optimization in XAE/XAT systems * refactor: naming * refactor: extract variable refactor for XAEApplyComponentsSystem.OnActivated insides * fix: duplicate xeno artifact unlocking sound fixed * feat: CreatePuddle xeno artifact effect now can have min and max borders for chamicals to be drafted, minor XAECreatePuddleSystem refactor * feat: networking for shared XAE components + xml-doc leftovers * refactor: more xml-doc, fix XAEApplyComponentsComponent.Components not being serializable but trying to be * refactor: xml-docs and XAEThrowThingsAroundSystem now uses circle and not box for prying tiles * refactor: xml-docs, minor refactors * revert XenoArtifactCommand.ArtifactPrototype being PrototId * refactor: simplify the way ExtractionResearchLabel works --------- Co-authored-by: EmoGarbage404 <retron404@gmail.com> Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru>
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
using Content.Shared.Xenoarchaeology.Artifact;
|
||||||
|
|
||||||
|
namespace Content.Client.Xenoarchaeology.Artifact;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public sealed class XenoArtifactSystem : SharedXenoArtifactSystem;
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,66 +1,50 @@
|
|||||||
using Content.Shared.Xenoarchaeology.Equipment;
|
using Content.Shared.Research.Components;
|
||||||
using JetBrains.Annotations;
|
using Content.Shared.Xenoarchaeology.Equipment.Components;
|
||||||
using Robust.Client.GameObjects;
|
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
namespace Content.Client.Xenoarchaeology.Ui;
|
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]
|
[UsedImplicitly]
|
||||||
public sealed class AnalysisConsoleBoundUserInterface : BoundUserInterface
|
public sealed class AnalysisConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
|
||||||
{
|
{
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
private AnalysisConsoleMenu? _consoleMenu;
|
private AnalysisConsoleMenu? _consoleMenu;
|
||||||
|
|
||||||
public AnalysisConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
/// <inheritdoc />
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Open()
|
protected override void Open()
|
||||||
{
|
{
|
||||||
base.Open();
|
base.Open();
|
||||||
|
|
||||||
_consoleMenu = this.CreateWindow<AnalysisConsoleMenu>();
|
_consoleMenu = this.CreateWindow<AnalysisConsoleMenu>();
|
||||||
|
_consoleMenu.SetOwner(owner);
|
||||||
|
|
||||||
|
_consoleMenu.OnClose += Close;
|
||||||
|
_consoleMenu.OpenCentered();
|
||||||
|
|
||||||
_consoleMenu.OnServerSelectionButtonPressed += () =>
|
_consoleMenu.OnServerSelectionButtonPressed += () =>
|
||||||
{
|
{
|
||||||
SendMessage(new AnalysisConsoleServerSelectionMessage());
|
SendMessage(new ConsoleServerSelectionMessage());
|
||||||
};
|
|
||||||
_consoleMenu.OnScanButtonPressed += () =>
|
|
||||||
{
|
|
||||||
SendMessage(new AnalysisConsoleScanButtonPressedMessage());
|
|
||||||
};
|
|
||||||
_consoleMenu.OnPrintButtonPressed += () =>
|
|
||||||
{
|
|
||||||
SendMessage(new AnalysisConsolePrintButtonPressedMessage());
|
|
||||||
};
|
};
|
||||||
_consoleMenu.OnExtractButtonPressed += () =>
|
_consoleMenu.OnExtractButtonPressed += () =>
|
||||||
{
|
{
|
||||||
SendMessage(new AnalysisConsoleExtractButtonPressedMessage());
|
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);
|
_consoleMenu?.Update(ent);
|
||||||
|
|
||||||
switch (state)
|
|
||||||
{
|
|
||||||
case AnalysisConsoleUpdateState msg:
|
|
||||||
_consoleMenu?.SetButtonsDisabled(msg);
|
|
||||||
_consoleMenu?.UpdateInformationDisplay(msg);
|
|
||||||
_consoleMenu?.UpdateProgressBar(msg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
|
|||||||
@@ -1,80 +1,91 @@
|
|||||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||||
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||||
|
xmlns:ui="clr-namespace:Content.Client.Xenoarchaeology.Ui"
|
||||||
Title="{Loc 'analysis-console-menu-title'}"
|
Title="{Loc 'analysis-console-menu-title'}"
|
||||||
MinSize="620 280"
|
MinSize="700 350"
|
||||||
SetSize="620 280">
|
SetSize="980 550">
|
||||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
|
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
|
||||||
<BoxContainer Margin="10 10 10 10" MinWidth="150" Orientation="Vertical"
|
<BoxContainer Margin="10 10 10 10" MaxWidth="240" SetWidth="240" Orientation="Vertical" HorizontalExpand="False" VerticalExpand="True">
|
||||||
VerticalExpand="True" SizeFlagsStretchRatio="1">
|
<PanelContainer Name="BackPanel" HorizontalAlignment="Center">
|
||||||
<BoxContainer Orientation="Vertical" VerticalExpand="True">
|
<PanelContainer.PanelOverride>
|
||||||
<Button Name="ServerSelectionButton"
|
<gfx:StyleBoxTexture Modulate="#1B1B1E" PatchMarginBottom="10" PatchMarginLeft="10" PatchMarginRight="10" PatchMarginTop="10"/>
|
||||||
Text="{Loc 'analysis-console-server-list-button'}"></Button>
|
</PanelContainer.PanelOverride>
|
||||||
<BoxContainer MinHeight="5"></BoxContainer>
|
<BoxContainer HorizontalExpand="True" VerticalExpand="True" MinSize="128 128">
|
||||||
<Button Name="ScanButton"
|
<SpriteView Name="ArtifactView" Scale="4 4" HorizontalAlignment="Center" VerticalAlignment="Center" HorizontalExpand="True" VerticalExpand="True"/>
|
||||||
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>
|
</BoxContainer>
|
||||||
<BoxContainer MinHeight="15"></BoxContainer>
|
</PanelContainer>
|
||||||
<Button Name="ExtractButton"
|
<customControls:HSeparator StyleClasses="HighDivider" Margin="0 15 0 10"/>
|
||||||
Text="{Loc 'analysis-console-extract-button'}"
|
<BoxContainer Name="ExtractContainer" Orientation="Vertical" VerticalExpand="True" Visible="False">
|
||||||
ToolTip="{Loc 'analysis-console-extract-button-info'}">
|
<PanelContainer HorizontalExpand="True" VerticalExpand="True" RectClipContent="True">
|
||||||
</Button>
|
|
||||||
</BoxContainer>
|
|
||||||
<BoxContainer Orientation="Vertical">
|
|
||||||
<Label Name="ProgressLabel"></Label>
|
|
||||||
<ProgressBar
|
|
||||||
Name="ProgressBar"
|
|
||||||
MinValue="0"
|
|
||||||
MaxValue="1"
|
|
||||||
SetHeight="20">
|
|
||||||
</ProgressBar>
|
|
||||||
</BoxContainer>
|
|
||||||
</BoxContainer>
|
|
||||||
<customControls:VSeparator StyleClasses="LowDivider" />
|
|
||||||
<PanelContainer Margin="10 10 10 10" HorizontalExpand="True" SizeFlagsStretchRatio="3">
|
|
||||||
<PanelContainer.PanelOverride>
|
<PanelContainer.PanelOverride>
|
||||||
<gfx:StyleBoxFlat BackgroundColor="#000000FF" />
|
<gfx:StyleBoxFlat BackgroundColor="#000000FF" />
|
||||||
</PanelContainer.PanelOverride>
|
</PanelContainer.PanelOverride>
|
||||||
<BoxContainer Margin="10 10 10 10" Orientation="Horizontal">
|
<BoxContainer Margin="10 10 10 5" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
|
||||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
<ScrollContainer HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True">
|
||||||
<BoxContainer VerticalExpand="True">
|
<BoxContainer HorizontalExpand="True" VerticalExpand="True">
|
||||||
<RichTextLabel Name="Information"> </RichTextLabel>
|
<RichTextLabel Name="ExtractionResearchLabel" VerticalAlignment="Top" HorizontalAlignment="Left"/>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
</BoxContainer>
|
</ScrollContainer>
|
||||||
<BoxContainer VerticalExpand="False" Orientation="Vertical" MaxSize="64 64">
|
<Control MinHeight="5"/>
|
||||||
<SpriteView
|
<RichTextLabel Name="ExtractionSumLabel" VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
|
||||||
Name="ArtifactDisplay"
|
|
||||||
OverrideDirection="South"
|
|
||||||
VerticalExpand="False"
|
|
||||||
SetSize="64 64"
|
|
||||||
MaxSize="64 64"
|
|
||||||
Scale="2 2">
|
|
||||||
</SpriteView>
|
|
||||||
</BoxContainer>
|
|
||||||
<BoxContainer VerticalExpand="True"></BoxContainer>
|
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
</PanelContainer>
|
</PanelContainer>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
|
<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" />
|
||||||
|
<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>
|
||||||
|
</PanelContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
</BoxContainer>
|
||||||
</controls:FancyWindow>
|
</controls:FancyWindow>
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
using Content.Client.Stylesheets;
|
using System.Text;
|
||||||
|
using Content.Client.Message;
|
||||||
|
using Content.Client.Resources;
|
||||||
using Content.Client.UserInterface.Controls;
|
using Content.Client.UserInterface.Controls;
|
||||||
using Content.Shared.Xenoarchaeology.Equipment;
|
using Content.Client.Xenoarchaeology.Artifact;
|
||||||
using Microsoft.VisualBasic;
|
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.AutoGenerated;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.ResourceManagement;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
@@ -14,170 +21,213 @@ namespace Content.Client.Xenoarchaeology.Ui;
|
|||||||
[GenerateTypedNameReferences]
|
[GenerateTypedNameReferences]
|
||||||
public sealed partial class AnalysisConsoleMenu : FancyWindow
|
public sealed partial class AnalysisConsoleMenu : FancyWindow
|
||||||
{
|
{
|
||||||
|
private static readonly TimeSpan ExtractInfoDisplayForDuration = TimeSpan.FromSeconds(3);
|
||||||
|
|
||||||
[Dependency] private readonly IEntityManager _ent = default!;
|
[Dependency] private readonly IEntityManager _ent = default!;
|
||||||
|
[Dependency] private readonly IResourceCache _resCache = default!;
|
||||||
[Dependency] private readonly IGameTiming _timing = default!;
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
|
|
||||||
|
private readonly ArtifactAnalyzerSystem _artifactAnalyzer;
|
||||||
|
private readonly XenoArtifactSystem _xenoArtifact;
|
||||||
|
private readonly AudioSystem _audio;
|
||||||
|
private readonly MetaDataSystem _meta = default!;
|
||||||
|
|
||||||
|
private Entity<AnalysisConsoleComponent> _owner;
|
||||||
|
private Entity<XenoArtifactNodeComponent>? _currentNode;
|
||||||
|
|
||||||
|
private TimeSpan? _hideExtractInfoIn;
|
||||||
|
private int _extractionSum;
|
||||||
|
|
||||||
public event Action? OnServerSelectionButtonPressed;
|
public event Action? OnServerSelectionButtonPressed;
|
||||||
public event Action? OnScanButtonPressed;
|
|
||||||
public event Action? OnPrintButtonPressed;
|
|
||||||
public event Action? OnExtractButtonPressed;
|
public event Action? OnExtractButtonPressed;
|
||||||
public event Action? OnUpBiasButtonPressed;
|
|
||||||
public event Action? OnDownBiasButtonPressed;
|
|
||||||
|
|
||||||
// For rendering the progress bar, updated from BUI state
|
|
||||||
private TimeSpan? _startTime;
|
|
||||||
private TimeSpan? _totalTime;
|
|
||||||
private TimeSpan? _accumulatedRunTime;
|
|
||||||
|
|
||||||
private bool _paused;
|
|
||||||
|
|
||||||
public AnalysisConsoleMenu()
|
public AnalysisConsoleMenu()
|
||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
IoCManager.InjectDependencies(this);
|
IoCManager.InjectDependencies(this);
|
||||||
|
|
||||||
ServerSelectionButton.OnPressed += _ => OnServerSelectionButtonPressed?.Invoke();
|
_xenoArtifact = _ent.System<XenoArtifactSystem>();
|
||||||
ScanButton.OnPressed += _ => OnScanButtonPressed?.Invoke();
|
_artifactAnalyzer = _ent.System<ArtifactAnalyzerSystem>();
|
||||||
PrintButton.OnPressed += _ => OnPrintButtonPressed?.Invoke();
|
_audio = _ent.System<AudioSystem>();
|
||||||
ExtractButton.OnPressed += _ => OnExtractButtonPressed?.Invoke();
|
_meta = _ent.System<MetaDataSystem>();
|
||||||
UpBiasButton.OnPressed += _ => OnUpBiasButtonPressed?.Invoke();
|
|
||||||
DownBiasButton.OnPressed += _ => OnDownBiasButtonPressed?.Invoke();
|
|
||||||
|
|
||||||
var buttonGroup = new ButtonGroup(false);
|
if (BackPanel.PanelOverride is StyleBoxTexture tex)
|
||||||
UpBiasButton.Group = buttonGroup;
|
tex.Texture = _resCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
|
||||||
DownBiasButton.Group = buttonGroup;
|
|
||||||
|
GraphControl.OnNodeSelected += node =>
|
||||||
|
{
|
||||||
|
_currentNode = node;
|
||||||
|
SetSelectedNode(node);
|
||||||
|
};
|
||||||
|
|
||||||
|
ServerButton.OnPressed += _ =>
|
||||||
|
{
|
||||||
|
OnServerSelectionButtonPressed?.Invoke();
|
||||||
|
};
|
||||||
|
|
||||||
|
ExtractButton.OnPressed += StartExtract;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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)
|
||||||
|
{
|
||||||
|
if (!_ent.TryGetComponent<AnalysisConsoleComponent>(owner, out var comp))
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_owner = (owner, comp);
|
||||||
|
Update(_owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartExtract(BaseButton.ButtonEventArgs obj)
|
||||||
|
{
|
||||||
|
if (!_artifactAnalyzer.TryGetArtifactFromConsole(_owner, out var artifact))
|
||||||
|
return;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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)));
|
||||||
|
|
||||||
|
_audio.PlayGlobal(_owner.Comp.ScanFinishedSound, _owner, AudioParams.Default.WithVolume(1f));
|
||||||
|
OnExtractButtonPressed?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void FrameUpdate(FrameEventArgs args)
|
protected override void FrameUpdate(FrameEventArgs args)
|
||||||
{
|
{
|
||||||
base.FrameUpdate(args);
|
base.FrameUpdate(args);
|
||||||
|
|
||||||
if (_startTime is not { } start || _totalTime is not { } total || _accumulatedRunTime is not { } accumulated)
|
if (_hideExtractInfoIn == null || _timing.CurTime + _meta.GetPauseTime(_owner) < _hideExtractInfoIn)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var remaining = total - accumulated;
|
ExtractContainer.Visible = false;
|
||||||
if (!_paused)
|
NodeViewContainer.Visible = true;
|
||||||
{
|
_hideExtractInfoIn = null;
|
||||||
// If the analyzer is running, its remaining time is further discounted by the time it's been running for.
|
|
||||||
remaining += start - _timing.CurTime;
|
|
||||||
}
|
|
||||||
var secsText = Math.Max((int) remaining.TotalSeconds, 0);
|
|
||||||
|
|
||||||
ProgressLabel.Text = Loc.GetString("analysis-console-progress-text",
|
|
||||||
("seconds", secsText));
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetButtonsDisabled(AnalysisConsoleUpdateState state)
|
public void Update(Entity<AnalysisConsoleComponent> ent)
|
||||||
{
|
{
|
||||||
ScanButton.Disabled = !state.CanScan;
|
_artifactAnalyzer.TryGetArtifactFromConsole(ent, out var arti);
|
||||||
PrintButton.Disabled = !state.CanPrint;
|
ArtifactView.SetEntity(arti);
|
||||||
if (state.IsTraversalDown)
|
GraphControl.SetArtifact(arti);
|
||||||
DownBiasButton.Pressed = true;
|
|
||||||
|
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
|
else
|
||||||
UpBiasButton.Pressed = true;
|
NoArtiLabel.Visible = false;
|
||||||
|
|
||||||
ExtractButton.Disabled = false;
|
if (_currentNode == null
|
||||||
if (!state.ServerConnected)
|
|| arti == null
|
||||||
|
|| !_xenoArtifact.TryGetIndex((arti.Value, arti.Value), _currentNode.Value, out _))
|
||||||
{
|
{
|
||||||
ExtractButton.Disabled = true;
|
SetSelectedNode(null);
|
||||||
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 (ExtractButton.Disabled)
|
public void SetSelectedNode(Entity<XenoArtifactNodeComponent>? node)
|
||||||
{
|
{
|
||||||
ExtractButton.RemoveStyleClass("ButtonColorGreen");
|
InfoContainer.Visible = node != null;
|
||||||
}
|
if (!_artifactAnalyzer.TryGetArtifactFromConsole(_owner, out var artifact))
|
||||||
else
|
|
||||||
{
|
|
||||||
ExtractButton.AddStyleClass("ButtonColorGreen");
|
|
||||||
ExtractButton.ToolTip = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void UpdateArtifactIcon(EntityUid? uid)
|
|
||||||
{
|
|
||||||
if (uid == null)
|
|
||||||
{
|
|
||||||
ArtifactDisplay.Visible = false;
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
ArtifactDisplay.Visible = true;
|
NoneSelectedLabel.Visible = node == null;
|
||||||
ArtifactDisplay.SetEntity(uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateInformationDisplay(AnalysisConsoleUpdateState state)
|
if (node == null)
|
||||||
{
|
|
||||||
var message = new FormattedMessage();
|
|
||||||
|
|
||||||
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
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
UpdateArtifactIcon(_ent.GetEntity(state.Artifact));
|
var nodeId = _xenoArtifact.GetNodeId(node.Value);
|
||||||
|
IDValueLabel.SetMarkup(Loc.GetString("analysis-console-info-id-value", ("id", nodeId)));
|
||||||
|
|
||||||
if (state.ScanReport == null)
|
// 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)));
|
||||||
|
|
||||||
|
var percent = (float) node.Value.Comp.Durability / node.Value.Comp.MaxDurability;
|
||||||
|
var color = percent switch
|
||||||
{
|
{
|
||||||
if (!state.AnalyzerConnected) //no analyzer connected
|
>= 0.75f => Color.Lime,
|
||||||
message.AddMarkupOrThrow(Loc.GetString("analysis-console-info-no-scanner"));
|
>= 0.50f => Color.Yellow,
|
||||||
else if (!state.CanScan) //no artifact
|
_ => Color.Red
|
||||||
message.AddMarkupOrThrow(Loc.GetString("analysis-console-info-no-artifact"));
|
};
|
||||||
else if (state.Artifact == null) //ready to go
|
DurabilityValueLabel.SetMarkup(Loc.GetString("analysis-console-info-durability-value",
|
||||||
message.AddMarkupOrThrow(Loc.GetString("analysis-console-info-ready"));
|
("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)
|
||||||
|
{
|
||||||
|
TriggerValueLabel.SetMarkup(Loc.GetString("analysis-console-info-effect-value", ("state", false)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
message.AddMessage(state.ScanReport);
|
var triggerStr = new StringBuilder();
|
||||||
}
|
triggerStr.Append("- ");
|
||||||
|
triggerStr.Append(Loc.GetString(node.Value.Comp.TriggerTip!));
|
||||||
|
|
||||||
Information.SetMessage(message);
|
foreach (var predecessor in predecessorNodes)
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateProgressBar(AnalysisConsoleUpdateState state)
|
|
||||||
{
|
{
|
||||||
ProgressBar.Visible = state.Scanning;
|
triggerStr.AppendLine();
|
||||||
ProgressLabel.Visible = state.Scanning;
|
triggerStr.Append("- ");
|
||||||
|
triggerStr.Append(Loc.GetString(predecessor.Comp.TriggerTip!));
|
||||||
|
}
|
||||||
|
TriggerValueLabel.SetMarkup(Loc.GetString("analysis-console-info-triggered-value", ("triggers", triggerStr.ToString())));
|
||||||
|
}
|
||||||
|
|
||||||
_startTime = state.StartTime;
|
ClassValueLabel.SetMarkup(Loc.GetString("analysis-console-info-class-value",
|
||||||
_totalTime = state.TotalTime;
|
("class", Loc.GetString($"artifact-node-class-{Math.Min(6, predecessorNodes.Count + 1)}"))));
|
||||||
_accumulatedRunTime = state.AccumulatedRunTime;
|
|
||||||
_paused = state.Paused;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
20
Content.Client/Xenoarchaeology/Ui/NodeScannerDisplay.xaml
Normal file
20
Content.Client/Xenoarchaeology/Ui/NodeScannerDisplay.xaml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<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>
|
||||||
86
Content.Client/Xenoarchaeology/Ui/NodeScannerDisplay.xaml.cs
Normal file
86
Content.Client/Xenoarchaeology/Ui/NodeScannerDisplay.xaml.cs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
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")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<controls:XenoArtifactGraphControl
|
||||||
|
xmlns="https://spacestation14.io"
|
||||||
|
xmlns:controls="clr-namespace:Content.Client.Xenoarchaeology.Ui"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
VerticalExpand="True"
|
||||||
|
MouseFilter="Stop"/>
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Shared.Xenoarchaeology.XenoArtifacts;
|
using Content.Shared.Xenoarchaeology.XenoArtifacts;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
|
|
||||||
namespace Content.Client.Xenoarchaeology.XenoArtifacts;
|
namespace Content.Client.Xenoarchaeology.XenoArtifacts;
|
||||||
@@ -13,19 +13,28 @@ public sealed class RandomArtifactSpriteSystem : VisualizerSystem<RandomArtifact
|
|||||||
if (!AppearanceSystem.TryGetData<int>(uid, SharedArtifactsVisuals.SpriteIndex, out var spriteIndex, args.Component))
|
if (!AppearanceSystem.TryGetData<int>(uid, SharedArtifactsVisuals.SpriteIndex, out var spriteIndex, args.Component))
|
||||||
return;
|
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))
|
if (!AppearanceSystem.TryGetData<bool>(uid, SharedArtifactsVisuals.IsActivated, out var isActivated, args.Component))
|
||||||
isActivated = false;
|
isActivated = false;
|
||||||
|
|
||||||
var spriteIndexStr = spriteIndex.ToString("D2");
|
var spriteIndexStr = spriteIndex.ToString("D2");
|
||||||
var spritePrefix = isActivated ? "_on" : "";
|
var spritePrefix = isUnlocking ? "_on" : "";
|
||||||
|
|
||||||
// layered artifact sprite
|
// 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;
|
var spriteState = "ano" + spriteIndexStr;
|
||||||
args.Sprite.LayerSetState(ArtifactsVisualLayers.Base, spriteState);
|
args.Sprite.LayerSetState(ArtifactsVisualLayers.Base, spriteState);
|
||||||
args.Sprite.LayerSetState(layer, spriteState + "_on");
|
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
|
// non-layered
|
||||||
else
|
else
|
||||||
@@ -33,12 +42,12 @@ public sealed class RandomArtifactSpriteSystem : VisualizerSystem<RandomArtifact
|
|||||||
var spriteState = "ano" + spriteIndexStr + spritePrefix;
|
var spriteState = "ano" + spriteIndexStr + spritePrefix;
|
||||||
args.Sprite.LayerSetState(ArtifactsVisualLayers.Base, spriteState);
|
args.Sprite.LayerSetState(ArtifactsVisualLayers.Base, spriteState);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ArtifactsVisualLayers : byte
|
public enum ArtifactsVisualLayers : byte
|
||||||
{
|
{
|
||||||
Base,
|
Base,
|
||||||
Effect // doesn't have to use this
|
UnlockingEffect, // doesn't have to use this
|
||||||
|
ActivationEffect
|
||||||
}
|
}
|
||||||
|
|||||||
419
Content.IntegrationTests/Tests/XenoArtifactTest.cs
Normal file
419
Content.IntegrationTests/Tests/XenoArtifactTest.cs
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,8 +10,6 @@ using Content.Server.Mind;
|
|||||||
using Content.Server.Mind.Commands;
|
using Content.Server.Mind.Commands;
|
||||||
using Content.Server.Prayer;
|
using Content.Server.Prayer;
|
||||||
using Content.Server.Station.Systems;
|
using Content.Server.Station.Systems;
|
||||||
using Content.Server.Xenoarchaeology.XenoArtifacts;
|
|
||||||
using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
|
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||||
using Content.Shared.Chemistry.EntitySystems;
|
using Content.Shared.Chemistry.EntitySystems;
|
||||||
@@ -60,7 +58,6 @@ namespace Content.Server.Administration.Systems
|
|||||||
[Dependency] private readonly EuiManager _euiManager = default!;
|
[Dependency] private readonly EuiManager _euiManager = default!;
|
||||||
[Dependency] private readonly GameTicker _ticker = default!;
|
[Dependency] private readonly GameTicker _ticker = default!;
|
||||||
[Dependency] private readonly GhostRoleSystem _ghostRoleSystem = default!;
|
[Dependency] private readonly GhostRoleSystem _ghostRoleSystem = default!;
|
||||||
[Dependency] private readonly ArtifactSystem _artifactSystem = default!;
|
|
||||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||||
[Dependency] private readonly PrayerSystem _prayerSystem = default!;
|
[Dependency] private readonly PrayerSystem _prayerSystem = default!;
|
||||||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
[Dependency] private readonly MindSystem _mindSystem = default!;
|
||||||
@@ -454,29 +451,6 @@ namespace Content.Server.Administration.Systems
|
|||||||
args.Verbs.Add(verb);
|
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
|
// Make Sentient verb
|
||||||
if (_groupController.CanCommand(player, "makesentient") &&
|
if (_groupController.CanCommand(player, "makesentient") &&
|
||||||
args.User != args.Target &&
|
args.User != args.Target &&
|
||||||
|
|||||||
@@ -44,6 +44,22 @@ public sealed class EmpSystem : SharedEmpSystem
|
|||||||
Spawn(EmpPulseEffectPrototype, coordinates);
|
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>
|
/// <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.
|
/// 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>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
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));
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
using Content.Server.UserInterface;
|
|
||||||
using Content.Shared.Instruments;
|
using Content.Shared.Instruments;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using ActivatableUIComponent = Content.Shared.UserInterface.ActivatableUIComponent;
|
using ActivatableUIComponent = Content.Shared.UserInterface.ActivatableUIComponent;
|
||||||
@@ -21,8 +20,3 @@ public sealed partial class InstrumentComponent : SharedInstrumentComponent
|
|||||||
_entMan.GetComponentOrNull<ActivatableUIComponent>(Owner)?.CurrentSingleUser
|
_entMan.GetComponentOrNull<ActivatableUIComponent>(Owner)?.CurrentSingleUser
|
||||||
?? _entMan.GetComponentOrNull<ActorComponent>(Owner)?.PlayerSession.AttachedEntity;
|
?? _entMan.GetComponentOrNull<ActorComponent>(Owner)?.PlayerSession.AttachedEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
[RegisterComponent]
|
|
||||||
public sealed partial class ActiveInstrumentComponent : Component
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using Content.Shared.PAI;
|
|||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Content.Shared.Instruments;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
|
|
||||||
namespace Content.Server.PAI;
|
namespace Content.Server.PAI;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Server.Radiation.Components;
|
using Content.Server.Radiation.Components;
|
||||||
using Content.Shared.Radiation.Components;
|
using Content.Shared.Radiation.Components;
|
||||||
using Content.Shared.Radiation.Events;
|
using Content.Shared.Radiation.Events;
|
||||||
using Content.Shared.Stacks;
|
using Content.Shared.Stacks;
|
||||||
@@ -50,7 +50,7 @@ public sealed partial class RadiationSystem : EntitySystem
|
|||||||
|
|
||||||
public void IrradiateEntity(EntityUid uid, float radsPerSecond, float time)
|
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);
|
RaiseLocalEvent(uid, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using Content.Shared.Station.Components;
|
|||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Content.Shared.Chemistry.Reaction;
|
||||||
|
|
||||||
namespace Content.Server.StationEvents.Events;
|
namespace Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ public sealed class VentClogRule : StationEventSystem<VentClogRuleComponent>
|
|||||||
var quantity = weak ? component.WeakReagentQuantity : component.ReagentQuantity;
|
var quantity = weak ? component.WeakReagentQuantity : component.ReagentQuantity;
|
||||||
solution.AddReagent(reagent, quantity);
|
solution.AddReagent(reagent, quantity);
|
||||||
|
|
||||||
var foamEnt = Spawn("Foam", transform.Coordinates);
|
var foamEnt = Spawn(ChemicalReactionSystem.FoamReaction, transform.Coordinates);
|
||||||
var spreadAmount = weak ? component.WeakSpread : component.Spread;
|
var spreadAmount = weak ? component.WeakSpread : component.Spread;
|
||||||
_smoke.StartSmoke(foamEnt, solution, component.Time, spreadAmount);
|
_smoke.StartSmoke(foamEnt, solution, component.Time, spreadAmount);
|
||||||
Audio.PlayPvs(component.Sound, transform.Coordinates);
|
Audio.PlayPvs(component.Sound, transform.Coordinates);
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
|
|
||||||
using Content.Shared.Item;
|
using Content.Shared.Item;
|
||||||
|
using Content.Shared.Xenoarchaeology.Artifact;
|
||||||
using Content.Shared.Xenoarchaeology.XenoArtifacts;
|
using Content.Shared.Xenoarchaeology.XenoArtifacts;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts;
|
namespace Content.Server.Xenoarchaeology.Artifact;
|
||||||
|
|
||||||
public sealed class RandomArtifactSpriteSystem : EntitySystem
|
public sealed class RandomArtifactSpriteSystem : EntitySystem
|
||||||
{
|
{
|
||||||
@@ -17,8 +17,11 @@ public sealed class RandomArtifactSpriteSystem : EntitySystem
|
|||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<RandomArtifactSpriteComponent, MapInitEvent>(OnMapInit);
|
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)
|
public override void Update(float frameTime)
|
||||||
@@ -47,9 +50,19 @@ public sealed class RandomArtifactSpriteSystem : EntitySystem
|
|||||||
_item.SetHeldPrefix(uid, "ano" + randomSprite.ToString("D2")); //set item artifact inhands
|
_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(uid, SharedArtifactsVisuals.IsActivated, true);
|
_appearance.SetData(ent, SharedArtifactsVisuals.IsUnlocking, true);
|
||||||
component.ActivationStart = _time.CurTime;
|
}
|
||||||
|
|
||||||
|
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(ent, SharedArtifactsVisuals.IsActivated, true);
|
||||||
|
ent.Comp.ActivationStart = _time.CurTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Flickers all the lights within a certain radius.
|
/// Flickers all the lights within a certain radius.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent, Access(typeof(XAELightFlickerSystem))]
|
||||||
public sealed partial class LightFlickerArtifactComponent : Component
|
public sealed partial class XAELightFlickerComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Lights within this radius will be flickered on activation
|
/// Lights within this radius will be flickered on activation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("radius")]
|
[DataField]
|
||||||
public float Radius = 4;
|
public float Radius = 4;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The chance that the light will flicker
|
/// The chance that the light will flicker.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("flickerChance")]
|
[DataField]
|
||||||
public float FlickerChance = 0.75f;
|
public float FlickerChance = 0.75f;
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,14 @@
|
|||||||
using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
|
|
||||||
using Robust.Shared.Audio;
|
|
||||||
using Content.Shared.Polymorph;
|
using Content.Shared.Polymorph;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Artifact polymorphs surrounding entities when triggered.
|
/// Artifact polymorphs entities when triggered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent, Access(typeof(XAEPolymorphSystem))]
|
||||||
[Access(typeof(PolyOthersArtifactSystem))]
|
public sealed partial class XAEPolymorphComponent : Component
|
||||||
public sealed partial class PolyOthersArtifactComponent : Component
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The polymorph effect to trigger.
|
/// The polymorph effect to trigger.
|
||||||
@@ -19,7 +17,7 @@ public sealed partial class PolyOthersArtifactComponent : Component
|
|||||||
public ProtoId<PolymorphPrototype> PolymorphPrototypeName = "ArtifactMonkey";
|
public ProtoId<PolymorphPrototype> PolymorphPrototypeName = "ArtifactMonkey";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// range of the effect.
|
/// Range of the effect.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public float Range = 2f;
|
public float Range = 2f;
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Harmless artifact that broadcast "thoughts" to players nearby.
|
/// Harmless artifact that broadcast "thoughts" to players nearby.
|
||||||
/// Thoughts are shown as popups and unique for each player.
|
/// Thoughts are shown as popups and unique for each player.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent, Access(typeof(XAETelepathicSystem))]
|
||||||
public sealed partial class TelepathicArtifactComponent : Component
|
public sealed partial class XAETelepathicComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loc string ids of telepathic messages.
|
/// Loc string ids of telepathic messages.
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
|
|
||||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Change atmospherics temperature until it reach target.
|
/// Change atmospherics temperature until it reach target.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent, Access(typeof(XAETemperatureSystem))]
|
||||||
public sealed partial class TemperatureArtifactComponent : Component
|
public sealed partial class XAETemperatureComponent : Component
|
||||||
{
|
{
|
||||||
[DataField("targetTemp"), ViewVariables(VVAccess.ReadWrite)]
|
[DataField("targetTemp"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
public float TargetTemperature = Atmospherics.T0C;
|
public float TargetTemperature = Atmospherics.T0C;
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Throws all nearby entities backwards.
|
/// Throws all nearby entities backwards.
|
||||||
/// Also pries nearby tiles.
|
/// Also pries nearby tiles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent, Access(typeof(XAEThrowThingsAroundSystem))]
|
||||||
public sealed partial class ThrowArtifactComponent : Component
|
public sealed partial class XAEThrowThingsAroundComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How close do you have to be to get yeeted?
|
/// How close do you have to be to get yeeted?
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
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 _);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
63
Content.Server/Xenoarchaeology/Artifact/XAE/XAEFoamSystem.cs
Normal file
63
Content.Server/Xenoarchaeology/Artifact/XAE/XAEFoamSystem.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,31 +1,34 @@
|
|||||||
using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
|
||||||
using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
|
|
||||||
using Content.Shared.Popups;
|
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.Player;
|
||||||
using Robust.Shared.Random;
|
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 IRobustRandom _random = default!;
|
||||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
/// <summary> Pre-allocated and re-used collection.</summary>
|
||||||
{
|
private readonly HashSet<EntityUid> _entities = new();
|
||||||
base.Initialize();
|
|
||||||
SubscribeLocalEvent<TelepathicArtifactComponent, ArtifactActivatedEvent>(OnActivate);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
// try to find victims nearby
|
||||||
var victims = _lookup.GetEntitiesInRange(uid, component.Range);
|
_entities.Clear();
|
||||||
foreach (var victimUid in victims)
|
_lookup.GetEntitiesInRange(ent, component.Range, _entities);
|
||||||
|
foreach (var victimUid in _entities)
|
||||||
{
|
{
|
||||||
if (!EntityManager.HasComponent<ActorComponent>(victimUid))
|
if (!HasComp<ActorComponent>(victimUid))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// roll if msg should be usual or drastic
|
// roll if msg should be usual or drastic
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
36
Content.Server/Xenoarchaeology/Artifact/XAT/XATGasSystem.cs
Normal file
36
Content.Server/Xenoarchaeology/Artifact/XAT/XATGasSystem.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
125
Content.Server/Xenoarchaeology/Artifact/XenoArtifactCommands.cs
Normal file
125
Content.Server/Xenoarchaeology/Artifact/XenoArtifactCommands.cs
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
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");
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
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";
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace Content.Server.Xenoarchaeology.Equipment.Components;
|
|
||||||
|
|
||||||
[RegisterComponent]
|
|
||||||
public sealed partial class NodeScannerComponent : Component
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
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
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,519 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
using Content.Server.Body.Systems;
|
using Content.Server.Body.Systems;
|
||||||
using Content.Server.Popups;
|
using Content.Server.Popups;
|
||||||
using Content.Server.Power.Components;
|
|
||||||
using Content.Server.Power.EntitySystems;
|
using Content.Server.Power.EntitySystems;
|
||||||
using Content.Server.Stack;
|
using Content.Server.Stack;
|
||||||
using Content.Server.Storage.Components;
|
using Content.Server.Storage.Components;
|
||||||
using Content.Server.Xenoarchaeology.XenoArtifacts;
|
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.Power;
|
using Content.Shared.Power;
|
||||||
using Content.Shared.Verbs;
|
using Content.Shared.Verbs;
|
||||||
using Content.Shared.Whitelist;
|
using Content.Shared.Whitelist;
|
||||||
using Content.Shared.Xenoarchaeology.Equipment;
|
using Content.Shared.Xenoarchaeology.Equipment;
|
||||||
|
using Content.Shared.Xenoarchaeology.Equipment.Components;
|
||||||
using Robust.Shared.Collections;
|
using Robust.Shared.Collections;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
@@ -22,7 +21,6 @@ public sealed class ArtifactCrusherSystem : SharedArtifactCrusherSystem
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IGameTiming _timing = default!;
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
[Dependency] private readonly ArtifactSystem _artifact = default!;
|
|
||||||
[Dependency] private readonly BodySystem _body = default!;
|
[Dependency] private readonly BodySystem _body = default!;
|
||||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||||
[Dependency] private readonly StackSystem _stack = default!;
|
[Dependency] private readonly StackSystem _stack = default!;
|
||||||
@@ -103,7 +101,6 @@ public sealed class ArtifactCrusherSystem : SharedArtifactCrusherSystem
|
|||||||
{
|
{
|
||||||
ContainerSystem.Insert((stack, null, null, null), crusher.OutputContainer);
|
ContainerSystem.Insert((stack, null, null, null), crusher.OutputContainer);
|
||||||
}
|
}
|
||||||
_artifact.ForceActivateArtifact(contained);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryComp<BodyComponent>(contained, out var body))
|
if (!TryComp<BodyComponent>(contained, out var body))
|
||||||
|
|||||||
@@ -1,63 +1,13 @@
|
|||||||
using Content.Server.Popups;
|
using Content.Shared.Xenoarchaeology.Equipment;
|
||||||
using Content.Server.Xenoarchaeology.Equipment.Components;
|
using Content.Shared.Xenoarchaeology.Equipment.Components;
|
||||||
using Content.Server.Xenoarchaeology.XenoArtifacts;
|
|
||||||
using Content.Shared.Interaction;
|
|
||||||
using Content.Shared.Timing;
|
|
||||||
using Content.Shared.Verbs;
|
|
||||||
|
|
||||||
namespace Content.Server.Xenoarchaeology.Equipment.Systems;
|
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!;
|
protected override void TryOpenUi(Entity<NodeScannerComponent> device, EntityUid actor)
|
||||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
{
|
||||||
SubscribeLocalEvent<NodeScannerComponent, BeforeRangedInteractEvent>(OnBeforeRangedInteract);
|
// no-op
|
||||||
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)
|
|
||||||
{
|
|
||||||
if (TryComp(uid, out UseDelayComponent? useDelay)
|
|
||||||
&& !_useDelay.TryResetDelay((uid, useDelay), true))
|
|
||||||
return;
|
|
||||||
|
|
||||||
_popupSystem.PopupEntity(Loc.GetString("node-scan-popup",
|
|
||||||
("id", $"{artifact.CurrentNodeId}")), target);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
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}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,244 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,299 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
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
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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";
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
|
||||||
|
|
||||||
[RegisterComponent]
|
|
||||||
public sealed partial class RandomInstrumentArtifactComponent : Component
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
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
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
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 _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user