Anomalies (#13371)
59
Content.Client/Anomaly/AnomalySystem.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
using Content.Shared.Anomaly;
|
||||||
|
using Content.Shared.Anomaly.Components;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
|
namespace Content.Client.Anomaly;
|
||||||
|
|
||||||
|
public sealed class AnomalySystem : SharedAnomalySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<AnomalyComponent, AppearanceChangeEvent>(OnAppearanceChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAppearanceChanged(EntityUid uid, AnomalyComponent component, ref AppearanceChangeEvent args)
|
||||||
|
{
|
||||||
|
if (args.Sprite is not { } sprite)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!Appearance.TryGetData(uid, AnomalyVisuals.IsPulsing, out bool pulsing, args.Component))
|
||||||
|
pulsing = false;
|
||||||
|
|
||||||
|
if (Appearance.TryGetData(uid, AnomalyVisuals.IsPulsing, out bool super, args.Component) && super)
|
||||||
|
pulsing = super;
|
||||||
|
|
||||||
|
if (HasComp<AnomalySupercriticalComponent>(uid))
|
||||||
|
pulsing = true;
|
||||||
|
|
||||||
|
if (!sprite.LayerMapTryGet(AnomalyVisualLayers.Base, out var layer) ||
|
||||||
|
!sprite.LayerMapTryGet(AnomalyVisualLayers.Animated, out var animatedLayer))
|
||||||
|
return;
|
||||||
|
|
||||||
|
sprite.LayerSetVisible(layer, !pulsing);
|
||||||
|
sprite.LayerSetVisible(animatedLayer, pulsing);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
base.Update(frameTime);
|
||||||
|
|
||||||
|
foreach (var (super, sprite) in EntityQuery<AnomalySupercriticalComponent, SpriteComponent>())
|
||||||
|
{
|
||||||
|
var completion = 1f - (float) ((super.EndTime - _timing.CurTime) / super.SupercriticalDuration);
|
||||||
|
var scale = completion * (super.MaxScaleAmount - 1f) + 1f;
|
||||||
|
sprite.Scale = new Vector2(scale, scale);
|
||||||
|
|
||||||
|
var transparency = (byte) (65 * (1f - completion) + 190);
|
||||||
|
if (transparency < sprite.Color.AByte)
|
||||||
|
{
|
||||||
|
sprite.Color = sprite.Color.WithAlpha(transparency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Content.Client/Anomaly/Effects/GravityAnomalySystem.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using Content.Shared.Anomaly.Effects;
|
||||||
|
|
||||||
|
namespace Content.Client.Anomaly.Effects;
|
||||||
|
|
||||||
|
public sealed class GravityAnomalySystem : SharedGravityAnomalySystem
|
||||||
|
{
|
||||||
|
// this is not the system you are looking for
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
using Content.Shared.Anomaly;
|
||||||
|
using Content.Shared.Gravity;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Client. Anomaly.Ui;
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
|
public sealed class AnomalyGeneratorBoundUserInterface : BoundUserInterface
|
||||||
|
{
|
||||||
|
private AnomalyGeneratorWindow? _window;
|
||||||
|
|
||||||
|
public AnomalyGeneratorBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base (owner, uiKey)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
base.Open();
|
||||||
|
|
||||||
|
_window = new (Owner.Owner);
|
||||||
|
|
||||||
|
_window.OpenCentered();
|
||||||
|
_window.OnClose += Close;
|
||||||
|
|
||||||
|
_window.OnGenerateButtonPressed += () =>
|
||||||
|
{
|
||||||
|
SendMessage(new AnomalyGeneratorGenerateButtonPressedEvent());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
base.UpdateState(state);
|
||||||
|
|
||||||
|
if (state is not AnomalyGeneratorUserInterfaceState msg)
|
||||||
|
return;
|
||||||
|
_window?.UpdateState(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
if (!disposing) return;
|
||||||
|
|
||||||
|
_window?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPowerSwitch(bool on)
|
||||||
|
{
|
||||||
|
SendMessage(new SharedGravityGeneratorComponent.SwitchGeneratorMessage(on));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
48
Content.Client/Anomaly/Ui/AnomalyGeneratorWindow.xaml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||||
|
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||||
|
Title="{Loc 'anomaly-generator-ui-title'}"
|
||||||
|
MinSize="270 180"
|
||||||
|
SetSize="360 180">
|
||||||
|
<BoxContainer Margin="10 0 10 0"
|
||||||
|
Orientation="Vertical"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
VerticalExpand="True">
|
||||||
|
<BoxContainer Orientation="Horizontal">
|
||||||
|
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||||
|
<BoxContainer Orientation="Horizontal"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
VerticalExpand="True"
|
||||||
|
Margin="0 0 0 0"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<Label Text="{Loc 'anomaly-generator-fuel-display'}" StyleClasses="StatusFieldTitle" />
|
||||||
|
<ProgressBar Name="FuelBar"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
MaxValue="1"
|
||||||
|
MinValue="0"
|
||||||
|
SetHeight="25"
|
||||||
|
Margin="10 0 10 0"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<Label Name="FuelText"
|
||||||
|
Margin="4 0"
|
||||||
|
Text="0 %" />
|
||||||
|
</ProgressBar>
|
||||||
|
</BoxContainer>
|
||||||
|
<RichTextLabel Name="CooldownLabel" StyleClasses="StatusFieldTitle" />
|
||||||
|
<RichTextLabel Name="ReadyLabel" StyleClasses="StatusFieldTitle" />
|
||||||
|
</BoxContainer>
|
||||||
|
<PanelContainer Margin="12 0 0 0"
|
||||||
|
StyleClasses="Inset"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<SpriteView Name="EntityView"
|
||||||
|
SetSize="96 96"
|
||||||
|
OverrideDirection="South" />
|
||||||
|
</PanelContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
<BoxContainer VerticalExpand="True"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<Button Name="GenerateButton"
|
||||||
|
Text="{Loc 'anomaly-generator-generate'}"></Button>
|
||||||
|
</BoxContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
</controls:FancyWindow>
|
||||||
80
Content.Client/Anomaly/Ui/AnomalyGeneratorWindow.xaml.cs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
using Content.Client.Message;
|
||||||
|
using Content.Shared.Anomaly;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow;
|
||||||
|
|
||||||
|
namespace Content.Client.Anomaly.Ui;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class AnomalyGeneratorWindow : FancyWindow
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
|
|
||||||
|
private TimeSpan _cooldownEnd = TimeSpan.Zero;
|
||||||
|
private bool _hasEnoughFuel;
|
||||||
|
|
||||||
|
public Action? OnGenerateButtonPressed;
|
||||||
|
|
||||||
|
public AnomalyGeneratorWindow(EntityUid gen)
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
|
||||||
|
EntityView.Sprite = _entityManager.GetComponent<SpriteComponent>(gen);
|
||||||
|
|
||||||
|
GenerateButton.OnPressed += _ => OnGenerateButtonPressed?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateState(AnomalyGeneratorUserInterfaceState state)
|
||||||
|
{
|
||||||
|
_cooldownEnd = state.CooldownEndTime;
|
||||||
|
_hasEnoughFuel = state.FuelCost <= state.FuelAmount;
|
||||||
|
|
||||||
|
var fuelCompletion = Math.Clamp((float) state.FuelAmount / state.FuelCost, 0f, 1f);
|
||||||
|
|
||||||
|
FuelBar.Value = fuelCompletion;
|
||||||
|
FuelText.Text = $"{fuelCompletion:P}";
|
||||||
|
|
||||||
|
UpdateTimer();
|
||||||
|
UpdateReady(); // yes this can trigger twice. no i don't care
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateTimer()
|
||||||
|
{
|
||||||
|
if (_timing.CurTime > _cooldownEnd)
|
||||||
|
{
|
||||||
|
CooldownLabel.SetMarkup(Loc.GetString("anomaly-generator-no-cooldown"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var timeLeft = _cooldownEnd - _timing.CurTime;
|
||||||
|
var timeString = $"{timeLeft.Minutes:0}:{timeLeft.Seconds:00}";
|
||||||
|
CooldownLabel.SetMarkup(Loc.GetString("anomaly-generator-cooldown", ("time", timeString)));
|
||||||
|
UpdateReady();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateReady()
|
||||||
|
{
|
||||||
|
var ready = _hasEnoughFuel && _timing.CurTime > _cooldownEnd;
|
||||||
|
|
||||||
|
var msg = ready
|
||||||
|
? Loc.GetString("anomaly-generator-yes-fire")
|
||||||
|
: Loc.GetString("anomaly-generator-no-fire");
|
||||||
|
ReadyLabel.SetMarkup(msg);
|
||||||
|
|
||||||
|
GenerateButton.Disabled = !ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void FrameUpdate(FrameEventArgs args)
|
||||||
|
{
|
||||||
|
base.FrameUpdate(args);
|
||||||
|
|
||||||
|
UpdateTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
using Content.Shared.Anomaly;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Client.Anomaly.Ui;
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
|
public sealed class AnomalyScannerBoundUserInterface : BoundUserInterface
|
||||||
|
{
|
||||||
|
private AnomalyScannerMenu? _menu;
|
||||||
|
|
||||||
|
public AnomalyScannerBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
base.Open();
|
||||||
|
|
||||||
|
_menu = new AnomalyScannerMenu();
|
||||||
|
_menu.OpenCentered();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
base.UpdateState(state);
|
||||||
|
|
||||||
|
if (state is not AnomalyScannerUserInterfaceState msg)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_menu == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_menu.LastMessage = msg.Message;
|
||||||
|
_menu.NextPulseTime = msg.NextPulseTime;
|
||||||
|
_menu.UpdateMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
if (!disposing)
|
||||||
|
return;
|
||||||
|
_menu?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
12
Content.Client/Anomaly/Ui/AnomalyScannerMenu.xaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<controls:FancyWindow
|
||||||
|
xmlns="https://spacestation14.io"
|
||||||
|
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||||
|
Title="{Loc 'anomaly-scanner-ui-title'}"
|
||||||
|
MinSize="350 260"
|
||||||
|
SetSize="350 260">
|
||||||
|
<BoxContainer Orientation="Vertical" VerticalExpand="True" Margin="10 0 10 10">
|
||||||
|
<RichTextLabel Name="TextDisplay"></RichTextLabel>
|
||||||
|
</BoxContainer>
|
||||||
|
</controls:FancyWindow>
|
||||||
|
|
||||||
|
|
||||||
47
Content.Client/Anomaly/Ui/AnomalyScannerMenu.xaml.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using Content.Client.Message;
|
||||||
|
using Content.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Client.Anomaly.Ui;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class AnomalyScannerMenu : FancyWindow
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
|
|
||||||
|
public FormattedMessage LastMessage = new();
|
||||||
|
public TimeSpan? NextPulseTime;
|
||||||
|
|
||||||
|
public AnomalyScannerMenu()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateMenu()
|
||||||
|
{
|
||||||
|
var msg = new FormattedMessage(LastMessage);
|
||||||
|
|
||||||
|
if (NextPulseTime != null)
|
||||||
|
{
|
||||||
|
msg.PushNewline();
|
||||||
|
msg.PushNewline();
|
||||||
|
var time = NextPulseTime.Value - _timing.CurTime;
|
||||||
|
var timestring = $"{time.Minutes:00}:{time.Seconds:00}";
|
||||||
|
msg.AddMarkup(Loc.GetString("anomaly-scanner-pulse-timer", ("time", timestring)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TextDisplay.SetMarkup(msg.ToMarkup());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void FrameUpdate(FrameEventArgs args)
|
||||||
|
{
|
||||||
|
base.FrameUpdate(args);
|
||||||
|
|
||||||
|
if (NextPulseTime != null)
|
||||||
|
UpdateMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
114
Content.Server/Anomaly/AnomalySystem.Generator.cs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
using Content.Server.Anomaly.Components;
|
||||||
|
using Content.Server.Power.Components;
|
||||||
|
using Content.Server.Power.EntitySystems;
|
||||||
|
using Content.Shared.Anomaly;
|
||||||
|
using Content.Shared.Materials;
|
||||||
|
using Robust.Shared.Map.Components;
|
||||||
|
|
||||||
|
namespace Content.Server.Anomaly;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This handles anomalous vessel as well as
|
||||||
|
/// the calculations for how many points they
|
||||||
|
/// should produce.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class AnomalySystem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A multiplier applied to the grid bounds
|
||||||
|
/// to make the likelihood of it spawning outside
|
||||||
|
/// of the main station less likely.
|
||||||
|
///
|
||||||
|
/// tl;dr anomalies only generate on the inner __% of the station.
|
||||||
|
/// </summary>
|
||||||
|
public const float GridBoundsMultiplier = 0.6f;
|
||||||
|
|
||||||
|
private void InitializeGenerator()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<AnomalyGeneratorComponent, BoundUIOpenedEvent>(OnGeneratorBUIOpened);
|
||||||
|
SubscribeLocalEvent<AnomalyGeneratorComponent, MaterialAmountChangedEvent>(OnGeneratorMaterialAmountChanged);
|
||||||
|
SubscribeLocalEvent<AnomalyGeneratorComponent, AnomalyGeneratorGenerateButtonPressedEvent>(OnGenerateButtonPressed);
|
||||||
|
SubscribeLocalEvent<AnomalyGeneratorComponent, PowerChangedEvent>(OnGeneratorPowerChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGeneratorPowerChanged(EntityUid uid, AnomalyGeneratorComponent component, ref PowerChangedEvent args)
|
||||||
|
{
|
||||||
|
_ambient.SetAmbience(uid, args.Powered);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGeneratorBUIOpened(EntityUid uid, AnomalyGeneratorComponent component, BoundUIOpenedEvent args)
|
||||||
|
{
|
||||||
|
UpdateGeneratorUi(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGeneratorMaterialAmountChanged(EntityUid uid, AnomalyGeneratorComponent component, ref MaterialAmountChangedEvent args)
|
||||||
|
{
|
||||||
|
UpdateGeneratorUi(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGenerateButtonPressed(EntityUid uid, AnomalyGeneratorComponent component, AnomalyGeneratorGenerateButtonPressedEvent args)
|
||||||
|
{
|
||||||
|
TryGeneratorCreateAnomaly(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateGeneratorUi(EntityUid uid, AnomalyGeneratorComponent component)
|
||||||
|
{
|
||||||
|
var materialAmount = _material.GetMaterialAmount(uid, component.RequiredMaterial);
|
||||||
|
|
||||||
|
var state = new AnomalyGeneratorUserInterfaceState(component.CooldownEndTime, materialAmount, component.MaterialPerAnomaly);
|
||||||
|
_ui.TrySetUiState(uid, AnomalyGeneratorUiKey.Key, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TryGeneratorCreateAnomaly(EntityUid uid, AnomalyGeneratorComponent? component = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!this.IsPowered(uid, EntityManager))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (Timing.CurTime < component.CooldownEndTime)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var grid = Transform(uid).GridUid;
|
||||||
|
if (grid == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_material.TryChangeMaterialAmount(uid, component.RequiredMaterial, -component.MaterialPerAnomaly))
|
||||||
|
return;
|
||||||
|
|
||||||
|
SpawnOnRandomGridLocation(grid.Value, component.SpawnerPrototype);
|
||||||
|
component.CooldownEndTime = Timing.CurTime + component.CooldownLength;
|
||||||
|
UpdateGeneratorUi(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SpawnOnRandomGridLocation(EntityUid grid, string toSpawn)
|
||||||
|
{
|
||||||
|
if (!TryComp<MapGridComponent>(grid, out var gridComp))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var xform = Transform(grid);
|
||||||
|
|
||||||
|
var targetCoords = xform.Coordinates;
|
||||||
|
var (gridPos, _, gridMatrix) = _transform.GetWorldPositionRotationMatrix(xform);
|
||||||
|
var gridBounds = gridMatrix.TransformBox(gridComp.LocalAABB);
|
||||||
|
|
||||||
|
for (var i = 0; i < 25; i++)
|
||||||
|
{
|
||||||
|
var randomX = Random.Next((int) (gridBounds.Left * GridBoundsMultiplier), (int) (gridBounds.Right * GridBoundsMultiplier));
|
||||||
|
var randomY = Random.Next((int) (gridBounds.Bottom * GridBoundsMultiplier), (int) (gridBounds.Top * GridBoundsMultiplier));
|
||||||
|
|
||||||
|
var tile = new Vector2i(randomX - (int) gridPos.X, randomY - (int) gridPos.Y);
|
||||||
|
if (_atmosphere.IsTileSpace(grid, Transform(grid).MapUid, tile,
|
||||||
|
mapGridComp: gridComp) || _atmosphere.IsTileAirBlocked(grid, tile, mapGridComp: gridComp))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
targetCoords = gridComp.GridTileToLocal(tile);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Spawn(toSpawn, targetCoords);
|
||||||
|
}
|
||||||
|
}
|
||||||
169
Content.Server/Anomaly/AnomalySystem.Scanner.cs
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
using Content.Server.Anomaly.Components;
|
||||||
|
using Content.Server.DoAfter;
|
||||||
|
using Content.Shared.Anomaly;
|
||||||
|
using Content.Shared.Anomaly.Components;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Server.Anomaly;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This handles the anomaly scanner and it's UI updates.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class AnomalySystem
|
||||||
|
{
|
||||||
|
private void InitializeScanner()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<AnomalyScannerComponent, BoundUIOpenedEvent>(OnScannerUiOpened);
|
||||||
|
SubscribeLocalEvent<AnomalyScannerComponent, AfterInteractEvent>(OnScannerAfterInteract);
|
||||||
|
SubscribeLocalEvent<AnomalyScannerComponent, AnomalyScanFinishedEvent>(OnScannerDoAfterFinished);
|
||||||
|
SubscribeLocalEvent<AnomalyScannerComponent, AnomalyScanCancelledEvent>(OnScannerDoAfterCancelled);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<AnomalyShutdownEvent>(OnScannerAnomalyShutdown);
|
||||||
|
SubscribeLocalEvent<AnomalySeverityChangedEvent>(OnScannerAnomalySeverityChanged);
|
||||||
|
SubscribeLocalEvent<AnomalyStabilityChangedEvent>(OnScannerAnomalyStabilityChanged);
|
||||||
|
SubscribeLocalEvent<AnomalyHealthChangedEvent>(OnScannerAnomalyHealthChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnScannerAnomalyShutdown(ref AnomalyShutdownEvent args)
|
||||||
|
{
|
||||||
|
foreach (var component in EntityQuery<AnomalyScannerComponent>())
|
||||||
|
{
|
||||||
|
if (component.ScannedAnomaly != args.Anomaly)
|
||||||
|
continue;
|
||||||
|
_ui.TryCloseAll(component.Owner, AnomalyScannerUiKey.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnScannerAnomalySeverityChanged(ref AnomalySeverityChangedEvent args)
|
||||||
|
{
|
||||||
|
foreach (var component in EntityQuery<AnomalyScannerComponent>())
|
||||||
|
{
|
||||||
|
if (component.ScannedAnomaly != args.Anomaly)
|
||||||
|
continue;
|
||||||
|
UpdateScannerUi(component.Owner, component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnScannerAnomalyStabilityChanged(ref AnomalyStabilityChangedEvent args)
|
||||||
|
{
|
||||||
|
foreach (var component in EntityQuery<AnomalyScannerComponent>())
|
||||||
|
{
|
||||||
|
if (component.ScannedAnomaly != args.Anomaly)
|
||||||
|
continue;
|
||||||
|
UpdateScannerUi(component.Owner, component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnScannerAnomalyHealthChanged(ref AnomalyHealthChangedEvent args)
|
||||||
|
{
|
||||||
|
foreach (var component in EntityQuery<AnomalyScannerComponent>())
|
||||||
|
{
|
||||||
|
if (component.ScannedAnomaly != args.Anomaly)
|
||||||
|
continue;
|
||||||
|
UpdateScannerUi(component.Owner, component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnScannerUiOpened(EntityUid uid, AnomalyScannerComponent component, BoundUIOpenedEvent args)
|
||||||
|
{
|
||||||
|
UpdateScannerUi(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnScannerAfterInteract(EntityUid uid, AnomalyScannerComponent component, AfterInteractEvent args)
|
||||||
|
{
|
||||||
|
if (component.TokenSource != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (args.Target is not { } target)
|
||||||
|
return;
|
||||||
|
if (!HasComp<AnomalyComponent>(target))
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.TokenSource = new();
|
||||||
|
_doAfter.DoAfter(new DoAfterEventArgs(args.User, component.ScanDoAfterDuration, component.TokenSource.Token, target, uid)
|
||||||
|
{
|
||||||
|
DistanceThreshold = 2f,
|
||||||
|
UsedFinishedEvent = new AnomalyScanFinishedEvent(target, args.User),
|
||||||
|
UsedCancelledEvent = new AnomalyScanCancelledEvent()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnScannerDoAfterFinished(EntityUid uid, AnomalyScannerComponent component, AnomalyScanFinishedEvent args)
|
||||||
|
{
|
||||||
|
component.TokenSource = null;
|
||||||
|
|
||||||
|
Audio.PlayPvs(component.CompleteSound, uid);
|
||||||
|
_popup.PopupEntity(Loc.GetString("anomaly-scanner-component-scan-complete"), uid);
|
||||||
|
UpdateScannerWithNewAnomaly(uid, args.Anomaly, component);
|
||||||
|
|
||||||
|
if (TryComp<ActorComponent>(args.User, out var actor))
|
||||||
|
_ui.TryOpen(uid, AnomalyScannerUiKey.Key, actor.PlayerSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnScannerDoAfterCancelled(EntityUid uid, AnomalyScannerComponent component, AnomalyScanCancelledEvent args)
|
||||||
|
{
|
||||||
|
component.TokenSource = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateScannerUi(EntityUid uid, AnomalyScannerComponent? component = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component))
|
||||||
|
return;
|
||||||
|
|
||||||
|
TimeSpan? nextPulse = null;
|
||||||
|
if (TryComp<AnomalyComponent>(component.ScannedAnomaly, out var anomalyComponent))
|
||||||
|
nextPulse = anomalyComponent.NextPulseTime;
|
||||||
|
|
||||||
|
var state = new AnomalyScannerUserInterfaceState(GetScannerMessage(component), nextPulse);
|
||||||
|
_ui.TrySetUiState(uid, AnomalyScannerUiKey.Key, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateScannerWithNewAnomaly(EntityUid scanner, EntityUid anomaly, AnomalyScannerComponent? scannerComp = null, AnomalyComponent? anomalyComp = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(scanner, ref scannerComp) || !Resolve(anomaly, ref anomalyComp))
|
||||||
|
return;
|
||||||
|
|
||||||
|
scannerComp.ScannedAnomaly = anomaly;
|
||||||
|
UpdateScannerUi(scanner, scannerComp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FormattedMessage GetScannerMessage(AnomalyScannerComponent component)
|
||||||
|
{
|
||||||
|
var msg = new FormattedMessage();
|
||||||
|
if (component.ScannedAnomaly is not { } anomaly || !TryComp<AnomalyComponent>(anomaly, out var anomalyComp))
|
||||||
|
{
|
||||||
|
msg.AddMarkup(Loc.GetString("anomaly-scanner-no-anomaly"));
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.AddMarkup(Loc.GetString("anomaly-scanner-severity-percentage", ("percent", anomalyComp.Severity.ToString("P"))));
|
||||||
|
msg.PushNewline();
|
||||||
|
string stateLoc;
|
||||||
|
if (anomalyComp.Stability < anomalyComp.DecayThreshold)
|
||||||
|
stateLoc = Loc.GetString("anomaly-scanner-stability-low");
|
||||||
|
else if (anomalyComp.Stability > anomalyComp.GrowthThreshold)
|
||||||
|
stateLoc = Loc.GetString("anomaly-scanner-stability-high");
|
||||||
|
else
|
||||||
|
stateLoc = Loc.GetString("anomaly-scanner-stability-medium");
|
||||||
|
msg.AddMarkup(stateLoc);
|
||||||
|
msg.PushNewline();
|
||||||
|
|
||||||
|
var points = GetAnomalyPointValue(anomaly, anomalyComp) / 10 * 10; //round to tens place
|
||||||
|
msg.AddMarkup(Loc.GetString("anomaly-scanner-point-output", ("point", points)));
|
||||||
|
msg.PushNewline();
|
||||||
|
msg.PushNewline();
|
||||||
|
|
||||||
|
msg.AddMarkup(Loc.GetString("anomaly-scanner-particle-readout"));
|
||||||
|
msg.PushNewline();
|
||||||
|
msg.AddMarkup(Loc.GetString("anomaly-scanner-particle-danger", ("type", GetParticleLocale(anomalyComp.SeverityParticleType))));
|
||||||
|
msg.PushNewline();
|
||||||
|
msg.AddMarkup(Loc.GetString("anomaly-scanner-particle-unstable", ("type", GetParticleLocale(anomalyComp.DestabilizingParticleType))));
|
||||||
|
msg.PushNewline();
|
||||||
|
msg.AddMarkup(Loc.GetString("anomaly-scanner-particle-containment", ("type", GetParticleLocale(anomalyComp.WeakeningParticleType))));
|
||||||
|
|
||||||
|
//The timer at the end here is actually added in the ui itself.
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
129
Content.Server/Anomaly/AnomalySystem.Vessel.cs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
using Content.Server.Anomaly.Components;
|
||||||
|
using Content.Server.Construction;
|
||||||
|
using Content.Server.Power.EntitySystems;
|
||||||
|
using Content.Shared.Anomaly;
|
||||||
|
using Content.Shared.Anomaly.Components;
|
||||||
|
using Content.Shared.Examine;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Content.Shared.Research.Components;
|
||||||
|
|
||||||
|
namespace Content.Server.Anomaly;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This handles anomalous vessel as well as
|
||||||
|
/// the calculations for how many points they
|
||||||
|
/// should produce.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class AnomalySystem
|
||||||
|
{
|
||||||
|
private void InitializeVessel()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<AnomalyVesselComponent, ComponentShutdown>(OnVesselShutdown);
|
||||||
|
SubscribeLocalEvent<AnomalyVesselComponent, MapInitEvent>(OnVesselMapInit);
|
||||||
|
SubscribeLocalEvent<AnomalyVesselComponent, RefreshPartsEvent>(OnRefreshParts);
|
||||||
|
SubscribeLocalEvent<AnomalyVesselComponent, InteractUsingEvent>(OnVesselInteractUsing);
|
||||||
|
SubscribeLocalEvent<AnomalyVesselComponent, ExaminedEvent>(OnExamined);
|
||||||
|
SubscribeLocalEvent<AnomalyVesselComponent, ResearchServerGetPointsPerSecondEvent>(OnVesselGetPointsPerSecond);
|
||||||
|
SubscribeLocalEvent<AnomalyShutdownEvent>(OnVesselAnomalyShutdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnExamined(EntityUid uid, AnomalyVesselComponent component, ExaminedEvent args)
|
||||||
|
{
|
||||||
|
if (!args.IsInDetailsRange)
|
||||||
|
return;
|
||||||
|
|
||||||
|
args.PushText(component.Anomaly == null
|
||||||
|
? Loc.GetString("anomaly-vessel-component-not-assigned")
|
||||||
|
: Loc.GetString("anomaly-vessel-component-assigned"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnVesselShutdown(EntityUid uid, AnomalyVesselComponent component, ComponentShutdown args)
|
||||||
|
{
|
||||||
|
if (component.Anomaly is not { } anomaly)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryComp<AnomalyComponent>(anomaly, out var anomalyComp))
|
||||||
|
return;
|
||||||
|
|
||||||
|
anomalyComp.ConnectedVessel = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnVesselMapInit(EntityUid uid, AnomalyVesselComponent component, MapInitEvent args)
|
||||||
|
{
|
||||||
|
UpdateVesselAppearance(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRefreshParts(EntityUid uid, AnomalyVesselComponent component, RefreshPartsEvent args)
|
||||||
|
{
|
||||||
|
var modifierRating = args.PartRatings[component.MachinePartPointModifier] - 1;
|
||||||
|
component.PointMultiplier = MathF.Pow(component.PartRatingPointModifier, modifierRating);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnVesselInteractUsing(EntityUid uid, AnomalyVesselComponent component, InteractUsingEvent args)
|
||||||
|
{
|
||||||
|
if (component.Anomaly != null ||
|
||||||
|
!TryComp<AnomalyScannerComponent>(args.Used, out var scanner) ||
|
||||||
|
scanner.ScannedAnomaly is not {} anomaly)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryComp<AnomalyComponent>(anomaly, out var anomalyComponent) || anomalyComponent.ConnectedVessel != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.Anomaly = scanner.ScannedAnomaly;
|
||||||
|
anomalyComponent.ConnectedVessel = uid;
|
||||||
|
UpdateVesselAppearance(uid, component);
|
||||||
|
_popup.PopupEntity(Loc.GetString("anomaly-vessel-component-anomaly-assigned"), uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnVesselGetPointsPerSecond(EntityUid uid, AnomalyVesselComponent component, ref ResearchServerGetPointsPerSecondEvent args)
|
||||||
|
{
|
||||||
|
if (!this.IsPowered(uid, EntityManager) || component.Anomaly is not {} anomaly)
|
||||||
|
{
|
||||||
|
args.Points = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
args.Points += (int) (GetAnomalyPointValue(anomaly) * component.PointMultiplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnVesselAnomalyShutdown(ref AnomalyShutdownEvent args)
|
||||||
|
{
|
||||||
|
foreach (var component in EntityQuery<AnomalyVesselComponent>())
|
||||||
|
{
|
||||||
|
var ent = component.Owner;
|
||||||
|
|
||||||
|
if (args.Anomaly != component.Anomaly)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
component.Anomaly = null;
|
||||||
|
UpdateVesselAppearance(ent, component);
|
||||||
|
|
||||||
|
if (!args.Supercritical)
|
||||||
|
continue;
|
||||||
|
_explosion.TriggerExplosive(ent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the appearance of an anomaly vessel
|
||||||
|
/// based on whether or not it has an anomaly
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uid"></param>
|
||||||
|
/// <param name="component"></param>
|
||||||
|
public void UpdateVesselAppearance(EntityUid uid, AnomalyVesselComponent? component = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var on = component.Anomaly != null;
|
||||||
|
|
||||||
|
Appearance.SetData(uid, AnomalyVesselVisuals.HasAnomaly, on);
|
||||||
|
if (TryComp<SharedPointLightComponent>(uid, out var pointLightComponent))
|
||||||
|
{
|
||||||
|
pointLightComponent.Enabled = on;
|
||||||
|
}
|
||||||
|
_ambient.SetAmbience(uid, on);
|
||||||
|
}
|
||||||
|
}
|
||||||
127
Content.Server/Anomaly/AnomalySystem.cs
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
using Content.Server.Anomaly.Components;
|
||||||
|
using Content.Server.Atmos.EntitySystems;
|
||||||
|
using Content.Server.Audio;
|
||||||
|
using Content.Server.DoAfter;
|
||||||
|
using Content.Server.Explosion.EntitySystems;
|
||||||
|
using Content.Server.Materials;
|
||||||
|
using Content.Server.Popups;
|
||||||
|
using Content.Shared.Anomaly;
|
||||||
|
using Content.Shared.Anomaly.Components;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.Physics.Events;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
namespace Content.Server.Anomaly;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This handles logic and interactions relating to <see cref="AnomalyComponent"/>
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class AnomalySystem : SharedAnomalySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly AmbientSoundSystem _ambient = default!;
|
||||||
|
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||||
|
[Dependency] private readonly DoAfterSystem _doAfter = default!;
|
||||||
|
[Dependency] private readonly ExplosionSystem _explosion = default!;
|
||||||
|
[Dependency] private readonly MaterialStorageSystem _material = default!;
|
||||||
|
[Dependency] private readonly PopupSystem _popup = default!;
|
||||||
|
[Dependency] private readonly TransformSystem _transform = default!;
|
||||||
|
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||||
|
|
||||||
|
public const float MinParticleVariation = 0.8f;
|
||||||
|
public const float MaxParticleVariation = 1.2f;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeLocalEvent<AnomalyComponent, MapInitEvent>(OnMapInit);
|
||||||
|
SubscribeLocalEvent<AnomalyComponent, ComponentShutdown>(OnShutdown);
|
||||||
|
SubscribeLocalEvent<AnomalyComponent, StartCollideEvent>(OnStartCollide);
|
||||||
|
|
||||||
|
InitializeGenerator();
|
||||||
|
InitializeScanner();
|
||||||
|
InitializeVessel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMapInit(EntityUid uid, AnomalyComponent component, MapInitEvent args)
|
||||||
|
{
|
||||||
|
component.NextPulseTime = Timing.CurTime + GetPulseLength(component) * 3; // longer the first time
|
||||||
|
ChangeAnomalyStability(uid, Random.NextFloat(component.InitialStabilityRange.Item1 , component.InitialStabilityRange.Item2), component);
|
||||||
|
ChangeAnomalySeverity(uid, Random.NextFloat(component.InitialSeverityRange.Item1, component.InitialSeverityRange.Item2), component);
|
||||||
|
|
||||||
|
var particles = new List<AnomalousParticleType>
|
||||||
|
{ AnomalousParticleType.Delta, AnomalousParticleType.Epsilon, AnomalousParticleType.Zeta };
|
||||||
|
component.SeverityParticleType = Random.PickAndTake(particles);
|
||||||
|
component.DestabilizingParticleType = Random.PickAndTake(particles);
|
||||||
|
component.WeakeningParticleType = Random.PickAndTake(particles);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnShutdown(EntityUid uid, AnomalyComponent component, ComponentShutdown args)
|
||||||
|
{
|
||||||
|
EndAnomaly(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStartCollide(EntityUid uid, AnomalyComponent component, ref StartCollideEvent args)
|
||||||
|
{
|
||||||
|
if (!TryComp<AnomalousParticleComponent>(args.OtherFixture.Body.Owner, out var particleComponent))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (args.OtherFixture.ID != particleComponent.FixtureId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// small function to randomize because it's easier to read like this
|
||||||
|
float VaryValue(float v) => v * Random.NextFloat(MinParticleVariation, MaxParticleVariation);
|
||||||
|
|
||||||
|
if (particleComponent.ParticleType == component.DestabilizingParticleType)
|
||||||
|
{
|
||||||
|
ChangeAnomalyStability(uid, VaryValue(component.StabilityPerDestabilizingHit), component);
|
||||||
|
}
|
||||||
|
else if (particleComponent.ParticleType == component.SeverityParticleType)
|
||||||
|
{
|
||||||
|
ChangeAnomalySeverity(uid, VaryValue(component.SeverityPerSeverityHit), component);
|
||||||
|
}
|
||||||
|
else if (particleComponent.ParticleType == component.WeakeningParticleType)
|
||||||
|
{
|
||||||
|
ChangeAnomalyHealth(uid, VaryValue(component.HealthPerWeakeningeHit), component);
|
||||||
|
ChangeAnomalyStability(uid, VaryValue(component.StabilityPerWeakeningeHit), component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the amount of research points generated per second for an anomaly.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="anomaly"></param>
|
||||||
|
/// <param name="component"></param>
|
||||||
|
/// <returns>The amount of points</returns>
|
||||||
|
public int GetAnomalyPointValue(EntityUid anomaly, AnomalyComponent? component = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(anomaly, ref component, false))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var multiplier = 1f;
|
||||||
|
if (component.Stability > component.GrowthThreshold)
|
||||||
|
multiplier = 1.25f; //more points for unstable
|
||||||
|
else if (component.Stability < component.DecayThreshold)
|
||||||
|
multiplier = 0.75f; //less points if it's dying
|
||||||
|
|
||||||
|
//penalty of up to 50% based on health
|
||||||
|
multiplier *= MathF.Pow(1.5f, component.Health) - 0.5f;
|
||||||
|
|
||||||
|
return (int) ((component.MaxPointsPerSecond - component.MinPointsPerSecond) * component.Severity * multiplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localized name of a particle.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string GetParticleLocale(AnomalousParticleType type)
|
||||||
|
{
|
||||||
|
return type switch
|
||||||
|
{
|
||||||
|
AnomalousParticleType.Delta => Loc.GetString("anomaly-particles-delta"),
|
||||||
|
AnomalousParticleType.Epsilon => Loc.GetString("anomaly-particles-epsilon"),
|
||||||
|
AnomalousParticleType.Zeta => Loc.GetString("anomaly-particles-zeta"),
|
||||||
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using Content.Shared.Anomaly;
|
||||||
|
|
||||||
|
namespace Content.Server.Anomaly.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is used for projectiles which affect anomalies through colliding with them.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class AnomalousParticleComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The type of particle that the projectile
|
||||||
|
/// imbues onto the anomaly on contact.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("particleType", required: true)]
|
||||||
|
public AnomalousParticleType ParticleType;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The fixture that's checked on collision.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("fixtureId")]
|
||||||
|
public string FixtureId = "projectile";
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using Content.Shared.Materials;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
|
|
||||||
|
namespace Content.Server.Anomaly.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is used for a machine that is able to generate
|
||||||
|
/// anomalies randomly on the station.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class AnomalyGeneratorComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The time at which the cooldown for generating another anomaly will be over
|
||||||
|
/// </summary>
|
||||||
|
[DataField("cooldownEndTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public TimeSpan CooldownEndTime = TimeSpan.Zero;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The cooldown between generating anomalies.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("cooldownLength"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public TimeSpan CooldownLength = TimeSpan.FromMinutes(5);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The material needed to generate an anomaly
|
||||||
|
/// </summary>
|
||||||
|
[DataField("requiredMaterial", customTypeSerializer: typeof(PrototypeIdSerializer<MaterialPrototype>)), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public string RequiredMaterial = "Plasma";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of material needed to generate a single anomaly
|
||||||
|
/// </summary>
|
||||||
|
[DataField("materialPerAnomaly"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public int MaterialPerAnomaly = 1500; // a bit less than a stack of plasma
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The random anomaly spawner entity
|
||||||
|
/// </summary>
|
||||||
|
[DataField("spawnerPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>)), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public string SpawnerPrototype = "RandomAnomalySpawner";
|
||||||
|
}
|
||||||
49
Content.Server/Anomaly/Components/AnomalyScannerComponent.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
|
||||||
|
namespace Content.Server.Anomaly.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is used for scanning anomalies and
|
||||||
|
/// displaying information about them in the ui
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class AnomalyScannerComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The anomaly that was last scanned by this scanner.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public EntityUid? ScannedAnomaly;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long the scan takes
|
||||||
|
/// </summary>
|
||||||
|
[DataField("scanDoAfterDuration")]
|
||||||
|
public float ScanDoAfterDuration = 5;
|
||||||
|
|
||||||
|
public CancellationTokenSource? TokenSource;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sound plays when the scan finished
|
||||||
|
/// </summary>
|
||||||
|
[DataField("completeSound")]
|
||||||
|
public SoundSpecifier? CompleteSound = new SoundPathSpecifier("/Audio/Items/beep.ogg");
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class AnomalyScanFinishedEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public EntityUid Anomaly;
|
||||||
|
|
||||||
|
public EntityUid User;
|
||||||
|
|
||||||
|
public AnomalyScanFinishedEvent(EntityUid anomaly, EntityUid user)
|
||||||
|
{
|
||||||
|
Anomaly = anomaly;
|
||||||
|
User = user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class AnomalyScanCancelledEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
}
|
||||||
40
Content.Server/Anomaly/Components/AnomalyVesselComponent.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using Content.Shared.Construction.Prototypes;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
|
|
||||||
|
namespace Content.Server.Anomaly.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anomaly Vessels can have an anomaly "stored" in them
|
||||||
|
/// by interacting on them with an anomaly scanner. Then,
|
||||||
|
/// they generate points for the selected server based on
|
||||||
|
/// the anomaly's stability and severity.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class AnomalyVesselComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The anomaly that the vessel is storing.
|
||||||
|
/// Can be null.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public EntityUid? Anomaly;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A multiplier applied to the amount of points generated.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float PointMultiplier = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The machine part that affects the point multiplier of the vessel
|
||||||
|
/// </summary>
|
||||||
|
[DataField("machinePartPointModifier", customTypeSerializer: typeof(PrototypeIdSerializer<MachinePartPrototype>))]
|
||||||
|
public string MachinePartPointModifier = "ScanningModule";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A value used to scale the point multiplier
|
||||||
|
/// with the corresponding part rating.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("partRatingPointModifier")]
|
||||||
|
public float PartRatingPointModifier = 1.5f;
|
||||||
|
}
|
||||||
61
Content.Server/Anomaly/Effects/ElectricityAnomalySystem.cs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
using Content.Server.Electrocution;
|
||||||
|
using Content.Server.Lightning;
|
||||||
|
using Content.Server.Power.Components;
|
||||||
|
using Content.Shared.Anomaly.Components;
|
||||||
|
using Content.Shared.Anomaly.Effects.Components;
|
||||||
|
using Content.Shared.Mobs.Components;
|
||||||
|
using Content.Shared.StatusEffect;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.Anomaly.Effects;
|
||||||
|
|
||||||
|
public sealed class ElectricityAnomalySystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
[Dependency] private readonly LightningSystem _lightning = default!;
|
||||||
|
[Dependency] private readonly ElectrocutionSystem _electrocution = default!;
|
||||||
|
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<ElectricityAnomalyComponent, AnomalyPulseEvent>(OnPulse);
|
||||||
|
SubscribeLocalEvent<ElectricityAnomalyComponent, AnomalySupercriticalEvent>(OnSupercritical);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPulse(EntityUid uid, ElectricityAnomalyComponent component, ref AnomalyPulseEvent args)
|
||||||
|
{
|
||||||
|
var range = component.MaxElectrocuteRange * args.Stabiltiy;
|
||||||
|
var damage = (int) (component.MaxElectrocuteDamage * args.Severity);
|
||||||
|
var duration = component.MaxElectrocuteDuration * args.Severity;
|
||||||
|
|
||||||
|
var xform = Transform(uid);
|
||||||
|
foreach (var comp in _lookup.GetComponentsInRange<StatusEffectsComponent>(xform.MapPosition, range))
|
||||||
|
{
|
||||||
|
var ent = comp.Owner;
|
||||||
|
|
||||||
|
_electrocution.TryDoElectrocution(ent, uid, damage, duration, true, statusEffects: comp, ignoreInsulation: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSupercritical(EntityUid uid, ElectricityAnomalyComponent component, ref AnomalySupercriticalEvent args)
|
||||||
|
{
|
||||||
|
var poweredQuery = GetEntityQuery<ApcPowerReceiverComponent>();
|
||||||
|
var mobQuery = GetEntityQuery<MobThresholdsComponent>();
|
||||||
|
var validEnts = new HashSet<EntityUid>();
|
||||||
|
foreach (var ent in _lookup.GetEntitiesInRange(uid, component.MaxElectrocuteRange * 2))
|
||||||
|
{
|
||||||
|
if (mobQuery.HasComponent(ent))
|
||||||
|
validEnts.Add(ent);
|
||||||
|
|
||||||
|
if (_random.Prob(0.1f) && poweredQuery.HasComponent(ent))
|
||||||
|
validEnts.Add(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// goodbye, sweet perf
|
||||||
|
foreach (var ent in validEnts)
|
||||||
|
{
|
||||||
|
_lightning.ShootLightning(uid, ent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
Content.Server/Anomaly/Effects/GravityAnomalySystem.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using Content.Server.Singularity.Components;
|
||||||
|
using Content.Shared.Anomaly.Components;
|
||||||
|
using Content.Shared.Anomaly.Effects;
|
||||||
|
using Content.Shared.Anomaly.Effects.Components;
|
||||||
|
using Content.Shared.Radiation.Components;
|
||||||
|
|
||||||
|
namespace Content.Server.Anomaly.Effects;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This handles logic and events relating to <see cref="GravityAnomalyComponent"/> and <seealso cref="AnomalySystem"/>
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GravityAnomalySystem : SharedGravityAnomalySystem
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeLocalEvent<GravityAnomalyComponent, AnomalySeverityChangedEvent>(OnSeverityChanged);
|
||||||
|
SubscribeLocalEvent<GravityAnomalyComponent, AnomalyStabilityChangedEvent>(OnStabilityChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSeverityChanged(EntityUid uid, GravityAnomalyComponent component, ref AnomalySeverityChangedEvent args)
|
||||||
|
{
|
||||||
|
if (TryComp<RadiationSourceComponent>(uid, out var radSource))
|
||||||
|
radSource.Intensity = component.MaxRadiationIntensity * args.Severity;
|
||||||
|
|
||||||
|
if (!TryComp<GravityWellComponent>(uid, out var gravityWell))
|
||||||
|
return;
|
||||||
|
var accel = (component.MaxAccel - component.MinAccel) * args.Severity + component.MinAccel;
|
||||||
|
gravityWell.BaseRadialAcceleration = accel;
|
||||||
|
gravityWell.BaseTangentialAcceleration = accel * 0.2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStabilityChanged(EntityUid uid, GravityAnomalyComponent component, ref AnomalyStabilityChangedEvent args)
|
||||||
|
{
|
||||||
|
if (TryComp<GravityWellComponent>(uid, out var gravityWell))
|
||||||
|
gravityWell.MaxRange = component.MaxGravityWellRange * args.Stability;
|
||||||
|
}
|
||||||
|
}
|
||||||
100
Content.Server/Anomaly/Effects/PyroclasticAnomalySystem.cs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
using Content.Server.Atmos.Components;
|
||||||
|
using Content.Server.Atmos.EntitySystems;
|
||||||
|
using Content.Server.Interaction;
|
||||||
|
using Content.Shared.Anomaly.Components;
|
||||||
|
using Content.Shared.Anomaly.Effects.Components;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
|
||||||
|
namespace Content.Server.Anomaly.Effects;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This handles <see cref="PyroclasticAnomalyComponent"/> and the events from <seealso cref="AnomalySystem"/>
|
||||||
|
/// </summary>
|
||||||
|
public sealed class PyroclasticAnomalySystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||||
|
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||||
|
[Dependency] private readonly FlammableSystem _flammable = default!;
|
||||||
|
[Dependency] private readonly InteractionSystem _interaction = default!;
|
||||||
|
[Dependency] private readonly TransformSystem _xform = default!;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<PyroclasticAnomalyComponent, AnomalyPulseEvent>(OnPulse);
|
||||||
|
SubscribeLocalEvent<PyroclasticAnomalyComponent, AnomalySupercriticalEvent>(OnSupercritical);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPulse(EntityUid uid, PyroclasticAnomalyComponent component, ref AnomalyPulseEvent args)
|
||||||
|
{
|
||||||
|
var xform = Transform(uid);
|
||||||
|
var ignitionRadius = component.MaximumIgnitionRadius * args.Stabiltiy;
|
||||||
|
IgniteNearby(xform.Coordinates, args.Severity, ignitionRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSupercritical(EntityUid uid, PyroclasticAnomalyComponent component, ref AnomalySupercriticalEvent args)
|
||||||
|
{
|
||||||
|
var xform = Transform(uid);
|
||||||
|
var grid = xform.GridUid;
|
||||||
|
var map = xform.MapUid;
|
||||||
|
|
||||||
|
var indices = _xform.GetGridOrMapTilePosition(uid, xform);
|
||||||
|
var mixture = _atmosphere.GetTileMixture(grid, map, indices, true);
|
||||||
|
|
||||||
|
if (mixture == null)
|
||||||
|
return;
|
||||||
|
mixture.AdjustMoles(component.SupercriticalGas, component.SupercriticalMoleAmount);
|
||||||
|
if (grid is { })
|
||||||
|
{
|
||||||
|
foreach (var ind in _atmosphere.GetAdjacentTiles(grid.Value, indices))
|
||||||
|
{
|
||||||
|
var mix = _atmosphere.GetTileMixture(grid, map, indices, true);
|
||||||
|
if (mix is not {})
|
||||||
|
continue;
|
||||||
|
mix.AdjustMoles(component.SupercriticalGas, component.SupercriticalMoleAmount);
|
||||||
|
mix.Temperature += component.HotspotExposeTemperature;
|
||||||
|
_atmosphere.HotspotExpose(grid.Value, indices, component.HotspotExposeTemperature, mix?.Volume ?? component.SupercriticalMoleAmount, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IgniteNearby(xform.Coordinates, 1, component.MaximumIgnitionRadius * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
base.Update(frameTime);
|
||||||
|
|
||||||
|
foreach (var (pyro, anom, xform) in EntityQuery<PyroclasticAnomalyComponent, AnomalyComponent, TransformComponent>())
|
||||||
|
{
|
||||||
|
var ent = pyro.Owner;
|
||||||
|
|
||||||
|
var grid = xform.GridUid;
|
||||||
|
var map = xform.MapUid;
|
||||||
|
var indices = _xform.GetGridOrMapTilePosition(ent, xform);
|
||||||
|
var mixture = _atmosphere.GetTileMixture(grid, map, indices, true);
|
||||||
|
if (mixture is { })
|
||||||
|
{
|
||||||
|
mixture.Temperature += pyro.HeatPerSecond * anom.Severity * frameTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (grid != null && anom.Severity > pyro.AnomalyHotspotThreshold)
|
||||||
|
{
|
||||||
|
_atmosphere.HotspotExpose(grid.Value, indices, pyro.HotspotExposeTemperature, pyro.HotspotExposeVolume, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void IgniteNearby(EntityCoordinates coordinates, float severity, float radius)
|
||||||
|
{
|
||||||
|
foreach (var flammable in _lookup.GetComponentsInRange<FlammableComponent>(coordinates, radius))
|
||||||
|
{
|
||||||
|
var ent = flammable.Owner;
|
||||||
|
if (!_interaction.InRangeUnobstructed(coordinates.ToMap(EntityManager), ent, -1))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var stackAmount = 1 + (int) (severity / 0.25f);
|
||||||
|
_flammable.AdjustFireStacks(ent, stackAmount, flammable);
|
||||||
|
_flammable.Ignite(ent, flammable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
using Content.Server.Beam.Components;
|
using Content.Server.Beam.Components;
|
||||||
using Content.Server.Lightning;
|
|
||||||
using Content.Shared.Beam;
|
using Content.Shared.Beam;
|
||||||
using Content.Shared.Beam.Components;
|
using Content.Shared.Beam.Components;
|
||||||
using Content.Shared.Interaction;
|
|
||||||
using Content.Shared.Physics;
|
using Content.Shared.Physics;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Physics;
|
using Robust.Shared.Physics;
|
||||||
using Robust.Shared.Physics.Collision.Shapes;
|
using Robust.Shared.Physics.Collision.Shapes;
|
||||||
using Robust.Shared.Physics.Components;
|
using Robust.Shared.Physics.Components;
|
||||||
using Robust.Shared.Physics.Dynamics;
|
|
||||||
using Robust.Shared.Physics.Systems;
|
using Robust.Shared.Physics.Systems;
|
||||||
|
|
||||||
namespace Content.Server.Beam;
|
namespace Content.Server.Beam;
|
||||||
@@ -17,8 +14,8 @@ public sealed class BeamSystem : SharedBeamSystem
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly FixtureSystem _fixture = default!;
|
[Dependency] private readonly FixtureSystem _fixture = default!;
|
||||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||||
|
[Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
|
||||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -79,8 +76,8 @@ public sealed class BeamSystem : SharedBeamSystem
|
|||||||
var shape = new EdgeShape(distanceCorrection, new Vector2(0,0));
|
var shape = new EdgeShape(distanceCorrection, new Vector2(0,0));
|
||||||
var distanceLength = distanceCorrection.Length;
|
var distanceLength = distanceCorrection.Length;
|
||||||
|
|
||||||
if (TryComp<PhysicsComponent>(ent, out var physics) && TryComp<BeamComponent>(ent, out var beam))
|
if (!TryComp<PhysicsComponent>(ent, out var physics) || !TryComp<BeamComponent>(ent, out var beam))
|
||||||
{
|
return;
|
||||||
FixturesComponent? manager = null;
|
FixturesComponent? manager = null;
|
||||||
_fixture.TryCreateFixture(
|
_fixture.TryCreateFixture(
|
||||||
ent,
|
ent,
|
||||||
@@ -94,6 +91,7 @@ public sealed class BeamSystem : SharedBeamSystem
|
|||||||
|
|
||||||
_physics.SetBodyType(ent, BodyType.Dynamic, manager: manager, body: physics);
|
_physics.SetBodyType(ent, BodyType.Dynamic, manager: manager, body: physics);
|
||||||
_physics.SetCanCollide(ent, true, manager: manager, body: physics);
|
_physics.SetCanCollide(ent, true, manager: manager, body: physics);
|
||||||
|
_broadphase.RegenerateContacts(physics, manager);
|
||||||
|
|
||||||
var beamVisualizerEvent = new BeamVisualizerEvent(ent, distanceLength, userAngle, bodyState, shader);
|
var beamVisualizerEvent = new BeamVisualizerEvent(ent, distanceLength, userAngle, bodyState, shader);
|
||||||
RaiseNetworkEvent(beamVisualizerEvent);
|
RaiseNetworkEvent(beamVisualizerEvent);
|
||||||
@@ -113,7 +111,7 @@ public sealed class BeamSystem : SharedBeamSystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Create the rest of the beam, sprites handled through the BeamVisualizerEvent
|
//Create the rest of the beam, sprites handled through the BeamVisualizerEvent
|
||||||
for (int i = 0; i < distanceLength-1; i++)
|
for (var i = 0; i < distanceLength-1; i++)
|
||||||
{
|
{
|
||||||
beamSpawnPos = beamSpawnPos.Offset(calculatedDistance.Normalized);
|
beamSpawnPos = beamSpawnPos.Offset(calculatedDistance.Normalized);
|
||||||
var newEnt = Spawn(prototype, beamSpawnPos);
|
var newEnt = Spawn(prototype, beamSpawnPos);
|
||||||
@@ -125,7 +123,6 @@ public sealed class BeamSystem : SharedBeamSystem
|
|||||||
var beamFiredEvent = new BeamFiredEvent(ent);
|
var beamFiredEvent = new BeamFiredEvent(ent);
|
||||||
RaiseLocalEvent(beam.VirtualBeamController.Value, beamFiredEvent);
|
RaiseLocalEvent(beam.VirtualBeamController.Value, beamFiredEvent);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called where you want an entity to create a beam from one target to another.
|
/// Called where you want an entity to create a beam from one target to another.
|
||||||
|
|||||||
@@ -7,16 +7,19 @@ using Content.Server.Projectiles;
|
|||||||
using Content.Server.Storage.Components;
|
using Content.Server.Storage.Components;
|
||||||
using Content.Server.Weapons.Ranged.Systems;
|
using Content.Server.Weapons.Ranged.Systems;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
|
using Content.Shared.Examine;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
using Content.Shared.Projectiles;
|
using Content.Shared.Projectiles;
|
||||||
using Content.Shared.Singularity.Components;
|
using Content.Shared.Singularity.Components;
|
||||||
using Content.Shared.Singularity.EntitySystems;
|
using Content.Shared.Singularity.EntitySystems;
|
||||||
|
using Content.Shared.Verbs;
|
||||||
using Content.Shared.Weapons.Ranged.Components;
|
using Content.Shared.Weapons.Ranged.Components;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Physics;
|
using Robust.Shared.Physics;
|
||||||
using Robust.Shared.Physics.Components;
|
using Robust.Shared.Physics.Components;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
using Timer = Robust.Shared.Timing.Timer;
|
using Timer = Robust.Shared.Timing.Timer;
|
||||||
@@ -27,6 +30,7 @@ namespace Content.Server.Singularity.EntitySystems
|
|||||||
public sealed class EmitterSystem : SharedEmitterSystem
|
public sealed class EmitterSystem : SharedEmitterSystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||||
@@ -38,14 +42,28 @@ namespace Content.Server.Singularity.EntitySystems
|
|||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<EmitterComponent, PowerConsumerReceivedChanged>(ReceivedChanged);
|
SubscribeLocalEvent<EmitterComponent, PowerConsumerReceivedChanged>(ReceivedChanged);
|
||||||
|
SubscribeLocalEvent<EmitterComponent, PowerChangedEvent>(OnApcChanged);
|
||||||
SubscribeLocalEvent<EmitterComponent, InteractHandEvent>(OnInteractHand);
|
SubscribeLocalEvent<EmitterComponent, InteractHandEvent>(OnInteractHand);
|
||||||
|
SubscribeLocalEvent<EmitterComponent, GetVerbsEvent<Verb>>(OnGetVerb);
|
||||||
|
SubscribeLocalEvent<EmitterComponent, ExaminedEvent>(OnExamined);
|
||||||
SubscribeLocalEvent<EmitterComponent, RefreshPartsEvent>(OnRefreshParts);
|
SubscribeLocalEvent<EmitterComponent, RefreshPartsEvent>(OnRefreshParts);
|
||||||
SubscribeLocalEvent<EmitterComponent, UpgradeExamineEvent>(OnUpgradeExamine);
|
SubscribeLocalEvent<EmitterComponent, UpgradeExamineEvent>(OnUpgradeExamine);
|
||||||
|
SubscribeLocalEvent<EmitterComponent, AnchorStateChangedEvent>(OnAnchorStateChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAnchorStateChanged(EntityUid uid, EmitterComponent component, ref AnchorStateChangedEvent args)
|
||||||
|
{
|
||||||
|
if (args.Anchored)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SwitchOff(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnInteractHand(EntityUid uid, EmitterComponent component, InteractHandEvent args)
|
private void OnInteractHand(EntityUid uid, EmitterComponent component, InteractHandEvent args)
|
||||||
{
|
{
|
||||||
args.Handled = true;
|
if (args.Handled)
|
||||||
|
return;
|
||||||
|
|
||||||
if (EntityManager.TryGetComponent(uid, out LockComponent? lockComp) && lockComp.Locked)
|
if (EntityManager.TryGetComponent(uid, out LockComponent? lockComp) && lockComp.Locked)
|
||||||
{
|
{
|
||||||
_popup.PopupEntity(Loc.GetString("comp-emitter-access-locked",
|
_popup.PopupEntity(Loc.GetString("comp-emitter-access-locked",
|
||||||
@@ -71,6 +89,7 @@ namespace Content.Server.Singularity.EntitySystems
|
|||||||
_adminLogger.Add(LogType.Emitter,
|
_adminLogger.Add(LogType.Emitter,
|
||||||
component.IsOn ? LogImpact.Medium : LogImpact.High,
|
component.IsOn ? LogImpact.Medium : LogImpact.High,
|
||||||
$"{ToPrettyString(args.User):player} toggled {ToPrettyString(uid):emitter}");
|
$"{ToPrettyString(args.User):player} toggled {ToPrettyString(uid):emitter}");
|
||||||
|
args.Handled = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -79,6 +98,47 @@ namespace Content.Server.Singularity.EntitySystems
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnGetVerb(EntityUid uid, EmitterComponent component, GetVerbsEvent<Verb> args)
|
||||||
|
{
|
||||||
|
if (!args.CanAccess || !args.CanInteract || args.Hands == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (TryComp<LockComponent>(uid, out var lockComp) && lockComp.Locked)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (component.SelectableTypes.Count < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var type in component.SelectableTypes)
|
||||||
|
{
|
||||||
|
var proto = _prototype.Index<EntityPrototype>(type);
|
||||||
|
|
||||||
|
var v = new Verb
|
||||||
|
{
|
||||||
|
Priority = 1,
|
||||||
|
Category = VerbCategory.SelectType,
|
||||||
|
Text = proto.Name,
|
||||||
|
Disabled = type == component.BoltType,
|
||||||
|
Impact = LogImpact.Medium,
|
||||||
|
DoContactInteraction = true,
|
||||||
|
Act = () =>
|
||||||
|
{
|
||||||
|
component.BoltType = type;
|
||||||
|
_popup.PopupEntity(Loc.GetString("emitter-component-type-set", ("type", proto.Name)), uid);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
args.Verbs.Add(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnExamined(EntityUid uid, EmitterComponent component, ExaminedEvent args)
|
||||||
|
{
|
||||||
|
if (component.SelectableTypes.Count < 2)
|
||||||
|
return;
|
||||||
|
var proto = _prototype.Index<EntityPrototype>(component.BoltType);
|
||||||
|
args.Message.AddText(Loc.GetString("emitter-component-current-type", ("type", proto.Name)));
|
||||||
|
}
|
||||||
|
|
||||||
private void ReceivedChanged(
|
private void ReceivedChanged(
|
||||||
EntityUid uid,
|
EntityUid uid,
|
||||||
EmitterComponent component,
|
EmitterComponent component,
|
||||||
@@ -99,6 +159,23 @@ namespace Content.Server.Singularity.EntitySystems
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnApcChanged(EntityUid uid, EmitterComponent component, ref PowerChangedEvent args)
|
||||||
|
{
|
||||||
|
if (!component.IsOn)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!args.Powered)
|
||||||
|
{
|
||||||
|
PowerOff(component);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PowerOn(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnRefreshParts(EntityUid uid, EmitterComponent component, RefreshPartsEvent args)
|
private void OnRefreshParts(EntityUid uid, EmitterComponent component, RefreshPartsEvent args)
|
||||||
{
|
{
|
||||||
var powerUseRating = args.PartRatings[component.MachinePartPowerUse];
|
var powerUseRating = args.PartRatings[component.MachinePartPowerUse];
|
||||||
@@ -122,7 +199,9 @@ namespace Content.Server.Singularity.EntitySystems
|
|||||||
{
|
{
|
||||||
component.IsOn = false;
|
component.IsOn = false;
|
||||||
if (TryComp<PowerConsumerComponent>(component.Owner, out var powerConsumer))
|
if (TryComp<PowerConsumerComponent>(component.Owner, out var powerConsumer))
|
||||||
powerConsumer.DrawRate = 0;
|
powerConsumer.DrawRate = 1; // this needs to be not 0 so that the visuals still work.
|
||||||
|
if (TryComp<ApcPowerReceiverComponent>(component.Owner, out var apcReceiever))
|
||||||
|
apcReceiever.Load = 1;
|
||||||
PowerOff(component);
|
PowerOff(component);
|
||||||
UpdateAppearance(component);
|
UpdateAppearance(component);
|
||||||
}
|
}
|
||||||
@@ -132,6 +211,11 @@ namespace Content.Server.Singularity.EntitySystems
|
|||||||
component.IsOn = true;
|
component.IsOn = true;
|
||||||
if (TryComp<PowerConsumerComponent>(component.Owner, out var powerConsumer))
|
if (TryComp<PowerConsumerComponent>(component.Owner, out var powerConsumer))
|
||||||
powerConsumer.DrawRate = component.PowerUseActive;
|
powerConsumer.DrawRate = component.PowerUseActive;
|
||||||
|
if (TryComp<ApcPowerReceiverComponent>(component.Owner, out var apcReceiever))
|
||||||
|
{
|
||||||
|
apcReceiever.Load = component.PowerUseActive;
|
||||||
|
PowerOn(component);
|
||||||
|
}
|
||||||
// Do not directly PowerOn().
|
// Do not directly PowerOn().
|
||||||
// OnReceivedPowerChanged will get fired due to DrawRate change which will turn it on.
|
// OnReceivedPowerChanged will get fired due to DrawRate change which will turn it on.
|
||||||
UpdateAppearance(component);
|
UpdateAppearance(component);
|
||||||
@@ -179,9 +263,6 @@ namespace Content.Server.Singularity.EntitySystems
|
|||||||
// and thus not firing
|
// and thus not firing
|
||||||
DebugTools.Assert(component.IsPowered);
|
DebugTools.Assert(component.IsPowered);
|
||||||
DebugTools.Assert(component.IsOn);
|
DebugTools.Assert(component.IsOn);
|
||||||
DebugTools.Assert(TryComp<PowerConsumerComponent>(component.Owner, out var powerConsumer) &&
|
|
||||||
(powerConsumer.DrawRate <= powerConsumer.ReceivedPower ||
|
|
||||||
MathHelper.CloseTo(powerConsumer.DrawRate, powerConsumer.ReceivedPower, 0.0001f)));
|
|
||||||
|
|
||||||
Fire(component);
|
Fire(component);
|
||||||
|
|
||||||
|
|||||||
@@ -81,4 +81,5 @@ public enum LogType
|
|||||||
Stamina = 76,
|
Stamina = 76,
|
||||||
EntitySpawn = 77,
|
EntitySpawn = 77,
|
||||||
AdminMessage = 78,
|
AdminMessage = 78,
|
||||||
|
Anomaly = 79
|
||||||
}
|
}
|
||||||
|
|||||||
268
Content.Shared/Anomaly/Components/AnomalyComponent.cs
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
|
||||||
|
namespace Content.Shared.Anomaly.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is used for tracking the general behavior of anomalies.
|
||||||
|
/// This doesn't contain the specific implementations for what
|
||||||
|
/// they do, just the generic behaviors associated with them.
|
||||||
|
///
|
||||||
|
/// Anomalies and their related components were designed here: https://hackmd.io/@ss14-design/r1sQbkJOs
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, NetworkedComponent]
|
||||||
|
public sealed class AnomalyComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// How likely an anomaly is to grow more dangerous. Moves both up and down.
|
||||||
|
/// Ranges from 0 to 1.
|
||||||
|
/// Values less than 0.5 indicate stability, whereas values greater
|
||||||
|
/// than 0.5 indicate instability, which causes increases in severity.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Note that this doesn't refer to stability as a percentage: This is an arbitrary
|
||||||
|
/// value that only matters in relation to the <see cref="GrowthThreshold"/> and <see cref="DecayThreshold"/>
|
||||||
|
/// </remarks>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float Stability = 0f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How severe the effects of an anomaly are. Moves only upwards.
|
||||||
|
/// Ranges from 0 to 1.
|
||||||
|
/// A value of 0 indicates effects of extrememly minimal severity, whereas greater
|
||||||
|
/// values indicate effects of linearly increasing severity.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Wacky-Stability scale lives on in my heart. - emo
|
||||||
|
/// </remarks>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float Severity = 0f;
|
||||||
|
|
||||||
|
#region Health
|
||||||
|
/// <summary>
|
||||||
|
/// The internal "health" of an anomaly.
|
||||||
|
/// Ranges from 0 to 1.
|
||||||
|
/// When the health of an anomaly reaches 0, it is destroyed without ever
|
||||||
|
/// reaching a supercritical point.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float Health = 1f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the <see cref="Stability"/> of the anomaly exceeds this value, it
|
||||||
|
/// becomes too unstable to support itself and starts decreasing in <see cref="Health"/>.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("decayhreshold"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float DecayThreshold = 0.15f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of health lost when the stability is below the <see cref="DecayThreshold"/>
|
||||||
|
/// </summary>
|
||||||
|
[DataField("healthChangePerSecond"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float HealthChangePerSecond = -0.05f;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Growth
|
||||||
|
/// <summary>
|
||||||
|
/// If the <see cref="Stability"/> of the anomaly exceeds this value, it
|
||||||
|
/// becomes unstable and starts increasing in <see cref="Severity"/>.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("growthThreshold"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float GrowthThreshold = 0.5f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A coefficient used for calculating the increase in severity when above the GrowthThreshold
|
||||||
|
/// </summary>
|
||||||
|
[DataField("severityGrowthCoefficient"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float SeverityGrowthCoefficient = 0.07f;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Pulse
|
||||||
|
/// <summary>
|
||||||
|
/// The time at which the next artifact pulse will occur.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("nextPulseTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public TimeSpan NextPulseTime = TimeSpan.MaxValue;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The minimum interval between pulses.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("minPulseLength")]
|
||||||
|
public TimeSpan MinPulseLength = TimeSpan.FromMinutes(1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum interval between pulses.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("maxPulseLength")]
|
||||||
|
public TimeSpan MaxPulseLength = TimeSpan.FromMinutes(2);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A percentage by which the length of a pulse might vary.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("pulseVariation")]
|
||||||
|
public float PulseVariation = .1f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sound played when an anomaly pulses
|
||||||
|
/// </summary>
|
||||||
|
[DataField("pulseSound")]
|
||||||
|
public SoundSpecifier? PulseSound = new SoundCollectionSpecifier("RadiationPulse");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sound plays when an anomaly goes supercritical
|
||||||
|
/// </summary>
|
||||||
|
[DataField("supercriticalSound")]
|
||||||
|
public SoundSpecifier? SupercriticalSound = new SoundCollectionSpecifier("explosion");
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The range of initial values for stability
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// +/- 0.2 from perfect stability (0.5)
|
||||||
|
/// </remarks>
|
||||||
|
[DataField("initialStabilityRange")]
|
||||||
|
public (float, float) InitialStabilityRange = (0.4f, 0.6f);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The range of initial values for severity
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Between 0 and 0.5, which should be all mild effects
|
||||||
|
/// </remarks>
|
||||||
|
[DataField("initialSeverityRange")]
|
||||||
|
public (float, float) InitialSeverityRange = (0.1f, 0.5f);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The particle type that increases the severity of the anomaly.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("severityParticleType")]
|
||||||
|
public AnomalousParticleType SeverityParticleType;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount that the <see cref="Severity"/> increases by when hit
|
||||||
|
/// of an anomalous particle of <seealso cref="SeverityParticleType"/>.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("severityPerSeverityHit")]
|
||||||
|
public float SeverityPerSeverityHit = 0.025f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The particle type that destabilizes the anomaly.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("destabilizingParticleType")]
|
||||||
|
public AnomalousParticleType DestabilizingParticleType;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount that the <see cref="Stability"/> increases by when hit
|
||||||
|
/// of an anomalous particle of <seealso cref="DestabilizingParticleType"/>.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("stabilityPerDestabilizingHit")]
|
||||||
|
public float StabilityPerDestabilizingHit = 0.04f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The particle type that weakens the anomalys health.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("weakeningParticleType")]
|
||||||
|
public AnomalousParticleType WeakeningParticleType;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount that the <see cref="Stability"/> increases by when hit
|
||||||
|
/// of an anomalous particle of <seealso cref="DestabilizingParticleType"/>.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("healthPerWeakeningeHit")]
|
||||||
|
public float HealthPerWeakeningeHit = -0.05f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount that the <see cref="Stability"/> increases by when hit
|
||||||
|
/// of an anomalous particle of <seealso cref="DestabilizingParticleType"/>.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("stabilityPerWeakeningeHit")]
|
||||||
|
public float StabilityPerWeakeningeHit = -0.02f;
|
||||||
|
|
||||||
|
#region Points and Vessels
|
||||||
|
/// <summary>
|
||||||
|
/// The vessel that the anomaly is connceted to. Stored so that multiple
|
||||||
|
/// vessels cannot connect to the same anomaly.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public EntityUid? ConnectedVessel;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The minimum amount of research points generated per second
|
||||||
|
/// </summary>
|
||||||
|
[DataField("minPointsPerSecond")]
|
||||||
|
public int MinPointsPerSecond;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum amount of research points generated per second
|
||||||
|
/// This doesn't include the point bonus for being unstable.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("maxPointsPerSecond")]
|
||||||
|
public int MaxPointsPerSecond = 100;
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class AnomalyComponentState : ComponentState
|
||||||
|
{
|
||||||
|
public float Severity;
|
||||||
|
public float Stability;
|
||||||
|
public float Health;
|
||||||
|
public TimeSpan NextPulseTime;
|
||||||
|
|
||||||
|
public AnomalyComponentState(float severity, float stability, float health, TimeSpan nextPulseTime)
|
||||||
|
{
|
||||||
|
Severity = severity;
|
||||||
|
Stability = stability;
|
||||||
|
Health = health;
|
||||||
|
NextPulseTime = nextPulseTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event raised at regular intervals on an anomaly to do whatever its effect is.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Stabiltiy"></param>
|
||||||
|
/// <param name="Severity"></param>
|
||||||
|
[ByRefEvent]
|
||||||
|
public readonly record struct AnomalyPulseEvent(float Stabiltiy, float Severity)
|
||||||
|
{
|
||||||
|
public readonly float Stabiltiy = Stabiltiy;
|
||||||
|
public readonly float Severity = Severity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event raised on an anomaly when it reaches a supercritical point.
|
||||||
|
/// </summary>
|
||||||
|
[ByRefEvent]
|
||||||
|
public readonly record struct AnomalySupercriticalEvent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event broadcast after an anomaly goes supercritical
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Anomaly">The anomaly being shut down.</param>
|
||||||
|
/// <param name="Supercritical">Whether or not the anomaly shut down passively or via a supercritical event.</param>
|
||||||
|
[ByRefEvent]
|
||||||
|
public readonly record struct AnomalyShutdownEvent(EntityUid Anomaly, bool Supercritical);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event broadcast when an anomaly's severity is changed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Anomaly">The anomaly being changed</param>
|
||||||
|
[ByRefEvent]
|
||||||
|
public readonly record struct AnomalySeverityChangedEvent(EntityUid Anomaly, float Severity);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event broadcast when an anomaly's stability is changed.
|
||||||
|
/// </summary>
|
||||||
|
[ByRefEvent]
|
||||||
|
public readonly record struct AnomalyStabilityChangedEvent(EntityUid Anomaly, float Stability);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event broadcast when an anomaly's health is changed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Anomaly">The anomaly being changed</param>
|
||||||
|
[ByRefEvent]
|
||||||
|
public readonly record struct AnomalyHealthChangedEvent(EntityUid Anomaly, float Health);
|
||||||
22
Content.Shared/Anomaly/Components/AnomalyPulsingComponent.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
|
||||||
|
namespace Content.Shared.Anomaly.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This component tracks anomalies that are currently pulsing
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class AnomalyPulsingComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The time at which the pulse will be over.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("endTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public TimeSpan EndTime = TimeSpan.MaxValue;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long the pulse visual lasts
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public TimeSpan PulseDuration = TimeSpan.FromSeconds(5);
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
|
||||||
|
namespace Content.Shared.Anomaly.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tracks anomalies going supercritical
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, NetworkedComponent]
|
||||||
|
public sealed class AnomalySupercriticalComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The time when the supercritical animation ends and it does whatever effect.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("endTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public TimeSpan EndTime = TimeSpan.MaxValue;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The length of the animation before it goes supercritical.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public TimeSpan SupercriticalDuration = TimeSpan.FromSeconds(10);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum size the anomaly scales to while going supercritical
|
||||||
|
/// </summary>
|
||||||
|
[DataField("maxScaleAmount")]
|
||||||
|
public float MaxScaleAmount = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class AnomalySupercriticalComponentState : ComponentState
|
||||||
|
{
|
||||||
|
public TimeSpan EndTime;
|
||||||
|
public TimeSpan Duration;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace Content.Shared.Anomaly.Effects.Components;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class ElectricityAnomalyComponent : Component
|
||||||
|
{
|
||||||
|
[DataField("maxElectrocutionRange"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float MaxElectrocuteRange = 6f;
|
||||||
|
|
||||||
|
[DataField("maxElectrocuteDamage"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float MaxElectrocuteDamage = 20f;
|
||||||
|
|
||||||
|
[DataField("maxElectrocuteDuration"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public TimeSpan MaxElectrocuteDuration = TimeSpan.FromSeconds(8);
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
namespace Content.Shared.Anomaly.Effects.Components;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class GravityAnomalyComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The maximumum size the GravityWellComponent MaxRange can be.
|
||||||
|
/// Is scaled linearly with stability.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("maxGravityWellRange"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float MaxGravityWellRange = 8f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum distance from which the anomaly
|
||||||
|
/// can throw you via a pulse.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("maxThrowRange"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float MaxThrowRange = 5f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum strength the anomaly
|
||||||
|
/// can throw you via a pulse
|
||||||
|
/// </summary>
|
||||||
|
[DataField("maxThrowStrength"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float MaxThrowStrength = 10;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum Intensity of the RadiationSourceComponent.
|
||||||
|
/// Is scaled linearly with stability.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("maxRadiationIntensity"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float MaxRadiationIntensity = 3f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The minimum acceleration value for GravityWellComponent
|
||||||
|
/// Is scaled linearly with stability.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("minAccel"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float MinAccel = 1f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum acceleration value for GravityWellComponent
|
||||||
|
/// Is scaled linearly with stability.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("maxAccel"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float MaxAccel = 5f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The range around the anomaly that will be spaced on supercritical.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("spaceRange"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float SpaceRange = 3f;
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
using Content.Shared.Atmos;
|
||||||
|
|
||||||
|
namespace Content.Shared.Anomaly.Effects.Components;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class PyroclasticAnomalyComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The MAXIMUM amount of heat released per second.
|
||||||
|
/// This is scaled linearly with the Severity of the anomaly.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// I have no clue if this is balanced.
|
||||||
|
/// </remarks>
|
||||||
|
[DataField("heatPerSecond")]
|
||||||
|
public float HeatPerSecond = 50;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum distance from which you can be ignited by the anomaly.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("maximumIgnitionRadius")]
|
||||||
|
public float MaximumIgnitionRadius = 8f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The minimum amount of severity required
|
||||||
|
/// before the anomaly becomes a hotspot.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("anomalyHotspotThreshold")]
|
||||||
|
public float AnomalyHotspotThreshold = 0.6f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The temperature of the hotspot where the anomaly is
|
||||||
|
/// </summary>
|
||||||
|
[DataField("hotspotExposeTemperature")]
|
||||||
|
public float HotspotExposeTemperature = 1000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The volume of the hotspot where the anomaly is.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("hotspotExposeVolume")]
|
||||||
|
public float HotspotExposeVolume = 50;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gas released when the anomaly goes supercritical.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("supercriticalGas")]
|
||||||
|
public Gas SupercriticalGas = Gas.Plasma;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of gas released when the anomaly goes supercritical
|
||||||
|
/// </summary>
|
||||||
|
[DataField("supercriticalMoleAmount")]
|
||||||
|
public float SupercriticalMoleAmount = 50f;
|
||||||
|
}
|
||||||
65
Content.Shared/Anomaly/Effects/SharedGravityAnomalySystem.cs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Content.Shared.Anomaly.Components;
|
||||||
|
using Content.Shared.Anomaly.Effects.Components;
|
||||||
|
using Content.Shared.Construction.Components;
|
||||||
|
using Content.Shared.Construction.EntitySystems;
|
||||||
|
using Content.Shared.Throwing;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
|
||||||
|
namespace Content.Shared.Anomaly.Effects;
|
||||||
|
|
||||||
|
public abstract class SharedGravityAnomalySystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IMapManager _map = default!;
|
||||||
|
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||||
|
[Dependency] private readonly SharedAnchorableSystem _anchorable = default!;
|
||||||
|
[Dependency] private readonly ThrowingSystem _throwing = default!;
|
||||||
|
[Dependency] private readonly SharedTransformSystem _xform = default!;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<GravityAnomalyComponent, AnomalyPulseEvent>(OnAnomalyPulse);
|
||||||
|
SubscribeLocalEvent<GravityAnomalyComponent, AnomalySupercriticalEvent>(OnSupercritical);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAnomalyPulse(EntityUid uid, GravityAnomalyComponent component, ref AnomalyPulseEvent args)
|
||||||
|
{
|
||||||
|
var xform = Transform(uid);
|
||||||
|
var range = component.MaxThrowRange * args.Severity;
|
||||||
|
var strength = component.MaxThrowStrength * args.Severity;
|
||||||
|
var lookup = _lookup.GetEntitiesInRange(uid, 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.Normalized * 10, strength, uid, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSupercritical(EntityUid uid, GravityAnomalyComponent component, ref AnomalySupercriticalEvent args)
|
||||||
|
{
|
||||||
|
var xform = Transform(uid);
|
||||||
|
if (!_map.TryGetGrid(xform.GridUid, out var grid))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var worldPos = _xform.GetWorldPosition(xform);
|
||||||
|
var tileref = grid.GetTilesIntersecting(new Circle(worldPos, component.SpaceRange)).ToArray();
|
||||||
|
var tiles = tileref.Select(t => (t.GridIndices, Tile.Empty)).ToList();
|
||||||
|
grid.SetTiles(tiles);
|
||||||
|
|
||||||
|
var range = component.MaxThrowRange * 2;
|
||||||
|
var strength = component.MaxThrowStrength * 2;
|
||||||
|
var lookup = _lookup.GetEntitiesInRange(uid, range, LookupFlags.Dynamic | LookupFlags.Sundries);
|
||||||
|
foreach (var ent in lookup)
|
||||||
|
{
|
||||||
|
var tempXform = Transform(ent);
|
||||||
|
|
||||||
|
var foo = tempXform.MapPosition.Position - xform.MapPosition.Position;
|
||||||
|
Logger.Debug($"{ToPrettyString(ent)}: {foo}: {foo.Normalized}: {foo.Normalized * 10}");
|
||||||
|
_throwing.TryThrow(ent, foo * 5, strength, uid, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
97
Content.Shared/Anomaly/SharedAnomaly.cs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Shared.Anomaly;
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum AnomalyVisuals : byte
|
||||||
|
{
|
||||||
|
IsPulsing,
|
||||||
|
Supercritical
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum AnomalyVisualLayers : byte
|
||||||
|
{
|
||||||
|
Base,
|
||||||
|
Animated
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The types of anomalous particles used
|
||||||
|
/// for interfacing with anomalies.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The only thought behind these names is that
|
||||||
|
/// they're a continuation of radioactive particles.
|
||||||
|
/// Yes i know detla+ waves exist, but they're not
|
||||||
|
/// common enough for me to care.
|
||||||
|
/// </remarks>
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum AnomalousParticleType : byte
|
||||||
|
{
|
||||||
|
Delta,
|
||||||
|
Epsilon,
|
||||||
|
Zeta
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum AnomalyVesselVisuals : byte
|
||||||
|
{
|
||||||
|
HasAnomaly
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum AnomalyVesselVisualLayers : byte
|
||||||
|
{
|
||||||
|
Base
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum AnomalyScannerUiKey : byte
|
||||||
|
{
|
||||||
|
Key
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class AnomalyScannerUserInterfaceState : BoundUserInterfaceState
|
||||||
|
{
|
||||||
|
public FormattedMessage Message;
|
||||||
|
|
||||||
|
public TimeSpan? NextPulseTime;
|
||||||
|
|
||||||
|
public AnomalyScannerUserInterfaceState(FormattedMessage message, TimeSpan? nextPulseTime)
|
||||||
|
{
|
||||||
|
Message = message;
|
||||||
|
NextPulseTime = nextPulseTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum AnomalyGeneratorUiKey : byte
|
||||||
|
{
|
||||||
|
Key
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class AnomalyGeneratorUserInterfaceState : BoundUserInterfaceState
|
||||||
|
{
|
||||||
|
public TimeSpan CooldownEndTime;
|
||||||
|
|
||||||
|
public int FuelAmount;
|
||||||
|
|
||||||
|
public int FuelCost;
|
||||||
|
|
||||||
|
public AnomalyGeneratorUserInterfaceState(TimeSpan cooldownEndTime, int fuelAmount, int fuelCost)
|
||||||
|
{
|
||||||
|
CooldownEndTime = cooldownEndTime;
|
||||||
|
FuelAmount = fuelAmount;
|
||||||
|
FuelCost = fuelCost;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class AnomalyGeneratorGenerateButtonPressedEvent : BoundUserInterfaceMessage
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
319
Content.Shared/Anomaly/SharedAnomalySystem.cs
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
using Content.Shared.Administration.Logs;
|
||||||
|
using Content.Shared.Anomaly.Components;
|
||||||
|
using Content.Shared.Database;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Shared.Anomaly;
|
||||||
|
|
||||||
|
public abstract class SharedAnomalySystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] protected readonly IGameTiming Timing = default!;
|
||||||
|
[Dependency] private readonly INetManager _net = default!;
|
||||||
|
[Dependency] protected readonly IRobustRandom Random = default!;
|
||||||
|
[Dependency] protected readonly ISharedAdminLogManager Log = default!;
|
||||||
|
[Dependency] protected readonly SharedAudioSystem Audio = default!;
|
||||||
|
[Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<AnomalyComponent, ComponentGetState>(OnAnomalyGetState);
|
||||||
|
SubscribeLocalEvent<AnomalyComponent, ComponentHandleState>(OnAnomalyHandleState);
|
||||||
|
SubscribeLocalEvent<AnomalySupercriticalComponent, ComponentGetState>(OnSupercriticalGetState);
|
||||||
|
SubscribeLocalEvent<AnomalySupercriticalComponent, ComponentHandleState>(OnSupercriticalHandleState);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<AnomalyComponent, EntityUnpausedEvent>(OnAnomalyUnpause);
|
||||||
|
SubscribeLocalEvent<AnomalyPulsingComponent, EntityUnpausedEvent>(OnPulsingUnpause);
|
||||||
|
SubscribeLocalEvent<AnomalySupercriticalComponent, EntityUnpausedEvent>(OnSupercriticalUnpause);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAnomalyGetState(EntityUid uid, AnomalyComponent component, ref ComponentGetState args)
|
||||||
|
{
|
||||||
|
args.State = new AnomalyComponentState(
|
||||||
|
component.Severity,
|
||||||
|
component.Stability,
|
||||||
|
component.Health,
|
||||||
|
component.NextPulseTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAnomalyHandleState(EntityUid uid, AnomalyComponent component, ref ComponentHandleState args)
|
||||||
|
{
|
||||||
|
if (args.Current is not AnomalyComponentState state)
|
||||||
|
return;
|
||||||
|
component.Severity = state.Severity;
|
||||||
|
component.Stability = state.Stability;
|
||||||
|
component.Health = state.Health;
|
||||||
|
component.NextPulseTime = state.NextPulseTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSupercriticalGetState(EntityUid uid, AnomalySupercriticalComponent component, ref ComponentGetState args)
|
||||||
|
{
|
||||||
|
args.State = new AnomalySupercriticalComponentState
|
||||||
|
{
|
||||||
|
EndTime = component.EndTime,
|
||||||
|
Duration = component.SupercriticalDuration
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSupercriticalHandleState(EntityUid uid, AnomalySupercriticalComponent component, ref ComponentHandleState args)
|
||||||
|
{
|
||||||
|
if (args.Current is not AnomalySupercriticalComponentState state)
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.EndTime = state.EndTime;
|
||||||
|
component.SupercriticalDuration = state.Duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAnomalyUnpause(EntityUid uid, AnomalyComponent component, ref EntityUnpausedEvent args)
|
||||||
|
{
|
||||||
|
component.NextPulseTime += args.PausedTime;
|
||||||
|
Dirty(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPulsingUnpause(EntityUid uid, AnomalyPulsingComponent component, ref EntityUnpausedEvent args)
|
||||||
|
{
|
||||||
|
component.EndTime += args.PausedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSupercriticalUnpause(EntityUid uid, AnomalySupercriticalComponent component, ref EntityUnpausedEvent args)
|
||||||
|
{
|
||||||
|
component.EndTime += args.PausedTime;
|
||||||
|
Dirty(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DoAnomalyPulse(EntityUid uid, AnomalyComponent? component = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var variation = Random.NextFloat(-component.PulseVariation, component.PulseVariation) + 1;
|
||||||
|
component.NextPulseTime = Timing.CurTime + GetPulseLength(component) * variation;
|
||||||
|
|
||||||
|
// if we are above the growth threshold, then grow before the pulse
|
||||||
|
if (component.Stability > component.GrowthThreshold)
|
||||||
|
{
|
||||||
|
ChangeAnomalySeverity(uid, GetSeverityIncreaseFromGrowth(component), component);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// just doing this to update the scanner ui
|
||||||
|
// as they hook into these events
|
||||||
|
ChangeAnomalySeverity(uid, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Add(LogType.Anomaly, LogImpact.Medium, $"Anomaly {ToPrettyString(uid)} pulsed with severity {component.Severity}.");
|
||||||
|
Audio.PlayPvs(component.PulseSound, uid);
|
||||||
|
|
||||||
|
var pulse = EnsureComp<AnomalyPulsingComponent>(uid);
|
||||||
|
pulse.EndTime = Timing.CurTime + pulse.PulseDuration;
|
||||||
|
Appearance.SetData(uid, AnomalyVisuals.IsPulsing, true);
|
||||||
|
|
||||||
|
var ev = new AnomalyPulseEvent(component.Stability, component.Severity);
|
||||||
|
RaiseLocalEvent(uid, ref ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Begins the animation for going supercritical
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uid"></param>
|
||||||
|
public void StartSupercriticalEvent(EntityUid uid)
|
||||||
|
{
|
||||||
|
// don't restart it if it's already begun
|
||||||
|
if (HasComp<AnomalySupercriticalComponent>(uid))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Log.Add(LogType.Anomaly, LogImpact.High, $"Anomaly {ToPrettyString(uid)} began to go supercritical.");
|
||||||
|
|
||||||
|
var super = EnsureComp<AnomalySupercriticalComponent>(uid);
|
||||||
|
super.EndTime = Timing.CurTime + super.SupercriticalDuration;
|
||||||
|
Appearance.SetData(uid, AnomalyVisuals.Supercritical, true);
|
||||||
|
Dirty(super);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Does the supercritical event for the anomaly.
|
||||||
|
/// This isn't called once the anomaly reaches the point, but
|
||||||
|
/// after the animation for it going supercritical
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uid"></param>
|
||||||
|
/// <param name="component"></param>
|
||||||
|
public void DoAnomalySupercriticalEvent(EntityUid uid, AnomalyComponent? component = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component))
|
||||||
|
return;
|
||||||
|
Audio.PlayPvs(component.SupercriticalSound, uid);
|
||||||
|
|
||||||
|
var ev = new AnomalySupercriticalEvent();
|
||||||
|
RaiseLocalEvent(uid, ref ev);
|
||||||
|
|
||||||
|
EndAnomaly(uid, component, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ends an anomaly, cleaning up all entities that may be associated with it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uid">The anomaly being shut down</param>
|
||||||
|
/// <param name="component"></param>
|
||||||
|
/// <param name="supercritical">Whether or not the anomaly ended via supercritical event</param>
|
||||||
|
public void EndAnomaly(EntityUid uid, AnomalyComponent? component = null, bool supercritical = false)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var ev = new AnomalyShutdownEvent(uid, supercritical);
|
||||||
|
RaiseLocalEvent(uid, ref ev, true);
|
||||||
|
|
||||||
|
Log.Add(LogType.Anomaly, LogImpact.Extreme, $"Anomaly {ToPrettyString(uid)} went supercritical.");
|
||||||
|
|
||||||
|
if (Terminating(uid) || _net.IsClient)
|
||||||
|
return;
|
||||||
|
Del(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the stability of the anomaly.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uid"></param>
|
||||||
|
/// <param name="change"></param>
|
||||||
|
/// <param name="component"></param>
|
||||||
|
public void ChangeAnomalyStability(EntityUid uid, float change, AnomalyComponent? component = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var newVal = component.Stability + change;
|
||||||
|
|
||||||
|
component.Stability = Math.Clamp(newVal, 0, 1);
|
||||||
|
Dirty(component);
|
||||||
|
|
||||||
|
var ev = new AnomalyStabilityChangedEvent(uid, component.Stability);
|
||||||
|
RaiseLocalEvent(uid, ref ev, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the severity of an anomaly, going supercritical if it exceeds 1.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uid"></param>
|
||||||
|
/// <param name="change"></param>
|
||||||
|
/// <param name="component"></param>
|
||||||
|
public void ChangeAnomalySeverity(EntityUid uid, float change, AnomalyComponent? component = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var newVal = component.Severity + change;
|
||||||
|
|
||||||
|
if (newVal >= 1)
|
||||||
|
StartSupercriticalEvent(uid);
|
||||||
|
|
||||||
|
component.Severity = Math.Clamp(newVal, 0, 1);
|
||||||
|
Dirty(component);
|
||||||
|
|
||||||
|
var ev = new AnomalySeverityChangedEvent(uid, component.Severity);
|
||||||
|
RaiseLocalEvent(uid, ref ev, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the health of an anomaly, ending it if it's less than 0.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uid"></param>
|
||||||
|
/// <param name="change"></param>
|
||||||
|
/// <param name="component"></param>
|
||||||
|
public void ChangeAnomalyHealth(EntityUid uid, float change, AnomalyComponent? component = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var newVal = component.Health + change;
|
||||||
|
|
||||||
|
if (newVal < 0)
|
||||||
|
{
|
||||||
|
EndAnomaly(uid, component);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
component.Health = Math.Clamp(newVal, 0, 1);
|
||||||
|
Dirty(component);
|
||||||
|
|
||||||
|
var ev = new AnomalyHealthChangedEvent(uid, component.Health);
|
||||||
|
RaiseLocalEvent(uid, ref ev, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the length of time between each pulse
|
||||||
|
/// for an anomaly based on its current stability.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// For anomalies under the instability theshold, this will return the maximum length.
|
||||||
|
/// For those over the theshold, they will return an amount between the maximum and
|
||||||
|
/// minium value based on a linear relationship with the stability.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="component"></param>
|
||||||
|
/// <returns>The length of time as a TimeSpan, not including random variation.</returns>
|
||||||
|
public TimeSpan GetPulseLength(AnomalyComponent component)
|
||||||
|
{
|
||||||
|
DebugTools.Assert(component.MaxPulseLength > component.MinPulseLength);
|
||||||
|
var modifier = Math.Clamp((component.Stability - component.GrowthThreshold) / component.GrowthThreshold, 0, 1);
|
||||||
|
return (component.MaxPulseLength - component.MinPulseLength) * modifier + component.MinPulseLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the increase in an anomaly's severity due
|
||||||
|
/// to being above its growth threshold
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="component"></param>
|
||||||
|
/// <returns>The increase in severity for this anomaly</returns>
|
||||||
|
private float GetSeverityIncreaseFromGrowth(AnomalyComponent component)
|
||||||
|
{
|
||||||
|
var score = 1 + Math.Max(component.Stability - component.GrowthThreshold, 0) * 10;
|
||||||
|
return score * component.SeverityGrowthCoefficient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
base.Update(frameTime);
|
||||||
|
|
||||||
|
foreach (var anomaly in EntityQuery<AnomalyComponent>())
|
||||||
|
{
|
||||||
|
var ent = anomaly.Owner;
|
||||||
|
|
||||||
|
// if the stability is under the death threshold,
|
||||||
|
// update it every second to start killing it slowly.
|
||||||
|
if (anomaly.Stability < anomaly.DecayThreshold)
|
||||||
|
{
|
||||||
|
ChangeAnomalyHealth(ent, anomaly.HealthChangePerSecond * frameTime, anomaly);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Timing.CurTime > anomaly.NextPulseTime)
|
||||||
|
{
|
||||||
|
DoAnomalyPulse(ent, anomaly);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var pulse in EntityQuery<AnomalyPulsingComponent>())
|
||||||
|
{
|
||||||
|
var ent = pulse.Owner;
|
||||||
|
|
||||||
|
if (Timing.CurTime > pulse.EndTime)
|
||||||
|
{
|
||||||
|
Appearance.SetData(ent, AnomalyVisuals.IsPulsing, false);
|
||||||
|
RemComp(ent, pulse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (super, anom) in EntityQuery<AnomalySupercriticalComponent, AnomalyComponent>())
|
||||||
|
{
|
||||||
|
var ent = anom.Owner;
|
||||||
|
|
||||||
|
if (Timing.CurTime <= super.EndTime)
|
||||||
|
continue;
|
||||||
|
DoAnomalySupercriticalEvent(ent, anom);
|
||||||
|
RemComp(ent, super);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Content.Shared.Construction.Prototypes;
|
using Content.Shared.Construction.Prototypes;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||||
|
|
||||||
namespace Content.Shared.Singularity.Components;
|
namespace Content.Shared.Singularity.Components;
|
||||||
|
|
||||||
@@ -25,9 +27,12 @@ public sealed class EmitterComponent : Component
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The entity that is spawned when the emitter fires.
|
/// The entity that is spawned when the emitter fires.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("boltType")]
|
[DataField("boltType", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||||
public string BoltType = "EmitterBolt";
|
public string BoltType = "EmitterBolt";
|
||||||
|
|
||||||
|
[DataField("selectableTypes", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
||||||
|
public List<string> SelectableTypes = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current amount of power being used.
|
/// The current amount of power being used.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -81,5 +81,7 @@ namespace Content.Shared.Verbs
|
|||||||
public static readonly VerbCategory SetSensor = new("verb-categories-set-sensor", null);
|
public static readonly VerbCategory SetSensor = new("verb-categories-set-sensor", null);
|
||||||
|
|
||||||
public static readonly VerbCategory Lever = new("verb-categories-lever", null);
|
public static readonly VerbCategory Lever = new("verb-categories-lever", null);
|
||||||
|
|
||||||
|
public static readonly VerbCategory SelectType = new("verb-categories-select-type", null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
Resources/Audio/Ambience/Objects/anomaly_generator.ogg
Normal file
4
Resources/Audio/Ambience/Objects/attributions.yml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
- files: ["anomaly_generator.ogg"]
|
||||||
|
license: "CC0-1.0"
|
||||||
|
copyright: "Created by steaq, converted Mono and .ogg by EmoGarbage"
|
||||||
|
source: "https://freesound.org/people/steaq/sounds/509249/"
|
||||||
BIN
Resources/Audio/Ambience/anomaly_drone.ogg
Normal file
4
Resources/Audio/Ambience/attributions.yml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
- files: ["anomaly_drone.ogg"]
|
||||||
|
license: "CC0-1.0"
|
||||||
|
copyright: "Created by Joao_Janz, edited and converted to Mono by EmoGarbage"
|
||||||
|
source: "https://freesound.org/people/Joao_Janz/sounds/478472/"
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
|
- files: ["beep.ogg"]
|
||||||
|
license: "CC0-1.0"
|
||||||
|
copyright: "Created by Pól, converted to OGG and Mono by EmoGarbage"
|
||||||
|
source: "https://freesound.org/people/P%C3%B3l/sounds/385927/"
|
||||||
|
|
||||||
- files: ["trayhit1.ogg"]
|
- files: ["trayhit1.ogg"]
|
||||||
license: "CC-BY-SA-3.0"
|
license: "CC-BY-SA-3.0"
|
||||||
copyright: "Time immemorial"
|
copyright: "Time immemorial"
|
||||||
|
|||||||
BIN
Resources/Audio/Items/beep.ogg
Normal file
30
Resources/Locale/en-US/anomaly/anomaly.ftl
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
anomaly-vessel-component-anomaly-assigned = Anomaly assigned to vessel.
|
||||||
|
anomaly-vessel-component-not-assigned = This vessel is not assigned to any anomaly. Try using a scanner on it.
|
||||||
|
anomaly-vessel-component-assigned = This vessel is currently assigned to an anomaly.
|
||||||
|
|
||||||
|
anomaly-particles-delta = Delta particles
|
||||||
|
anomaly-particles-epsilon = Epsilon particles
|
||||||
|
anomaly-particles-zeta = Zeta particles
|
||||||
|
|
||||||
|
anomaly-scanner-component-scan-complete = Scan complete!
|
||||||
|
|
||||||
|
anomaly-scanner-ui-title = anomaly scanner
|
||||||
|
anomaly-scanner-no-anomaly = No anomaly currently scanned.
|
||||||
|
anomaly-scanner-severity-percentage = Current severity: [color=gray]{$percent}[/color]
|
||||||
|
anomaly-scanner-stability-low = Current anomaly state: [color=gold]Decaying[/color]
|
||||||
|
anomaly-scanner-stability-medium = Current anomaly state: [color=forestgreen]Stable[/color]
|
||||||
|
anomaly-scanner-stability-high = Current anomaly state: [color=crimson]Growing[/color]
|
||||||
|
anomaly-scanner-point-output = Approximate point output: [color=gray]{$point}[/color]
|
||||||
|
anomaly-scanner-particle-readout = Particle Reaction Analysis:
|
||||||
|
anomaly-scanner-particle-danger = - [color=crimson]Danger type:[/color] {$type}
|
||||||
|
anomaly-scanner-particle-unstable = - [color=plum]Unstable type:[/color] {$type}
|
||||||
|
anomaly-scanner-particle-containment = - [color=goldenrod]Containment type:[/color] {$type}
|
||||||
|
anomaly-scanner-pulse-timer = Time until next pulse: [color=gray]{$time}[/color]
|
||||||
|
|
||||||
|
anomaly-generator-ui-title = anomaly generator
|
||||||
|
anomaly-generator-fuel-display = Fuel:
|
||||||
|
anomaly-generator-cooldown = Cooldown: [color=gray]{$time}[/color]
|
||||||
|
anomaly-generator-no-cooldown = Cooldown: [color=gray]Complete[/color]
|
||||||
|
anomaly-generator-yes-fire = Status: [color=forestgreen]Ready[/color]
|
||||||
|
anomaly-generator-no-fire = Status: [color=crimson]Not ready[/color]
|
||||||
|
anomaly-generator-generate = Generate Anomaly
|
||||||
@@ -73,6 +73,9 @@ technologies-super-powercell-printing-description = Print super powercells.
|
|||||||
technologies-scientific-technology = Scientific technology
|
technologies-scientific-technology = Scientific technology
|
||||||
technologies-scientific-technology-description = The basics of a research team's supplies.
|
technologies-scientific-technology-description = The basics of a research team's supplies.
|
||||||
|
|
||||||
|
technologies-anomaly-technology = Anomaly technology
|
||||||
|
technologies-anomaly-technology-description = Machines for advanced anomaly containment.
|
||||||
|
|
||||||
technologies-robotics-technology = Robotics technology
|
technologies-robotics-technology = Robotics technology
|
||||||
technologies-robotics-technology-description = Parts needed for constructing mechanized friends.
|
technologies-robotics-technology-description = Parts needed for constructing mechanized friends.
|
||||||
|
|
||||||
|
|||||||
@@ -13,3 +13,6 @@ comp-emitter-not-anchored = The {$target} isn't anchored to the ground!
|
|||||||
|
|
||||||
# Upgrades
|
# Upgrades
|
||||||
emitter-component-upgrade-fire-rate = fire rate
|
emitter-component-upgrade-fire-rate = fire rate
|
||||||
|
|
||||||
|
emitter-component-current-type = The current selected type is: {$type}.
|
||||||
|
emitter-component-type-set = Type set to: {$type}
|
||||||
@@ -26,6 +26,7 @@ verb-categories-channel-select = Channels
|
|||||||
verb-categories-set-sensor = Sensor
|
verb-categories-set-sensor = Sensor
|
||||||
verb-categories-timer = Set Delay
|
verb-categories-timer = Set Delay
|
||||||
verb-categories-lever = Lever
|
verb-categories-lever = Lever
|
||||||
|
verb-categories-select-type = Select Type
|
||||||
verb-categories-fax = Set Destination
|
verb-categories-fax = Set Destination
|
||||||
|
|
||||||
verb-common-toggle-light = Toggle light
|
verb-common-toggle-light = Toggle light
|
||||||
|
|||||||
@@ -9,3 +9,9 @@
|
|||||||
- id: ClothingHeadsetScience
|
- id: ClothingHeadsetScience
|
||||||
- id: ClothingMaskSterile
|
- id: ClothingMaskSterile
|
||||||
- id: ClothingOuterCoatLab
|
- id: ClothingOuterCoatLab
|
||||||
|
- id: AnomalyScanner
|
||||||
|
prob: 0.5
|
||||||
|
orGroup: Scanner
|
||||||
|
- id: NodeScanner
|
||||||
|
prob: 0.5
|
||||||
|
orGroup: Scanner
|
||||||
|
|||||||
@@ -518,6 +518,21 @@
|
|||||||
- MicroManipulatorStockPart
|
- MicroManipulatorStockPart
|
||||||
- ScanningModuleStockPart
|
- ScanningModuleStockPart
|
||||||
- NodeScanner
|
- NodeScanner
|
||||||
|
- AnomalyScanner
|
||||||
|
|
||||||
|
- type: technology
|
||||||
|
name: technologies-anomaly-technology
|
||||||
|
id: AnomalyTechnology
|
||||||
|
description: technologies-anomaly-technology-description
|
||||||
|
icon:
|
||||||
|
sprite: Structures/Machines/Anomaly/ape.rsi
|
||||||
|
state: base
|
||||||
|
requiredPoints: 10000
|
||||||
|
requiredTechnologies:
|
||||||
|
- ScientificTechnology
|
||||||
|
unlockedRecipes:
|
||||||
|
- AnomalyVesselCircuitboard
|
||||||
|
- APECircuitboard
|
||||||
|
|
||||||
- type: technology
|
- type: technology
|
||||||
name: technologies-robotics-technology
|
name: technologies-robotics-technology
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
- type: entity
|
||||||
|
id: RandomAnomalySpawner
|
||||||
|
name: random anomaly spawner
|
||||||
|
parent: MarkerBase
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
layers:
|
||||||
|
- state: red
|
||||||
|
- sprite: Structures/Specific/anomaly.rsi
|
||||||
|
state: anom1
|
||||||
|
- type: RandomSpawner
|
||||||
|
prototypes:
|
||||||
|
- AnomalyPyroclastic
|
||||||
|
- AnomalyGravity
|
||||||
|
- AnomalyElectricity
|
||||||
|
chance: 1
|
||||||
@@ -179,6 +179,38 @@
|
|||||||
Steel: 5
|
Steel: 5
|
||||||
Cable: 1
|
Cable: 1
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: BaseMachineCircuitboard
|
||||||
|
id: AnomalyVesselCircuitboard
|
||||||
|
name: anomaly vessel machine board
|
||||||
|
description: A machine printed circuit board for an anomaly vessel
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
state: science
|
||||||
|
- type: MachineBoard
|
||||||
|
prototype: MachineAnomalyVessel
|
||||||
|
requirements:
|
||||||
|
ScanningModule: 5
|
||||||
|
materialRequirements:
|
||||||
|
Cable: 1
|
||||||
|
PlasmaGlass: 10
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: BaseMachineCircuitboard
|
||||||
|
id: APECircuitboard
|
||||||
|
name: A.P.E. machine board
|
||||||
|
description: A machine printed circuit board for an A.P.E.
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
state: science
|
||||||
|
- type: MachineBoard
|
||||||
|
prototype: MachineAPE
|
||||||
|
requirements:
|
||||||
|
Capacitor: 1
|
||||||
|
Laser: 3
|
||||||
|
materialRequirements:
|
||||||
|
Cable: 1
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: ThermomachineFreezerMachineCircuitBoard
|
id: ThermomachineFreezerMachineCircuitBoard
|
||||||
parent: BaseMachineCircuitboard
|
parent: BaseMachineCircuitboard
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
- type: entity
|
||||||
|
parent: BaseItem
|
||||||
|
id: AnomalyScanner
|
||||||
|
name: anomaly scanner
|
||||||
|
description: A hand-held scanner built to collect information on various anomalous objects.
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Objects/Specific/Research/anomalyscanner.rsi
|
||||||
|
netsync: false
|
||||||
|
state: icon
|
||||||
|
- type: ActivatableUI
|
||||||
|
key: enum.AnomalyScannerUiKey.Key
|
||||||
|
closeOnHandDeselect: false
|
||||||
|
inHandsOnly: true
|
||||||
|
- type: UserInterface
|
||||||
|
interfaces:
|
||||||
|
- key: enum.AnomalyScannerUiKey.Key
|
||||||
|
type: AnomalyScannerBoundUserInterface
|
||||||
|
- type: AnomalyScanner
|
||||||
@@ -269,6 +269,58 @@
|
|||||||
- type: TimedDespawn
|
- type: TimedDespawn
|
||||||
lifetime: 0.4
|
lifetime: 0.4
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: BaseBullet
|
||||||
|
id: AnomalousParticleDelta
|
||||||
|
name: delta particles
|
||||||
|
noSpawn: true
|
||||||
|
components:
|
||||||
|
- type: AnomalousParticle
|
||||||
|
particleType: Delta
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Objects/Weapons/Guns/Projectiles/magic.rsi
|
||||||
|
layers:
|
||||||
|
- state: magicm
|
||||||
|
shader: unshaded
|
||||||
|
- type: Ammo
|
||||||
|
muzzleFlash: null
|
||||||
|
- type: Physics
|
||||||
|
- type: Fixtures
|
||||||
|
fixtures:
|
||||||
|
- shape:
|
||||||
|
!type:PhysShapeAabb
|
||||||
|
bounds: "-0.2,-0.2,0.2,0.2"
|
||||||
|
hard: false
|
||||||
|
id: projectile
|
||||||
|
mask:
|
||||||
|
- Impassable
|
||||||
|
- Opaque
|
||||||
|
- *flybyfixture
|
||||||
|
- type: Projectile
|
||||||
|
damage:
|
||||||
|
types:
|
||||||
|
Heat: 3
|
||||||
|
- type: TimedDespawn
|
||||||
|
lifetime: 3
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: AnomalousParticleDelta
|
||||||
|
id: AnomalousParticleEpsilon
|
||||||
|
name: epsilon particles
|
||||||
|
noSpawn: true
|
||||||
|
components:
|
||||||
|
- type: AnomalousParticle
|
||||||
|
particleType: Epsilon
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: AnomalousParticleDelta
|
||||||
|
id: AnomalousParticleZeta
|
||||||
|
name: zeta particles
|
||||||
|
noSpawn: true
|
||||||
|
components:
|
||||||
|
- type: AnomalousParticle
|
||||||
|
particleType: Zeta
|
||||||
|
|
||||||
# Launcher projectiles (grenade / rocket)
|
# Launcher projectiles (grenade / rocket)
|
||||||
- type: entity
|
- type: entity
|
||||||
id: BulletRocket
|
id: BulletRocket
|
||||||
|
|||||||
@@ -0,0 +1,240 @@
|
|||||||
|
- type: entity
|
||||||
|
id: MachineAnomalyVessel
|
||||||
|
parent: [ BaseMachinePowered, ConstructibleMachine ]
|
||||||
|
name: anomaly vessel
|
||||||
|
description: A container able to harness a scan of an anomaly and turn it into research points.
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
noRot: true
|
||||||
|
sprite: Structures/Machines/Anomaly/anomaly_vessel.rsi
|
||||||
|
layers:
|
||||||
|
- state: base
|
||||||
|
- state: powered
|
||||||
|
shader: unshaded
|
||||||
|
map: ["enum.PowerDeviceVisualLayers.Powered"]
|
||||||
|
- state: anomaly
|
||||||
|
shader: unshaded
|
||||||
|
map: ["enum.AnomalyVesselVisualLayers.Base"]
|
||||||
|
- state: panel
|
||||||
|
map: ["enum.WiresVisualLayers.MaintenancePanel"]
|
||||||
|
- type: Transform
|
||||||
|
noRot: false
|
||||||
|
- type: AnomalyVessel
|
||||||
|
- type: ResearchClient
|
||||||
|
- type: ActivatableUI
|
||||||
|
key: enum.ResearchClientUiKey.Key
|
||||||
|
- type: ActivatableUIRequiresPower
|
||||||
|
- type: UserInterface
|
||||||
|
interfaces:
|
||||||
|
- key: enum.ResearchClientUiKey.Key
|
||||||
|
type: ResearchClientBoundUserInterface
|
||||||
|
- type: Machine
|
||||||
|
board: AnomalyVesselCircuitboard
|
||||||
|
- type: PointLight
|
||||||
|
radius: 1.2
|
||||||
|
energy: 2
|
||||||
|
color: "#fca3c0"
|
||||||
|
- type: Appearance
|
||||||
|
- type: Wires
|
||||||
|
BoardName: "Vessel"
|
||||||
|
LayoutId: Vessel
|
||||||
|
- type: AmbientSound
|
||||||
|
enabled: false
|
||||||
|
range: 3
|
||||||
|
volume: -8
|
||||||
|
sound:
|
||||||
|
path: /Audio/Ambience/anomaly_drone.ogg
|
||||||
|
- type: GenericVisualizer
|
||||||
|
visuals:
|
||||||
|
enum.PowerDeviceVisuals.Powered:
|
||||||
|
enum.PowerDeviceVisualLayers.Powered:
|
||||||
|
True: { visible: true }
|
||||||
|
False: { visible: false }
|
||||||
|
enum.AnomalyVesselVisuals.HasAnomaly:
|
||||||
|
enum.AnomalyVesselVisualLayers.Base:
|
||||||
|
True: { visible: true }
|
||||||
|
False: { visible: false }
|
||||||
|
enum.WiresVisuals.MaintenancePanelState:
|
||||||
|
enum.WiresVisualLayers.MaintenancePanel:
|
||||||
|
True: { visible: false }
|
||||||
|
False: { visible: true }
|
||||||
|
- type: Explosive
|
||||||
|
explosionType: Default
|
||||||
|
maxIntensity: 20
|
||||||
|
intensitySlope: 30
|
||||||
|
totalIntensity: 30
|
||||||
|
canCreateVacuum: false
|
||||||
|
- type: Destructible
|
||||||
|
thresholds:
|
||||||
|
- trigger:
|
||||||
|
!type:DamageTrigger
|
||||||
|
damage: 150
|
||||||
|
behaviors:
|
||||||
|
- !type:DoActsBehavior
|
||||||
|
acts: ["Destruction"]
|
||||||
|
- !type:PlaySoundBehavior
|
||||||
|
sound:
|
||||||
|
path: /Audio/Effects/metalbreak.ogg
|
||||||
|
- !type:ExplodeBehavior
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: MachineAPE
|
||||||
|
parent: [ BaseMachinePowered, ConstructibleMachine ]
|
||||||
|
name: A.P.E.
|
||||||
|
description: An Anomalous Particle Emitter, capable of shooting out unstable particles which can interface with anomalies.
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
noRot: true
|
||||||
|
sprite: Structures/Machines/Anomaly/ape.rsi
|
||||||
|
layers:
|
||||||
|
- state: base
|
||||||
|
- state: unshaded
|
||||||
|
shader: unshaded
|
||||||
|
map: ["enum.PowerDeviceVisualLayers.Powered"]
|
||||||
|
- state: panel
|
||||||
|
map: ["enum.WiresVisualLayers.MaintenancePanel"]
|
||||||
|
- state: firing
|
||||||
|
shader: unshaded
|
||||||
|
visible: false
|
||||||
|
map: ["enum.EmitterVisualLayers.Lights"]
|
||||||
|
- state: locked
|
||||||
|
shader: unshaded
|
||||||
|
visible: false
|
||||||
|
map: ["enum.StorageVisualLayers.Lock"]
|
||||||
|
- type: Transform
|
||||||
|
noRot: false
|
||||||
|
- type: Fixtures
|
||||||
|
fixtures:
|
||||||
|
- shape:
|
||||||
|
!type:PhysShapeCircle
|
||||||
|
radius: 0.35
|
||||||
|
density: 190
|
||||||
|
mask:
|
||||||
|
- MachineMask
|
||||||
|
layer:
|
||||||
|
- MachineLayer
|
||||||
|
- type: Rotatable
|
||||||
|
rotateWhileAnchored: true
|
||||||
|
- type: Machine
|
||||||
|
board: APECircuitboard
|
||||||
|
- type: Lock
|
||||||
|
locked: false
|
||||||
|
- type: AccessReader
|
||||||
|
access: [[ "Research" ]]
|
||||||
|
- type: Emitter
|
||||||
|
onState: firing
|
||||||
|
powerUseActive: 100
|
||||||
|
boltType: AnomalousParticleDelta
|
||||||
|
underpoweredState: underpowered
|
||||||
|
selectableTypes:
|
||||||
|
- AnomalousParticleDelta
|
||||||
|
- AnomalousParticleEpsilon
|
||||||
|
- AnomalousParticleZeta
|
||||||
|
fireBurstSize: 1
|
||||||
|
baseFireBurstDelayMin: 2
|
||||||
|
baseFireBurstDelayMax: 6
|
||||||
|
- type: ApcPowerReceiver
|
||||||
|
powerLoad: 100
|
||||||
|
- type: Gun
|
||||||
|
fireRate: 10 #just has to be fast enough to keep up with upgrades
|
||||||
|
showExamineText: false
|
||||||
|
selectedMode: SemiAuto
|
||||||
|
availableModes:
|
||||||
|
- SemiAuto
|
||||||
|
soundGunshot:
|
||||||
|
path: /Audio/Weapons/Guns/Gunshots/taser2.ogg
|
||||||
|
- type: Appearance
|
||||||
|
- type: WiresVisuals
|
||||||
|
- type: Wires
|
||||||
|
BoardName: "Ape"
|
||||||
|
LayoutId: Ape
|
||||||
|
- type: GenericVisualizer
|
||||||
|
visuals:
|
||||||
|
enum.PowerDeviceVisuals.Powered:
|
||||||
|
enum.PowerDeviceVisualLayers.Powered:
|
||||||
|
True: { visible: true }
|
||||||
|
False: { visible: false }
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: MachineAnomalyGenerator
|
||||||
|
parent: BaseMachinePowered
|
||||||
|
name: anomaly generator
|
||||||
|
description: The peak of psuedoscientific technology.
|
||||||
|
placement:
|
||||||
|
mode: AlignTileAny
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
netsync: false
|
||||||
|
sprite: Structures/Machines/Anomaly/anomaly_generator.rsi
|
||||||
|
snapCardinals: true
|
||||||
|
layers:
|
||||||
|
- state: base
|
||||||
|
- state: panel
|
||||||
|
map: ["enum.WiresVisualLayers.MaintenancePanel"]
|
||||||
|
visible: false
|
||||||
|
- state: unshaded
|
||||||
|
shader: unshaded
|
||||||
|
map: ["enum.PowerDeviceVisualLayers.Powered"]
|
||||||
|
- state: inserting
|
||||||
|
visible: false
|
||||||
|
map: ["enum.MaterialStorageVisualLayers.Inserting"]
|
||||||
|
- type: Transform
|
||||||
|
anchored: true
|
||||||
|
- type: ApcPowerReceiver
|
||||||
|
powerLoad: 1500
|
||||||
|
- type: ExtensionCableReceiver
|
||||||
|
- type: AmbientSound
|
||||||
|
range: 5
|
||||||
|
volume: -3
|
||||||
|
sound:
|
||||||
|
path: /Audio/Ambience/Objects/anomaly_generator.ogg
|
||||||
|
- type: Physics
|
||||||
|
bodyType: Static
|
||||||
|
- type: AnomalyGenerator
|
||||||
|
- type: MaterialStorage
|
||||||
|
whitelist:
|
||||||
|
tags:
|
||||||
|
- Sheet
|
||||||
|
materialWhiteList:
|
||||||
|
- Plasma
|
||||||
|
- type: Fixtures
|
||||||
|
fixtures:
|
||||||
|
- shape:
|
||||||
|
!type:PhysShapeAabb
|
||||||
|
bounds: "-1.3,-1.3,1.3,1.3"
|
||||||
|
density: 50
|
||||||
|
mask:
|
||||||
|
- LargeMobMask
|
||||||
|
layer:
|
||||||
|
- WallLayer
|
||||||
|
- type: Repairable
|
||||||
|
fuelCost: 10
|
||||||
|
doAfterDelay: 5
|
||||||
|
- type: Wires
|
||||||
|
BoardName: "AnomalyGenerator"
|
||||||
|
LayoutId: AnomalyGenerator
|
||||||
|
- type: Destructible
|
||||||
|
thresholds:
|
||||||
|
- trigger:
|
||||||
|
!type:DamageTrigger
|
||||||
|
damage: 500
|
||||||
|
behaviors:
|
||||||
|
- !type:DoActsBehavior
|
||||||
|
acts: ["Breakage"]
|
||||||
|
- type: ActivatableUI
|
||||||
|
key: enum.AnomalyGeneratorUiKey.Key
|
||||||
|
- type: ActivatableUIRequiresPower
|
||||||
|
- type: UserInterface
|
||||||
|
interfaces:
|
||||||
|
- key: enum.AnomalyGeneratorUiKey.Key
|
||||||
|
type: AnomalyGeneratorBoundUserInterface
|
||||||
|
- type: Appearance
|
||||||
|
- type: GenericVisualizer
|
||||||
|
visuals:
|
||||||
|
enum.PowerDeviceVisuals.Powered:
|
||||||
|
enum.PowerDeviceVisualLayers.Powered:
|
||||||
|
True: { visible: true }
|
||||||
|
False: { visible: false }
|
||||||
|
- type: WiresVisuals
|
||||||
|
- type: StaticPrice
|
||||||
|
price: 5000
|
||||||
@@ -169,6 +169,7 @@
|
|||||||
- MiningDrill
|
- MiningDrill
|
||||||
- ConveyorBeltAssembly
|
- ConveyorBeltAssembly
|
||||||
- AppraisalTool
|
- AppraisalTool
|
||||||
|
- AnomalyScanner
|
||||||
- RCD
|
- RCD
|
||||||
- RCDAmmo
|
- RCDAmmo
|
||||||
- HydroponicsToolScythe
|
- HydroponicsToolScythe
|
||||||
@@ -314,6 +315,8 @@
|
|||||||
- SeedExtractorMachineCircuitboard
|
- SeedExtractorMachineCircuitboard
|
||||||
- AnalysisComputerCircuitboard
|
- AnalysisComputerCircuitboard
|
||||||
- ExosuitFabricatorMachineCircuitboard
|
- ExosuitFabricatorMachineCircuitboard
|
||||||
|
- AnomalyVesselCircuitboard
|
||||||
|
- APECircuitboard
|
||||||
- ArtifactAnalyzerMachineCircuitboard
|
- ArtifactAnalyzerMachineCircuitboard
|
||||||
- TraversalDistorterMachineCircuitboard
|
- TraversalDistorterMachineCircuitboard
|
||||||
- BoozeDispenserMachineCircuitboard
|
- BoozeDispenserMachineCircuitboard
|
||||||
|
|||||||
104
Resources/Prototypes/Entities/Structures/Specific/anomalies.yml
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
- type: entity
|
||||||
|
abstract: true
|
||||||
|
id: BaseAnomaly
|
||||||
|
name: anomaly
|
||||||
|
description: A impossible object in space. Should you be standing this close to it?
|
||||||
|
components:
|
||||||
|
- type: Anomaly
|
||||||
|
pulseSound:
|
||||||
|
collection: RadiationPulse
|
||||||
|
params:
|
||||||
|
volume: 5
|
||||||
|
- type: AmbientSound
|
||||||
|
range: 5
|
||||||
|
volume: -5
|
||||||
|
sound:
|
||||||
|
path: /Audio/Ambience/anomaly_drone.ogg
|
||||||
|
- type: Transform
|
||||||
|
anchored: true
|
||||||
|
- type: Physics
|
||||||
|
bodyType: Static
|
||||||
|
- type: Fixtures
|
||||||
|
fixtures:
|
||||||
|
- shape:
|
||||||
|
!type:PhysShapeCircle
|
||||||
|
radius: 0.35
|
||||||
|
density: 50
|
||||||
|
mask:
|
||||||
|
- MobMask
|
||||||
|
layer:
|
||||||
|
- MobLayer
|
||||||
|
- type: Sprite
|
||||||
|
netsync: false
|
||||||
|
drawdepth: Items
|
||||||
|
sprite: Structures/Specific/anomaly.rsi
|
||||||
|
- type: InteractionOutline
|
||||||
|
- type: Clickable
|
||||||
|
- type: Damageable
|
||||||
|
damageContainer: Inorganic
|
||||||
|
damageModifierSet: Metallic
|
||||||
|
- type: Appearance
|
||||||
|
- type: EmitSoundOnSpawn
|
||||||
|
sound:
|
||||||
|
path: /Audio/Effects/teleport_arrival.ogg
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: AnomalyPyroclastic
|
||||||
|
parent: BaseAnomaly
|
||||||
|
suffix: Pyroclastic
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
layers:
|
||||||
|
- state: anom1
|
||||||
|
map: ["enum.AnomalyVisualLayers.Base"]
|
||||||
|
- state: anom1-pulse
|
||||||
|
map: ["enum.AnomalyVisualLayers.Animated"]
|
||||||
|
visible: false
|
||||||
|
- type: PointLight
|
||||||
|
radius: 2.0
|
||||||
|
energy: 7.5
|
||||||
|
color: "#fca3c0"
|
||||||
|
castShadows: false
|
||||||
|
- type: PyroclasticAnomaly
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: AnomalyGravity
|
||||||
|
parent: BaseAnomaly
|
||||||
|
suffix: Gravity
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
drawdepth: Effects #it needs to draw over stuff.
|
||||||
|
layers:
|
||||||
|
- state: anom2
|
||||||
|
map: ["enum.AnomalyVisualLayers.Base"]
|
||||||
|
- state: anom2-pulse
|
||||||
|
map: ["enum.AnomalyVisualLayers.Animated"]
|
||||||
|
visible: false
|
||||||
|
- type: PointLight
|
||||||
|
radius: 5.0
|
||||||
|
energy: 20
|
||||||
|
color: "#1e070e"
|
||||||
|
castShadows: false
|
||||||
|
- type: GravityAnomaly
|
||||||
|
- type: GravityWell
|
||||||
|
- type: RadiationSource
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: AnomalyElectricity
|
||||||
|
parent: BaseAnomaly
|
||||||
|
suffix: Electricity
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
layers:
|
||||||
|
- state: anom3
|
||||||
|
map: ["enum.AnomalyVisualLayers.Base"]
|
||||||
|
- state: anom3-pulse
|
||||||
|
map: ["enum.AnomalyVisualLayers.Animated"]
|
||||||
|
visible: false
|
||||||
|
- type: PointLight
|
||||||
|
radius: 2.0
|
||||||
|
energy: 5.0
|
||||||
|
color: "#ffffaa"
|
||||||
|
castShadows: false
|
||||||
|
- type: ElectricityAnomaly
|
||||||
|
- type: Electrified
|
||||||
@@ -246,12 +246,21 @@
|
|||||||
- type: entity
|
- type: entity
|
||||||
parent: BaseSign
|
parent: BaseSign
|
||||||
id: SignAnomaly
|
id: SignAnomaly
|
||||||
name: xeno-archeology lab sign
|
name: xenoarcheology lab sign
|
||||||
description: A sign indicating the xeno-archeology lab.
|
description: A sign indicating the xenoarcheology lab.
|
||||||
components:
|
components:
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
state: anomaly
|
state: anomaly
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: BaseSign
|
||||||
|
id: SignAnomaly2
|
||||||
|
name: anomaly lab sign
|
||||||
|
description: A sign indicating the anomalous research lab.
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
state: anomaly2
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: BaseSign
|
parent: BaseSign
|
||||||
id: SignAtmos
|
id: SignAtmos
|
||||||
|
|||||||
@@ -48,3 +48,11 @@
|
|||||||
Steel: 100
|
Steel: 100
|
||||||
Plastic: 200
|
Plastic: 200
|
||||||
Glass: 100
|
Glass: 100
|
||||||
|
|
||||||
|
- type: latheRecipe
|
||||||
|
id: AnomalyScanner
|
||||||
|
result: AnomalyScanner
|
||||||
|
completetime: 2
|
||||||
|
materials:
|
||||||
|
Plastic: 200
|
||||||
|
Glass: 150
|
||||||
@@ -202,6 +202,22 @@
|
|||||||
Glass: 900
|
Glass: 900
|
||||||
Gold: 100
|
Gold: 100
|
||||||
|
|
||||||
|
- type: latheRecipe
|
||||||
|
id: AnomalyVesselCircuitboard
|
||||||
|
result: AnomalyVesselCircuitboard
|
||||||
|
completetime: 4
|
||||||
|
materials:
|
||||||
|
Steel: 100
|
||||||
|
Glass: 900
|
||||||
|
|
||||||
|
- type: latheRecipe
|
||||||
|
id: APECircuitboard
|
||||||
|
result: APECircuitboard
|
||||||
|
completetime: 4
|
||||||
|
materials:
|
||||||
|
Steel: 100
|
||||||
|
Glass: 900
|
||||||
|
|
||||||
- type: latheRecipe
|
- type: latheRecipe
|
||||||
id: ReagentGrinderMachineCircuitboard
|
id: ReagentGrinderMachineCircuitboard
|
||||||
result: ReagentGrinderMachineCircuitboard
|
result: ReagentGrinderMachineCircuitboard
|
||||||
|
|||||||
|
After Width: | Height: | Size: 942 B |
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"license": "CC0-1.0",
|
||||||
|
"copyright": "Created by EmoGarbage",
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "icon"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 609 B |
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"version":1,
|
||||||
|
"size":{
|
||||||
|
"x":96,
|
||||||
|
"y":96
|
||||||
|
},
|
||||||
|
"license":"CC0-1.0",
|
||||||
|
"copyright":"Created by EmoGarbage",
|
||||||
|
"states":[
|
||||||
|
{
|
||||||
|
"name":"base"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "inserting",
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "panel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "unshaded",
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
1.0,
|
||||||
|
0.25,
|
||||||
|
0.25,
|
||||||
|
0.25,
|
||||||
|
0.25,
|
||||||
|
0.25,
|
||||||
|
0.25,
|
||||||
|
0.5
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 347 B |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 283 B |
|
After Width: | Height: | Size: 615 B |
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC0-1.0",
|
||||||
|
"copyright": "Created by EmoGarbage404",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "anomaly"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "base"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "panel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "powered"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 228 B |
|
After Width: | Height: | Size: 133 B |
BIN
Resources/Textures/Structures/Machines/Anomaly/ape.rsi/base.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 306 B |
|
After Width: | Height: | Size: 142 B |
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC0-1.0",
|
||||||
|
"copyright": "Created by EmoGarbage404",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "base",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "firing",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "locked",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "panel",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "underpowered",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "unshaded",
|
||||||
|
"directions": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
Resources/Textures/Structures/Machines/Anomaly/ape.rsi/panel.png
Normal file
|
After Width: | Height: | Size: 289 B |
|
After Width: | Height: | Size: 172 B |
|
After Width: | Height: | Size: 283 B |
|
After Width: | Height: | Size: 2.6 KiB |
BIN
Resources/Textures/Structures/Specific/anomaly.rsi/anom1.png
Normal file
|
After Width: | Height: | Size: 663 B |
|
After Width: | Height: | Size: 2.2 KiB |
BIN
Resources/Textures/Structures/Specific/anomaly.rsi/anom2.png
Normal file
|
After Width: | Height: | Size: 379 B |
|
After Width: | Height: | Size: 4.1 KiB |
BIN
Resources/Textures/Structures/Specific/anomaly.rsi/anom3.png
Normal file
|
After Width: | Height: | Size: 658 B |
|
After Width: | Height: | Size: 1.7 KiB |
BIN
Resources/Textures/Structures/Specific/anomaly.rsi/anom4.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
97
Resources/Textures/Structures/Specific/anomaly.rsi/meta.json
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC0-1.0",
|
||||||
|
"copyright": "Created by EmoGarbage; anom3, anom3-pulse, anom4, anom4-pulse are CC-BY-SA-3.0 at https://github.com/ParadiseSS13/Paradise/blob/master/icons/effects/effects.dmi",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "anom1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anom1-pulse",
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.15625,
|
||||||
|
0.15625,
|
||||||
|
0.15625,
|
||||||
|
0.15625,
|
||||||
|
0.15625,
|
||||||
|
0.15625,
|
||||||
|
0.15625,
|
||||||
|
0.15625
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anom2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anom2-pulse",
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.15625,
|
||||||
|
0.15625,
|
||||||
|
0.15625,
|
||||||
|
0.15625,
|
||||||
|
0.15625,
|
||||||
|
0.15625,
|
||||||
|
0.15625,
|
||||||
|
0.15625
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anom3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anom3-pulse",
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anom4",
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.3,
|
||||||
|
0.3,
|
||||||
|
0.3,
|
||||||
|
0.3,
|
||||||
|
0.3,
|
||||||
|
0.3,
|
||||||
|
0.3
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "anom4-pulse",
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.15,
|
||||||
|
0.15,
|
||||||
|
0.15,
|
||||||
|
0.15,
|
||||||
|
0.15,
|
||||||
|
0.15,
|
||||||
|
0.15
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
Resources/Textures/Structures/Wallmounts/signs.rsi/anomaly2.png
Normal file
|
After Width: | Height: | Size: 454 B |
@@ -23,6 +23,9 @@
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "anomaly2"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "armory",
|
"name": "armory",
|
||||||
"delays": [
|
"delays": [
|
||||||
|
|||||||