XenoArch [Science Overhaul] (#12204)
* multi-node xeno artifacts * refactor existing artifact effects * more tweaks to generation * more shit plus fix tests * more generation stuff plus threat levels * doink * now make it build * defer the artifact activation to not cause errors also pricing * some changes * all of the yaml + ui stuff for artifact analyzer * machine linking and starting to make the ui functional * artifact analyzer display * a shit ton of artifact analyzer stuff * more changes; making destroy work properly; progress bar tweaks * getting shit going! ALL RIGHT * small tweaks that didn't help much * Komm susser todd: the end of analysis * recipes and hints and ui, oh my! * add some in-game sources gotta prepare for day 1 launch * node data + ditch random seed in place of id * bunch of triggers * finish off the last few triggers * implement machine examine verb * knock, flicker, blink, throw * shatter, foam, shuffle, heat * fix all the shit i broke * *some* of these have to be good, no? 25 effects * callin' it there for effects * comments + reword some trigger hints * don't mind this little commit here * byref event * fix brokey node entry * fix low pressure trigger * mirror review plus fixing 0x40's bug also the throw artifact threw incorrectly * randomize the event message a teeny bit
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
59
Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml
Normal file
59
Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml
Normal file
@@ -0,0 +1,59 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
Title="{Loc 'analysis-console-menu-title'}"
|
||||
MinSize="620 280"
|
||||
SetSize="620 280">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
|
||||
<BoxContainer Margin="10 10 10 10" MinWidth="150" Orientation="Vertical" VerticalExpand="True" SizeFlagsStretchRatio="1">
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True">
|
||||
<Button Name="ServerSelectionButton"
|
||||
Text="{Loc 'analysis-console-server-list-button'}"></Button>
|
||||
<BoxContainer MinHeight="5"></BoxContainer>
|
||||
<Button Name="ScanButton"
|
||||
Text="{Loc 'analysis-console-scan-button'}"
|
||||
ToolTip="{Loc 'analysis-console-scan-tooltip-info'}">
|
||||
</Button>
|
||||
<BoxContainer MinHeight="10"></BoxContainer>
|
||||
<Button Name="DestroyButton"
|
||||
Text="{Loc 'analysis-console-destroy-button'}"
|
||||
ToolTip="{Loc 'analysis-console-destroy-button-info'}">
|
||||
</Button>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<Label Name="ProgressLabel"></Label>
|
||||
<ProgressBar
|
||||
Name="ProgressBar"
|
||||
MinValue="0"
|
||||
MaxValue="1"
|
||||
SetHeight="20">
|
||||
</ProgressBar>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<customControls:VSeparator StyleClasses="LowDivider"/>
|
||||
<PanelContainer Margin="10 10 10 10" HorizontalExpand="True" SizeFlagsStretchRatio="3">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#000000FF" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer Margin="10 10 10 10" Orientation="Horizontal">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" >
|
||||
<BoxContainer VerticalExpand="True">
|
||||
<RichTextLabel Name="Information"> </RichTextLabel>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<BoxContainer VerticalExpand="False" Orientation="Vertical" MaxSize="64 64">
|
||||
<SpriteView
|
||||
Name="ArtifactDisplay"
|
||||
OverrideDirection="South"
|
||||
VerticalExpand="False"
|
||||
SetSize="64 64"
|
||||
MaxSize="64 64"
|
||||
Scale="2 2">
|
||||
</SpriteView>
|
||||
</BoxContainer>
|
||||
<BoxContainer VerticalExpand="True"></BoxContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
157
Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml.cs
Normal file
157
Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml.cs
Normal file
@@ -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<BaseButton.ButtonEventArgs>? OnServerSelectionButtonPressed;
|
||||
public event Action<BaseButton.ButtonEventArgs>? OnScanButtonPressed;
|
||||
public event Action<BaseButton.ButtonEventArgs>? 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<SpriteComponent>(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<ArtifactTriggerPrototype>(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<ArtifactEffectPrototype>(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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<IEntityManager>();
|
||||
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<AudioSystem>()
|
||||
.PlayPvs(sound, ent, AudioParams.Default.WithVariation(0.125f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
Content.Server/Chemistry/ReagentEffects/ActivateArtifact.cs
Normal file
13
Content.Server/Chemistry/ReagentEffects/ActivateArtifact.cs
Normal file
@@ -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<ArtifactSystem>();
|
||||
artifact.TryActivateArtifact(args.SolutionEntity);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
46
Content.Server/StationEvents/Events/BluespaceArtifact.cs
Normal file
46
Content.Server/StationEvents/Events/BluespaceArtifact.cs
Normal file
@@ -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<string> 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}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations;
|
||||
|
||||
namespace Content.Server.Xenoarchaeology.Equipment.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Activecomp used for tracking artifact analyzers that are currently
|
||||
/// in the process of scanning an artifact.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ActiveArtifactAnalyzerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// When did the scanning start?
|
||||
/// </summary>
|
||||
[DataField("startTime", customTypeSerializer: typeof(TimespanSerializer))]
|
||||
public TimeSpan StartTime;
|
||||
|
||||
/// <summary>
|
||||
/// What is being scanned?
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid Artifact;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.Xenoarchaeology.Equipment.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for tracking artifacts that are currently
|
||||
/// being scanned by <see cref="ActiveArtifactAnalyzerComponent"/>
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ActiveScannedArtifactComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The scanner that is scanning this artifact
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid Scanner;
|
||||
|
||||
/// <summary>
|
||||
/// The sound that plays when the scan fails
|
||||
/// </summary>
|
||||
public readonly SoundSpecifier ScanFailureSound = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg");
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// The console that is used for artifact analysis
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class AnalysisConsoleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The analyzer entity the console is linked.
|
||||
/// Can be null if not linked.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public EntityUid? AnalyzerEntity;
|
||||
|
||||
/// <summary>
|
||||
/// The machine linking port for the analyzer
|
||||
/// </summary>
|
||||
[DataField("linkingPort", customTypeSerializer: typeof(PrototypeIdSerializer<TransmitterPortPrototype>))]
|
||||
public readonly string LinkingPort = "ArtifactAnalyzerSender";
|
||||
|
||||
/// <summary>
|
||||
/// The sound played when an artifact is destroyed.
|
||||
/// </summary>
|
||||
[DataField("destroySound")]
|
||||
public SoundSpecifier DestroySound = new SoundPathSpecifier("/Audio/Effects/radpulse11.ogg");
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// A machine that is combined and linked to the <see cref="AnalysisConsoleComponent"/>
|
||||
/// in order to analyze and destroy artifacts.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ArtifactAnalyzerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How long it takes to analyze an artifact
|
||||
/// </summary>
|
||||
[DataField("analysisDuration", customTypeSerializer: typeof(TimespanSerializer))]
|
||||
public TimeSpan AnalysisDuration = TimeSpan.FromSeconds(60);
|
||||
|
||||
/// <summary>
|
||||
/// A mulitplier on the duration of analysis.
|
||||
/// Used for machine upgrading.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float AnalysisDurationMulitplier = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The machine part that modifies analysis duration.
|
||||
/// </summary>
|
||||
[DataField("machinePartAnalysisDuration", customTypeSerializer: typeof(PrototypeIdSerializer<MachinePartPrototype>))]
|
||||
public string MachinePartAnalysisDuration = "ScanningModule";
|
||||
|
||||
/// <summary>
|
||||
/// The modifier raised to the part rating to determine the duration multiplier.
|
||||
/// </summary>
|
||||
[DataField("partRatingAnalysisDurationMultiplier")]
|
||||
public float PartRatingAnalysisDurationMultiplier = 0.75f;
|
||||
|
||||
/// <summary>
|
||||
/// The corresponding console entity.
|
||||
/// Can be null if not linked.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? Console;
|
||||
|
||||
/// <summary>
|
||||
/// All of the valid artifacts currently touching the analyzer.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public HashSet<EntityUid> 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
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Equipment.Components;
|
||||
namespace Content.Server.Xenoarchaeology.Equipment.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Suppress artifact activation, when entity is placed inside this container.
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// This system is used for managing the artifact analyzer as well as the analysis console.
|
||||
/// It also hanadles scanning and ui updates for both systems.
|
||||
/// </summary>
|
||||
public sealed class ArtifactAnalyzerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
[Dependency] private readonly ArtifactSystem _artifact = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ActiveScannedArtifactComponent, MoveEvent>(OnScannedMoved);
|
||||
SubscribeLocalEvent<ActiveScannedArtifactComponent, ArtifactActivatedEvent>(OnArtifactActivated);
|
||||
|
||||
SubscribeLocalEvent<ActiveArtifactAnalyzerComponent, ComponentStartup>(OnAnalyzeStart);
|
||||
SubscribeLocalEvent<ActiveArtifactAnalyzerComponent, ComponentShutdown>(OnAnalyzeEnd);
|
||||
SubscribeLocalEvent<ActiveArtifactAnalyzerComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
|
||||
SubscribeLocalEvent<ArtifactAnalyzerComponent, UpgradeExamineEvent>(OnUpgradeExamine);
|
||||
SubscribeLocalEvent<ArtifactAnalyzerComponent, RefreshPartsEvent>(OnRefreshParts);
|
||||
SubscribeLocalEvent<ArtifactAnalyzerComponent, StartCollideEvent>(OnCollide);
|
||||
SubscribeLocalEvent<ArtifactAnalyzerComponent, EndCollideEvent>(OnEndCollide);
|
||||
|
||||
SubscribeLocalEvent<AnalysisConsoleComponent, NewLinkEvent>(OnNewLink);
|
||||
SubscribeLocalEvent<AnalysisConsoleComponent, PortDisconnectedEvent>(OnPortDisconnected);
|
||||
|
||||
SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsoleServerSelectionMessage>(OnServerSelectionMessage);
|
||||
SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsoleScanButtonPressedMessage>(OnScanButton);
|
||||
SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsoleDestroyButtonPressedMessage>(OnDestroyButton);
|
||||
|
||||
SubscribeLocalEvent<AnalysisConsoleComponent, ResearchClientServerSelectedMessage>((e,c,_) => UpdateUserInterface(e,c),
|
||||
after: new []{typeof(ResearchSystem)});
|
||||
SubscribeLocalEvent<AnalysisConsoleComponent, ResearchClientServerDeselectedMessage>((e,c,_) => UpdateUserInterface(e,c),
|
||||
after: new []{typeof(ResearchSystem)});
|
||||
SubscribeLocalEvent<AnalysisConsoleComponent, BeforeActivatableUIOpenEvent>((e,c,_) => UpdateUserInterface(e,c));
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
foreach (var (active, scan) in EntityQuery<ActiveArtifactAnalyzerComponent, ArtifactAnalyzerComponent>())
|
||||
{
|
||||
if (scan.Console != null)
|
||||
UpdateUserInterface(scan.Console.Value);
|
||||
|
||||
if (_timing.CurTime - active.StartTime < (scan.AnalysisDuration * scan.AnalysisDurationMulitplier))
|
||||
continue;
|
||||
|
||||
FinishScan(scan.Owner, scan, active);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the current scan on the artifact analyzer
|
||||
/// </summary>
|
||||
/// <param name="uid">The analyzer being reset</param>
|
||||
/// <param name="component"></param>
|
||||
[PublicAPI]
|
||||
public void ResetAnalyzer(EntityUid uid, ArtifactAnalyzerComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
component.LastAnalyzedArtifact = null;
|
||||
UpdateAnalyzerInformation(uid, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Goes through the current contacts on
|
||||
/// the analyzer and returns a valid artifact
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <returns></returns>
|
||||
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<ArtifactComponent>).ToHashSet();
|
||||
return validEnts.FirstOrNull();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the current scan information based on
|
||||
/// the last artifact that was scanned.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
private void UpdateAnalyzerInformation(EntityUid uid, ArtifactAnalyzerComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (component.LastAnalyzedArtifact == null)
|
||||
{
|
||||
component.LastAnalyzedCompletion = null;
|
||||
component.LastAnalyzedNode = null;
|
||||
}
|
||||
else if (TryComp<ArtifactComponent>(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<ArtifactAnalyzerComponent>(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<ArtifactAnalyzerComponent>(component.AnalyzerEntity, out var analyzezr))
|
||||
analyzezr.Console = null;
|
||||
component.AnalyzerEntity = null;
|
||||
}
|
||||
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
|
||||
private void UpdateUserInterface(EntityUid uid, AnalysisConsoleComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
EntityUid? artifact = null;
|
||||
ArtifactNode? node = null;
|
||||
float? completion = null;
|
||||
var totalTime = TimeSpan.Zero;
|
||||
var canScan = false;
|
||||
if (component.AnalyzerEntity != null && TryComp<ArtifactAnalyzerComponent>(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<ResearchClientComponent>(uid, out var client) && client.ConnectedToServer;
|
||||
|
||||
var scanning = TryComp<ActiveArtifactAnalyzerComponent>(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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// opens the server selection menu.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <param name="args"></param>
|
||||
private void OnServerSelectionMessage(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleServerSelectionMessage args)
|
||||
{
|
||||
_ui.TryOpen(uid, ResearchClientUiKey.Key, (IPlayerSession) args.Session);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts scanning the artifact.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <param name="args"></param>
|
||||
private void OnScanButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleScanButtonPressedMessage args)
|
||||
{
|
||||
if (component.AnalyzerEntity == null)
|
||||
return;
|
||||
|
||||
if (HasComp<ActiveArtifactAnalyzerComponent>(component.AnalyzerEntity))
|
||||
return;
|
||||
|
||||
var ent = GetArtifactForAnalysis(component.AnalyzerEntity);
|
||||
if (ent == null)
|
||||
return;
|
||||
|
||||
var activeComp = EnsureComp<ActiveArtifactAnalyzerComponent>(component.AnalyzerEntity.Value);
|
||||
activeComp.StartTime = _timing.CurTime;
|
||||
activeComp.Artifact = ent.Value;
|
||||
|
||||
var activeArtifact = EnsureComp<ActiveScannedArtifactComponent>(ent.Value);
|
||||
activeArtifact.Scanner = component.AnalyzerEntity.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// destroys the artifact and updates the server points
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <param name="args"></param>
|
||||
private void OnDestroyButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleDestroyButtonPressedMessage args)
|
||||
{
|
||||
if (!TryComp<ResearchClientComponent>(uid, out var client) || client.Server == null || component.AnalyzerEntity == null)
|
||||
return;
|
||||
|
||||
var entToDestroy = GetArtifactForAnalysis(component.AnalyzerEntity);
|
||||
if (entToDestroy == null)
|
||||
return;
|
||||
|
||||
if (TryComp<ArtifactAnalyzerComponent>(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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels scans if the artifact changes nodes (is activated) during the scan.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <param name="args"></param>
|
||||
private void OnArtifactActivated(EntityUid uid, ActiveScannedArtifactComponent component, ArtifactActivatedEvent args)
|
||||
{
|
||||
CancelScan(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to make sure that the currently scanned artifact isn't moved off of the scanner
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <param name="args"></param>
|
||||
private void OnScannedMoved(EntityUid uid, ActiveScannedArtifactComponent component, ref MoveEvent args)
|
||||
{
|
||||
if (!TryComp<ArtifactAnalyzerComponent>(component.Scanner, out var analyzer))
|
||||
return;
|
||||
|
||||
if (analyzer.Contacts.Contains(uid))
|
||||
return;
|
||||
|
||||
CancelScan(uid, component, analyzer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the current scan
|
||||
/// </summary>
|
||||
/// <param name="artifact">The artifact being scanned</param>
|
||||
/// <param name="component"></param>
|
||||
/// <param name="analyzer">The artifact analyzer component</param>
|
||||
[PublicAPI]
|
||||
public void CancelScan(EntityUid artifact, ActiveScannedArtifactComponent? component = null, ArtifactAnalyzerComponent? analyzer = null)
|
||||
{
|
||||
if (!Resolve(artifact, ref component, false))
|
||||
return;
|
||||
|
||||
if (!Resolve(component.Scanner, ref analyzer))
|
||||
return;
|
||||
|
||||
_audio.PlayPvs(component.ScanFailureSound, component.Scanner, AudioParams.Default.WithVolume(3f));
|
||||
|
||||
RemComp<ActiveArtifactAnalyzerComponent>(component.Scanner);
|
||||
if (analyzer.Console != null)
|
||||
UpdateUserInterface(analyzer.Console.Value);
|
||||
|
||||
RemCompDeferred(artifact, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finishes the current scan.
|
||||
/// </summary>
|
||||
/// <param name="uid">The analyzer that is scanning</param>
|
||||
/// <param name="component"></param>
|
||||
/// <param name="active"></param>
|
||||
[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<ActiveScannedArtifactComponent>(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<ArtifactComponent>(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<ArtifactComponent>(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<ApcPowerReceiverComponent>(uid, out var powa))
|
||||
powa.NeedsPower = true;
|
||||
|
||||
if (TryComp<AmbientSoundComponent>(uid, out var ambientSound))
|
||||
{
|
||||
ambientSound.Enabled = true;
|
||||
Dirty(ambientSound);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAnalyzeEnd(EntityUid uid, ActiveArtifactAnalyzerComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (TryComp<ApcPowerReceiverComponent>(uid, out var powa))
|
||||
powa.NeedsPower = false;
|
||||
|
||||
if (TryComp<AmbientSoundComponent>(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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Artifacts go from 2k to 4k, 1.5k net profit (considering the container price
|
||||
/// </summary>
|
||||
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<StaticPriceComponent>(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<StaticPriceComponent>(args.Entity, out var price))
|
||||
{
|
||||
price.Price /= ContainedArtifactModifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Should artifact pick a random trigger on startup?
|
||||
/// The artifact's node tree.
|
||||
/// </summary>
|
||||
[DataField("randomTrigger")]
|
||||
public bool RandomTrigger = true;
|
||||
[ViewVariables]
|
||||
public ArtifactTree? NodeTree;
|
||||
|
||||
/// <summary>
|
||||
/// List of all possible triggers activations.
|
||||
/// Should be same as components names.
|
||||
/// The current node the artifact is on.
|
||||
/// </summary>
|
||||
[DataField("possibleTriggers")]
|
||||
public string[] PossibleTriggers = {
|
||||
"ArtifactInteractionTrigger",
|
||||
"ArtifactGasTrigger",
|
||||
"ArtifactHeatTrigger",
|
||||
"ArtifactElectricityTrigger",
|
||||
};
|
||||
[ViewVariables]
|
||||
public ArtifactNode? CurrentNode;
|
||||
|
||||
#region Node Tree Gen
|
||||
/// <summary>
|
||||
/// Minimum number of nodes to generate, inclusive
|
||||
/// </summary>
|
||||
[DataField("nodesMin")]
|
||||
public int NodesMin = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of nodes to generate, exclusive
|
||||
/// </summary>
|
||||
[DataField("nodesMax")]
|
||||
public int NodesMax = 9;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Cooldown time between artifact activations (in seconds).
|
||||
/// </summary>
|
||||
[DataField("timer")]
|
||||
[DataField("timer", customTypeSerializer: typeof(TimespanSerializer))]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public double CooldownTime = 10;
|
||||
public TimeSpan CooldownTime = TimeSpan.FromSeconds(5);
|
||||
|
||||
/// <summary>
|
||||
/// Is this artifact under some suppression device?
|
||||
/// If true, will ignore all trigger activations attempts.
|
||||
/// f true, will ignore all trigger activations attempts.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool IsSuppressed;
|
||||
|
||||
/// <summary>
|
||||
/// The last time the artifact was activated.
|
||||
/// </summary>
|
||||
[DataField("lastActivationTime", customTypeSerializer: typeof(TimespanSerializer))]
|
||||
public TimeSpan LastActivationTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A tree of nodes.
|
||||
/// </summary>
|
||||
[DataDefinition]
|
||||
public sealed class ArtifactTree
|
||||
{
|
||||
/// <summary>
|
||||
/// The first node of the tree
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public ArtifactNode StartNode = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Every node contained in the tree
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public readonly List<ArtifactNode> AllNodes = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A single "node" of an artifact that contains various data about it.
|
||||
/// </summary>
|
||||
[DataDefinition]
|
||||
public sealed class ArtifactNode : ICloneable
|
||||
{
|
||||
/// <summary>
|
||||
/// A numeric id corresponding to each node. used for display purposes
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int Id;
|
||||
|
||||
/// <summary>
|
||||
/// how "deep" into the node tree. used for generation and price/value calculations
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int Depth = 0;
|
||||
|
||||
/// <summary>
|
||||
/// A list of surrounding nodes. Used for tree traversal
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public List<ArtifactNode> Edges = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the node has been entered
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Discovered = false;
|
||||
|
||||
/// <summary>
|
||||
/// The trigger for the node
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public ArtifactTriggerPrototype Trigger = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the node has been triggered
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Triggered = false;
|
||||
|
||||
/// <summary>
|
||||
/// The effect when the node is activated
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public ArtifactEffectPrototype Effect = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Used for storing cumulative information about nodes
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Dictionary<string, object> NodeData = new();
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
return new ArtifactNode
|
||||
{
|
||||
Id = Id,
|
||||
Depth = Depth,
|
||||
Edges = Edges,
|
||||
Discovered = Discovered,
|
||||
Trigger = Trigger,
|
||||
Triggered = Triggered,
|
||||
Effect = Effect,
|
||||
NodeData = NodeData
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Generate an Artifact tree with fully developed nodes.
|
||||
/// </summary>
|
||||
/// <param name="tree">The tree being generated.</param>
|
||||
/// <param name="nodeAmount">The amount of nodes it has.</param>
|
||||
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<ArtifactNode> { new() };
|
||||
tree.StartNode = uninitializedNodes.First(); //the first node
|
||||
|
||||
while (uninitializedNodes.Any())
|
||||
{
|
||||
GenerateNode(ref uninitializedNodes, ref tree, nodeAmount);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate an individual node on the tree.
|
||||
/// </summary>
|
||||
private void GenerateNode(ref List<ArtifactNode> 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<ArtifactTriggerPrototype>().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<ArtifactEffectPrototype>().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);
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// The goal is that the depth that is closest to targetDepth has the highest chance of appearing.
|
||||
/// The issue is that we also want some variance, so levels that are +/- 1 should also have a
|
||||
/// decent shot of appearing. This function should probably get some tweaking at some point.
|
||||
/// </remarks>
|
||||
private Dictionary<int, float> GetDepthWeights(IEnumerable<int> depths, int targetDepth)
|
||||
{
|
||||
var weights = new Dictionary<int, float>();
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses a weighted random system to get a random depth.
|
||||
/// </summary>
|
||||
private int GetRandomTargetDepth(Dictionary<int, float> weights)
|
||||
{
|
||||
var sum = weights.Values.Sum();
|
||||
var accumulated = 0f;
|
||||
|
||||
var rand = _random.NextFloat() * sum;
|
||||
|
||||
foreach (var (key, weight) in weights)
|
||||
{
|
||||
accumulated += weight;
|
||||
|
||||
if (accumulated >= rand)
|
||||
{
|
||||
return key;
|
||||
}
|
||||
}
|
||||
return _random.Pick(weights.Keys); //shouldn't happen
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enter a node: attach the relevant components
|
||||
/// </summary>
|
||||
private void EnterNode(EntityUid uid, ref ArtifactNode node, ArtifactComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (component.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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exit a node: remove the relevant components.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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<ArtifactComponent, MapInitEvent>(OnInit);
|
||||
SubscribeLocalEvent<ArtifactComponent, PriceCalculationEvent>(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)
|
||||
/// <summary>
|
||||
/// Calculates the price of an artifact based on
|
||||
/// how many nodes have been unlocked/triggered
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// General balancing (for fully unlocked artifacts):
|
||||
/// Simple (1-2 Nodes): 1-2K
|
||||
/// Medium (5-8 Nodes): 6-7K
|
||||
/// Complex (7-12 Nodes): 10-11K
|
||||
/// </remarks>
|
||||
private void GetPrice(EntityUid uid, ArtifactComponent component, ref PriceCalculationEvent args)
|
||||
{
|
||||
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()))
|
||||
// 25% bonus for fully exploring every node.
|
||||
var fullyExploredBonus = component.NodeTree.AllNodes.Any(x => !x.Triggered) ? 1 : 1.25f;
|
||||
|
||||
args.Price =+ price * fullyExploredBonus;
|
||||
}
|
||||
|
||||
private float GetNodePrice(ArtifactNode node)
|
||||
{
|
||||
Logger.Error($"Attempted to add a random artifact trigger ({triggerName}) to an entity ({ToPrettyString(uid)}), but it already has the trigger");
|
||||
return;
|
||||
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;
|
||||
}
|
||||
|
||||
EntityManager.AddComponent(uid, trigger);
|
||||
RaiseLocalEvent(uid, new RandomizeTriggerEvent(), true);
|
||||
/// <summary>
|
||||
/// Calculates how many research points the artifact is worht
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Rebalance this shit at some point. Definitely OP.
|
||||
/// </remarks>
|
||||
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;
|
||||
}
|
||||
|
||||
public bool TryActivateArtifact(EntityUid uid, EntityUid? user = null,
|
||||
ArtifactComponent? component = null)
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Randomize a given artifact.
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to activate the artifact
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <returns></returns>
|
||||
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)
|
||||
/// <summary>
|
||||
/// Forces an artifact to activate
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="component"></param>
|
||||
public void ForceActivateArtifact(EntityUid uid, EntityUid? user = null, ArtifactComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
if (component.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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try and get a data object from a node
|
||||
/// </summary>
|
||||
/// <param name="uid">The entity you're getting the data from</param>
|
||||
/// <param name="key">The data's key</param>
|
||||
/// <param name="data">The data you are trying to get.</param>
|
||||
/// <param name="component"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public bool TryGetNodeData<T>(EntityUid uid, string key, [NotNullWhen(true)] out T data, ArtifactComponent? component = null)
|
||||
{
|
||||
data = default!;
|
||||
|
||||
if (!Resolve(uid, ref component))
|
||||
return false;
|
||||
|
||||
if (component.CurrentNode == null)
|
||||
return false;
|
||||
|
||||
if (component.CurrentNode.NodeData.TryGetValue(key, out var dat) && dat is T value)
|
||||
{
|
||||
data = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the node data to a certain value
|
||||
/// </summary>
|
||||
/// <param name="uid">The artifact</param>
|
||||
/// <param name="key">The key being set</param>
|
||||
/// <param name="value">The value it's being set to</param>
|
||||
/// <param name="component"></param>
|
||||
public void SetNodeData(EntityUid uid, string key, object value, ArtifactComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (component.CurrentNode == null)
|
||||
return;
|
||||
|
||||
component.CurrentNode.NodeData[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Whitelist;
|
||||
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
||||
|
||||
/// <summary>
|
||||
/// When activated, damages nearby entities.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class DamageNearbyArtifactComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The radius of entities that will be affected
|
||||
/// </summary>
|
||||
[DataField("radius")]
|
||||
public float Radius = 3f;
|
||||
|
||||
/// <summary>
|
||||
/// A whitelist for filtering certain damage.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// TODO: The component portion, since it uses an array, does not work currently.
|
||||
/// </remarks>
|
||||
[DataField("whitelist")]
|
||||
public EntityWhitelist? Whitelist;
|
||||
|
||||
/// <summary>
|
||||
/// The damage that is applied
|
||||
/// </summary>
|
||||
[DataField("damage", required: true)]
|
||||
public DamageSpecifier Damage = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The chance that damage is applied to each individual entity
|
||||
/// </summary>
|
||||
[DataField("damageChance")]
|
||||
public float DamageChance = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not this should ignore resistances for the damage
|
||||
/// </summary>
|
||||
[DataField("ignoreResistances")]
|
||||
public bool IgnoreResistances;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Disease;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
||||
/// <summary>
|
||||
@@ -8,10 +9,15 @@ namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
||||
public sealed class DiseaseArtifactComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Disease the artifact will spawn
|
||||
/// If empty, picks a random one from its list
|
||||
/// The diseases that the artifact can use.
|
||||
/// </summary>
|
||||
[DataField("diseasePrototype", customTypeSerializer: typeof(PrototypeIdListSerializer<DiseasePrototype>))]
|
||||
public List<string> DiseasePrototypes = new();
|
||||
|
||||
/// <summary>
|
||||
/// Disease the artifact will spawn
|
||||
/// Picks a random one from its list
|
||||
/// </summary>
|
||||
[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
|
||||
/// </summary>
|
||||
[DataField("range")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("range"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Range = 5f;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Generates foam from the artifact when activated
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class FoamArtifactComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of reagents that will randomly be picked from
|
||||
/// to choose the foam reagent
|
||||
/// </summary>
|
||||
[DataField("reagents", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<ReagentPrototype>))]
|
||||
public List<string> Reagents = new();
|
||||
|
||||
/// <summary>
|
||||
/// The foam reagent
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string? SelectedReagent;
|
||||
|
||||
/// <summary>
|
||||
/// How long does the foam last?
|
||||
/// </summary>
|
||||
[DataField("duration")]
|
||||
public float Duration = 10;
|
||||
|
||||
/// <summary>
|
||||
/// How much reagent is in the foam?
|
||||
/// </summary>
|
||||
[DataField("reagentAmount")]
|
||||
public float ReagentAmount = 100;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum radius of foam spawned
|
||||
/// </summary>
|
||||
[DataField("minFoamAmount")]
|
||||
public int MinFoamAmount = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum radius of foam spawned
|
||||
/// </summary>
|
||||
[DataField("maxFoamAmount")]
|
||||
public int MaxFoamAmount = 6;
|
||||
|
||||
/// <summary>
|
||||
/// How long it takes for each tile of foam to spawn
|
||||
/// </summary>
|
||||
[DataField("spreadDuration")]
|
||||
public float SpreadDuration = 1;
|
||||
}
|
||||
@@ -20,7 +20,7 @@ public sealed class GasArtifactComponent : Component
|
||||
/// List of possible activation gases to pick on startup.
|
||||
/// </summary>
|
||||
[DataField("possibleGas")]
|
||||
public Gas[] PossibleGases =
|
||||
public List<Gas> PossibleGases = new()
|
||||
{
|
||||
Gas.Oxygen,
|
||||
Gas.Plasma,
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for using the "knock" spell when the artifact is activated
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class KnockArtifactComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The range of the spell
|
||||
/// </summary>
|
||||
[DataField("knockRange")]
|
||||
public float KnockRange = 4f;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Flickers all the lights within a certain radius.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class LightFlickerArtifactComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Lights within this radius will be flickered on activation
|
||||
/// </summary>
|
||||
[DataField("radius")]
|
||||
public float Radius = 4;
|
||||
|
||||
/// <summary>
|
||||
/// The chance that the light will flicker
|
||||
/// </summary>
|
||||
[DataField("flickerChance")]
|
||||
public float FlickerChance = 0.75f;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Spawn RadiationPulse when artifact activated.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class RadiateArtifactComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Radiation pulse prototype to spawn.
|
||||
/// </summary>
|
||||
[DataField("pulsePrototype", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string PulsePrototype = "RadiationPulse";
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
||||
|
||||
/// <summary>
|
||||
/// When activated, will teleport the artifact
|
||||
/// to a random position within a certain radius
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class RandomTeleportArtifactComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The max distance that the artifact will teleport.
|
||||
/// </summary>
|
||||
[DataField("range")]
|
||||
public float Range = 7.5f;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
||||
|
||||
/// <summary>
|
||||
/// When activated, will shuffle the position of all players
|
||||
/// within a certain radius.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ShuffleArtifactComponent : Component
|
||||
{
|
||||
[DataField("radius")]
|
||||
public float Radius = 7.5f;
|
||||
}
|
||||
@@ -11,21 +11,35 @@ namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
||||
[RegisterComponent]
|
||||
public sealed class SpawnArtifactComponent : Component
|
||||
{
|
||||
[DataField("random")]
|
||||
public bool RandomPrototype = true;
|
||||
|
||||
/// <summary>
|
||||
/// The list of possible prototypes to spawn that it picks from.
|
||||
/// </summary>
|
||||
[DataField("possiblePrototypes", customTypeSerializer:typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
||||
public List<string> PossiblePrototypes = new();
|
||||
|
||||
/// <summary>
|
||||
/// The prototype it selected to spawn.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("prototype", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? Prototype;
|
||||
|
||||
/// <summary>
|
||||
/// The range around the artifact that it will spawn the entity
|
||||
/// </summary>
|
||||
[DataField("range")]
|
||||
public float Range = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum number of times the spawn will occur
|
||||
/// </summary>
|
||||
[DataField("maxSpawns")]
|
||||
public int MaxSpawns = 20;
|
||||
|
||||
public int SpawnsCount = 0;
|
||||
/// <summary>
|
||||
/// Whether or not the artifact spawns the same entity every time
|
||||
/// or picks through the list each time.
|
||||
/// </summary>
|
||||
[DataField("consistentSpawn")]
|
||||
public bool ConsistentSpawn = true;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ public sealed class TelepathicArtifactComponent : Component
|
||||
/// </summary>
|
||||
[DataField("messages")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string[] Messages = default!;
|
||||
public List<string> Messages = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Loc string ids of telepathic messages (spooky version).
|
||||
@@ -21,7 +21,7 @@ public sealed class TelepathicArtifactComponent : Component
|
||||
/// </summary>
|
||||
[DataField("drastic")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string[] DrasticMessages = default!;
|
||||
public List<string>? DrasticMessages;
|
||||
|
||||
/// <summary>
|
||||
/// Probability to pick drastic version of message.
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
[DataField("effectAdjacent")]
|
||||
public bool EffectAdjacentTiles = true;
|
||||
[DataField("affectAdjacent")]
|
||||
public bool AffectAdjacentTiles = true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Throws all nearby entities backwards.
|
||||
/// Also pries nearby tiles.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ThrowArtifactComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How close do you have to be to get yeeted?
|
||||
/// </summary>
|
||||
[DataField("range")]
|
||||
public float Range = 2f;
|
||||
|
||||
/// <summary>
|
||||
/// How likely is it that an individual tile will get pried?
|
||||
/// </summary>
|
||||
[DataField("tilePryChance")]
|
||||
public float TilePryChance = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// How strongly does stuff get thrown?
|
||||
/// </summary>
|
||||
[DataField("throwStrength"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float ThrowStrength = 5f;
|
||||
}
|
||||
@@ -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!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<DamageNearbyArtifactComponent, ArtifactActivatedEvent>(OnActivated);
|
||||
}
|
||||
|
||||
private void OnActivated(EntityUid uid, DamageNearbyArtifactComponent component, ArtifactActivatedEvent args)
|
||||
{
|
||||
var ents = _lookup.GetEntitiesInRange(uid, component.Radius);
|
||||
if (args.Activator != null)
|
||||
ents.Add(args.Activator.Value);
|
||||
foreach (var ent in ents)
|
||||
{
|
||||
if (component.Whitelist != null && !component.Whitelist.IsValid(ent))
|
||||
continue;
|
||||
|
||||
if (!_random.Prob(component.DamageChance))
|
||||
return;
|
||||
|
||||
_damageable.TryChangeDamage(ent, component.Damage, component.IgnoreResistances);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<string> ArtifactDiseases = new[]
|
||||
{
|
||||
"VanAusdallsRobovirus",
|
||||
"OwOnavirus",
|
||||
"BleedersBite",
|
||||
"Ultragigacancer",
|
||||
"MemeticAmirmir",
|
||||
"TongueTwister",
|
||||
"AMIV"
|
||||
};
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<DiseaseArtifactComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<DiseaseArtifactComponent, ArtifactNodeEnteredEvent>(OnNodeEntered);
|
||||
SubscribeLocalEvent<DiseaseArtifactComponent, ArtifactActivatedEvent>(OnActivate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes sure this artifact is assigned a disease
|
||||
/// </summary>
|
||||
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<DiseasePrototype>(diseaseName, out var disease))
|
||||
{
|
||||
|
||||
@@ -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!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<FoamArtifactComponent, ArtifactNodeEnteredEvent>(OnNodeEntered);
|
||||
SubscribeLocalEvent<FoamArtifactComponent, ArtifactActivatedEvent>(OnActivated);
|
||||
}
|
||||
|
||||
private void OnNodeEntered(EntityUid uid, FoamArtifactComponent component, ArtifactNodeEnteredEvent args)
|
||||
{
|
||||
if (!component.Reagents.Any())
|
||||
return;
|
||||
|
||||
component.SelectedReagent = component.Reagents[args.RandomSeed % component.Reagents.Count];
|
||||
}
|
||||
|
||||
private void OnActivated(EntityUid uid, FoamArtifactComponent component, ArtifactActivatedEvent args)
|
||||
{
|
||||
if (component.SelectedReagent == null)
|
||||
return;
|
||||
|
||||
var sol = new Solution();
|
||||
var xform = Transform(uid);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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<GasArtifactComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<GasArtifactComponent, ArtifactNodeEnteredEvent>(OnNodeEntered);
|
||||
SubscribeLocalEvent<GasArtifactComponent, ArtifactActivatedEvent>(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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<KnockArtifactComponent, ArtifactActivatedEvent>(OnActivated);
|
||||
}
|
||||
|
||||
private void OnActivated(EntityUid uid, KnockArtifactComponent component, ArtifactActivatedEvent args)
|
||||
{
|
||||
var ev = new KnockSpellEvent
|
||||
{
|
||||
Performer = uid,
|
||||
Range = component.KnockRange
|
||||
};
|
||||
RaiseLocalEvent(ev);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// This handles...
|
||||
/// </summary>
|
||||
public sealed class LightFlickerArtifactSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly GhostSystem _ghost = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<LightFlickerArtifactComponent, ArtifactActivatedEvent>(OnActivated);
|
||||
}
|
||||
|
||||
private void OnActivated(EntityUid uid, LightFlickerArtifactComponent component, ArtifactActivatedEvent args)
|
||||
{
|
||||
var lights = GetEntityQuery<PoweredLightComponent>();
|
||||
foreach (var light in _lookup.GetEntitiesInRange(uid, component.Radius, LookupFlags.StaticSundries ))
|
||||
{
|
||||
if (!lights.HasComponent(light))
|
||||
continue;
|
||||
|
||||
if (!_random.Prob(component.FlickerChance))
|
||||
continue;
|
||||
|
||||
_ghost.DoGhostBooEvent(light);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,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<RadiateArtifactComponent, ArtifactActivatedEvent>(OnActivate);
|
||||
}
|
||||
|
||||
private void OnActivate(EntityUid uid, RadiateArtifactComponent component, ArtifactActivatedEvent args)
|
||||
{
|
||||
var transform = Transform(uid);
|
||||
EntityManager.SpawnEntity(component.PulsePrototype, transform.Coordinates);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// This handles...
|
||||
/// </summary>
|
||||
public sealed class RandomTeleportArtifactSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<RandomTeleportArtifactComponent, ArtifactActivatedEvent>(OnActivate);
|
||||
}
|
||||
|
||||
private void OnActivate(EntityUid uid, RandomTeleportArtifactComponent component, ArtifactActivatedEvent args)
|
||||
{
|
||||
var xform = Transform(uid);
|
||||
_popup.PopupCoordinates(Loc.GetString("blink-artifact-popup"), xform.Coordinates, Filter.Pvs(uid), PopupType.Medium);
|
||||
|
||||
xform.Coordinates = xform.Coordinates.Offset(_random.NextVector2(component.Range));
|
||||
}
|
||||
}
|
||||
@@ -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!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ShuffleArtifactComponent, ArtifactActivatedEvent>(OnActivated);
|
||||
}
|
||||
|
||||
private void OnActivated(EntityUid uid, ShuffleArtifactComponent component, ArtifactActivatedEvent args)
|
||||
{
|
||||
var mobState = GetEntityQuery<MobStateComponent>();
|
||||
|
||||
List<EntityCoordinates> allCoords = new();
|
||||
List<TransformComponent> toShuffle = new();
|
||||
|
||||
foreach (var ent in _lookup.GetEntitiesInRange(uid, component.Radius, LookupFlags.Dynamic | LookupFlags.Sundries))
|
||||
{
|
||||
if (!mobState.HasComponent(ent))
|
||||
continue;
|
||||
|
||||
var xform = Transform(ent);
|
||||
|
||||
toShuffle.Add(xform);
|
||||
allCoords.Add(xform.Coordinates);
|
||||
}
|
||||
|
||||
foreach (var xform in toShuffle)
|
||||
{
|
||||
xform.Coordinates = _random.PickAndTake(allCoords);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<SpawnArtifactComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<SpawnArtifactComponent, ArtifactNodeEnteredEvent>(OnNodeEntered);
|
||||
SubscribeLocalEvent<SpawnArtifactComponent, ArtifactActivatedEvent>(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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<string> 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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ThrowArtifactComponent, ArtifactActivatedEvent>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,3 +12,20 @@ public sealed class ArtifactActivatedEvent : EntityEventArgs
|
||||
/// </summary>
|
||||
public EntityUid? Activator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force to randomize artifact triggers.
|
||||
/// </summary>
|
||||
public sealed class ArtifactNodeEnteredEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// An entity-specific seed that can be used to
|
||||
/// generate random values.
|
||||
/// </summary>
|
||||
public readonly int RandomSeed;
|
||||
|
||||
public ArtifactNodeEnteredEvent(int randomSeed)
|
||||
{
|
||||
RandomSeed = randomSeed;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Force to randomize artifact triggers.
|
||||
/// </summary>
|
||||
public sealed class RandomizeTriggerEvent : EntityEventArgs
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Triggers when an artifact is anchored
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Not every trigger can be a winner
|
||||
/// </remarks>
|
||||
[RegisterComponent]
|
||||
public sealed class ArtifactAnchorTriggerComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Triggers when a certain threshold of damage of certain types is reached
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ArtifactDamageTriggerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// What damage types are accumulated for the trigger?
|
||||
/// </summary>
|
||||
[DataField("damageTypes", customTypeSerializer: typeof(PrototypeIdListSerializer<DamageTypePrototype>))]
|
||||
public List<string>? DamageTypes;
|
||||
|
||||
/// <summary>
|
||||
/// What threshold has to be reached before it is activated?
|
||||
/// </summary>
|
||||
[DataField("damageThreshold", required: true)]
|
||||
public float DamageThreshold;
|
||||
|
||||
/// <summary>
|
||||
/// How much damage has been accumulated on the artifact so far
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float AccumulatedDamage = 0;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Triggers when a nearby entity dies
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ArtifactDeathTriggerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How close to the death the artifact has to be for it to trigger.
|
||||
/// </summary>
|
||||
[DataField("range")]
|
||||
public float Range = 15f;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Triggers when the artifact is examined.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ArtifactExamineTriggerComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -12,7 +12,7 @@ public sealed class ArtifactGasTriggerComponent : Component
|
||||
/// List of possible activation gases to pick on startup.
|
||||
/// </summary>
|
||||
[DataField("possibleGas")]
|
||||
public Gas[] PossibleGases =
|
||||
public List<Gas> PossibleGases = new()
|
||||
{
|
||||
Gas.Oxygen,
|
||||
Gas.Plasma,
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Triggers when the salvage magnet is activated
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ArtifactMagnetTriggerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// how close to the magnet do you have to be?
|
||||
/// </summary>
|
||||
[DataField("range")]
|
||||
public float Range = 40f;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Triggers when an instrument is played nearby
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ArtifactMusicTriggerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// how close does the artifact have to be to the instrument to activate
|
||||
/// </summary>
|
||||
[DataField("range")]
|
||||
public float Range = 5;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Triggers when a certain pressure threshold is hit
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ArtifactPressureTriggerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The lower-end pressure threshold
|
||||
/// </summary>
|
||||
[DataField("minPressureThreshold")]
|
||||
public float? MinPressureThreshold;
|
||||
|
||||
/// <summary>
|
||||
/// The higher-end pressure threshold
|
||||
/// </summary>
|
||||
[DataField("maxPressureThreshold")]
|
||||
public float? MaxPressureThreshold;
|
||||
}
|
||||
@@ -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!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ArtifactAnchorTriggerComponent, AnchorStateChangedEvent>(OnAnchorStateChanged);
|
||||
}
|
||||
|
||||
private void OnAnchorStateChanged(EntityUid uid, ArtifactAnchorTriggerComponent component, ref AnchorStateChangedEvent args)
|
||||
{
|
||||
if (args.Detaching)
|
||||
return;
|
||||
|
||||
_artifact.TryActivateArtifact(uid);
|
||||
}
|
||||
}
|
||||
@@ -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!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ArtifactDamageTriggerComponent, DamageChangedEvent>(OnDamageChanged);
|
||||
}
|
||||
|
||||
private void OnDamageChanged(EntityUid uid, ArtifactDamageTriggerComponent component, DamageChangedEvent args)
|
||||
{
|
||||
if (!args.DamageIncreased)
|
||||
return;
|
||||
|
||||
if (args.DamageDelta == null)
|
||||
return;
|
||||
|
||||
foreach (var (type, amount) in args.DamageDelta.DamageDict)
|
||||
{
|
||||
if (component.DamageTypes != null && !component.DamageTypes.Contains(type))
|
||||
continue;
|
||||
|
||||
component.AccumulatedDamage += (float) amount;
|
||||
}
|
||||
|
||||
if (component.AccumulatedDamage >= component.DamageThreshold)
|
||||
_artifact.TryActivateArtifact(uid, args.Origin);
|
||||
}
|
||||
}
|
||||
@@ -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!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<MobStateChangedEvent>(OnMobStateChanged);
|
||||
}
|
||||
|
||||
private void OnMobStateChanged(MobStateChangedEvent ev)
|
||||
{
|
||||
if (ev.CurrentMobState != DamageState.Dead)
|
||||
return;
|
||||
|
||||
var deathXform = Transform(ev.Entity);
|
||||
|
||||
var toActivate = new List<ArtifactDeathTriggerComponent>();
|
||||
foreach (var (trigger, xform) in EntityQuery<ArtifactDeathTriggerComponent, TransformComponent>())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,13 +20,18 @@ public sealed class ArtifactElectricityTriggerSystem : EntitySystem
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
var query = EntityManager.EntityQuery<ArtifactElectricityTriggerComponent, PowerConsumerComponent, ArtifactComponent>();
|
||||
foreach (var (trigger, power, artifact) in query)
|
||||
List<ArtifactComponent> toUpdate = new();
|
||||
foreach (var (trigger, power, artifact) in EntityQuery<ArtifactElectricityTriggerComponent, PowerConsumerComponent, ArtifactComponent>())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ArtifactExamineTriggerComponent, ExaminedEvent>(OnExamine);
|
||||
}
|
||||
|
||||
private void OnExamine(EntityUid uid, ArtifactExamineTriggerComponent component, ExaminedEvent args)
|
||||
{
|
||||
_artifact.TryActivateArtifact(uid);
|
||||
}
|
||||
}
|
||||
@@ -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<ArtifactGasTriggerComponent, RandomizeTriggerEvent>(OnRandomizeTrigger);
|
||||
SubscribeLocalEvent<ArtifactGasTriggerComponent, ArtifactNodeEnteredEvent>(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);
|
||||
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<ArtifactGasTriggerComponent, TransformComponent>();
|
||||
foreach (var (trigger, transform) in query)
|
||||
|
||||
List<ArtifactComponent> toUpdate = new();
|
||||
foreach (var (trigger, artifact, transform) in EntityQuery<ArtifactGasTriggerComponent, ArtifactComponent, TransformComponent>())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ArtifactHeatTriggerComponent, TransformComponent, ArtifactComponent>();
|
||||
foreach (var (trigger, transform, artifact) in query)
|
||||
List<ArtifactComponent> toUpdate = new();
|
||||
foreach (var (trigger, transform, artifact) in EntityQuery<ArtifactHeatTriggerComponent, TransformComponent, ArtifactComponent>())
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using Content.Server.Salvage;
|
||||
using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
|
||||
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// This handles...
|
||||
/// </summary>
|
||||
public sealed class ArtifactMagnetTriggerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ArtifactSystem _artifact = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<SalvageMagnetActivatedEvent>(OnMagnetActivated);
|
||||
}
|
||||
|
||||
private void OnMagnetActivated(SalvageMagnetActivatedEvent ev)
|
||||
{
|
||||
var magXform = Transform(ev.Magnet);
|
||||
|
||||
var toActivate = new List<EntityUid>();
|
||||
foreach (var (artifact, xform) in EntityQuery<ArtifactMagnetTriggerComponent, TransformComponent>())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// This handles activating an artifact when music is playing nearby
|
||||
/// </summary>
|
||||
public sealed class ArtifactMusicTriggerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ArtifactSystem _artifact = default!;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var artifactQuery = EntityQuery<ArtifactMusicTriggerComponent, TransformComponent>().ToArray();
|
||||
if (!artifactQuery.Any())
|
||||
return;
|
||||
|
||||
List<EntityUid> toActivate = new();
|
||||
|
||||
//assume that there's more instruments than artifacts
|
||||
foreach (var activeinstrument in EntityQuery<ActiveInstrumentComponent>())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// This handles activation upon certain pressure thresholds.
|
||||
/// </summary>
|
||||
public sealed class ArtifactPressureTriggerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
[Dependency] private readonly ArtifactSystem _artifactSystem = default!;
|
||||
[Dependency] private readonly TransformSystem _transformSystem = default!;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
List<ArtifactComponent> toUpdate = new();
|
||||
foreach (var (trigger, artifact, transform) in EntityQuery<ArtifactPressureTriggerComponent, ArtifactComponent, TransformComponent>())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<ArtifactTimerTriggerComponent, ComponentStartup>(OnStartup);
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, ArtifactTimerTriggerComponent component, ComponentStartup args)
|
||||
{
|
||||
component.LastActivation = _time.CurTime;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityManager.EntityQuery<ArtifactTimerTriggerComponent, ArtifactComponent>();
|
||||
foreach (var (trigger, artifact) in query)
|
||||
List<ArtifactComponent> toUpdate = new();
|
||||
foreach (var (trigger, artifact) in EntityQuery<ArtifactTimerTriggerComponent, ArtifactComponent>())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Xenoarchaeology.XenoArtifacts;
|
||||
|
||||
/// <summary>
|
||||
/// This is a prototype for...
|
||||
/// </summary>
|
||||
[Prototype("artifactEffect")]
|
||||
[DataDefinition]
|
||||
public sealed class ArtifactEffectPrototype : IPrototype
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Components that are added to the artifact when the specfic effect is active.
|
||||
/// These are removed after the node is exited and the effect is changed.
|
||||
/// </summary>
|
||||
[DataField("components", serverOnly: true)]
|
||||
public EntityPrototype.ComponentRegistry Components = new();
|
||||
|
||||
/// <summary>
|
||||
/// Components that are permanently added to an entity when the effect's node is entered.
|
||||
/// </summary>
|
||||
[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;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Xenoarchaeology.XenoArtifacts;
|
||||
|
||||
/// <summary>
|
||||
/// This is a prototype for...
|
||||
/// </summary>
|
||||
[Prototype("artifactTrigger")]
|
||||
[DataDefinition]
|
||||
public sealed class ArtifactTriggerPrototype : IPrototype
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[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;
|
||||
}
|
||||
BIN
Resources/Audio/Effects/Lightning/lightningbolt.ogg
Normal file
BIN
Resources/Audio/Effects/Lightning/lightningbolt.ogg
Normal file
Binary file not shown.
@@ -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
|
||||
BIN
Resources/Audio/Machines/scan_finish.ogg
Normal file
BIN
Resources/Audio/Machines/scan_finish.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Machines/scan_loop.ogg
Normal file
BIN
Resources/Audio/Machines/scan_loop.ogg
Normal file
Binary file not shown.
@@ -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
|
||||
|
||||
@@ -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
|
||||
29
Resources/Locale/en-US/xenoarchaeology/artifact-analyzer.ftl
Normal file
29
Resources/Locale/en-US/xenoarchaeology/artifact-analyzer.ftl
Normal file
@@ -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!
|
||||
27
Resources/Locale/en-US/xenoarchaeology/artifact-hints.ftl
Normal file
27
Resources/Locale/en-US/xenoarchaeology/artifact-hints.ftl
Normal file
@@ -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
|
||||
4
Resources/Locale/en-US/xenoarchaeology/misc-artifact.ftl
Normal file
4
Resources/Locale/en-US/xenoarchaeology/misc-artifact.ftl
Normal file
@@ -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!
|
||||
@@ -749,7 +749,7 @@ entities:
|
||||
parent: 52
|
||||
type: Transform
|
||||
- uid: 79
|
||||
type: AngryMobsSpawnArtifact
|
||||
type: RandomArtifactSpawner
|
||||
components:
|
||||
- pos: -0.5,-0.5
|
||||
parent: 52
|
||||
|
||||
@@ -207974,7 +207974,7 @@ entities:
|
||||
parent: 82
|
||||
type: Transform
|
||||
- uid: 25103
|
||||
type: JunkSpawnArtifact
|
||||
type: RandomArtifactSpawner
|
||||
components:
|
||||
- pos: -69.5,-47.5
|
||||
parent: 82
|
||||
|
||||
@@ -129076,7 +129076,7 @@ entities:
|
||||
parent: 30
|
||||
type: Transform
|
||||
- uid: 15282
|
||||
type: BananaSpawnArtifact
|
||||
type: RandomArtifactSpawner
|
||||
components:
|
||||
- pos: 36.5,12.5
|
||||
parent: 30
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -157,6 +157,8 @@
|
||||
- id: DoorRemoteResearch
|
||||
- id: RubberStampRd
|
||||
- id: ClothingHeadsetAltScience
|
||||
- id: AnalysisComputerCircuitboard
|
||||
- id: ArtifactAnalyzerMachineCircuitboard
|
||||
|
||||
- type: entity
|
||||
id: LockerHeadOfSecurityFilled
|
||||
|
||||
@@ -320,6 +320,8 @@
|
||||
- SolarControlComputerCircuitboard
|
||||
- PowerComputerCircuitboard
|
||||
- GeneratorPlasmaMachineCircuitboard
|
||||
- AnalysisComputerCircuitboard
|
||||
- ArtifactAnalyzerMachineCircuitboard
|
||||
- Signaller
|
||||
- SignalTrigger
|
||||
- VoiceTrigger
|
||||
|
||||
13
Resources/Prototypes/Entities/Effects/bluespace_flash.yml
Normal file
13
Resources/Prototypes/Entities/Effects/bluespace_flash.yml
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
- type: Weldable
|
||||
- type: SuppressArtifactContainer
|
||||
- type: PlaceableSurface
|
||||
isPlaceable: false
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
damageModifierSet: Metallic
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
@@ -298,6 +298,8 @@
|
||||
- EmitterCircuitboard
|
||||
- GasRecyclerMachineCircuitboard
|
||||
- SeedExtractorMachineCircuitboard
|
||||
- AnalysisComputerCircuitboard
|
||||
- ArtifactAnalyzerMachineCircuitboard
|
||||
- type: MaterialStorage
|
||||
whitelist:
|
||||
tags:
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
map: ["enum.PowerDeviceVisualLayers.Powered"]
|
||||
- type: ResearchClient
|
||||
- type: ResearchPointSource
|
||||
pointspersecond: 100
|
||||
pointspersecond: 25
|
||||
active: true
|
||||
- type: PointLight
|
||||
radius: 1.5
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ]
|
||||
@@ -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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user