diff --git a/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleBoundUserInterface.cs b/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleBoundUserInterface.cs
new file mode 100644
index 0000000000..53c148d347
--- /dev/null
+++ b/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleBoundUserInterface.cs
@@ -0,0 +1,64 @@
+using Content.Shared.Xenoarchaeology.Equipment;
+using JetBrains.Annotations;
+using Robust.Client.GameObjects;
+
+namespace Content.Client.Xenoarchaeology.Ui;
+
+[UsedImplicitly]
+public sealed class AnalysisConsoleBoundUserInterface : BoundUserInterface
+{
+ private AnalysisConsoleMenu? _consoleMenu;
+
+ public AnalysisConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
+ {
+
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+
+ _consoleMenu = new AnalysisConsoleMenu();
+
+ _consoleMenu.OnClose += Close;
+ _consoleMenu.OpenCentered();
+
+ _consoleMenu.OnServerSelectionButtonPressed += _ =>
+ {
+ SendMessage(new AnalysisConsoleServerSelectionMessage());
+ };
+ _consoleMenu.OnScanButtonPressed += _ =>
+ {
+ SendMessage(new AnalysisConsoleScanButtonPressedMessage());
+ };
+ _consoleMenu.OnDestroyButtonPressed += _ =>
+ {
+ SendMessage(new AnalysisConsoleDestroyButtonPressedMessage());
+ };
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(state);
+
+ switch (state)
+ {
+ case AnalysisConsoleScanUpdateState msg:
+ _consoleMenu?.SetDestroyButtonDisabled(msg);
+ _consoleMenu?.SetScanButtonDisabled(msg);
+ _consoleMenu?.UpdateInformationDisplay(msg);
+ _consoleMenu?.UpdateProgressBar(msg);
+ break;
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (!disposing)
+ return;
+ _consoleMenu?.Dispose();
+ }
+}
+
diff --git a/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml b/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml
new file mode 100644
index 0000000000..fc705306aa
--- /dev/null
+++ b/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml.cs b/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml.cs
new file mode 100644
index 0000000000..24c0542f1a
--- /dev/null
+++ b/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml.cs
@@ -0,0 +1,157 @@
+using Content.Client.Stylesheets;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Xenoarchaeology.Equipment;
+using Content.Shared.Xenoarchaeology.XenoArtifacts;
+using Robust.Client.AutoGenerated;
+using Robust.Client.GameObjects;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Xenoarchaeology.Ui;
+
+[GenerateTypedNameReferences]
+public sealed partial class AnalysisConsoleMenu : FancyWindow
+{
+ [Dependency] private readonly IEntityManager _ent = default!;
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+
+ public event Action? OnServerSelectionButtonPressed;
+ public event Action? OnScanButtonPressed;
+ public event Action? OnDestroyButtonPressed;
+
+ public AnalysisConsoleMenu()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ ServerSelectionButton.OnPressed += a => OnServerSelectionButtonPressed?.Invoke(a);
+ ScanButton.OnPressed += a => OnScanButtonPressed?.Invoke(a);
+ DestroyButton.OnPressed += a => OnDestroyButtonPressed?.Invoke(a);
+ }
+
+ public void SetScanButtonDisabled(AnalysisConsoleScanUpdateState state)
+ {
+ var disabled = !state.CanScan;
+
+ ScanButton.Disabled = disabled;
+ }
+
+ public void SetDestroyButtonDisabled(AnalysisConsoleScanUpdateState state)
+ {
+ var disabled = !state.ServerConnected || !state.CanScan;
+
+ DestroyButton.Disabled = disabled;
+
+ if (disabled)
+ {
+ DestroyButton.RemoveStyleClass(StyleBase.ButtonCaution);
+ }
+ else
+ {
+ DestroyButton.AddStyleClass(StyleBase.ButtonCaution);
+ }
+ }
+
+ public void UpdateArtifactIcon(EntityUid? uid)
+ {
+ if (uid == null)
+ {
+ ArtifactDisplay.Visible = false;
+ return;
+ }
+ ArtifactDisplay.Visible = true;
+
+ if (!_ent.TryGetComponent(uid, out var sprite))
+ return;
+
+ ArtifactDisplay.Sprite = sprite;
+ }
+
+ public void UpdateInformationDisplay(AnalysisConsoleScanUpdateState state)
+ {
+ var message = new FormattedMessage();
+
+ if (state.Scanning)
+ {
+ message.AddMarkup(Loc.GetString("analysis-console-info-scanner"));
+ Information.SetMessage(message);
+ return;
+ }
+
+ //do this here
+ UpdateArtifactIcon(state.Artifact);
+
+ if (state.Artifact == null)//no scan present
+ {
+ if (!state.AnalyzerConnected) //no analyzer connected
+ message.AddMarkup(Loc.GetString("analysis-console-info-no-scanner"));
+ else if (!state.CanScan) //no artifact
+ message.AddMarkup(Loc.GetString("analysis-console-info-no-artifact"));
+ else if (state.Artifact == null) //ready to go
+ message.AddMarkup(Loc.GetString("analysis-console-info-ready"));
+ }
+
+ if (state.Id != null) //node id
+ message.AddMarkup(Loc.GetString("analysis-console-info-id", ("id", state.Id))+"\n");
+ if (state.Depth != null) //node depth
+ message.AddMarkup(Loc.GetString("analysis-console-info-depth", ("depth", state.Depth))+"\n");
+
+ if (state.Triggered != null) //whether it has been triggered
+ {
+ var activated = state.Triggered.Value
+ ? "analysis-console-info-triggered-true"
+ : "analysis-console-info-triggered-false";
+ message.AddMarkup(Loc.GetString(activated)+"\n");
+ }
+
+ message.AddMarkup("\n");
+ var needSecondNewline = false;
+
+ if (state.TriggerProto != null && //possible triggers
+ _proto.TryIndex(state.TriggerProto, out var trigger) &&
+ trigger.TriggerHint != null)
+ {
+ message.AddMarkup(Loc.GetString("analysis-console-info-trigger",
+ ("trigger", Loc.GetString(trigger.TriggerHint))) + "\n");
+ needSecondNewline = true;
+ }
+
+ if (state.EffectProto != null && //possible effects
+ _proto.TryIndex(state.EffectProto, out var effect) &&
+ effect.EffectHint != null)
+ {
+ message.AddMarkup(Loc.GetString("analysis-console-info-effect",
+ ("effect", Loc.GetString(effect.EffectHint))) + "\n");
+ needSecondNewline = true;
+ }
+
+ if (needSecondNewline)
+ message.AddMarkup("\n");
+
+ if (state.Edges != null) //number of edges
+ message.AddMarkup(Loc.GetString("analysis-console-info-edges", ("edges", state.Edges))+"\n");
+ if (state.Completion != null) //completion percentage
+ {
+ message.AddMarkup(Loc.GetString("analysis-console-info-completion",
+ ("percentage", Math.Round(state.Completion.Value * 100)))+"\n");
+ }
+
+ Information.SetMessage(message);
+ }
+
+ public void UpdateProgressBar(AnalysisConsoleScanUpdateState state)
+ {
+ ProgressBar.Visible = state.Scanning;
+ ProgressLabel.Visible = state.Scanning;
+
+ if (!state.Scanning)
+ return;
+
+ ProgressLabel.Text = Loc.GetString("analysis-console-progress-text",
+ ("seconds", (int) state.TotalTime.TotalSeconds - (int) state.TimeRemaining.TotalSeconds));
+ ProgressBar.Value = (float) state.TimeRemaining.Divide(state.TotalTime);
+ }
+}
+
diff --git a/Content.IntegrationTests/Tests/PrototypeSaveTest.cs b/Content.IntegrationTests/Tests/PrototypeSaveTest.cs
index 2ef6f53aa8..64e8441fdf 100644
--- a/Content.IntegrationTests/Tests/PrototypeSaveTest.cs
+++ b/Content.IntegrationTests/Tests/PrototypeSaveTest.cs
@@ -39,7 +39,6 @@ public sealed class PrototypeSaveTest
// The rest of these prototypes (probably) shouldn't be getting ignored.
// There should be an issue up tracking all of these prototypes, indicating that still need to get fixed.
"HeadSkeleton",
- "CrateArtifactContainer",
// The followjng are all fixture-less phsyics entities that set can-collide to false on init.
"CarpRift",
"GasMinerOxygen",
diff --git a/Content.Server/Chemistry/ReactionEffects/FoamAreaReactionEffect.cs b/Content.Server/Chemistry/ReactionEffects/FoamAreaReactionEffect.cs
index 83004eb4dc..a1a1243d0e 100644
--- a/Content.Server/Chemistry/ReactionEffects/FoamAreaReactionEffect.cs
+++ b/Content.Server/Chemistry/ReactionEffects/FoamAreaReactionEffect.cs
@@ -3,6 +3,7 @@ using Content.Server.Coordinates.Helpers;
using Content.Shared.Audio;
using Content.Shared.Chemistry.Components;
using JetBrains.Annotations;
+using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Map;
using Robust.Shared.Player;
@@ -19,7 +20,7 @@ namespace Content.Server.Chemistry.ReactionEffects
}
public static void SpawnFoam(string entityPrototype, EntityCoordinates coords, Solution? contents, int amount, float duration, float spreadDelay,
- float removeDelay, SoundSpecifier sound, IEntityManager? entityManager = null)
+ float removeDelay, SoundSpecifier? sound = null, IEntityManager? entityManager = null)
{
entityManager ??= IoCManager.Resolve();
var ent = entityManager.SpawnEntity(entityPrototype, coords.SnapToGrid());
@@ -37,7 +38,8 @@ namespace Content.Server.Chemistry.ReactionEffects
areaEffectComponent.TryAddSolution(contents);
areaEffectComponent.Start(amount, duration, spreadDelay, removeDelay);
- SoundSystem.Play(sound.GetSound(), Filter.Pvs(ent), ent, AudioHelpers.WithVariation(0.125f));
+ entityManager.EntitySysManager.GetEntitySystem()
+ .PlayPvs(sound, ent, AudioParams.Default.WithVariation(0.125f));
}
}
}
diff --git a/Content.Server/Chemistry/ReagentEffects/ActivateArtifact.cs b/Content.Server/Chemistry/ReagentEffects/ActivateArtifact.cs
new file mode 100644
index 0000000000..04ae4ca6f5
--- /dev/null
+++ b/Content.Server/Chemistry/ReagentEffects/ActivateArtifact.cs
@@ -0,0 +1,13 @@
+using Content.Server.Xenoarchaeology.XenoArtifacts;
+using Content.Shared.Chemistry.Reagent;
+
+namespace Content.Server.Chemistry.ReagentEffects;
+
+public sealed class ActivateArtifact : ReagentEffect
+{
+ public override void Effect(ReagentEffectArgs args)
+ {
+ var artifact = args.EntityManager.EntitySysManager.GetEntitySystem();
+ artifact.TryActivateArtifact(args.SolutionEntity);
+ }
+}
diff --git a/Content.Server/Salvage/SalvageMagnetComponent.cs b/Content.Server/Salvage/SalvageMagnetComponent.cs
index 96b338f2f3..6ec6339467 100644
--- a/Content.Server/Salvage/SalvageMagnetComponent.cs
+++ b/Content.Server/Salvage/SalvageMagnetComponent.cs
@@ -54,6 +54,16 @@ namespace Content.Server.Salvage
{
public static readonly MagnetState Inactive = new (MagnetStateType.Inactive, TimeSpan.Zero);
};
+
+ public sealed class SalvageMagnetActivatedEvent : EntityEventArgs
+ {
+ public EntityUid Magnet;
+
+ public SalvageMagnetActivatedEvent(EntityUid magnet)
+ {
+ Magnet = magnet;
+ }
+ }
public enum MagnetStateType
{
Inactive,
diff --git a/Content.Server/Salvage/SalvageSystem.cs b/Content.Server/Salvage/SalvageSystem.cs
index d03e512525..54b2eb693f 100644
--- a/Content.Server/Salvage/SalvageSystem.cs
+++ b/Content.Server/Salvage/SalvageSystem.cs
@@ -169,6 +169,7 @@ namespace Content.Server.Salvage
}
gridState.ActiveMagnets.Add(component);
component.MagnetState = new MagnetState(MagnetStateType.Attaching, gridState.CurrentTime + AttachingTime);
+ RaiseLocalEvent(new SalvageMagnetActivatedEvent(component.Owner));
Report(component.Owner, component.SalvageChannel, "salvage-system-report-activate-success");
break;
case MagnetStateType.Attaching:
diff --git a/Content.Server/StationEvents/Events/BluespaceArtifact.cs b/Content.Server/StationEvents/Events/BluespaceArtifact.cs
new file mode 100644
index 0000000000..fe203a9d53
--- /dev/null
+++ b/Content.Server/StationEvents/Events/BluespaceArtifact.cs
@@ -0,0 +1,46 @@
+using Robust.Shared.Random;
+
+namespace Content.Server.StationEvents.Events;
+
+public sealed class BluespaceArtifact : StationEventSystem
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+
+ public override string Prototype => "BluespaceArtifact";
+
+ public readonly string ArtifactSpawnerPrototype = "RandomArtifactSpawner";
+ public readonly string ArtifactFlashPrototype = "EffectFlashBluespace";
+
+ public readonly List PossibleSighting = new()
+ {
+ "bluespace-artifact-sighting-1",
+ "bluespace-artifact-sighting-2",
+ "bluespace-artifact-sighting-3",
+ "bluespace-artifact-sighting-4",
+ "bluespace-artifact-sighting-5",
+ "bluespace-artifact-sighting-6",
+ "bluespace-artifact-sighting-7"
+ };
+
+ public override void Added()
+ {
+ base.Added();
+
+ var str = Loc.GetString("bluespace-artifact-event-announcement",
+ ("sighting", Loc.GetString(_random.Pick(PossibleSighting))));
+ ChatSystem.DispatchGlobalAnnouncement(str, colorOverride: Color.FromHex("#18abf5"));
+ }
+
+ public override void Started()
+ {
+ base.Started();
+
+ if (!TryFindRandomTile(out _, out _, out _, out var coords))
+ return;
+
+ EntityManager.SpawnEntity(ArtifactSpawnerPrototype, coords);
+ EntityManager.SpawnEntity(ArtifactFlashPrototype, coords);
+
+ Sawmill.Info($"Spawning random artifact at {coords}");
+ }
+}
diff --git a/Content.Server/Xenoarchaeology/Equipment/Components/ActiveArtifactAnalyzerComponent.cs b/Content.Server/Xenoarchaeology/Equipment/Components/ActiveArtifactAnalyzerComponent.cs
new file mode 100644
index 0000000000..f4b089636b
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/Equipment/Components/ActiveArtifactAnalyzerComponent.cs
@@ -0,0 +1,24 @@
+using Robust.Shared.Audio;
+using Robust.Shared.Serialization.TypeSerializers.Implementations;
+
+namespace Content.Server.Xenoarchaeology.Equipment.Components;
+
+///
+/// Activecomp used for tracking artifact analyzers that are currently
+/// in the process of scanning an artifact.
+///
+[RegisterComponent]
+public sealed class ActiveArtifactAnalyzerComponent : Component
+{
+ ///
+ /// When did the scanning start?
+ ///
+ [DataField("startTime", customTypeSerializer: typeof(TimespanSerializer))]
+ public TimeSpan StartTime;
+
+ ///
+ /// What is being scanned?
+ ///
+ [ViewVariables]
+ public EntityUid Artifact;
+}
diff --git a/Content.Server/Xenoarchaeology/Equipment/Components/ActiveScannedArtifactComponent.cs b/Content.Server/Xenoarchaeology/Equipment/Components/ActiveScannedArtifactComponent.cs
new file mode 100644
index 0000000000..3475e2ab03
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/Equipment/Components/ActiveScannedArtifactComponent.cs
@@ -0,0 +1,22 @@
+using Robust.Shared.Audio;
+
+namespace Content.Server.Xenoarchaeology.Equipment.Components;
+
+///
+/// This is used for tracking artifacts that are currently
+/// being scanned by
+///
+[RegisterComponent]
+public sealed class ActiveScannedArtifactComponent : Component
+{
+ ///
+ /// The scanner that is scanning this artifact
+ ///
+ [ViewVariables]
+ public EntityUid Scanner;
+
+ ///
+ /// The sound that plays when the scan fails
+ ///
+ public readonly SoundSpecifier ScanFailureSound = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg");
+}
diff --git a/Content.Server/Xenoarchaeology/Equipment/Components/AnalysisConsoleComponent.cs b/Content.Server/Xenoarchaeology/Equipment/Components/AnalysisConsoleComponent.cs
new file mode 100644
index 0000000000..13e3b615ab
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/Equipment/Components/AnalysisConsoleComponent.cs
@@ -0,0 +1,31 @@
+using Content.Shared.MachineLinking;
+using Robust.Shared.Audio;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+namespace Content.Server.Xenoarchaeology.Equipment.Components;
+
+///
+/// The console that is used for artifact analysis
+///
+[RegisterComponent]
+public sealed class AnalysisConsoleComponent : Component
+{
+ ///
+ /// The analyzer entity the console is linked.
+ /// Can be null if not linked.
+ ///
+ [ViewVariables(VVAccess.ReadWrite)]
+ public EntityUid? AnalyzerEntity;
+
+ ///
+ /// The machine linking port for the analyzer
+ ///
+ [DataField("linkingPort", customTypeSerializer: typeof(PrototypeIdSerializer))]
+ public readonly string LinkingPort = "ArtifactAnalyzerSender";
+
+ ///
+ /// The sound played when an artifact is destroyed.
+ ///
+ [DataField("destroySound")]
+ public SoundSpecifier DestroySound = new SoundPathSpecifier("/Audio/Effects/radpulse11.ogg");
+}
diff --git a/Content.Server/Xenoarchaeology/Equipment/Components/ArtifactAnalyzerComponent.cs b/Content.Server/Xenoarchaeology/Equipment/Components/ArtifactAnalyzerComponent.cs
new file mode 100644
index 0000000000..19d56c0482
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/Equipment/Components/ArtifactAnalyzerComponent.cs
@@ -0,0 +1,67 @@
+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;
+
+///
+/// A machine that is combined and linked to the
+/// in order to analyze and destroy artifacts.
+///
+[RegisterComponent]
+public sealed class ArtifactAnalyzerComponent : Component
+{
+ ///
+ /// How long it takes to analyze an artifact
+ ///
+ [DataField("analysisDuration", customTypeSerializer: typeof(TimespanSerializer))]
+ public TimeSpan AnalysisDuration = TimeSpan.FromSeconds(60);
+
+ ///
+ /// A mulitplier on the duration of analysis.
+ /// Used for machine upgrading.
+ ///
+ [ViewVariables(VVAccess.ReadWrite)]
+ public float AnalysisDurationMulitplier = 1;
+
+ ///
+ /// The machine part that modifies analysis duration.
+ ///
+ [DataField("machinePartAnalysisDuration", customTypeSerializer: typeof(PrototypeIdSerializer))]
+ public string MachinePartAnalysisDuration = "ScanningModule";
+
+ ///
+ /// The modifier raised to the part rating to determine the duration multiplier.
+ ///
+ [DataField("partRatingAnalysisDurationMultiplier")]
+ public float PartRatingAnalysisDurationMultiplier = 0.75f;
+
+ ///
+ /// The corresponding console entity.
+ /// Can be null if not linked.
+ ///
+ [ViewVariables]
+ public EntityUid? Console;
+
+ ///
+ /// All of the valid artifacts currently touching the analyzer.
+ ///
+ [ViewVariables]
+ public HashSet Contacts = new();
+
+ [DataField("scanFinishedSound")]
+ public readonly SoundSpecifier ScanFinishedSound = new SoundPathSpecifier("/Audio/Machines/scan_finish.ogg");
+
+ #region Analysis Data
+ [ViewVariables]
+ public EntityUid? LastAnalyzedArtifact;
+
+ [ViewVariables]
+ public ArtifactNode? LastAnalyzedNode;
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ public float? LastAnalyzedCompletion;
+ #endregion
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Equipment/Components/SuppressArtifactContainerComponent.cs b/Content.Server/Xenoarchaeology/Equipment/Components/SuppressArtifactContainerComponent.cs
similarity index 71%
rename from Content.Server/Xenoarchaeology/XenoArtifacts/Equipment/Components/SuppressArtifactContainerComponent.cs
rename to Content.Server/Xenoarchaeology/Equipment/Components/SuppressArtifactContainerComponent.cs
index fddcf44438..7f210f6efd 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Equipment/Components/SuppressArtifactContainerComponent.cs
+++ b/Content.Server/Xenoarchaeology/Equipment/Components/SuppressArtifactContainerComponent.cs
@@ -1,4 +1,4 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Equipment.Components;
+namespace Content.Server.Xenoarchaeology.Equipment.Components;
///
/// Suppress artifact activation, when entity is placed inside this container.
diff --git a/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs
new file mode 100644
index 0000000000..af9092277c
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs
@@ -0,0 +1,413 @@
+using System.Linq;
+using Content.Server.Construction;
+using Content.Server.MachineLinking.Events;
+using Content.Server.Power.Components;
+using Content.Server.Research;
+using Content.Server.Research.Components;
+using Content.Server.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.MachineLinking.Events;
+using Content.Shared.Popups;
+using Content.Shared.Research.Components;
+using Content.Shared.Xenoarchaeology.Equipment;
+using JetBrains.Annotations;
+using Robust.Server.GameObjects;
+using Robust.Server.Player;
+using Robust.Shared.Audio;
+using Robust.Shared.Physics.Events;
+using Robust.Shared.Player;
+using Robust.Shared.Timing;
+using Robust.Shared.Utility;
+
+namespace Content.Server.Xenoarchaeology.Equipment.Systems;
+
+///
+/// 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.
+///
+public sealed class ArtifactAnalyzerSystem : EntitySystem
+{
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly UserInterfaceSystem _ui = default!;
+ [Dependency] private readonly ArtifactSystem _artifact = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnScannedMoved);
+ SubscribeLocalEvent(OnArtifactActivated);
+
+ SubscribeLocalEvent(OnAnalyzeStart);
+ SubscribeLocalEvent(OnAnalyzeEnd);
+ SubscribeLocalEvent(OnPowerChanged);
+
+ SubscribeLocalEvent(OnUpgradeExamine);
+ SubscribeLocalEvent(OnRefreshParts);
+ SubscribeLocalEvent(OnCollide);
+ SubscribeLocalEvent(OnEndCollide);
+
+ SubscribeLocalEvent(OnNewLink);
+ SubscribeLocalEvent(OnPortDisconnected);
+
+ SubscribeLocalEvent(OnServerSelectionMessage);
+ SubscribeLocalEvent(OnScanButton);
+ SubscribeLocalEvent(OnDestroyButton);
+
+ SubscribeLocalEvent((e,c,_) => UpdateUserInterface(e,c),
+ after: new []{typeof(ResearchSystem)});
+ SubscribeLocalEvent((e,c,_) => UpdateUserInterface(e,c),
+ after: new []{typeof(ResearchSystem)});
+ SubscribeLocalEvent((e,c,_) => UpdateUserInterface(e,c));
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ foreach (var (active, scan) in EntityQuery())
+ {
+ if (scan.Console != null)
+ UpdateUserInterface(scan.Console.Value);
+
+ if (_timing.CurTime - active.StartTime < (scan.AnalysisDuration * scan.AnalysisDurationMulitplier))
+ continue;
+
+ FinishScan(scan.Owner, scan, active);
+ }
+ }
+
+ ///
+ /// Resets the current scan on the artifact analyzer
+ ///
+ /// The analyzer being reset
+ ///
+ [PublicAPI]
+ public void ResetAnalyzer(EntityUid uid, ArtifactAnalyzerComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return;
+
+ component.LastAnalyzedArtifact = null;
+ UpdateAnalyzerInformation(uid, component);
+ }
+
+ ///
+ /// Goes through the current contacts on
+ /// the analyzer and returns a valid artifact
+ ///
+ ///
+ ///
+ ///
+ private EntityUid? GetArtifactForAnalysis(EntityUid? uid, ArtifactAnalyzerComponent? component = null)
+ {
+ if (uid == null)
+ return null;
+
+ if (!Resolve(uid.Value, ref component))
+ return null;
+
+ var validEnts = component.Contacts.Where(HasComp).ToHashSet();
+ return validEnts.FirstOrNull();
+ }
+
+ ///
+ /// Updates the current scan information based on
+ /// the last artifact that was scanned.
+ ///
+ ///
+ ///
+ private void UpdateAnalyzerInformation(EntityUid uid, ArtifactAnalyzerComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return;
+
+ if (component.LastAnalyzedArtifact == null)
+ {
+ component.LastAnalyzedCompletion = null;
+ component.LastAnalyzedNode = null;
+ }
+ else if (TryComp(component.LastAnalyzedArtifact, out var artifact))
+ {
+ var lastNode = (ArtifactNode?) artifact.CurrentNode?.Clone();
+ component.LastAnalyzedNode = lastNode;
+
+ if (artifact.NodeTree != null)
+ {
+ var discoveredNodes = artifact.NodeTree.AllNodes.Count(x => x.Discovered && x.Triggered);
+ component.LastAnalyzedCompletion = (float) discoveredNodes / artifact.NodeTree.AllNodes.Count;
+ }
+ }
+ }
+
+ private void OnNewLink(EntityUid uid, AnalysisConsoleComponent component, NewLinkEvent args)
+ {
+ if (!TryComp(args.Receiver, out var analyzer))
+ return;
+
+ component.AnalyzerEntity = args.Receiver;
+ 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(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))
+ return;
+
+ EntityUid? artifact = null;
+ ArtifactNode? node = null;
+ float? completion = null;
+ var totalTime = TimeSpan.Zero;
+ var canScan = false;
+ if (component.AnalyzerEntity != null && TryComp(component.AnalyzerEntity, out var analyzer))
+ {
+ artifact = analyzer.LastAnalyzedArtifact;
+ node = analyzer.LastAnalyzedNode;
+ completion = analyzer.LastAnalyzedCompletion;
+ totalTime = analyzer.AnalysisDuration * analyzer.AnalysisDurationMulitplier;
+ canScan = analyzer.Contacts.Any();
+ }
+
+ var analyzerConnected = component.AnalyzerEntity != null;
+ var serverConnected = TryComp(uid, out var client) && client.ConnectedToServer;
+
+ var scanning = TryComp(component.AnalyzerEntity, out var active);
+ var remaining = active != null ? _timing.CurTime - active.StartTime : TimeSpan.Zero;
+
+ var state = new AnalysisConsoleScanUpdateState(artifact, analyzerConnected, serverConnected, canScan,
+ node?.Id, node?.Depth, node?.Edges.Count, node?.Triggered, node?.Effect.ID, node?.Trigger.ID, completion,
+ scanning, remaining, totalTime);
+
+ var bui = _ui.GetUi(uid, ArtifactAnalzyerUiKey.Key);
+ _ui.SetUiState(bui, state);
+ }
+
+ ///
+ /// opens the server selection menu.
+ ///
+ ///
+ ///
+ ///
+ private void OnServerSelectionMessage(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleServerSelectionMessage args)
+ {
+ _ui.TryOpen(uid, ResearchClientUiKey.Key, (IPlayerSession) args.Session);
+ }
+
+ ///
+ /// Starts scanning the artifact.
+ ///
+ ///
+ ///
+ ///
+ private void OnScanButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleScanButtonPressedMessage args)
+ {
+ if (component.AnalyzerEntity == null)
+ return;
+
+ if (HasComp(component.AnalyzerEntity))
+ return;
+
+ var ent = GetArtifactForAnalysis(component.AnalyzerEntity);
+ if (ent == null)
+ return;
+
+ var activeComp = EnsureComp(component.AnalyzerEntity.Value);
+ activeComp.StartTime = _timing.CurTime;
+ activeComp.Artifact = ent.Value;
+
+ var activeArtifact = EnsureComp(ent.Value);
+ activeArtifact.Scanner = component.AnalyzerEntity.Value;
+ }
+
+ ///
+ /// destroys the artifact and updates the server points
+ ///
+ ///
+ ///
+ ///
+ private void OnDestroyButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleDestroyButtonPressedMessage args)
+ {
+ if (!TryComp(uid, out var client) || client.Server == null || component.AnalyzerEntity == null)
+ return;
+
+ var entToDestroy = GetArtifactForAnalysis(component.AnalyzerEntity);
+ if (entToDestroy == null)
+ return;
+
+ if (TryComp(component.AnalyzerEntity.Value, out var analyzer) &&
+ analyzer.LastAnalyzedArtifact == entToDestroy)
+ {
+ ResetAnalyzer(component.AnalyzerEntity.Value);
+ }
+
+ client.Server.Points += _artifact.GetResearchPointValue(entToDestroy.Value);
+ EntityManager.DeleteEntity(entToDestroy.Value);
+
+ _audio.PlayPvs(component.DestroySound, component.AnalyzerEntity.Value, AudioParams.Default.WithVolume(2f));
+
+ _popup.PopupEntity(Loc.GetString("analyzer-artifact-destroy-popup"),
+ component.AnalyzerEntity.Value, Filter.Pvs(component.AnalyzerEntity.Value), PopupType.Large);
+
+ UpdateUserInterface(uid, component);
+ }
+
+ ///
+ /// Cancels scans if the artifact changes nodes (is activated) during the scan.
+ ///
+ ///
+ ///
+ ///
+ private void OnArtifactActivated(EntityUid uid, ActiveScannedArtifactComponent component, ArtifactActivatedEvent args)
+ {
+ CancelScan(uid);
+ }
+
+ ///
+ /// Checks to make sure that the currently scanned artifact isn't moved off of the scanner
+ ///
+ ///
+ ///
+ ///
+ private void OnScannedMoved(EntityUid uid, ActiveScannedArtifactComponent component, ref MoveEvent args)
+ {
+ if (!TryComp(component.Scanner, out var analyzer))
+ return;
+
+ if (analyzer.Contacts.Contains(uid))
+ return;
+
+ CancelScan(uid, component, analyzer);
+ }
+
+ ///
+ /// Stops the current scan
+ ///
+ /// The artifact being scanned
+ ///
+ /// The artifact analyzer component
+ [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(component.Scanner);
+ if (analyzer.Console != null)
+ UpdateUserInterface(analyzer.Console.Value);
+
+ RemCompDeferred(artifact, component);
+ }
+
+ ///
+ /// Finishes the current scan.
+ ///
+ /// The analyzer that is scanning
+ ///
+ ///
+ [PublicAPI]
+ public void FinishScan(EntityUid uid, ArtifactAnalyzerComponent? component = null, ActiveArtifactAnalyzerComponent? active = null)
+ {
+ if (!Resolve(uid, ref component, ref active))
+ return;
+
+ _audio.PlayPvs(component.ScanFinishedSound, uid);
+ component.LastAnalyzedArtifact = active.Artifact;
+ UpdateAnalyzerInformation(uid, component);
+
+ RemComp(active.Artifact);
+ RemComp(uid, active);
+ if (component.Console != null)
+ UpdateUserInterface(component.Console.Value);
+ }
+
+ private void OnRefreshParts(EntityUid uid, ArtifactAnalyzerComponent component, RefreshPartsEvent args)
+ {
+ var analysisRating = args.PartRatings[component.MachinePartAnalysisDuration];
+
+ component.AnalysisDurationMulitplier = MathF.Pow(component.PartRatingAnalysisDurationMultiplier, analysisRating - 1);
+ }
+
+ private void OnUpgradeExamine(EntityUid uid, ArtifactAnalyzerComponent component, UpgradeExamineEvent args)
+ {
+ args.AddPercentageUpgrade("analyzer-artifact-component-upgrade-analysis", component.AnalysisDurationMulitplier);
+ }
+
+ private void OnCollide(EntityUid uid, ArtifactAnalyzerComponent component, ref StartCollideEvent args)
+ {
+ var otherEnt = args.OtherFixture.Body.Owner;
+
+ if (!HasComp(otherEnt))
+ return;
+
+ component.Contacts.Add(otherEnt);
+
+ if (component.Console != null)
+ UpdateUserInterface(component.Console.Value);
+ }
+
+ private void OnEndCollide(EntityUid uid, ArtifactAnalyzerComponent component, ref EndCollideEvent args)
+ {
+ var otherEnt = args.OtherFixture.Body.Owner;
+
+ if (!HasComp(otherEnt))
+ return;
+ component.Contacts.Remove(otherEnt);
+
+ if (component.Console != null)
+ UpdateUserInterface(component.Console.Value);
+ }
+
+ private void OnAnalyzeStart(EntityUid uid, ActiveArtifactAnalyzerComponent component, ComponentStartup args)
+ {
+ if (TryComp(uid, out var powa))
+ powa.NeedsPower = true;
+
+ if (TryComp(uid, out var ambientSound))
+ {
+ ambientSound.Enabled = true;
+ Dirty(ambientSound);
+ }
+ }
+
+ private void OnAnalyzeEnd(EntityUid uid, ActiveArtifactAnalyzerComponent component, ComponentShutdown args)
+ {
+ if (TryComp(uid, out var powa))
+ powa.NeedsPower = false;
+
+ if (TryComp(uid, out var ambientSound))
+ {
+ ambientSound.Enabled = false;
+ Dirty(ambientSound);
+ }
+ }
+
+ private void OnPowerChanged(EntityUid uid, ActiveArtifactAnalyzerComponent component, ref PowerChangedEvent args)
+ {
+ if (!args.Powered)
+ CancelScan(component.Artifact);
+ }
+}
+
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Equipment/Systems/SuppressArtifactContainerSystem.cs b/Content.Server/Xenoarchaeology/Equipment/Systems/SuppressArtifactContainerSystem.cs
similarity index 58%
rename from Content.Server/Xenoarchaeology/XenoArtifacts/Equipment/Systems/SuppressArtifactContainerSystem.cs
rename to Content.Server/Xenoarchaeology/Equipment/Systems/SuppressArtifactContainerSystem.cs
index f60f86801c..16aea2a3b3 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Equipment/Systems/SuppressArtifactContainerSystem.cs
+++ b/Content.Server/Xenoarchaeology/Equipment/Systems/SuppressArtifactContainerSystem.cs
@@ -1,16 +1,11 @@
-using Content.Server.Cargo.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Equipment.Components;
+using Content.Server.Xenoarchaeology.Equipment.Components;
+using Content.Server.Xenoarchaeology.XenoArtifacts;
using Robust.Shared.Containers;
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Equipment.Systems;
+namespace Content.Server.Xenoarchaeology.Equipment.Systems;
public sealed class SuppressArtifactContainerSystem : EntitySystem
{
- ///
- /// Artifacts go from 2k to 4k, 1.5k net profit (considering the container price
- ///
- public const double ContainedArtifactModifier = 2;
-
public override void Initialize()
{
base.Initialize();
@@ -24,11 +19,6 @@ public sealed class SuppressArtifactContainerSystem : EntitySystem
return;
artifact.IsSuppressed = true;
-
- if (TryComp(args.Entity, out var price))
- {
- price.Price *= ContainedArtifactModifier;
- }
}
private void OnRemoved(EntityUid uid, SuppressArtifactContainerComponent component, EntRemovedFromContainerMessage args)
@@ -37,10 +27,5 @@ public sealed class SuppressArtifactContainerSystem : EntitySystem
return;
artifact.IsSuppressed = false;
-
- if (TryComp(args.Entity, out var price))
- {
- price.Price /= ContainedArtifactModifier;
- }
}
}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactComponent.cs
index 17e23c4e81..46b9e067de 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactComponent.cs
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactComponent.cs
@@ -1,39 +1,143 @@
+using Content.Shared.Xenoarchaeology.XenoArtifacts;
+using Robust.Shared.Serialization.TypeSerializers.Implementations;
+
namespace Content.Server.Xenoarchaeology.XenoArtifacts;
[RegisterComponent]
public sealed class ArtifactComponent : Component
{
///
- /// Should artifact pick a random trigger on startup?
+ /// The artifact's node tree.
///
- [DataField("randomTrigger")]
- public bool RandomTrigger = true;
+ [ViewVariables]
+ public ArtifactTree? NodeTree;
///
- /// List of all possible triggers activations.
- /// Should be same as components names.
+ /// The current node the artifact is on.
///
- [DataField("possibleTriggers")]
- public string[] PossibleTriggers = {
- "ArtifactInteractionTrigger",
- "ArtifactGasTrigger",
- "ArtifactHeatTrigger",
- "ArtifactElectricityTrigger",
- };
+ [ViewVariables]
+ public ArtifactNode? CurrentNode;
+
+ #region Node Tree Gen
+ ///
+ /// Minimum number of nodes to generate, inclusive
+ ///
+ [DataField("nodesMin")]
+ public int NodesMin = 3;
///
- /// Cooldown time between artifact activations (in seconds).
+ /// Maximum number of nodes to generate, exclusive
///
- [DataField("timer")]
+ [DataField("nodesMax")]
+ public int NodesMax = 9;
+ #endregion
+
+ ///
+ /// Cooldown time between artifact activations (in seconds).
+ ///
+ [DataField("timer", customTypeSerializer: typeof(TimespanSerializer))]
[ViewVariables(VVAccess.ReadWrite)]
- public double CooldownTime = 10;
+ public TimeSpan CooldownTime = TimeSpan.FromSeconds(5);
///
- /// Is this artifact under some suppression device?
- /// If true, will ignore all trigger activations attempts.
+ /// Is this artifact under some suppression device?
+ /// f true, will ignore all trigger activations attempts.
///
[ViewVariables(VVAccess.ReadWrite)]
public bool IsSuppressed;
+ ///
+ /// The last time the artifact was activated.
+ ///
+ [DataField("lastActivationTime", customTypeSerializer: typeof(TimespanSerializer))]
public TimeSpan LastActivationTime;
}
+
+///
+/// A tree of nodes.
+///
+[DataDefinition]
+public sealed class ArtifactTree
+{
+ ///
+ /// The first node of the tree
+ ///
+ [ViewVariables]
+ public ArtifactNode StartNode = default!;
+
+ ///
+ /// Every node contained in the tree
+ ///
+ [ViewVariables]
+ public readonly List AllNodes = new();
+}
+
+///
+/// A single "node" of an artifact that contains various data about it.
+///
+[DataDefinition]
+public sealed class ArtifactNode : ICloneable
+{
+ ///
+ /// A numeric id corresponding to each node. used for display purposes
+ ///
+ [ViewVariables]
+ public int Id;
+
+ ///
+ /// how "deep" into the node tree. used for generation and price/value calculations
+ ///
+ [ViewVariables]
+ public int Depth = 0;
+
+ ///
+ /// A list of surrounding nodes. Used for tree traversal
+ ///
+ [ViewVariables]
+ public List Edges = new();
+
+ ///
+ /// Whether or not the node has been entered
+ ///
+ [ViewVariables(VVAccess.ReadWrite)]
+ public bool Discovered = false;
+
+ ///
+ /// The trigger for the node
+ ///
+ [ViewVariables]
+ public ArtifactTriggerPrototype Trigger = default!;
+
+ ///
+ /// Whether or not the node has been triggered
+ ///
+ [ViewVariables(VVAccess.ReadWrite)]
+ public bool Triggered = false;
+
+ ///
+ /// The effect when the node is activated
+ ///
+ [ViewVariables]
+ public ArtifactEffectPrototype Effect = default!;
+
+ ///
+ /// Used for storing cumulative information about nodes
+ ///
+ [ViewVariables]
+ public Dictionary NodeData = new();
+
+ public object Clone()
+ {
+ return new ArtifactNode
+ {
+ Id = Id,
+ Depth = Depth,
+ Edges = Edges,
+ Discovered = Discovered,
+ Trigger = Trigger,
+ Triggered = Triggered,
+ Effect = Effect,
+ NodeData = NodeData
+ };
+ }
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs
new file mode 100644
index 0000000000..d616637dcf
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs
@@ -0,0 +1,198 @@
+using System.Linq;
+using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
+using Content.Shared.Xenoarchaeology.XenoArtifacts;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Robust.Shared.Serialization.Manager;
+
+namespace Content.Server.Xenoarchaeology.XenoArtifacts;
+
+public sealed partial class ArtifactSystem
+{
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+ [Dependency] private readonly IComponentFactory _componentFactory = default!;
+ [Dependency] private readonly ISerializationManager _serialization = default!;
+
+ private const int MaxEdgesPerNode = 3;
+
+ ///
+ /// Generate an Artifact tree with fully developed nodes.
+ ///
+ /// The tree being generated.
+ /// The amount of nodes it has.
+ private void GenerateArtifactNodeTree(ref ArtifactTree tree, int nodeAmount)
+ {
+ if (nodeAmount < 1)
+ {
+ Logger.Error($"nodeAmount {nodeAmount} is less than 1. Aborting artifact tree generation.");
+ return;
+ }
+
+ var uninitializedNodes = new List { new() };
+ tree.StartNode = uninitializedNodes.First(); //the first node
+
+ while (uninitializedNodes.Any())
+ {
+ GenerateNode(ref uninitializedNodes, ref tree, nodeAmount);
+ }
+ }
+
+ ///
+ /// Generate an individual node on the tree.
+ ///
+ private void GenerateNode(ref List uninitializedNodes, ref ArtifactTree tree, int targetNodeAmount)
+ {
+ if (!uninitializedNodes.Any())
+ return;
+
+ var node = uninitializedNodes.First();
+ uninitializedNodes.Remove(node);
+
+ node.Id = _random.Next(0, 10000);
+
+ //Generate the connected nodes
+ var maxEdges = Math.Max(1, targetNodeAmount - tree.AllNodes.Count - uninitializedNodes.Count - 1);
+ maxEdges = Math.Min(maxEdges, MaxEdgesPerNode);
+ var minEdges = Math.Clamp(targetNodeAmount - tree.AllNodes.Count - uninitializedNodes.Count - 1, 0, 1);
+
+ var edgeAmount = _random.Next(minEdges, maxEdges);
+
+ for (var i = 0; i < edgeAmount; i++)
+ {
+ var neighbor = new ArtifactNode
+ {
+ Depth = node.Depth + 1
+ };
+ node.Edges.Add(neighbor);
+ neighbor.Edges.Add(node);
+
+ uninitializedNodes.Add(neighbor);
+ }
+
+ node.Trigger = GetRandomTrigger(ref node);
+ node.Effect = GetRandomEffect(ref node);
+
+ tree.AllNodes.Add(node);
+ }
+
+ //yeah these two functions are near duplicates but i don't
+ //want to implement an interface or abstract parent
+
+ private ArtifactTriggerPrototype GetRandomTrigger(ref ArtifactNode node)
+ {
+ var allTriggers = _prototype.EnumeratePrototypes().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);
+ }
+
+ private ArtifactEffectPrototype GetRandomEffect(ref ArtifactNode node)
+ {
+ var allEffects = _prototype.EnumeratePrototypes().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);
+ }
+
+ ///
+ /// 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.
+ ///
+ private Dictionary GetDepthWeights(IEnumerable depths, int targetDepth)
+ {
+ var weights = new Dictionary();
+ foreach (var d in depths)
+ {
+ //TODO: is this equation sus? idk. -emo
+ // 0.3 / (|current_iterated_depth - our_actual_depth| + 1)^2
+ var w = 0.3f / MathF.Pow(Math.Abs(d - targetDepth) + 1, 2);
+ weights.Add(d, w);
+ }
+ return weights;
+ }
+
+ ///
+ /// Uses a weighted random system to get a random depth.
+ ///
+ private int GetRandomTargetDepth(Dictionary 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
+ }
+
+ ///
+ /// Enter a node: attach the relevant components
+ ///
+ private void EnterNode(EntityUid uid, ref ArtifactNode node, ArtifactComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return;
+
+ if (component.CurrentNode != null)
+ {
+ ExitNode(uid, component);
+ }
+
+ component.CurrentNode = node;
+ node.Discovered = true;
+
+ var allComponents = node.Effect.Components.Concat(node.Effect.PermanentComponents).Concat(node.Trigger.Components);
+ foreach (var (name, entry) in allComponents)
+ {
+ var reg = _componentFactory.GetRegistration(name);
+ var comp = (Component) _componentFactory.GetComponent(reg);
+ comp.Owner = uid;
+
+ var temp = (object) comp;
+ _serialization.Copy(entry.Component, ref temp);
+ EntityManager.AddComponent(uid, (Component) temp!, true);
+ }
+
+ RaiseLocalEvent(uid, new ArtifactNodeEnteredEvent(component.CurrentNode.Id));
+ }
+
+ ///
+ /// Exit a node: remove the relevant components.
+ ///
+ private void ExitNode(EntityUid uid, ArtifactComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return;
+
+ var node = component.CurrentNode;
+ if (node == null)
+ return;
+
+ foreach (var name in node.Effect.Components.Keys.Concat(node.Trigger.Components.Keys))
+ {
+ var comp = _componentFactory.GetRegistration(name);
+ EntityManager.RemoveComponentDeferred(uid, comp.Type);
+ }
+
+ component.CurrentNode = null;
+ }
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs
index dee74ac09c..ab0d43a523 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs
@@ -1,50 +1,122 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Content.Server.Cargo.Systems;
using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
+using JetBrains.Annotations;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.Xenoarchaeology.XenoArtifacts;
-public sealed class ArtifactSystem : EntitySystem
+public sealed partial class ArtifactSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly IComponentFactory _componentFactory = default!;
+
+ private const int PricePerNode = 500;
+ private const int PointsPerNode = 5000;
public override void Initialize()
{
base.Initialize();
+
SubscribeLocalEvent(OnInit);
+ SubscribeLocalEvent(GetPrice);
}
private void OnInit(EntityUid uid, ArtifactComponent component, MapInitEvent args)
{
- if (component.RandomTrigger)
- {
- AddRandomTrigger(uid, component);
- }
+ RandomizeArtifact(component);
}
- private void AddRandomTrigger(EntityUid uid, ArtifactComponent? component = null)
+ ///
+ /// Calculates the price of an artifact based on
+ /// how many nodes have been unlocked/triggered
+ ///
+ ///
+ /// General balancing (for fully unlocked artifacts):
+ /// Simple (1-2 Nodes): 1-2K
+ /// Medium (5-8 Nodes): 6-7K
+ /// Complex (7-12 Nodes): 10-11K
+ ///
+ private void GetPrice(EntityUid uid, ArtifactComponent component, ref PriceCalculationEvent args)
{
- if (!Resolve(uid, ref component))
+ if (component.NodeTree == null)
return;
- var triggerName = _random.Pick(component.PossibleTriggers);
- var trigger = (Component) _componentFactory.GetComponent(triggerName);
- trigger.Owner = uid;
+ var price = component.NodeTree.AllNodes.Sum(GetNodePrice);
- if (EntityManager.HasComponent(uid, trigger.GetType()))
- {
- Logger.Error($"Attempted to add a random artifact trigger ({triggerName}) to an entity ({ToPrettyString(uid)}), but it already has the trigger");
- return;
- }
+ // 25% bonus for fully exploring every node.
+ var fullyExploredBonus = component.NodeTree.AllNodes.Any(x => !x.Triggered) ? 1 : 1.25f;
- EntityManager.AddComponent(uid, trigger);
- RaiseLocalEvent(uid, new RandomizeTriggerEvent(), true);
+ args.Price =+ price * fullyExploredBonus;
}
- public bool TryActivateArtifact(EntityUid uid, EntityUid? user = null,
- ArtifactComponent? component = null)
+ private float GetNodePrice(ArtifactNode node)
+ {
+ if (!node.Discovered) //no money for undiscovered nodes.
+ return 0;
+
+ //quarter price if not triggered
+ var priceMultiplier = node.Triggered ? 1f : 0.25f;
+ //the danger is the average of node depth, effect danger, and trigger danger.
+ var nodeDanger = (node.Depth + node.Effect.TargetDepth + node.Trigger.TargetDepth) / 3;
+
+ var price = MathF.Pow(2f, nodeDanger) * PricePerNode * priceMultiplier;
+ return price;
+ }
+
+ ///
+ /// Calculates how many research points the artifact is worht
+ ///
+ ///
+ /// Rebalance this shit at some point. Definitely OP.
+ ///
+ public int GetResearchPointValue(EntityUid uid, ArtifactComponent? component = null)
+ {
+ if (!Resolve(uid, ref component) || component.NodeTree == null)
+ return 0;
+
+ var sumValue = component.NodeTree.AllNodes.Sum(GetNodePointValue);
+ var fullyExploredBonus = component.NodeTree.AllNodes.Any(x => !x.Triggered) ? 1 : 1.25f;
+
+ var pointValue = (int) (sumValue * fullyExploredBonus);
+ return pointValue;
+ }
+
+ private float GetNodePointValue(ArtifactNode node)
+ {
+ if (!node.Discovered)
+ return 0;
+
+ var valueDeduction = !node.Triggered ? 0.5f : 1;
+ var nodeDanger = (node.Depth + node.Effect.TargetDepth + node.Trigger.TargetDepth) / 3;
+
+ return (nodeDanger+1) * PointsPerNode * valueDeduction;
+ }
+
+ ///
+ /// Randomize a given artifact.
+ ///
+ [PublicAPI]
+ public void RandomizeArtifact(ArtifactComponent component)
+ {
+ var nodeAmount = _random.Next(component.NodesMin, component.NodesMax);
+
+ component.NodeTree = new ArtifactTree();
+
+ GenerateArtifactNodeTree(ref component.NodeTree, nodeAmount);
+ EnterNode(component.Owner, ref component.NodeTree.StartNode, component);
+ }
+
+ ///
+ /// Tries to activate the artifact
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool TryActivateArtifact(EntityUid uid, EntityUid? user = null, ArtifactComponent? component = null)
{
if (!Resolve(uid, ref component))
return false;
@@ -55,25 +127,85 @@ public sealed class ArtifactSystem : EntitySystem
// check if artifact isn't under cooldown
var timeDif = _gameTiming.CurTime - component.LastActivationTime;
- if (timeDif.TotalSeconds < component.CooldownTime)
+ if (timeDif < component.CooldownTime)
return false;
ForceActivateArtifact(uid, user, component);
return true;
}
- public void ForceActivateArtifact(EntityUid uid, EntityUid? user = null,
- ArtifactComponent? component = null)
+ ///
+ /// Forces an artifact to activate
+ ///
+ ///
+ ///
+ ///
+ public void ForceActivateArtifact(EntityUid uid, EntityUid? user = null, ArtifactComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
+ if (component.CurrentNode == null)
+ return;
component.LastActivationTime = _gameTiming.CurTime;
- var ev = new ArtifactActivatedEvent()
+ var ev = new ArtifactActivatedEvent
{
Activator = user
};
RaiseLocalEvent(uid, ev, true);
+
+ component.CurrentNode.Triggered = true;
+ if (component.CurrentNode.Edges.Any())
+ {
+ var newNode = _random.Pick(component.CurrentNode.Edges);
+ EnterNode(uid, ref newNode, component);
+ }
+ }
+
+ ///
+ /// Try and get a data object from a node
+ ///
+ /// The entity you're getting the data from
+ /// The data's key
+ /// The data you are trying to get.
+ ///
+ ///
+ ///
+ public bool TryGetNodeData(EntityUid uid, string key, [NotNullWhen(true)] out T data, ArtifactComponent? component = null)
+ {
+ data = default!;
+
+ if (!Resolve(uid, ref component))
+ return false;
+
+ if (component.CurrentNode == null)
+ return false;
+
+ if (component.CurrentNode.NodeData.TryGetValue(key, out var dat) && dat is T value)
+ {
+ data = value;
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Sets the node data to a certain value
+ ///
+ /// The artifact
+ /// The key being set
+ /// The value it's being set to
+ ///
+ public void SetNodeData(EntityUid uid, string key, object value, ArtifactComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return;
+
+ if (component.CurrentNode == null)
+ return;
+
+ component.CurrentNode.NodeData[key] = value;
}
}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/DamageNearbyArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/DamageNearbyArtifactComponent.cs
new file mode 100644
index 0000000000..530f730687
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/DamageNearbyArtifactComponent.cs
@@ -0,0 +1,44 @@
+using Content.Shared.Damage;
+using Content.Shared.Whitelist;
+
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+
+///
+/// When activated, damages nearby entities.
+///
+[RegisterComponent]
+public sealed class DamageNearbyArtifactComponent : Component
+{
+ ///
+ /// The radius of entities that will be affected
+ ///
+ [DataField("radius")]
+ public float Radius = 3f;
+
+ ///
+ /// A whitelist for filtering certain damage.
+ ///
+ ///
+ /// TODO: The component portion, since it uses an array, does not work currently.
+ ///
+ [DataField("whitelist")]
+ public EntityWhitelist? Whitelist;
+
+ ///
+ /// The damage that is applied
+ ///
+ [DataField("damage", required: true)]
+ public DamageSpecifier Damage = default!;
+
+ ///
+ /// The chance that damage is applied to each individual entity
+ ///
+ [DataField("damageChance")]
+ public float DamageChance = 1f;
+
+ ///
+ /// Whether or not this should ignore resistances for the damage
+ ///
+ [DataField("ignoreResistances")]
+ public bool IgnoreResistances;
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/DiseaseArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/DiseaseArtifactComponent.cs
index 91ffa08e9d..0136b68667 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/DiseaseArtifactComponent.cs
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/DiseaseArtifactComponent.cs
@@ -1,4 +1,5 @@
using Content.Shared.Disease;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
///
@@ -8,10 +9,15 @@ namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
public sealed class DiseaseArtifactComponent : Component
{
///
- /// Disease the artifact will spawn
- /// If empty, picks a random one from its list
+ /// The diseases that the artifact can use.
+ ///
+ [DataField("diseasePrototype", customTypeSerializer: typeof(PrototypeIdListSerializer))]
+ public List DiseasePrototypes = new();
+
+ ///
+ /// Disease the artifact will spawn
+ /// Picks a random one from its list
///
- [DataField("disease")]
[ViewVariables(VVAccess.ReadWrite)]
public DiseasePrototype? SpawnDisease;
@@ -19,7 +25,6 @@ public sealed class DiseaseArtifactComponent : Component
/// How far away it will check for people
/// If empty, picks a random one from its list
///
- [DataField("range")]
- [ViewVariables(VVAccess.ReadWrite)]
+ [DataField("range"), ViewVariables(VVAccess.ReadWrite)]
public float Range = 5f;
}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/FoamArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/FoamArtifactComponent.cs
new file mode 100644
index 0000000000..7dfec6db86
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/FoamArtifactComponent.cs
@@ -0,0 +1,54 @@
+using Content.Shared.Chemistry.Reagent;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
+
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+
+///
+/// Generates foam from the artifact when activated
+///
+[RegisterComponent]
+public sealed class FoamArtifactComponent : Component
+{
+ ///
+ /// The list of reagents that will randomly be picked from
+ /// to choose the foam reagent
+ ///
+ [DataField("reagents", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer))]
+ public List Reagents = new();
+
+ ///
+ /// The foam reagent
+ ///
+ [ViewVariables(VVAccess.ReadWrite)]
+ public string? SelectedReagent;
+
+ ///
+ /// How long does the foam last?
+ ///
+ [DataField("duration")]
+ public float Duration = 10;
+
+ ///
+ /// How much reagent is in the foam?
+ ///
+ [DataField("reagentAmount")]
+ public float ReagentAmount = 100;
+
+ ///
+ /// Minimum radius of foam spawned
+ ///
+ [DataField("minFoamAmount")]
+ public int MinFoamAmount = 2;
+
+ ///
+ /// Maximum radius of foam spawned
+ ///
+ [DataField("maxFoamAmount")]
+ public int MaxFoamAmount = 6;
+
+ ///
+ /// How long it takes for each tile of foam to spawn
+ ///
+ [DataField("spreadDuration")]
+ public float SpreadDuration = 1;
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/GasArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/GasArtifactComponent.cs
index 2239b8b9e2..4d9d9b5ac9 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/GasArtifactComponent.cs
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/GasArtifactComponent.cs
@@ -20,7 +20,7 @@ public sealed class GasArtifactComponent : Component
/// List of possible activation gases to pick on startup.
///
[DataField("possibleGas")]
- public Gas[] PossibleGases =
+ public List PossibleGases = new()
{
Gas.Oxygen,
Gas.Plasma,
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/KnockArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/KnockArtifactComponent.cs
new file mode 100644
index 0000000000..ee4b804827
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/KnockArtifactComponent.cs
@@ -0,0 +1,14 @@
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+
+///
+/// This is used for using the "knock" spell when the artifact is activated
+///
+[RegisterComponent]
+public sealed class KnockArtifactComponent : Component
+{
+ ///
+ /// The range of the spell
+ ///
+ [DataField("knockRange")]
+ public float KnockRange = 4f;
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/LightFlickerArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/LightFlickerArtifactComponent.cs
new file mode 100644
index 0000000000..9f8a7a304f
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/LightFlickerArtifactComponent.cs
@@ -0,0 +1,20 @@
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+
+///
+/// Flickers all the lights within a certain radius.
+///
+[RegisterComponent]
+public sealed class LightFlickerArtifactComponent : Component
+{
+ ///
+ /// Lights within this radius will be flickered on activation
+ ///
+ [DataField("radius")]
+ public float Radius = 4;
+
+ ///
+ /// The chance that the light will flicker
+ ///
+ [DataField("flickerChance")]
+ public float FlickerChance = 0.75f;
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/RadiateArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/RadiateArtifactComponent.cs
deleted file mode 100644
index 1e57e46b23..0000000000
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/RadiateArtifactComponent.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-///
-/// Spawn RadiationPulse when artifact activated.
-///
-[RegisterComponent]
-public sealed class RadiateArtifactComponent : Component
-{
- ///
- /// Radiation pulse prototype to spawn.
- ///
- [DataField("pulsePrototype", customTypeSerializer:typeof(PrototypeIdSerializer))]
- public string PulsePrototype = "RadiationPulse";
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/RandomTeleportArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/RandomTeleportArtifactComponent.cs
new file mode 100644
index 0000000000..ce9d823a4f
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/RandomTeleportArtifactComponent.cs
@@ -0,0 +1,15 @@
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+
+///
+/// When activated, will teleport the artifact
+/// to a random position within a certain radius
+///
+[RegisterComponent]
+public sealed class RandomTeleportArtifactComponent : Component
+{
+ ///
+ /// The max distance that the artifact will teleport.
+ ///
+ [DataField("range")]
+ public float Range = 7.5f;
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ShuffleArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ShuffleArtifactComponent.cs
new file mode 100644
index 0000000000..aac7a0fe28
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ShuffleArtifactComponent.cs
@@ -0,0 +1,12 @@
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+
+///
+/// When activated, will shuffle the position of all players
+/// within a certain radius.
+///
+[RegisterComponent]
+public sealed class ShuffleArtifactComponent : Component
+{
+ [DataField("radius")]
+ public float Radius = 7.5f;
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/SpawnArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/SpawnArtifactComponent.cs
index e42f50d42f..4cf5a3d0a9 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/SpawnArtifactComponent.cs
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/SpawnArtifactComponent.cs
@@ -11,21 +11,35 @@ namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
[RegisterComponent]
public sealed class SpawnArtifactComponent : Component
{
- [DataField("random")]
- public bool RandomPrototype = true;
-
+ ///
+ /// The list of possible prototypes to spawn that it picks from.
+ ///
[DataField("possiblePrototypes", customTypeSerializer:typeof(PrototypeIdListSerializer))]
public List PossiblePrototypes = new();
+ ///
+ /// The prototype it selected to spawn.
+ ///
[ViewVariables(VVAccess.ReadWrite)]
[DataField("prototype", customTypeSerializer:typeof(PrototypeIdSerializer))]
public string? Prototype;
+ ///
+ /// The range around the artifact that it will spawn the entity
+ ///
[DataField("range")]
public float Range = 0.5f;
+ ///
+ /// The maximum number of times the spawn will occur
+ ///
[DataField("maxSpawns")]
public int MaxSpawns = 20;
- public int SpawnsCount = 0;
+ ///
+ /// Whether or not the artifact spawns the same entity every time
+ /// or picks through the list each time.
+ ///
+ [DataField("consistentSpawn")]
+ public bool ConsistentSpawn = true;
}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/TelepathicArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/TelepathicArtifactComponent.cs
index 9595e75c0f..523fb43fde 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/TelepathicArtifactComponent.cs
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/TelepathicArtifactComponent.cs
@@ -13,7 +13,7 @@ public sealed class TelepathicArtifactComponent : Component
///
[DataField("messages")]
[ViewVariables(VVAccess.ReadWrite)]
- public string[] Messages = default!;
+ public List Messages = default!;
///
/// Loc string ids of telepathic messages (spooky version).
@@ -21,7 +21,7 @@ public sealed class TelepathicArtifactComponent : Component
///
[DataField("drastic")]
[ViewVariables(VVAccess.ReadWrite)]
- public string[] DrasticMessages = default!;
+ public List? DrasticMessages;
///
/// Probability to pick drastic version of message.
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/TemperatureArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/TemperatureArtifactComponent.cs
index abc7fcf76b..e77d387e14 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/TemperatureArtifactComponent.cs
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/TemperatureArtifactComponent.cs
@@ -8,7 +8,7 @@ namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
[RegisterComponent]
public sealed class TemperatureArtifactComponent : Component
{
- [DataField("targetTemp")]
+ [DataField("targetTemp"), ViewVariables(VVAccess.ReadWrite)]
public float TargetTemperature = Atmospherics.T0C;
[DataField("spawnTemp")]
@@ -21,6 +21,6 @@ public sealed class TemperatureArtifactComponent : Component
/// If true, artifact will heat/cool not only its current tile, but surrounding tiles too.
/// This will change room temperature much faster.
///
- [DataField("effectAdjacent")]
- public bool EffectAdjacentTiles = true;
+ [DataField("affectAdjacent")]
+ public bool AffectAdjacentTiles = true;
}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ThrowArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ThrowArtifactComponent.cs
new file mode 100644
index 0000000000..f3150576dc
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ThrowArtifactComponent.cs
@@ -0,0 +1,27 @@
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+
+///
+/// Throws all nearby entities backwards.
+/// Also pries nearby tiles.
+///
+[RegisterComponent]
+public sealed class ThrowArtifactComponent : Component
+{
+ ///
+ /// How close do you have to be to get yeeted?
+ ///
+ [DataField("range")]
+ public float Range = 2f;
+
+ ///
+ /// How likely is it that an individual tile will get pried?
+ ///
+ [DataField("tilePryChance")]
+ public float TilePryChance = 0.5f;
+
+ ///
+ /// How strongly does stuff get thrown?
+ ///
+ [DataField("throwStrength"), ViewVariables(VVAccess.ReadWrite)]
+ public float ThrowStrength = 5f;
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DamageNearbyArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DamageNearbyArtifactSystem.cs
new file mode 100644
index 0000000000..a2023a18d4
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DamageNearbyArtifactSystem.cs
@@ -0,0 +1,36 @@
+using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
+using Content.Shared.Damage;
+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!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(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 (component.Whitelist != null && !component.Whitelist.IsValid(ent))
+ continue;
+
+ if (!_random.Prob(component.DamageChance))
+ return;
+
+ _damageable.TryChangeDamage(ent, component.Damage, component.IgnoreResistances);
+ }
+ }
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DiseaseArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DiseaseArtifactSystem.cs
index 66cd7ccc43..6328a8e43b 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DiseaseArtifactSystem.cs
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DiseaseArtifactSystem.cs
@@ -1,9 +1,9 @@
+using System.Linq;
using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
using Content.Shared.Disease;
using Content.Server.Disease;
using Content.Server.Disease.Components;
-using Robust.Shared.Random;
using Robust.Shared.Prototypes;
using Content.Shared.Interaction;
@@ -15,38 +15,25 @@ namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems
public sealed class DiseaseArtifactSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly DiseaseSystem _disease = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
- // TODO: YAML Serializer won't catch this.
- [ViewVariables(VVAccess.ReadWrite)]
- public readonly IReadOnlyList ArtifactDiseases = new[]
- {
- "VanAusdallsRobovirus",
- "OwOnavirus",
- "BleedersBite",
- "Ultragigacancer",
- "MemeticAmirmir",
- "TongueTwister",
- "AMIV"
- };
-
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent(OnMapInit);
+ SubscribeLocalEvent(OnNodeEntered);
SubscribeLocalEvent(OnActivate);
}
///
/// Makes sure this artifact is assigned a disease
///
- private void OnMapInit(EntityUid uid, DiseaseArtifactComponent component, MapInitEvent args)
+ private void OnNodeEntered(EntityUid uid, DiseaseArtifactComponent component, ArtifactNodeEnteredEvent args)
{
- if (component.SpawnDisease != null || ArtifactDiseases.Count == 0) return;
- var diseaseName = _random.Pick(ArtifactDiseases);
+ if (component.SpawnDisease != null || !component.DiseasePrototypes.Any())
+ return;
+ var diseaseName = component.DiseasePrototypes[args.RandomSeed % component.DiseasePrototypes.Count];
if (!_prototypeManager.TryIndex(diseaseName, out var disease))
{
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/FoamArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/FoamArtifactSystem.cs
new file mode 100644
index 0000000000..d034c4989c
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/FoamArtifactSystem.cs
@@ -0,0 +1,42 @@
+using System.Linq;
+using Content.Server.Chemistry.ReactionEffects;
+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!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnNodeEntered);
+ SubscribeLocalEvent(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);
+ sol.AddReagent(component.SelectedReagent, component.ReagentAmount);
+
+ FoamAreaReactionEffect.SpawnFoam("Foam", xform.Coordinates, sol,
+ _random.Next(component.MinFoamAmount, component.MaxFoamAmount), component.Duration,
+ component.SpreadDuration, component.SpreadDuration, entityManager: EntityManager);
+ }
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/GasArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/GasArtifactSystem.cs
index 5ac8c76026..e24d31a113 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/GasArtifactSystem.cs
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/GasArtifactSystem.cs
@@ -2,33 +2,32 @@
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 GasArtifactSystem : EntitySystem
{
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
- [Dependency] private readonly IRobustRandom _random = default!;
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent(OnMapInit);
+ SubscribeLocalEvent(OnNodeEntered);
SubscribeLocalEvent(OnActivate);
}
- private void OnMapInit(EntityUid uid, GasArtifactComponent component, MapInitEvent args)
+ private void OnNodeEntered(EntityUid uid, GasArtifactComponent component, ArtifactNodeEnteredEvent args)
{
- if (component.SpawnGas == null && component.PossibleGases.Length != 0)
+ if (component.SpawnGas == null && component.PossibleGases.Count != 0)
{
- var gas = _random.Pick(component.PossibleGases);
+ var gas = component.PossibleGases[args.RandomSeed % component.PossibleGases.Count];
component.SpawnGas = gas;
}
if (component.SpawnTemperature == null)
{
- var temp = _random.NextFloat(component.MinRandomTemperature, component.MaxRandomTemperature);
+ var temp = args.RandomSeed % component.MaxRandomTemperature - component.MinRandomTemperature +
+ component.MinRandomTemperature;
component.SpawnTemperature = temp;
}
}
@@ -38,8 +37,6 @@ public sealed class GasArtifactSystem : EntitySystem
if (component.SpawnGas == null || component.SpawnTemperature == null)
return;
- var transform = Transform(uid);
-
var environment = _atmosphereSystem.GetContainingMixture(uid, false, true);
if (environment == null)
return;
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/KnockArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/KnockArtifactSystem.cs
new file mode 100644
index 0000000000..4554ff94b2
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/KnockArtifactSystem.cs
@@ -0,0 +1,24 @@
+using Content.Server.Magic.Events;
+using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
+
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
+
+public sealed class KnockArtifactSystem : EntitySystem
+{
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnActivated);
+ }
+
+ private void OnActivated(EntityUid uid, KnockArtifactComponent component, ArtifactActivatedEvent args)
+ {
+ var ev = new KnockSpellEvent
+ {
+ Performer = uid,
+ Range = component.KnockRange
+ };
+ RaiseLocalEvent(ev);
+ }
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/LightFlickerArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/LightFlickerArtifactSystem.cs
new file mode 100644
index 0000000000..52d9fb0b36
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/LightFlickerArtifactSystem.cs
@@ -0,0 +1,38 @@
+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;
+
+///
+/// This handles...
+///
+public sealed class LightFlickerArtifactSystem : EntitySystem
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+ [Dependency] private readonly GhostSystem _ghost = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnActivated);
+ }
+
+ private void OnActivated(EntityUid uid, LightFlickerArtifactComponent component, ArtifactActivatedEvent args)
+ {
+ var lights = GetEntityQuery();
+ 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);
+ }
+ }
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/RadiateArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/RadiateArtifactSystem.cs
deleted file mode 100644
index de67aa970d..0000000000
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/RadiateArtifactSystem.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using Content.Server.Radiation;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class RadiateArtifactSystem : EntitySystem
-{
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent(OnActivate);
- }
-
- private void OnActivate(EntityUid uid, RadiateArtifactComponent component, ArtifactActivatedEvent args)
- {
- var transform = Transform(uid);
- EntityManager.SpawnEntity(component.PulsePrototype, transform.Coordinates);
- }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/RandomTeleportArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/RandomTeleportArtifactSystem.cs
new file mode 100644
index 0000000000..a8da6c9a51
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/RandomTeleportArtifactSystem.cs
@@ -0,0 +1,30 @@
+using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
+using Content.Shared.Popups;
+using Robust.Shared.Player;
+using Robust.Shared.Random;
+
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
+
+///
+/// This handles...
+///
+public sealed class RandomTeleportArtifactSystem : EntitySystem
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnActivate);
+ }
+
+ private void OnActivate(EntityUid uid, RandomTeleportArtifactComponent component, ArtifactActivatedEvent args)
+ {
+ var xform = Transform(uid);
+ _popup.PopupCoordinates(Loc.GetString("blink-artifact-popup"), xform.Coordinates, Filter.Pvs(uid), PopupType.Medium);
+
+ xform.Coordinates = xform.Coordinates.Offset(_random.NextVector2(component.Range));
+ }
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ShuffleArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ShuffleArtifactSystem.cs
new file mode 100644
index 0000000000..93b01d68da
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ShuffleArtifactSystem.cs
@@ -0,0 +1,43 @@
+using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
+using Content.Shared.MobState.Components;
+using Robust.Shared.Map;
+using Robust.Shared.Random;
+
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
+
+public sealed class ShuffleArtifactSystem : EntitySystem
+{
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnActivated);
+ }
+
+ private void OnActivated(EntityUid uid, ShuffleArtifactComponent component, ArtifactActivatedEvent args)
+ {
+ var mobState = GetEntityQuery();
+
+ List allCoords = new();
+ List toShuffle = new();
+
+ foreach (var ent in _lookup.GetEntitiesInRange(uid, component.Radius, LookupFlags.Dynamic | LookupFlags.Sundries))
+ {
+ if (!mobState.HasComponent(ent))
+ continue;
+
+ var xform = Transform(ent);
+
+ toShuffle.Add(xform);
+ allCoords.Add(xform.Coordinates);
+ }
+
+ foreach (var xform in toShuffle)
+ {
+ xform.Coordinates = _random.PickAndTake(allCoords);
+ }
+ }
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/SpawnArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/SpawnArtifactSystem.cs
index 4bc28eb063..b2cc629cac 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/SpawnArtifactSystem.cs
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/SpawnArtifactSystem.cs
@@ -9,25 +9,40 @@ public sealed class SpawnArtifactSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
+ [Dependency] private readonly ArtifactSystem _artifact = default!;
+
+ public const string NodeDataSpawnAmount = "nodeDataSpawnAmount";
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent(OnMapInit);
+ SubscribeLocalEvent(OnNodeEntered);
SubscribeLocalEvent(OnActivate);
}
- private void OnMapInit(EntityUid uid, SpawnArtifactComponent component, MapInitEvent args)
+ private void OnNodeEntered(EntityUid uid, SpawnArtifactComponent component, ArtifactNodeEnteredEvent args)
{
- ChooseRandomPrototype(uid, component);
+ if (component.PossiblePrototypes.Count == 0)
+ return;
+
+ var proto = component.PossiblePrototypes[args.RandomSeed % component.PossiblePrototypes.Count];
+ component.Prototype = proto;
}
private void OnActivate(EntityUid uid, SpawnArtifactComponent component, ArtifactActivatedEvent args)
{
if (component.Prototype == null)
return;
- if (component.SpawnsCount >= component.MaxSpawns)
+
+ if (!_artifact.TryGetNodeData(uid, NodeDataSpawnAmount, out int amount))
+ amount = 0;
+
+ if (amount >= component.MaxSpawns)
return;
+ var toSpawn = component.Prototype;
+ if (!component.ConsistentSpawn)
+ toSpawn = _random.Pick(component.PossiblePrototypes);
+
// select spawn position near artifact
var artifactCord = Transform(uid).Coordinates;
var dx = _random.NextFloat(-component.Range, component.Range);
@@ -35,25 +50,11 @@ public sealed class SpawnArtifactSystem : EntitySystem
var spawnCord = artifactCord.Offset(new Vector2(dx, dy));
// spawn entity
- var spawned = EntityManager.SpawnEntity(component.Prototype, spawnCord);
- component.SpawnsCount++;
+ var spawned = EntityManager.SpawnEntity(toSpawn, spawnCord);
+ _artifact.SetNodeData(uid, NodeDataSpawnAmount, amount+1);
// if there is an user - try to put spawned item in their hands
// doesn't work for spawners
_handsSystem.PickupOrDrop(args.Activator, spawned);
}
-
- private void ChooseRandomPrototype(EntityUid uid, SpawnArtifactComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- if (!component.RandomPrototype)
- return;
- if (component.PossiblePrototypes.Count == 0)
- return;
-
- var proto = _random.Pick(component.PossiblePrototypes);
- component.Prototype = proto;
- }
}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TelepathicArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TelepathicArtifactSystem.cs
index 87181dd97d..06be114d42 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TelepathicArtifactSystem.cs
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TelepathicArtifactSystem.cs
@@ -29,8 +29,15 @@ public sealed class TelepathicArtifactSystem : EntitySystem
continue;
// roll if msg should be usual or drastic
- var isDrastic = _random.NextFloat() <= component.DrasticMessageProb;
- var msgArr = isDrastic ? component.DrasticMessages : component.Messages;
+ List msgArr;
+ if (_random.NextFloat() <= component.DrasticMessageProb && component.DrasticMessages != null)
+ {
+ msgArr = component.DrasticMessages;
+ }
+ else
+ {
+ msgArr = component.Messages;
+ }
// pick a random message
var msgId = _random.Pick(msgArr);
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TemperatureArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TemperatureArtifactSystem.cs
index f124ad1caa..97c53f3ecc 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TemperatureArtifactSystem.cs
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TemperatureArtifactSystem.cs
@@ -26,7 +26,7 @@ public sealed class TemperatureArtifactSystem : EntitySystem
return;
UpdateTileTemperature(component, center);
- if (component.EffectAdjacentTiles && transform.GridUid != null)
+ if (component.AffectAdjacentTiles && transform.GridUid != null)
{
var adjacent = _atmosphereSystem.GetAdjacentTileMixtures(transform.GridUid.Value,
_transformSystem.GetGridOrMapTilePosition(uid, transform), excite: true);
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ThrowArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ThrowArtifactSystem.cs
new file mode 100644
index 0000000000..b210bb053f
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ThrowArtifactSystem.cs
@@ -0,0 +1,49 @@
+using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
+using Content.Shared.Maps;
+using Content.Shared.Throwing;
+using Robust.Shared.Map;
+using Robust.Shared.Random;
+
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
+
+public sealed class ThrowArtifactSystem : EntitySystem
+{
+ [Dependency] private readonly IMapManager _map = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+ [Dependency] private readonly ThrowingSystem _throwing = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnActivated);
+ }
+
+ private void OnActivated(EntityUid uid, ThrowArtifactComponent component, ArtifactActivatedEvent args)
+ {
+ var xform = Transform(uid);
+ if (_map.TryGetGrid(xform.GridUid, out var grid))
+ {
+ var tiles = grid.GetTilesIntersecting(
+ Box2.CenteredAround(xform.WorldPosition, (component.Range*2, component.Range)));
+
+ foreach (var tile in tiles)
+ {
+ if (!_random.Prob(component.TilePryChance))
+ continue;
+
+ tile.PryTile();
+ }
+ }
+
+ var lookup = _lookup.GetEntitiesInRange(uid, component.Range, LookupFlags.Dynamic | LookupFlags.Sundries);
+ foreach (var ent in lookup)
+ {
+ var tempXform = Transform(ent);
+
+ var foo = tempXform.MapPosition.Position - xform.MapPosition.Position;
+ _throwing.TryThrow(ent, foo*2, component.ThrowStrength, uid, 0);
+ }
+ }
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Events/ArtifactActivatedEvent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Events/ArtifactEvents.cs
similarity index 52%
rename from Content.Server/Xenoarchaeology/XenoArtifacts/Events/ArtifactActivatedEvent.cs
rename to Content.Server/Xenoarchaeology/XenoArtifacts/Events/ArtifactEvents.cs
index 9757316427..57f5089825 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Events/ArtifactActivatedEvent.cs
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Events/ArtifactEvents.cs
@@ -12,3 +12,20 @@ public sealed class ArtifactActivatedEvent : EntityEventArgs
///
public EntityUid? Activator;
}
+
+///
+/// Force to randomize artifact triggers.
+///
+public sealed class ArtifactNodeEnteredEvent : EntityEventArgs
+{
+ ///
+ /// An entity-specific seed that can be used to
+ /// generate random values.
+ ///
+ public readonly int RandomSeed;
+
+ public ArtifactNodeEnteredEvent(int randomSeed)
+ {
+ RandomSeed = randomSeed;
+ }
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Events/RandomizeTriggerEvent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Events/RandomizeTriggerEvent.cs
deleted file mode 100644
index 45ceae788b..0000000000
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Events/RandomizeTriggerEvent.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-
-///
-/// Force to randomize artifact triggers.
-///
-public sealed class RandomizeTriggerEvent : EntityEventArgs
-{
-
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/RandomArtifactSpriteSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/RandomArtifactSpriteSystem.cs
index 74a2ad94a5..50e2b0ecb3 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/RandomArtifactSpriteSystem.cs
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/RandomArtifactSpriteSystem.cs
@@ -1,5 +1,6 @@
using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
using Content.Shared.Xenoarchaeology.XenoArtifacts;
+using Robust.Server.GameObjects;
using Robust.Shared.Random;
using Robust.Shared.Timing;
@@ -9,6 +10,7 @@ public sealed class RandomArtifactSpriteSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IGameTiming _time = default!;
+ [Dependency] private readonly AppearanceSystem _appearance = default!;
public override void Initialize()
{
@@ -29,7 +31,7 @@ public sealed class RandomArtifactSpriteSystem : EntitySystem
var timeDif = _time.CurTime - component.ActivationStart.Value;
if (timeDif.Seconds >= component.ActivationTime)
{
- appearance.SetData(SharedArtifactsVisuals.IsActivated, false);
+ _appearance.SetData(appearance.Owner, SharedArtifactsVisuals.IsActivated, false, appearance);
component.ActivationStart = null;
}
}
@@ -37,19 +39,13 @@ public sealed class RandomArtifactSpriteSystem : EntitySystem
private void OnMapInit(EntityUid uid, RandomArtifactSpriteComponent component, MapInitEvent args)
{
- if (!TryComp(uid, out AppearanceComponent? appearance))
- return;
-
var randomSprite = _random.Next(component.MinSprite, component.MaxSprite + 1);
- appearance.SetData(SharedArtifactsVisuals.SpriteIndex, randomSprite);
+ _appearance.SetData(uid, SharedArtifactsVisuals.SpriteIndex, randomSprite);
}
private void OnActivated(EntityUid uid, RandomArtifactSpriteComponent component, ArtifactActivatedEvent args)
{
- if (!TryComp(uid, out AppearanceComponent? appearance))
- return;
-
- appearance.SetData(SharedArtifactsVisuals.IsActivated, true);
+ _appearance.SetData(uid, SharedArtifactsVisuals.IsActivated, true);
component.ActivationStart = _time.CurTime;
}
}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactAnchorTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactAnchorTriggerComponent.cs
new file mode 100644
index 0000000000..3855bdc4ac
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactAnchorTriggerComponent.cs
@@ -0,0 +1,13 @@
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
+
+///
+/// Triggers when an artifact is anchored
+///
+///
+/// Not every trigger can be a winner
+///
+[RegisterComponent]
+public sealed class ArtifactAnchorTriggerComponent : Component
+{
+
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactDamageTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactDamageTriggerComponent.cs
new file mode 100644
index 0000000000..d442d5d430
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactDamageTriggerComponent.cs
@@ -0,0 +1,29 @@
+using Content.Shared.Damage.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
+
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
+
+///
+/// Triggers when a certain threshold of damage of certain types is reached
+///
+[RegisterComponent]
+public sealed class ArtifactDamageTriggerComponent : Component
+{
+ ///
+ /// What damage types are accumulated for the trigger?
+ ///
+ [DataField("damageTypes", customTypeSerializer: typeof(PrototypeIdListSerializer))]
+ public List? DamageTypes;
+
+ ///
+ /// What threshold has to be reached before it is activated?
+ ///
+ [DataField("damageThreshold", required: true)]
+ public float DamageThreshold;
+
+ ///
+ /// How much damage has been accumulated on the artifact so far
+ ///
+ [ViewVariables(VVAccess.ReadWrite)]
+ public float AccumulatedDamage = 0;
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactDeathTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactDeathTriggerComponent.cs
new file mode 100644
index 0000000000..797583a8a0
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactDeathTriggerComponent.cs
@@ -0,0 +1,14 @@
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
+
+///
+/// Triggers when a nearby entity dies
+///
+[RegisterComponent]
+public sealed class ArtifactDeathTriggerComponent : Component
+{
+ ///
+ /// How close to the death the artifact has to be for it to trigger.
+ ///
+ [DataField("range")]
+ public float Range = 15f;
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactExamineTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactExamineTriggerComponent.cs
new file mode 100644
index 0000000000..a494b95468
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactExamineTriggerComponent.cs
@@ -0,0 +1,10 @@
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
+
+///
+/// Triggers when the artifact is examined.
+///
+[RegisterComponent]
+public sealed class ArtifactExamineTriggerComponent : Component
+{
+
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactGasTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactGasTriggerComponent.cs
index 5338f19f88..ceba7c8461 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactGasTriggerComponent.cs
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactGasTriggerComponent.cs
@@ -12,7 +12,7 @@ public sealed class ArtifactGasTriggerComponent : Component
/// List of possible activation gases to pick on startup.
///
[DataField("possibleGas")]
- public Gas[] PossibleGases =
+ public List PossibleGases = new()
{
Gas.Oxygen,
Gas.Plasma,
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMagnetTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMagnetTriggerComponent.cs
new file mode 100644
index 0000000000..d19880e73d
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMagnetTriggerComponent.cs
@@ -0,0 +1,14 @@
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
+
+///
+/// Triggers when the salvage magnet is activated
+///
+[RegisterComponent]
+public sealed class ArtifactMagnetTriggerComponent : Component
+{
+ ///
+ /// how close to the magnet do you have to be?
+ ///
+ [DataField("range")]
+ public float Range = 40f;
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMusicTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMusicTriggerComponent.cs
new file mode 100644
index 0000000000..c0c5bd9a7f
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMusicTriggerComponent.cs
@@ -0,0 +1,14 @@
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
+
+///
+/// Triggers when an instrument is played nearby
+///
+[RegisterComponent]
+public sealed class ArtifactMusicTriggerComponent : Component
+{
+ ///
+ /// how close does the artifact have to be to the instrument to activate
+ ///
+ [DataField("range")]
+ public float Range = 5;
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactPressureTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactPressureTriggerComponent.cs
new file mode 100644
index 0000000000..e28169b6b5
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactPressureTriggerComponent.cs
@@ -0,0 +1,20 @@
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
+
+///
+/// Triggers when a certain pressure threshold is hit
+///
+[RegisterComponent]
+public sealed class ArtifactPressureTriggerComponent : Component
+{
+ ///
+ /// The lower-end pressure threshold
+ ///
+ [DataField("minPressureThreshold")]
+ public float? MinPressureThreshold;
+
+ ///
+ /// The higher-end pressure threshold
+ ///
+ [DataField("maxPressureThreshold")]
+ public float? MaxPressureThreshold;
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactAnchorTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactAnchorTriggerSystem.cs
new file mode 100644
index 0000000000..568273efbd
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactAnchorTriggerSystem.cs
@@ -0,0 +1,22 @@
+using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
+
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
+
+public sealed class ArtifactAnchorTriggerSystem : EntitySystem
+{
+ [Dependency] private readonly ArtifactSystem _artifact = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnAnchorStateChanged);
+ }
+
+ private void OnAnchorStateChanged(EntityUid uid, ArtifactAnchorTriggerComponent component, ref AnchorStateChangedEvent args)
+ {
+ if (args.Detaching)
+ return;
+
+ _artifact.TryActivateArtifact(uid);
+ }
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactDamageTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactDamageTriggerSystem.cs
new file mode 100644
index 0000000000..aa7c70753c
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactDamageTriggerSystem.cs
@@ -0,0 +1,35 @@
+using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
+using Content.Shared.Damage;
+
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
+
+public sealed class ArtifactDamageTriggerSystem : EntitySystem
+{
+ [Dependency] private readonly ArtifactSystem _artifact = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnDamageChanged);
+ }
+
+ private void OnDamageChanged(EntityUid uid, ArtifactDamageTriggerComponent component, DamageChangedEvent args)
+ {
+ if (!args.DamageIncreased)
+ return;
+
+ if (args.DamageDelta == null)
+ return;
+
+ foreach (var (type, amount) in args.DamageDelta.DamageDict)
+ {
+ if (component.DamageTypes != null && !component.DamageTypes.Contains(type))
+ continue;
+
+ component.AccumulatedDamage += (float) amount;
+ }
+
+ if (component.AccumulatedDamage >= component.DamageThreshold)
+ _artifact.TryActivateArtifact(uid, args.Origin);
+ }
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactDeathTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactDeathTriggerSystem.cs
new file mode 100644
index 0000000000..b3b5528244
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactDeathTriggerSystem.cs
@@ -0,0 +1,40 @@
+using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
+using Content.Shared.MobState;
+
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
+
+public sealed class ArtifactDeathTriggerSystem : EntitySystem
+{
+ [Dependency] private readonly ArtifactSystem _artifact = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnMobStateChanged);
+ }
+
+ private void OnMobStateChanged(MobStateChangedEvent ev)
+ {
+ if (ev.CurrentMobState != DamageState.Dead)
+ return;
+
+ var deathXform = Transform(ev.Entity);
+
+ var toActivate = new List();
+ foreach (var (trigger, xform) in EntityQuery())
+ {
+ if (!deathXform.Coordinates.TryDistance(EntityManager, xform.Coordinates, out var distance))
+ continue;
+
+ if (distance > trigger.Range)
+ continue;
+
+ toActivate.Add(trigger);
+ }
+
+ foreach (var a in toActivate)
+ {
+ _artifact.TryActivateArtifact(a.Owner);
+ }
+ }
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs
index 8cef4952fc..5988997101 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs
@@ -20,13 +20,18 @@ public sealed class ArtifactElectricityTriggerSystem : EntitySystem
public override void Update(float frameTime)
{
base.Update(frameTime);
- var query = EntityManager.EntityQuery();
- foreach (var (trigger, power, artifact) in query)
+ List toUpdate = new();
+ foreach (var (trigger, power, artifact) in EntityQuery())
{
if (power.ReceivedPower <= trigger.MinPower)
continue;
- _artifactSystem.TryActivateArtifact(trigger.Owner, component: artifact);
+ toUpdate.Add(artifact);
+ }
+
+ foreach (var a in toUpdate)
+ {
+ _artifactSystem.TryActivateArtifact(a.Owner, null, a);
}
}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactExamineTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactExamineTriggerSystem.cs
new file mode 100644
index 0000000000..cbade1682e
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactExamineTriggerSystem.cs
@@ -0,0 +1,20 @@
+using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
+using Content.Shared.Examine;
+
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
+
+public sealed class ArtifactExamineTriggerSystem : EntitySystem
+{
+ [Dependency] private readonly ArtifactSystem _artifact = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnExamine);
+ }
+
+ private void OnExamine(EntityUid uid, ArtifactExamineTriggerComponent component, ExaminedEvent args)
+ {
+ _artifact.TryActivateArtifact(uid);
+ }
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactGasTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactGasTriggerSystem.cs
index b773c71bea..9a88968923 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactGasTriggerSystem.cs
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactGasTriggerSystem.cs
@@ -2,13 +2,11 @@ using Content.Server.Atmos.EntitySystems;
using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
using Robust.Server.GameObjects;
-using Robust.Shared.Random;
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
public sealed class ArtifactGasTriggerSystem : EntitySystem
{
- [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly ArtifactSystem _artifactSystem = default!;
[Dependency] private readonly TransformSystem _transformSystem = default!;
@@ -16,23 +14,24 @@ public sealed class ArtifactGasTriggerSystem : EntitySystem
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent(OnRandomizeTrigger);
+ SubscribeLocalEvent(OnRandomizeTrigger);
}
- private void OnRandomizeTrigger(EntityUid uid, ArtifactGasTriggerComponent component, RandomizeTriggerEvent args)
+ private void OnRandomizeTrigger(EntityUid uid, ArtifactGasTriggerComponent component, ArtifactNodeEnteredEvent args)
{
- if (component.ActivationGas == null)
- {
- var gas = _random.Pick(component.PossibleGases);
- component.ActivationGas = gas;
- }
+ if (component.ActivationGas != null)
+ return;
+
+ var gas = component.PossibleGases[args.RandomSeed % component.PossibleGases.Count];
+ component.ActivationGas = gas;
}
public override void Update(float frameTime)
{
base.Update(frameTime);
- var query = EntityManager.EntityQuery();
- foreach (var (trigger, transform) in query)
+
+ List toUpdate = new();
+ foreach (var (trigger, artifact, transform) in EntityQuery())
{
var uid = trigger.Owner;
@@ -50,7 +49,12 @@ public sealed class ArtifactGasTriggerSystem : EntitySystem
if (moles < trigger.ActivationMoles)
continue;
- _artifactSystem.TryActivateArtifact(trigger.Owner);
+ toUpdate.Add(artifact);
+ }
+
+ foreach (var a in toUpdate)
+ {
+ _artifactSystem.TryActivateArtifact(a.Owner, null, a);
}
}
}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactHeatTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactHeatTriggerSystem.cs
index 6fd85cd366..e54dc9a353 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactHeatTriggerSystem.cs
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactHeatTriggerSystem.cs
@@ -2,7 +2,6 @@
using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
using Content.Shared.Interaction;
using Content.Shared.Temperature;
-using Content.Shared.Weapons.Melee;
using Content.Shared.Weapons.Melee.Events;
using Robust.Server.GameObjects;
@@ -25,8 +24,8 @@ public sealed class ArtifactHeatTriggerSystem : EntitySystem
{
base.Update(frameTime);
- var query = EntityManager.EntityQuery();
- foreach (var (trigger, transform, artifact) in query)
+ List toUpdate = new();
+ foreach (var (trigger, transform, artifact) in EntityQuery())
{
var uid = trigger.Owner;
var environment = _atmosphereSystem.GetTileMixture(transform.GridUid, transform.MapUid,
@@ -37,7 +36,12 @@ public sealed class ArtifactHeatTriggerSystem : EntitySystem
if (environment.Temperature < trigger.ActivationTemperature)
continue;
- _artifactSystem.TryActivateArtifact(trigger.Owner, component: artifact);
+ toUpdate.Add(artifact);
+ }
+
+ foreach (var a in toUpdate)
+ {
+ _artifactSystem.TryActivateArtifact(a.Owner, null, a);
}
}
@@ -61,7 +65,7 @@ public sealed class ArtifactHeatTriggerSystem : EntitySystem
private bool CheckHot(EntityUid usedUid)
{
var hotEvent = new IsHotEvent();
- RaiseLocalEvent(usedUid, hotEvent, false);
+ RaiseLocalEvent(usedUid, hotEvent);
return hotEvent.IsHot;
}
}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactInteractionTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactInteractionTriggerSystem.cs
index 79dd85ff2b..239b674160 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactInteractionTriggerSystem.cs
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactInteractionTriggerSystem.cs
@@ -1,7 +1,6 @@
using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
using Content.Shared.Interaction;
using Content.Shared.Physics.Pull;
-using Content.Shared.Weapons.Melee;
using Content.Shared.Weapons.Melee.Events;
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMagnetTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMagnetTriggerSystem.cs
new file mode 100644
index 0000000000..e23cb2cdbd
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMagnetTriggerSystem.cs
@@ -0,0 +1,40 @@
+using Content.Server.Salvage;
+using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
+
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
+
+///
+/// This handles...
+///
+public sealed class ArtifactMagnetTriggerSystem : EntitySystem
+{
+ [Dependency] private readonly ArtifactSystem _artifact = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnMagnetActivated);
+ }
+
+ private void OnMagnetActivated(SalvageMagnetActivatedEvent ev)
+ {
+ var magXform = Transform(ev.Magnet);
+
+ var toActivate = new List();
+ foreach (var (artifact, xform) in EntityQuery())
+ {
+ if (!magXform.Coordinates.TryDistance(EntityManager, xform.Coordinates, out var distance))
+ continue;
+
+ if (distance > artifact.Range)
+ continue;
+
+ toActivate.Add(artifact.Owner);
+ }
+
+ foreach (var a in toActivate)
+ {
+ _artifact.TryActivateArtifact(a);
+ }
+ }
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMusicTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMusicTriggerSystem.cs
new file mode 100644
index 0000000000..1f2718da2b
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMusicTriggerSystem.cs
@@ -0,0 +1,46 @@
+using System.Linq;
+using Content.Server.Instruments;
+using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
+
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
+
+///
+/// This handles activating an artifact when music is playing nearby
+///
+public sealed class ArtifactMusicTriggerSystem : EntitySystem
+{
+ [Dependency] private readonly ArtifactSystem _artifact = default!;
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var artifactQuery = EntityQuery().ToArray();
+ if (!artifactQuery.Any())
+ return;
+
+ List toActivate = new();
+
+ //assume that there's more instruments than artifacts
+ foreach (var activeinstrument in EntityQuery())
+ {
+ var instXform = Transform(activeinstrument.Owner);
+
+ foreach (var (trigger, xform) in artifactQuery)
+ {
+ if (!instXform.Coordinates.TryDistance(EntityManager, xform.Coordinates, out var distance))
+ continue;
+
+ if (distance > trigger.Range)
+ continue;
+
+ toActivate.Add(trigger.Owner);
+ }
+ }
+
+ foreach (var a in toActivate)
+ {
+ _artifact.TryActivateArtifact(a);
+ }
+ }
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactPressureTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactPressureTriggerSystem.cs
new file mode 100644
index 0000000000..aa1d40c5fb
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactPressureTriggerSystem.cs
@@ -0,0 +1,40 @@
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
+using Robust.Server.GameObjects;
+
+namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
+
+///
+/// This handles activation upon certain pressure thresholds.
+///
+public sealed class ArtifactPressureTriggerSystem : EntitySystem
+{
+ [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
+ [Dependency] private readonly ArtifactSystem _artifactSystem = default!;
+ [Dependency] private readonly TransformSystem _transformSystem = default!;
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ List toUpdate = new();
+ foreach (var (trigger, artifact, transform) in EntityQuery())
+ {
+ var uid = trigger.Owner;
+ var environment = _atmosphereSystem.GetTileMixture(transform.GridUid, transform.MapUid,
+ _transformSystem.GetGridOrMapTilePosition(uid, transform));
+
+ if (environment == null)
+ continue;
+
+ var pressure = environment.Pressure;
+ if (pressure >= trigger.MaxPressureThreshold || pressure <= trigger.MinPressureThreshold)
+ toUpdate.Add(artifact);
+ }
+
+ foreach (var a in toUpdate)
+ {
+ _artifactSystem.TryActivateArtifact(a.Owner, null, a);
+ }
+ }
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactTimerTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactTimerTriggerSystem.cs
index 8600dfbcd6..d8c500c3b1 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactTimerTriggerSystem.cs
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactTimerTriggerSystem.cs
@@ -8,19 +8,36 @@ public sealed class ArtifactTimerTriggerSystem : EntitySystem
[Dependency] private readonly IGameTiming _time = default!;
[Dependency] private readonly ArtifactSystem _artifactSystem = default!;
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnStartup);
+ }
+
+ private void OnStartup(EntityUid uid, ArtifactTimerTriggerComponent component, ComponentStartup args)
+ {
+ component.LastActivation = _time.CurTime;
+ }
+
public override void Update(float frameTime)
{
base.Update(frameTime);
- var query = EntityManager.EntityQuery();
- foreach (var (trigger, artifact) in query)
+ List toUpdate = new();
+ foreach (var (trigger, artifact) in EntityQuery())
{
var timeDif = _time.CurTime - trigger.LastActivation;
if (timeDif <= trigger.ActivationRate)
continue;
- _artifactSystem.TryActivateArtifact(trigger.Owner, component: artifact);
+ toUpdate.Add(artifact);
trigger.LastActivation = _time.CurTime;
}
+
+ foreach (var a in toUpdate)
+ {
+ _artifactSystem.TryActivateArtifact(a.Owner, null, a);
+ }
}
}
diff --git a/Content.Shared/Xenoarchaeology/Equipment/SharedArtifactAnalyzer.cs b/Content.Shared/Xenoarchaeology/Equipment/SharedArtifactAnalyzer.cs
new file mode 100644
index 0000000000..2adb0391ac
--- /dev/null
+++ b/Content.Shared/Xenoarchaeology/Equipment/SharedArtifactAnalyzer.cs
@@ -0,0 +1,78 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Xenoarchaeology.Equipment;
+
+[Serializable, NetSerializable]
+public enum ArtifactAnalzyerUiKey : byte
+{
+ Key
+}
+
+[Serializable, NetSerializable]
+public sealed class AnalysisConsoleServerSelectionMessage : BoundUserInterfaceMessage
+{
+}
+
+[Serializable, NetSerializable]
+public sealed class AnalysisConsoleScanButtonPressedMessage : BoundUserInterfaceMessage
+{
+}
+
+[Serializable, NetSerializable]
+public sealed class AnalysisConsoleDestroyButtonPressedMessage : BoundUserInterfaceMessage
+{
+}
+
+[Serializable, NetSerializable]
+public sealed class AnalysisConsoleScanUpdateState : BoundUserInterfaceState
+{
+ public EntityUid? Artifact;
+
+ public bool AnalyzerConnected;
+
+ public bool ServerConnected;
+
+ public bool CanScan;
+
+ public int? Id;
+
+ public int? Depth;
+
+ public int? Edges;
+
+ public bool? Triggered;
+
+ public string? EffectProto;
+
+ public string? TriggerProto;
+
+ public float? Completion;
+
+ public bool Scanning;
+
+ public TimeSpan TimeRemaining;
+
+ public TimeSpan TotalTime;
+
+ public AnalysisConsoleScanUpdateState(EntityUid? artifact, bool analyzerConnected, bool serverConnected, bool canScan,
+ int? id, int? depth, int? edges, bool? triggered, string? effectProto, string? triggerProto, float? completion,
+ bool scanning, TimeSpan timeRemaining, TimeSpan totalTime)
+ {
+ Artifact = artifact;
+ AnalyzerConnected = analyzerConnected;
+ ServerConnected = serverConnected;
+ CanScan = canScan;
+
+ Id = id;
+ Depth = depth;
+ Edges = edges;
+ Triggered = triggered;
+ EffectProto = effectProto;
+ TriggerProto = triggerProto;
+ Completion = completion;
+
+ Scanning = scanning;
+ TimeRemaining = timeRemaining;
+ TotalTime = totalTime;
+ }
+}
diff --git a/Content.Shared/Xenoarchaeology/XenoArtifacts/ArtifactEffectPrototype.cs b/Content.Shared/Xenoarchaeology/XenoArtifacts/ArtifactEffectPrototype.cs
new file mode 100644
index 0000000000..2b42cff916
--- /dev/null
+++ b/Content.Shared/Xenoarchaeology/XenoArtifacts/ArtifactEffectPrototype.cs
@@ -0,0 +1,36 @@
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Xenoarchaeology.XenoArtifacts;
+
+///
+/// This is a prototype for...
+///
+[Prototype("artifactEffect")]
+[DataDefinition]
+public sealed class ArtifactEffectPrototype : IPrototype
+{
+ ///
+ [IdDataField]
+ public string ID { get; } = default!;
+
+ ///
+ /// Components that are added to the artifact when the specfic effect is active.
+ /// These are removed after the node is exited and the effect is changed.
+ ///
+ [DataField("components", serverOnly: true)]
+ public EntityPrototype.ComponentRegistry Components = new();
+
+ ///
+ /// Components that are permanently added to an entity when the effect's node is entered.
+ ///
+ [DataField("permanentComponents")]
+ public EntityPrototype.ComponentRegistry PermanentComponents = new();
+
+ //TODO: make this a list so we can have multiple target depths
+ [DataField("targetDepth")]
+ public int TargetDepth = 0;
+
+ [DataField("effectHint")]
+ public string? EffectHint;
+}
diff --git a/Content.Shared/Xenoarchaeology/XenoArtifacts/ArtifactTriggerPrototype.cs b/Content.Shared/Xenoarchaeology/XenoArtifacts/ArtifactTriggerPrototype.cs
new file mode 100644
index 0000000000..c00fde2abb
--- /dev/null
+++ b/Content.Shared/Xenoarchaeology/XenoArtifacts/ArtifactTriggerPrototype.cs
@@ -0,0 +1,25 @@
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Xenoarchaeology.XenoArtifacts;
+
+///
+/// This is a prototype for...
+///
+[Prototype("artifactTrigger")]
+[DataDefinition]
+public sealed class ArtifactTriggerPrototype : IPrototype
+{
+ ///
+ [IdDataField]
+ public string ID { get; } = default!;
+
+ [DataField("components", serverOnly: true)]
+ public EntityPrototype.ComponentRegistry Components = new();
+
+ [DataField("targetDepth")]
+ public int TargetDepth = 0;
+
+ [DataField("triggerHint")]
+ public string? TriggerHint;
+}
diff --git a/Resources/Audio/Effects/Lightning/lightningbolt.ogg b/Resources/Audio/Effects/Lightning/lightningbolt.ogg
new file mode 100644
index 0000000000..df3145a08a
Binary files /dev/null and b/Resources/Audio/Effects/Lightning/lightningbolt.ogg differ
diff --git a/Resources/Audio/Machines/license.txt b/Resources/Audio/Machines/license.txt
index 5c7ec91fe2..71c2b9b9da 100644
--- a/Resources/Audio/Machines/license.txt
+++ b/Resources/Audio/Machines/license.txt
@@ -17,3 +17,7 @@ reclaimer_startup.ogg - https://freesound.org/people/cmorris035/sounds/319152/,
airlock_open.ogg and airlock_close.ogg are from: https://github.com/tgstation/tgstation/tree/5f5002b21253354c20ea224be3f604d79299b37e/sound/machines
machine_vend_hot_drink.ogg original from https://freesound.org/people/waxsocks/sounds/402603/ CC0 (by waxsocks) and edited
+
+scan_loop.ogg from https://freesound.org/people/steaq/sounds/509249/ CC-0 by steaq
+
+scan_finish.ogg from https://freesound.org/people/pan14/sounds/263133/ CC-0 by pan14
\ No newline at end of file
diff --git a/Resources/Audio/Machines/scan_finish.ogg b/Resources/Audio/Machines/scan_finish.ogg
new file mode 100644
index 0000000000..7d1a737fc2
Binary files /dev/null and b/Resources/Audio/Machines/scan_finish.ogg differ
diff --git a/Resources/Audio/Machines/scan_loop.ogg b/Resources/Audio/Machines/scan_loop.ogg
new file mode 100644
index 0000000000..bb7155514a
Binary files /dev/null and b/Resources/Audio/Machines/scan_loop.ogg differ
diff --git a/Resources/Locale/en-US/machine-linking/receiver_ports.ftl b/Resources/Locale/en-US/machine-linking/receiver_ports.ftl
index bb70d59d48..5380ac84d7 100644
--- a/Resources/Locale/en-US/machine-linking/receiver_ports.ftl
+++ b/Resources/Locale/en-US/machine-linking/receiver_ports.ftl
@@ -45,3 +45,9 @@ signal-port-description-med-scanner-sender = Medical scanner signal sender
signal-port-name-med-scanner-receiver = Medical scanner
signal-port-description-med-scanner-receiver = Medical scanner signal receiver
+
+signal-port-name-artifact-analyzer-sender = Console
+signal-port-description-artifact-analyzer-sender = Analysis console signal sender
+
+signal-port-name-artifact-analyzer-receiver = Pad
+signal-port-description-artifact-analyzer-receiver = Artifact analyzer signal receiver
diff --git a/Resources/Locale/en-US/station-events/events/bluespace-artifact.ftl b/Resources/Locale/en-US/station-events/events/bluespace-artifact.ftl
new file mode 100644
index 0000000000..a7c948ef3d
--- /dev/null
+++ b/Resources/Locale/en-US/station-events/events/bluespace-artifact.ftl
@@ -0,0 +1,9 @@
+bluespace-artifact-event-announcement = Our readings have detected an incoming anomalous object. Please inform the research team of { $sighting }.
+
+bluespace-artifact-sighting-1 = bright flashes of light
+bluespace-artifact-sighting-2 = strange sounds coming from maintenance tunnels
+bluespace-artifact-sighting-3 = otherworldly structures
+bluespace-artifact-sighting-4 = incomprehensible alien objects
+bluespace-artifact-sighting-5 = unfamiliar objects in strange places
+bluespace-artifact-sighting-6 = unknown alien artifacts
+bluespace-artifact-sighting-7 = explosions of light accompanied by weird sounds
\ No newline at end of file
diff --git a/Resources/Locale/en-US/xenoarchaeology/artifact-analyzer.ftl b/Resources/Locale/en-US/xenoarchaeology/artifact-analyzer.ftl
new file mode 100644
index 0000000000..11937ea17f
--- /dev/null
+++ b/Resources/Locale/en-US/xenoarchaeology/artifact-analyzer.ftl
@@ -0,0 +1,29 @@
+analysis-console-menu-title = analysis console
+analysis-console-server-list-button = Server List
+analysis-console-scan-button = Scan
+analysis-console-scan-tooltip-info = Scan artifacts to learn information about their structure.
+analysis-console-destroy-button = Destroy
+analysis-console-destroy-button-info = Destroy artifacts to generate points based on how much has been unlocked.
+
+analysis-console-info-no-scanner = No analyzer connected! Please connect one using a multitool.
+analysis-console-info-no-artifact = No artifact present! Place one on the pad then scan for information.
+analysis-console-info-ready = Systems operational. Ready to scan.
+
+analysis-console-info-id = NODE_ID: {$id}
+analysis-console-info-depth = DEPTH: {$depth}
+analysis-console-info-triggered-true = ACTIVATED: TRUE
+analysis-console-info-triggered-false = ACTIVATED: FALSE
+analysis-console-info-effect = REACTION: {$effect}
+analysis-console-info-trigger = STIMULUS: {$trigger}
+analysis-console-info-edges = EDGES: {$edges}
+analysis-console-info-completion = COMPLETION_PERCENTAGE: {$percentage}%
+
+analysis-console-info-scanner = Scanning...
+analysis-console-progress-text = {$seconds ->
+ [one] T-{$seconds} second
+ *[other] T-{$seconds} seconds
+}
+
+analyzer-artifact-component-upgrade-analysis = analysis duration
+
+analyzer-artifact-destroy-popup = The artifact disintegrated into energy!
diff --git a/Resources/Locale/en-US/xenoarchaeology/artifact-hints.ftl b/Resources/Locale/en-US/xenoarchaeology/artifact-hints.ftl
new file mode 100644
index 0000000000..a8e5d7bf27
--- /dev/null
+++ b/Resources/Locale/en-US/xenoarchaeology/artifact-hints.ftl
@@ -0,0 +1,27 @@
+# you shouldn't be creating new hints for every effect/trigger
+# try and reuse them so that a hint isn't a dead giveaway. -emo
+
+artifact-effect-hint-mental = Cerebral influence
+artifact-effect-hint-environment = Environmental disruption
+artifact-effect-hint-electrical-interference = Electrical interference
+artifact-effect-hint-displacement = Metaphysical displacement
+artifact-effect-hint-creation = Matter creation
+artifact-effect-hint-consumption = Energy consumption
+artifact-effect-hint-release = Energy release
+artifact-effect-hint-biochemical = Biochemical disruption
+artifact-effect-hint-destruction = Station-wide destruction
+
+# the triggers should be more obvious than the effects
+# gives people an idea of what to do: don't be too specific (i.e. no "welders")
+
+artifact-trigger-hint-electricity = Electricity
+artifact-trigger-hint-heat = High temperatures
+artifact-trigger-hint-physical = Physical trauma
+artifact-trigger-hint-tool = Tool usage
+artifact-trigger-hint-music = Sonic vibrations
+artifact-trigger-hint-water = Hydro-reactive
+artifact-trigger-hint-magnet = Magnetic waves
+artifact-trigger-hint-death = Life essence
+artifact-trigger-hint-radiation = Radiation
+artifact-trigger-hint-pressure = Extreme pressure
+artifact-trigger-hint-gas = Gas
\ No newline at end of file
diff --git a/Resources/Locale/en-US/xenoarchaeology/misc-artifact.ftl b/Resources/Locale/en-US/xenoarchaeology/misc-artifact.ftl
new file mode 100644
index 0000000000..7e1ebc7a57
--- /dev/null
+++ b/Resources/Locale/en-US/xenoarchaeology/misc-artifact.ftl
@@ -0,0 +1,4 @@
+blink-artifact-popup = The artifact disappeared in an instant!
+foam-artifact-popup = Strange foam pours out of the artifact!
+
+shuffle-artifact-popup = You feel yourself teleport instantly!
\ No newline at end of file
diff --git a/Resources/Maps/Salvage/medium-pirate.yml b/Resources/Maps/Salvage/medium-pirate.yml
index bb856e16ea..e9abeed88a 100644
--- a/Resources/Maps/Salvage/medium-pirate.yml
+++ b/Resources/Maps/Salvage/medium-pirate.yml
@@ -749,7 +749,7 @@ entities:
parent: 52
type: Transform
- uid: 79
- type: AngryMobsSpawnArtifact
+ type: RandomArtifactSpawner
components:
- pos: -0.5,-0.5
parent: 52
diff --git a/Resources/Maps/kettle.yml b/Resources/Maps/kettle.yml
index 33073ceadd..e4c0604c8a 100644
--- a/Resources/Maps/kettle.yml
+++ b/Resources/Maps/kettle.yml
@@ -207974,7 +207974,7 @@ entities:
parent: 82
type: Transform
- uid: 25103
- type: JunkSpawnArtifact
+ type: RandomArtifactSpawner
components:
- pos: -69.5,-47.5
parent: 82
diff --git a/Resources/Maps/marathon.yml b/Resources/Maps/marathon.yml
index 4a729d11d4..71ecb20ad9 100644
--- a/Resources/Maps/marathon.yml
+++ b/Resources/Maps/marathon.yml
@@ -129076,7 +129076,7 @@ entities:
parent: 30
type: Transform
- uid: 15282
- type: BananaSpawnArtifact
+ type: RandomArtifactSpawner
components:
- pos: 36.5,12.5
parent: 30
diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_science.yml b/Resources/Prototypes/Catalog/Cargo/cargo_science.yml
index c7c20e1510..7380ae1234 100644
--- a/Resources/Prototypes/Catalog/Cargo/cargo_science.yml
+++ b/Resources/Prototypes/Catalog/Cargo/cargo_science.yml
@@ -7,3 +7,13 @@
cost: 500
category: Science
group: market
+
+- type: cargoProduct
+ id: RandomArtifact
+ icon:
+ sprite: Objects/Specific/Xenoarchaeology/xeno_artifacts.rsi
+ state: ano13
+ product: RandomArtifactSpawner
+ cost: 2000
+ category: Science
+ group: market
diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml
index 6d3fd7468b..8a147be55d 100644
--- a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml
+++ b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml
@@ -157,6 +157,8 @@
- id: DoorRemoteResearch
- id: RubberStampRd
- id: ClothingHeadsetAltScience
+ - id: AnalysisComputerCircuitboard
+ - id: ArtifactAnalyzerMachineCircuitboard
- type: entity
id: LockerHeadOfSecurityFilled
diff --git a/Resources/Prototypes/Catalog/Research/technologies.yml b/Resources/Prototypes/Catalog/Research/technologies.yml
index ac447e4bfa..b6492dad4a 100644
--- a/Resources/Prototypes/Catalog/Research/technologies.yml
+++ b/Resources/Prototypes/Catalog/Research/technologies.yml
@@ -320,6 +320,8 @@
- SolarControlComputerCircuitboard
- PowerComputerCircuitboard
- GeneratorPlasmaMachineCircuitboard
+ - AnalysisComputerCircuitboard
+ - ArtifactAnalyzerMachineCircuitboard
- Signaller
- SignalTrigger
- VoiceTrigger
diff --git a/Resources/Prototypes/Entities/Effects/bluespace_flash.yml b/Resources/Prototypes/Entities/Effects/bluespace_flash.yml
new file mode 100644
index 0000000000..3421f0d9a6
--- /dev/null
+++ b/Resources/Prototypes/Entities/Effects/bluespace_flash.yml
@@ -0,0 +1,13 @@
+- type: entity
+ id: EffectFlashBluespace
+ noSpawn: true
+ components:
+ - type: PointLight
+ radius: 10.5
+ energy: 15
+ color: "#18abf5"
+ - type: TimedDespawn
+ lifetime: 1
+ - type: EmitSoundOnSpawn
+ sound:
+ path: /Audio/Effects/Lightning/lightningbolt.ogg
\ No newline at end of file
diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/artifacts.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/artifacts.yml
index f57703c744..38b5288eaf 100644
--- a/Resources/Prototypes/Entities/Markers/Spawners/Random/artifacts.yml
+++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/artifacts.yml
@@ -9,16 +9,12 @@
- texture: Objects/Specific/Xenoarchaeology/xeno_artifacts.rsi/ano01.png
- type: RandomSpawner
prototypes:
- - BadfeelingArtifact
- - GoodfeelingArtifact
- - AngryMobsSpawnArtifact
- - JunkSpawnArtifact
- - BananaSpawnArtifact
- - HeatArtifact
- - ColdArtifact
- - RadiateArtifact
- - GasArtifact
- - DiseaseArtifact
+ - SimpleXenoArtifact
+ - MediumXenoArtifact
+ - MediumXenoArtifact
+ - MediumXenoArtifact
+ - ComplexXenoArtifact
+ - ComplexXenoArtifact
chance: 1
- type: entity
diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml
index 0ef1403436..3dbb4be59a 100644
--- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml
+++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml
@@ -131,6 +131,22 @@
materialRequirements:
Cable: 5
+- type: entity
+ id: ArtifactAnalyzerMachineCircuitboard
+ parent: BaseMachineCircuitboard
+ name: artifact analyzer machine board
+ description: A machine printed circuit board for an artifact analyzer
+ components:
+ - type: Sprite
+ state: science
+ - type: MachineBoard
+ prototype: MachineArtifactAnalyzer
+ requirements:
+ ScanningModule: 3
+ Capacitor: 1
+ materialRequirements:
+ Glass: 5
+
- type: entity
id: ThermomachineFreezerMachineCircuitBoard
parent: BaseMachineCircuitboard
diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml
index 9aee9c1332..77fa621842 100644
--- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml
+++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml
@@ -145,6 +145,17 @@
- type: ComputerBoard
prototype: ComputerResearchAndDevelopment
+- type: entity
+ parent: BaseComputerCircuitboard
+ id: AnalysisComputerCircuitboard
+ name: analysis computer board
+ description: A computer printed circuit board for an analysis console.
+ components:
+ - type: Sprite
+ state: cpu_science
+ - type: ComputerBoard
+ prototype: ComputerAnalysisConsole
+
- type: entity
parent: BaseComputerCircuitboard
id: CrewMonitoringComputerCircuitboard
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/artifact_equipment.yml b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/artifact_equipment.yml
index 23785f4d6f..ddd4ebe997 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/artifact_equipment.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/artifact_equipment.yml
@@ -44,6 +44,7 @@
- type: Weldable
- type: SuppressArtifactContainer
- type: PlaceableSurface
+ isPlaceable: false
- type: Damageable
damageContainer: Inorganic
damageModifierSet: Metallic
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/artifacts.yml b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/artifacts.yml
index 4eec0ef013..3e50531a85 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/artifacts.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/artifacts.yml
@@ -10,6 +10,8 @@
sprite: Objects/Specific/Xenoarchaeology/xeno_artifacts.rsi
netsync: false
state: ano01
+ noRot: true
+ - type: Damageable
- type: Physics
bodyType: Dynamic
- type: Transform
@@ -31,152 +33,34 @@
- type: Appearance
visuals:
- type: RandomArtifactVisualizer
- - type: PowerConsumer
- voltage: Medium
- drawRate: 500
- - type: NodeContainer
- nodes:
- medium:
- !type:CableDeviceNode
- nodeGroupID: MVPower
- # sadly, HVPower and Apc cables doesn't work right now
+
- type: StaticPrice
- price: 2000
- - type: Electrified
- requirePower: true
- noWindowInTile: true
- highVoltageNode: high
- mediumVoltageNode: medium
- lowVoltageNode: low
-
-# Telepathic
-- type: entity
- parent: BaseXenoArtifact
- id: BadfeelingArtifact
- suffix: Badfeeling
- components:
- - type: TelepathicArtifact
- messages:
- - badfeeling-artifact-1
- - badfeeling-artifact-2
- - badfeeling-artifact-3
- - badfeeling-artifact-4
- - badfeeling-artifact-5
- - badfeeling-artifact-6
- - badfeeling-artifact-7
- - badfeeling-artifact-8
- - badfeeling-artifact-9
- - badfeeling-artifact-10
- - badfeeling-artifact-11
- - badfeeling-artifact-12
- - badfeeling-artifact-13
- - badfeeling-artifact-14
- - badfeeling-artifact-15
- drastic:
- - badfeeling-artifact-drastic-1
- - badfeeling-artifact-drastic-2
- - badfeeling-artifact-drastic-3
- - badfeeling-artifact-drastic-4
- - badfeeling-artifact-drastic-5
- - badfeeling-artifact-drastic-6
+ price: 500
- type: entity
parent: BaseXenoArtifact
- id: GoodfeelingArtifact
- suffix: Goodfeeling
+ id: SimpleXenoArtifact
+ suffix: Simple
components:
- - type: TelepathicArtifact
- messages:
- - goodfeeling-artifact-1
- - goodfeeling-artifact-2
- - goodfeeling-artifact-3
- - goodfeeling-artifact-4
- - goodfeeling-artifact-5
- - goodfeeling-artifact-6
- - goodfeeling-artifact-7
- - goodfeeling-artifact-8
- - goodfeeling-artifact-9
- - goodfeeling-artifact-10
- - goodfeeling-artifact-11
- - goodfeeling-artifact-12
- - goodfeeling-artifact-13
- - goodfeeling-artifact-14
- drastic:
- - goodfeeling-artifact-drastic-1
- - goodfeeling-artifact-drastic-2
- - goodfeeling-artifact-drastic-3
- - goodfeeling-artifact-drastic-4
- - goodfeeling-artifact-drastic-5
- - goodfeeling-artifact-drastic-6
-
-# Spawners
-- type: entity
- parent: BaseXenoArtifact
- id: AngryMobsSpawnArtifact
- suffix: Angry Mobs Spawn
- components:
- - type: SpawnArtifact
- maxSpawns: 5
- possiblePrototypes:
- - MobCarpHolo
- - MobCarpMagic
+ - type: Artifact
+ nodesMin: 2
+ nodesMax: 5
- type: entity
parent: BaseXenoArtifact
- id: JunkSpawnArtifact
- suffix: Junk Spawn
+ id: MediumXenoArtifact
+ suffix: Medium
components:
- - type: SpawnArtifact
- maxSpawns: 10
- possiblePrototypes:
- - FoodPacketSyndiTrash
- - FoodPacketSemkiTrash
- - RandomInstruments
- - ToySpawner
+ - type: Artifact
+ nodesMin: 5
+ nodesMax: 9
- type: entity
parent: BaseXenoArtifact
- id: BananaSpawnArtifact
- suffix: Banana Spawn
+ id: ComplexXenoArtifact
+ suffix: Complex
components:
- - type: SpawnArtifact
- maxSpawns: 20
- possiblePrototypes:
- - FoodBanana
+ - type: Artifact
+ nodesMin: 9
+ nodesMax: 13
-- type: entity
- parent: BaseXenoArtifact
- id: HeatArtifact
- suffix: Heat
- components:
- - type: TemperatureArtifact
- targetTemp: 400 # around 125 celsius
-
-- type: entity
- parent: BaseXenoArtifact
- id: ColdArtifact
- suffix: Cold
- components:
- - type: TemperatureArtifact
- targetTemp: 150 # around -125 celsius
-
-- type: entity
- parent: BaseXenoArtifact
- id: RadiateArtifact
- suffix: Radiation
- components:
- - type: RadiateArtifact
-
-- type: entity
- parent: BaseXenoArtifact
- id: GasArtifact
- suffix: Gas
- components:
- - type: GasArtifact
-
-- type: entity
- parent: BaseXenoArtifact
- id: DiseaseArtifact
- suffix: Disease
- components:
- - type: DiseaseArtifact
diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml
index 8e0733b3b8..e081701f8f 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml
@@ -335,6 +335,46 @@
energy: 1.6
color: "#b53ca1"
+- type: entity
+ parent: BaseComputer
+ id: ComputerAnalysisConsole
+ name: analysis console
+ description: A computer used to interface with the artifact analyzer.
+ components:
+ - type: Appearance
+ visuals:
+ - type: ComputerVisualizer
+ key: tech_key
+ screen: artifact
+ - type: ResearchClient
+ - type: AnalysisConsole
+ - type: DeviceList
+ - type: DeviceNetwork
+ deviceNetId: Wired
+ - type: SignalTransmitter
+ transmissionRange: 5
+ outputs:
+ ArtifactAnalyzerSender: []
+ - type: ActivatableUI
+ key: enum.ArtifactAnalzyerUiKey.Key
+ - type: ActivatableUIRequiresPower
+ - type: UserInterface
+ interfaces:
+ - key: enum.ArtifactAnalzyerUiKey.Key
+ type: AnalysisConsoleBoundUserInterface
+ - key: enum.ResearchClientUiKey.Key
+ type: ResearchClientBoundUserInterface
+ - type: ApcPowerReceiver
+ powerLoad: 1000
+ priority: Low
+ - type: ExtensionCableReceiver
+ - type: Computer
+ board: AnalysisComputerCircuitboard
+ - type: PointLight
+ radius: 1.5
+ energy: 1.6
+ color: "#b53ca1"
+
- type: entity
parent: BaseComputer
id: ComputerId
diff --git a/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml b/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml
new file mode 100644
index 0000000000..f65025c522
--- /dev/null
+++ b/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml
@@ -0,0 +1,68 @@
+- type: entity
+ id: MachineArtifactAnalyzer
+ parent: [ BaseMachinePowered, ConstructibleMachine ]
+ name: artifact analyzer
+ description: A platform capable of performing analysis on various types of artifacts.
+ components:
+ - type: Sprite
+ noRot: true
+ netsync: false
+ sprite: Structures/Machines/artifact_analyzer.rsi
+ drawdepth: FloorObjects
+ layers:
+ - state: icon
+ - state: unshaded
+ shader: unshaded
+ map: ["enum.PowerDeviceVisualLayers.Powered"]
+ - type: Physics
+ bodyType: Static
+ canCollide: true
+ - type: AmbientSound
+ enabled: false
+ sound:
+ path: /Audio/Machines/scan_loop.ogg
+ range: 5
+ volume: -8
+ - type: Fixtures
+ fixtures:
+ - shape:
+ !type:PhysShapeAabb
+ bounds: "-0.35,-0.35,0.35,0.35"
+ density: 190
+ mask:
+ - MachineMask
+ layer:
+ - Impassable
+ - MidImpassable
+ - LowImpassable
+ hard: False
+ - type: Transform
+ anchored: true
+ noRot: false
+ - type: ApcPowerReceiver
+ powerLoad: 15000 #really freaking high
+ needsPower: false #only turns on when scanning
+ - type: UpgradePowerDraw
+ powerDrawMultiplier: 0.80
+ scaling: Exponential
+ - type: ArtifactAnalyzer
+ - type: DeviceNetwork
+ deviceNetId: Wired
+ - type: DeviceList
+ - type: SignalReceiver
+ inputs:
+ ArtifactAnalyzerReceiver: []
+ - type: Machine
+ board: ArtifactAnalyzerMachineCircuitboard
+ - type: PointLight
+ radius: 1.5
+ energy: 1.6
+ color: "#b53ca1"
+ - type: LitOnPowered
+ - type: Appearance
+ - type: GenericVisualizer
+ visuals:
+ enum.PowerDeviceVisuals.Powered:
+ enum.PowerDeviceVisualLayers.Powered:
+ True: { visible: true }
+ False: { visible: false }
diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
index 4569a642f1..dd3b6b9a48 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
@@ -298,6 +298,8 @@
- EmitterCircuitboard
- GasRecyclerMachineCircuitboard
- SeedExtractorMachineCircuitboard
+ - AnalysisComputerCircuitboard
+ - ArtifactAnalyzerMachineCircuitboard
- type: MaterialStorage
whitelist:
tags:
diff --git a/Resources/Prototypes/Entities/Structures/Machines/research.yml b/Resources/Prototypes/Entities/Structures/Machines/research.yml
index 851619e1f4..d35b71a2a6 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/research.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/research.yml
@@ -52,7 +52,7 @@
map: ["enum.PowerDeviceVisualLayers.Powered"]
- type: ResearchClient
- type: ResearchPointSource
- pointspersecond: 100
+ pointspersecond: 25
active: true
- type: PointLight
radius: 1.5
diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml
index 5405f2ecbe..4ea6e56068 100644
--- a/Resources/Prototypes/GameRules/events.yml
+++ b/Resources/Prototypes/GameRules/events.yml
@@ -1,4 +1,14 @@
- type: gameRule
+ id: BluespaceArtifact
+ config:
+ !type:StationEventRuleConfiguration
+ id: BluespaceArtifact
+ weight: 5
+ maxOccurrences: 5
+ startAfter: 30
+ endAfter: 35
+
+- type: gameRule
id: BreakerFlip
config:
!type:StationEventRuleConfiguration
diff --git a/Resources/Prototypes/MachineLinking/receiver_ports.yml b/Resources/Prototypes/MachineLinking/receiver_ports.yml
index 397688b601..16cb34e9bd 100644
--- a/Resources/Prototypes/MachineLinking/receiver_ports.yml
+++ b/Resources/Prototypes/MachineLinking/receiver_ports.yml
@@ -62,3 +62,8 @@
id: MedicalScannerReceiver
name: signal-port-name-med-scanner-receiver
description: signal-port-description-med-scanner-receiver
+
+- type: receiverPort
+ id: ArtifactAnalyzerReceiver
+ name: signal-port-name-artifact-analyzer-receiver
+ description: signal-port-description-artifact-analyzer-receiver
diff --git a/Resources/Prototypes/MachineLinking/transmitter_ports.yml b/Resources/Prototypes/MachineLinking/transmitter_ports.yml
index 3ce176d59a..7282253347 100644
--- a/Resources/Prototypes/MachineLinking/transmitter_ports.yml
+++ b/Resources/Prototypes/MachineLinking/transmitter_ports.yml
@@ -49,3 +49,9 @@
id: MedicalScannerSender
name: signal-port-name-med-scanner-sender
description: signal-port-description-med-scanner-sender
+
+- type: transmitterPort
+ id: ArtifactAnalyzerSender
+ name: signal-port-name-artifact-analyzer-sender
+ description: signal-port-description-artifact-analyzer-sender
+ defaultLinks: [ ArtifactAnalyzerReceiver ]
\ No newline at end of file
diff --git a/Resources/Prototypes/Recipes/Lathes/electronics.yml b/Resources/Prototypes/Recipes/Lathes/electronics.yml
index 308c368c63..f49ca51063 100644
--- a/Resources/Prototypes/Recipes/Lathes/electronics.yml
+++ b/Resources/Prototypes/Recipes/Lathes/electronics.yml
@@ -176,6 +176,15 @@
Glass: 900
Gold: 100
+- type: latheRecipe
+ id: ArtifactAnalyzerMachineCircuitboard
+ icon: Objects/Misc/module.rsi/science.png
+ result: ArtifactAnalyzerMachineCircuitboard
+ completetime: 4
+ materials:
+ Steel: 100
+ Glass: 900
+ Gold: 100
- type: latheRecipe
id: ReagentGrinderMachineCircuitboard
@@ -186,6 +195,16 @@
Steel: 100
Glass: 900
+- type: latheRecipe
+ id: AnalysisComputerCircuitboard
+ icon: Objects/Misc/module.rsi/cpu_science.png
+ result: AnalysisComputerCircuitboard
+ completetime: 4
+ materials:
+ Steel: 100
+ Glass: 900
+ Gold: 100
+
- type: latheRecipe
id: CrewMonitoringComputerCircuitboard
icon: Objects/Misc/module.rsi/id_mod.png
diff --git a/Resources/Prototypes/XenoArch/artifact_effects.yml b/Resources/Prototypes/XenoArch/artifact_effects.yml
new file mode 100644
index 0000000000..8452bad16c
--- /dev/null
+++ b/Resources/Prototypes/XenoArch/artifact_effects.yml
@@ -0,0 +1,318 @@
+- type: artifactEffect
+ id: EffectBadFeeling
+ targetDepth: 0
+ effectHint: artifact-effect-hint-mental
+ components:
+ - type: TelepathicArtifact
+ messages:
+ - badfeeling-artifact-1
+ - badfeeling-artifact-2
+ - badfeeling-artifact-3
+ - badfeeling-artifact-4
+ - badfeeling-artifact-5
+ - badfeeling-artifact-6
+ - badfeeling-artifact-7
+ - badfeeling-artifact-8
+ - badfeeling-artifact-9
+ - badfeeling-artifact-10
+ - badfeeling-artifact-11
+ - badfeeling-artifact-12
+ - badfeeling-artifact-13
+ - badfeeling-artifact-14
+ - badfeeling-artifact-15
+ drastic:
+ - badfeeling-artifact-drastic-1
+ - badfeeling-artifact-drastic-2
+ - badfeeling-artifact-drastic-3
+ - badfeeling-artifact-drastic-4
+ - badfeeling-artifact-drastic-5
+ - badfeeling-artifact-drastic-6
+
+- type: artifactEffect
+ id: EffectGoodFeeling
+ targetDepth: 0
+ effectHint: artifact-effect-hint-mental
+ components:
+ - type: TelepathicArtifact
+ messages:
+ - goodfeeling-artifact-1
+ - goodfeeling-artifact-2
+ - goodfeeling-artifact-3
+ - goodfeeling-artifact-4
+ - goodfeeling-artifact-5
+ - goodfeeling-artifact-6
+ - goodfeeling-artifact-7
+ - goodfeeling-artifact-8
+ - goodfeeling-artifact-9
+ - goodfeeling-artifact-10
+ - goodfeeling-artifact-11
+ - goodfeeling-artifact-12
+ - goodfeeling-artifact-13
+ - goodfeeling-artifact-14
+ drastic:
+ - goodfeeling-artifact-drastic-1
+ - goodfeeling-artifact-drastic-2
+ - goodfeeling-artifact-drastic-3
+ - goodfeeling-artifact-drastic-4
+ - goodfeeling-artifact-drastic-5
+ - goodfeeling-artifact-drastic-6
+
+- type: artifactEffect
+ id: EffectJunkSpawn
+ targetDepth: 0
+ effectHint: artifact-effect-hint-creation
+ components:
+ - type: SpawnArtifact
+ maxSpawns: 10
+ consistentSpawns: false
+ possiblePrototypes:
+ - FoodPacketSyndiTrash
+ - FoodPacketSemkiTrash
+ - FoodPacketBoritosTrash
+ - FoodPacketCheesieTrash
+ - FoodPacketChipsTrash
+ - FoodPacketChocolateTrash
+ - FoodPacketChowMeinTrash
+ - FoodPacketEnergyTrash
+ - FoodPacketPopcornTrash
+ - FoodPacketRaisinsTrash
+ - RandomInstruments
+ - ToySpawner
+
+- type: artifactEffect
+ id: EffectLightFlicker
+ targetDepth: 0
+ effectHint: artifact-effect-hint-electrical-interference
+ components:
+ - type: LightFlickerArtifact
+
+- type: artifactEffect
+ id: EffectPointLight
+ targetDepth: 0
+ components:
+ - type: PointLight
+ radius: 2
+ energy: 5
+ color: "#27153b"
+
+- type: artifactEffect #bornana
+ id: EffectBananaSpawn
+ targetDepth: 1
+ effectHint: artifact-effect-hint-creation
+ components:
+ - type: SpawnArtifact
+ maxSpawns: 20
+ possiblePrototypes:
+ - FoodBanana
+
+- type: artifactEffect
+ id: EffectCold
+ targetDepth: 1
+ effectHint: artifact-effect-hint-consumption
+ components:
+ - type: TemperatureArtifact
+ targetTemp: 150
+
+- type: artifactEffect
+ id: EffectThrow
+ targetDepth: 1
+ effectHint: artifact-effect-hint-environment
+ components:
+ - type: ThrowArtifact
+
+- type: artifactEffect
+ id: EffectFoamMild
+ targetDepth: 1
+ effectHint: artifact-effect-hint-biochemical
+ components:
+ - type: FoamArtifact
+ reagents:
+ - Oxygen
+ - Plasma
+ - Blood
+ - SpaceCleaner
+ - Nutriment
+ - SpaceLube
+ - Ethanol
+ - Mercury
+ - VentCrud
+ - WeldingFuel
+
+- type: artifactEffect
+ id: EffectInstrumentSpawn
+ targetDepth: 1
+ effectHint: artifact-effect-hint-creation
+ components:
+ - type: SpawnArtifact
+ maxSpawns: 5
+ possiblePrototypes:
+ - RandomInstruments
+
+- type: artifactEffect
+ id: EffectAngryCarpSpawn
+ targetDepth: 2
+ effectHint: artifact-effect-hint-environment
+ components:
+ - type: SpawnArtifact
+ maxSpawns: 5
+ possiblePrototypes:
+ - MobCarpHolo
+ - MobCarpMagic
+
+- type: artifactEffect
+ id: EffectRadiate
+ targetDepth: 2
+ effectHint: artifact-effect-hint-release
+ components:
+ - type: RadiationSource
+ intensity: 2
+
+- type: artifactEffect
+ id: EffectKnock
+ targetDepth: 2
+ effectHint: artifact-effect-hint-electrical-interference
+ components:
+ - type: KnockArtifact
+
+- type: artifactEffect
+ id: EffectShatterWindows
+ targetDepth: 2
+ effectHint: artifact-effect-hint-environment
+ components:
+ - type: DamageNearbyArtifact
+ damageChance: 0.75
+ whitelist:
+ tags:
+ - Window
+ damage:
+ types:
+ Structural: 100
+
+- type: artifactEffect
+ id: EffectMaterialSpawn
+ targetDepth: 3
+ effectHint: artifact-effect-hint-creation
+ components:
+ - type: SpawnArtifact
+ maxSpawns: 5
+ consistentSpawn: false
+ possiblePrototypes:
+ - SheetGlass
+ - SheetSteel
+ - SheetPlastic
+
+- type: artifactEffect
+ id: EffectShuffle
+ targetDepth: 3
+ effectHint: artifact-effect-hint-displacement
+ components:
+ - type: ShuffleArtifact
+ - type: TelepathicArtifact
+ range: 7.5
+ messages:
+ - shuffle-artifact-popup
+
+- type: artifactEffect
+ id: EffectT3PartsSpawn
+ targetDepth: 3
+ effectHint: artifact-effect-hint-creation
+ components:
+ - type: SpawnArtifact
+ maxSpawns: 10
+ consistentSpawn: false
+ possiblePrototypes:
+ - SuperCapacitorStockPart
+ - PhasicScanningModuleStockPart
+ - PicoManipulatorStockPart
+ - UltraHighPowerMicroLaserStockPart
+ - SuperMatterBinStockPart
+
+- type: artifactEffect
+ id: EffectGas
+ targetDepth: 3
+ effectHint: artifact-effect-hint-environment
+ components:
+ - type: GasArtifact
+
+- type: artifactEffect
+ id: EffectBlink
+ targetDepth: 3
+ effectHint: artifact-effect-hint-displacement
+ components:
+ - type: RandomTeleportArtifact
+
+- type: artifactEffect
+ id: EffectDisease
+ targetDepth: 3
+ effectHint: artifact-effect-hint-biochemical
+ components:
+ - type: DiseaseArtifact
+ diseasePrototypes:
+ - VanAusdallsRobovirus
+ - OwOnavirus
+ - BleedersBite
+ - Ultragigacancer
+ - MemeticAmirmir
+ - TongueTwister
+ - AMIV
+
+- type: artifactEffect
+ id: EffectRareMaterialSpawn
+ targetDepth: 4
+ effectHint: artifact-effect-hint-creation
+ components:
+ - type: SpawnArtifact
+ maxSpawns: 5
+ consistentSpawn: false
+ possiblePrototypes:
+ - SilverOre1
+ - PlasmaOre1
+ - GoldOre1
+ - UraniumOre1
+
+- type: artifactEffect
+ id: EffectPowerGen20K
+ targetDepth: 4
+ effectHint: artifact-effect-hint-release
+ components:
+ - type: PowerSupplier
+ supplyRate: 20000
+
+- type: artifactEffect
+ id: EffectHeat
+ targetDepth: 4
+ effectHint: artifact-effect-hint-release
+ components:
+ - type: TemperatureArtifact
+ targetTemp: 450
+
+- type: artifactEffect
+ id: EffectFoamDangerous
+ targetDepth: 4
+ effectHint: artifact-effect-hint-biochemical
+ components:
+ - type: FoamArtifact
+ spreadDuration: 0.5
+ duration: 5
+ reagents:
+ - Tritium
+ - Plasma
+ - SulfuricAcid
+ - SpaceDrugs
+ - Nocturine
+ - MuteToxin
+ - Napalm
+ - CarpoToxin
+ - ChloralHydrate
+ - Mold
+ - Amatoxin
+
+- type: artifactEffect
+ id: EffectSingulo
+ targetDepth: 100
+ effectHint: artifact-effect-hint-destruction
+ components:
+ - type: SpawnArtifact
+ maxSpawns: 1
+ possiblePrototypes:
+ - Singularity
\ No newline at end of file
diff --git a/Resources/Prototypes/XenoArch/artifact_triggers.yml b/Resources/Prototypes/XenoArch/artifact_triggers.yml
new file mode 100644
index 0000000000..51e0be3df1
--- /dev/null
+++ b/Resources/Prototypes/XenoArch/artifact_triggers.yml
@@ -0,0 +1,144 @@
+- type: artifactTrigger
+ id: TriggerInteraction
+ targetDepth: 0
+ triggerHint: artifact-trigger-hint-physical
+ components:
+ - type: ArtifactInteractionTrigger
+
+- type: artifactTrigger
+ id: TriggerTimer
+ targetDepth: 0
+ components:
+ - type: ArtifactTimerTrigger
+
+- type: artifactTrigger
+ id: TriggerExamine
+ targetDepth: 0
+ components:
+ - type: ArtifactExamineTrigger
+
+- type: artifactTrigger
+ id: TriggerAnchor
+ targetDepth: 0
+ triggerHint: artifact-trigger-hint-tool
+ components:
+ - type: ArtifactAnchorTrigger
+
+- type: artifactTrigger
+ id: TriggerElectricity
+ targetDepth: 0
+ triggerHint: artifact-trigger-hint-electricity
+ components:
+ - type: ArtifactElectricityTrigger
+ - type: PowerConsumer
+ voltage: Medium
+ drawRate: 500
+ - type: Electrified
+ requirePower: true
+ noWindowInTile: true
+ highVoltageNode: high
+ mediumVoltageNode: medium
+ lowVoltageNode: low
+ - type: NodeContainer
+ nodes:
+ medium:
+ !type:CableDeviceNode
+ nodeGroupID: MVPower
+ # sadly, HVPower and Apc cables doesn't work right now
+
+- type: artifactTrigger
+ id: TriggerMusic
+ targetDepth: 1
+ triggerHint: artifact-trigger-hint-music
+ components:
+ - type: ArtifactMusicTrigger
+
+- type: artifactTrigger
+ id: TriggerBruteDamage
+ targetDepth: 1
+ triggerHint: artifact-trigger-hint-physical
+ components:
+ - type: ArtifactDamageTrigger
+ damageTypes:
+ - Blunt
+ - Slash
+ - Piercing
+ damageThreshold: 50
+
+- type: artifactTrigger
+ id: TriggerHeat
+ targetDepth: 1
+ triggerHint: artifact-trigger-hint-heat
+ components:
+ - type: ArtifactHeatTrigger
+
+- type: artifactTrigger
+ id: TriggerWater
+ targetDepth: 1
+ triggerHint: artifact-trigger-hint-water
+ components:
+ - type: Reactive
+ reactions:
+ - reagents: [ Water ]
+ methods: [ Touch ]
+ effects:
+ - !type:ActivateArtifact
+
+- type: artifactTrigger
+ id: TriggerDeath
+ targetDepth: 2
+ triggerHint: artifact-trigger-hint-death
+ components:
+ - type: ArtifactDeathTrigger
+
+- type: artifactTrigger
+ id: TriggerMagnet
+ targetDepth: 2
+ triggerHint: artifact-trigger-hint-magnet
+ components:
+ - type: ArtifactMagnetTrigger
+
+- type: artifactTrigger
+ id: TriggerLowPressure
+ targetDepth: 2
+ triggerHint: artifact-trigger-hint-pressure
+ components:
+ - type: ArtifactPressureTrigger
+ minPressureThreshold: 50
+
+- type: artifactTrigger
+ id: TriggerHighDamage
+ targetDepth: 3
+ triggerHint: artifact-trigger-hint-physical
+ components:
+ - type: ArtifactDamageTrigger
+ damageThreshold: 500 #make it go boom or w/e
+
+- type: artifactTrigger
+ id: TriggerRadiation
+ targetDepth: 3
+ triggerHint: artifact-trigger-hint-radiation
+ components:
+ - type: ArtifactDamageTrigger
+ damageTypes:
+ - Radiation
+ damageThreshold: 100
+ - type: RadiationReceiver
+
+- type: artifactTrigger
+ id: TriggerHighPressure
+ targetDepth: 3
+ triggerHint: artifact-trigger-hint-pressure
+ components:
+ - type: ArtifactPressureTrigger
+ maxPressureThreshold: 385
+
+- type: artifactTrigger
+ id: TriggerGas
+ targetDepth: 3
+ triggerHint: artifact-trigger-hint-gas
+ components:
+ - type: ArtifactGasTrigger
+
+#don't add in new targetdepth values until you have a few
+#or else it will skew heavily towards a few options.
\ No newline at end of file
diff --git a/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/icon.png b/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/icon.png
new file mode 100644
index 0000000000..c034a187f3
Binary files /dev/null and b/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/icon.png differ
diff --git a/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/meta.json b/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/meta.json
new file mode 100644
index 0000000000..413b7c38ed
--- /dev/null
+++ b/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/meta.json
@@ -0,0 +1,17 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Created by EmoGarbage404",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "icon"
+ },
+ {
+ "name": "unshaded"
+ }
+ ]
+}
diff --git a/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/unshaded.png b/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/unshaded.png
new file mode 100644
index 0000000000..795efc3d38
Binary files /dev/null and b/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/unshaded.png differ
diff --git a/Resources/Textures/Structures/Machines/computers.rsi/artifact.png b/Resources/Textures/Structures/Machines/computers.rsi/artifact.png
new file mode 100644
index 0000000000..5fc7c22947
Binary files /dev/null and b/Resources/Textures/Structures/Machines/computers.rsi/artifact.png differ
diff --git a/Resources/Textures/Structures/Machines/computers.rsi/meta.json b/Resources/Textures/Structures/Machines/computers.rsi/meta.json
index 653238d34c..1b4bde5f71 100644
--- a/Resources/Textures/Structures/Machines/computers.rsi/meta.json
+++ b/Resources/Textures/Structures/Machines/computers.rsi/meta.json
@@ -153,6 +153,48 @@
]
]
},
+ {
+ "name": "artifact",
+ "directions": 4,
+ "delays": [
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ ]
+ },
{
"name": "atmos_key",
"directions": 4